当前位置: 首页 > news >正文

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

在这里插入图片描述

文章目录

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

在这里插入图片描述

第一部分: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)是一种软件架构风格,包含以下核心原则:

  1. 统一接口:使用标准的HTTP方法和状态码
  2. 无状态:每个请求包含所有必要信息
  3. 可缓存:响应应标记为可缓存或不可缓存
  4. 分层系统:客户端不需要知道是否连接到最终服务器
  5. 按需代码:服务器可以临时扩展功能

第二部分:开发环境搭建

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"?>
<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><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.yml
server:port: 8080servlet:context-path: /apispring:datasource:url: jdbc:mysql://localhost:3306/webapi_dbusername: rootpassword: passworddriver-class-name: com.mysql.cj.jdbc.Driverjpa:hibernate:ddl-auto: updateshow-sql: trueproperties:hibernate:dialect: org.hibernate.dialect.MySQL8Dialectformat_sql: truelogging:level:com.example: DEBUGorg.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.java
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();}// Getter和Setter方法// ... 省略具体实现
}enum UserStatus {ACTIVE, INACTIVE, DELETED
}

4.2 DTO设计

// UserDTO.java
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() {}// Getter和Setter// ... 省略具体实现
}// CreateUserRequest.java
package com.example.webapi.model.dto;import javax.validation.constraints.*;public class CreateUserRequest {@NotBlank(message = "用户名不能为空")@Size(min = 3, max = 50)private String username;@Email@NotBlankprivate String email;@NotBlank@Size(min = 6)private String password;private String phone;// Getter和Setter// ... 省略具体实现
}

第五部分:数据访问层实现

5.1 Repository接口

// UserRepository.java
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);
}

5.2 自定义Repository实现

// UserRepositoryCustom.java
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();
}// UserRepositoryCustomImpl.java
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 {@PersistenceContextprivate EntityManager entityManager;@Overridepublic 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);}@Overridepublic 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();}
}

第六部分:业务逻辑层实现

6.1 Service接口设计

// UserService.java
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);
}

6.2 Service实现类

// UserServiceImpl.java
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 {@Autowiredprivate UserRepository userRepository;@Autowiredprivate PasswordEncoder passwordEncoder;@Overridepublic 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());}@Overridepublic 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);}@Overridepublic 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);}// 转换实体为DTOprivate 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;}
}

第七部分:控制器层实现

7.1 基础控制器

// UserController.java
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.List;
import java.util.Map;@RestController
@RequestMapping("/users")
@Validated
public class UserController {@Autowiredprivate UserService userService;@PostMappingpublic 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()));}}@GetMappingpublic 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;}
}

7.2 全局异常处理

// GlobalExceptionHandler.java
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);}
}

第八部分:安全配置

8.1 Spring Security配置

// SecurityConfig.java
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 {@Beanpublic 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();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

8.2 JWT认证配置

// JwtUtils.java
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;}
}

第九部分:高级特性实现

9.1 缓存配置

// CacheConfig.java
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 {@Beanpublic CacheManager cacheManager() {ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();cacheManager.setCacheNames(Arrays.asList("users", "products"));return cacheManager;}
}// 在Service中使用缓存
@Service
public class UserServiceImpl implements UserService {@Cacheable(value = "users", key = "#id")@Overridepublic UserDTO getUserById(Long id) {// 从数据库获取用户}@CacheEvict(value = "users", key = "#id")@Overridepublic UserDTO updateUser(Long id, UpdateUserRequest request) {// 更新用户}
}

9.2 异步处理

// AsyncConfig.java
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;}
}// 异步服务
@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();}}
}

第十部分:测试

10.1 单元测试

// UserServiceTest.java
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 {@Mockprivate UserRepository userRepository;@Mockprivate PasswordEncoder passwordEncoder;@InjectMocksprivate UserServiceImpl userService;private CreateUserRequest createUserRequest;@BeforeEachvoid setUp() {createUserRequest = new CreateUserRequest();createUserRequest.setUsername("testuser");createUserRequest.setEmail("test@example.com");createUserRequest.setPassword("password123");createUserRequest.setPhone("13800138000");}@Testvoid createUser_Success() {// 准备when(userRepository.existsByUsername("testuser")).thenReturn(false);when(userRepository.existsByEmail("test@example.com")).thenReturn(false);when(passwordEncoder.encode("password123")).thenReturn("encodedPassword");User savedUser = new User();savedUser.setId(1L);savedUser.setUsername("testuser");savedUser.setEmail("test@example.com");when(userRepository.save(any(User.class))).thenReturn(savedUser);// 执行UserDTO result = userService.createUser(createUserRequest);// 验证assertNotNull(result);assertEquals(1L, result.getId());assertEquals("testuser", result.getUsername());assertEquals("test@example.com", result.getEmail());verify(userRepository, times(1)).save(any(User.class));}@Testvoid getUserById_UserExists() {// 准备User user = new User();user.setId(1L);user.setUsername("testuser");user.setEmail("test@example.com");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.java
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 {@Autowiredprivate MockMvc mockMvc;@Autowiredprivate ObjectMapper objectMapper;@Autowiredprivate UserRepository userRepository;@Testvoid createUser_ValidRequest_ReturnsCreated() throws Exception {CreateUserRequest request = new CreateUserRequest();request.setUsername("integrationtest");request.setEmail("integration@test.com");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"));}@Testvoid getUserById_UserExists_ReturnsUser() throws Exception {// 先创建用户CreateUserRequest request = new CreateUserRequest();request.setUsername("testuser");request.setEmail("test@example.com");request.setPassword("password123");String response = mockMvc.perform(post("/api/users").contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(request))).andReturn().getResponse().getContentAsString();// 提取用户ID并查询// 这里简化处理,实际应该解析响应获取IDmockMvc.perform(get("/api/users/1")).andExpect(status().isOk()).andExpect(jsonPath("$.success").value(true));}
}

第十一部分:部署与监控

11.1 Docker配置

# Dockerfile
FROM openjdk:11-jre-slimWORKDIR /appCOPY target/webapi-demo-1.0.0.jar app.jarRUN sh -c 'touch /app.jar'ENV JAVA_OPTS=""EXPOSE 8080ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]
# docker-compose.yml
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=passworddepends_on:- mysqlmysql:image: mysql:8.0environment:- MYSQL_ROOT_PASSWORD=password- MYSQL_DATABASE=webapi_dbports:- "3306:3306"volumes:- mysql_data:/var/lib/mysqlvolumes:mysql_data:

11.2 健康检查与监控

// HealthCheckController.java
package com.example.webapi.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.HealthComponent;
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 {@Autowiredprivate JdbcTemplate jdbcTemplate;@Autowiredprivate HealthEndpoint healthEndpoint;@GetMappingpublic 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");}// 系统健康检查HealthComponent systemHealth = healthEndpoint.health();health.put("status", systemHealth.getStatus().getCode());health.put("timestamp", System.currentTimeMillis());return health;}
}

第十二部分:最佳实践与总结

12.1 API设计最佳实践

  1. 使用合适的HTTP状态码

    • 200: 成功
    • 201: 创建成功
    • 400: 客户端错误
    • 401: 未授权
    • 403: 禁止访问
    • 404: 资源不存在
    • 500: 服务器错误
  2. 统一的响应格式

{"success": true,"message": "操作成功","data": {},"timestamp": 1640995200000
}
  1. 版本控制

    • URL路径版本: /api/v1/users
    • 请求头版本: Accept: application/vnd.example.v1+json
  2. 分页和过滤

    • GET /api/users?page=0&size=10&sort=createdAt,desc
    • GET /api/users?name=john&email=example.com

12.2 性能优化建议

  1. 数据库优化

    • 合理使用索引
    • 避免N+1查询问题
    • 使用连接查询替代多次查询
  2. 缓存策略

    • 使用Redis进行会话存储
    • 缓存热点数据
    • 设置合理的缓存过期时间
  3. 异步处理

    • 使用消息队列处理耗时操作
    • 异步发送邮件和通知
    • 后台任务处理

12.3 安全考虑

  1. 输入验证

    • 使用Bean Validation注解
    • 防范SQL注入
    • XSS防护
  2. 认证授权

    • 使用JWT进行无状态认证
    • 基于角色的访问控制
    • API密钥管理
  3. 其他安全措施

    • HTTPS强制使用
    • 定期更新依赖
    • 安全头部配置

12.4 总结

通过本文的详细讲解,您应该已经掌握了Java后端Web API开发的全流程。从环境搭建、项目架构设计,到具体的编码实现和测试部署,我们覆盖了开发一个完整Web API项目所需的所有关键知识点。

核心要点回顾:

  • 采用分层架构,保持代码清晰和可维护性
  • 使用Spring Boot快速开发,减少配置工作
  • 实现完整的CRUD操作和业务逻辑
  • 添加适当的异常处理和日志记录
  • 编写全面的测试用例
  • 考虑安全性和性能优化

在实际项目开发中,还需要根据具体需求不断调整和优化架构设计,同时关注代码质量、团队协作和持续集成等工程实践。希望本文能为您的Java Web API开发之旅提供有力的帮助!

http://www.dtcms.com/a/430645.html

相关文章:

  • 网站加速器免费永久网站开发学习课程
  • SpringBoot 整合Jasypt 实现配置文件加密读取操作详解
  • apache 服务器如何使用
  • CI/CD 流水线与 agentic AI:如何创建自我纠正的 monorepos
  • Coze源码分析-资源库-编辑工作流-后端源码-IDL/API/应用服务层
  • 网站建设与维护课程设计报告书wordpress 多媒体管理系统
  • 一文了解国产算子编程语言 TileLang,TileLang 对国产开源生态的影响与启示
  • C#和Java正则表达式开发
  • 从零开始:MCP数据库助手(一)- 基础搭建
  • ORB_SLAM2原理及代码解析:SetPose() 函数
  • 蚌埠市建设学校网站网站排名权重怎么做
  • Android android.util.LruCache源码阅读
  • 安卓基础组件020-页面跳转传递数据001
  • Postman 学习笔记 IV:Workflow、Newman 与 Mock Server 实战技巧
  • 安卓基础组件016--第三方Toasty组件
  • ESNP LAB 笔记:配置静态BFD检测MPLS LDP LSP
  • Day30 | Java集合框架之Collections工具类
  • 【STM32项目开源】基于STM32的智能养殖场环境监测系统
  • 【Java并发】揭秘Lock体系 -- condition等待通知机制
  • 计算机网络-网络边缘网络核心
  • 安卓13_ROM修改定制化-----修改固件 去除主题防止恢复 破解主题等操作解析
  • 怎么做网站301重定向可口可乐公司的企业网站建设
  • NS4168输出音频通过ESP32C3测试
  • 24.使用 HTML 和 CSS 实现无限旋转正方形动画效果
  • 音频降噪技术:从原理到工具的完整指南(scipy librosa noisereduce soundfile pedalboard)
  • 网站建设构成技术要求wordpress书籍推荐
  • CoCoSim(2020): 连接Simulink与Lustre生态的模型检测框架
  • 第2篇|风机设计的基本原则:从“会弯的高楼”到“会自救的系统”
  • SpringSecurity详解
  • [linux仓库]深入解析Linux动态链接与动态库加载:理解背后的原理与技巧