背景
由于历史背景,公司项目中的接口都使用json格式入参,使用com.alibaba.fastjson.JSONObject来接收,形如
public @ResponseBody
Result xxx(@RequestBody JSONObject reqJsonObj) {
String a = reqJsonObj.getString("a");
String b = reqJsonObj.getString("b");
String c = reqJsonObj.getString("b");
String d = reqJsonObj.getString("b");
String e = reqJsonObj.getString("b");
if (StringUtils.isEmpty(a)) {
logger.error("参数不全");
return new ErrorResult();
}
...
所有接口都是如此风格,想来大家师承一家。现在说说我的修改
参数校验注解
在SpringMVC中提供了很多校验参数的注解,比如@NotNull等。那么我们直接使用注解岂不美滋滋,说干就干,创建实体,在需要参数的上面加规则,执行,没作用。。。
public @ResponseBody
Result xxx(@RequestBody ClassA obj) {
啊,没加@Valid
public @ResponseBody
Result xxx(@RequestBody @Valid ClassA obj) {
起作用了,客户端调用会报400,但是这样太粗暴了,系统希望给客户端返回一个错误信息,而不是直接抛异常。那么这里就需要配合BindingResult使用,
public @ResponseBody
Result xxx(@RequestBody @Valid ClassA obj, BindingResult bindingResult) {
这里Spring会将错误信息注入到BindingResult中,然后我们通过判断BindingResult来处理后续操作,例如
if(bindingResult.hasError()) {
return new ErrorResult(bindingResult.getFieldError().getDefaultMessage())
}
这里可以获取到注解中写入到信息,比如
@NotNull(message = "名字不能为空")
private String name;
这里我多加一步操作,因为我们需要返回一些事先和客户端定义好的状态码,所以我自定了错误码注解,然后在报错的地方获取Field,再获取到错误码。
String fieldName = bindingResult.getFieldError().getField();
Field field = null;
try {
// klass 为需要校验的类
field = klass.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
if (field != null && field.isAnnotationPresent(ErrorParameter.class)) {
ErrorParameter errMsg = field.getAnnotation(ErrorParameter.class);
if (errMsg != null) { // 获取定义的异常码
code = errMsg.errorCode();
}
}
满心欢喜再次测试,嗯,没啥问题,正经测试下,模拟真实数据,然后400…Orz
原因是,入参比我们接收实体中的参数多,所以导致报错,这个关于@RequestBody的知识点自己是真不知道,罪过罪过,挖坑,后面会写Spring学习笔记。
原因找到了,怎么解决呢,虽然多出来的参数后台并不需要(为什么传。。。),但是线上已经是这样了,客户端也无法修改(也不会改~.~),所以只能我这里处理。
要不还是改回去吧,原来的代码也没那么难看。。。
不行不行。。。
自定义注解校验
既然无法使用现成的方法,那就自己写。思路:使用JSONObject接收参数,然后转换为自定义实体类,然后校验实体类参数,最后返回结果。
// 转换
AForm form = jsonObject.toJavaObject(AForm.class);
// 通用校验方法 object 就是上面的form
Field[] fieldArray = object.getClass().getDeclaredFields();
for (Field field : fieldArray) { // 遍历属性
if (field.isAnnotationPresent(MyRule.class)) { // 拿出需要检查的属性
// 自定义校验规则注解
MyRule rule = field.getAnnotation(MyRule.class);
String fieldName = field.getName();
MyRuleEnum[] ruleEnums = rule.rules();
if (!ruleCheck(ruleEnums, field, object)) { // 检查不通过则返回想要的结果
return false;
}
}
}
// 这里有点丑
private static boolean check(MyRuleEnum[] myRuleEnum, Field field, Object object) {
// 这里就是拼一个get方法
Object value = getValue(field, object);
for (MyRuleEnum rule : myRuleEnum) {
switch (rule) {
case NOT_NULL_RULE:
if (value == null)
return false;
break;
case NOT_BLANK_RULE:
if (value == null)
return false;
if (value instanceof String) {
String tmp = (String) value;
if ("".equals(tmp))
return false;
}
break;
}
}
return true;
}
自定义注解如下
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyRule {
MyRuleEnum[] rules() default MyRuleEnum.NOT_NULL_RULE;
int errorCode() default 1;
String errorMsg() default "";
}
那么现在接口部分大概如下
// 转换
AForm form = jsonObject.toJavaObject(AForm.class);
if(!CommonUtil.check(form)) {
// 错误执行代码
}
运行测试通过~~
嗯。。。有点不舒服,每个接口都要写一遍~~来吧,再改下
aop吧~
我的需求很简单,就是在接口入参的地方对入参进行一些校验,校验规则事先定义好,最好还能返回自定义的一些信息,这些需求上面的代码都已经实现,现在再整理下需求,发现一个点,入参的地方,那不就是aop么,在入参的地方一切,悄悄做些操作,接口也不知道,美滋滋,好,干~
思路:拦截controller层,获取入参,获取入参类型,重复上面的校验方法。
aop配置这里就不说了;因为项目中接口很多,不可能一次改完,而且还要测试,所以只能一点一点改,没有修改的接口最好不受影响,所以这里我定义一个注解,表示这个接口需要进行校验。并且可以声明这个接口的入参类型,一举两得 美滋滋。
@MyAPICheck
public @ResponseBody
Result xxx(@RequestBody JSONObject reqJsonObj)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAPICheck {
Class klass() default SomeClass.class;
}
那现在就剩拦截获取注解了
public Object handle(ProceedingJoinPoint pjd) throws Throwable {
Object[] objects = pjd.getArgs();
if (objects != null && objects.length > 0) {
// 没有参数就直接跳过
Class klass = pjd.getTarget().getClass();
Method method = ((MethodSignature) pjd.getSignature()).getMethod();
if (method.isAnnotationPresent(MyAPICheck.class)) {
// 没有这个注解就跳过
MyAPICheck MyAPICheck = method.getAnnotation(MyAPICheck.class);
Class parameterClass = MyAPICheck.klass();
Object arg0 = objects[0];
JSONObject jsonObject = (JSONObject) arg0;
Object form = jsonObject.toJavaObject(parameterClass);
if(!CommonUtil.check(form)) {
// 错误执行代码
}
}
}
return pjd.proceed();
}
到此算是真的把自己坑进去了。。。前面说到希望可以温和的返回异常结果,但是现在这里只能抛异常。。。Orz
要不改回原来的版本吧,能用就行,要啥自行车~
不行不行。。。
aop 异常拦截
不幸中的万幸,上面抛出的异常进入了项目controller层的异常拦截(之前的@valid导致的400异常为什么不进?啊,我的Spring~),也是使用aop实现,在这里我只需要根据异常类型返回需要的错误结果即可。
总结
到此一次入参校验的“小改动”算是完成了。。。
ProceedingJoinPoint.getTarget() 获取拦截的对象
Method method = ((MethodSignature) pjd.getSignature()).getMethod();获取拦截的方法
急需补充Spring的知识。