SpringBoot 实现统一参数校验

一、业务需求

与第三方平台对接,第三方调用接口实现数据上报。由于接口传参较多,要对每一个参数做校验,如果写工具类对每个参数校验会很麻烦,因为,使用springboot自带的校验功能实现对参数的统一校验,大大减少代码量,通过注解的方式,使代码更加简洁。

二、具体实现

首先说明下传参的格式,因为传参的格式不同也会影响注解的使用。由于文档格式是之前的同事定好,所以不好随意更改,只能按照他这种格式写。

{
    "info": [
        {
            "param": "320106",
            "param1":"11111",
            "param2": "测试部门",
            "param3": "测试单位",
            "param4": "测试1213",
            "param5": "17311111111"
        
        }
    ]
}
  1. 参数封装类编写
    这是一个List集合,对于这种格式的参数校验,我采取的办法是使用@valid注解。需要注意的是,@Valid注解是可以加在属性上的,而@Validate注解是不能加载属性的。加在属性上,可以实现嵌套验证。
  • 定义传参结构和实体类
@Data
public class MsgSyncList<E>{
    // 属性添加Valid注解,支持该属性进行嵌套验证
    @Valid
    List<E> info;
}
  • 定义参数实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SyncDeptParam {
    @NotBlank(message = "区域编码不能为空")
    private String  regionCode;

    @NotBlank(message = "部门id不能为空")
    private String deptId;

    @NotBlank(message = "部门名称不能为空")
    private String groupName;

    private String superGroup;

    @NotBlank(message = "部门联系人不能为空")
    private String contactName;

    @NotBlank(message = "部门联系人手机号不能为空")
    @Length(min = 11, max = 11, message = "部门联系人手机号只能为11位")
    @Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "部门联系人手机号格式有误")
    private String contactPhone;

}
  1. 封装返回信息
@Getter
public enum SyncMsgCode {
    /**
     * 成功
     */
    SUCCESS(100, "成功"),
    /**
     * 失败
     */
    FAILED(102, "失败"),
    /**
     * 推送的JSON格式有误
     */
    ERR_JSON(103, "推送的JSON格式有误"),
    /**
     * 数据验证错误
     */
    ERR_VALIDATE(104, "数据验证错误"),
    /**
     * 其他错误
     */
    ERR_OTHER(105, "其他错误"),
    /**
     * 所属部门不存在
     */
     DEPT_NOT_FOUND(106, "所属部门不存在"),
   
    /**
     * 区域编码不存在
     */
    ERR_REGIONCODE(111, "区域编码不存在"),
    /**
     * 用户名或者密码不对
     */
    ERR_USERNAME_PWD(112, "用户名或者密码不对");

    private int code;
    private String msg;
    SyncMsgCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}
  1. 统一异常处理

/**
 * 全局统一的异常处理
 **/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 方法参数错误异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public SyncReturnMsg methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        log.error("方法参数错误异常");
        Set<String> list = new HashSet<>();        // 从异常对象中拿到ObjectError对象
        List<ObjectError> objectErrorList = e.getBindingResult().getAllErrors();
        if (!objectErrorList.isEmpty()) {
            for (ObjectError error : objectErrorList) {
                list.add(error.getDefaultMessage());
            }
        }
        // 然后提取错误提示信息进行返回
        return new SyncReturnMsg(SyncMsgCode.ERR_VALIDATE, list);
    }

    /**
     * 集合校验异常处理
     *
     * @param exception
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public SyncReturnMsg handlerConstraintViolationException(ConstraintViolationException exception) {
        log.error("方法参数错误异常:{}", exception);
        Set<ConstraintViolation<?>> constraintViolations = exception.getConstraintViolations();
        List<String> list = new ArrayList<>();
        for (ConstraintViolation<?> constraintViolation : constraintViolations) {
            String index = constraintViolation.getPropertyPath() != null ?
                    constraintViolation.getPropertyPath().toString() : "";
            list.add(index.substring((index.substring(0, index.indexOf(".")).length() + 1))
                    + ":" + constraintViolation.getMessage());
        }
        return new SyncReturnMsg(SyncMsgCode.ERR_VALIDATE, list);
    }
}

5.控制器添加注解实现参数校验

@Api(tags = "下级平台数据同步到省级平台")
@RestController
@RequestMapping("/api")
@Slf4j
@Validated
public class GwcMsgSyncController {

    @Autowired
    private SyncService syncService;

    @ApiOperation("部门基本信息同步")
    @SecurityParam
    @RequestMapping(value = "/editDept")
    public SyncReturnMsg editGroup(@RequestBody @Valid MsgSyncList<SyncDeptParam> deptSyncParamList) {
        log.info("部门信息同步入参:{}", JSONObject.toJSONString(deptSyncParamList));
        try {
            return syncService.editDept(deptSyncParamList);
        } catch (Exception e) {
           log.error("部门基本信息同步异常:{}", e);
           return new SyncReturnMsg(SyncMsgCode.FAILED, "部门信息同步失败");
        }
    }
 }
  1. 由于对接需要对参数进行加密,所以使用了自定义注解,对加了注解的请求进行参数解密。
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Mapping
@Documented
public @interface SecurityParam {
    /**
     * 入参是否解密,默认解密
     */
    boolean inDecode() default true;

    /**
     * 出参是否加密,默认加密
     */
    boolean outEncode() default true;
}

  1. 对参数进行解密处理

/** 下级平台上报请求数据解密
 * @author : hezr
 * @description :
 * @date : 2022/02/23
 **/
@ControllerAdvice
public class SyncRequestControllerAdvice implements RequestBodyAdvice {
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return methodParameter.getMethod().isAnnotationPresent(SecurityParam.class);
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        if (parameter.getMethod().isAnnotationPresent(SecurityParam.class)) {
            SecurityParam secretAnnotation = parameter.getMethod().getAnnotation(SecurityParam.class);
            if (secretAnnotation.inDecode()) {
                return new HttpInputMessage() {
                    @Override
                    public InputStream getBody() throws IOException {
                        String bodyStr = IOUtils.toString(inputMessage.getBody(), "utf-8");
                        MsgSyncList<Object> list = null;
                        if(!JsonHelper.isJson(bodyStr)) {
                            bodyStr = bodyStr.substring(1, bodyStr.lastIndexOf("\""));
                            try {
                                bodyStr = SM4Util.decodeSms4HexToString(bodyStr);
                                list = JSONObject.parseObject(bodyStr, MsgSyncList.class);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            return IOUtils.toInputStream(JSONObject.toJSONString(list), "utf-8");
                        }
                        list = JSONObject.parseObject(bodyStr, MsgSyncList.class);
                        return IOUtils.toInputStream(JSONObject.toJSONString(list), "utf-8");
                    }
                    @Override
                    public HttpHeaders getHeaders() {
                        return inputMessage.getHeaders();
                    }
                };
            }
        }
        return inputMessage;
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }
}
上一篇:JAVA审计-文件上传


下一篇:对朋友已有的项目进行二次开发