跳到主要内容
Java 后端 Web API 开发实战指南 | 极客日志
Java java
Java 后端 Web API 开发实战指南 Java 后端 Web API 开发涉及环境搭建、分层架构设计、数据模型定义、业务逻辑实现及安全配置等关键环节。本文基于 Spring Boot 框架,详细演示了从项目初始化到 Docker 部署的全流程。涵盖 RESTful 原则应用、JPA 数据访问、Spring Security 认证及 JWT 令牌管理。通过实际代码示例,展示了控制器层异常处理、缓存策略与异步任务优化。重点强调代码规范、测试覆盖及性能调优建议,帮助开发者构建高可用、易维护的后端服务系统。
CryptoLab 发布于 2026/3/28 0 浏览Java 后端 Web API 开发实战指南
Web API 基础概念
Web API(Application Programming Interface)是不同软件系统间通信的桥梁。在 Web 开发中,我们通常基于 HTTP 协议,采用 RESTful 架构风格,通过 URL 端点提供数据和服务。
核心特点包括:
基于 HTTP/HTTPS 协议
返回结构化数据(JSON/XML)
无状态通信
跨平台兼容
REST(Representational State Transfer)作为主流架构风格,遵循以下原则:统一接口、无状态、可缓存、分层系统以及按需代码。理解这些原则有助于设计出更健壮的接口。
开发环境搭建
环境要求
确保本地已安装 JDK 8 或以上版本、Maven 3.6+ 或 Gradle,并准备好 MySQL 或 PostgreSQL 数据库。IDE 推荐使用 IntelliJ IDEA。
创建 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
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 >
<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/
数据模型设计
实体类设计 使用 JPA 注解映射数据库表。注意字段验证与状态管理:
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 设计 避免直接暴露 Entity,使用 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;
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 | MalformedJwtException | ExpiredJwtException |
UnsupportedJwtException | 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;
}
}
@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;
}
}
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();
}
}
}
测试
单元测试
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());
}
}
集成测试
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.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 未授权,404 资源不存在,500 服务器错误。
统一响应格式 :
{ "success" : true , "message" : "操作成功" , "data" : { } , "timestamp" : 1640995200000 }
版本控制 :URL 路径 /api/v1/users 或请求头 Accept。
分页过滤 :page, size, sort 参数标准化。
性能优化建议
数据库 :合理使用索引,避免 N+1 查询,使用连接查询。
缓存 :Redis 存储会话,缓存热点数据,设置合理过期时间。
异步 :消息队列处理耗时操作,如发送邮件。
安全考虑
输入验证 :Bean Validation 注解,防范 SQL 注入与 XSS。
认证授权 :JWT 无状态认证,RBAC 角色控制。
其他 :强制 HTTPS,定期更新依赖,配置安全头部。
总结 本文详细演示了 Java 后端 Web API 开发的全流程。从环境搭建、分层架构设计,到具体的编码实现、测试部署,覆盖了关键知识点。核心在于采用分层架构保持代码清晰,利用 Spring Boot 减少配置,实现完整的 CRUD 与业务逻辑,并添加适当的异常处理与安全机制。在实际项目中,还需根据需求持续优化架构,关注代码质量与团队协作。
相关免费在线工具 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