
文章目录
-
- 第一部分:Web API开发基础概念
-
- 1.1 什么是Web API
- 1.2 RESTful API设计原则
- 1.1 什么是Web API
- 第二部分:开发环境搭建
-
- 2.1 环境要求
- 2.2 创建Spring Boot项目
- 2.3 配置文件
- 2.1 环境要求
- 第三部分:项目架构设计
-
- 3.1 分层架构
- 3.2 包结构设计
- 3.1 分层架构
- 第四部分:数据模型设计
-
- 4.1 实体类设计
- 4.2 DTO设计
- 4.1 实体类设计
- 第五部分:数据访问层实现
-
- 5.1 Repository接口
- 5.2 自定义Repository实现
- 5.1 Repository接口
- 第六部分:业务逻辑层实现
-
- 6.1 Service接口设计
- 6.2 Service实现类
- 6.1 Service接口设计
- 第七部分:控制器层实现
-
- 7.1 基础控制器
- 7.2 全局异常处理
- 7.1 基础控制器
- 第八部分:安全配置
-
- 8.1 Spring Security配置
- 8.2 JWT认证配置
- 8.1 Spring Security配置
- 第九部分:高级特性实现
-
- 9.1 缓存配置
- 9.2 异步处理
- 9.1 缓存配置
- 第十部分:测试
-
- 10.1 单元测试
- 10.2 集成测试
- 10.1 单元测试
- 第十一部分:部署与监控
-
- 11.1 Docker配置
- 11.2 健康检查与监控
- 11.1 Docker配置
- 第十二部分:最佳实践与总结
-
- 12.1 API设计最佳实践
- 12.2 性能优化建议
- 12.3 安全考虑
- 12.4 总结
- 12.1 API设计最佳实践

第一部分:Web API开发基础概念
1.1 什么是Web API
Web API(Application Programming Interface)是一种允许不同软件系统之间进行通信的接口。在Web开发中,API通常基于HTTP协议,使用RESTful架构风格,通过URL端点提供数据和服务。
Web API的核心特点:
- 基于HTTP/HTTPS协议
- 返回结构化数据(JSON/XML)
- 无状态通信
- 跨平台兼容
1.2 RESTful API设计原则
REST(Representational State Transfer)是一种软件架构风格,包含以下核心原则:
- 统一接口:使用标准的HTTP方法和状态码
- 无状态:每个请求包含所有必要信息
- 可缓存:响应应标记为可缓存或不可缓存
- 分层系统:客户端不需要知道是否连接到最终服务器
- 按需代码:服务器可以临时扩展功能
第二部分:开发环境搭建
2.1 环境要求
必需工具:
- JDK 8或以上版本
- IDE(IntelliJ IDEA/Eclipse)
- Maven 3.6+ 或 Gradle
- MySQL/PostgreSQL数据库
2.2 创建Spring Boot项目
使用Spring Initializr创建项目:
1<!-- pom.xml --> 2<?xml version="1.0" encoding="UTF-8"?> 3<project xmlns="http://maven.apache.org/POM/4.0.0"> 4 <modelVersion>4.0.0</modelVersion> 5 6 <parent> 7 <groupId>org.springframework.boot</groupId> 8 <artifactId>spring-boot-starter-parent</artifactId> 9 <version>2.7.0</version> 10 <relativePath/> 11 </parent> 12 13 <groupId>com.example</groupId> 14 <artifactId>webapi-demo</artifactId> 15 <version>1.0.0</version> 16 17 <properties> 18 <java.version>11</java.version> 19 </properties> 20 21 <dependencies> 22 <dependency> 23 <groupId>org.springframework.boot</groupId> 24 <artifactId>spring-boot-starter-web</artifactId> 25 </dependency> 26 <dependency> 27 <groupId>org.springframework.boot</groupId> 28 <artifactId>spring-boot-starter-data-jpa</artifactId> 29 </dependency> 30 <dependency> 31 <groupId>org.springframework.boot</groupId> 32 <artifactId>spring-boot-starter-validation</artifactId> 33 </dependency> 34 <dependency> 35 <groupId>mysql</groupId> 36 <artifactId>mysql-connector-java</artifactId> 37 </dependency> 38 <dependency> 39 <groupId>org.springframework.boot</groupId> 40 <artifactId>spring-boot-starter-test</artifactId> 41 <scope>test</scope> 42 </dependency> 43 </dependencies> 44</project> 45
2.3 配置文件
1# application.yml 2server: 3 port: 8080 4 servlet: 5 context-path: /api 6 7spring: 8 datasource: 9 url: jdbc:mysql://localhost:3306/webapi_db 10 username: root 11 password: password 12 driver-class-name: com.mysql.cj.jdbc.Driver 13 14 jpa: 15 hibernate: 16 ddl-auto: update 17 show-sql: true 18 properties: 19 hibernate: 20 dialect: org.hibernate.dialect.MySQL8Dialect 21 format_sql: true 22 23logging: 24 level: 25 com.example: DEBUG 26 org.hibernate.SQL: DEBUG 27
第三部分:项目架构设计
3.1 分层架构
典型的Java Web API采用分层架构:
Controller层 (API接口)
↓
Service层 (业务逻辑)
↓
Repository层 (数据访问)
↓
Model层 (数据模型)
3.2 包结构设计
src/main/java/com/example/webapi/
├── config/ # 配置类
├── controller/ # 控制器
├── service/ # 业务逻辑
├── repository/ # 数据访问
├── model/ # 数据模型
│ ├── entity/ # 实体类
│ ├── dto/ # 数据传输对象
│ └── vo/ # 视图对象
├── exception/ # 异常处理
└── util/ # 工具类
第四部分:数据模型设计
4.1 实体类设计
1// User.java 2package com.example.webapi.model.entity; 3 4import javax.persistence.*; 5import javax.validation.constraints.*; 6import java.time.LocalDateTime; 7import java.util.List; 8 9@Entity 10@Table(name = "users") 11public class User { 12 @Id 13 @GeneratedValue(strategy = GenerationType.IDENTITY) 14 private Long id; 15 16 @NotBlank(message = "用户名不能为空") 17 @Size(min = 3, max = 50, message = "用户名长度必须在3-50字符之间") 18 @Column(unique = true, nullable = false) 19 private String username; 20 21 @Email(message = "邮箱格式不正确") 22 @Column(unique = true, nullable = false) 23 private String email; 24 25 @NotBlank(message = "密码不能为空") 26 @Size(min = 6, message = "密码长度至少6位") 27 private String password; 28 29 private String phone; 30 31 @Enumerated(EnumType.STRING) 32 private UserStatus status = UserStatus.ACTIVE; 33 34 @Column(name = "created_at") 35 private LocalDateTime createdAt; 36 37 @Column(name = "updated_at") 38 private LocalDateTime updatedAt; 39 40 // 构造方法 41 public User() { 42 this.createdAt = LocalDateTime.now(); 43 this.updatedAt = LocalDateTime.now(); 44 } 45 46 // Getter和Setter方法 47 // ... 省略具体实现 48} 49 50enum UserStatus { 51 ACTIVE, INACTIVE, DELETED 52} 53
4.2 DTO设计
1// UserDTO.java 2package com.example.webapi.model.dto; 3 4import javax.validation.constraints.*; 5import java.time.LocalDateTime; 6 7public class UserDTO { 8 private Long id; 9 10 @NotBlank(message = "用户名不能为空") 11 private String username; 12 13 @Email(message = "邮箱格式不正确") 14 private String email; 15 16 private String phone; 17 18 private LocalDateTime createdAt; 19 20 // 构造方法 21 public UserDTO() {} 22 23 // Getter和Setter 24 // ... 省略具体实现 25} 26 27// CreateUserRequest.java 28package com.example.webapi.model.dto; 29 30import javax.validation.constraints.*; 31 32public class CreateUserRequest { 33 @NotBlank(message = "用户名不能为空") 34 @Size(min = 3, max = 50) 35 private String username; 36 37 @Email 38 @NotBlank 39 private String email; 40 41 @NotBlank 42 @Size(min = 6) 43 private String password; 44 45 private String phone; 46 47 // Getter和Setter 48 // ... 省略具体实现 49} 50
第五部分:数据访问层实现
5.1 Repository接口
1// UserRepository.java 2package com.example.webapi.repository; 3 4import com.example.webapi.model.entity.User; 5import com.example.webapi.model.entity.UserStatus; 6import org.springframework.data.jpa.repository.JpaRepository; 7import org.springframework.data.jpa.repository.Query; 8import org.springframework.data.repository.query.Param; 9import org.springframework.stereotype.Repository; 10 11import java.util.List; 12import java.util.Optional; 13 14@Repository 15public interface UserRepository extends JpaRepository<User, Long> { 16 17 Optional<User> findByUsername(String username); 18 19 Optional<User> findByEmail(String email); 20 21 List<User> findByStatus(UserStatus status); 22 23 boolean existsByUsername(String username); 24 25 boolean existsByEmail(String email); 26 27 @Query("SELECT u FROM User u WHERE u.email LIKE %:email%") 28 List<User> findByEmailContaining(@Param("email") String email); 29 30 @Query("SELECT u FROM User u WHERE u.createdAt >= :startDate AND u.createdAt < :endDate") 31 List<User> findUsersByCreateTimeRange(@Param("startDate") LocalDateTime startDate, 32 @Param("endDate") LocalDateTime endDate); 33} 34
5.2 自定义Repository实现
1// UserRepositoryCustom.java 2package com.example.webapi.repository; 3 4import com.example.webapi.model.entity.User; 5import org.springframework.data.domain.Page; 6import org.springframework.data.domain.Pageable; 7 8import java.util.List; 9 10public interface UserRepositoryCustom { 11 Page<User> findUsersWithPagination(String keyword, Pageable pageable); 12 13 List<User> findActiveUsersWithRecentActivity(); 14} 15 16// UserRepositoryCustomImpl.java 17package com.example.webapi.repository; 18 19import com.example.webapi.model.entity.User; 20import com.example.webapi.model.entity.UserStatus; 21import org.springframework.data.domain.Page; 22import org.springframework.data.domain.PageImpl; 23import org.springframework.data.domain.Pageable; 24import org.springframework.stereotype.Repository; 25 26import javax.persistence.EntityManager; 27import javax.persistence.PersistenceContext; 28import javax.persistence.TypedQuery; 29import java.time.LocalDateTime; 30import java.util.List; 31 32@Repository 33public class UserRepositoryCustomImpl implements UserRepositoryCustom { 34 35 @PersistenceContext 36 private EntityManager entityManager; 37 38 @Override 39 public Page<User> findUsersWithPagination(String keyword, Pageable pageable) { 40 String countQueryStr = "SELECT COUNT(u) FROM User u WHERE " + 41 "(u.username LIKE :keyword OR u.email LIKE :keyword) AND u.status = 'ACTIVE'"; 42 43 TypedQuery<Long> countQuery = entityManager.createQuery(countQueryStr, Long.class); 44 countQuery.setParameter("keyword", "%" + keyword + "%"); 45 46 Long total = countQuery.getSingleResult(); 47 48 String queryStr = "SELECT u FROM User u WHERE " + 49 "(u.username LIKE :keyword OR u.email LIKE :keyword) AND u.status = 'ACTIVE' " + 50 "ORDER BY u.createdAt DESC"; 51 52 TypedQuery<User> query = entityManager.createQuery(queryStr, User.class); 53 query.setParameter("keyword", "%" + keyword + "%"); 54 query.setFirstResult((int) pageable.getOffset()); 55 query.setMaxResults(pageable.getPageSize()); 56 57 List<User> users = query.getResultList(); 58 59 return new PageImpl<>(users, pageable, total); 60 } 61 62 @Override 63 public List<User> findActiveUsersWithRecentActivity() { 64 String queryStr = "SELECT u FROM User u WHERE u.status = 'ACTIVE' " + 65 "AND u.updatedAt >= :recentTime"; 66 67 return entityManager.createQuery(queryStr, User.class) 68 .setParameter("recentTime", LocalDateTime.now().minusDays(7)) 69 .getResultList(); 70 } 71} 72
第六部分:业务逻辑层实现
6.1 Service接口设计
1// UserService.java 2package com.example.webapi.service; 3 4import com.example.webapi.model.dto.CreateUserRequest; 5import com.example.webapi.model.dto.UpdateUserRequest; 6import com.example.webapi.model.dto.UserDTO; 7import org.springframework.data.domain.Page; 8import org.springframework.data.domain.Pageable; 9 10import java.util.List; 11 12public interface UserService { 13 14 UserDTO createUser(CreateUserRequest request); 15 16 UserDTO getUserById(Long id); 17 18 UserDTO getUserByUsername(String username); 19 20 Page<UserDTO> getAllUsers(Pageable pageable); 21 22 List<UserDTO> searchUsers(String keyword); 23 24 UserDTO updateUser(Long id, UpdateUserRequest request); 25 26 void deleteUser(Long id); 27 28 boolean existsByUsername(String username); 29 30 boolean existsByEmail(String email); 31} 32
6.2 Service实现类
1// UserServiceImpl.java 2package com.example.webapi.service.impl; 3 4import com.example.webapi.model.dto.CreateUserRequest; 5import com.example.webapi.model.dto.UpdateUserRequest; 6import com.example.webapi.model.dto.UserDTO; 7import com.example.webapi.model.entity.User; 8import com.example.webapi.model.entity.UserStatus; 9import com.example.webapi.repository.UserRepository; 10import com.example.webapi.service.UserService; 11import org.springframework.beans.factory.annotation.Autowired; 12import org.springframework.data.domain.Page; 13import org.springframework.data.domain.Pageable; 14import org.springframework.security.crypto.password.PasswordEncoder; 15import org.springframework.stereotype.Service; 16import org.springframework.transaction.annotation.Transactional; 17 18import java.util.List; 19import java.util.stream.Collectors; 20 21@Service 22@Transactional 23public class UserServiceImpl implements UserService { 24 25 @Autowired 26 private UserRepository userRepository; 27 28 @Autowired 29 private PasswordEncoder passwordEncoder; 30 31 @Override 32 public UserDTO createUser(CreateUserRequest request) { 33 // 检查用户名和邮箱是否已存在 34 if (userRepository.existsByUsername(request.getUsername())) { 35 throw new RuntimeException("用户名已存在"); 36 } 37 38 if (userRepository.existsByEmail(request.getEmail())) { 39 throw new RuntimeException("邮箱已存在"); 40 } 41 42 // 创建用户实体 43 User user = new User(); 44 user.setUsername(request.getUsername()); 45 user.setEmail(request.getEmail()); 46 user.setPassword(passwordEncoder.encode(request.getPassword())); 47 user.setPhone(request.getPhone()); 48 user.setStatus(UserStatus.ACTIVE); 49 50 User savedUser = userRepository.save(user); 51 52 return convertToDTO(savedUser); 53 } 54 55 @Override 56 @Transactional(readOnly = true) 57 public UserDTO getUserById(Long id) { 58 User user = userRepository.findById(id) 59 .orElseThrow(() -> new RuntimeException("用户不存在")); 60 61 return convertToDTO(user); 62 } 63 64 @Override 65 @Transactional(readOnly = true) 66 public UserDTO getUserByUsername(String username) { 67 User user = userRepository.findByUsername(username) 68 .orElseThrow(() -> new RuntimeException("用户不存在")); 69 70 return convertToDTO(user); 71 } 72 73 @Override 74 @Transactional(readOnly = true) 75 public Page<UserDTO> getAllUsers(Pageable pageable) { 76 return userRepository.findAll(pageable) 77 .map(this::convertToDTO); 78 } 79 80 @Override 81 @Transactional(readOnly = true) 82 public List<UserDTO> searchUsers(String keyword) { 83 return userRepository.findByEmailContaining(keyword).stream() 84 .map(this::convertToDTO) 85 .collect(Collectors.toList()); 86 } 87 88 @Override 89 public UserDTO updateUser(Long id, UpdateUserRequest request) { 90 User user = userRepository.findById(id) 91 .orElseThrow(() -> new RuntimeException("用户不存在")); 92 93 // 更新用户信息 94 if (request.getEmail() != null && !request.getEmail().equals(user.getEmail())) { 95 if (userRepository.existsByEmail(request.getEmail())) { 96 throw new RuntimeException("邮箱已存在"); 97 } 98 user.setEmail(request.getEmail()); 99 } 100 101 if (request.getPhone() != null) { 102 user.setPhone(request.getPhone()); 103 } 104 105 User updatedUser = userRepository.save(user); 106 107 return convertToDTO(updatedUser); 108 } 109 110 @Override 111 public void deleteUser(Long id) { 112 User user = userRepository.findById(id) 113 .orElseThrow(() -> new RuntimeException("用户不存在")); 114 115 user.setStatus(UserStatus.DELETED); 116 userRepository.save(user); 117 } 118 119 @Override 120 @Transactional(readOnly = true) 121 public boolean existsByUsername(String username) { 122 return userRepository.existsByUsername(username); 123 } 124 125 @Override 126 @Transactional(readOnly = true) 127 public boolean existsByEmail(String email) { 128 return userRepository.existsByEmail(email); 129 } 130 131 // 转换实体为DTO 132 private UserDTO convertToDTO(User user) { 133 UserDTO dto = new UserDTO(); 134 dto.setId(user.getId()); 135 dto.setUsername(user.getUsername()); 136 dto.setEmail(user.getEmail()); 137 dto.setPhone(user.getPhone()); 138 dto.setCreatedAt(user.getCreatedAt()); 139 return dto; 140 } 141} 142
第七部分:控制器层实现
7.1 基础控制器
1// UserController.java 2package com.example.webapi.controller; 3 4import com.example.webapi.model.dto.CreateUserRequest; 5import com.example.webapi.model.dto.UpdateUserRequest; 6import com.example.webapi.model.dto.UserDTO; 7import com.example.webapi.service.UserService; 8import org.springframework.beans.factory.annotation.Autowired; 9import org.springframework.data.domain.Page; 10import org.springframework.data.domain.PageRequest; 11import org.springframework.data.domain.Pageable; 12import org.springframework.data.domain.Sort; 13import org.springframework.http.HttpStatus; 14import org.springframework.http.ResponseEntity; 15import org.springframework.validation.annotation.Validated; 16import org.springframework.web.bind.annotation.*; 17 18import javax.validation.Valid; 19import java.util.HashMap; 20import java.util.List; 21import java.util.Map; 22 23@RestController 24@RequestMapping("/users") 25@Validated 26public class UserController { 27 28 @Autowired 29 private UserService userService; 30 31 @PostMapping 32 public ResponseEntity<?> createUser(@Valid @RequestBody CreateUserRequest request) { 33 try { 34 UserDTO user = userService.createUser(request); 35 return ResponseEntity.status(HttpStatus.CREATED).body( 36 createSuccessResponse("用户创建成功", user) 37 ); 38 } catch (RuntimeException e) { 39 return ResponseEntity.badRequest().body( 40 createErrorResponse(e.getMessage()) 41 ); 42 } 43 } 44 45 @GetMapping("/{id}") 46 public ResponseEntity<?> getUserById(@PathVariable Long id) { 47 try { 48 UserDTO user = userService.getUserById(id); 49 return ResponseEntity.ok(createSuccessResponse("获取用户成功", user)); 50 } catch (RuntimeException e) { 51 return ResponseEntity.status(HttpStatus.NOT_FOUND).body( 52 createErrorResponse(e.getMessage()) 53 ); 54 } 55 } 56 57 @GetMapping 58 public ResponseEntity<?> getAllUsers( 59 @RequestParam(defaultValue = "0") int page, 60 @RequestParam(defaultValue = "10") int size, 61 @RequestParam(defaultValue = "createdAt") String sort) { 62 63 Pageable pageable = PageRequest.of(page, size, Sort.by(sort).descending()); 64 Page<UserDTO> users = userService.getAllUsers(pageable); 65 66 Map<String, Object> response = new HashMap<>(); 67 response.put("success", true); 68 response.put("message", "获取用户列表成功"); 69 response.put("data", users.getContent()); 70 response.put("currentPage", users.getNumber()); 71 response.put("totalItems", users.getTotalElements()); 72 response.put("totalPages", users.getTotalPages()); 73 74 return ResponseEntity.ok(response); 75 } 76 77 @GetMapping("/search") 78 public ResponseEntity<?> searchUsers(@RequestParam String keyword) { 79 List<UserDTO> users = userService.searchUsers(keyword); 80 81 return ResponseEntity.ok(createSuccessResponse("搜索用户成功", users)); 82 } 83 84 @PutMapping("/{id}") 85 public ResponseEntity<?> updateUser( 86 @PathVariable Long id, 87 @Valid @RequestBody UpdateUserRequest request) { 88 89 try { 90 UserDTO user = userService.updateUser(id, request); 91 return ResponseEntity.ok(createSuccessResponse("用户更新成功", user)); 92 } catch (RuntimeException e) { 93 return ResponseEntity.badRequest().body( 94 createErrorResponse(e.getMessage()) 95 ); 96 } 97 } 98 99 @DeleteMapping("/{id}") 100 public ResponseEntity<?> deleteUser(@PathVariable Long id) { 101 try { 102 userService.deleteUser(id); 103 return ResponseEntity.ok(createSuccessResponse("用户删除成功", null)); 104 } catch (RuntimeException e) { 105 return ResponseEntity.badRequest().body( 106 createErrorResponse(e.getMessage()) 107 ); 108 } 109 } 110 111 // 工具方法:创建成功响应 112 private Map<String, Object> createSuccessResponse(String message, Object data) { 113 Map<String, Object> response = new HashMap<>(); 114 response.put("success", true); 115 response.put("message", message); 116 response.put("data", data); 117 response.put("timestamp", System.currentTimeMillis()); 118 return response; 119 } 120 121 // 工具方法:创建错误响应 122 private Map<String, Object> createErrorResponse(String message) { 123 Map<String, Object> response = new HashMap<>(); 124 response.put("success", false); 125 response.put("message", message); 126 response.put("timestamp", System.currentTimeMillis()); 127 return response; 128 } 129} 130
7.2 全局异常处理
1// GlobalExceptionHandler.java 2package com.example.webapi.exception; 3 4import org.springframework.http.HttpStatus; 5import org.springframework.http.ResponseEntity; 6import org.springframework.validation.FieldError; 7import org.springframework.web.bind.MethodArgumentNotValidException; 8import org.springframework.web.bind.annotation.ExceptionHandler; 9import org.springframework.web.bind.annotation.RestControllerAdvice; 10 11import javax.servlet.http.HttpServletRequest; 12import java.util.HashMap; 13import java.util.Map; 14 15@RestControllerAdvice 16public class GlobalExceptionHandler { 17 18 @ExceptionHandler(MethodArgumentNotValidException.class) 19 public ResponseEntity<?> handleValidationExceptions( 20 MethodArgumentNotValidException ex, HttpServletRequest request) { 21 22 Map<String, String> errors = new HashMap<>(); 23 ex.getBindingResult().getAllErrors().forEach((error) -> { 24 String fieldName = ((FieldError) error).getField(); 25 String errorMessage = error.getDefaultMessage(); 26 errors.put(fieldName, errorMessage); 27 }); 28 29 Map<String, Object> response = new HashMap<>(); 30 response.put("success", false); 31 response.put("message", "参数验证失败"); 32 response.put("errors", errors); 33 response.put("path", request.getRequestURI()); 34 response.put("timestamp", System.currentTimeMillis()); 35 36 return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); 37 } 38 39 @ExceptionHandler(RuntimeException.class) 40 public ResponseEntity<?> handleRuntimeException( 41 RuntimeException ex, HttpServletRequest request) { 42 43 Map<String, Object> response = new HashMap<>(); 44 response.put("success", false); 45 response.put("message", ex.getMessage()); 46 response.put("path", request.getRequestURI()); 47 response.put("timestamp", System.currentTimeMillis()); 48 49 return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); 50 } 51 52 @ExceptionHandler(Exception.class) 53 public ResponseEntity<?> handleGlobalException( 54 Exception ex, HttpServletRequest request) { 55 56 Map<String, Object> response = new HashMap<>(); 57 response.put("success", false); 58 response.put("message", "服务器内部错误"); 59 response.put("path", request.getRequestURI()); 60 response.put("timestamp", System.currentTimeMillis()); 61 62 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); 63 } 64} 65
第八部分:安全配置
8.1 Spring Security配置
1// SecurityConfig.java 2package com.example.webapi.config; 3 4import org.springframework.beans.factory.annotation.Autowired; 5import org.springframework.context.annotation.Bean; 6import org.springframework.context.annotation.Configuration; 7import org.springframework.security.config.annotation.web.builders.HttpSecurity; 8import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 9import org.springframework.security.config.http.SessionCreationPolicy; 10import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 11import org.springframework.security.crypto.password.PasswordEncoder; 12import org.springframework.security.web.SecurityFilterChain; 13 14@Configuration 15@EnableWebSecurity 16public class SecurityConfig { 17 18 @Bean 19 public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 20 http 21 .cors().and() 22 .csrf().disable() 23 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 24 .and() 25 .authorizeRequests() 26 .antMatchers("/api/auth/**").permitAll() 27 .antMatchers("/api/users/create").permitAll() 28 .antMatchers("/api/public/**").permitAll() 29 .anyRequest().authenticated(); 30 31 return http.build(); 32 } 33 34 @Bean 35 public PasswordEncoder passwordEncoder() { 36 return new BCryptPasswordEncoder(); 37 } 38} 39
8.2 JWT认证配置
1// JwtUtils.java 2package com.example.webapi.util; 3 4import io.jsonwebtoken.*; 5import org.springframework.beans.factory.annotation.Value; 6import org.springframework.stereotype.Component; 7 8import java.util.Date; 9 10@Component 11public class JwtUtils { 12 13 @Value("${app.jwt.secret}") 14 private String jwtSecret; 15 16 @Value("${app.jwt.expiration}") 17 private int jwtExpirationMs; 18 19 public String generateJwtToken(String username) { 20 return Jwts.builder() 21 .setSubject(username) 22 .setIssuedAt(new Date()) 23 .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)) 24 .signWith(SignatureAlgorithm.HS512, jwtSecret) 25 .compact(); 26 } 27 28 public String getUserNameFromJwtToken(String token) { 29 return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject(); 30 } 31 32 public boolean validateJwtToken(String authToken) { 33 try { 34 Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken); 35 return true; 36 } catch (SignatureException e) { 37 // 日志记录 38 } catch (MalformedJwtException e) { 39 // 日志记录 40 } catch (ExpiredJwtException e) { 41 // 日志记录 42 } catch (UnsupportedJwtException e) { 43 // 日志记录 44 } catch (IllegalArgumentException e) { 45 // 日志记录 46 } 47 return false; 48 } 49} 50
第九部分:高级特性实现
9.1 缓存配置
1// CacheConfig.java 2package com.example.webapi.config; 3 4import org.springframework.cache.CacheManager; 5import org.springframework.cache.annotation.EnableCaching; 6import org.springframework.cache.concurrent.ConcurrentMapCacheManager; 7import org.springframework.context.annotation.Bean; 8import org.springframework.context.annotation.Configuration; 9 10import java.util.Arrays; 11 12@Configuration 13@EnableCaching 14public class CacheConfig { 15 16 @Bean 17 public CacheManager cacheManager() { 18 ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager(); 19 cacheManager.setCacheNames(Arrays.asList("users", "products")); 20 return cacheManager; 21 } 22} 23 24// 在Service中使用缓存 25@Service 26public class UserServiceImpl implements UserService { 27 28 @Cacheable(value = "users", key = "#id") 29 @Override 30 public UserDTO getUserById(Long id) { 31 // 从数据库获取用户 32 } 33 34 @CacheEvict(value = "users", key = "#id") 35 @Override 36 public UserDTO updateUser(Long id, UpdateUserRequest request) { 37 // 更新用户 38 } 39} 40
9.2 异步处理
1// AsyncConfig.java 2package com.example.webapi.config; 3 4import org.springframework.context.annotation.Configuration; 5import org.springframework.scheduling.annotation.EnableAsync; 6import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 7 8import java.util.concurrent.Executor; 9 10@Configuration 11@EnableAsync 12public class AsyncConfig { 13 14 @Bean(name = "taskExecutor") 15 public Executor taskExecutor() { 16 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 17 executor.setCorePoolSize(5); 18 executor.setMaxPoolSize(10); 19 executor.setQueueCapacity(100); 20 executor.setThreadNamePrefix("AsyncThread-"); 21 executor.initialize(); 22 return executor; 23 } 24} 25 26// 异步服务 27@Service 28public class EmailService { 29 30 @Async("taskExecutor") 31 public void sendWelcomeEmail(String email, String username) { 32 // 发送邮件的逻辑 33 try { 34 Thread.sleep(5000); // 模拟耗时操作 35 System.out.println("欢迎邮件已发送至: " + email); 36 } catch (InterruptedException e) { 37 Thread.currentThread().interrupt(); 38 } 39 } 40} 41
第十部分:测试
10.1 单元测试
1// UserServiceTest.java 2package com.example.webapi.service; 3 4import com.example.webapi.model.dto.CreateUserRequest; 5import com.example.webapi.model.dto.UserDTO; 6import com.example.webapi.model.entity.User; 7import com.example.webapi.repository.UserRepository; 8import org.junit.jupiter.api.BeforeEach; 9import org.junit.jupiter.api.Test; 10import org.junit.jupiter.api.extension.ExtendWith; 11import org.mockito.InjectMocks; 12import org.mockito.Mock; 13import org.mockito.junit.jupiter.MockitoExtension; 14import org.springframework.security.crypto.password.PasswordEncoder; 15 16import java.util.Optional; 17 18import static org.junit.jupiter.api.Assertions.*; 19import static org.mockito.ArgumentMatchers.any; 20import static org.mockito.Mockito.*; 21 22@ExtendWith(MockitoExtension.class) 23class UserServiceTest { 24 25 @Mock 26 private UserRepository userRepository; 27 28 @Mock 29 private PasswordEncoder passwordEncoder; 30 31 @InjectMocks 32 private UserServiceImpl userService; 33 34 private CreateUserRequest createUserRequest; 35 36 @BeforeEach 37 void setUp() { 38 createUserRequest = new CreateUserRequest(); 39 createUserRequest.setUsername("testuser"); 40 createUserRequest.setEmail("[email protected]"); 41 createUserRequest.setPassword("password123"); 42 createUserRequest.setPhone("13800138000"); 43 } 44 45 @Test 46 void createUser_Success() { 47 // 准备 48 when(userRepository.existsByUsername("testuser")).thenReturn(false); 49 when(userRepository.existsByEmail("[email protected]")).thenReturn(false); 50 when(passwordEncoder.encode("password123")).thenReturn("encodedPassword"); 51 52 User savedUser = new User(); 53 savedUser.setId(1L); 54 savedUser.setUsername("testuser"); 55 savedUser.setEmail("[email protected]"); 56 when(userRepository.save(any(User.class))).thenReturn(savedUser); 57 58 // 执行 59 UserDTO result = userService.createUser(createUserRequest); 60 61 // 验证 62 assertNotNull(result); 63 assertEquals(1L, result.getId()); 64 assertEquals("testuser", result.getUsername()); 65 assertEquals("[email protected]", result.getEmail()); 66 67 verify(userRepository, times(1)).save(any(User.class)); 68 } 69 70 @Test 71 void getUserById_UserExists() { 72 // 准备 73 User user = new User(); 74 user.setId(1L); 75 user.setUsername("testuser"); 76 user.setEmail("[email protected]"); 77 78 when(userRepository.findById(1L)).thenReturn(Optional.of(user)); 79 80 // 执行 81 UserDTO result = userService.getUserById(1L); 82 83 // 验证 84 assertNotNull(result); 85 assertEquals(1L, result.getId()); 86 assertEquals("testuser", result.getUsername()); 87 } 88} 89
10.2 集成测试
1// UserControllerIntegrationTest.java 2package com.example.webapi.controller; 3 4import com.example.webapi.model.dto.CreateUserRequest; 5import com.example.webapi.repository.UserRepository; 6import com.fasterxml.jackson.databind.ObjectMapper; 7import org.junit.jupiter.api.Test; 8import org.springframework.beans.factory.annotation.Autowired; 9import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 10import org.springframework.boot.test.context.SpringBootTest; 11import org.springframework.http.MediaType; 12import org.springframework.test.web.servlet.MockMvc; 13import org.springframework.transaction.annotation.Transactional; 14 15import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 16import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 17 18@SpringBootTest 19@AutoConfigureMockMvc 20@Transactional 21class UserControllerIntegrationTest { 22 23 @Autowired 24 private MockMvc mockMvc; 25 26 @Autowired 27 private ObjectMapper objectMapper; 28 29 @Autowired 30 private UserRepository userRepository; 31 32 @Test 33 void createUser_ValidRequest_ReturnsCreated() throws Exception { 34 CreateUserRequest request = new CreateUserRequest(); 35 request.setUsername("integrationtest"); 36 request.setEmail("[email protected]"); 37 request.setPassword("password123"); 38 39 mockMvc.perform(post("/api/users") 40 .contentType(MediaType.APPLICATION_JSON) 41 .content(objectMapper.writeValueAsString(request))) 42 .andExpect(status().isCreated()) 43 .andExpect(jsonPath("$.success").value(true)) 44 .andExpect(jsonPath("$.data.username").value("integrationtest")); 45 } 46 47 @Test 48 void getUserById_UserExists_ReturnsUser() throws Exception { 49 // 先创建用户 50 CreateUserRequest request = new CreateUserRequest(); 51 request.setUsername("testuser"); 52 request.setEmail("[email protected]"); 53 request.setPassword("password123"); 54 55 String response = mockMvc.perform(post("/api/users") 56 .contentType(MediaType.APPLICATION_JSON) 57 .content(objectMapper.writeValueAsString(request))) 58 .andReturn().getResponse().getContentAsString(); 59 60 // 提取用户ID并查询 61 // 这里简化处理,实际应该解析响应获取ID 62 mockMvc.perform(get("/api/users/1")) 63 .andExpect(status().isOk()) 64 .andExpect(jsonPath("$.success").value(true)); 65 } 66} 67
第十一部分:部署与监控
11.1 Docker配置
1# Dockerfile 2FROM openjdk:11-jre-slim 3 4WORKDIR /app 5 6COPY target/webapi-demo-1.0.0.jar app.jar 7 8RUN sh -c 'touch /app.jar' 9 10ENV JAVA_OPTS="" 11 12EXPOSE 8080 13 14ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ] 15
1# docker-compose.yml 2version: '3.8' 3 4services: 5 webapi: 6 build: . 7 ports: 8 - "8080:8080" 9 environment: 10 - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/webapi_db 11 - SPRING_DATASOURCE_USERNAME=root 12 - SPRING_DATASOURCE_PASSWORD=password 13 depends_on: 14 - mysql 15 16 mysql: 17 image: mysql:8.0 18 environment: 19 - MYSQL_ROOT_PASSWORD=password 20 - MYSQL_DATABASE=webapi_db 21 ports: 22 - "3306:3306" 23 volumes: 24 - mysql_data:/var/lib/mysql 25 26volumes: 27 mysql_data: 28
11.2 健康检查与监控
1// HealthCheckController.java 2package com.example.webapi.controller; 3 4import org.springframework.beans.factory.annotation.Autowired; 5import org.springframework.boot.actuate.health.HealthComponent; 6import org.springframework.boot.actuate.health.HealthEndpoint; 7import org.springframework.jdbc.core.JdbcTemplate; 8import org.springframework.web.bind.annotation.GetMapping; 9import org.springframework.web.bind.annotation.RequestMapping; 10import org.springframework.web.bind.annotation.RestController; 11 12import java.util.HashMap; 13import java.util.Map; 14 15@RestController 16@RequestMapping("/health") 17public class HealthCheckController { 18 19 @Autowired 20 private JdbcTemplate jdbcTemplate; 21 22 @Autowired 23 private HealthEndpoint healthEndpoint; 24 25 @GetMapping 26 public Map<String, Object> healthCheck() { 27 Map<String, Object> health = new HashMap<>(); 28 29 // 数据库健康检查 30 try { 31 jdbcTemplate.execute("SELECT 1"); 32 health.put("database", "UP"); 33 } catch (Exception e) { 34 health.put("database", "DOWN"); 35 } 36 37 // 系统健康检查 38 HealthComponent systemHealth = healthEndpoint.health(); 39 health.put("status", systemHealth.getStatus().getCode()); 40 health.put("timestamp", System.currentTimeMillis()); 41 42 return health; 43 } 44} 45
第十二部分:最佳实践与总结
12.1 API设计最佳实践
- 使用合适的HTTP状态码
- 200: 成功
- 201: 创建成功
- 400: 客户端错误
- 401: 未授权
- 403: 禁止访问
- 404: 资源不存在
- 500: 服务器错误
- 统一的响应格式
1{ 2 "success": true, 3 "message": "操作成功", 4 "data": {}, 5 "timestamp": 1640995200000 6} 7
- 版本控制
- URL路径版本:
/api/v1/users - 请求头版本:
Accept: application/vnd.example.v1+json
- URL路径版本:
- 分页和过滤
GET /api/users?page=0&size=10&sort=createdAt,descGET /api/users?name=john&email=example.com
12.2 性能优化建议
- 数据库优化
- 合理使用索引
- 避免N+1查询问题
- 使用连接查询替代多次查询
- 缓存策略
- 使用Redis进行会话存储
- 缓存热点数据
- 设置合理的缓存过期时间
- 异步处理
- 使用消息队列处理耗时操作
- 异步发送邮件和通知
- 后台任务处理
12.3 安全考虑
- 输入验证
- 使用Bean Validation注解
- 防范SQL注入
- XSS防护
- 认证授权
- 使用JWT进行无状态认证
- 基于角色的访问控制
- API密钥管理
- 其他安全措施
- HTTPS强制使用
- 定期更新依赖
- 安全头部配置
12.4 总结
通过本文的详细讲解,您应该已经掌握了Java后端Web API开发的全流程。从环境搭建、项目架构设计,到具体的编码实现和测试部署,我们覆盖了开发一个完整Web API项目所需的所有关键知识点。
核心要点回顾:
- 采用分层架构,保持代码清晰和可维护性
- 使用Spring Boot快速开发,减少配置工作
- 实现完整的CRUD操作和业务逻辑
- 添加适当的异常处理和日志记录
- 编写全面的测试用例
- 考虑安全性和性能优化
在实际项目开发中,还需要根据具体需求不断调整和优化架构设计,同时关注代码质量、团队协作和持续集成等工程实践。希望本文能为您的Java Web API开发之旅提供有力的帮助!
《快学快用系列:一文学会java后端WebApi开发》 是转载文章,点击查看原文。
