跳到主要内容
Java 后端 Web API 开发实战:从架构到部署 | 极客日志
Java SaaS java
Java 后端 Web API 开发实战:从架构到部署 综述由AI生成 Java 后端 Web API 开发实战涵盖了从环境搭建到部署监控的全流程。基于 Spring Boot 框架,采用分层架构设计,整合了 JPA 数据访问、Spring Security 认证授权及 JWT 令牌机制。文章详细演示了实体类与 DTO 建模、Repository 自定义查询、Service 业务逻辑封装以及 Controller 层 RESTful 接口实现。此外,还包含单元测试、集成测试、Docker 容器化部署及性能优化建议,旨在帮助开发者构建高可用、安全且易维护的后端服务系统。
雾岛听风 发布于 2026/3/16 更新于 2026/4/30 4 浏览Java 后端 Web API 开发实战:从架构到部署
1. Web API 基础概念
1.1 什么是 Web API
Web API(Application Programming Interface)是不同软件系统间通信的桥梁。在 Web 开发中,它通常基于 HTTP/HTTPS 协议,采用 RESTful 风格,通过 URL 端点提供数据和服务。
核心特点包括:
基于 HTTP/HTTPS 协议
返回结构化数据(JSON/XML)
无状态通信
跨平台兼容
1.2 RESTful API 设计原则
REST(Representational State Transfer)是一种架构风格,遵循以下原则:
统一接口 :使用标准 HTTP 方法和状态码
无状态 :每个请求包含所有必要信息
可缓存 :响应应标记为可缓存或不可缓存
分层系统 :客户端无需知道是否连接到最终服务器
按需代码 :服务器可临时扩展功能
2. 开发环境搭建
2.1 环境要求
准备以下工具:
JDK 8 或以上版本
IDE(推荐 IntelliJ IDEA)
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"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" >
<modelVersion > 4.0.0</modelVersion >
<parent >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-parent</ >
2.7.0
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
artifactId
<version >
</version >
<relativePath />
</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 >
2.3 配置文件 application.yml 用于配置服务端口及数据库连接:
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
3. 项目架构设计
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. 数据模型设计
4.1 实体类设计 使用 JPA 注解定义数据库映射关系,注意字段验证:
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 }
4.2 DTO 设计 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;
}
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;
}
5. 数据访问层实现
5.1 Repository 接口 Spring Data JPA 提供了强大的查询能力,只需定义接口即可:
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 实现 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();
}
}
6. 业务逻辑层实现
6.1 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) ;
}
6.2 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;
}
}
7. 控制器层实现
7.1 基础控制器 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;
}
}
7.2 全局异常处理 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. 安全配置
8.1 Spring Security 配置 启用无状态会话管理,禁用 CSRF(针对纯 API 场景):
package com.example.webapi.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain (HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**" ).permitAll()
.antMatchers("/api/users/create" ).permitAll()
.antMatchers("/api/public/**" ).permitAll()
.anyRequest().authenticated();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder () {
return new BCryptPasswordEncoder ();
}
}
8.2 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 ;
}
}
9. 高级特性实现
9.1 缓存配置 利用 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) {
return null ;
}
@CacheEvict(value = "users", key = "#id")
@Override
public UserDTO updateUser (Long id, UpdateUserRequest request) {
return null ;
}
}
9.2 异步处理 package com.example.webapi.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor () {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor ();
executor.setCorePoolSize(5 );
executor.setMaxPoolSize(10 );
executor.setQueueCapacity(100 );
executor.setThreadNamePrefix("AsyncThread-" );
executor.initialize();
return executor;
}
}
package com.example.webapi.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class EmailService {
@Async("taskExecutor")
public void sendWelcomeEmail (String email, String username) {
try {
Thread.sleep(5000 );
System.out.println("欢迎邮件已发送至:" + email);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
10. 测试
10.1 单元测试 使用 Mockito 隔离依赖,专注于业务逻辑验证:
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());
}
}
10.2 集成测试 使用 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.post;
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 ));
}
}
11. 部署与监控
11.1 Docker 配置 FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/webapi-demo-1.0.0.jar app.jar
RUN sh -c 'touch /app.jar'
ENV EXPOSE 8080
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]
version: '3.8'
services:
webapi:
build: .
ports:
- "8080:8080"
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/webapi_db
- SPRING_DATASOURCE_USERNAME=root
- SPRING_DATASOURCE_PASSWORD=password
depends_on:
- mysql
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=webapi_db
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
11.2 健康检查与监控 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" , "UP" );
health.put("timestamp" , System.currentTimeMillis());
return health;
}
}
12. 最佳实践与总结
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
分页和过滤
GET /api/users?page=0&size=10&sort=createdAt,desc
GET /api/users?name=john&email=example.com
12.2 性能优化建议
数据库优化
合理使用索引
避免 N+1 查询问题
使用连接查询替代多次查询
缓存策略
使用 Redis 进行会话存储
缓存热点数据
设置合理的缓存过期时间
异步处理
使用消息队列处理耗时操作
异步发送邮件和通知
后台任务处理
12.3 安全考虑
输入验证
使用 Bean Validation 注解
防范 SQL 注入
XSS 防护
认证授权
使用 JWT 进行无状态认证
基于角色的访问控制
API 密钥管理
其他安全措施
12.4 总结 构建一个健壮的 Java 后端 Web API 需要兼顾架构设计与工程实践。通过分层架构保持代码清晰,利用 Spring Boot 减少配置负担,结合 JPA 和 Security 完善数据与安全机制。在实际项目中,还需根据具体需求调整架构,关注代码质量、团队协作和持续集成。希望这份指南能为你的开发之旅提供实质性的帮助。
相关免费在线工具 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