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.pswp.cn/web/92507.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/92507.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于STM32F030C8T6单片机实现与CH224Q诱骗芯片的I2C通信和电压输出配置

基于项目的需要,对STM32F030的IIC研究了几天,终于完成了通信,接下来具体实现如下: 本单片机使用的是PB8和PB9管脚进行实现,采用的是模拟的IIC进行 void MyI2C_W_SCL(uint8_t BitValue)//这三个函数将读写io口封装起来,增强可读性 { GPIO_WriteBit(GPIOB, GPIO_Pin_8…

TSMaster-C小程序使用

打开同星的TSMaster&#xff0c;推荐用32版本的&#xff0c;比64更稳定。同星的TSMaster的C小程序支持用户嵌入代码来控制CAN报文的收发逻辑。便于开发。点击设计里面的C小程序。 比如我现在想用小程序来实现继电器0先开后关开1s关1s&#xff0c;然后继电器1开1s关1s…如此往复…

XSS渗透测试原理/步骤/攻击方法/防御/常用语法

**核心概念回顾&#xff1a;**XSS漏洞一直被评估为web漏洞中危害较大的漏洞&#xff0c;在OWASP TOP10的排名中一直属于前三的江湖地位。XSS是一种发生在前端浏览器端的漏洞&#xff0c;所以其危害的对象也是前端用户。 形成XSS漏洞的主要原因是程序对输入和输出没有做合适的处…

目标检测数据集 - 自动驾驶场景道路异常检测数据集下载「包含VOC、COCO、YOLO三种格式」

数据集介绍&#xff1a;自动驾驶场景道路异常检测数据集&#xff0c;真实场景高质量道路图片数据&#xff0c;涉及场景丰富&#xff0c;且类别丰富&#xff0c;划分为 "LMVs 轻型机动车&#xff08;汽车、摩托车、小型卡车、小型货车"、"HMVs 公交车、卡车、拖拉…

多模态新方向|从数据融合到场景落地,解锁视觉感知新范式

来gongzhonghao【图灵学术计算机论文辅导】&#xff0c;快速拿捏更多计算机SCI/CCF发文资讯&#xff5e;多模态学习&#xff08;Multimodal Learning&#xff09;是通过整合多种数据模态来提升模型对复杂场景感知与理解能力的技术&#xff0c;其核心是利用不同模态的互补性突破…

机器学习之随机森林

目录 一、什么是随机森林&#xff1f; 1. 从决策树到集成学习&#xff1a;为什么需要 "森林"&#xff1f; 2.什么是集成学习 二、随机森林的工作原理 三、随机森林构造过程 四、随机森林api介绍 五、随机森林的优缺点 六、垃圾邮件判断案例 1.数据集介绍 ​…

云平台运维工具 —— 阿里云原生工具

一、简介阿里云作为国内领先的云服务提供商&#xff0c;拥有一套完整的原生运维工具体系&#xff0c;这些工具与阿里云的各类服务深度融合&#xff0c;能够满足用户在资源部署、监控告警、权限管理、自动化运维等方面的需求。无论是简单的应用托管还是复杂的企业级架构&#xf…

Linux-Day10.系统安全保护web服务管理

今日目标&#xff1a;- 日志管理- 系统安全保护 SELinux&#xff08;重点&#xff09;- 构建基本web服务&#xff08;重点&#xff09;环境准备还原快照网络配置完成&#xff0c;开启虚拟机A与虚拟机B用真机连通虚拟机去操作&#xff0c;准本好Xshell一、常用的网络工具ip命令1…

解决:开启魔法后vscode pip命令不能安装中科大python镜像问题

闲言少叙&#xff0c;最终实现效果就是在开启魔法情况下&#xff0c;vscode命令行任何能通过中科大python镜像安装第三方库&#xff0c;又快又不消耗魔法流量。简单来说就两步&#x1f447;&#xff1a; 第一步&#xff1a;配置 pip.ini 中的代理 找到或创建 pip.ini 文件&…

优化Google Pubsub到GCS的文件整合策略

引言 在使用Google Cloud Platform (GCP) 的Pubsub服务时,我们常常会遇到将消息存储到Google Cloud Storage (GCS) 作为Avro文件的问题。本文将深入探讨如何优化Google Pubsub到GCS的文件整合策略,以避免每个消息都单独生成一个Avro文件,达到将多个消息整合到一个文件的目的…

基于铁头山羊STM32的平衡车电机转速开环闭环matlab仿真

基于铁头山羊STM32的平衡车电机转速开环闭环matlab仿真前言一、电机开环传递函数1.1 电机开环传递函数的零极点1.2 求系统的参数和绘制波特图二、增加PI控制器后系统开环传递函数三、电机系统闭环传递函数四、simulink仿真五、幅值裕度、相位裕度、相位穿越频率和截止频率&…

P1044 [NOIP 2003 普及组] 栈

P1044 [NOIP 2003 普及组] 栈 - 洛谷 题解来自洛谷题解&#xff0c;做笔记用 假设用一个函数来表示&#xff1a; x表示当前还未入栈的数字个数 y表示当前栈中的数字个数 orz&#xff0c;大佬们真的是很厉害&#xff0c;想着递推但是只拿了60分 #include <bits/stdc.h&g…

linux mysql 8.X主从复制

准备两台linux服务器,注意要锁ip我这里如上图 主库 192.168.5.5/24 从库 192.168.5.10/24 接下来确定mysql是否启动成功并且能从外部连接 主库从库主服务器配置 vim编辑主服务器配置 vim /etc/my.cnf注意是下面那个添加配置代码 log-binmysql-bin # 配置二进制日志 server-id1…

豆包新模型矩阵+PromptPilot:AI开发效率革命的终极方案

> **一套让AI开发者告别“调参炼狱”的黄金组合,效率提升300%的实战指南** ## 一、AI开发的范式转移:从通用模型到**场景化矩阵** 2025年,AI应用开发面临核心矛盾:**业务场景高度细分**与**模型能力同质化**的冲突。火山引擎的破局之道是推出**豆包1.6模型矩阵**——三…

瑞利杂波背景下不同环境的虚警概率与目标检测概率仿真

仿真方案&#xff0c;研究在瑞利杂波背景下&#xff0c;均匀环境、多目标环境和杂波墙环境中的虚警概率(Pfa)和目标检测概率(Pd)。 理论基础 瑞利分布 瑞利分布常用于描述雷达杂波的幅度分布&#xff1a; p(x) (x/σ) * exp(-x/(2σ)), x ≥ 0其中σ是尺度参数&#xff0c;决定…

Spring Boot + Tesseract异步处理框架深度解析,OCR发票识别流水线

Spring Boot Tesseract异步处理框架深度解析&#xff0c;OCR发票识别流水线一、系统架构设计1.1 分布式流水线架构1.2 核心组件职责1.3 数据流设计二、Spring Boot异步框架实现2.1 线程池优化配置2.2 异步服务层设计2.3 异步流水线编排三、Tesseract深度优化3.1 发票专用训练模…

Arm Qt编译Qt例程出错 GLES3/gl3.h: No such file or directory

解决方法 PC&#xff1a;Ubuntu22.04.1 QtCreator&#xff1a; 4.11.1 交叉编译环境&#xff1a;YC6254 开发板提供的 5-编译工具链->qt交叉编译工具 在之前博客配置成功的交叉编译环境&#xff0c;编译Qt5.14.8自带部分Example时&#xff0c;出现 GLES3/gl3.h: No such …

HydroOJ:开源在线判题系统的创新与实践

HydroOJ&#xff1a;开源在线判题系统的创新与实践 在数字化与信息化深度融合的今天&#xff0c;编程教育已成为全球教育改革的重要方向&#xff0c;而在线判题系统&#xff08;Online Judge&#xff0c;简称 OJ&#xff09;作为编程学习、算法训练和竞赛组织的核心工具&#…

tcpdump问题记录

问题一: scapy发送vlan报文&#xff0c;tcpdump过滤抓包未抓到包的问题 发包 sendp([Ether(src"11:22:33:44:55:00")/Dot1Q(vlan1001)/IP()/UDP()/"Hello, VLAN!"], iface"ens9")vlan过滤抓包&#xff0c;不OK。 # tcpdump -i ens9 -nnvve -Q ou…

计算机视觉面试保温:CLIP(对比语言-图像预训练)和BERT技术概述

一、CLIP技术 CLIP&#xff0c;全称 Contrastive Language-Image Pre-training&#xff08;对比语言-图像预训练&#xff09;&#xff0c;是由 OpenAI 在 2021 年提出的一个里程碑式的模型。它的核心思想在于利用自然语言作为监督信号来学习强大的视觉表示&#xff0c;从而打破…