快学快用系列:一文学会java后端WebApi开发
Web API开发基础与实践摘要 Web API是基于HTTP协议的应用接口,采用RESTful架构风格,具有无状态、可缓存等特点。本文介绍了Spring Boot开发Web API的全流程: 环境搭建:使用JDK 11+、Spring Initializr创建项目,配置MySQL数据库和 status 状态字段 架构设计:采用分层架构(Controller-Service-Repository-
·
文章目录

第一部分:Web API开发基础概念
1.1 什么是Web API
Web API(Application Programming Interface)是一种允许不同软件系统之间进行通信的接口。在Web开发中,API通常基于HTTP协议,使用RESTful架构风格,通过URL端点提供数据和服务。
Web API的核心特点:
- 基于HTTP/HTTPS协议
- 返回结构化数据(JSON/XML)
- 无状态通信
- 跨平台兼容
1.2 RESTful API设计原则
REST(Representational State Transfer)是一种软件架构风格,包含以下核心原则:
- 统一接口:使用标准的HTTP方法和状态码
- 无状态:每个请求包含所有必要信息
- 可缓存:响应应标记为可缓存或不可缓存
- 分层系统:客户端不需要知道是否连接到最终服务器
- 按需代码:服务器可以临时扩展功能
第二部分:开发环境搭建
2.1 环境要求
必需工具:
- JDK 8或以上版本
- IDE(IntelliJ IDEA/Eclipse)
- Maven 3.6+ 或 Gradle
- MySQL/PostgreSQL数据库
2.2 创建Spring Boot项目
使用Spring Initializr创建项目:
<!-- pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>webapi-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
2.3 配置文件
# application.yml
server:
port: 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.1 分层架构
典型的Java Web API采用分层架构:
Controller层 (API接口)
↓
Service层 (业务逻辑)
↓
Repository层 (数据访问)
↓
Model层 (数据模型)
3.2 包结构设计
src/main/java/com/example/webapi/
├── config/ # 配置类
├── controller/ # 控制器
├── service/ # 业务逻辑
├── repository/ # 数据访问
├── model/ # 数据模型
│ ├── entity/ # 实体类
│ ├── dto/ # 数据传输对象
│ └── vo/ # 视图对象
├── exception/ # 异常处理
└── util/ # 工具类
第四部分:数据模型设计
4.1 实体类设计
// User.java
package com.example.webapi.model.entity;
import javax.persistence.*;
import javax.validation.constraints.*;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度必须在3-50字符之间")
@Column(unique = true, nullable = false)
private String username;
@Email(message = "邮箱格式不正确")
@Column(unique = true, nullable = false)
private String email;
@NotBlank(message = "密码不能为空")
@Size(min = 6, message = "密码长度至少6位")
private String password;
private String phone;
@Enumerated(EnumType.STRING)
private UserStatus status = UserStatus.ACTIVE;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// 构造方法
public User() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
// Getter和Setter方法
// ... 省略具体实现
}
enum UserStatus {
ACTIVE, INACTIVE, DELETED
}
4.2 DTO设计
// UserDTO.java
package com.example.webapi.model.dto;
import javax.validation.constraints.*;
import java.time.LocalDateTime;
public class UserDTO {
private Long id;
@NotBlank(message = "用户名不能为空")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
private String phone;
private LocalDateTime createdAt;
// 构造方法
public UserDTO() {}
// Getter和Setter
// ... 省略具体实现
}
// CreateUserRequest.java
package com.example.webapi.model.dto;
import javax.validation.constraints.*;
public class CreateUserRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50)
private String username;
@Email
@NotBlank
private String email;
@NotBlank
@Size(min = 6)
private String password;
private String phone;
// Getter和Setter
// ... 省略具体实现
}
第五部分:数据访问层实现
5.1 Repository接口
// UserRepository.java
package com.example.webapi.repository;
import com.example.webapi.model.entity.User;
import com.example.webapi.model.entity.UserStatus;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
List<User> findByStatus(UserStatus status);
boolean existsByUsername(String username);
boolean existsByEmail(String email);
@Query("SELECT u FROM User u WHERE u.email LIKE %:email%")
List<User> findByEmailContaining(@Param("email") String email);
@Query("SELECT u FROM User u WHERE u.createdAt >= :startDate AND u.createdAt < :endDate")
List<User> findUsersByCreateTimeRange(@Param("startDate") LocalDateTime startDate,
@Param("endDate") LocalDateTime endDate);
}
5.2 自定义Repository实现
// UserRepositoryCustom.java
package com.example.webapi.repository;
import com.example.webapi.model.entity.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.List;
public interface UserRepositoryCustom {
Page<User> findUsersWithPagination(String keyword, Pageable pageable);
List<User> findActiveUsersWithRecentActivity();
}
// UserRepositoryCustomImpl.java
package com.example.webapi.repository;
import com.example.webapi.model.entity.User;
import com.example.webapi.model.entity.UserStatus;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import java.time.LocalDateTime;
import java.util.List;
@Repository
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
@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.1 Service接口设计
// UserService.java
package com.example.webapi.service;
import com.example.webapi.model.dto.CreateUserRequest;
import com.example.webapi.model.dto.UpdateUserRequest;
import com.example.webapi.model.dto.UserDTO;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.List;
public interface UserService {
UserDTO createUser(CreateUserRequest request);
UserDTO getUserById(Long id);
UserDTO getUserByUsername(String username);
Page<UserDTO> getAllUsers(Pageable pageable);
List<UserDTO> searchUsers(String keyword);
UserDTO updateUser(Long id, UpdateUserRequest request);
void deleteUser(Long id);
boolean existsByUsername(String username);
boolean existsByEmail(String email);
}
6.2 Service实现类
// UserServiceImpl.java
package com.example.webapi.service.impl;
import com.example.webapi.model.dto.CreateUserRequest;
import com.example.webapi.model.dto.UpdateUserRequest;
import com.example.webapi.model.dto.UserDTO;
import com.example.webapi.model.entity.User;
import com.example.webapi.model.entity.UserStatus;
import com.example.webapi.repository.UserRepository;
import com.example.webapi.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Transactional
public class UserServiceImpl implements UserService {
@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);
}
// 转换实体为DTO
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.1 基础控制器
// UserController.java
package com.example.webapi.controller;
import com.example.webapi.model.dto.CreateUserRequest;
import com.example.webapi.model.dto.UpdateUserRequest;
import com.example.webapi.model.dto.UserDTO;
import com.example.webapi.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/users")
@Validated
public class UserController {
@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 全局异常处理
// GlobalExceptionHandler.java
package com.example.webapi.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleValidationExceptions(
MethodArgumentNotValidException ex, HttpServletRequest request) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "参数验证失败");
response.put("errors", errors);
response.put("path", request.getRequestURI());
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<?> handleRuntimeException(
RuntimeException ex, HttpServletRequest request) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", ex.getMessage());
response.put("path", request.getRequestURI());
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleGlobalException(
Exception ex, HttpServletRequest request) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "服务器内部错误");
response.put("path", request.getRequestURI());
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
第八部分:安全配置
8.1 Spring Security配置
// SecurityConfig.java
package com.example.webapi.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@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认证配置
// JwtUtils.java
package com.example.webapi.util;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class JwtUtils {
@Value("${app.jwt.secret}")
private String jwtSecret;
@Value("${app.jwt.expiration}")
private int jwtExpirationMs;
public String generateJwtToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public String getUserNameFromJwtToken(String token) {
return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
}
public boolean validateJwtToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
// 日志记录
} catch (MalformedJwtException e) {
// 日志记录
} catch (ExpiredJwtException e) {
// 日志记录
} catch (UnsupportedJwtException e) {
// 日志记录
} catch (IllegalArgumentException e) {
// 日志记录
}
return false;
}
}
第九部分:高级特性实现
9.1 缓存配置
// CacheConfig.java
package com.example.webapi.config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
cacheManager.setCacheNames(Arrays.asList("users", "products"));
return cacheManager;
}
}
// 在Service中使用缓存
@Service
public class UserServiceImpl implements UserService {
@Cacheable(value = "users", key = "#id")
@Override
public UserDTO getUserById(Long id) {
// 从数据库获取用户
}
@CacheEvict(value = "users", key = "#id")
@Override
public UserDTO updateUser(Long id, UpdateUserRequest request) {
// 更新用户
}
}
9.2 异步处理
// AsyncConfig.java
package com.example.webapi.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("AsyncThread-");
executor.initialize();
return executor;
}
}
// 异步服务
@Service
public class EmailService {
@Async("taskExecutor")
public void sendWelcomeEmail(String email, String username) {
// 发送邮件的逻辑
try {
Thread.sleep(5000); // 模拟耗时操作
System.out.println("欢迎邮件已发送至: " + email);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
第十部分:测试
10.1 单元测试
// UserServiceTest.java
package com.example.webapi.service;
import com.example.webapi.model.dto.CreateUserRequest;
import com.example.webapi.model.dto.UserDTO;
import com.example.webapi.model.entity.User;
import com.example.webapi.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@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("test@example.com");
createUserRequest.setPassword("password123");
createUserRequest.setPhone("13800138000");
}
@Test
void createUser_Success() {
// 准备
when(userRepository.existsByUsername("testuser")).thenReturn(false);
when(userRepository.existsByEmail("test@example.com")).thenReturn(false);
when(passwordEncoder.encode("password123")).thenReturn("encodedPassword");
User savedUser = new User();
savedUser.setId(1L);
savedUser.setUsername("testuser");
savedUser.setEmail("test@example.com");
when(userRepository.save(any(User.class))).thenReturn(savedUser);
// 执行
UserDTO result = userService.createUser(createUserRequest);
// 验证
assertNotNull(result);
assertEquals(1L, result.getId());
assertEquals("testuser", result.getUsername());
assertEquals("test@example.com", result.getEmail());
verify(userRepository, times(1)).save(any(User.class));
}
@Test
void getUserById_UserExists() {
// 准备
User user = new User();
user.setId(1L);
user.setUsername("testuser");
user.setEmail("test@example.com");
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
// 执行
UserDTO result = userService.getUserById(1L);
// 验证
assertNotNull(result);
assertEquals(1L, result.getId());
assertEquals("testuser", result.getUsername());
}
}
10.2 集成测试
// UserControllerIntegrationTest.java
package com.example.webapi.controller;
import com.example.webapi.model.dto.CreateUserRequest;
import com.example.webapi.repository.UserRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class UserControllerIntegrationTest {
@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("integration@test.com");
request.setPassword("password123");
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.data.username").value("integrationtest"));
}
@Test
void getUserById_UserExists_ReturnsUser() throws Exception {
// 先创建用户
CreateUserRequest request = new CreateUserRequest();
request.setUsername("testuser");
request.setEmail("test@example.com");
request.setPassword("password123");
String response = mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andReturn().getResponse().getContentAsString();
// 提取用户ID并查询
// 这里简化处理,实际应该解析响应获取ID
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true));
}
}
第十一部分:部署与监控
11.1 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 JAVA_OPTS=""
EXPOSE 8080
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]
# docker-compose.yml
version: '3.8'
services:
webapi:
build: .
ports:
- "8080:8080"
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/webapi_db
- SPRING_DATASOURCE_USERNAME=root
- SPRING_DATASOURCE_PASSWORD=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 健康检查与监控
// HealthCheckController.java
package com.example.webapi.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.HealthComponent;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/health")
public class HealthCheckController {
@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;
}
}
第十二部分:最佳实践与总结
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
- URL路径版本:
-
分页和过滤
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密钥管理
-
其他安全措施
- HTTPS强制使用
- 定期更新依赖
- 安全头部配置
12.4 总结
通过本文的详细讲解,您应该已经掌握了Java后端Web API开发的全流程。从环境搭建、项目架构设计,到具体的编码实现和测试部署,我们覆盖了开发一个完整Web API项目所需的所有关键知识点。
核心要点回顾:
- 采用分层架构,保持代码清晰和可维护性
- 使用Spring Boot快速开发,减少配置工作
- 实现完整的CRUD操作和业务逻辑
- 添加适当的异常处理和日志记录
- 编写全面的测试用例
- 考虑安全性和性能优化
在实际项目开发中,还需要根据具体需求不断调整和优化架构设计,同时关注代码质量、团队协作和持续集成等工程实践。希望本文能为您的Java Web API开发之旅提供有力的帮助!
更多推荐
所有评论(0)