实现参数校验,统一异常处理,自定义参数校验器

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值