跳到主要内容
Java 后端 Web API 开发实战:从架构到部署 | 极客日志
Java java
Java 后端 Web API 开发实战:从架构到部署 深入解析基于 Spring Boot 构建 Java 后端 Web API 的全流程。内容涵盖 RESTful 设计原则、分层架构搭建、数据模型与 Repository 实现、业务逻辑封装、安全配置(Spring Security/JWT)、缓存与异步处理,以及单元测试与 Docker 部署方案。通过实际代码示例,展示如何编写高可维护性、安全的接口服务,适合希望快速掌握企业级 API 开发模式的开发者参考。
城市逃兵 发布于 2026/3/15 更新于 2026/4/23 1 浏览
Web API 开发基础概念
什么是 Web API
Web API(Application Programming Interface)是不同软件系统之间通信的桥梁。在 Web 开发中,API 通常基于 HTTP 协议,采用 RESTful 架构风格,通过 URL 端点提供数据和服务。
核心特点:
基于 HTTP/HTTPS 协议
返回结构化数据(JSON/XML)
无状态通信
跨平台兼容
RESTful API 设计原则
REST(Representational State Transfer)是一种软件架构风格,遵循以下核心原则:
统一接口 :使用标准的 HTTP 方法和状态码
无状态 :每个请求包含所有必要信息
可缓存 :响应应标记为可缓存或不可缓存
分层系统 :客户端不需要知道是否连接到最终服务器
按需代码 :服务器可以临时扩展功能
开发环境搭建
环境要求
必需工具:
JDK 8 或以上版本
IDE(IntelliJ IDEA/Eclipse)
Maven 3.6+ 或 Gradle
MySQL/PostgreSQL 数据库
创建 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
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
</groupId >
<artifactId >
</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 >
配置文件 在 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
项目架构设计
分层架构 典型的 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;
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();
}
}
enum UserStatus {
ACTIVE, INACTIVE, DELETED
}
DTO 设计 DTO(Data Transfer Object)用于前后端交互,避免直接暴露 Entity:
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 接口
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 接口设计
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;
}
}
控制器层实现
基础控制器
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;
}
}
全局异常处理
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 配置
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 ;
}
}
高级特性实现
缓存配置
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;
}
}
异步处理
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;
}
}
测试
单元测试
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());
assertEquals("[email protected] " , result.getEmail());
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());
}
}
集成测试
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'
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:
健康检查与监控
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 {
@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" );
}
HealthComponent systemHealth = healthEndpoint.health();
health.put("status" , systemHealth.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