基礎設施建設——全域性請求引數校驗

爱吃麦辣鸡翅發表於2024-06-07

基礎設施建設——全域性請求引數校驗

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);
}

本部落格內容僅供個人學習使用,禁止用於商業用途。轉載需註明出處並連結至原文。

相關文章