[心缘地方]同学录
首页 | 功能说明 | 站长通知 | 最近更新 | 编码查看转换 | 代码下载 | 常见问题及讨论 | 《深入解析ASP核心技术》 | 王小鸭自动发工资条VBA版
登录系统:用户名: 密码: 如果要讨论问题,请先注册。

[备忘]Nutz框架,服务端check的修改。

上一篇:[备忘]span的宽度width设置了无效的问题
下一篇:[备忘]easyui的表单验证

添加日期:2014/10/4 20:08:45 快速返回   返回列表 阅读3871次
原版的在此:
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检查一下得了,

我是搞不动了~~

懒得写注释了,就这样吧,备忘一下得了~~
 

评论 COMMENTS
没有评论 No Comments.

添加评论 Add new comment.
昵称 Name:
评论内容 Comment:
验证码(不区分大小写)
Validation Code:
(not case sensitive)
看不清?点这里换一张!(Change it here!)
 
评论由管理员查看后才能显示。the comment will be showed after it is checked by admin.
CopyRight © 心缘地方 2005-2999. All Rights Reserved