API 设计规范
RuoYi-Plus 项目的API接口设计规范,包含RESTful设计、响应格式、错误处理等最佳实践。
📋 设计原则
1. RESTful 风格
采用 RESTful 架构风格设计API,使用HTTP动词表示操作类型。
java
// ✅ 好的RESTful设计
@RestController
@RequestMapping("/api/users")
public class UserController {
// GET /api/users - 获取用户列表
@GetMapping
public R<PageResult<UserVo>> getUsers(@RequestParam Map<String, Object> params) {
return R.ok(userService.queryPageList(params));
}
// GET /api/users/123 - 获取指定用户
@GetMapping("/{id}")
public R<UserVo> getUser(@PathVariable Long id) {
return R.ok(userService.selectUserById(id));
}
// POST /api/users - 创建用户
@PostMapping
public R<UserVo> createUser(@RequestBody UserBo user) {
return R.ok(userService.insertUser(user));
}
// PUT /api/users/123 - 更新用户
@PutMapping("/{id}")
public R<UserVo> updateUser(@PathVariable Long id, @RequestBody UserBo user) {
user.setUserId(id);
return R.ok(userService.updateUser(user));
}
// DELETE /api/users/123 - 删除用户
@DeleteMapping("/{id}")
public R<Void> deleteUser(@PathVariable Long id) {
userService.deleteUserById(id);
return R.ok();
}
// PATCH /api/users/123/status - 部分更新(状态)
@PatchMapping("/{id}/status")
public R<Void> updateUserStatus(@PathVariable Long id, @RequestBody StatusBo status) {
userService.updateUserStatus(id, status.getStatus());
return R.ok();
}
}
// ❌ 不好的设计
@RestController
@RequestMapping("/api/user")
public class UserController {
@PostMapping("/getUserList") // 应该用GET
public R<List<UserVo>> getUserList() { }
@GetMapping("/addUser") // 应该用POST
public R<Void> addUser() { }
@PostMapping("/updateUser") // 应该用PUT
public R<Void> updateUser() { }
}2. 统一响应格式
所有API接口使用统一的响应格式。
java
/**
* 统一响应结果
*/
@Data
public class R<T> implements Serializable {
/** 成功 */
public static final int SUCCESS = 200;
/** 失败 */
public static final int FAIL = 500;
private int code;
private String msg;
private T data;
public static <T> R<T> ok() {
return restResult(null, SUCCESS, "操作成功");
}
public static <T> R<T> ok(T data) {
return restResult(data, SUCCESS, "操作成功");
}
public static <T> R<T> ok(String msg) {
return restResult(null, SUCCESS, msg);
}
public static <T> R<T> ok(String msg, T data) {
return restResult(data, SUCCESS, msg);
}
public static <T> R<T> fail() {
return restResult(null, FAIL, "操作失败");
}
public static <T> R<T> fail(String msg) {
return restResult(null, FAIL, msg);
}
public static <T> R<T> fail(T data) {
return restResult(data, FAIL, "操作失败");
}
public static <T> R<T> fail(String msg, T data) {
return restResult(data, FAIL, msg);
}
public static <T> R<T> fail(int code, String msg) {
return restResult(null, code, msg);
}
private static <T> R<T> restResult(T data, int code, String msg) {
R<T> r = new R<>();
r.setCode(code);
r.setData(data);
r.setMsg(msg);
return r;
}
}
// 使用示例
@RestController
public class UserController {
@GetMapping("/users/{id}")
public R<UserVo> getUser(@PathVariable Long id) {
UserVo user = userService.selectUserById(id);
return R.ok(user);
}
@PostMapping("/users")
public R<Long> createUser(@RequestBody UserBo user) {
Long userId = userService.insertUser(user);
return R.ok("用户创建成功", userId);
}
@DeleteMapping("/users/{id}")
public R<Void> deleteUser(@PathVariable Long id) {
boolean success = userService.deleteUserById(id);
return success ? R.ok("删除成功") : R.fail("删除失败");
}
}3. 分页响应格式
统一的分页响应格式。
java
/**
* 分页响应结果
*/
@Data
public class PageResult<T> implements Serializable {
/** 总记录数 */
private Long total;
/** 列表数据 */
private List<T> records;
/** 当前页码 */
private Long current;
/** 每页显示记录数 */
private Long size;
/** 总页数 */
private Long pages;
public static <T> PageResult<T> build(Page<T> page) {
PageResult<T> result = new PageResult<>();
result.setRecords(page.getRecords());
result.setTotal(page.getTotal());
result.setCurrent(page.getCurrent());
result.setSize(page.getSize());
result.setPages(page.getPages());
return result;
}
public static <T> PageResult<T> build(List<T> list, Long total) {
PageResult<T> result = new PageResult<>();
result.setRecords(list);
result.setTotal(total);
return result;
}
}
// 分页查询示例
@GetMapping("/users")
public R<PageResult<UserVo>> getUsers(UserQuery query, PageQuery pageQuery) {
PageResult<UserVo> result = userService.queryPageList(query, pageQuery);
return R.ok(result);
}🔗 URL 设计
资源命名
java
// ✅ 好的资源命名
/api/users // 用户资源
/api/users/123 // 特定用户
/api/users/123/roles // 用户的角色
/api/users/123/permissions // 用户的权限
/api/departments // 部门资源
/api/departments/456/users // 部门下的用户
// ❌ 不好的资源命名
/api/user // 单数形式
/api/getUserList // 动词形式
/api/user_management // 下划线分隔
/api/users-list // 混合命名查询参数
java
// ✅ 好的查询参数设计
@GetMapping("/users")
public R<PageResult<UserVo>> getUsers(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String name,
@RequestParam(required = false) String email,
@RequestParam(required = false) String status,
@RequestParam(required = false) String sort,
@RequestParam(required = false) String order) {
// 构建查询条件
UserQuery query = UserQuery.builder()
.name(name)
.email(email)
.status(status)
.build();
PageQuery pageQuery = PageQuery.builder()
.pageNum(page)
.pageSize(size)
.orderByColumn(sort)
.isAsc("asc".equalsIgnoreCase(order))
.build();
return R.ok(userService.queryPageList(query, pageQuery));
}
// 使用示例
// GET /api/users?page=1&size=20&name=admin&status=active&sort=createTime&order=desc过滤和搜索
java
@RestController
@RequestMapping("/api/users")
public class UserController {
// 基础过滤
@GetMapping
public R<PageResult<UserVo>> getUsers(UserQuery query, PageQuery pageQuery) {
return R.ok(userService.queryPageList(query, pageQuery));
}
// 搜索功能
@GetMapping("/search")
public R<List<UserVo>> searchUsers(@RequestParam String keyword) {
List<UserVo> users = userService.searchUsers(keyword);
return R.ok(users);
}
// 高级搜索
@PostMapping("/search")
public R<PageResult<UserVo>> advancedSearch(@RequestBody UserSearchBo searchBo, PageQuery pageQuery) {
PageResult<UserVo> result = userService.advancedSearch(searchBo, pageQuery);
return R.ok(result);
}
}
@Data
public class UserSearchBo {
private String keyword;
private List<String> statuses;
private List<Long> departmentIds;
private DateRange createTimeRange;
private DateRange loginTimeRange;
}📊 状态码设计
HTTP状态码
java
@RestController
public class UserController {
// 200 OK - 成功
@GetMapping("/users/{id}")
public ResponseEntity<UserVo> getUser(@PathVariable Long id) {
UserVo user = userService.selectUserById(id);
return ResponseEntity.ok(user);
}
// 201 Created - 创建成功
@PostMapping("/users")
public ResponseEntity<UserVo> createUser(@RequestBody UserBo user) {
UserVo createdUser = userService.insertUser(user);
return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
}
// 204 No Content - 删除成功
@DeleteMapping("/users/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUserById(id);
return ResponseEntity.noContent().build();
}
// 400 Bad Request - 请求参数错误
@PostMapping("/users")
public ResponseEntity<R<Void>> createUser(@Valid @RequestBody UserBo user) {
// Spring Validation 会自动返回400状态码
return ResponseEntity.ok(R.ok());
}
// 404 Not Found - 资源不存在
@GetMapping("/users/{id}")
public ResponseEntity<UserVo> getUser(@PathVariable Long id) {
UserVo user = userService.selectUserById(id);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(user);
}
}业务状态码
java
/**
* 业务状态码枚举
*/
public enum BusinessCode {
SUCCESS(200, "操作成功"),
// 系统异常 1000-1999
SYSTEM_ERROR(1000, "系统异常"),
SYSTEM_BUSY(1001, "系统繁忙"),
SYSTEM_TIMEOUT(1002, "系统超时"),
// 参数异常 2000-2999
PARAM_ERROR(2000, "参数错误"),
PARAM_MISSING(2001, "缺少必要参数"),
PARAM_INVALID(2002, "参数格式不正确"),
// 认证异常 3000-3999
AUTH_FAILED(3000, "认证失败"),
TOKEN_INVALID(3001, "Token无效"),
TOKEN_EXPIRED(3002, "Token已过期"),
// 权限异常 4000-4999
PERMISSION_DENIED(4000, "权限不足"),
ACCESS_FORBIDDEN(4001, "访问被拒绝"),
// 用户异常 5000-5999
USER_NOT_FOUND(5001, "用户不存在"),
USER_EXISTS(5002, "用户已存在"),
PASSWORD_ERROR(5003, "密码错误"),
ACCOUNT_DISABLED(5004, "账户已被禁用"),
// 业务异常 6000-9999
DEPT_NOT_FOUND(6001, "部门不存在"),
ROLE_NOT_FOUND(6002, "角色不存在");
private final Integer code;
private final String message;
BusinessCode(Integer code, String message) {
this.code = code;
this.message = message;
}
// getter methods...
}
// 使用示例
@Service
public class UserServiceImpl implements IUserService {
public UserVo selectUserById(Long userId) {
UserVo user = baseMapper.selectVoById(userId);
if (ObjectUtil.isNull(user)) {
throw new ServiceException(BusinessCode.USER_NOT_FOUND.getCode(),
BusinessCode.USER_NOT_FOUND.getMessage());
}
return user;
}
}📝 请求响应设计
请求体设计
java
// ✅ 好的请求体设计
@Data
@Valid
public class CreateUserRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度在2-20之间")
private String username;
@NotBlank(message = "密码不能为空")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$",
message = "密码必须包含大小写字母和数字,长度至少8位")
private String password;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@NotNull(message = "部门ID不能为空")
private Long departmentId;
private List<Long> roleIds;
}
@Data
@Valid
public class UpdateUserRequest {
@Size(min = 2, max = 20, message = "用户名长度在2-20之间")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
private Long departmentId;
private List<Long> roleIds;
private String status;
}
// ❌ 不好的请求体设计
@Data
public class UserRequest {
private String action; // 不应该在请求体中指定操作类型
private Map<String, Object> data; // 不应该使用通用Map
}响应体设计
java
// ✅ 好的响应体设计
@Data
public class UserResponse {
private Long id;
private String username;
private String email;
private String phone;
private String status;
private String statusName;
private DepartmentResponse department;
private List<RoleResponse> roles;
private Date createTime;
private Date updateTime;
}
@Data
public class DepartmentResponse {
private Long id;
private String name;
private String code;
}
@Data
public class RoleResponse {
private Long id;
private String name;
private String code;
private String description;
}
// ❌ 不好的响应体设计
@Data
public class UserResponse {
private Map<String, Object> user; // 不应该使用通用Map
private List<Map<String, Object>> roles; // 缺少类型安全
}🔒 安全设计
输入验证
java
@RestController
@Validated
public class UserController {
@PostMapping("/users")
public R<UserVo> createUser(@Valid @RequestBody CreateUserRequest request) {
// Spring Validation会自动验证
return R.ok(userService.createUser(request));
}
@GetMapping("/users/{id}")
public R<UserVo> getUser(@PathVariable @Positive(message = "用户ID必须为正数") Long id) {
return R.ok(userService.selectUserById(id));
}
@GetMapping("/users")
public R<PageResult<UserVo>> getUsers(
@RequestParam @Min(value = 1, message = "页码最小为1") Integer page,
@RequestParam @Range(min = 1, max = 100, message = "每页记录数在1-100之间") Integer size) {
return R.ok(userService.queryPageList(page, size));
}
}权限控制
java
@RestController
@RequestMapping("/api/users")
public class UserController {
@SaCheckPermission("system:user:list")
@GetMapping
public R<PageResult<UserVo>> getUsers() {
return R.ok(userService.queryPageList());
}
@SaCheckPermission("system:user:add")
@PostMapping
public R<UserVo> createUser(@RequestBody CreateUserRequest request) {
return R.ok(userService.createUser(request));
}
@SaCheckPermission("system:user:edit")
@PutMapping("/{id}")
public R<UserVo> updateUser(@PathVariable Long id, @RequestBody UpdateUserRequest request) {
return R.ok(userService.updateUser(id, request));
}
@SaCheckPermission("system:user:remove")
@DeleteMapping("/{id}")
public R<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return R.ok();
}
}数据脱敏
java
@Data
public class UserResponse {
private Long id;
private String username;
@JsonSerialize(using = PhoneDesensitizeSerializer.class)
private String phone;
@JsonSerialize(using = EmailDesensitizeSerializer.class)
private String email;
@JsonIgnore
private String password; // 敏感信息不返回
}
/**
* 手机号脱敏序列化器
*/
public class PhoneDesensitizeSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
if (StringUtils.isNotBlank(value) && value.length() == 11) {
String desensitized = value.substring(0, 3) + "****" + value.substring(7);
gen.writeString(desensitized);
} else {
gen.writeString(value);
}
}
}📚 API文档
OpenAPI规范
java
@Tag(name = "用户管理", description = "用户相关的增删改查操作")
@RestController
@RequestMapping("/api/users")
public class UserController {
@Operation(summary = "获取用户列表", description = "分页查询用户列表,支持条件筛选")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "查询成功"),
@ApiResponse(responseCode = "400", description = "参数错误"),
@ApiResponse(responseCode = "401", description = "未认证"),
@ApiResponse(responseCode = "403", description = "权限不足")
})
@GetMapping
public R<PageResult<UserVo>> getUsers(
@Parameter(description = "页码", example = "1") @RequestParam(defaultValue = "1") Integer page,
@Parameter(description = "每页记录数", example = "10") @RequestParam(defaultValue = "10") Integer size,
@Parameter(description = "用户名", example = "admin") @RequestParam(required = false) String username,
@Parameter(description = "状态", example = "active") @RequestParam(required = false) String status) {
return R.ok(userService.queryPageList(page, size, username, status));
}
@Operation(summary = "创建用户", description = "创建新用户")
@PostMapping
public R<UserVo> createUser(
@Parameter(description = "用户信息") @Valid @RequestBody CreateUserRequest request) {
return R.ok(userService.createUser(request));
}
}🔄 版本控制
URL 版本控制
java
// ✅ 推荐 - URL路径版本控制
@RestController
@RequestMapping("/api/v1/users")
public class UserV1Controller {
@GetMapping("/{id}")
public R<UserVo> getUser(@PathVariable Long id) {
return R.ok(userService.selectUserById(id));
}
}
@RestController
@RequestMapping("/api/v2/users")
public class UserV2Controller {
@GetMapping("/{id}")
public R<UserDetailVo> getUser(@PathVariable Long id) {
// V2版本返回更详细的用户信息
return R.ok(userService.selectUserDetailById(id));
}
}请求头版本控制
java
// 通过请求头指定版本
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping(value = "/{id}", headers = "X-API-Version=1")
public R<UserVo> getUserV1(@PathVariable Long id) {
return R.ok(userService.selectUserById(id));
}
@GetMapping(value = "/{id}", headers = "X-API-Version=2")
public R<UserDetailVo> getUserV2(@PathVariable Long id) {
return R.ok(userService.selectUserDetailById(id));
}
}版本兼容策略
java
/**
* 版本兼容处理
*/
@Component
public class ApiVersionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
String version = request.getHeader("X-API-Version");
if (StringUtils.isBlank(version)) {
version = "1"; // 默认版本
}
// 将版本信息存入请求属性
request.setAttribute("apiVersion", version);
return true;
}
}
// 在 Service 层根据版本处理
@Service
public class UserServiceImpl implements IUserService {
public Object getUserByVersion(Long id, String version) {
return switch (version) {
case "2" -> selectUserDetailById(id);
default -> selectUserById(id);
};
}
}⚠️ 错误处理规范
全局异常处理
java
/**
* 全局异常处理器
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 业务异常
*/
@ExceptionHandler(ServiceException.class)
public R<Void> handleServiceException(ServiceException e, HttpServletRequest request) {
log.error("请求地址: {}, 业务异常: {}", request.getRequestURI(), e.getMessage());
return R.fail(e.getCode(), e.getMessage());
}
/**
* 参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public R<Map<String, String>> handleValidException(MethodArgumentNotValidException e) {
Map<String, String> errors = new HashMap<>();
e.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
return R.fail(BusinessCode.PARAM_ERROR.getCode(), "参数校验失败", errors);
}
/**
* 参数绑定异常
*/
@ExceptionHandler(BindException.class)
public R<Void> handleBindException(BindException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
return R.fail(BusinessCode.PARAM_ERROR.getCode(), message);
}
/**
* 约束违反异常
*/
@ExceptionHandler(ConstraintViolationException.class)
public R<Void> handleConstraintViolationException(ConstraintViolationException e) {
String message = e.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(", "));
return R.fail(BusinessCode.PARAM_ERROR.getCode(), message);
}
/**
* 权限不足异常
*/
@ExceptionHandler(NotPermissionException.class)
public R<Void> handleNotPermissionException(NotPermissionException e) {
return R.fail(BusinessCode.PERMISSION_DENIED.getCode(), "权限不足: " + e.getMessage());
}
/**
* 认证失败异常
*/
@ExceptionHandler(NotLoginException.class)
public R<Void> handleNotLoginException(NotLoginException e) {
return R.fail(BusinessCode.AUTH_FAILED.getCode(), "认证失败: " + e.getMessage());
}
/**
* 请求方法不支持异常
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public R<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e) {
return R.fail(BusinessCode.PARAM_ERROR.getCode(),
"不支持的请求方法: " + e.getMethod());
}
/**
* 资源不存在异常
*/
@ExceptionHandler(NoHandlerFoundException.class)
public R<Void> handleNoHandlerFoundException(NoHandlerFoundException e) {
return R.fail(404, "资源不存在: " + e.getRequestURL());
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public R<Void> handleException(Exception e, HttpServletRequest request) {
log.error("请求地址: {}, 系统异常: ", request.getRequestURI(), e);
return R.fail(BusinessCode.SYSTEM_ERROR.getCode(), "系统繁忙,请稍后重试");
}
}错误响应格式
java
/**
* 错误详情响应
*/
@Data
@Builder
public class ErrorDetail {
/** 错误码 */
private Integer code;
/** 错误信息 */
private String message;
/** 错误详情 */
private String detail;
/** 请求路径 */
private String path;
/** 时间戳 */
private Long timestamp;
/** 追踪ID */
private String traceId;
/** 字段错误列表 */
private List<FieldError> fieldErrors;
@Data
@Builder
public static class FieldError {
private String field;
private String message;
private Object rejectedValue;
}
}
// 使用示例
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorDetail> handleValidException(
MethodArgumentNotValidException e, HttpServletRequest request) {
List<ErrorDetail.FieldError> fieldErrors = e.getBindingResult().getFieldErrors()
.stream()
.map(error -> ErrorDetail.FieldError.builder()
.field(error.getField())
.message(error.getDefaultMessage())
.rejectedValue(error.getRejectedValue())
.build())
.collect(Collectors.toList());
ErrorDetail errorDetail = ErrorDetail.builder()
.code(400)
.message("参数校验失败")
.path(request.getRequestURI())
.timestamp(System.currentTimeMillis())
.traceId(MDC.get("traceId"))
.fieldErrors(fieldErrors)
.build();
return ResponseEntity.badRequest().body(errorDetail);
}📦 批量操作设计
批量查询
java
@RestController
@RequestMapping("/api/users")
public class UserController {
/**
* 批量查询用户 - GET 方式
*/
@GetMapping("/batch")
public R<List<UserVo>> getUsersByIds(
@RequestParam @Size(max = 100, message = "一次最多查询100条") List<Long> ids) {
return R.ok(userService.selectUsersByIds(ids));
}
/**
* 批量查询用户 - POST 方式(推荐,URL长度无限制)
*/
@PostMapping("/batch/query")
public R<List<UserVo>> batchQueryUsers(
@RequestBody @Valid BatchQueryRequest request) {
return R.ok(userService.selectUsersByIds(request.getIds()));
}
}
@Data
public class BatchQueryRequest {
@NotEmpty(message = "ID列表不能为空")
@Size(max = 100, message = "一次最多查询100条")
private List<Long> ids;
}批量创建
java
/**
* 批量创建用户
*/
@PostMapping("/batch")
@SaCheckPermission("system:user:add")
public R<BatchCreateResult> batchCreateUsers(
@RequestBody @Valid @Size(max = 100, message = "一次最多创建100条") List<CreateUserRequest> requests) {
BatchCreateResult result = userService.batchCreateUsers(requests);
return R.ok(result);
}
@Data
public class BatchCreateResult {
/** 成功数量 */
private Integer successCount;
/** 失败数量 */
private Integer failCount;
/** 成功的ID列表 */
private List<Long> successIds;
/** 失败详情 */
private List<FailDetail> failDetails;
@Data
public static class FailDetail {
private Integer index;
private String reason;
private Object data;
}
}批量更新
java
/**
* 批量更新用户状态
*/
@PatchMapping("/batch/status")
@SaCheckPermission("system:user:edit")
public R<BatchUpdateResult> batchUpdateStatus(
@RequestBody @Valid BatchUpdateStatusRequest request) {
BatchUpdateResult result = userService.batchUpdateStatus(
request.getIds(), request.getStatus());
return R.ok(result);
}
@Data
public class BatchUpdateStatusRequest {
@NotEmpty(message = "ID列表不能为空")
@Size(max = 100, message = "一次最多更新100条")
private List<Long> ids;
@NotBlank(message = "状态不能为空")
private String status;
}批量删除
java
/**
* 批量删除用户
*/
@DeleteMapping("/batch")
@SaCheckPermission("system:user:remove")
public R<BatchDeleteResult> batchDeleteUsers(
@RequestBody @Valid BatchDeleteRequest request) {
BatchDeleteResult result = userService.batchDeleteUsers(request.getIds());
return R.ok(result);
}
@Data
public class BatchDeleteRequest {
@NotEmpty(message = "ID列表不能为空")
@Size(max = 100, message = "一次最多删除100条")
private List<Long> ids;
/** 是否强制删除 */
private Boolean force = false;
}
@Data
public class BatchDeleteResult {
private Integer successCount;
private Integer failCount;
private List<Long> failedIds;
private List<String> failReasons;
}🔐 并发控制设计
乐观锁控制
java
// 实体类添加版本字段
@Data
public class User {
private Long id;
private String username;
@Version
private Integer version;
}
// 更新时携带版本号
@Data
public class UpdateUserRequest {
private Long id;
private String username;
@NotNull(message = "版本号不能为空")
private Integer version;
}
// Controller
@PutMapping("/{id}")
public R<UserVo> updateUser(@PathVariable Long id,
@RequestBody @Valid UpdateUserRequest request) {
try {
UserVo user = userService.updateUserWithVersion(id, request);
return R.ok(user);
} catch (OptimisticLockException e) {
return R.fail(BusinessCode.CONCURRENT_CONFLICT.getCode(),
"数据已被其他用户修改,请刷新后重试");
}
}悲观锁控制
java
/**
* 使用分布式锁保护关键操作
*/
@Service
@RequiredArgsConstructor
public class OrderServiceImpl implements IOrderService {
private final RedissonClient redissonClient;
@Override
@Transactional(rollbackFor = Exception.class)
public boolean payOrder(Long orderId) {
String lockKey = "order:pay:" + orderId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试获取锁,等待3秒,锁自动释放时间30秒
if (lock.tryLock(3, 30, TimeUnit.SECONDS)) {
try {
// 执行支付逻辑
return doPayOrder(orderId);
} finally {
lock.unlock();
}
} else {
throw ServiceException.of("订单正在处理中,请勿重复操作");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw ServiceException.of("操作被中断");
}
}
}幂等性设计
java
/**
* 幂等性注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
/** 幂等键的前缀 */
String prefix() default "";
/** 幂等键的参数表达式 */
String key() default "";
/** 过期时间(秒) */
int expireTime() default 10;
/** 提示消息 */
String message() default "请勿重复提交";
}
// 使用示例
@RestController
@RequestMapping("/api/orders")
public class OrderController {
/**
* 创建订单 - 幂等性保护
*/
@PostMapping
@Idempotent(prefix = "order:create", key = "#request.userId", expireTime = 5)
public R<OrderVo> createOrder(@RequestBody @Valid CreateOrderRequest request) {
return R.ok(orderService.createOrder(request));
}
/**
* 支付订单 - 幂等性保护
*/
@PostMapping("/{orderId}/pay")
@Idempotent(prefix = "order:pay", key = "#orderId", expireTime = 30)
public R<PayResult> payOrder(@PathVariable Long orderId,
@RequestBody @Valid PayRequest request) {
return R.ok(orderService.payOrder(orderId, request));
}
}🚀 缓存策略设计
接口缓存
java
@RestController
@RequestMapping("/api/config")
public class ConfigController {
/**
* 获取系统配置 - 使用缓存
*/
@GetMapping("/{key}")
@Cacheable(cacheNames = "sys:config", key = "#key")
public R<ConfigVo> getConfig(@PathVariable String key) {
return R.ok(configService.getByKey(key));
}
/**
* 更新系统配置 - 清除缓存
*/
@PutMapping("/{key}")
@CacheEvict(cacheNames = "sys:config", key = "#key")
public R<ConfigVo> updateConfig(@PathVariable String key,
@RequestBody @Valid UpdateConfigRequest request) {
return R.ok(configService.updateByKey(key, request));
}
}响应头缓存控制
java
@RestController
@RequestMapping("/api/static")
public class StaticResourceController {
/**
* 获取静态资源 - 设置缓存头
*/
@GetMapping("/image/{id}")
public ResponseEntity<Resource> getImage(@PathVariable Long id) {
Resource resource = resourceService.getImage(id);
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS))
.eTag(resourceService.getImageETag(id))
.body(resource);
}
/**
* 获取动态数据 - 禁止缓存
*/
@GetMapping("/realtime/{id}")
public ResponseEntity<RealtimeData> getRealtimeData(@PathVariable Long id) {
return ResponseEntity.ok()
.cacheControl(CacheControl.noCache())
.body(dataService.getRealtimeData(id));
}
}🛡️ 限流设计
接口限流注解
java
/**
* 限流注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimiter {
/** 限流键 */
String key() default "";
/** 限流时间窗口(秒) */
int time() default 60;
/** 限流次数 */
int count() default 100;
/** 限流类型 */
LimitType limitType() default LimitType.DEFAULT;
/** 提示消息 */
String message() default "访问过于频繁,请稍后重试";
}
public enum LimitType {
/** 默认按IP限流 */
DEFAULT,
/** 按用户ID限流 */
USER,
/** 全局限流 */
GLOBAL
}
// 使用示例
@RestController
@RequestMapping("/api/sms")
public class SmsController {
/**
* 发送验证码 - IP限流
*/
@PostMapping("/code")
@RateLimiter(key = "sms:code", time = 60, count = 3, message = "验证码发送过于频繁")
public R<Void> sendCode(@RequestBody @Valid SendCodeRequest request) {
smsService.sendCode(request.getPhone());
return R.ok();
}
}全局限流配置
java
@Configuration
public class RateLimiterConfig {
/**
* IP限流器
*/
@Bean
public RateLimiterRegistry rateLimiterRegistry() {
RateLimiterConfig config = RateLimiterConfig.custom()
.limitRefreshPeriod(Duration.ofSeconds(1))
.limitForPeriod(100)
.timeoutDuration(Duration.ofMillis(500))
.build();
return RateLimiterRegistry.of(config);
}
}
// 限流拦截器
@Component
@RequiredArgsConstructor
public class RateLimiterInterceptor implements HandlerInterceptor {
private final RedisTemplate<String, Object> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if (handler instanceof HandlerMethod handlerMethod) {
RateLimiter rateLimiter = handlerMethod.getMethodAnnotation(RateLimiter.class);
if (rateLimiter != null) {
String key = buildKey(rateLimiter, request);
if (!tryAcquire(key, rateLimiter.time(), rateLimiter.count())) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(
JsonUtils.toJsonString(R.fail(429, rateLimiter.message()))
);
return false;
}
}
}
return true;
}
private boolean tryAcquire(String key, int time, int count) {
String luaScript = """
local current = redis.call('incr', KEYS[1])
if current == 1 then
redis.call('expire', KEYS[1], ARGV[1])
end
return current
""";
Long current = redisTemplate.execute(
new DefaultRedisScript<>(luaScript, Long.class),
List.of(key), time
);
return current != null && current <= count;
}
}📋 日志规范
请求日志记录
java
/**
* API日志注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiLog {
/** 操作描述 */
String value() default "";
/** 业务类型 */
BusinessType businessType() default BusinessType.OTHER;
/** 是否保存请求参数 */
boolean isSaveRequestData() default true;
/** 是否保存响应数据 */
boolean isSaveResponseData() default true;
}
// 日志切面
@Slf4j
@Aspect
@Component
public class ApiLogAspect {
@Around("@annotation(apiLog)")
public Object around(ProceedingJoinPoint point, ApiLog apiLog) throws Throwable {
long startTime = System.currentTimeMillis();
String traceId = MDC.get("traceId");
// 获取请求信息
HttpServletRequest request = getRequest();
String method = request.getMethod();
String uri = request.getRequestURI();
String ip = ServletUtils.getClientIP(request);
// 记录请求日志
log.info("[{}] {} {} - IP: {} - 开始处理", traceId, method, uri, ip);
Object result;
try {
result = point.proceed();
long costTime = System.currentTimeMillis() - startTime;
log.info("[{}] {} {} - 处理完成 - 耗时: {}ms", traceId, method, uri, costTime);
} catch (Exception e) {
long costTime = System.currentTimeMillis() - startTime;
log.error("[{}] {} {} - 处理失败 - 耗时: {}ms - 异常: {}",
traceId, method, uri, costTime, e.getMessage());
throw e;
}
return result;
}
}审计日志
java
/**
* 操作日志记录
*/
@Data
public class OperLog {
private Long id;
private String title;
private String businessType;
private String method;
private String requestMethod;
private String operatorType;
private String operName;
private String operUrl;
private String operIp;
private String operParam;
private String jsonResult;
private Integer status;
private String errorMsg;
private Date operTime;
private Long costTime;
}
// 保存操作日志
@Service
@RequiredArgsConstructor
public class OperLogServiceImpl implements IOperLogService {
private final IOperLogDao operLogDao;
@Async
@Override
public void recordOperLog(OperLog operLog) {
operLogDao.insert(operLog);
}
}🔧 常见问题与解决方案
1. 大数据量分页查询
问题: 深度分页性能差
java
// ❌ 不好的做法 - 深度分页
@GetMapping("/users")
public R<PageResult<UserVo>> getUsers(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
// 当 page 很大时,SQL: LIMIT 1000000, 10 性能很差
return R.ok(userService.page(page, size));
}
// ✅ 好的做法 - 游标分页
@GetMapping("/users")
public R<CursorPageResult<UserVo>> getUsers(
@RequestParam(required = false) Long cursor,
@RequestParam(defaultValue = "10") Integer size) {
// SQL: WHERE id > cursor ORDER BY id LIMIT 10
return R.ok(userService.cursorPage(cursor, size));
}
@Data
public class CursorPageResult<T> {
private List<T> records;
private Long nextCursor;
private Boolean hasMore;
}2. 大文件上传
java
/**
* 分片上传
*/
@RestController
@RequestMapping("/api/upload")
public class UploadController {
/**
* 初始化分片上传
*/
@PostMapping("/init")
public R<InitUploadResult> initUpload(@RequestBody InitUploadRequest request) {
return R.ok(uploadService.initUpload(request));
}
/**
* 上传分片
*/
@PostMapping("/chunk")
public R<Void> uploadChunk(
@RequestParam String uploadId,
@RequestParam Integer chunkIndex,
@RequestParam MultipartFile file) {
uploadService.uploadChunk(uploadId, chunkIndex, file);
return R.ok();
}
/**
* 合并分片
*/
@PostMapping("/merge")
public R<FileInfo> mergeChunks(@RequestBody MergeRequest request) {
return R.ok(uploadService.mergeChunks(request));
}
}3. 长时间操作
java
/**
* 异步任务处理
*/
@RestController
@RequestMapping("/api/tasks")
public class TaskController {
/**
* 提交异步任务
*/
@PostMapping
public R<TaskInfo> submitTask(@RequestBody TaskRequest request) {
String taskId = taskService.submitTask(request);
return R.ok(TaskInfo.builder()
.taskId(taskId)
.status("PENDING")
.checkUrl("/api/tasks/" + taskId)
.build());
}
/**
* 查询任务状态
*/
@GetMapping("/{taskId}")
public R<TaskInfo> getTaskStatus(@PathVariable String taskId) {
return R.ok(taskService.getTaskStatus(taskId));
}
/**
* 获取任务结果
*/
@GetMapping("/{taskId}/result")
public R<Object> getTaskResult(@PathVariable String taskId) {
return R.ok(taskService.getTaskResult(taskId));
}
}
@Data
@Builder
public class TaskInfo {
private String taskId;
private String status; // PENDING, RUNNING, SUCCESS, FAILED
private Integer progress;
private String message;
private String checkUrl;
private Object result;
}4. 跨域处理
java
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
// 或使用注解
@RestController
@CrossOrigin(origins = "*", maxAge = 3600)
@RequestMapping("/api/users")
public class UserController {
// ...
}📊 API 设计检查清单
开发 API 接口时,请确认以下事项:
设计规范
- [ ] ✅ 使用 RESTful 风格设计
- [ ] ✅ 使用正确的 HTTP 方法
- [ ] ✅ 资源命名使用复数形式
- [ ] ✅ URL 使用小写和连字符
请求处理
- [ ] ✅ 添加参数校验注解
- [ ] ✅ 使用 BO 对象接收参数
- [ ] ✅ 处理空值和边界情况
响应格式
- [ ] ✅ 使用统一的响应格式
- [ ] ✅ 返回合适的状态码
- [ ] ✅ 使用 VO 对象返回数据
安全性
- [ ] ✅ 添加权限注解
- [ ] ✅ 敏感数据脱敏
- [ ] ✅ 防止 SQL 注入和 XSS
性能
- [ ] ✅ 合理使用缓存
- [ ] ✅ 批量操作有数量限制
- [ ] ✅ 分页查询有合理默认值
文档
- [ ] ✅ 添加 OpenAPI 注解
- [ ] ✅ 参数和响应有清晰描述
- [ ] ✅ 示例数据完整
总结
API 设计核心原则:
- RESTful 风格 - 使用标准 HTTP 方法和资源命名
- 统一响应格式 - 所有接口返回格式一致
- 完善的错误处理 - 提供清晰的错误信息
- 安全第一 - 参数校验、权限控制、数据脱敏
- 性能优化 - 缓存、限流、分页优化
- 良好的文档 - 使用 OpenAPI 规范
遵循这些规范,可以设计出一致、安全、高性能的 API 接口,为前端开发和第三方集成提供良好的开发体验。
