原版的在此: https://github.com/nutzam/nutzmore/commit/22ccd951426c8e9ba1eb10920edadb7349da3227
但是,一直有个让我不爽的问题,
比如bean里有个int字段,在表单里输入字母,则AbstractAdaptor类里直接报错,转换失败。 根本没有走到后面的validate阶段。
如果在action方法最后加个AdaptorErrorContext类型参数,则AbstractAdaptor类不会报错,可以到后面的validate,但是错误的输入,已经丢失了,没有在bean里面,check不出来了。
对Nutz框架也不熟(熟了也不一定会,哈哈),一直想不通怎么解决。
后来发现了动作链这个东西,所以validate不应该用拦截器的形式,应该用processor的形式, 然后把它放到AdaptorProcessor前面,也就是先进行check,再进行适配器转换。
鼓捣了一天,才大概搞成,还是个半成品,哎~~
大概原理: ------------------------------------------------ PairAdaptor.java里有这么一段:
// POJO if ("..".equals(pm)) { if (clazz.isAssignableFrom(Map.class)) return new MapPairInjector(); //return new ObjectPairInjector(null, type); return new CheckObjectPairInjector(null, type); //就这行不一样 }
就是类似这样的:
@Aop("validationInterceptor") public void doEdit(@Param("..") UserEditDto userDto, HttpServletRequest request, HttpSession session)
代码,userDto就是要check的对象,我把原来的new ObjectPairInjector(null, type)改成了 new CheckObjectPairInjector(null, type)。 在这里面,从request里取到bean的属性值后,不set到bean里,而是返回map<String,Object>。
这样,不用转换,就不会报错了。然后进行check。 ------------------------------------------
(1)加一个mychain.json
{ default : { ps : [ "org.nutz.mvc.impl.processor.UpdateRequestAttributesProcessor", "org.nutz.mvc.impl.processor.EncodingProcessor", "org.nutz.mvc.impl.processor.ModuleProcessor", "org.nutz.mvc.impl.processor.ActionFiltersProcessor", "cn.xxxx.validation.ValidationProcessor", "org.nutz.mvc.impl.processor.AdaptorProcessor", "org.nutz.mvc.impl.processor.MethodInvokeProcessor", "org.nutz.mvc.impl.processor.ViewProcessor" ], error : 'org.nutz.mvc.impl.processor.FailProcessor' } }
其中的,"cn.xxxx.validation.ValidationProcessor",就是增加的处理器,其它是nutz默认的。 每个带url映射的action方法,都会按这个顺序,挨个走processor。
(2)在主模块声明使用该动作链。 @ChainBy(args={"/mychain.json"}) public class MainModule { ..
(3)ValidationProcessor.java
package cn.xxx.data.transfer.validation;
import java.util.Iterator; import java.util.List; import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.nutz.ioc.aop.Aop; import org.nutz.mvc.ActionContext; import org.nutz.mvc.ActionInfo; import org.nutz.mvc.HttpAdaptor; import org.nutz.mvc.Mvcs; import org.nutz.mvc.NutConfig; import org.nutz.mvc.adaptor.CheckPairAdaptor; import org.nutz.mvc.impl.processor.AbstractProcessor;
import cn.xxx.data.transfer.bean.BaseDto; import cn.xxx.data.transfer.validation.annotation.AnnotationValidation;
/** * 可用于 MVC 效验的动作链 * <p> * 要求 action 参数中必须有一个 Errors 类型的参数(不需要使用 Param 声明)。当验证完成后会向这个参数赋值 * * @author QinerG(QinerG@gmail.com) */ public class ValidationProcessor extends AbstractProcessor {
private HttpAdaptor adaptor; private static AnnotationValidation av = new AnnotationValidation();
@Override public void init(NutConfig config, ActionInfo ai) throws Throwable { adaptor = evalHttpAdaptor(config, ai); }
protected static HttpAdaptor evalHttpAdaptor(NutConfig config, ActionInfo ai) { HttpAdaptor re = evalObj(config, ai.getAdaptorInfo()); if (null == re) re = new CheckPairAdaptor(); // 方法上没声明适配器的时候,使用这个默认的适配器。声明的时候呢? else { // JsonAdaptor,上传文件之类的适配器,忽视,不进行check,特别是上传文件,这边解析一遍,会影响后面正常解析。 //那就简单点,文件上传什么的,用客户端check拦截得了。不费这个劲了,累,实在搞不动。 return null; } re.init(ai.getMethod()); return re; }
public void process(ActionContext ac) throws Throwable {
// 不是普通的适配器,则啥也不做 if (adaptor == null) { doNext(ac); return; }
// 看方法上是否有@Aop("validationInterceptor")这样的注解,有注解的才check Aop aop = ac.getMethod().getAnnotation(Aop.class); if (aop == null) { doNext(ac); return; }
// 判断参数是否有BaseDto Class<?>[] types = ac.getMethod().getParameterTypes(); if (types.length == 0) { doNext(ac); return; }
// 看哪个参数是BaseDto的子类 int index = -1; Class<?> dtoClass = null; for (int i = 0; i < types.length; i++) { Class<?> cla = types[i]; if (BaseDto.class.isAssignableFrom(cla)) { index = i; dtoClass = cla; } }
// 没找到,跳过继续 if (index == -1) { doNext(ac); return; }
// 从AdaptorProcessor抄的,从request得到实际参数,但是没放到bean里,是用Map传回来的。 List<String> phArgs = ac.getPathArgs(); Object[] args = adaptor.adapt(ac.getServletContext(), ac.getRequest(), ac.getResponse(), phArgs.toArray(new String[phArgs.size()])); ac.setMethodArgs(args);
// -------------下面是check输入的------------------------------
// 方法没参数,直接跳过 if (args == null || args.length == 0) { doNext(ac); return; }
// BaseDto的才检查,其它无视 Errors es = new Errors(); // 直接new一个 Object dataMap = args[index]; Map<String, Object> formData = null; if (dataMap instanceof Map<?, ?>) { formData = (Map<String, Object>) dataMap; av.validate(dtoClass, formData, es); }
// check未通过,则跳回输入页面 if (es.errorCount() > 0) { StringBuilder sb = new StringBuilder(); Iterator<String> ite = es.getErrorsList().iterator(); while (ite.hasNext()) { sb.append("*").append(ite.next()).append("<br/>"); }
HttpServletRequest request = Mvcs.getReq(); request.setAttribute("errmsg", sb.toString()); // 错误信息 request.setAttribute("formdata", formData); // 表单数据 throw new RuntimeException(); }
doNext(ac); } }
(4)
package org.nutz.mvc.adaptor;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Map;
import org.nutz.lang.Lang; import org.nutz.log.Log; import org.nutz.log.Logs; import org.nutz.mvc.adaptor.injector.ArrayInjector; import org.nutz.mvc.adaptor.injector.CheckObjectPairInjector; import org.nutz.mvc.adaptor.injector.MapPairInjector; import org.nutz.mvc.adaptor.injector.NameInjector; import org.nutz.mvc.adaptor.injector.ObjectNavlPairInjector; import org.nutz.mvc.annotation.Param;
/** * 为了输入检查使用的,抄袭PairAdaptor类. * */ public class CheckPairAdaptor extends AbstractAdaptor{ private static final Log log = Logs.get(); protected ParamInjector evalInjectorBy(Type type, Param param) { // TODO 这里的实现感觉很丑, 感觉可以直接用type进行验证与传递 // TODO 这里将Type的影响局限在了 github issue #30 中提到的局部范围 Class<?> clazz = Lang.getTypeClass(type); if (clazz == null) { if (log.isWarnEnabled()) log.warnf("!!Fail to get Type Class : type=%s , param=%s", type, param); return null; }
Type[] paramTypes = null; if (type instanceof ParameterizedType) paramTypes = ((ParameterizedType) type).getActualTypeArguments();
if (null == param) return null;// 让超类来处理吧,我不管了!! String pm = param.value(); String datefmt = param.dfmt(); // POJO if ("..".equals(pm)) { if (clazz.isAssignableFrom(Map.class)) return new MapPairInjector(); //return new ObjectPairInjector(null, type); return new CheckObjectPairInjector(null, type); //就这行不一样 } // POJO with prefix else if (pm.startsWith("::") && pm.length() > 2) { return new ObjectNavlPairInjector(pm.substring(2), type); } // POJO[] else if (clazz.isArray()) return new ArrayInjector(pm, clazz, paramTypes);
// Name-value return new NameInjector(pm, datefmt, clazz, paramTypes); }
}
(5) [code] package org.nutz.mvc.adaptor.injector;
import java.lang.reflect.Field; import java.lang.reflect.Type; import java.util.LinkedHashMap; import java.util.Map;
import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.nutz.lang.Mirror; import org.nutz.lang.Strings; import org.nutz.lang.inject.Injecting; import org.nutz.mvc.adaptor.ParamConvertor; import org.nutz.mvc.adaptor.ParamExtractor; import org.nutz.mvc.adaptor.ParamInjector; import org.nutz.mvc.adaptor.Params; import org.nutz.mvc.adaptor.convertor.StringParamConvertor; import org.nutz.mvc.annotation.Param;
/** * 为了check使用的,抄袭ObjectPairInjector. * */ public class CheckObjectPairInjector implements ParamInjector{ protected Injecting[] injs; protected String[] names; protected Mirror<?> mirror; protected Field[] fields; protected ParamConvertor[] converters;
public CheckObjectPairInjector(String prefix, Type type) { prefix = Strings.isBlank(prefix) ? "" : Strings.trim(prefix); this.mirror = Mirror.me(type); fields = mirror.getFields(); this.injs = new Injecting[fields.length]; this.names = new String[fields.length]; this.converters = new ParamConvertor[fields.length];
for (int i = 0; i < fields.length; i++) { Field f = fields[i]; this.injs[i] = mirror.getInjecting(f.getName()); Param param = f.getAnnotation(Param.class); String nm = null == param ? f.getName() : param.value(); String datefmt = null == param ? null : param.dfmt(); this.names[i] = prefix + nm; this.converters[i] = new StringParamConvertor(); //这行不一样,全用字符串转换器 } }
public Map<String,Object> get(ServletContext sc, HttpServletRequest req, HttpServletResponse resp, Object refer) { ParamExtractor pe = Params.makeParamExtractor(req, refer);
//下面这段和原来不太一样,没把值写到bean里,那样会转型,会报错。直接扔Map里返回。 Map<String,Object> beanMap = new LinkedHashMap<String,Object>(); for (int i = 0; i < injs.length; i++) { Object param = converters[i].convert(pe.extractor(names[i])); beanMap.put(names[i], param); } return beanMap; }
}
(6)
package cn.xxx.data.transfer.validation.annotation;
import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Map;
import org.nutz.lang.Lang; import org.nutz.lang.Mirror; import org.nutz.lang.Strings;
import cn.xxx.data.transfer.validation.Errors; import cn.xxx.data.transfer.validation.Validation; import cn.xxx.data.transfer.validation.ValidationUtils;
/** * 使用注解驱动的验证解决方案 * * @see ValidationUtils * @see Validation * @author QinerG(QinerG@gmail.com) */ public class AnnotationValidation implements Validation {
/** * 通过注解对一个Pojo进行验证 */ public Errors validate(Object target) { Errors errors = new Errors(); validate(target, errors); return errors; }
/** * 通过注解对一个Pojo进行验证 */ public void validate(Object target, Errors errors) { if (null == target) { return; } // 遍历对象的所有字段 Mirror<?> mirror = Mirror.me(target.getClass()); Field[] fields = mirror.getFields(Validations.class); for (Field field : fields) { // 检查该字段是否声明了需要验证 Validations vals = field.getAnnotation(Validations.class); String errMsg = vals.errorMsg(); try { Method getMethod = mirror.getGetter(field);
Object value = getMethod.invoke(target, new Object[] {}); // 这个对象字段get方法的值
// 验证该字段是否必须 if (vals.required() && !ValidationUtils.required(field.getName(), value, errMsg, errors)) { continue; }
// 账号验证 if (vals.account() && !ValidationUtils.account(field.getName(), value, errMsg, errors)) { continue; }
// 手机号码验证 if (vals.mobile() && Lang.length(value) > 0 && !ValidationUtils.mobile(field.getName(), value, errMsg, errors)) { continue; }
// 验证是否为 email if (vals.email() && !ValidationUtils.email(field.getName(), value, errMsg, errors)) { continue; }
// 验证是否为 qq 号 if (vals.qq() && !ValidationUtils.qq(field.getName(), value, errMsg, errors)) { continue; }
// 必须为中文效验 if (vals.chinese() && !ValidationUtils.chinese(field.getName(), value, errMsg, errors)) { continue; }
// 邮政编码效验 if (vals.post() && !ValidationUtils.post(field.getName(), value, errMsg, errors)) { continue; }
// 验证正则表达式 if (!Strings.isBlank(vals.regex()) && !ValidationUtils.regex(field.getName(), value, vals.regex(), errMsg, errors)) { continue; }
// 验证该字段长度 if (vals.strLen().length > 0 && !ValidationUtils.stringLength(field.getName(), value, vals.strLen(), errMsg, errors)) { continue; }
// 重复值检验 if (!Strings.isBlank(vals.repeat())) { Object repeatValue = mirror.getGetter(vals.repeat()).invoke(target, new Object[] {}); if (!ValidationUtils.repeat(field.getName(), value, repeatValue, errMsg, errors)) { continue; } }
// 判断指定值是否在某个区间 if (vals.limit().length > 0 && !ValidationUtils.limit(field.getName(), value, vals.limit(), errMsg, errors)) { continue; }
//通过 el 表达式进行数值验证 if (!Strings.isBlank(vals.el()) && !ValidationUtils.el(field.getName(), value, vals.el(), errMsg, errors)) { continue;
}
// 自定义验证方法 if (!Strings.isBlank(vals.custom()) && !ValidationUtils.custom(field.getName(), target, vals.custom(), errMsg, errors)) { continue; }
} catch (Exception e) { errors.add(field.getName(), "验证出错,请正确输入。"); } } }
/** * 验证. * @param dtoClass DTO的class * @param formData DTO的数据 * @param errors */ public void validate(Class<?> dtoClass, Map<String, Object> formData, Errors errors) { if (null == formData) { return; } // 遍历对象的所有字段 Mirror<?> mirror = Mirror.me(dtoClass); Field[] fields = mirror.getFields(Validations.class); for (Field field : fields) { // 检查该字段是否声明了需要验证 Validations vals = field.getAnnotation(Validations.class); String errMsg = vals.errorMsg(); try { Object value = formData.get(field.getName()); // 这个对象字段get方法的值
// 验证该字段是否必须 if (vals.required() && !ValidationUtils.required(field.getName(), value, errMsg, errors)) { continue; }
// 账号验证 if (vals.account() && !ValidationUtils.account(field.getName(), value, errMsg, errors)) { continue; }
// 手机号码验证 if (vals.mobile() && Lang.length(value) > 0 && !ValidationUtils.mobile(field.getName(), value, errMsg, errors)) { continue; }
// 验证是否为 email if (vals.email() && !ValidationUtils.email(field.getName(), value, errMsg, errors)) { continue; }
// 验证是否为 qq 号 if (vals.qq() && !ValidationUtils.qq(field.getName(), value, errMsg, errors)) { continue; }
// 必须为中文效验 if (vals.chinese() && !ValidationUtils.chinese(field.getName(), value, errMsg, errors)) { continue; }
// 邮政编码效验 if (vals.post() && !ValidationUtils.post(field.getName(), value, errMsg, errors)) { continue; }
// 验证正则表达式 if (!Strings.isBlank(vals.regex()) && !ValidationUtils.regex(field.getName(), value, vals.regex(), errMsg, errors)) { continue; }
// 验证该字段长度 if (vals.strLen().length > 0 && !ValidationUtils.stringLength(field.getName(), value, vals.strLen(), errMsg, errors)) { continue; }
// 重复值检验 if (!Strings.isBlank(vals.repeat())) { Object repeatValue = formData.get(vals.repeat()); if (!ValidationUtils.repeat(field.getName(), value, repeatValue, errMsg, errors)) { continue; } }
// 判断指定值是否在某个区间 if (vals.limit().length > 0 && !ValidationUtils.limit(field.getName(), value, vals.limit(), errMsg, errors)) { continue; }
//通过 el 表达式进行数值验证 if (!Strings.isBlank(vals.el()) && !ValidationUtils.el(field.getName(), value, vals.el(), errMsg, errors)) { continue;
}
// 自定义验证方法,注掉,反正也不会写 // if (!Strings.isBlank(vals.custom()) // && !ValidationUtils.custom(field.getName(), target, vals.custom(), errMsg, errors)) { // continue; // }
} catch (Exception e) { errors.add(field.getName(), errMsg); } } } }
======================================================== 这是个半成品,只适合普通的表单POST,而文件上传,jsonAdaptor的还是和原来一样,没搞,
反正我没咋用,只是有一个文件上传,直接用客户端js检查一下得了,
我是搞不动了~~
懒得写注释了,就这样吧,备忘一下得了~~
|