目录
REST API设计原则
REST架构约束
Roy Fielding的REST约束:
- 客户端-服务器(Client-Server):分离关注点
- 无状态(Stateless):请求独立,服务器不保存客户端状态
- 缓存(Cacheable):响应可以被缓存
- 统一接口(Uniform Interface):标准化接口设计
- 分层系统(Layered System):分层架构
- 按需编码(Code on Demand):可选约束
资源命名规范
资源命名最佳实践:
// ✅ 好的设计
GET /api/v1/users // 获取用户列表
GET /api/v1/users/{id} // 获取特定用户
POST /api/v1/users // 创建用户
PUT /api/v1/users/{id} // 完整更新用户
PATCH /api/v1/users/{id} // 部分更新用户
DELETE /api/v1/users/{id} // 删除用户
GET /api/v1/users/{id}/orders // 获取用户的订单
POST /api/v1/users/{id}/orders // 为用户创建订单
GET /api/v1/users/{id}/orders/{orderId} // 获取特定订单
// ❌ 不好的设计
POST /api/v1/getUsers // 动词在URL中
GET /api/v1/usersList // 不必要的词汇
POST /api/v1/user/create // 资源和方法混淆
GET /api/v1/userById/123 // 路径参数命名不规范
统一响应格式
API响应封装:
// 统一响应结果类
public class ApiResponse<T> {
private boolean success;
private String message;
private T data;
private String errorCode;
private long timestamp;
private Map<String, Object> metadata;
// 成功响应
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.success = true;
response.data = data;
response.timestamp = System.currentTimeMillis();
return response;
}
public static <T> ApiResponse<T> success(String message, T data) {
ApiResponse<T> response = success(data);
response.message = message;
return response;
}
// 错误响应
public static <T> ApiResponse<T> error(String message) {
ApiResponse<T> response = new ApiResponse<>();
response.success = false;
response.message = message;
response.timestamp = System.currentTimeMillis();
return response;
}
public static <T> ApiResponse<T> error(String errorCode, String message) {
ApiResponse<T> response = error(message);
response.errorCode = errorCode;
return response;
}
// getters and setters
}
// REST控制器实现
@RestController
@RequestMapping("/api/v1/users")
@Validated
public class UserController {
@Autowired
private UserService userService;
@Autowired
private UserMapper userMapper;
@GetMapping
public ResponseEntity<ApiResponse<PageResult<UserDto>>> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "createdDate") String sortBy,
@RequestParam(defaultValue = "desc") String sortDir,
@RequestParam(required = false) String search) {
Pageable pageable = PageRequest.of(page, size,
Sort.by(Sort.Direction.fromString(sortDir), sortBy));
PageResult<User> users = userService.getUsers(search, pageable);
PageResult<UserDto> userDtos = userMapper.toPageResult(users);
return ResponseEntity.ok(ApiResponse.success("用户列表获取成功", userDtos));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<UserDto>> getUser(@PathVariable Long id) {
User user = userService.findById(id);
UserDto userDto = userMapper.toDto(user);
return ResponseEntity.ok(ApiResponse.success("用户信息获取成功", userDto));
}
@PostMapping
public ResponseEntity<ApiResponse<UserDto>> createUser(
@Valid @RequestBody UserCreateRequest request) {
User user = userMapper.toEntity(request);
User savedUser = userService.createUser(user);
UserDto userDto = userMapper.toDto(savedUser);
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success("用户创建成功", userDto));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<UserDto>> updateUser(
@PathVariable Long id,
@Valid @RequestBody UserUpdateRequest request) {
User user = userService.findById(id);
userMapper.updateEntity(user, request);
User updatedUser = userService.updateUser(user);
UserDto userDto = userMapper.toDto(updatedUser);
return ResponseEntity.ok(ApiResponse.success("用户更新成功", userDto));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.ok(ApiResponse.success("用户删除成功", null));
}
}
资源与HATEOAS
HATEOAS实现
Spring HATEOAS集成:
<dependency>
<groupId>org.springframework.hateoas</groupId>
<artifactId>spring-hateoas</artifactId>
<version>1.1.5</version>
</dependency>
HATEOAS资源表示:
// HATEOAS资源类
@Relation(collectionRelation = "users")
public class UserResource extends EntityModel<User> {
public UserResource(User user) {
super(user);
add(linkTo(UserController.class).withRel("collection"));
add(linkTo(methodOn(UserController.class).getUser(user.getId())).withSelfRel());
add(linkTo(methodOn(UserController.class).updateUser(user.getId(), null)).withRel("edit"));
add(linkTo(methodOn(UserController.class).deleteUser(user.getId())).withRel("delete"));
// 相关的资源链接
if (user.getOrders() != null && !user.getOrders().isEmpty()) {
add(linkTo(methodOn(OrderController.class).getUserOrders(user.getId()))
.withRel("orders"));
}
}
}
// 控制器中的HATEOAS应用
@RestController
@RequestMapping("/api/v1/users")
public class UserHateoasController {
@GetMapping(value = "/{id}", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity<UserResource> getUser(@PathVariable Long id) {
User user = userService.findById(id);
UserResource userResource = new UserResource(user);
return ResponseEntity.ok(userResource);
}
@GetMapping(produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity<PagedModel<EntityModel<User>>> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
Pageable pageable = PageRequest.of(page, size);
Page<User> users = userService.getUsers(pageable);
List<EntityModel<User>> userModels = users.getContent().stream()
.map(this::toUserModel)
.collect(Collectors.toList());
Link[] links = {
linkTo(methodOn(UserHateoasController.class).getUsers(0, size)).withRel("first"),
selfLink()
};
if (users.hasNext()) {
links = Arrays.copyOf(links, links.length + 1);
links[links.length - 1] = linkTo(methodOn(UserHateoasController.class)
.getUsers(users.next().getPageNumber(), size)).withRel("next");
}
PagedModel<EntityModel<User>> pagedModel = PagedModel.of(
userModels,
new PagedModel.PageMetadata(users.getSize(), users.getNumber(),
users.getTotalElements(), users.getTotalPages())
);
pagedModel.forEach(user -> {
user.add(linkTo(UserHateoasController.class).slash(user.getContent().getId())
.withSelfRel());
});
return ResponseEntity.ok(pagedModel);
}
private EntityModel<User> toUserModel(User user) {
return EntityModel.of(user,
linkTo(UserHateoasController.class).slash(user.getId()).withSelfRel(),
linkTo(UserHateoasController.class).withRel("collection"));
}
}
资源链接构建
链接构建服务:
@Component
public class LinkBuilderService {
public void addSelfLinks(RepresentationModel<?> model, Class<?> controllerClass, Object... pathVariables) {
model.add(WebMvcLinkBuilder.linkTo(controllerClass).slash(pathVariables).withSelfRel());
}
public void addCollectionLinks(RepresentationModel<?> model, Class<?> controllerClass) {
model.add(WebMvcLinkBuilder.linkTo(controllerClass).withRel("collection"));
}
public <T extends RepresentatioModel<?>> T addUserLinks(T userModel, Long userId) {
userModel.add(linkTo(UserController.class).slash(userId).withSelfRel());
userModel.add(linkTo(UserController.class).slash(userId).slash("edit").withRel("edit"));
userModel.add(linkTo(UserController.class).slash(userId).slash("orders").withRel("orders"));
return userModel;
}
public <T extends RepresentationModel<?>> T addOrderLinks(T orderModel, Long orderId, Long userId) {
orderModel.add(linkTo(OrderController.class).slash(orderId).withSelfRel());
orderModel.add(linkTo(UserController.class).slash(userId).withRel("user"));
orderModel.add(linkTo(OrderController.class).slash(orderId).slash("items").withRel("items"));
return orderModel;
}
}
HTTP状态码应用
标准状态码使用
状态码应用规范:
@RestController
public class HttpStatusController {
// 2xx 成功状态码
@GetMapping("/users/{id}")
public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
User user = userService.findById(id);
UserDto userDto = userMapper.toDto(user);
return ResponseEntity.ok(userDto); // 200 OK
}
@PostMapping("/users")
public ResponseEntity<UserDto> createUser(@Valid @RequestBody UserCreateRequest request) {
User user = userService.createUser(userMapper.toEntity(request));
UserDto userDto = userMapper.toDto(user);
return ResponseEntity.status(HttpStatus.CREATED)
.body(userDto); // 201 Created
}
@PutMapping("/users/{id}")
public ResponseEntity<Void> updateUser(@PathVariable Long id, @Valid @RequestBody UserUpdateRequest request) {
userService.updateUser(id, request);
return ResponseEntity.noContent().build(); // 204 No Content
}
// 3xx 重定向状态码
@GetMapping("/users/{id}/profile")
public ResponseEntity<Void> redirectToProfile(@PathVariable Long id) {
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(id)
.toUri();
return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY)
.location(location)
.build(); // 301 Moved Permanently
}
// 4xx 客户端错误状态码
@GetMapping("/users/{id}")
public ResponseEntity<UserDto> getUserById(@PathVariable Long id) {
try {
User user = userService.findById(id);
UserDto userDto = userMapper.toDto(user);
return ResponseEntity.ok(userDto);
} catch (UserNotFoundException e) {
return ResponseEntity.notFound().build(); // 404 Not Found
} catch (AccessDeniedException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); // 403 Forbidden
}
}
@PostMapping("/users")
public ResponseEntity<?> createUserWithValidation(@Valid @RequestBody UserCreateRequest request) {
if (userService.existsByEmail(request.getEmail())) {
ApiError error = new ApiError(HttpStatus.BAD_REQUEST, "邮箱已被使用");
return ResponseEntity.badRequest().body(error); // 400 Bad Request
}
User user = userService.createUser(userMapper.toEntity(request));
UserDto userDto = userMapper.toDto(user);
return ResponseEntity.status(HttpStatus.CREATED).body(userDto);
}
// 5xx 服务器错误状态码
@PostMapping("/users/{id}/process")
public ResponseEntity<Void> processUser(@PathVariable Long id) {
try {
userService.processUserData(userId);
return ResponseEntity.ok().build();
} catch (DatabaseException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); // 500 Internal Server Error
} catch (ServiceUnavailableException e) {
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build(); // 503 Service Unavailable
}
}
}
自定义状态码
业务特定状态码:
// 自定义业务异常
public class BusinessException extends RuntimeException {
private final HttpStatus httpStatus;
private final String errorCode;
public BusinessException(String message, HttpStatus httpStatus, String errorCode) {
super(message);
this.httpStatus = httpStatus;
this.errorCode = errorCode;
}
// getters
}
// 全局异常处理器
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ApiError> handleUserNotFound(UserNotFoundException ex) {
ApiError error = new ApiError(HttpStatus.NOT_FOUND, ex.getMessage(), "USER_NOT_FOUND");
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(ValidationException.class)
public ResponseEntity<ApiError> handleValidationException(ValidationException ex) {
ApiError error = new ApiError(HttpStatus.BAD_REQUEST, ex.getMessage(), "VALIDATION_ERROR");
return ResponseEntity.badRequest().body(error);
}
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiError> handleBusinessException(BusinessException ex) {
ApiError error = new ApiError(ex.getHttpStatus(), ex.getMessage(), ex.getErrorCode());
return ResponseEntity.status(ex.getHttpStatus()).body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiError> handleGenericException(Exception ex) {
logger.error("未处理的异常", ex);
ApiError error = new ApiError(
HttpStatus.INTERNAL_SERVER_ERROR,
"内部服务器错误",
"INTERNAL_ERROR");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
API版本控制
URL路径版本控制
路径版本控制实现:
// 版本1控制器
@RestController
@RequestMapping("/api/v1/users")
@Validated
public class UserV1Controller {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<UserV1Dto> getUserV1(@PathVariable Long id) {
User user = userService.findById(id);
UserV1Dto dto = UserV1Mapper.INSTANCE.toDtoV1(user);
return ResponseEntity.ok(dto);
}
@PostMapping
public ResponseEntity<UserV1Dto> createUserV1(@Valid @RequestBody UserCreateV1Request request) {
User user = userService.createUser(UserV1Mapper.INSTANCE.toEntity(request));
UserV1Dto dto = UserV1Mapper.INSTANCE.toDtoV1(user);
return ResponseEntity.status(HttpStatus.CREATED).body(dto);
}
}
// 版本2控制器
@RestController
@RequestMapping("/api/v2/users")
@Validated
public class UserV2Controller {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<UserV2Dto> getUserV2(@PathVariable Long id) {
User user = userService.findById(id);
UserV2Dto dto = UserV2Mapper.INSTANCE.toDtoV2(user);
// V2版本增加了更多字段
return ResponseEntity.ok(dto);
}
@PostMapping
public ResponseEntity<UserV2Dto> createUserV2(@Valid @RequestBody UserCreateV2Request request) {
User user = userService.createUser(UserV2Mapper.INSTANCE.toEntity(request));
UserV2Dto dto = UserV2Mapper.INSTANCE.toDtoV2(user);
return ResponseEntity.status(HttpStatus.CREATED).body(dto);
}
}
请求头版本控制
Accept版本控制:
@RestController
@RequestMapping("/api/users")
public class UserVersionController {
@Autowired
private UserService userService;
@GetMapping(value = "/{id}", produces = {
"application/vnd.company.app-v1+json",
"application/vnd.company.app-v2+json"
})
public ResponseEntity<?> getUser(@PathVariable Long id, HttpServletRequest request) {
User user = userService.findById(id);
String acceptHeader = request.getHeader("Accept");
if (acceptHeader.contains("v1")) {
UserV1Dto dto = UserV1Mapper.INSTANCE.toDtoV1(user);
return ResponseEntity.ok()
.contentType(MediaType.valueOf("application/vnd.company.app-v1+json"))
.body(dto);
} else if (acceptHeader.contains("v2")) {
UserV2Dto dto = UserV2Mapper.INSTANCE.toDtoV2(user);
return ResponseEntity.ok()
.contentType(MediaType.valueOf("application/vnd.company.app-v2+json"))
.body(dto);
} else {
// 默认返回最新版本
UserV2Dto dto = UserV2Mapper.INSTANCE.toDtoV2(user);
return ResponseEntity.ok(dto);
}
}
}
版本管理策略
版本生命周期管理:
@Service
public class ApiVersionService {
private static final Map<String, String> VERSION_DEPRECATION_INFO = Map.of(
"v1", "2024-12-31", // V1版本将于2024年12月31日废弃
"v2", "2025-06-30" // V2版本将于2025年6月30日废弃
);
public void addDeprecationHeaders(HttpServletResponse response, String version) {
if (VERSION_DEPRECATION_INFO.containsKey(version)) {
response.setHeader("Deprecation", "true");
response.setHeader("Sunset", VERSION_DEPRECATION_INFO.get(version));
response.setHeader("Link", "<https://api.example.com/docs/deprecation>; rel=\"deprecation\"");
}
}
public boolean isVersionDeprecated(String version) {
return VERSION_DEPRECATION_INFO.containsKey(version);
}
public String getLatestVersion() {
return "v3";
}
}
// 版本拦截器
@Component
public class ApiVersionInterceptor implements HandlerInterceptor {
@Autowired
private ApiVersionService versionService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String requestURI = request.getRequestURI();
String version = extractVersionFromUri(requestURI);
if (version != null && versionService.isVersionDeprecated(version)) {
versionService.addDeprecationHeaders(response, version);
}
return true;
}
private String extract_versionFromUri(String uri) {
Pattern pattern = Pattern.compile("/api/v(\\d+)/");
Matcher matcher = pattern.matcher(uri);
return matcher.find() ? matcher.group(1) : null;
}
}
API安全实现
API密钥认证
API密钥管理:
@Component
public class ApiKeyAuthenticationFilter extends GenericFilterBean {
@Autowired
private ApiKeyService apiKeyService;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String apiKey = request.getHeader("X-API-Key");
if (apiKey != null) {
try {
ApiKeyInfo keyInfo = apiKeyService.validateApiKey(apiKey);
if (keyInfo != null) {
// 设置安全上下文
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(new ApiKeyAuthentication(keyInfo));
SecurityContextHolder.setContext(context);
// 设置请求限制
applyRateLimiting(keyInfo, httpRequest);
chain.doFilter(request, response);
return;
}
} catch (Exception e) {
logger.error("API密钥验证失败", e);
}
}
// 无有效的API密钥
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.getWriter().write("{\"error\":\"无效的API密钥\"}");
}
private void applyRateLimiting(ApiKeyInfo keyInfo, HttpServletRequest request) {
// 根据API密钥类型应用不同的请求限制
RateLimiter rateLimiter = getRateLimiterForKey(keyInfo);
if (!rateLimiter.tryAcquire()) {
throw new TooManyRequestsException("请求频率超限");
}
}
}
@Service
public class ApiKeyService {
@Autowired
private ApiKeyRepository apiKeyRepository;
public ApiKeyInfo validateApiKey(String key) {
Optional<ApiKey> apiKey = apiKeyRepository.findByKeyHash(hashKey(key));
if (apiKey.isEmpty() || !apiKey.get().isActive()) {
return null;
}
// 检查密钥是否过期
ApiKey keyEntity = apiKey.get();
if (keyEntity.getExpiresAt() != null &&
keyEntity.getExpiresAt().isBefore(LocalDateTime.now())) {
return null;
}
// 更新最后使用时间
keyEntity.setLastUsedAt(LocalDateTime.now());
apiKeyRepository.save(keyEntity);
return mapToInfo(keyEntity);
}
public ApiKeyInfo createApiKey(String owner, ApiKeyType type) {
String keyValue = generateApiKey();
String keyHash = hashKey(keyValue);
ApiKey apiKey = new ApiKey();
apiKey.setOwner(owner);
apiKey.setType(type);
apiKey.setKeyHash(keyHash);
apiKey.setActive(true);
apiKey.setCreatedAt(LocalDateTime.now());
apiKey.setExpiresAt(LocalDateTime.now().plusDays(365)); // 1年有效期
ApiKey savedKey = apiKeyRepository.save(apiKey);
savedKey.setKeyValue(keyValue); // 只在创建时返回明文密钥
return mapToInfo(savedKey);
}
private String generateApiKey() {
return "sk-" + UUID.randomUUID().toString().replace("-", "");
}
private String hashKey(String key) {
return DigestUtils.sha256Hex(key + "salt"); // 加盐哈希
}
private ApiKeyInfo mapToInfo(ApiKey apiKey) {
return ApiKeyInfo.builder()
.id(apiKey.getId())
.owner(apiKey.getOwner())
.type(apiKey.getType())
.keyValue(apiKey.getKeyValue()) // 注意:这里只在创建时设置
.active(apiKey.isActive())
.expiresAt(apiKey.getExpiresAt())
.build();
}
}
总结
Spring MVC RESTful API设计教程涵盖了现代API开发的核心要素:
核心技术要点
- REST设计原则 - 遵循REST架构约束和最佳实践
- HATEOAS实现 - 提供资源间的导航链接
- HTTP状态码 - 正确使用状态码表达API状态
- 版本控制 - 支持API演进和向后兼容
- 错误处理 - 统一的错误响应格式
- 安全实现 - API密钥认证和权限控制
- 性能优化 - 缓存策略和分页机制
企业级API价值
- 可维护性:清晰的API结构和版本管理
- 可扩展性:模块化的API设计支持功能扩展
- 用户体验:一致的响应格式和错误处理
- 开发者友好:完善的文档和示例代码
设计最佳实践
- 资源导向:以资源为核心的API设计
- 无状态:每个请求都是独立的
- 缓存友好:设计可缓存的响应
- 版本稳定:保持向后兼容性
- 错误明确:提供清晰的错误信息
通过这些教程的深入学习,您将掌握现代REST API的完整开发技能,为构建高质量的企业级Web服务打下坚实基础!
3381

被折叠的 条评论
为什么被折叠?



