SpringBoot参数校验和返回值封装

SpringBoot参数校验和返回值封装

Spring Boot 简化了配置,但日志管理依然需要重视。日志配置、链路追踪、排查思路都是日常开发中会遇到的问题。本文讲实际项目中的日志管理经验。

参数校验

#

引入依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

#

常用校验注解

注解 说明
@NotNull 不能为 null
@NotBlank 不能为 null 且不为空字符串
@NotEmpty 不能为 null 且不为空(字符串/集合)
@Min / @Max 数字范围
@Size 字符串长度/集合大小
@Email 邮箱格式
@Pattern 正则匹配
@Positive 正数
@Future / @Past 未来/过去日期

#

请求参数校验

##

@Valid + @RequestBody

@Data
public class CreateUserRequest {

@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度2-20")
private String username;

@NotBlank(message = "密码不能为空")
@Size(min = 6, message = "密码至少6位")
private String password;

@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;

@Min(value = 1, message = "年龄不能小于1")
@Max(value = 150, message = "年龄不能大于150")
private Integer age;
}
@RestController
@RequestMapping("/users")
public class UserController {

@PostMapping
public Result<User> createUser(@Valid @RequestBody CreateUserRequest request) {
// ...
}
}

##

@Validated + @RequestParam / @PathVariable

@RestController
@Validated
public class UserController {

@GetMapping("/users/{id}")
public Result<User> getUser(@PathVariable @Min(1) Long id) {
// ...
}

@GetMapping("/users")
public Result<List<User>> listUsers(
@RequestParam @Min(1) @Max(100) Integer pageSize) {
// ...
}
}

##

分组校验

public interface CreateGroup {}
public interface UpdateGroup {}

@Data
public class UserRequest {

@NotNull(groups = UpdateGroup.class)
private Long id;

@NotBlank(groups = {CreateGroup.class, UpdateGroup.class})
private String username;

@NotBlank(groups = CreateGroup.class)
private String password;
}
@PostMapping
public Result<User> create(@Validated(CreateGroup.class) @RequestBody UserRequest request) {
// ...
}

@PutMapping
public Result<User> update(@Validated(UpdateGroup.class) @RequestBody UserRequest request) {
// ...
}

##

嵌套校验

@Data
public class CreateOrderRequest {

@NotBlank
private String orderNo;

@Valid // 嵌套校验
@NotEmpty
private List<OrderItemRequest> items;
}

@Data
public class OrderItemRequest {

@NotBlank
private String skuId;

@Min(1)
private Integer quantity;
}

#

自定义校验注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
String message() default "手机号格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

public class PhoneValidator implements ConstraintValidator<Phone, String> {

private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true; // @NotNull 处理 null
}
return PHONE_PATTERN.matcher(value).matches();
}
}

统一返回值封装

#

统一响应结构

@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
private Long timestamp;

public Result() {
this.timestamp = System.currentTimeMillis();
}

public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}

public static <T> Result<T> fail(Integer code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
return result;
}

public static <T> Result<T> fail(String message) {
return fail(500, message);
}
}

#

全局响应封装

@RestControllerAdvice(basePackages = "com.example.controller")
public class ResponseAdvice implements ResponseBodyAdvice<Object> {

@Autowired
private ObjectMapper objectMapper;

@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 只处理返回类型不是 Result 的方法
return !returnType.getParameterType().equals(Result.class);
}

@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
// String 类型特殊处理
if (body instanceof String) {
try {
return objectMapper.writeValueAsString(Result.success(body));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}

return Result.success(body);
}
}

全局异常处理

@RestControllerAdvice
public class GlobalExceptionHandler {

private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

// 参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleValidation(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining(", "));

return Result.fail(400, message);
}

// 参数校验异常(@RequestParam/@PathVariable)
@ExceptionHandler(ConstraintViolationException.class)
public Result<Void> handleConstraintViolation(ConstraintViolationException e) {
String message = e.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(", "));

return Result.fail(400, message);
}

// 业务异常
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusiness(BusinessException e) {
log.warn("业务异常: {}", e.getMessage());
return Result.fail(e.getCode(), e.getMessage());
}

// 其他异常
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("系统异常", e);
return Result.fail(500, "系统繁忙,请稍后再试");
}
}

分页结果封装

@Data
public class PageResult<T> {
private List<T> list;
private Long total;
private Integer pageNum;
private Integer pageSize;
private Integer totalPages;

public static <T> PageResult<T> of(PageInfo<T> pageInfo) {
PageResult<T> result = new PageResult<>();
result.setList(pageInfo.getList());
result.setTotal(pageInfo.getTotal());
result.setPageNum(pageInfo.getPageNum());
result.setPageSize(pageInfo.getPageSize());
result.setTotalPages(pageInfo.getPages());
return result;
}
}

最佳实践

#

1. 校验消息统一

# messages.properties
javax.validation.constraints.NotBlank.message={0}不能为空
javax.validation.constraints.Size.message={0}长度必须在{min}到{max}之间

#

2. 控制器保持简洁

@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService;

@PostMapping("/users")
public Result<Long> create(@Valid @RequestBody CreateUserRequest request) {
return Result.success(userService.create(request));
}
}

#

3. 异常不暴露内部信息

@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("系统异常", e); // 内部记录详细日志
return Result.fail(500, "系统繁忙"); // 对外返回通用消息
}

总结

场景 方案
请求体校验 @Valid + @RequestBody
URL 参数校验 @Validated + @RequestParam
分组校验 @Validated(Group.class)
嵌套校验 @Valid
自定义规则 自定义注解 + Validator
返回值封装 ResponseBodyAdvice
异常处理 @RestControllerAdvice

规范的参数校验和返回值封装,是构建高质量 REST API 的基础。

核心要点

  1. 日志级别设置:根据环境设置合适的级别

  2. 日志格式配置:添加 traceId 便于链路追踪

  3. 日志输出:控制台输出和文件输出的配置

  4. 日志归档:设置滚动策略和保留时间

总结

日志是排查问题的生命线,合理配置日志可以提升排查效率。在实际项目中,结合 ELK 等工具搭建日志系统,可以更好地管理和分析日志。


   转载规则


《SpringBoot参数校验和返回值封装》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录