目录
- [🌐 39 Spring Boot Web实战------RESTful、参数校验、统一响应与Swagger文档](#🌐 39 Spring Boot Web实战——RESTful、参数校验、统一响应与Swagger文档)
-
- [一、RESTful API设计规范](#一、RESTful API设计规范)
-
- [1.1 什么是RESTful](#1.1 什么是RESTful)
- [1.2 RESTful核心原则](#1.2 RESTful核心原则)
- [1.3 HTTP方法与资源映射](#1.3 HTTP方法与资源映射)
- [1.4 URL设计规范](#1.4 URL设计规范)
- [二、Spring Boot构建RESTful接口](#二、Spring Boot构建RESTful接口)
-
- [2.1 项目依赖](#2.1 项目依赖)
- [2.2 实体类定义](#2.2 实体类定义)
- [2.3 Controller完整实现](#2.3 Controller完整实现)
- [2.4 常用注解速查](#2.4 常用注解速查)
- 三、参数校验实战
-
- [3.1 为什么需要参数校验](#3.1 为什么需要参数校验)
- [3.2 创建请求DTO](#3.2 创建请求DTO)
- [3.3 常用校验注解](#3.3 常用校验注解)
- [3.4 嵌套对象校验](#3.4 嵌套对象校验)
- [3.5 分组校验](#3.5 分组校验)
- 四、统一响应封装
-
- [4.1 为什么需要统一响应](#4.1 为什么需要统一响应)
- [4.2 统一响应类](#4.2 统一响应类)
- [4.3 分页结果封装](#4.3 分页结果封装)
- [4.4 使用示例](#4.4 使用示例)
- 五、全局异常处理
-
- [5.1 自定义业务异常](#5.1 自定义业务异常)
- [5.2 全局异常处理器](#5.2 全局异常处理器)
- [5.3 异常处理流程](#5.3 异常处理流程)
- 六、拦截器与过滤器
-
- [6.1 过滤器 vs 拦截器](#6.1 过滤器 vs 拦截器)
- [6.2 拦截器实现:请求日志](#6.2 拦截器实现:请求日志)
- [6.3 拦截器实现:Token鉴权](#6.3 拦截器实现:Token鉴权)
- [6.4 注册拦截器](#6.4 注册拦截器)
- [6.5 拦截器执行顺序](#6.5 拦截器执行顺序)
- 七、Swagger/Knife4j接口文档
-
- [7.1 添加依赖](#7.1 添加依赖)
- [7.2 配置文件](#7.2 配置文件)
- [7.3 为Controller添加文档注解](#7.3 为Controller添加文档注解)
- [7.4 Swagger常用注解](#7.4 Swagger常用注解)
- [7.5 DTO文档化](#7.5 DTO文档化)
- [7.6 访问文档](#7.6 访问文档)
- 八、完整项目实战
-
- [8.1 项目结构](#8.1 项目结构)
- [8.2 Service层完整实现](#8.2 Service层完整实现)
- [8.3 接口测试示例](#8.3 接口测试示例)
- 九、常见面试题解析
- 十、总结与下篇预告
-
- 本文核心要点
- [🎯 动手实践](#🎯 动手实践)
- [📖 下篇预告](#📖 下篇预告)
🌐 39 Spring Boot Web实战------RESTful、参数校验、统一响应与Swagger文档
更新日期 :2026年6月 | Java入门到精通系列 · 第五阶段·企业级开发
© 版权声明:本文为原创技术文章,转载请联系作者并注明出处。
一、RESTful API设计规范
1.1 什么是RESTful
REST(Representational State Transfer)是一种架构风格 ,它将后端服务看作一组资源,通过HTTP方法对资源进行操作。
1.2 RESTful核心原则
| 原则 | 说明 | 示例 |
|---|---|---|
| 资源标识 | 每个资源有唯一URI | /api/users/42 |
| 统一接口 | 使用HTTP方法表示操作 | GET/POST/PUT/DELETE |
| 无状态 | 每次请求包含所有信息 | Token放在Header |
| 分层系统 | 客户端无需关心服务端架构 | 网关、负载均衡透明 |
| 统一响应 | 规范化的返回格式 | {code, message, data} |
1.3 HTTP方法与资源映射
┌────────────┬──────────────────────┬────────────────────────┐
│ HTTP方法 │ URL路径 │ 含义 │
├────────────┼──────────────────────┼────────────────────────┤
│ GET │ /api/users │ 查询所有用户(列表) │
│ GET │ /api/users/{id} │ 查询指定用户 │
│ POST │ /api/users │ 创建新用户 │
│ PUT │ /api/users/{id} │ 全量更新用户 │
│ PATCH │ /api/users/{id} │ 部分更新用户 │
│ DELETE │ /api/users/{id} │ 删除指定用户 │
└────────────┴──────────────────────┴────────────────────────┘
1.4 URL设计规范
bash
# ✅ 正确示例
GET /api/v1/users # 获取用户列表
GET /api/v1/users/123 # 获取ID为123的用户
POST /api/v1/users # 创建用户
GET /api/v1/users/123/orders # 获取用户123的订单
# ❌ 错误示例
GET /api/getUsers # 不要用动词
POST /api/deleteUser/123 # 删除应该用DELETE方法
GET /api/user_list # 用复数名词,不用下划线
二、Spring Boot构建RESTful接口
2.1 项目依赖
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2.2 实体类定义
java
package com.example.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String username;
private String email;
private Integer age;
private String phone;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
2.3 Controller完整实现
java
package com.example.controller;
import com.example.entity.User;
import com.example.service.UserService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
/**
* 查询用户列表
* GET /api/v1/users
*/
@GetMapping
public List<User> listUsers() {
return userService.findAll();
}
/**
* 根据ID查询用户
* GET /api/v1/users/{id}
*/
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.findById(id);
}
/**
* 创建用户
* POST /api/v1/users
*/
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public User createUser(@Valid @RequestBody UserCreateRequest request) {
return userService.create(request);
}
/**
* 更新用户
* PUT /api/v1/users/{id}
*/
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id,
@Valid @RequestBody UserUpdateRequest request) {
return userService.update(id, request);
}
/**
* 删除用户
* DELETE /api/v1/users/{id}
*/
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
/**
* 条件查询
* GET /api/v1/users/search?keyword=zhang&page=1&size=10
*/
@GetMapping("/search")
public List<User> searchUsers(
@RequestParam(required = false) String keyword,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {
return userService.search(keyword, page, size);
}
}
2.4 常用注解速查
| 注解 | 作用 | 位置 |
|---|---|---|
@RestController |
组合了@Controller+@ResponseBody | 类上 |
@RequestMapping |
映射请求路径 | 类/方法上 |
@GetMapping |
映射GET请求 | 方法上 |
@PostMapping |
映射POST请求 | 方法上 |
@PutMapping |
映射PUT请求 | 方法上 |
@DeleteMapping |
映射DELETE请求 | 方法上 |
@PathVariable |
获取路径中的参数 | 参数上 |
@RequestParam |
获取查询参数 | 参数上 |
@RequestBody |
获取请求体JSON | 参数上 |
@ResponseStatus |
设置响应状态码 | 方法上 |
三、参数校验实战
3.1 为什么需要参数校验
永远不要信任客户端传来的数据! 参数校验是保障系统安全和数据完整性的第一道防线。
3.2 创建请求DTO
java
package com.example.dto;
import jakarta.validation.constraints.*;
import lombok.Data;
@Data
public class UserCreateRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度必须在2-20之间")
private String username;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotNull(message = "年龄不能为空")
@Min(value = 1, message = "年龄不能小于1")
@Max(value = 150, message = "年龄不能大于150")
private Integer age;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
}
3.3 常用校验注解
| 注解 | 作用 | 示例 |
|---|---|---|
@NotNull |
不能为null | @NotNull Integer age |
@NotBlank |
不能为null/空/空白 | @NotBlank String name |
@NotEmpty |
不能为null/空集合 | @NotEmpty List<String> tags |
@Size |
长度/大小限制 | @Size(min=2, max=20) |
@Min / @Max |
数值范围 | @Min(0) @Max(150) |
@Email |
邮箱格式 | @Email |
@Pattern |
正则匹配 | @Pattern(regexp="^1\\d{10}$") |
@Past / @Future |
日期范围 | @Past LocalDate birthday |
@Positive |
正数 | @Positive Double price |
3.4 嵌套对象校验
java
@Data
public class OrderCreateRequest {
@NotNull(message = "用户ID不能为空")
private Long userId;
@Valid // ← 加上@Valid才能触发嵌套校验
@NotNull(message = "收货地址不能为空")
private AddressDTO address;
@NotEmpty(message = "订单商品不能为空")
@Size(max = 50, message = "单次最多下单50件商品")
private List<@Valid OrderItemDTO> items;
}
@Data
public class AddressDTO {
@NotBlank(message = "省不能为空")
private String province;
@NotBlank(message = "市不能为空")
private String city;
@NotBlank(message = "详细地址不能为空")
private String detail;
}
3.5 分组校验
java
// 定义校验分组
public interface ValidationGroups {
interface Create {}
interface Update {}
}
@Data
public class UserRequest {
@Null(groups = ValidationGroups.Create.class, message = "创建时ID必须为空")
@NotNull(groups = ValidationGroups.Update.class, message = "更新时ID不能为空")
private Long id;
@NotBlank(message = "用户名不能为空")
private String username;
}
// Controller中指定分组
@PostMapping
public User createUser(@Validated(ValidationGroups.Create.class)
@RequestBody UserRequest request) { ... }
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id,
@Validated(ValidationGroups.Update.class)
@RequestBody UserRequest request) { ... }
四、统一响应封装
4.1 为什么需要统一响应
| 问题 | 不统一时 | 统一后 |
|---|---|---|
| 前端解析 | 每个接口返回结构不同,需分别处理 | 统一code/message/data结构 |
| 错误处理 | 有的返回字符串,有的返回对象 | 统一错误码体系 |
| 接口文档 | 格式混乱 | 规范、清晰 |
4.2 统一响应类
java
package com.example.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> {
private int code;
private String message;
private T data;
/** 成功 ------ 有数据 */
public static <T> ApiResponse<T> ok(T data) {
return new ApiResponse<>(200, "操作成功", data);
}
/** 成功 ------ 自定义消息 */
public static <T> ApiResponse<T> ok(String message, T data) {
return new ApiResponse<>(200, message, data);
}
/** 成功 ------ 无数据 */
public static <T> ApiResponse<T> ok() {
return new ApiResponse<>(200, "操作成功", null);
}
/** 失败 */
public static <T> ApiResponse<T> fail(int code, String message) {
return new ApiResponse<>(code, message, null);
}
/** 失败 ------ 默认错误码 */
public static <T> ApiResponse<T> fail(String message) {
return new ApiResponse<>(500, message, null);
}
/** 分页响应封装 */
public static <T> ApiResponse<PageResult<T>> okPage(
java.util.List<T> list, long total, int page, int size) {
return ok(new PageResult<>(list, total, page, size));
}
}
4.3 分页结果封装
java
@Data
@AllArgsConstructor
public class PageResult<T> {
private java.util.List<T> records;
private long total;
private int page;
private int size;
private long totalPages;
public PageResult(java.util.List<T> records, long total, int page, int size) {
this.records = records;
this.total = total;
this.page = page;
this.size = size;
this.totalPages = (total + size - 1) / size;
}
}
4.4 使用示例
java
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/{id}")
public ApiResponse<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return ApiResponse.ok(user);
}
@PostMapping
public ApiResponse<User> createUser(@Valid @RequestBody UserCreateRequest req) {
User user = userService.create(req);
return ApiResponse.ok("创建成功", user);
}
@GetMapping
public ApiResponse<PageResult<User>> listUsers(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {
PageResult<User> result = userService.findByPage(page, size);
return ApiResponse.ok(result);
}
}
五、全局异常处理
5.1 自定义业务异常
java
package com.example.exception;
import lombok.Getter;
@Getter
public class BusinessException extends RuntimeException {
private final int code;
public BusinessException(String message) {
super(message);
this.code = 400;
}
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
// ---- 常用静态工厂 ----
public static BusinessException notFound(String resource) {
return new BusinessException(404, resource + "不存在");
}
public static BusinessException duplicate(String field) {
return new BusinessException(409, field + "已存在");
}
}
5.2 全局异常处理器
java
package com.example.exception;
import com.example.common.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.stream.Collectors;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理业务异常
*/
@ExceptionHandler(BusinessException.class)
public ApiResponse<Void> handleBusinessException(BusinessException e) {
log.warn("业务异常: code={}, message={}", e.getCode(), e.getMessage());
return ApiResponse.fail(e.getCode(), e.getMessage());
}
/**
* 处理参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Void> handleValidationException(
MethodArgumentNotValidException e) {
String errors = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining("; "));
log.warn("参数校验失败: {}", errors);
return ApiResponse.fail(400, errors);
}
/**
* 处理其他未捕获异常
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ApiResponse<Void> handleException(Exception e) {
log.error("系统异常", e);
return ApiResponse.fail(500, "系统内部错误,请稍后再试");
}
}
5.3 异常处理流程
客户端请求
│
▼
Controller方法
│
├── 正常返回 ──► @RestControllerAdvice (不干预)
│
└── 抛出异常
│
├── BusinessException ──► 返回 code + message
│
├── MethodArgumentNotValidException ──► 返回参数错误详情
│
└── 其他Exception ──► 记录日志,返回 "系统内部错误"
六、拦截器与过滤器
6.1 过滤器 vs 拦截器
| 对比项 | Filter(过滤器) | Interceptor(拦截器) |
|---|---|---|
| 规范 | Servlet规范 | Spring MVC框架 |
| 作用范围 | 所有请求(含静态资源) | 只拦截Controller请求 |
| 可获取信息 | 只有request/response | 可获取handler信息 |
| 执行顺序 | 先于拦截器 | 后于过滤器 |
| 典型用途 | 编码、CORS、请求体缓存 | 鉴权、日志、限流 |
6.2 拦截器实现:请求日志
java
package com.example.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Slf4j
@Component
public class RequestLogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
request.setAttribute("startTime", System.currentTimeMillis());
log.info("→ {} {} from {}",
request.getMethod(),
request.getRequestURI(),
request.getRemoteAddr());
return true; // 返回false会中断请求
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
long startTime = (long) request.getAttribute("startTime");
long duration = System.currentTimeMillis() - startTime;
log.info("← {} {} [{}] {}ms",
request.getMethod(),
request.getRequestURI(),
response.getStatus(),
duration);
}
}
6.3 拦截器实现:Token鉴权
java
@Slf4j
@Component
@RequiredArgsConstructor
public class AuthInterceptor implements HandlerInterceptor {
private final JwtTokenUtil jwtTokenUtil;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// 放行OPTIONS预检请求
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
return true;
}
// 获取Token
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.setStatus(401);
return false;
}
token = token.substring(7);
// 验证Token
if (!jwtTokenUtil.validateToken(token)) {
response.setStatus(401);
return false;
}
// 将用户信息放入请求属性
Long userId = jwtTokenUtil.getUserId(token);
request.setAttribute("currentUserId", userId);
return true;
}
}
6.4 注册拦截器
java
package com.example.config;
import com.example.interceptor.AuthInterceptor;
import com.example.interceptor.RequestLogInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
private final RequestLogInterceptor requestLogInterceptor;
private final AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 请求日志拦截器 ------ 拦截所有请求
registry.addInterceptor(requestLogInterceptor)
.addPathPatterns("/api/**");
// 鉴权拦截器 ------ 排除登录等公开接口
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns(
"/api/v1/auth/login",
"/api/v1/auth/register",
"/api/v1/public/**"
);
}
}
6.5 拦截器执行顺序
请求 → Filter链 → DispatcherServlet
│
▼
preHandle(拦截器1) → preHandle(拦截器2)
│
▼
Controller方法执行
│
▼
postHandle(拦截器2) → postHandle(拦截器1)
│
▼
afterCompletion(拦截器2) → afterCompletion(拦截器1)
│
▼
Filter链 → 响应
七、Swagger/Knife4j接口文档
7.1 添加依赖
xml
<!-- 方式一:使用SpringDoc + Knife4j(推荐,Spring Boot 3.x) -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>
7.2 配置文件
yaml
# application.yml
springdoc:
swagger-ui:
path: /swagger-ui.html # Swagger UI路径
tags-sorter: alpha # 按名称排序
operations-sorter: alpha # 按方法排序
api-docs:
path: /v3/api-docs # OpenAPI JSON路径
knife4j:
enable: true # 启用Knife4j增强
setting:
language: zh_cn # 中文界面
7.3 为Controller添加文档注解
java
@Tag(name = "用户管理", description = "用户的CRUD操作")
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@Operation(summary = "查询用户列表", description = "支持分页和关键词搜索")
@GetMapping
public ApiResponse<PageResult<User>> listUsers(
@ParameterObject PageRequest pageRequest) {
return ApiResponse.ok(userService.findByPage(pageRequest));
}
@Operation(summary = "根据ID查询用户")
@GetMapping("/{id}")
public ApiResponse<User> getUser(
@Parameter(description = "用户ID", example = "1", required = true)
@PathVariable Long id) {
return ApiResponse.ok(userService.findById(id));
}
@Operation(summary = "创建用户")
@PostMapping
public ApiResponse<User> createUser(
@Valid @RequestBody UserCreateRequest request) {
return ApiResponse.ok("创建成功", userService.create(request));
}
@Operation(summary = "删除用户")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "删除成功"),
@ApiResponse(responseCode = "404", description = "用户不存在")
})
@DeleteMapping("/{id}")
public ApiResponse<Void> deleteUser(@PathVariable Long id) {
userService.delete(id);
return ApiResponse.ok();
}
}
7.4 Swagger常用注解
| 注解 | 作用位置 | 说明 |
|---|---|---|
@Tag |
Controller类 | 模块/分组描述 |
@Operation |
方法 | 接口描述 |
@Parameter |
参数 | 请求参数描述 |
@ParameterObject |
对象参数 | 自动解析对象字段 |
@Schema |
DTO/Entity | 数据模型描述 |
@ApiResponse |
方法 | 响应状态码描述 |
7.5 DTO文档化
java
@Schema(description = "用户创建请求")
@Data
public class UserCreateRequest {
@Schema(description = "用户名", example = "zhangsan", requiredMode = RequiredMode.REQUIRED)
@NotBlank(message = "用户名不能为空")
private String username;
@Schema(description = "邮箱", example = "zhangsan@example.com")
@NotBlank @Email
private String email;
@Schema(description = "年龄", example = "25", minimum = "1", maximum = "150")
@NotNull @Min(1) @Max(150)
private Integer age;
}
7.6 访问文档
启动应用后,访问以下地址:
| 地址 | 说明 |
|---|---|
http://localhost:8080/doc.html |
Knife4j增强文档(推荐) |
http://localhost:8080/swagger-ui.html |
原生Swagger UI |
http://localhost:8080/v3/api-docs |
OpenAPI 3.0 JSON |
八、完整项目实战
8.1 项目结构
src/main/java/com/example/
├── common/
│ ├── ApiResponse.java # 统一响应
│ └── PageResult.java # 分页结果
├── config/
│ └── WebMvcConfig.java # 拦截器注册
├── controller/
│ └── UserController.java # 用户控制器
├── dto/
│ ├── UserCreateRequest.java # 创建请求
│ ├── UserUpdateRequest.java # 更新请求
│ └── UserSearchRequest.java # 搜索请求
├── entity/
│ └── User.java # 用户实体
├── exception/
│ ├── BusinessException.java # 业务异常
│ └── GlobalExceptionHandler.java # 全局异常
├── interceptor/
│ ├── AuthInterceptor.java # 鉴权拦截器
│ └── RequestLogInterceptor.java # 日志拦截器
├── service/
│ ├── UserService.java # 用户服务接口
│ └── UserServiceImpl.java # 用户服务实现
└── Application.java # 启动类
8.2 Service层完整实现
java
@Slf4j
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
// 模拟数据库(实际项目用MyBatis/JPA)
private final Map<Long, User> userStore = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong(0);
@Override
public List<User> findAll() {
return new ArrayList<>(userStore.values());
}
@Override
public User findById(Long id) {
User user = userStore.get(id);
if (user == null) {
throw BusinessException.notFound("用户");
}
return user;
}
@Override
@Transactional
public User create(UserCreateRequest request) {
// 检查用户名是否重复
boolean exists = userStore.values().stream()
.anyMatch(u -> u.getUsername().equals(request.getUsername()));
if (exists) {
throw BusinessException.duplicate("用户名");
}
long id = idGenerator.incrementAndGet();
User user = User.builder()
.id(id)
.username(request.getUsername())
.email(request.getEmail())
.age(request.getAge())
.phone(request.getPhone())
.createTime(LocalDateTime.now())
.build();
userStore.put(id, user);
log.info("用户创建成功: id={}, username={}", id, user.getUsername());
return user;
}
@Override
@Transactional
public User update(Long id, UserUpdateRequest request) {
User existing = findById(id);
existing.setUsername(request.getUsername());
existing.setEmail(request.getEmail());
existing.setAge(request.getAge());
existing.setUpdateTime(LocalDateTime.now());
log.info("用户更新成功: id={}", id);
return existing;
}
@Override
@Transactional
public void delete(Long id) {
if (userStore.remove(id) == null) {
throw BusinessException.notFound("用户");
}
log.info("用户删除成功: id={}", id);
}
@Override
public PageResult<User> search(String keyword, int page, int size) {
List<User> filtered = userStore.values().stream()
.filter(u -> keyword == null ||
u.getUsername().contains(keyword) ||
u.getEmail().contains(keyword))
.collect(Collectors.toList());
long total = filtered.size();
int from = (page - 1) * size;
List<User> pageData = filtered.stream()
.skip(from)
.limit(size)
.collect(Collectors.toList());
return new PageResult<>(pageData, total, page, size);
}
}
8.3 接口测试示例
bash
# 创建用户
curl -X POST http://localhost:8080/api/v1/users \
-H "Content-Type: application/json" \
-d '{"username":"zhangsan","email":"zhangsan@example.com","age":25,"phone":"13800138000"}'
# 响应
# {"code":200,"message":"创建成功","data":{"id":1,"username":"zhangsan",...}}
# 查询用户
curl http://localhost:8080/api/v1/users/1
# 更新用户
curl -X PUT http://localhost:8080/api/v1/users/1 \
-H "Content-Type: application/json" \
-d '{"username":"zhangsan_new","email":"new@example.com","age":26}'
# 删除用户
curl -X DELETE http://localhost:8080/api/v1/users/1
# 分页搜索
curl "http://localhost:8080/api/v1/users/search?keyword=zhang&page=1&size=10"
九、常见面试题解析
Q1:@RestController和@Controller的区别?
@Controller返回视图名,配合ViewResolver渲染页面;@RestController相当于@Controller+@ResponseBody,直接将返回值序列化为JSON。
Q2:@Valid和@Validated的区别?
@Valid是Jakarta标准注解,支持嵌套校验;@Validated是Spring注解,额外支持分组校验 。嵌套校验用@Valid,分组校验用@Validated。
Q3:拦截器和过滤器的执行顺序?
先执行Filter链,再进入Spring MVC的Interceptor链。具体的拦截器执行顺序由注册顺序决定。
Q4:@RestControllerAdvice的作用?
它是
@ControllerAdvice+@ResponseBody的组合,用于定义全局异常处理、数据绑定和数据预处理。
Q5:如何实现接口版本控制?
常见方案:
- URL路径版本:
/api/v1/users、/api/v2/users- 请求头版本:
X-API-Version: 1- 参数版本:
/api/users?version=1
十、总结与下篇预告
本文核心要点
| 知识点 | 关键内容 |
|---|---|
| RESTful | 资源导向、HTTP方法语义、URL设计规范 |
| 参数校验 | Jakarta Validation、嵌套校验、分组校验 |
| 统一响应 | ApiResponse封装、分页结果 |
| 全局异常 | BusinessException、@RestControllerAdvice |
| 拦截器 | HandlerInterceptor、执行顺序、鉴权场景 |
| Swagger | SpringDoc + Knife4j、注解体系 |
🎯 动手实践
- 创建一个完整的CRUD项目,包含User和Order两张表
- 实现自定义校验注解(如
@PhoneNumber) - 添加一个限流拦截器(基于Redis计数器)
- 为所有接口添加Swagger文档注解
📖 下篇预告
第40篇:Redis与微服务入门------将学习Redis数据类型、Spring Data Redis集成、缓存策略设计以及Spring Cloud微服务架构简介,为进入分布式开发打下基础!
📚 参考资料