跳到主要内容Java 后端 Web API 开发全流程实战指南 | 极客日志Javajava
Java 后端 Web API 开发全流程实战指南
Java 后端 Web API 开发涉及环境搭建、分层架构设计、数据模型与业务逻辑实现、安全认证及部署监控等关键环节。通过 Spring Boot 框架演示了从 POM 配置到 RESTful 接口编写的完整流程,涵盖 JPA 数据访问、JWT 身份验证、缓存异步处理及单元测试实践。重点讲解了控制器层异常统一处理、DTO 对象转换以及 Docker 容器化部署方案,帮助开发者构建高可用、可维护的后端服务系统。
剑仙2 浏览 Java 后端 Web API 开发全流程实战指南

Web API 开发基础
Web API(Application Programming Interface)是不同软件系统间通信的桥梁。在 Web 开发中,我们通常基于 HTTP 协议,采用 RESTful 架构风格,通过 URL 端点提供数据和服务。
核心特点包括:
- 基于 HTTP/HTTPS 协议
- 返回结构化数据(JSON/XML)
- 无状态通信
- 跨平台兼容
REST(Representational State Transfer)作为主流架构风格,遵循以下原则:统一接口、无状态、可缓存、分层系统以及按需代码。这意味着每个请求都应包含必要信息,且客户端无需关心服务器内部结构。
环境搭建与项目初始化
必需工具
确保本地已安装 JDK 8 或以上版本、IDE(推荐 IntelliJ IDEA)、Maven 3.6+ 或 Gradle,以及 MySQL 或 PostgreSQL 数据库。
创建 Spring Boot 项目
借助 Spring Initializr 可以快速搭建骨架。我们需要在 pom.xml 中引入核心依赖,包括 Web 启动器、JPA 数据访问、验证框架以及 MySQL 驱动。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/>
</parent>
<>com.example
webapi-demo
1.0.0
11
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-validation
mysql
mysql-connector-java
org.springframework.boot
spring-boot-starter-test
test
groupId
</groupId>
<artifactId>
</artifactId>
<version>
</version>
<properties>
<java.version>
</java.version>
</properties>
<dependencies>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<scope>
</scope>
</dependency>
</dependencies>
</project>
配置文件
接下来配置 application.yml,指定服务端口、上下文路径以及数据库连接信息。开启 SQL 日志有助于调试。
server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
url: jdbc:mysql://localhost:3306/webapi_db
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
format_sql: true
logging:
level:
com.example: DEBUG
org.hibernate.SQL: DEBUG
项目架构设计
典型的 Java Web API 采用分层架构,从上至下依次为 Controller 层、Service 层、Repository 层和 Model 层。这种结构能保持代码清晰,便于维护。
src/main/java/com/example/webapi/
├── config/
├── controller/
├── service/
├── repository/
├── model/
│ ├── entity/
│ ├── dto/
│ └── vo/
├── exception/
└── util/
数据模型设计
实体类设计
定义用户实体时,需要添加 JPA 注解和校验约束。注意字段命名规范及唯一性约束。
package com.example.webapi.model.entity;
import javax.persistence.*;
import javax.validation.constraints.*;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度必须在 3-50 字符之间")
@Column(unique = true, nullable = false)
private String username;
@Email(message = "邮箱格式不正确")
@Column(unique = true, nullable = false)
private String email;
@NotBlank(message = "密码不能为空")
@Size(min = 6, message = "密码长度至少 6 位")
private String password;
private String phone;
@Enumerated(EnumType.STRING)
private UserStatus status = UserStatus.ACTIVE;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
public User() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
}
enum UserStatus { ACTIVE, INACTIVE, DELETED }
DTO 设计
为了安全起见,Controller 层不应直接暴露 Entity。我们使用 DTO(Data Transfer Object)进行数据传输,并定义专门的请求对象。
package com.example.webapi.model.dto;
import javax.validation.constraints.*;
import java.time.LocalDateTime;
public class UserDTO {
private Long id;
@NotBlank(message = "用户名不能为空")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
private String phone;
private LocalDateTime createdAt;
public UserDTO() {}
}
package com.example.webapi.model.dto;
import javax.validation.constraints.*;
public class CreateUserRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50)
private String username;
@Email
@NotBlank
private String email;
@NotBlank
@Size(min = 6)
private String password;
private String phone;
}
数据访问层实现
Repository 接口
Spring Data JPA 允许我们通过接口方法名自动生成查询语句。对于复杂查询,可以使用 @Query 注解。
package com.example.webapi.repository;
import com.example.webapi.model.entity.User;
import com.example.webapi.model.entity.UserStatus;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
List<User> findByStatus(UserStatus status);
boolean existsByUsername(String username);
boolean existsByEmail(String email);
@Query("SELECT u FROM User u WHERE u.email LIKE %:email%")
List<User> findByEmailContaining(@Param("email") String email);
@Query("SELECT u FROM User u WHERE u.createdAt >= :startDate AND u.createdAt < :endDate")
List<User> findUsersByCreateTimeRange(@Param("startDate") LocalDateTime startDate,
@Param("endDate") LocalDateTime endDate);
}
自定义 Repository 实现
当标准接口无法满足需求时,可以自定义分页查询逻辑。这里演示了如何手动构建 Count 查询和主查询。
package com.example.webapi.repository;
import com.example.webapi.model.entity.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.List;
public interface UserRepositoryCustom {
Page<User> findUsersWithPagination(String keyword, Pageable pageable);
List<User> findActiveUsersWithRecentActivity();
}
package com.example.webapi.repository;
import com.example.webapi.model.entity.User;
import com.example.webapi.model.entity.UserStatus;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import java.time.LocalDateTime;
import java.util.List;
@Repository
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
@PersistenceContext
private EntityManager entityManager;
@Override
public Page<User> findUsersWithPagination(String keyword, Pageable pageable) {
String countQueryStr = "SELECT COUNT(u) FROM User u WHERE " +
"(u.username LIKE :keyword OR u.email LIKE :keyword) AND u.status = 'ACTIVE'";
TypedQuery<Long> countQuery = entityManager.createQuery(countQueryStr, Long.class);
countQuery.setParameter("keyword", "%" + keyword + "%");
Long total = countQuery.getSingleResult();
String queryStr = "SELECT u FROM User u WHERE " +
"(u.username LIKE :keyword OR u.email LIKE :keyword) AND u.status = 'ACTIVE' " +
"ORDER BY u.createdAt DESC";
TypedQuery<User> query = entityManager.createQuery(queryStr, User.class);
query.setParameter("keyword", "%" + keyword + "%");
query.setFirstResult((int) pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
List<User> users = query.getResultList();
return new PageImpl<>(users, pageable, total);
}
@Override
public List<User> findActiveUsersWithRecentActivity() {
String queryStr = "SELECT u FROM User u WHERE u.status = 'ACTIVE' " +
"AND u.updatedAt >= :recentTime";
return entityManager.createQuery(queryStr, User.class)
.setParameter("recentTime", LocalDateTime.now().minusDays(7))
.getResultList();
}
}
业务逻辑层实现
Service 接口设计
Service 层负责编排业务逻辑。接口定义应清晰表达输入输出,支持分页和搜索功能。
package com.example.webapi.service;
import com.example.webapi.model.dto.CreateUserRequest;
import com.example.webapi.model.dto.UpdateUserRequest;
import com.example.webapi.model.dto.UserDTO;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.List;
public interface UserService {
UserDTO createUser(CreateUserRequest request);
UserDTO getUserById(Long id);
UserDTO getUserByUsername(String username);
Page<UserDTO> getAllUsers(Pageable pageable);
List<UserDTO> searchUsers(String keyword);
UserDTO updateUser(Long id, UpdateUserRequest request);
void deleteUser(Long id);
boolean existsByUsername(String username);
boolean existsByEmail(String email);
}
Service 实现类
在实现类中,我们处理事务、密码加密以及 DTO 转换。注意检查重复数据,避免脏写。
package com.example.webapi.service.impl;
import com.example.webapi.model.dto.CreateUserRequest;
import com.example.webapi.model.dto.UpdateUserRequest;
import com.example.webapi.model.dto.UserDTO;
import com.example.webapi.model.entity.User;
import com.example.webapi.model.entity.UserStatus;
import com.example.webapi.repository.UserRepository;
import com.example.webapi.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDTO createUser(CreateUserRequest request) {
if (userRepository.existsByUsername(request.getUsername())) {
throw new RuntimeException("用户名已存在");
}
if (userRepository.existsByEmail(request.getEmail())) {
throw new RuntimeException("邮箱已存在");
}
User user = new User();
user.setUsername(request.getUsername());
user.setEmail(request.getEmail());
user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setPhone(request.getPhone());
user.setStatus(UserStatus.ACTIVE);
User savedUser = userRepository.save(user);
return convertToDTO(savedUser);
}
@Override
@Transactional(readOnly = true)
public UserDTO getUserById(Long id) {
User user = userRepository.findById(id).orElseThrow(() -> new RuntimeException("用户不存在"));
return convertToDTO(user);
}
@Override
@Transactional(readOnly = true)
public UserDTO getUserByUsername(String username) {
User user = userRepository.findByUsername(username).orElseThrow(() -> new RuntimeException("用户不存在"));
return convertToDTO(user);
}
@Override
@Transactional(readOnly = true)
public Page<UserDTO> getAllUsers(Pageable pageable) {
return userRepository.findAll(pageable).map(this::convertToDTO);
}
@Override
@Transactional(readOnly = true)
public List<UserDTO> searchUsers(String keyword) {
return userRepository.findByEmailContaining(keyword).stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
@Override
public UserDTO updateUser(Long id, UpdateUserRequest request) {
User user = userRepository.findById(id).orElseThrow(() -> new RuntimeException("用户不存在"));
if (request.getEmail() != null && !request.getEmail().equals(user.getEmail())) {
if (userRepository.existsByEmail(request.getEmail())) {
throw new RuntimeException("邮箱已存在");
}
user.setEmail(request.getEmail());
}
if (request.getPhone() != null) {
user.setPhone(request.getPhone());
}
User updatedUser = userRepository.save(user);
return convertToDTO(updatedUser);
}
@Override
public void deleteUser(Long id) {
User user = userRepository.findById(id).orElseThrow(() -> new RuntimeException("用户不存在"));
user.setStatus(UserStatus.DELETED);
userRepository.save(user);
}
@Override
@Transactional(readOnly = true)
public boolean existsByUsername(String username) {
return userRepository.existsByUsername(username);
}
@Override
@Transactional(readOnly = true)
public boolean existsByEmail(String email) {
return userRepository.existsByEmail(email);
}
private UserDTO convertToDTO(User user) {
UserDTO dto = new UserDTO();
dto.setId(user.getId());
dto.setUsername(user.getUsername());
dto.setEmail(user.getEmail());
dto.setPhone(user.getPhone());
dto.setCreatedAt(user.getCreatedAt());
return dto;
}
}
控制器层实现
基础控制器
Controller 层负责接收请求并返回响应。我们使用 @RestController 和 @RequestMapping 定义端点,并通过 ResponseEntity 控制状态码。
package com.example.webapi.controller;
import com.example.webapi.model.dto.CreateUserRequest;
import com.example.webapi.model.dto.UpdateUserRequest;
import com.example.webapi.model.dto.UserDTO;
import com.example.webapi.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/users")
@Validated
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public ResponseEntity<?> createUser(@Valid @RequestBody CreateUserRequest request) {
try {
UserDTO user = userService.createUser(request);
return ResponseEntity.status(HttpStatus.CREATED).body(createSuccessResponse("用户创建成功", user));
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body(createErrorResponse(e.getMessage()));
}
}
@GetMapping("/{id}")
public ResponseEntity<?> getUserById(@PathVariable Long id) {
try {
UserDTO user = userService.getUserById(id);
return ResponseEntity.ok(createSuccessResponse("获取用户成功", user));
} catch (RuntimeException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(createErrorResponse(e.getMessage()));
}
}
@GetMapping
public ResponseEntity<?> getAllUsers(@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "createdAt") String sort) {
Pageable pageable = PageRequest.of(page, size, Sort.by(sort).descending());
Page<UserDTO> users = userService.getAllUsers(pageable);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "获取用户列表成功");
response.put("data", users.getContent());
response.put("currentPage", users.getNumber());
response.put("totalItems", users.getTotalElements());
response.put("totalPages", users.getTotalPages());
return ResponseEntity.ok(response);
}
@GetMapping("/search")
public ResponseEntity<?> searchUsers(@RequestParam String keyword) {
List<UserDTO> users = userService.searchUsers(keyword);
return ResponseEntity.ok(createSuccessResponse("搜索用户成功", users));
}
@PutMapping("/{id}")
public ResponseEntity<?> updateUser(@PathVariable Long id, @Valid @RequestBody UpdateUserRequest request) {
try {
UserDTO user = userService.updateUser(id, request);
return ResponseEntity.ok(createSuccessResponse("用户更新成功", user));
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body(createErrorResponse(e.getMessage()));
}
}
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteUser(@PathVariable Long id) {
try {
userService.deleteUser(id);
return ResponseEntity.ok(createSuccessResponse("用户删除成功", null));
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body(createErrorResponse(e.getMessage()));
}
}
private Map<String, Object> createSuccessResponse(String message, Object data) {
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", message);
response.put("data", data);
response.put("timestamp", System.currentTimeMillis());
return response;
}
private Map<String, Object> createErrorResponse(String message) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", message);
response.put("timestamp", System.currentTimeMillis());
return response;
}
}
全局异常处理
为了避免在每个 Controller 中重复编写错误处理逻辑,我们可以使用 @RestControllerAdvice 集中捕获异常,统一返回格式。
package com.example.webapi.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleValidationExceptions(MethodArgumentNotValidException ex, HttpServletRequest request) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "参数验证失败");
response.put("errors", errors);
response.put("path", request.getRequestURI());
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<?> handleRuntimeException(RuntimeException ex, HttpServletRequest request) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", ex.getMessage());
response.put("path", request.getRequestURI());
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleGlobalException(Exception ex, HttpServletRequest request) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "服务器内部错误");
response.put("path", request.getRequestURI());
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
安全配置
Spring Security 配置
安全是 Web API 的重中之重。我们禁用 CSRF(针对纯 API),设置会话为无状态,并配置白名单。
package com.example.webapi.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/users/create").permitAll()
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
JWT 认证配置
JWT(JSON Web Token)适合无状态认证。工具类负责生成令牌和解析用户信息。
package com.example.webapi.util;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class JwtUtils {
@Value("${app.jwt.secret}")
private String jwtSecret;
@Value("${app.jwt.expiration}")
private int jwtExpirationMs;
public String generateJwtToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public String getUserNameFromJwtToken(String token) {
return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
}
public boolean validateJwtToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
} catch (MalformedJwtException e) {
} catch (ExpiredJwtException e) {
} catch (UnsupportedJwtException e) {
} catch (IllegalArgumentException e) {
}
return false;
}
}
高级特性实现
缓存配置
对于读多写少的场景,引入缓存能显著提升性能。Spring Cache 提供了简单的注解支持。
package com.example.webapi.config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
cacheManager.setCacheNames(Arrays.asList("users", "products"));
return cacheManager;
}
}
在 Service 中使用缓存非常直观,只需添加 @Cacheable 和 @CacheEvict。
@Service
public class UserServiceImpl implements UserService {
@Cacheable(value = "users", key = "#id")
@Override
public UserDTO getUserById(Long id) {
}
@CacheEvict(value = "users", key = "#id")
@Override
public UserDTO updateUser(Long id, UpdateUserRequest request) {
}
}
异步处理
耗时操作如发送邮件应异步执行,避免阻塞主线程。配置线程池并启用 @Async 即可。
package com.example.webapi.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("AsyncThread-");
executor.initialize();
return executor;
}
}
package com.example.webapi.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class EmailService {
@Async("taskExecutor")
public void sendWelcomeEmail(String email, String username) {
try {
Thread.sleep(5000);
System.out.println("欢迎邮件已发送至:" + email);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
测试
单元测试
使用 Mockito 对 Service 层进行隔离测试,重点验证业务逻辑而非数据库交互。
package com.example.webapi.service;
import com.example.webapi.model.dto.CreateUserRequest;
import com.example.webapi.model.dto.UserDTO;
import com.example.webapi.model.entity.User;
import com.example.webapi.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private PasswordEncoder passwordEncoder;
@InjectMocks
private UserServiceImpl userService;
private CreateUserRequest createUserRequest;
@BeforeEach
void setUp() {
createUserRequest = new CreateUserRequest();
createUserRequest.setUsername("testuser");
createUserRequest.setEmail("[email protected]");
createUserRequest.setPassword("password123");
createUserRequest.setPhone("13800138000");
}
@Test
void createUser_Success() {
when(userRepository.existsByUsername("testuser")).thenReturn(false);
when(userRepository.existsByEmail("[email protected]")).thenReturn(false);
when(passwordEncoder.encode("password123")).thenReturn("encodedPassword");
User savedUser = new User();
savedUser.setId(1L);
savedUser.setUsername("testuser");
savedUser.setEmail("[email protected]");
when(userRepository.save(any(User.class))).thenReturn(savedUser);
UserDTO result = userService.createUser(createUserRequest);
assertNotNull(result);
assertEquals(1L, result.getId());
assertEquals("testuser", result.getUsername());
verify(userRepository, times(1)).save(any(User.class));
}
@Test
void getUserById_UserExists() {
User user = new User();
user.setId(1L);
user.setUsername("testuser");
user.setEmail("[email protected]");
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
UserDTO result = userService.getUserById(1L);
assertNotNull(result);
assertEquals(1L, result.getId());
assertEquals("testuser", result.getUsername());
}
}
集成测试
集成测试关注 Controller 与整个上下文的交互,使用 MockMvc 模拟 HTTP 请求。
package com.example.webapi.controller;
import com.example.webapi.model.dto.CreateUserRequest;
import com.example.webapi.repository.UserRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class UserControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private UserRepository userRepository;
@Test
void createUser_ValidRequest_ReturnsCreated() throws Exception {
CreateUserRequest request = new CreateUserRequest();
request.setUsername("integrationtest");
request.setEmail("[email protected]");
request.setPassword("password123");
mockMvc.perform(post("/api/users").contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.data.username").value("integrationtest"));
}
@Test
void getUserById_UserExists_ReturnsUser() throws Exception {
CreateUserRequest request = new CreateUserRequest();
request.setUsername("testuser");
request.setEmail("[email protected]");
request.setPassword("password123");
String response = mockMvc.perform(post("/api/users").contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andReturn().getResponse().getContentAsString();
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true));
}
}
部署与监控
Docker 配置
容器化部署能保证环境一致性。编写 Dockerfile 和 docker-compose.yml 即可一键拉起服务。
FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/webapi-demo-1.0.0.jar app.jar
RUN sh -c 'touch /app.jar'
ENV EXPOSE 8080
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]
version: '3.8'
services:
webapi:
build: .
ports:
- "8080:8080"
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/webapi_db
- SPRING_DATASOURCE_USERNAME=root
- SPRING_DATASOURCE_PASSWORD=password
depends_on:
- mysql
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=webapi_db
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
健康检查与监控
生产环境中,健康检查接口必不可少。它能让负载均衡器快速剔除故障节点。
package com.example.webapi.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/health")
public class HealthCheckController {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private HealthEndpoint healthEndpoint;
@GetMapping
public Map<String, Object> healthCheck() {
Map<String, Object> health = new HashMap<>();
try {
jdbcTemplate.execute("SELECT 1");
health.put("database", "UP");
} catch (Exception e) {
health.put("database", "DOWN");
}
health.put("status", healthEndpoint.health().getStatus().getCode());
health.put("timestamp", System.currentTimeMillis());
return health;
}
}
最佳实践与总结
API 设计最佳实践
- 使用合适的 HTTP 状态码:200 表示成功,201 表示创建,400 客户端错误,401 未授权,404 资源不存在,500 服务器错误。
- 统一的响应格式:建议统一包裹
success, message, data, timestamp 字段。
- 版本控制:URL 路径版本 (
/api/v1/users) 或请求头版本。
- 分页和过滤:明确约定分页参数 (
page, size) 和排序规则。
性能优化建议
- 数据库优化:合理使用索引,避免 N+1 查询问题,必要时使用连接查询。
- 缓存策略:热点数据放入 Redis,设置合理的过期时间。
- 异步处理:非核心流程(如通知、日志)走消息队列或异步任务。
安全考虑
- 输入验证:始终校验前端传入数据,防范 SQL 注入和 XSS。
- 认证授权:使用 JWT 进行无状态认证,实施基于角色的访问控制。
- 其他措施:强制 HTTPS,定期更新依赖库,配置安全响应头。
总结
通过本文的详细讲解,您应该已经掌握了 Java 后端 Web API 开发的全流程。从环境搭建、项目架构设计,到具体的编码实现和测试部署,我们覆盖了开发一个完整 Web API 项目所需的所有关键知识点。
- 采用分层架构,保持代码清晰和可维护性
- 使用 Spring Boot 快速开发,减少配置工作
- 实现完整的 CRUD 操作和业务逻辑
- 添加适当的异常处理和日志记录
- 编写全面的测试用例
- 考虑安全性和性能优化
在实际项目开发中,还需要根据具体需求不断调整和优化架构设计,同时关注代码质量、团队协作和持续集成等工程实践。希望本文能为您的 Java Web API 开发之旅提供有力的帮助!
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online