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

Spring MVC REST API设计详解:从零构建高效接口

1. Spring MVC与REST API基础

1.1 RESTful架构的六大约束详解

RESTful架构是Roy Thomas Fielding在2000年博士论文中提出的软件架构风格,它包含六个核心约束,这些约束共同构成了RESTful API的设计原则。

客户端-服务器约束(Client-Server):将系统分为客户端和服务器两个独立部分,客户端负责用户界面和交互,服务器负责数据存储和业务逻辑 。这种分离使得客户端和服务器可以独立演进,提高系统的可维护性和可扩展性。

无状态约束(Stateless):每个请求都必须包含理解该请求所需的所有信息,服务器不保存客户端的状态 。这种设计带来了可见性、可靠性和可伸缩性的优势,但也要求客户端在每次请求中携带必要的身份验证信息。

缓存约束(Cacheable):允许客户端或中间层缓存服务器响应,减少重复请求,提高系统性能 。服务器需要明确标记响应是否可缓存,客户端则需要正确处理缓存数据的有效性。

统一接口约束(Uniform Interface):这是RESTful架构的核心特征,要求接口遵循统一的标准 。具体包括:

  1. 资源标识:使用URI作为资源的唯一标识符
  2. 资源操作:通过HTTP方法对资源进行操作
  3. 自描述消息:每个消息包含足够的信息来描述如何处理它
  4. 超媒体驱动应用状态(HATEOAS):在响应中包含相关操作的链接

分层系统约束(Layered System):系统由多个层次组成,每个层次只与相邻层次交互,这种设计提高了系统的可维护性和可扩展性 。

按需代码约束(Code on Demand):允许服务器向客户端发送可执行代码,扩展客户端功能 。虽然这个约束不是必须的,但在某些场景下可以简化客户端开发。

在实际开发中,我们通常关注Richardson成熟度模型,它将RESTful API分为四个级别:

  • Level 0:基于远程过程调用(RPC)的Web服务
  • Level 1:将服务抽象为资源,每个资源由唯一的URI标识
  • Level 2:使用HTTP方法(GET、POST、PUT、DELETE)对资源进行操作
  • Level 3:实现HATEOAS,客户端通过超媒体链接发现下一步操作

在Spring MVC中构建RESTful API,我们通常至少达到Level 2级别,即使用HTTP方法对资源进行操作,但可能不完全实现HATEOAS。

1.2 Spring MVC框架核心组件与工作流程

Spring MVC是一个基于Java的Web框架,实现了MVC(模型-视图-控制器)设计模式

8

。它通过分离业务逻辑、数据和界面显示来简化Web应用开发。

核心组件

  • DispatcherServlet:前端控制器,负责接收HTTP请求并分发给相应的处理器
  • HandlerMapping:处理器映射,负责将请求映射到对应的控制器方法
  • Controller:控制器,处理请求并返回处理结果
  • Model:模型,封装请求处理过程中产生的数据
  • View:视图,负责将模型数据渲染成特定格式的响应
  • ViewResolver:视图解析器,负责解析视图名称并返回视图对象

工作流程

  1. 客户端发送HTTP请求到服务器
  2. 请求由Servlet容器(如Tomcat)接收并传递给DispatcherServlet
  3. DispatcherServlet查询HandlerMapping,找到处理该请求的Controller
  4. Controller处理请求,生成Model数据
  5. DispatcherServlet将Model和视图名称传递给ViewResolver
  6. ViewResolver解析视图名称,找到对应的View
  7. View使用Model数据渲染响应,返回给客户端

在构建RESTful API时,我们通常使用@RestController代替传统的@Controller和 JSP/Thymeleaf视图,这样可以将响应直接以JSON格式返回,无需视图解析器。

1.3 Spring MVC与Spring WebFlux对比分析

Spring MVC和Spring WebFlux都是Spring框架提供的Web开发解决方案,但它们在底层实现和适用场景上有显著差异。

特性Spring MVCSpring WebFlux
编程模型命令式编程响应式编程
线程模型阻塞式IO,每个请求一个线程非阻塞式IO,使用事件循环和背压机制
适用场景传统Web应用,高并发但非实时场景高并发、实时性要求高的场景,如IoT、实时监控
性能依赖线程池,线程数受限更高的资源利用率,支持百万级并发连接
依赖Servlet APIReactor或RxJava

Spring MVC是基于Servlet API的传统框架,采用命令式编程方式,每个请求对应一个线程 。它适合大多数企业级应用开发,尤其是需要与大量遗留系统集成的场景。

Spring WebFlux是Spring 5引入的响应式框架,基于Reactor库实现 。它采用函数式编程方式,能够处理非阻塞IO操作,适合高并发、实时性要求高的场景。

在REST API设计中,Spring MVC提供了更为简洁和直观的实现方式,而Spring WebFlux则提供了更好的并发性能。对于大多数企业级应用,Spring MVC已经足够;而对于需要处理大量并发连接的场景,Spring WebFlux可能是更好的选择。

2. REST API设计核心流程

2.1 资源建模与路径设计原则

资源建模是REST API设计的第一步,它要求我们将业务实体抽象为资源

12

。在Spring MVC中,通常使用实体类(如User、Product)来表示资源。

路径设计原则

  • 使用名词复数形式表示资源集合(如/users)
  • 使用单数形式表示单个资源(如/users/1)
  • 避免使用动词(如/getUsers),而是通过HTTP方法表示操作
  • 使用连字符增加可读性(如/user-profile)
  • 使用层级关系表示资源关系(如/users/1/orders)

在Spring MVC中,我们可以使用@RequestMapping注解来定义资源路径:

java

深色版本

@RestController
@RequestMapping("/users")
public class UserController {@GetMappingpublic ResponseEntity<List<User>> AllUsers() {// 返回所有用户}@GetMapping("{/id}")public ResponseEntity<User> getUserById(@PathVariable Long id) {// 返回指定ID的用户}
}

资源建模的关键点

  • 每个资源应该有明确的边界和职责
  • 资源之间可以通过链接(HATEOAS)建立关系
  • 路径设计应该简洁、直观,易于理解
2.2 HTTP方法与状态码的语义化使用

HTTP方法是RESTful API中操作资源的核心方式,它们具有特定的语义和约束

12

  • GET:获取资源,应该是安全的(不修改资源状态)和幂等的
  • POST:创建资源,非幂等的
  • PUT:全量更新资源,幂等的
  • DELETE:删除资源,幂等的
  • PATCH:部分更新资源,幂等的(如果正确实现)
  • HEAD:获取资源元数据,与GET类似但不返回内容

在Spring MVC中,我们可以使用以下注解来映射HTTP方法

3

java

深色版本

@RestController
@RequestMapping("/users")
public class UserController {@GetMappingpublic ResponseEntity<List<User>> AllUsers() {// 获取所有用户}@PostMappingpublic ResponseEntity<User> createUser(@RequestBody User user) {// 创建新用户}@PutMapping("{/id}")public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {// 全量更新用户}@DeleteMapping("{/id}")public ResponseEntity<?>.deleteUser(@PathVariable Long id) {// 删除用户}@PatchMapping("{/id}")public ResponseEntity<User> patchUser(@PathVariable Long id, @RequestBody UserPatch patch) {// 部分更新用户}
}

HTTP状态码用于表示请求的处理结果

12

  • 200 OK:请求成功,返回资源
  • 201 Created:资源创建成功,包含新资源的URI
  • 204 No Content:请求成功,但没有返回内容
  • 400 Bad Request:请求参数错误
  • 401 Unauthorized:用户未认证
  • 403 Forbidden:用户无权限
  • 404 Not Found:资源不存在
  • 500 Internal Server Error:服务器内部错误

在Spring MVC中,我们可以使用ResponseEntity来返回带有状态码的响应

3

java

深色版本

@GetMapping("{/id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {User user = userService.getUser(id);if (user == null) {return ResponseEntity.notFound().build();}return ResponseEntity.ok(user);
}

HTTP方法与状态码的正确使用是RESTful API设计的基础,它们帮助客户端理解资源的操作方式和结果。

2.3 参数绑定与数据传输对象设计

参数绑定是REST API处理请求数据的关键步骤,在Spring MVC中我们可以通过多种注解来实现:

java

深色版本

@RestController
@RequestMapping("/users")
public class UserController {// 路径变量参数绑定@GetMapping("{/id}")public ResponseEntity<User> getUser(@PathVariable Long id) {// ...}// 查询参数绑定@GetMappingpublic ResponseEntity<List<User>> getUsers(@RequestParam(required = false, defaultValue = "0") Integer page,@RequestParam(required = false, defaultValue = "10") Integer size,@RequestParam(required = false) String name) {// ...}// 请求体参数绑定@PostMappingpublic ResponseEntity<User> createUser(@RequestBody UserDTO userDTO) {// ...}
}

数据传输对象(DTO)设计是REST API的重要环节,它帮助我们解耦业务实体和API响应

16

java

深色版本

@Data
public class UserDTO {private Long id;private String name;private String email;private String role;// 省略getter和setter
}@Service
public class UserService {public UserDTO getUserDTO(Long id) {User user = userRepository.findById(id).orElse(null);if (user == null) {return null;}return modelMapper.map(user, UserDTO.class);}
}

参数绑定的最佳实践

  • 使用路径变量(@PathVariable)表示资源标识符
  • 使用查询参数(@RequestParam)表示过滤条件、分页参数等
  • 使用请求体(@RequestBody)传递复杂数据
  • 使用DTO解耦业务实体和API响应,避免暴露内部细节
  • 使用参数校验注解(@Valid)确保输入数据的有效性
2.4 响应格式化与多格式支持配置

响应格式化是REST API设计的重要环节,它决定了客户端如何接收和处理数据

15

。在Spring MVC中,我们可以使用以下方式实现:

java

深色版本

@RestController
@RequestMapping("/users")
public class UserController {@GetMapping(produces = "application/json")public ResponseEntity<List<User>> AllUsers() {// 返回JSON格式}@GetMapping(produces = "application/xml")public ResponseEntity<List<User>> AllUsersXML() {// 返回XML格式}
}

多格式支持是RESTful API的重要特性,它允许客户端指定期望的数据格式

11

。在Spring Boot中,我们可以使用ContentNegotiationManager来配置多格式支持:

java

深色版本

@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void configureContentNegotiation ContentNegotiationManagerBuilder builder) {builder.defaultContentNegotiationStrategy(ContentNegotiationManagerBuilder七大默认策略);}
}

响应格式化的最佳实践

  • 使用JSON作为主要数据格式,因为它轻量且易于解析
  • 支持多种内容类型(如JSON、XML),通过Accept头协商
  • 使用标准的HTTP状态码表示请求结果
  • 对于错误响应,返回包含错误信息和代码的标准格式 

    7

  • 使用自定义响应对象统一API响应格式

3. 关键注解与配置详解

3.1 @RestController与请求映射注解

@RestController是Spring MVC中构建RESTful API的核心注解,它相当于@Controller和@ResponseBody的组合

3

java

深色版本

@RestController
public class UserController {// 所有方法返回值都会被转换为响应体,无需额外使用@ResponseBody
}

请求映射注解用于将HTTP请求映射到控制器方法

3

java

深色版本

@RestController
@RequestMapping("/users")
public class UserController {@GetMappingpublic ResponseEntity<List<User>> AllUsers() {// ...}@PostMappingpublic ResponseEntity<User> createUser(@RequestBody User user) {// ...}@PutMapping("{/id}")public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {// ...}@DeleteMapping("{/id}")public ResponseEntity<?>.deleteUser(@PathVariable Long id) {// ...}@PatchMapping("{/id}")public ResponseEntity<User> patchUser(@PathVariable Long id, @RequestBody UserPatch patch) {// ...}
}

请求映射注解的高级用法

  • 使用consumes指定请求内容类型
  • 使用produces指定响应内容类型
  • 使用method指定允许的HTTP方法
  • 使用params指定请求参数条件

java

深色版本

@RestController
@RequestMapping("/users")
public class UserController {@PostMapping(consumes = "application/json",produces = "application/json",method = RequestMethod.POST,params = "action=register")public ResponseEntity<User> createUser(@RequestBody User user) {// ...}
}

关键配置

  • 在Spring Boot中,无需额外配置就可以使用这些注解
  • 在传统Spring MVC中,需要配置HandlerMapping和HandlerAdapter
3.2 @RequestBody与@PathVariable的深度解析

@RequestBody用于将HTTP请求体转换为Java对象

4

java

深色版本

@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody UserDTO userDTO) {User user = modelMapper.map(userDTO, User.class);User savedUser = userService.save(user);return ResponseEntity created(URI.create("/users/" + savedUser.getId())).body(savedUser);
}

@PathVariable用于从URL路径中提取变量值

4

java

深色版本

@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {User user = userService.getUser(id);if (user == null) {return ResponseEntity.notFound().build();}return ResponseEntity.ok(user);
}

@RequestBody的配置

  • 默认使用Jackson库处理JSON转换
  • 可以通过配置改变转换器
  • 需要处理可能的转换异常

java

深色版本

@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {// 添加或修改消息转换器}
}

@PathVariable的使用场景

  • 表示资源标识符(如/users/123)
  • 表示资源层级关系(如/users/123/orders)
  • 表示资源筛选条件(如/users?status=active)

关键配置

  • 在Spring Boot中,无需额外配置就可以使用这些注解
  • 在传统Spring MVC中,需要配置HandlerMapping和HandlerAdapter
3.3 异常处理注解与全局异常处理器

异常处理是REST API设计的重要环节,它决定了API如何处理错误情况

22

。在Spring MVC中,我们可以使用以下方式实现:

java

深色版本

@RestController
@RequestMapping("/users")
public class UserController {@GetMapping("{/id}")public ResponseEntity<User> getUser(@PathVariable Long id) {User user = userService.getUser(id);if (user == null) {throw new UserNotFoundException("用户不存在");}return ResponseEntity.ok(user);}
}

全局异常处理器用于统一处理API中的异常

22

java

深色版本

@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(UserNotFoundException.class)public ResponseEntity<Error> handleUserNotFound(UserNotFoundException ex) {Error error = new Error();error码 = 404;error消息 = ex.getMessage();return ResponseEntity.status(HttpStatus NOT_FOUND).body(error);}@ExceptionHandler(AssertionError.class)public ResponseEntity<Error> handleAssertionError(AssertionError ex) {Error error = new Error();error码 = 500;error消息 = ex.getMessage();return ResponseEntity.status(HttpStatus BAD_REQUEST).body(error);}
}

自定义异常用于表示特定的业务错误

22

java

深色版本

public class UserNotFoundException extends RuntimeException {public UserNotFoundException(String message) {super(message);}
}

异常处理的最佳实践

  • 使用自定义异常表示业务错误
  • 使用全局异常处理器统一处理异常
  • 返回包含错误信息和代码的标准格式
  • 根据异常类型返回合适的HTTP状态码
  • 避免暴露敏感信息和内部错误细节
3.4 跨域请求处理与安全配置

跨域请求处理(CORS)是REST API设计中常见的需求,在Spring MVC中可以通过以下方式实现:

java

深色版本

@RestController
@RequestMapping("/users")
@CrossOrigin(origins = "http://localhost:3000")
public class UserController {// ...
}

全局CORS配置用于统一处理所有API的跨域请求

17

java

深色版本

@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOrigins("http://localhost:3000", "https://example.com").allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH").allowedHeaders("*").exposedHeaders("Authorization").allowCredentials(true).maxAge(3600);}
}

安全配置是REST API设计的重要环节,在Spring Security中可以通过以下方式实现:

java

深色版本

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/users/**").permitAll().antMatchers("/admin/**").hasRole("ADMIN").anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and().csrf().disable(); // 关闭CSRF防护}@Autowiredpublic void configureGlobal plasure.antMatchers("/users/**").permitAll().antMatchers("/admin/**").hasRole("ADMIN").anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and().csrf().disable(); // 关闭CSRF防护}@Autowiredpublic void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("user").password("user").roles("USER").and().withUser("admin").password("admin").roles("ADMIN");}
}

安全配置的最佳实践

  • 使用Spring Security进行身份验证和授权
  • 根据需要启用或禁用CSRF防护
  • 使用HTTPS保护数据传输
  • 使用JWT进行无状态认证
  • 使用OAuth2进行第三方认证

4. 实战案例:用户管理API开发

4.1 用户实体类与DTO设计

用户实体类(User)表示业务层的用户数据:

java

深色版本

@Entity
@Table(name = "users")
@Data
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(length = 50, nullable = false)@NotBlank(message = "用户名不能为空")@Size(min = 3, max = 50, message = "用户名长度应在3-50之间")private String username;@Column(length = 100, nullable = false)@NotBlank(message = "密码不能为空")@Size(min = 8, max = 100, message = "密码长度应在8-100之间")private String password;@Column(length = 100, nullable = false)@Email(message = "邮箱格式不正确")private String email;@Enumerated(EnumType.STRING)@Column(length = 20)private Role role = Role USER;@Column(length = 20)@Enumerated(EnumType.STRING)private Status status = Status active;@Column@Temporal(TemporalType.TIMESTAMP)private Date createTime;@Column@Temporal(TemporalType.TIMESTAMP)private Date last登陆时间;
}

用户DTO(UserDTO)表示API响应的数据:

java

深色版本

@Data
public class UserDTO {private Long id;private String username;private String email;private String role;private String status;private String createTime;private String last登陆时间;// 转换方法public static UserDTO fromUser(User user) {UserDTO userDTO = new UserDTO();userDTO.id = user.getId();userDTO.username = user username();userDTO.email = user email();userDTO.role = user.getRole().name();userDTO.status = user.getStatus().name();userDTO.createTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(user.getCreateTime());userDTO.last登陆时间 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(user LastLoginTime());return userDTO;}
}

角色枚举(Role)表示用户角色:

java

深色版本

public enum Role {USER,ADMIN,MODERATOR
}

状态枚举(Status)表示用户状态:

java

深色版本

public enum Status {活跃,不活跃,已删除
}

实体类与DTO设计的关键点

  • 实体类包含完整的业务逻辑和约束
  • DTO只包含需要暴露给客户端的字段
  • 使用转换方法(如fromUser)将实体转换为DTO
  • 使用枚举表示状态和角色,提高代码可读性
4.2 基础CRUD接口实现

创建用户接口(POST /users):

java

深色版本

@RestController
@RequestMapping("/api/v1/users")
public class UserController {@Autowiredprivate UserService userService;@PostMappingpublic ResponseEntity<UserDTO> createUser(@RequestBody UserCreateDTO userCreateDTO) {// 参数校验if (userCreateDTO == null || Strings.isEmpty(userCreateDTO username())) {return ResponseEntity badRequest().body(new Error("用户名不能为空"));}// 转换为实体User user = UserCreateDTO.toUser(userCreateDTO);// 保存用户User savedUser = userService.save(user);// 返回创建的用户UserDTO userDTO = UserDTO.fromUser(savedUser);return ResponseEntity created(URI.create("/api/v1/users/" + savedUser.getId())).body(userDTO);}
}

获取用户列表接口(GET /users):

java

深色版本

@RestController
@RequestMapping("/api/v1/users")
public class UserController {@Autowiredprivate UserService userService;@GetMappingpublic ResponseEntity<List<UserDTO>> AllUsers() {List<User> users = userService AllUsers();List<UserDTO> userDTOS = users.stream().map(UserDTO::fromUser).collect(Collectors.toList());return ResponseEntity ok(userDTOS);}
}

获取单个用户接口(GET /users/{id}):

java

深色版本

@RestController
@RequestMapping("/api/v1/users")
public class UserController {@Autowiredprivate UserService userService;@GetMapping("{/id}")public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {User user = userService.getUser(id);if (user == null) {return ResponseEntity notFound().build();}UserDTO userDTO = UserDTO.fromUser(user);return ResponseEntity ok(userDTO);}
}

更新用户接口(PUT /users/{id}):

java

深色版本

@RestController
@RequestMapping("/api/v1/users")
public class UserController {@Autowiredprivate UserService userService;@PutMapping("{/id}")public ResponseEntity<UserDTO> updateUser(@PathVariable Long id, @RequestBody UserUpdateDTO userUpdateDTO) {User existingUser = userService.getUser(id);if (existingUser == null) {return ResponseEntity notFound().build();}// 转换为实体User updatedUser = UserUpdateDTO.toUser(existingUser, userUpdateDTO);// 保存用户User savedUser = userService.save(updatedUser);// 返回更新后的用户UserDTO userDTO = UserDTO.fromUser(savedUser);return ResponseEntity ok(userDTO);}
}

删除用户接口(DELETE /users/{id}):

java

深色版本

@RestController
@RequestMapping("/api/v1/users")
public class UserController {@Autowiredprivate UserService userService;@DeleteMapping("{/id}")public ResponseEntity<?>.deleteUser(@PathVariable Long id) {User user = userService.getUser(id);if (user == null) {return ResponseEntity notFound().build();}userService.delete(user);return ResponseEntity noContent().build();}
}

基础CRUD接口的关键点

  • 使用标准的HTTP方法表示操作(GET、POST、PUT、DELETE)
  • 使用标准的HTTP状态码表示结果(200、201、204、404、400)
  • 使用DTO解耦业务实体和API响应
  • 使用ResponseEntity返回带有状态码的响应
  • 使用参数校验确保输入数据的有效性
4.3 分页与复杂查询接口

分页接口(GET /users?page=0&size=10):

java

深色版本

@RestController
@RequestMapping("/api/v1/users")
public class UserController {@Autowiredprivate UserService userService;@GetMappingpublic ResponseEntity<Page<UserDTO>>页码, @RequestParam(required = false, defaultValue = "10") Integer大小,@RequestParam(required = false) String name,@RequestParam(required = false) String email,@RequestParam(required = false) String role,@RequestParam(required = false) String status) {// 构建分页参数Pageable pageable = PageRequest ofPage(page, size);// 构建查询条件Example<User> example = Example of(User.class).with ignoreNulls().where(name != null ? Example where username(). containing(name) : null,email != null ? Example where email(). containing(email) : null,role != null ? Example where role(). eq(Role.valueOf(role)) : null,status != null ? Example where status(). eq(Status.valueOf(status)) : null);// 查询用户Page<User> users = userService AllUsers(example, pageable);// 转换为DTOPage<UserDTO> userDTOS = users.map(UserDTO::fromUser);return ResponseEntity ok(userDTOS);}
}

复杂查询接口(GET /users?sort=name&order=asc):

java

深色版本

@RestController
@RequestMapping("/api/v1/users")
public class UserController {@Autowiredprivate UserService userService;@GetMappingpublic ResponseEntity<List<UserDTO>>查询(@RequestParam(required = false) String name,@RequestParam(required = false) String email,@RequestParam(required = false) String role,@RequestParam(required = false) String status,@RequestParam(required = false) String sort,@RequestParam(required = false) String order) {// 构建查询条件Example<User> example = Example of(User.class).with ignoreNulls().where(name != null ? Example where username(). containing(name) : null,email != null ? Example where email(). containing(email) : null,role != null ? Example where role(). eq(Role.valueOf(role)) : null,status != null ? Example where status(). eq(Status.valueOf(status)) : null);// 构建排序条件Sort sort = Sort by((sort != null && order != null) ?new Sort(Sort Order.valueOf(order), sort) : null);// 查询用户List<User> users = userService AllUsers(example, sort);// 转换为DTOList<UserDTO> userDTOS = users.stream().map(UserDTO::fromUser).collect(Collectors.toList());return ResponseEntity ok(userDTOS);}
}

分页与复杂查询的关键点

  • 使用Pageable参数处理分页
  • 使用Example参数处理复杂查询条件
  • 使用Sort参数处理排序
  • 使用参数校验确保输入数据的有效性
  • 返回包含分页信息的响应
4.4 全局异常处理与自定义错误响应

全局异常处理器(GlobalExceptionHandler)统一处理API异常

22

java

深色版本

@RestControllerAdvice
public class GlobalExceptionHandler {// 处理用户不存在异常@ExceptionHandler(UserNotFoundException.class)public ResponseEntity<Error> handleUserNotFound(UserNotFoundException ex) {return createErrorEntity(HttpStatus NOT_FOUND, ex.getMessage());}// 处理参数校验异常@ExceptionHandler(MethodArgumentNotValidException.class)public ResponseEntity<Error> handleValidationException(MethodArgumentNotValidException ex) {String message = ex.getBindingResult(). All errors(). get(0).DEFAULT Message();return createErrorEntity(HttpStatus BAD_REQUEST, message);}// 处理一般异常@ExceptionHandler(Exception.class)public ResponseEntity<Error> handleGeneralException(Exception ex) {return createErrorEntity(HttpStatus INTERNAL_SERVER_ERROR, "服务器内部错误");}// 创建错误响应实体private ResponseEntity<Error> createErrorEntity(HttpStatus status, String message) {Error error = new Error();error码 = status.value();error消息 = message;return ResponseEntity.status(status).body(error);}
}

自定义错误对象(Error)表示API错误信息

13

java

深色版本

@Data
public class Error {private Integer code; // 错误码private String message; // 错误信息private String timestamp; // 时间戳private String path; // 请求路径// 构造方法public Error() {this.timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());}public Error(Integer code, String message) {this();this.code = code;this.message = message;}
}

全局异常处理的关键点

  • 使用@RestControllerAdvice注解声明全局异常处理器
  • 使用@ExceptionHandler注解处理特定异常
  • 返回统一的错误格式,包含错误码和信息
  • 避免暴露敏感信息和内部错误细节
  • 根据异常类型返回合适的HTTP状态码
4.5 API版本控制策略实现

URI版本控制(/api/v1/users):

java

深色版本

@RestController
@RequestMapping("/api/v1/users")
public class UserControllerV1 {// v1版本的接口实现
}@RestController
@RequestMapping("/api/v2/users")
public class UserControllerV2 {// v2版本的接口实现
}

请求头版本控制(X-API-Version: v1):

java

深色版本

// 自定义版本条件
public class VersionCondition implements RequestCondition<VersionCondition> {private final String version;public VersionCondition(String version) {this.version = version;}@Overridepublic VersionCondition combine(VersionCondition other) {return new VersionCondition(other.version);}@Overridepublic VersionCondition getMatchingCondition(HttpServletRequest request) {String requestedVersion = request.getHeader("X-API-Version");if (version.equals(requestedVersion)) {return this;}return null;}@Overridepublic int.compareTo(VersionCondition other, HttpServletRequest request) {return this.version.compareTo(other.version);}
}// 自定义版本注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interfaceApiVersion {String value() default "v1";
}// 自定义版本映射
public class VersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {@Overrideprotected RequestMappingInfo getMappingForMethod(Method method, Class<?> declaringClass) {RequestMappingInfo info = super.getMappingForMethod(method, declaringClass);if (info != null) {info = info.combinedWith(new VersionCondition(method.getAnnotation(ApiVersion.class) != null ?method.getAnnotation(ApiVersion.class).value() :declaringClass.getAnnotation(ApiVersion.class) != null ?declaringClass.getAnnotation(ApiVersion.class).value() :"v1"));}return info;}
}// 注册自定义版本映射
@Configuration
public class WebConfig implements WebMvcConfigurer {@Beanpublic RequestMappingHandlerMapping requestMappingHandlerMapping() {return new VersionRequestMappingHandlerMapping();}
}// 使用版本注解的控制器
@RestController
@RequestMapping("/api/users")
@ApiVersion("v1")
public class UserControllerV1 {// v1版本的接口实现
}@RestController
@RequestMapping("/api/users")
@ApiVersion("v2")
public class UserControllerV2 {// v2版本的接口实现
}

API版本控制的关键点

  • 使用URI路径或请求头标识API版本
  • 使用自定义注解和条件匹配实现版本控制
  • 全局配置版本映射处理器
  • 不同版本的接口可以共存,客户端通过版本标识选择
  • 版本控制策略应该简单明了,易于理解

5. 设计最佳实践与常见问题

5.1 参数校验与自定义校验规则

参数校验是REST API设计的重要环节,它确保客户端传递的参数符合预期

7

java

深色版本

@RestController
@RequestMapping("/api/v1/users")
public class UserController {@PostMappingpublic ResponseEntity<UserDTO> createUser(@Valid @RequestBody UserCreateDTO userCreateDTO) {// ...}
}// 参数校验DTO
@Data
public class UserCreateDTO {@NotBlank(message = "用户名不能为空")@Size(min = 3, max = 50, message = "用户名长度应在3-50之间")private String username;@NotBlank(message = "密码不能为空")@Size(min = 8, max = 100, message = "密码长度应在8-100之间")private String password;@Email(message = "邮箱格式不正确")private String email;@Enumerated(EnumType STRING)@Column(length = 20)private Role role = Role USER;// 转换方法public static User toUser(UserCreateDTO userCreateDTO) {User user = new User();user.setUsername(userCreateDTO username());user.setPassword(userCreateDTO password());user.setEmail(userCreateDTO email());user.setRole(userCreateDTO.getRole());return user;}
}

自定义校验规则(CustomValidator)用于处理更复杂的校验逻辑

7

java

深色版本

@Constraint(validatedBy = UniqueEmailValidator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface UniqueEmail {String message() default "邮箱已存在";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}// 自定义校验器
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {@Autowiredprivate UserService userService;@Overridepublic void initialize(UniqueEmail constraintAnnotation) {// 初始化}@Overridepublic boolean isValid(String email, ConstraintValidatorContext context) {if (email == null || email.trim().isEmpty()) {return true;}return userService.checkEmail Unique(email);}
}// 使用自定义校验注解
@Data
public class UserCreateDTO {@NotBlank(message = "用户名不能为空")@Size(min = 3, max = 50, message = "用户名长度应在3-50之间")private String username;@NotBlank(message = "密码不能为空")@Size(min = 8, max = 100, message = "密码长度应在8-100之间")private String password;@Email(message = "邮箱格式不正确")@UniqueEmailprivate String email;@Enumerated(EnumType STRING)@Column(length = 20)private Role role = Role USER;// 转换方法public static User toUser(UserCreateDTO userCreateDTO) {User user = new User();user.setUsername(userCreateDTO username());user.setPassword(userCreateDTO password());user plemail(userCreateDTO email());user.setRole(userCreateDTO.getRole());return user;}
}
http://www.dtcms.com/a/320999.html

相关文章:

  • 2025 TexLive+VScode排版IEEE TGRS论文
  • 使用 Gulp 替换 XML 文件内容
  • SpringMVC(四)
  • 跨平台音乐管理新方案:Melody如何实现一站式音源整合
  • 设计模式(二)——策略模式
  • MySQL 索引详细说明
  • 12. 消息队列-RabbitMQ
  • 行业速览:中国新能源汽车市场格局与关键趋势
  • 【LLM实战】RAG初体验,两种实现方式
  • wstool的一个完整的工作流解析
  • 安全合规1--实验:ARP欺骗、mac洪水攻击、ICMP攻击、TCP SYN Flood攻击
  • 【Day 19】Linux-网站操作
  • mac笔记本如何重新设置ssh key
  • 使用 ECharts GL 实现 3D 中国地图点位飞线效果
  • GoLand 项目从 0 到 1:第六天 —— 权限接口开发与问题攻坚
  • 笔试——Day32
  • pycharm常见环境配置和快捷键
  • 微软XBOX游戏部门大裁员
  • vue项目常见BUG和优化注意事项
  • HTTP 请求返回状态码和具体含义?200、400、403、404、502、503、504等
  • OpenBMC中libgpio架构与驱动交互全解析:从硬件映射到应用控制
  • 智能厨具机器人的革命性升级:Deepoc具身模型外拓板技术解析
  • 【Rust】多级目录模块化集成测试——以Cucumber为例
  • 服务器登上去,显示 failed to send WATCHDOG 重启有效吗?
  • 当服务器多了时,如何管理?
  • 机柜内部除了服务器还有哪些组件?
  • 防火墙概述
  • 手动开发一个TCP服务器调试工具(四):构建完整的UI与功能联合的TCP服务器应用
  • 脚本统计MongoDB集合结构信息
  • 从0开始的中后台管理系统-5(userList动态展示以及上传图片和弹出创建用户表单)