👋 大家好,我是 阿问学长
!专注于分享优质开源项目
解析、毕业设计项目指导
支持、幼小初高
的教辅资料
推荐等,欢迎关注交流!🚀
20-SpringMVC RESTful API开发
📖 本文概述
本文是SSM框架系列SpringMVC进阶篇的最后一篇,将深入探讨如何使用SpringMVC开发RESTful API。通过详细的代码示例和最佳实践,帮助读者掌握现代Web API开发的核心技能。
🎯 学习目标
- 深入理解RESTful架构风格和设计原则
- 掌握SpringMVC RESTful API开发技巧
- 学会处理JSON数据和内容协商
- 了解API版本控制和文档生成
- 掌握RESTful API的测试方法
1. RESTful架构概述
1.1 REST基本概念
java
/**
* RESTful架构概述
*/
public class RESTfulOverview {
/**
* REST (Representational State Transfer) 是一种架构风格,
* 它定义了一组约束条件和原则,用于创建Web服务。
*
* REST的核心原则:
*
* 1. 统一接口 (Uniform Interface)
* - 资源标识:每个资源都有唯一的URI
* - 资源操作:通过HTTP方法操作资源
* - 自描述消息:消息包含足够的信息来描述如何处理
* - 超媒体驱动:通过超链接发现可用操作
*
* 2. 无状态 (Stateless)
* - 每个请求都包含处理该请求所需的所有信息
* - 服务器不保存客户端状态
*
* 3. 可缓存 (Cacheable)
* - 响应数据可以被缓存以提高性能
*
* 4. 客户端-服务器分离 (Client-Server)
* - 客户端和服务器独立演化
*
* 5. 分层系统 (Layered System)
* - 系统可以由多层组成,每层只知道相邻层
*
* 6. 按需代码 (Code on Demand) - 可选
* - 服务器可以向客户端发送可执行代码
*/
/**
* HTTP方法与CRUD操作的对应关系:
*
* GET - 读取 (Read)
* POST - 创建 (Create)
* PUT - 更新 (Update) - 完整更新
* PATCH - 更新 (Update) - 部分更新
* DELETE - 删除 (Delete)
* HEAD - 获取资源元数据
* OPTIONS - 获取资源支持的操作
*/
/**
* RESTful URL设计原则:
*
* 1. 使用名词而不是动词
* ✅ GET /users/123
* ❌ GET /getUser/123
*
* 2. 使用复数形式
* ✅ GET /users
* ❌ GET /user
*
* 3. 使用层次结构表示关系
* ✅ GET /users/123/orders
* ❌ GET /getUserOrders/123
*
* 4. 使用查询参数进行过滤、排序、分页
* ✅ GET /users?status=active&page=1&size=10
* ❌ GET /users/active/page1/size10
*/
}
1.2 HTTP状态码规范
java
/**
* HTTP状态码使用规范
*/
public class HTTPStatusCodes {
/**
* 2xx 成功状态码
*/
public static final int OK = 200; // 请求成功
public static final int CREATED = 201; // 资源创建成功
public static final int ACCEPTED = 202; // 请求已接受,正在处理
public static final int NO_CONTENT = 204; // 请求成功,无返回内容
/**
* 3xx 重定向状态码
*/
public static final int NOT_MODIFIED = 304; // 资源未修改
/**
* 4xx 客户端错误状态码
*/
public static final int BAD_REQUEST = 400; // 请求参数错误
public static final int UNAUTHORIZED = 401; // 未授权
public static final int FORBIDDEN = 403; // 禁止访问
public static final int NOT_FOUND = 404; // 资源不存在
public static final int METHOD_NOT_ALLOWED = 405; // 方法不允许
public static final int CONFLICT = 409; // 资源冲突
public static final int UNPROCESSABLE_ENTITY = 422; // 请求格式正确但语义错误
/**
* 5xx 服务器错误状态码
*/
public static final int INTERNAL_SERVER_ERROR = 500; // 服务器内部错误
public static final int NOT_IMPLEMENTED = 501; // 功能未实现
public static final int SERVICE_UNAVAILABLE = 503; // 服务不可用
/**
* 状态码使用示例
*/
public void statusCodeExamples() {
/*
* GET /users/123
* 200 OK - 用户存在,返回用户信息
* 404 Not Found - 用户不存在
*
* POST /users
* 201 Created - 用户创建成功
* 400 Bad Request - 请求参数错误
* 409 Conflict - 用户已存在
*
* PUT /users/123
* 200 OK - 更新成功,返回更新后的用户信息
* 204 No Content - 更新成功,无返回内容
* 404 Not Found - 用户不存在
*
* DELETE /users/123
* 204 No Content - 删除成功
* 404 Not Found - 用户不存在
*/
}
}
2. SpringMVC RESTful API开发
2.1 基础RESTful控制器
java
/**
* 用户RESTful API控制器
*/
@RestController
@RequestMapping("/api/v1/users")
@CrossOrigin(origins = "*", maxAge = 3600)
public class UserRestController {
@Autowired
private UserService userService;
/**
* 获取所有用户 - 支持分页和过滤
* GET /api/v1/users?page=1&size=10&status=active&keyword=john
*/
@GetMapping
public ResponseEntity<ApiResponse<PageResult<UserDTO>>> getUsers(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String status,
@RequestParam(required = false) String keyword,
@RequestParam(defaultValue = "id") String sortBy,
@RequestParam(defaultValue = "asc") String sortDir) {
UserQueryParams params = UserQueryParams.builder()
.page(page)
.size(size)
.status(status)
.keyword(keyword)
.sortBy(sortBy)
.sortDir(sortDir)
.build();
PageResult<UserDTO> result = userService.findUsers(params);
return ResponseEntity.ok(ApiResponse.success(result));
}
/**
* 根据ID获取用户
* GET /api/v1/users/{id}
*/
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<UserDTO>> getUserById(@PathVariable Long id) {
UserDTO user = userService.findById(id);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(ApiResponse.success(user));
}
/**
* 创建新用户
* POST /api/v1/users
*/
@PostMapping
public ResponseEntity<ApiResponse<UserDTO>> createUser(
@Valid @RequestBody CreateUserRequest request,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("参数验证失败", getValidationErrors(bindingResult)));
}
try {
UserDTO createdUser = userService.createUser(request);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(createdUser.getId())
.toUri();
return ResponseEntity.created(location)
.body(ApiResponse.success(createdUser));
} catch (UserAlreadyExistsException e) {
return ResponseEntity.status(HttpStatus.CONFLICT)
.body(ApiResponse.error("用户已存在", e.getMessage()));
}
}
/**
* 完整更新用户
* PUT /api/v1/users/{id}
*/
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<UserDTO>> updateUser(
@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("参数验证失败", getValidationErrors(bindingResult)));
}
try {
UserDTO updatedUser = userService.updateUser(id, request);
return ResponseEntity.ok(ApiResponse.success(updatedUser));
} catch (UserNotFoundException e) {
return ResponseEntity.notFound().build();
}
}
/**
* 部分更新用户
* PATCH /api/v1/users/{id}
*/
@PatchMapping("/{id}")
public ResponseEntity<ApiResponse<UserDTO>> patchUser(
@PathVariable Long id,
@RequestBody Map<String, Object> updates) {
try {
UserDTO updatedUser = userService.patchUser(id, updates);
return ResponseEntity.ok(ApiResponse.success(updatedUser));
} catch (UserNotFoundException e) {
return ResponseEntity.notFound().build();
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("无效的更新字段", e.getMessage()));
}
}
/**
* 删除用户
* DELETE /api/v1/users/{id}
*/
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteUser(@PathVariable Long id) {
try {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
} catch (UserNotFoundException e) {
return ResponseEntity.notFound().build();
}
}
/**
* 批量删除用户
* DELETE /api/v1/users
*/
@DeleteMapping
public ResponseEntity<ApiResponse<BatchOperationResult>> batchDeleteUsers(
@RequestBody List<Long> userIds) {
BatchOperationResult result = userService.batchDeleteUsers(userIds);
return ResponseEntity.ok(ApiResponse.success(result));
}
/**
* 获取用户的订单
* GET /api/v1/users/{id}/orders
*/
@GetMapping("/{id}/orders")
public ResponseEntity<ApiResponse<List<OrderDTO>>> getUserOrders(
@PathVariable Long id,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {
try {
PageResult<OrderDTO> orders = userService.getUserOrders(id, page, size);
return ResponseEntity.ok(ApiResponse.success(orders.getData()));
} catch (UserNotFoundException e) {
return ResponseEntity.notFound().build();
}
}
/**
* 激活/禁用用户
* PATCH /api/v1/users/{id}/status
*/
@PatchMapping("/{id}/status")
public ResponseEntity<ApiResponse<UserDTO>> updateUserStatus(
@PathVariable Long id,
@RequestBody UpdateStatusRequest request) {
try {
UserDTO updatedUser = userService.updateUserStatus(id, request.getStatus());
return ResponseEntity.ok(ApiResponse.success(updatedUser));
} catch (UserNotFoundException e) {
return ResponseEntity.notFound().build();
}
}
/**
* 重置用户密码
* POST /api/v1/users/{id}/reset-password
*/
@PostMapping("/{id}/reset-password")
public ResponseEntity<ApiResponse<Void>> resetPassword(@PathVariable Long id) {
try {
userService.resetPassword(id);
return ResponseEntity.ok(ApiResponse.success(null, "密码重置成功"));
} catch (UserNotFoundException e) {
return ResponseEntity.notFound().build();
}
}
/**
* 获取验证错误信息
*/
private Map<String, String> getValidationErrors(BindingResult bindingResult) {
Map<String, String> errors = new HashMap<>();
bindingResult.getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
return errors;
}
}
2.2 请求和响应DTO
java
/**
* 创建用户请求DTO
*/
public class CreateUserRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度必须在3-50个字符之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
private String username;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度必须在6-20个字符之间")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d@$!%*?&]{6,}$",
message = "密码必须包含至少一个大写字母、一个小写字母和一个数字")
private String password;
@NotBlank(message = "确认密码不能为空")
private String confirmPassword;
@NotNull(message = "年龄不能为空")
@Min(value = 18, message = "年龄不能小于18岁")
@Max(value = 100, message = "年龄不能大于100岁")
private Integer age;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@Size(max = 200, message = "个人简介不能超过200个字符")
private String bio;
// getter/setter...
/**
* 自定义验证:确认密码必须与密码一致
*/
@AssertTrue(message = "确认密码与密码不一致")
public boolean isPasswordMatching() {
return password != null && password.equals(confirmPassword);
}
}
/**
* 更新用户请求DTO
*/
public class UpdateUserRequest {
@Size(min = 3, max = 50, message = "用户名长度必须在3-50个字符之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
@Min(value = 18, message = "年龄不能小于18岁")
@Max(value = 100, message = "年龄不能大于100岁")
private Integer age;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@Size(max = 200, message = "个人简介不能超过200个字符")
private String bio;
// getter/setter...
}
/**
* 用户响应DTO
*/
public class UserDTO {
private Long id;
private String username;
private String email;
private Integer age;
private String phone;
private String bio;
private String status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private LocalDateTime lastLoginTime;
// 关联数据
private List<RoleDTO> roles;
private UserProfileDTO profile;
// getter/setter...
}
/**
* 统一API响应格式
*/
public class ApiResponse<T> {
private boolean success;
private String message;
private T data;
private Map<String, Object> meta;
private long timestamp;
public ApiResponse() {
this.timestamp = System.currentTimeMillis();
}
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setSuccess(true);
response.setMessage("操作成功");
response.setData(data);
return response;
}
public static <T> ApiResponse<T> success(T data, String message) {
ApiResponse<T> response = new ApiResponse<>();
response.setSuccess(true);
response.setMessage(message);
response.setData(data);
return response;
}
public static <T> ApiResponse<T> error(String message) {
ApiResponse<T> response = new ApiResponse<>();
response.setSuccess(false);
response.setMessage(message);
return response;
}
public static <T> ApiResponse<T> error(String message, T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setSuccess(false);
response.setMessage(message);
response.setData(data);
return response;
}
// getter/setter...
}
/**
* 分页结果DTO
*/
public class PageResult<T> {
private List<T> data;
private long total;
private int page;
private int size;
private int totalPages;
private boolean hasNext;
private boolean hasPrevious;
public PageResult(List<T> data, long total, int page, int size) {
this.data = data;
this.total = total;
this.page = page;
this.size = size;
this.totalPages = (int) Math.ceil((double) total / size);
this.hasNext = page < totalPages;
this.hasPrevious = page > 1;
}
// getter/setter...
}
/**
* 批量操作结果DTO
*/
public class BatchOperationResult {
private int total;
private int success;
private int failed;
private List<String> errors;
public BatchOperationResult(int total, int success, int failed) {
this.total = total;
this.success = success;
this.failed = failed;
this.errors = new ArrayList<>();
}
// getter/setter...
}
3. 内容协商和数据转换
3.1 JSON配置
java
/**
* JSON配置
*/
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 配置消息转换器
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// JSON转换器
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
jsonConverter.setObjectMapper(objectMapper());
converters.add(jsonConverter);
// XML转换器(可选)
MappingJackson2XmlHttpMessageConverter xmlConverter = new MappingJackson2XmlHttpMessageConverter();
converters.add(xmlConverter);
// 字符串转换器
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
converters.add(stringConverter);
}
/**
* 配置ObjectMapper
*/
@Bean
@Primary
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
// 时间格式配置
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// 序列化配置
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
// 反序列化配置
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
// 字段命名策略
mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
// 空值处理
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper;
}
/**
* 配置内容协商
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.favorParameter(true)
.parameterName("format")
.ignoreAcceptHeader(false)
.useRegisteredExtensionsOnly(false)
.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}
/**
* 配置CORS
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
3.2 自定义序列化
java
/**
* 自定义JSON序列化器
*/
public class UserDTOSerializer extends JsonSerializer<UserDTO> {
@Override
public void serialize(UserDTO user, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeStartObject();
gen.writeNumberField("id", user.getId());
gen.writeStringField("username", user.getUsername());
gen.writeStringField("email", maskEmail(user.getEmail()));
gen.writeNumberField("age", user.getAge());
if (user.getPhone() != null) {
gen.writeStringField("phone", maskPhone(user.getPhone()));
}
gen.writeStringField("status", user.getStatus());
// 格式化时间
if (user.getCreateTime() != null) {
gen.writeStringField("create_time",
user.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
// 序列化关联对象
if (user.getRoles() != null && !user.getRoles().isEmpty()) {
gen.writeArrayFieldStart("roles");
for (RoleDTO role : user.getRoles()) {
gen.writeStartObject();
gen.writeNumberField("id", role.getId());
gen.writeStringField("name", role.getName());
gen.writeEndObject();
}
gen.writeEndArray();
}
gen.writeEndObject();
}
private String maskEmail(String email) {
if (email == null || !email.contains("@")) {
return email;
}
String[] parts = email.split("@");
String username = parts[0];
if (username.length() <= 2) {
return email;
}
return username.substring(0, 2) + "***@" + parts[1];
}
private String maskPhone(String phone) {
if (phone == null || phone.length() != 11) {
return phone;
}
return phone.substring(0, 3) + "****" + phone.substring(7);
}
}
/**
* 自定义反序列化器
*/
public class CreateUserRequestDeserializer extends JsonDeserializer<CreateUserRequest> {
@Override
public CreateUserRequest deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
JsonNode node = p.getCodec().readTree(p);
CreateUserRequest request = new CreateUserRequest();
// 用户名处理:去除空格并转小写
if (node.has("username")) {
String username = node.get("username").asText().trim().toLowerCase();
request.setUsername(username);
}
// 邮箱处理:去除空格并转小写
if (node.has("email")) {
String email = node.get("email").asText().trim().toLowerCase();
request.setEmail(email);
}
// 密码处理
if (node.has("password")) {
request.setPassword(node.get("password").asText());
}
if (node.has("confirm_password")) {
request.setConfirmPassword(node.get("confirm_password").asText());
}
// 年龄处理
if (node.has("age")) {
request.setAge(node.get("age").asInt());
}
// 手机号处理:去除空格和特殊字符
if (node.has("phone")) {
String phone = node.get("phone").asText().replaceAll("[^0-9]", "");
request.setPhone(phone);
}
if (node.has("bio")) {
request.setBio(node.get("bio").asText());
}
return request;
}
}
4. 异常处理和错误响应
4.1 全局异常处理器
java
/**
* 全局异常处理器
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 处理参数验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Map<String, String>>> handleValidationException(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
logger.warn("参数验证失败: {}", errors);
return ResponseEntity.badRequest()
.body(ApiResponse.error("参数验证失败", errors));
}
/**
* 处理请求参数绑定异常
*/
@ExceptionHandler(BindException.class)
public ResponseEntity<ApiResponse<Map<String, String>>> handleBindException(BindException ex) {
Map<String, String> errors = new HashMap<>();
ex.getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
return ResponseEntity.badRequest()
.body(ApiResponse.error("请求参数错误", errors));
}
/**
* 处理业务异常
*/
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiResponse<Void>> handleBusinessException(BusinessException ex) {
logger.warn("业务异常: {}", ex.getMessage());
return ResponseEntity.badRequest()
.body(ApiResponse.error(ex.getMessage()));
}
/**
* 处理资源不存在异常
*/
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ApiResponse<Void>> handleResourceNotFoundException(
ResourceNotFoundException ex) {
logger.warn("资源不存在: {}", ex.getMessage());
return ResponseEntity.notFound().build();
}
/**
* 处理权限异常
*/
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ApiResponse<Void>> handleAccessDeniedException(AccessDeniedException ex) {
logger.warn("访问被拒绝: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("访问被拒绝"));
}
/**
* 处理HTTP方法不支持异常
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<ApiResponse<Void>> handleMethodNotSupportedException(
HttpRequestMethodNotSupportedException ex) {
String message = String.format("不支持的HTTP方法: %s", ex.getMethod());
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
.body(ApiResponse.error(message));
}
/**
* 处理媒体类型不支持异常
*/
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public ResponseEntity<ApiResponse<Void>> handleMediaTypeNotSupportedException(
HttpMediaTypeNotSupportedException ex) {
String message = String.format("不支持的媒体类型: %s", ex.getContentType());
return ResponseEntity.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
.body(ApiResponse.error(message));
}
/**
* 处理JSON解析异常
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ApiResponse<Void>> handleJsonParseException(
HttpMessageNotReadableException ex) {
logger.warn("JSON解析失败: {}", ex.getMessage());
return ResponseEntity.badRequest()
.body(ApiResponse.error("请求体格式错误"));
}
/**
* 处理类型转换异常
*/
@ExceptionHandler(TypeMismatchException.class)
public ResponseEntity<ApiResponse<Void>> handleTypeMismatchException(TypeMismatchException ex) {
String message = String.format("参数类型错误: %s", ex.getPropertyName());
return ResponseEntity.badRequest()
.body(ApiResponse.error(message));
}
/**
* 处理数据库约束异常
*/
@ExceptionHandler(DataIntegrityViolationException.class)
public ResponseEntity<ApiResponse<Void>> handleDataIntegrityViolationException(
DataIntegrityViolationException ex) {
logger.error("数据完整性约束违反", ex);
String message = "数据操作失败,请检查数据完整性";
if (ex.getMessage().contains("Duplicate entry")) {
message = "数据已存在,不能重复添加";
}
return ResponseEntity.status(HttpStatus.CONFLICT)
.body(ApiResponse.error(message));
}
/**
* 处理系统异常
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleException(Exception ex) {
logger.error("系统异常", ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("系统内部错误"));
}
}
/**
* 自定义业务异常
*/
public class BusinessException extends RuntimeException {
private String code;
public BusinessException(String message) {
super(message);
}
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
}
public String getCode() {
return code;
}
}
/**
* 资源不存在异常
*/
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
public ResourceNotFoundException(String resource, Object id) {
super(String.format("%s not found with id: %s", resource, id));
}
}
5. API版本控制
5.1 URL版本控制
java
/**
* URL版本控制示例
*/
@RestController
@RequestMapping("/api/v1/users")
public class UserV1Controller {
@GetMapping("/{id}")
public ResponseEntity<UserV1DTO> getUser(@PathVariable Long id) {
// V1版本的用户信息,字段较少
UserV1DTO user = userService.findUserV1(id);
return ResponseEntity.ok(user);
}
}
@RestController
@RequestMapping("/api/v2/users")
public class UserV2Controller {
@GetMapping("/{id}")
public ResponseEntity<UserV2DTO> getUser(@PathVariable Long id) {
// V2版本的用户信息,增加了新字段
UserV2DTO user = userService.findUserV2(id);
return ResponseEntity.ok(user);
}
}
5.2 请求头版本控制
java
/**
* 请求头版本控制
*/
@RestController
@RequestMapping("/api/users")
public class UserVersionController {
@GetMapping(value = "/{id}", headers = "API-Version=1")
public ResponseEntity<UserV1DTO> getUserV1(@PathVariable Long id) {
UserV1DTO user = userService.findUserV1(id);
return ResponseEntity.ok(user);
}
@GetMapping(value = "/{id}", headers = "API-Version=2")
public ResponseEntity<UserV2DTO> getUserV2(@PathVariable Long id) {
UserV2DTO user = userService.findUserV2(id);
return ResponseEntity.ok(user);
}
@GetMapping("/{id}")
public ResponseEntity<UserV2DTO> getUserDefault(@PathVariable Long id) {
// 默认使用最新版本
UserV2DTO user = userService.findUserV2(id);
return ResponseEntity.ok(user);
}
}
5.3 参数版本控制
java
/**
* 参数版本控制
*/
@RestController
@RequestMapping("/api/users")
public class UserParamVersionController {
@GetMapping(value = "/{id}", params = "version=1")
public ResponseEntity<UserV1DTO> getUserV1(@PathVariable Long id) {
UserV1DTO user = userService.findUserV1(id);
return ResponseEntity.ok(user);
}
@GetMapping(value = "/{id}", params = "version=2")
public ResponseEntity<UserV2DTO> getUserV2(@PathVariable Long id) {
UserV2DTO user = userService.findUserV2(id);
return ResponseEntity.ok(user);
}
}
6. 小结
本文深入介绍了SpringMVC RESTful API开发:
- RESTful架构:REST原则、HTTP方法和状态码规范
- API开发:控制器设计、请求响应DTO、数据验证
- 内容协商:JSON配置、自定义序列化、消息转换
- 异常处理:全局异常处理器、错误响应格式
- 版本控制:URL、请求头、参数等版本控制方式
掌握RESTful API开发的关键点:
- 遵循REST架构原则和设计规范
- 合理设计API接口和数据结构
- 实现完善的异常处理机制
- 考虑API的版本控制和向后兼容
- 注重API的安全性和性能优化
🔗 下一篇预告
下一篇文章将介绍SSM项目部署与优化,学习如何将SSM项目部署到生产环境并进行性能优化。
相关文章: