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