当前位置: 首页 > news >正文

SSM入门到实战: 3.6 SpringMVC RESTful API开发

👋 大家好,我是 阿问学长!专注于分享优质开源项目解析、毕业设计项目指导支持、幼小初高教辅资料推荐等,欢迎关注交流!🚀

20-SpringMVC RESTful API开发

📖 本文概述

本文是SSM框架系列SpringMVC进阶篇的最后一篇,将深入探讨如何使用SpringMVC开发RESTful API。通过详细的代码示例和最佳实践,帮助读者掌握现代Web API开发的核心技能。

🎯 学习目标

  • 深入理解RESTful架构风格和设计原则
  • 掌握SpringMVC RESTful API开发技巧
  • 学会处理JSON数据和内容协商
  • 了解API版本控制和文档生成
  • 掌握RESTful API的测试方法

1. RESTful架构概述

1.1 REST基本概念

/*** 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状态码规范

/*** 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控制器

/*** 用户RESTful API控制器*/
@RestController
@RequestMapping("/api/v1/users")
@CrossOrigin(origins = "*", maxAge = 3600)
public class UserRestController {@Autowiredprivate UserService userService;/*** 获取所有用户 - 支持分页和过滤* GET /api/v1/users?page=1&size=10&status=active&keyword=john*/@GetMappingpublic 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*/@PostMappingpublic 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*/@DeleteMappingpublic 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

/*** 创建用户请求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配置

/*** JSON配置*/
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {/*** 配置消息转换器*/@Overridepublic 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@Primarypublic 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;}/*** 配置内容协商*/@Overridepublic 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*/@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/api/**").allowedOriginPatterns("*").allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS").allowedHeaders("*").allowCredentials(true).maxAge(3600);}
}

3.2 自定义序列化

/*** 自定义JSON序列化器*/
public class UserDTOSerializer extends JsonSerializer<UserDTO> {@Overridepublic 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> {@Overridepublic 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 全局异常处理器

/*** 全局异常处理器*/
@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版本控制

/*** 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 请求头版本控制

/*** 请求头版本控制*/
@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 参数版本控制

/*** 参数版本控制*/
@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开发:

  1. RESTful架构:REST原则、HTTP方法和状态码规范
  2. API开发:控制器设计、请求响应DTO、数据验证
  3. 内容协商:JSON配置、自定义序列化、消息转换
  4. 异常处理:全局异常处理器、错误响应格式
  5. 版本控制:URL、请求头、参数等版本控制方式

掌握RESTful API开发的关键点:

  • 遵循REST架构原则和设计规范
  • 合理设计API接口和数据结构
  • 实现完善的异常处理机制
  • 考虑API的版本控制和向后兼容
  • 注重API的安全性和性能优化

🔗 下一篇预告

下一篇文章将介绍SSM项目部署与优化,学习如何将SSM项目部署到生产环境并进行性能优化。


相关文章:

  • 上一篇:19-SpringMVC拦截器与异常处理
  • 下一篇:21-SSM框架整合配置详解
  • 返回目录
http://www.dtcms.com/a/352132.html

相关文章:

  • 基于muduo库的图床云共享存储项目(一)
  • vs2019安装cpu版本的fftw 以实现傅里叶变换
  • 《护理学》10月版面征稿论文速递
  • 【46页PPT】AI智能中台用ABC+IOT重新定义制造(附下载方式)
  • SQLBot:一款基于大语言模型和RAG的智能数据分析工具
  • AI人工智能一体化HR系统如何选型?
  • 重塑金融管理会计核心引擎,容智管会智能体打造智能决策新基建
  • 手写MyBatis第35弹:@Select、@Insert等注解的背后原理
  • 【软考论文】论DevOps及其应用
  • BotCash:2025 中国算力大会——国家级数据标注产业供需对接大会
  • 【自记】Python 局部变量、全局变量及global的示例
  • Python实现RANSAC进行点云直线、平面、曲面、圆、球体和圆柱拟合
  • 负载均衡之平滑加权轮询(Smooth Weighted Round Robin)详解与实现
  • 前沿技术趋势与应用:探索数字世界的下一个十年
  • 本地通过阿里云ECS建立SSH隧道连接阿里云RDS MYSQL数据库
  • 【P2P】RELAY服务2:cmake+ c实现及ubuntu运行
  • 淘宝/天猫商品详情API数据解析【附代码】
  • 软件检测报告:XML外部实体(XXE)注入漏洞原因和影响
  • 【Erdas实验教程】031:遥感图像频率域增强(傅立叶变换)
  • BCI良好棉花认证标准及申请条件(2025年最新版)
  • 加密狗与U盘的核心区别,U盘能否替代加密狗?
  • 电力工程大模型驱动AI工程计算:从“算错挨骂”到“一键精准”
  • 开发指南135-CSS中定义参数
  • 技术干货丨基于SimSolid的塑胶模具温度场瞬态分析
  • 【贪心算法】day3
  • win11在安装com0com软件后,在设备管理器中虚拟串口黄色感叹号得解决方法
  • 什么是Webpack的热更新(Hot Module Replacement)?原理是什么?
  • 2 梯度下降算法
  • 面试 总结(1)
  • 博士招生 | 南洋理工大学 PINE Lab 招收全奖博士