关于 spring-boot-rest-api-standards
面向 Spring Boot 开发者的 REST API 设计规范技能,涵盖端点、DTO、校验、错误处理、分页与安全头配置。适用于新建或审查 API。
主要功能
- 01资源式 URL 与 HTTP 方法规范
- 02状态码及错误响应设计
- 03DTO 与 Jakarta 校验
- 04分页、过滤与安全头配置
适用场景
- 01创建 REST 端点和 API 路由
- 02设计 DTO 与 API 契约
- 03实现统一异常处理和输入校验
- 04审查 REST API 架构
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ giuseppe-trisciuoglio/ developer-kit --skill spring-boot-rest-api-standards安装后在 Claude Code 中输入/spring-boot-rest-api-standards确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/architecture-patterns.mdexamples.mdhttp-reference.mdpagination-and-filtering.mdreferences.mdsecurity-headers.mdspring-web-annotations.md详细介绍
Spring Boot REST API 标准
概述
适用于 Spring Boot 的 REST API 设计标准,涵盖 URL 设计、HTTP 方法、状态码、DTO、验证、错误处理、分页和安全标头。
适用场景
- 创建 REST 端点和 API 路由
- 设计 DTO 和 API 契约
- 实现错误处理和验证
- 设置分页和筛选
- 配置安全标头和 CORS
- 审查 REST API 架构
说明
构建 RESTful API 端点
按照以下步骤创建设计良好的 REST API 端点:
-
设计基于资源的 URL
- 使用复数名词作为资源名称
- 遵循 REST 约定:GET /users、POST /users、PUT /users/{id}
- 避免使用基于操作的 URL,例如 /getUserList
-
使用恰当的 HTTP 方法
- GET:检索资源(安全、幂等)
- POST:创建资源(非幂等)
- PUT:替换整个资源(幂等)
- PATCH:部分更新(非幂等)
- DELETE:删除资源(幂等)
-
使用恰当的状态码
- 200 OK:GET/PUT/PATCH 请求成功
- 201 Created:POST 请求成功,并带有 Location 标头
- 204 No Content:DELETE 请求成功
- 400 Bad Request:请求数据无效
- 404 Not Found:资源不存在
- 409 Conflict:资源重复
- 500 Internal Server Error:发生意外错误
-
创建请求/响应 DTO
- 将 API 契约与领域实体分离
- 使用 Java record 或 Lombok
@Data/@Value - 应用 Jakarta 验证注解
- 尽可能让 DTO 保持不可变
-
实现验证
- 在
@RequestBody参数上使用@Valid注解 - 应用验证约束(
@NotBlank、@Email、@Size等) - 使用
MethodArgumentNotValidException处理验证错误
- 在
-
设置错误处理
- 使用
@RestControllerAdvice进行全局异常处理 - 返回包含状态、错误、消息和时间戳的标准化错误响应
- 对特定 HTTP 状态码使用
ResponseStatusException
- 使用
-
配置分页
- 对大型数据集使用 Pageable
- 包含 page、size、sort 参数
- 返回包含 total elements、totalPages 等信息的元数据
-
添加安全标头
- 配置 CORS 策略
- 设置内容安全策略
- 包含 X-Frame-Options、X-Content-Type-Options
验证检查点:
- 步骤 1-2 后:确认 URL 结构遵循 REST 约定(使用 /users,而非 /getUsers)
- 步骤 3 后:测试每个端点是否返回正确的状态码
- 步骤 4-5 后:在继续操作前,使用 curl 或 HTTPie 验证 DTO
- 步骤 6 后:确认错误响应符合标准格式
示例
基本 CRUD 控制器
@RestController
@RequestMapping("/v1/users")
@RequiredArgsConstructor
@Slf4j
public class UserController {
private final UserService userService;
@GetMapping
public ResponseEntity<Page<UserResponse>> getAllUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int pageSize) {
log.debug("Fetching users page {} size {}", page, pageSize);
Page<UserResponse> users = userService.getAll(page, pageSize);
return ResponseEntity.ok(users);
}
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUserById(@PathVariable Long id) {
return ResponseEntity.ok(userService.getById(id));
}
@PostMapping
public ResponseEntity<UserResponse> createUser(@Valid @RequestBody CreateUserRequest request) {
UserResponse created = userService.create(request);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
@PutMapping("/{id}")
public ResponseEntity<UserResponse> updateUser(
@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request) {
return ResponseEntity.ok(userService.update(id, request));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build();
}
}
请求/响应 DTO
// Request DTO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateUserRequest {
@NotBlank(message = "User name cannot be blank")
private String name;
@Email(message = "Valid email required")
private String email;
}
// Response DTO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserResponse {
private Long id;
private String name;
private String email;
private LocalDateTime createdAt;
}
全局异常处理器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(
MethodArgumentNotValidException ex, WebRequest request) {
String errors = ex.getBindingResult().getFieldErrors().stream()
.map(f -> f.getField() + ": " + f.getDefaultMessage())
.collect(Collectors.joining(", "));
ErrorResponse errorResponse = new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
"Validation Error",
"Validation failed: " + errors,
request.getDescription(false).replaceFirst("uri=", "")
);
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<ErrorResponse> handleResponseStatusException(
ResponseStatusException ex, WebRequest request) {
ErrorResponse error = new ErrorResponse(
ex.getStatusCode().value(),
ex.getStatusCode().toString(),
ex.getReason(),
request.getDescription(false).replaceFirst("uri=", "")
);
return new ResponseEntity<>(error, ex.getStatusCode());
}
}
最佳实践
1. 使用构造函数注入
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
}
2. 优先使用不可变 DTO(Java Records 或 @Value)
public record UserResponse(Long id, String name, String email) {}
3. 实现恰当的事务管理
@Service
@Transactional
public class UserService {
@Transactional(readOnly = true)
public Optional<User> findById(Long id) { return userRepository.findById(id); }
@Transactional
public User create(User user) { return userRepository.save(user); }
}
约束和警告
- 切勿直接暴露实体——使用 DTO 将 API 契约与领域模型分离
- 遵循 REST 约定——资源使用名词(/users),使用正确的 HTTP 方法、复数名称和恰当的状态码
- 全局处理所有异常——使用
@RestControllerAdvice,绝不让原始异常向外传播 - 始终对大型结果集进行分页——避免性能问题和 DDoS 漏洞
- 验证所有输入数据——在请求 DTO 上使用 Jakarta 验证注解
- 切勿暴露敏感数据——不要记录或暴露密码、令牌、个人身份信息
参考资料
- 查看
references/目录,获取全面的参考资料,包括 HTTP 状态码、Spring 注解和详细示例 - 参考
developer-kit-java:spring-boot-code-review-expertAgent,了解代码审查指南 - 查看
spring-boot-dependency-injection/SKILL.md,了解依赖注入模式 - 查看
../spring-boot-test-patterns/SKILL.md,了解 REST API 测试方法
评论
支持 Markdown