dubbo实现参数校验
Filter类
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.kuyu.framework.core.vo.ResultVO;
import com.kuyu.framework.enums.ReturnEnum;
import com.kuyu.framework.message.SpringContextUtil;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.AsyncRpcResult;
import org.apache.dubbo.rpc.Filter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.executable.ExecutableValidator;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* dubbo参数校验过滤器
*
* @author zx
* 2021-10-27
*/
@Activate(order = 2)
public class DubboServiceParameterFilter implements Filter {
/**
* 日志
*/
private static final Logger LOGGER = LoggerFactory.getLogger(DubboServiceParameterFilter.class);
/**
* 校验器线程池
*/
private static ExecutableValidator executableValidator = Validation.buildDefaultValidatorFactory().getValidator().forExecutables();
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) {
// 获取方法
Method method = getMethod(invoker, invocation);
// 记录下请求参数,用于打印参数
Object[] arguments = invocation.getArguments();
if (Objects.nonNull(method)) {
// 获取返回值对象,只有返回值包装成了ResultVO的对象才做处理
Class returnType = method.getReturnType();
String returnClassName = returnType.getName();
if ("com.kuyu.framework.core.vo.ResultVO".equals(returnClassName)) {
// 日志信息
StringBuilder logInfo = new StringBuilder();
// 获取接口类
Class interfaceName = invoker.getInterface();
logInfo.append("|className:").append(interfaceName.getName()).append(".").append(method.getName())
.append("|requestParam:").append(JSON.toJSONString(arguments))
.append("|context:").append(JSON.toJSONString(invocation.getAttachments()));
ApplicationContext applicationContext = SpringContextUtil.getApplicationContext();
Object object = applicationContext.getBean(interfaceName);
Object[] paramList = invocation.getArguments();
//用校验器,校验此object的method方法的paramList是否合法,返回校验结果
Set<ConstraintViolation<Object>> constraintViolations = executableValidator.validateParameters(object, method, paramList);
ResultVO response = getValidationResult(constraintViolations);
if (response != null && ReturnEnum.ERROR_KY00001.getCode().equals(response.getCode())) {
AsyncRpcResult asyncRpcResult = new AsyncRpcResult(invocation);
asyncRpcResult.setValue(response);
logInfo.append("|response:").append(JSON.toJSONString(response));
LOGGER.info("commonAccessLog:" + logInfo.toString());
return asyncRpcResult;
}
}
}
return invoker.invoke(invocation);
}
/**
* 获取校验方法
*/
private static Method getMethod(Invoker<?> invoker, Invocation invocation) {
Method[] methods = invoker.getInterface().getDeclaredMethods();
for (Method m : methods) {
// 即获取此次dubbo invocation对应的method对象
boolean needCheck = m.getName().equals(invocation.getMethodName())
&& invocation.getArguments().length == m.getParameterCount();
if (needCheck) {
if (matchMethod(invocation.getParameterTypes(), m.getParameterTypes())) {
return m;
}
}
}
return null;
}
/**
* 获取匹配的方法
*
* @param invokerMethodParamClassList 方法列表
* @param matchMethodParamClassList 匹配的方法列表
* @return boolean
*/
private static boolean matchMethod(Class[] invokerMethodParamClassList, Class[] matchMethodParamClassList) {
for (int i = 0; i < invokerMethodParamClassList.length; i++) {
if (!invokerMethodParamClassList[i].equals(matchMethodParamClassList[i])) {
return false;
}
}
return true;
}
/**
* 校验结果转换返回对象
*/
private static <T> ResultVO getValidationResult(Set<ConstraintViolation<T>> set) {
if (set != null && !set.isEmpty()) {
Map<String, String> errorMsg = Maps.newHashMap();
for (ConstraintViolation<T> violation : set) {
errorMsg.put(violation.getPropertyPath().toString(), violation.getMessage());
}
return ResultVO.error(ReturnEnum.ERROR_KY00001.getCode(), ReturnEnum.ERROR_KY00001.getMsg(), errorMsg);
}
return ResultVO.success();
}
}
springboot 参数校验统一异常处理
导入依赖
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.1.Final</version>
</dependency>
统一异常处理类
import com.example.common.ApiResult;
import com.example.common.enums.ApiCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolationException;
/**
* 统一异常处理
* 前面说过,如果校验失败,会抛出MethodArgumentNotValidException或者ConstraintViolationException异常。
* 在实际项目开发中,通常会用统一异常处理来返回一个更友好的提示。比如我们系统要求无论发送什么异常,
* http的状态码必须返回200,由业务码去区分系统的异常情况。
*/
@RestControllerAdvice
public class CommonExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(CommonExceptionHandler.class);
/**
* 参数校验错误错误,如果校验失败,
* 会抛出MethodArgumentNotValidException或者ConstraintViolationException异常
*
* @param ex
* @return
*/
@ExceptionHandler({MethodArgumentNotValidException.class})
public ApiResult handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
BindingResult bindingResult = ex.getBindingResult();
StringBuilder sb = new StringBuilder("校验失败:");
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
}
String msg = sb.toString();
return ApiResult.fail(ApiCode.PARAMETER_ERROR, msg);
}
@ExceptionHandler({BindException.class})
public ApiResult handleMethodArgumentNotValidException(BindException ex) {
FieldError fieldError = ((BindException) ex).getBindingResult().getFieldError();
return ApiResult.fail(ApiCode.PARAMETER_ERROR, fieldError.getField() + fieldError.getDefaultMessage());
}
@ExceptionHandler({ConstraintViolationException.class})
public ApiResult handleConstraintViolationException(ConstraintViolationException ex) {
return ApiResult.fail(ApiCode.PARAMETER_ERROR, ex.getMessage());
}
/**
* 400
*
* @param e
* @return
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public ApiResult handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
log.error("参数解析失败", e);
return ApiResult.fail(ApiCode.ERROR_400004);
}
/**
* 405
*
* @param e
* @return
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ApiResult handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
log.error("不支持的请求方式", e);
return ApiResult.fail(ApiCode.ERROR_400005);
}
/**
* 不支持的媒体格式
*
* @param e
* @return
*/
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public ApiResult handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
log.error("不支持的媒体类型", e);
return ApiResult.fail(ApiCode.ERROR_400006);
}
/**
* 其它未知异常
*
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
public ApiResult handlerException(Exception e) {
log.error("未知异常", e);
return ApiResult.fail(ApiCode.ERROR);
}
}
自定义参数校验器:
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {EncryptIdValidator.class})
public @interface EncryptId {
String message() default "加密id格式错误";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
/**
* 自定义一个参数校验器
*/
public class EncryptIdValidator implements ConstraintValidator<EncryptId, String> {
private static final Pattern PATTERN = Pattern.compile("^[0-9]*$");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
//校验逻辑
if (value != null) {
Matcher matcher = PATTERN.matcher(value);
return matcher.find();
}
return true;
}
}
使用
@Data
public class UserDto implements Serializable {
private static final long serialVersionUID = 8612985472141018146L;
@EncryptId(message = "必须是0-9的数字格式")
private String userId;
@NotBlank(message = "用户名不能为空")
@Length(min = 2, max = 10)
private String userName;
@NotBlank(message = "account不能为空")
@Length(min = 6, max = 20)
private String account;
@NotBlank(message = "password不能为空")
@Length(min = 6, max = 20)
private String password;
}