快学快用系列:一文学会java后端WebApi开发

文章目录

第一部分: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创建项目:
<!-- pom.xml --><?xml version="1.0" encoding="UTF-8"?><projectxmlns="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><groupId>com.example</groupId><artifactId>webapi-demo</artifactId><version>1.0.0</version><properties><java.version>11</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies></project>2.3 配置文件
# application.ymlserver:port:8080servlet: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:trueproperties:hibernate:dialect: org.hibernate.dialect.MySQL8Dialect format_sql:truelogging:level:com.example: DEBUG org.hibernate.SQL: DEBUG 第三部分:项目架构设计
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 实体类设计
// User.javapackagecom.example.webapi.model.entity;importjavax.persistence.*;importjavax.validation.constraints.*;importjava.time.LocalDateTime;importjava.util.List;@Entity@Table(name ="users")publicclassUser{@Id@GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;@NotBlank(message ="用户名不能为空")@Size(min =3, max =50, message ="用户名长度必须在3-50字符之间")@Column(unique =true, nullable =false)privateString username;@Email(message ="邮箱格式不正确")@Column(unique =true, nullable =false)privateString email;@NotBlank(message ="密码不能为空")@Size(min =6, message ="密码长度至少6位")privateString password;privateString phone;@Enumerated(EnumType.STRING)privateUserStatus status =UserStatus.ACTIVE;@Column(name ="created_at")privateLocalDateTime createdAt;@Column(name ="updated_at")privateLocalDateTime updatedAt;// 构造方法publicUser(){this.createdAt =LocalDateTime.now();this.updatedAt =LocalDateTime.now();}// Getter和Setter方法// ... 省略具体实现}enumUserStatus{ ACTIVE, INACTIVE, DELETED }4.2 DTO设计
// UserDTO.javapackagecom.example.webapi.model.dto;importjavax.validation.constraints.*;importjava.time.LocalDateTime;publicclassUserDTO{privateLong id;@NotBlank(message ="用户名不能为空")privateString username;@Email(message ="邮箱格式不正确")privateString email;privateString phone;privateLocalDateTime createdAt;// 构造方法publicUserDTO(){}// Getter和Setter// ... 省略具体实现}// CreateUserRequest.javapackagecom.example.webapi.model.dto;importjavax.validation.constraints.*;publicclassCreateUserRequest{@NotBlank(message ="用户名不能为空")@Size(min =3, max =50)privateString username;@Email@NotBlankprivateString email;@NotBlank@Size(min =6)privateString password;privateString phone;// Getter和Setter// ... 省略具体实现}第五部分:数据访问层实现
5.1 Repository接口
// UserRepository.javapackagecom.example.webapi.repository;importcom.example.webapi.model.entity.User;importcom.example.webapi.model.entity.UserStatus;importorg.springframework.data.jpa.repository.JpaRepository;importorg.springframework.data.jpa.repository.Query;importorg.springframework.data.repository.query.Param;importorg.springframework.stereotype.Repository;importjava.util.List;importjava.util.Optional;@RepositorypublicinterfaceUserRepositoryextendsJpaRepository<User,Long>{Optional<User>findByUsername(String username);Optional<User>findByEmail(String email);List<User>findByStatus(UserStatus status);booleanexistsByUsername(String username);booleanexistsByEmail(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);}5.2 自定义Repository实现
// UserRepositoryCustom.javapackagecom.example.webapi.repository;importcom.example.webapi.model.entity.User;importorg.springframework.data.domain.Page;importorg.springframework.data.domain.Pageable;importjava.util.List;publicinterfaceUserRepositoryCustom{Page<User>findUsersWithPagination(String keyword,Pageable pageable);List<User>findActiveUsersWithRecentActivity();}// UserRepositoryCustomImpl.javapackagecom.example.webapi.repository;importcom.example.webapi.model.entity.User;importcom.example.webapi.model.entity.UserStatus;importorg.springframework.data.domain.Page;importorg.springframework.data.domain.PageImpl;importorg.springframework.data.domain.Pageable;importorg.springframework.stereotype.Repository;importjavax.persistence.EntityManager;importjavax.persistence.PersistenceContext;importjavax.persistence.TypedQuery;importjava.time.LocalDateTime;importjava.util.List;@RepositorypublicclassUserRepositoryCustomImplimplementsUserRepositoryCustom{@PersistenceContextprivateEntityManager entityManager;@OverridepublicPage<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();returnnewPageImpl<>(users, pageable, total);}@OverridepublicList<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();}}第六部分:业务逻辑层实现
6.1 Service接口设计
// UserService.javapackagecom.example.webapi.service;importcom.example.webapi.model.dto.CreateUserRequest;importcom.example.webapi.model.dto.UpdateUserRequest;importcom.example.webapi.model.dto.UserDTO;importorg.springframework.data.domain.Page;importorg.springframework.data.domain.Pageable;importjava.util.List;publicinterfaceUserService{UserDTOcreateUser(CreateUserRequest request);UserDTOgetUserById(Long id);UserDTOgetUserByUsername(String username);Page<UserDTO>getAllUsers(Pageable pageable);List<UserDTO>searchUsers(String keyword);UserDTOupdateUser(Long id,UpdateUserRequest request);voiddeleteUser(Long id);booleanexistsByUsername(String username);booleanexistsByEmail(String email);}6.2 Service实现类
// UserServiceImpl.javapackagecom.example.webapi.service.impl;importcom.example.webapi.model.dto.CreateUserRequest;importcom.example.webapi.model.dto.UpdateUserRequest;importcom.example.webapi.model.dto.UserDTO;importcom.example.webapi.model.entity.User;importcom.example.webapi.model.entity.UserStatus;importcom.example.webapi.repository.UserRepository;importcom.example.webapi.service.UserService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.domain.Page;importorg.springframework.data.domain.Pageable;importorg.springframework.security.crypto.password.PasswordEncoder;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Transactional;importjava.util.List;importjava.util.stream.Collectors;@Service@TransactionalpublicclassUserServiceImplimplementsUserService{@AutowiredprivateUserRepository userRepository;@AutowiredprivatePasswordEncoder passwordEncoder;@OverridepublicUserDTOcreateUser(CreateUserRequest request){// 检查用户名和邮箱是否已存在if(userRepository.existsByUsername(request.getUsername())){thrownewRuntimeException("用户名已存在");}if(userRepository.existsByEmail(request.getEmail())){thrownewRuntimeException("邮箱已存在");}// 创建用户实体User user =newUser(); 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);returnconvertToDTO(savedUser);}@Override@Transactional(readOnly =true)publicUserDTOgetUserById(Long id){User user = userRepository.findById(id).orElseThrow(()->newRuntimeException("用户不存在"));returnconvertToDTO(user);}@Override@Transactional(readOnly =true)publicUserDTOgetUserByUsername(String username){User user = userRepository.findByUsername(username).orElseThrow(()->newRuntimeException("用户不存在"));returnconvertToDTO(user);}@Override@Transactional(readOnly =true)publicPage<UserDTO>getAllUsers(Pageable pageable){return userRepository.findAll(pageable).map(this::convertToDTO);}@Override@Transactional(readOnly =true)publicList<UserDTO>searchUsers(String keyword){return userRepository.findByEmailContaining(keyword).stream().map(this::convertToDTO).collect(Collectors.toList());}@OverridepublicUserDTOupdateUser(Long id,UpdateUserRequest request){User user = userRepository.findById(id).orElseThrow(()->newRuntimeException("用户不存在"));// 更新用户信息if(request.getEmail()!=null&&!request.getEmail().equals(user.getEmail())){if(userRepository.existsByEmail(request.getEmail())){thrownewRuntimeException("邮箱已存在");} user.setEmail(request.getEmail());}if(request.getPhone()!=null){ user.setPhone(request.getPhone());}User updatedUser = userRepository.save(user);returnconvertToDTO(updatedUser);}@OverridepublicvoiddeleteUser(Long id){User user = userRepository.findById(id).orElseThrow(()->newRuntimeException("用户不存在")); user.setStatus(UserStatus.DELETED); userRepository.save(user);}@Override@Transactional(readOnly =true)publicbooleanexistsByUsername(String username){return userRepository.existsByUsername(username);}@Override@Transactional(readOnly =true)publicbooleanexistsByEmail(String email){return userRepository.existsByEmail(email);}// 转换实体为DTOprivateUserDTOconvertToDTO(User user){UserDTO dto =newUserDTO(); dto.setId(user.getId()); dto.setUsername(user.getUsername()); dto.setEmail(user.getEmail()); dto.setPhone(user.getPhone()); dto.setCreatedAt(user.getCreatedAt());return dto;}}第七部分:控制器层实现
7.1 基础控制器
// UserController.javapackagecom.example.webapi.controller;importcom.example.webapi.model.dto.CreateUserRequest;importcom.example.webapi.model.dto.UpdateUserRequest;importcom.example.webapi.model.dto.UserDTO;importcom.example.webapi.service.UserService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.domain.Page;importorg.springframework.data.domain.PageRequest;importorg.springframework.data.domain.Pageable;importorg.springframework.data.domain.Sort;importorg.springframework.http.HttpStatus;importorg.springframework.http.ResponseEntity;importorg.springframework.validation.annotation.Validated;importorg.springframework.web.bind.annotation.*;importjavax.validation.Valid;importjava.util.HashMap;importjava.util.List;importjava.util.Map;@RestController@RequestMapping("/users")@ValidatedpublicclassUserController{@AutowiredprivateUserService userService;@PostMappingpublicResponseEntity<?>createUser(@Valid@RequestBodyCreateUserRequest request){try{UserDTO user = userService.createUser(request);returnResponseEntity.status(HttpStatus.CREATED).body(createSuccessResponse("用户创建成功", user));}catch(RuntimeException e){returnResponseEntity.badRequest().body(createErrorResponse(e.getMessage()));}}@GetMapping("/{id}")publicResponseEntity<?>getUserById(@PathVariableLong id){try{UserDTO user = userService.getUserById(id);returnResponseEntity.ok(createSuccessResponse("获取用户成功", user));}catch(RuntimeException e){returnResponseEntity.status(HttpStatus.NOT_FOUND).body(createErrorResponse(e.getMessage()));}}@GetMappingpublicResponseEntity<?>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 =newHashMap<>(); 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());returnResponseEntity.ok(response);}@GetMapping("/search")publicResponseEntity<?>searchUsers(@RequestParamString keyword){List<UserDTO> users = userService.searchUsers(keyword);returnResponseEntity.ok(createSuccessResponse("搜索用户成功", users));}@PutMapping("/{id}")publicResponseEntity<?>updateUser(@PathVariableLong id,@Valid@RequestBodyUpdateUserRequest request){try{UserDTO user = userService.updateUser(id, request);returnResponseEntity.ok(createSuccessResponse("用户更新成功", user));}catch(RuntimeException e){returnResponseEntity.badRequest().body(createErrorResponse(e.getMessage()));}}@DeleteMapping("/{id}")publicResponseEntity<?>deleteUser(@PathVariableLong id){try{ userService.deleteUser(id);returnResponseEntity.ok(createSuccessResponse("用户删除成功",null));}catch(RuntimeException e){returnResponseEntity.badRequest().body(createErrorResponse(e.getMessage()));}}// 工具方法:创建成功响应privateMap<String,Object>createSuccessResponse(String message,Object data){Map<String,Object> response =newHashMap<>(); response.put("success",true); response.put("message", message); response.put("data", data); response.put("timestamp",System.currentTimeMillis());return response;}// 工具方法:创建错误响应privateMap<String,Object>createErrorResponse(String message){Map<String,Object> response =newHashMap<>(); response.put("success",false); response.put("message", message); response.put("timestamp",System.currentTimeMillis());return response;}}7.2 全局异常处理
// GlobalExceptionHandler.javapackagecom.example.webapi.exception;importorg.springframework.http.HttpStatus;importorg.springframework.http.ResponseEntity;importorg.springframework.validation.FieldError;importorg.springframework.web.bind.MethodArgumentNotValidException;importorg.springframework.web.bind.annotation.ExceptionHandler;importorg.springframework.web.bind.annotation.RestControllerAdvice;importjavax.servlet.http.HttpServletRequest;importjava.util.HashMap;importjava.util.Map;@RestControllerAdvicepublicclassGlobalExceptionHandler{@ExceptionHandler(MethodArgumentNotValidException.class)publicResponseEntity<?>handleValidationExceptions(MethodArgumentNotValidException ex,HttpServletRequest request){Map<String,String> errors =newHashMap<>(); ex.getBindingResult().getAllErrors().forEach((error)->{String fieldName =((FieldError) error).getField();String errorMessage = error.getDefaultMessage(); errors.put(fieldName, errorMessage);});Map<String,Object> response =newHashMap<>(); response.put("success",false); response.put("message","参数验证失败"); response.put("errors", errors); response.put("path", request.getRequestURI()); response.put("timestamp",System.currentTimeMillis());returnResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);}@ExceptionHandler(RuntimeException.class)publicResponseEntity<?>handleRuntimeException(RuntimeException ex,HttpServletRequest request){Map<String,Object> response =newHashMap<>(); response.put("success",false); response.put("message", ex.getMessage()); response.put("path", request.getRequestURI()); response.put("timestamp",System.currentTimeMillis());returnResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);}@ExceptionHandler(Exception.class)publicResponseEntity<?>handleGlobalException(Exception ex,HttpServletRequest request){Map<String,Object> response =newHashMap<>(); response.put("success",false); response.put("message","服务器内部错误"); response.put("path", request.getRequestURI()); response.put("timestamp",System.currentTimeMillis());returnResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);}}第八部分:安全配置
8.1 Spring Security配置
// SecurityConfig.javapackagecom.example.webapi.config;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.EnableWebSecurity;importorg.springframework.security.config.http.SessionCreationPolicy;importorg.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;importorg.springframework.security.crypto.password.PasswordEncoder;importorg.springframework.security.web.SecurityFilterChain;@Configuration@EnableWebSecuritypublicclassSecurityConfig{@BeanpublicSecurityFilterChainfilterChain(HttpSecurity http)throwsException{ 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();}@BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}}8.2 JWT认证配置
// JwtUtils.javapackagecom.example.webapi.util;importio.jsonwebtoken.*;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.stereotype.Component;importjava.util.Date;@ComponentpublicclassJwtUtils{@Value("${app.jwt.secret}")privateString jwtSecret;@Value("${app.jwt.expiration}")privateint jwtExpirationMs;publicStringgenerateJwtToken(String username){returnJwts.builder().setSubject(username).setIssuedAt(newDate()).setExpiration(newDate((newDate()).getTime()+ jwtExpirationMs)).signWith(SignatureAlgorithm.HS512, jwtSecret).compact();}publicStringgetUserNameFromJwtToken(String token){returnJwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();}publicbooleanvalidateJwtToken(String authToken){try{Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);returntrue;}catch(SignatureException e){// 日志记录}catch(MalformedJwtException e){// 日志记录}catch(ExpiredJwtException e){// 日志记录}catch(UnsupportedJwtException e){// 日志记录}catch(IllegalArgumentException e){// 日志记录}returnfalse;}}第九部分:高级特性实现
9.1 缓存配置
// CacheConfig.javapackagecom.example.webapi.config;importorg.springframework.cache.CacheManager;importorg.springframework.cache.annotation.EnableCaching;importorg.springframework.cache.concurrent.ConcurrentMapCacheManager;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importjava.util.Arrays;@Configuration@EnableCachingpublicclassCacheConfig{@BeanpublicCacheManagercacheManager(){ConcurrentMapCacheManager cacheManager =newConcurrentMapCacheManager(); cacheManager.setCacheNames(Arrays.asList("users","products"));return cacheManager;}}// 在Service中使用缓存@ServicepublicclassUserServiceImplimplementsUserService{@Cacheable(value ="users", key ="#id")@OverridepublicUserDTOgetUserById(Long id){// 从数据库获取用户}@CacheEvict(value ="users", key ="#id")@OverridepublicUserDTOupdateUser(Long id,UpdateUserRequest request){// 更新用户}}9.2 异步处理
// AsyncConfig.javapackagecom.example.webapi.config;importorg.springframework.context.annotation.Configuration;importorg.springframework.scheduling.annotation.EnableAsync;importorg.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;importjava.util.concurrent.Executor;@Configuration@EnableAsyncpublicclassAsyncConfig{@Bean(name ="taskExecutor")publicExecutortaskExecutor(){ThreadPoolTaskExecutor executor =newThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(100); executor.setThreadNamePrefix("AsyncThread-"); executor.initialize();return executor;}}// 异步服务@ServicepublicclassEmailService{@Async("taskExecutor")publicvoidsendWelcomeEmail(String email,String username){// 发送邮件的逻辑try{Thread.sleep(5000);// 模拟耗时操作System.out.println("欢迎邮件已发送至: "+ email);}catch(InterruptedException e){Thread.currentThread().interrupt();}}}第十部分:测试
10.1 单元测试
// UserServiceTest.javapackagecom.example.webapi.service;importcom.example.webapi.model.dto.CreateUserRequest;importcom.example.webapi.model.dto.UserDTO;importcom.example.webapi.model.entity.User;importcom.example.webapi.repository.UserRepository;importorg.junit.jupiter.api.BeforeEach;importorg.junit.jupiter.api.Test;importorg.junit.jupiter.api.extension.ExtendWith;importorg.mockito.InjectMocks;importorg.mockito.Mock;importorg.mockito.junit.jupiter.MockitoExtension;importorg.springframework.security.crypto.password.PasswordEncoder;importjava.util.Optional;importstaticorg.junit.jupiter.api.Assertions.*;importstaticorg.mockito.ArgumentMatchers.any;importstaticorg.mockito.Mockito.*;@ExtendWith(MockitoExtension.class)classUserServiceTest{@MockprivateUserRepository userRepository;@MockprivatePasswordEncoder passwordEncoder;@InjectMocksprivateUserServiceImpl userService;privateCreateUserRequest createUserRequest;@BeforeEachvoidsetUp(){ createUserRequest =newCreateUserRequest(); createUserRequest.setUsername("testuser"); createUserRequest.setEmail("[email protected]"); createUserRequest.setPassword("password123"); createUserRequest.setPhone("13800138000");}@TestvoidcreateUser_Success(){// 准备when(userRepository.existsByUsername("testuser")).thenReturn(false);when(userRepository.existsByEmail("[email protected]")).thenReturn(false);when(passwordEncoder.encode("password123")).thenReturn("encodedPassword");User savedUser =newUser(); 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());assertEquals("[email protected]", result.getEmail());verify(userRepository,times(1)).save(any(User.class));}@TestvoidgetUserById_UserExists(){// 准备User user =newUser(); 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());}}10.2 集成测试
// UserControllerIntegrationTest.javapackagecom.example.webapi.controller;importcom.example.webapi.model.dto.CreateUserRequest;importcom.example.webapi.repository.UserRepository;importcom.fasterxml.jackson.databind.ObjectMapper;importorg.junit.jupiter.api.Test;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.http.MediaType;importorg.springframework.test.web.servlet.MockMvc;importorg.springframework.transaction.annotation.Transactional;importstaticorg.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;importstaticorg.springframework.test.web.servlet.result.MockMvcResultMatchers.*;@SpringBootTest@AutoConfigureMockMvc@TransactionalclassUserControllerIntegrationTest{@AutowiredprivateMockMvc mockMvc;@AutowiredprivateObjectMapper objectMapper;@AutowiredprivateUserRepository userRepository;@TestvoidcreateUser_ValidRequest_ReturnsCreated()throwsException{CreateUserRequest request =newCreateUserRequest(); 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"));}@TestvoidgetUserById_UserExists_ReturnsUser()throwsException{// 先创建用户CreateUserRequest request =newCreateUserRequest(); 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();// 提取用户ID并查询// 这里简化处理,实际应该解析响应获取ID mockMvc.perform(get("/api/users/1")).andExpect(status().isOk()).andExpect(jsonPath("$.success").value(true));}}第十一部分:部署与监控
11.1 Docker配置
# Dockerfile 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" ] # docker-compose.ymlversion:'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.0environment:- MYSQL_ROOT_PASSWORD=password - MYSQL_DATABASE=webapi_db ports:-"3306:3306"volumes:- mysql_data:/var/lib/mysql volumes:mysql_data:11.2 健康检查与监控
// HealthCheckController.javapackagecom.example.webapi.controller;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.actuate.health.HealthComponent;importorg.springframework.boot.actuate.health.HealthEndpoint;importorg.springframework.jdbc.core.JdbcTemplate;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjava.util.HashMap;importjava.util.Map;@RestController@RequestMapping("/health")publicclassHealthCheckController{@AutowiredprivateJdbcTemplate jdbcTemplate;@AutowiredprivateHealthEndpoint healthEndpoint;@GetMappingpublicMap<String,Object>healthCheck(){Map<String,Object> health =newHashMap<>();// 数据库健康检查try{ jdbcTemplate.execute("SELECT 1"); health.put("database","UP");}catch(Exception e){ health.put("database","DOWN");}// 系统健康检查HealthComponent systemHealth = healthEndpoint.health(); health.put("status", systemHealth.getStatus().getCode()); health.put("timestamp",System.currentTimeMillis());return health;}}第十二部分:最佳实践与总结
12.1 API设计最佳实践
- 使用合适的HTTP状态码
- 200: 成功
- 201: 创建成功
- 400: 客户端错误
- 401: 未授权
- 403: 禁止访问
- 404: 资源不存在
- 500: 服务器错误
- 统一的响应格式
{"success":true,"message":"操作成功","data":{},"timestamp":1640995200000}- 版本控制
- 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开发之旅提供有力的帮助!