一、业务需求
与第三方平台对接,第三方调用接口实现数据上报。由于接口传参较多,要对每一个参数做校验,如果写工具类对每个参数校验会很麻烦,因为,使用springboot自带的校验功能实现对参数的统一校验,大大减少代码量,通过注解的方式,使代码更加简洁。
二、具体实现
首先说明下传参的格式,因为传参的格式不同也会影响注解的使用。由于文档格式是之前的同事定好,所以不好随意更改,只能按照他这种格式写。
{
"info": [
{
"param": "320106",
"param1":"11111",
"param2": "测试部门",
"param3": "测试单位",
"param4": "测试1213",
"param5": "17311111111"
}
]
}
- 参数封装类编写
这是一个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;
}
- 封装返回信息
@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;
}
}
- 统一异常处理
/**
* 全局统一的异常处理
**/
@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, "部门信息同步失败");
}
}
}
- 由于对接需要对参数进行加密,所以使用了自定义注解,对加了注解的请求进行参数解密。
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Mapping
@Documented
public @interface SecurityParam {
/**
* 入参是否解密,默认解密
*/
boolean inDecode() default true;
/**
* 出参是否加密,默认加密
*/
boolean outEncode() default true;
}
- 对参数进行解密处理
/** 下级平台上报请求数据解密
* @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;
}
}