基础设施建设——全局请求参数校验

基础设施建设——全局请求参数校验

Bean Validation 漫谈 一文中已经对Bean Validation进行了详细的介绍,以及Spring Validator与Jakarta Bean Validation 规范的关系,本文讨论在微服务架构中,如何做全局的请求参数校验。

1.基于SpringMVC的http接口如何校验

在Spring Framework中有自己的Validator接口,但是其API 设计的比较简陋,而且需要编写大量 Validator 实现类,与javax bean validation的注解式校验相形见绌,于是在Spring 3.0版本开始,Spring Validator将其所有校验请求转发至Jakarta Bean Validation接口的实现中。下面举例介绍在Spring Web框架下如何做请求参数校验。

实体类:

@Data
public class Entity {
  
    @NotBlank(message = "名称不能为空")
    private String name;

    @NotBlank(message = "描述不能为空")
    private String description;

    @NotNull(message = "类型不能为null")
    private Byte type;
}

controller层:

@RestController
@RequestMapping("/demo")
@Validated
public class DemoController {

    @PostMapping("create")
    public String create(@Valid @RequestBody Entity entity) {
        return "ok";
    }
}

上述代码涉及到两个注解@Validated和@Valid,其中@Valid是JSR-303规范中的注解,@Validated是由Spring提供的注解,具体区别为:

  1. 注解使用的位置:@Validated可以用在类型、方法和方法参数上,但是不能用在成员属性上;而@Valid可以用在方法、构造函数、方法参数和成员属性上;
  2. 分组校验:@Validated提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制;
  3. 嵌套校验:二者均无法单独提供嵌套校验的功能,但是可以通过在引用属性上加@Valid注解实现对引用属性中的字段校验;

其中分组校验:

// 分组
public interface Group1{
}
public interface Group2{
}

// 实体类
public class Entity {
	@NotNull(message = "id不能为null", groups = { Group1.class })
	private int id;
 
	@NotBlank(message = "用户名不能为空", groups = { Group2.class })
	private String username;
}

// controller层
public String create(@Validated( { Group1.class }) Entity entity, BindingResult result) {
	if (result.hasErrors()) {
		return "validate error";
	}
	return "redirect:/success";
}

其中嵌套校验:

// Controller层
@RestController
public class DemoController {

    @RequestMapping("/create")
    public void create(@Validated Outer outer, BindingResult bindingResult) {
        doSomething();
    }
}

// 实体类
public class Outer {

    @NotNull(message = "id不能为空")
    @Min(value = 1, message = "id必须为正整数")
    private Long id;

    @Valid
    @NotNull(message = "inner不能为空")
    private Inner inner;
}

// 引用属性
public class Inner {

    @NotNull(message = "id不能为空")
    @Min(value = 1, message = "id必须为正整数")
    private Long id;

    @NotBlank(message = "name不能为空")
    private String name;
}

2.Dubbo接口如何校验

利用dubbo的拦截器扩展点,判断请求参数是否为自定义的Request类型,如果是的话调用validator.validate方法校验参数,并将结果映射出一个属性路径拼接错误信息的list,最终将校验异常信息封装为失败响应返回。下面结合我司的请求规范举例说明:

@Activate(group = Constants.PROVIDER, before = {"DubboExceptionFilter"}, order = -20)
public class ValidatorDubboProviderFilter implements Filter {

    private Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {

        Object[] arguments = invocation.getArguments();
        if (arguments != null && arguments.length > 0 && arguments[0] instanceof Request<?> request) {
            Object requestParams = request.getRequestParams();
            Set<ConstraintViolation<Object>> errors = validator.validate(requestParams);
            List<String> collect = errors.stream().map(error -> error.getPropertyPath() + "," + error.getMessage()).collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(collect)) {
                OpenApiResponse openApiResponse = new OpenApiResponse();
                openApiResponse.setSuccess(false);
                openApiResponse.setCode(DddCons.ValidateError);
                openApiResponse.setMessage(String.join("||", collect));
                if (request.getRequestId() != null) {
                    openApiResponse.setRequestId(request.getRequestId());
                }
                return  AsyncRpcResult.newDefaultAsyncResult(openApiResponse ,invocation);
            }
        }
        return invoker.invoke(invocation);
    }
}

最后别忘了添加META-INF/dubbo/org.apache.dubbo.rpc.Filter

ValidatorDubboProviderFilter=com.xxx.infrastructure.tech.validator.dubbo.ValidatorDubboProviderFilter

3.OpenFeign接口如何校验

OpenFeign接口的校验与SpringMVC的接口校验类似,下面举例说明:

@FeignClient(value = "user", fallback = UserHystrix.class)
@Validated
public interface UserService {

    @PostMapping(value = "/user/hello")
    UserDto hello(@RequestParam("name") @NotEmpty String name);
    
    @PostMapping("/user/add")
    UserDto add(@RequestBody @Validated UserDto userDTO);
    
    @PostMapping("/user/list")
    void testParamList(@RequestBody @Valid List<UserDto> userList);
}

本博客内容仅供个人学习使用,禁止用于商业用途。转载需注明出处并链接至原文。

posted @ 2024-06-07 21:03  爱吃麦辣鸡翅  阅读(13)  评论(0编辑  收藏  举报