先来描述一下vt(viewTag)难题的根源:

  • 千帆竞发须要:
    在开发的经过中有诸如此类的四个拍卖时间突显的必要:
    比方时间低于1分钟展现秒,
    json,一经时光低于1小时展现分钟,
    假诺时间低于1天来得小时,
    设若当先1天彰显日期。
  • 需要衍生和变化一:
    每三个显得时间的地点后台开发人士都要编写制定1个拍卖这样的3个顺序。
    于是乎想起在后台创立工具类消除这些题目。
  • 要求衍变二:
    只是选择工具类又推动了别的1个题材,每便都要取出这一个字段显示调用工具类
    接下来再塞进去,那样造成代码重复臃肿。
  • 供给演化三:
    后台开发不想用代码完毕就能够轮换来功,换句话说突显和作业并非耦合在协同。
  • 要求衍生和变化四:
    倘使整站出现类似于岁月这种字符串处理的地方重重该怎么处理??
  • 最后供给:
    到此处最终须求就规定啦,相同概念的字符串在页面呈现时不期望体现的逻辑各样人都关心,有框架统一提供达成该怎样落到实处???

解析难题:

咱俩需求分明在哪里举行字符串替换比较适当?

甭管json如故ftl依然jsp的字符串替换大家都必须在并未真的重临给浏览器在此以前来做字符串替换的工作。
大体思路:
发送八个http请求经过解析域名找到呼应的tomcat服务,由tomcat决定调用哪贰个应用程序响应,
然后应用程序找到相应的jsp或许html,大家通过某种情势获得到jsp恐怕html的内容通过规则匹配
就能够展开字符串替换。
实际,最简单易行的点子大家能够创立2个filter,在url请求过来的时候在doFilter()方法中依照某种url规则(比如以.jsp结尾的请求)
收获请求的公文(jsp,ftl,html),
然后读取文件内容开始展览匹配,最后得以成功替换。

我们要明确哪些内容须求被轮换也正是说咱们供给二个标识?

概念三个平整凡是符合规则的就开始展览字符串替换工作,
能够定义1个特有的竹签:比如:

那么五体系的vt我们怎么分明使用哪三个vt处理类进行拍卖啊??

其中

什么样让使用者用的简便??

小编们得以抽取一个vt处理器接口,提供三个对准这一个接口的肤浅达成类,
让其处理相同逻辑的落实并约定好必要求达成的不二法门,
使用者能够由此落到实处那些抽象类实行字符串替换逻辑编写。

轮换相同意义的字符串,那我们怎么规定怎样是一律意义的呢??

本条由使用者去做,去抽象。

何以在框架上进展合并??

在框架上利用那么些的时候需找五个点:
1.框架在视图层有没有提供能够让大家对url访问的财富(文件)进行处理的输入?
2.框架有没有可以让我们的vt相关处理类放到ioc容器管理的办法??

类图设计

json 1

类图讲解:

IViewTag:完成这几个接口,即能够依照标签替换到对应的视图展现内容
AbstractCacheViewTag:抽象的缓存视图标签
DateViewTag: vt引擎的日子处理类 处理 <vt:date uid=”二零一二-12-13
12:31:54″/> 转化为 xx秒前,xx分钟前,xx天前
ViewTag: vt主旨引擎,负责对外提供劳务

具体代码

IViewTag

/**
 * 视图标签
 * <p>
 * 实现这个接口,即可以根据标签替换成对应的视图显示内容
 * 
 * @Date     2016年11月30日 
 */
public interface IViewTag {
    /**
     * 转换
     * <p>
     * 所有需要转换的字符串进行过滤之后,将能进行转换的进行转换放入map
     *
     * @param cmsTags 所有的cms标签
     * @param needChange 需要转换的map
     */
    void change(final HttpServletRequest req, final Set<String> cmsTags, final Map<String, String> needChange);
    /**
     * 清除缓存
     *
     * @param type 数据类别
     * @param id 唯一标示
     */
    void rmCache(final String type, final String id);
}

AbstractCacheViewTag

/**
 * 抽象的缓存视图标签
 * <p>
 * 增加了自动缓存的功能
 *
 * @author   ZhuanJunxiang
 * @Date     2016年11月25日      
 */
public abstract class AbstractCacheViewTag extends AbstractViewTag {
    /**
    * Logger for this class
    */
    private static final Logger logger = LoggerFactory.getLogger(AbstractCacheViewTag.class);
    /**
     * 原始的字符串
     */
    private static final String ORIGIN = "origin";
    /**
     * 原始的字符串,()
     */
    public static final String AFTER_CMS = "afterCms";
    /**
     * 是否开启
     */
    @Value("#{configProperties['vtCache']}")
    private boolean vtCache;
    @Autowired
    protected IRedisDao redisDao;
    @Override
    protected void changeMatch(final HttpServletRequest req, final Set<String> needDeal,
            final Map<String, String> result) {
        //获取每个cms参数的列表
        List<Map<String, String>> props = getPropMap(needDeal);
        //先从redis中获取
        if (vtCache) {
            loadFromRedis(props, result);
        }
        //剩余的从db中获取后,进行构造,并塞入缓存
        loadFormDb(props, result);
        //如果还有未处理的暂时不处理
    }

    protected List<Map<String, String>> getPropMap(final Set<String> needDeal) {
        @SuppressWarnings("unchecked")
        List<Map<String, String>> result = CollectionUtil.list();
        for (String one : needDeal) {
            Map<String, String> props = ParseUtil.getProps(one);
            props.put(ORIGIN, one);
            result.add(props);
        }
        return result;
    }
    public void loadFromRedis(final List<Map<String, String>> tagProps, final Map<String, String> result) {
        List<String> keys = CollectionUtil.list();
        for (Map<String, String> one : tagProps) {
            keys.add(getRedisKey(one));
        }
        String[] collection2array = CollectionUtil.collection2array(keys);
        List<String> re = redisDao.mget(collection2array);
        if (Util.isEmpty(re)) {
            return;
        }
        for (int i = re.size() - 1; i >= 0; i--) {
            String string = re.get(i);
            if (string == null) {
                continue;
            }
            Map<String, String> remove = tagProps.remove(i);
            result.put(remove.get(ORIGIN), string);
        }
    }

    protected String getRedisKey(final Map<String, String> map) {
        List<String> keys = CollectionUtil.list("vt", getType());
        if (map.containsKey(UID)) {
            keys.add(map.get(UID));
        }
        if (map.containsKey(SUB_TYPE)) {
            keys.add(map.get(SUB_TYPE));
        }
        return StringUtil.join(":", keys);
    }

    protected void loadFormDb(final List<Map<String, String>> tagProps, final Map<String, String> result) {
        if (Util.isEmpty(tagProps)) {
            return;
        }
        Map<String, String> needCache = MapUtil.map();
        for (Map<String, String> one : tagProps) {
            String content;
            try {
                content = loadFormDb(one);
            } catch (Exception e) {
                logger.error(e.getMessage());
                content = "";
            }
            result.put(one.get(ORIGIN), content);
            needCache.put(getRedisKey(one), content);
        }
        //        redis.set(needCache);
        //设置过期时间
        for (Entry<String, String> entry : needCache.entrySet()) {
            redisDao.set(entry.getKey(), entry.getValue());
            redisDao.expire(entry.getKey(), getExpireTime());
        }
    }
    /**
     * 过期时间 
     * <p>
     * 默认的过期时间 
     *
     * @return 过期时间 
    */
    protected int getExpireTime() {
        return 1000;
    }
    /**
     * 从数据库里面加载数据
     * 处理完毕之后,直接放到tagProps的每个map的AFTER_CMS属性里面
     *
     * @param tagProp 待处理的数据
     */
    protected abstract String loadFormDb(final Map<String, String> tagProp);
    /**
     * 如果 subType为空,则更新某个对象的所有类型的缓存
     * <p>
     * 如果id为空,则更新subType类型的所有对象的缓存
     * <p>
     * 如果,两个都为空,则表示更新该类getType()的所有缓存
     */
    @Override
    public void rmCache(final String subType, final String id) {
        List<String> keys = CollectionUtil.list("vt", getType());
        if (!Util.isEmpty(id)) {
            keys.add(id);
        }
        if (!Util.isEmpty(subType)) {
            keys.add(subType);
        }
        String key = StringUtil.join(":", keys) + "*";
        redisDao.del(key);
    }
    /**
     * 替换
     * <p>
     * 把标签替换成根据业务处理后的字符串
     *
     * @param tag 标签对象
     * @param result 需要替换的字符串
    */
    protected void replace(Map<String, String> tag, String result) {
        tag.put(AFTER_CMS, result);
    }
    protected String getFtlPath(String subType) {
        return getType() + "/" + subType + ".ftl";
    }
}

ViewTag

/**
 * vt核心引擎,负责对外提供服务
 * 
 * @author   ZhuangJunxiang
 * @Date     2013-12-20      
 */
@Data
public class ViewTag {
    /**
    * Logger for this class
    */
    private static final Logger logger = LoggerFactory.getLogger(ViewTag.class);
    private static int MAX_PARSE_NUM = 3;
    private static String PATTERNSTR = "<vt:.*?/>";
    private static Pattern PATTERN = Pattern.compile(PATTERNSTR);
    private List<IViewTag> changes;
    /**
     * 使用模板解析并替换内容
     *
     * @param str 原字符串 
     * @param req http请求对象
     * @param context ServletContext对象
     * @return 待替换的字符传
     */
    public String parse(final HttpServletRequest req, final String str) {
        return parseTime(req, str, 0);
    }
    /**
     * 防止存在嵌套标签,进行多次处理
     * 在不超过最大次数的情况下进行多次处理
     * @param req 请求对象
     * @param str 待处理的字符串
     * @param num 处理次数
     * @return 处理后结果
     */
    private String parseTime(final HttpServletRequest req, final String str, final int num) {
        if (Util.isEmpty(str)) {
            return "";
        }
        if (Util.isEmpty(changes)) {
            logger.error("not set cms changes yet!"); //$NON-NLS-1$
            return str;
        }
        Set<String> cmsTags = matchCms(str);
        if (Util.isEmpty(cmsTags)) {
            return str;
        }
        Map<String, String> needChange = MapUtil.map();
        for (IViewTag cms : changes) {
            cms.change(req, cmsTags, needChange);
        }
        String result = str;
        for (Entry<String, String> en : needChange.entrySet()) {
            String value = filterAjaxValue(req, en.getValue());
            String key = filterAjaxKey(req, en.getKey());
            result = StringUtil.replaceAll(result, key, value);
        }
        if (!Util.isEmpty(cmsTags)) {
            logger.error("not changed cms: {0}", Json.toJson(cmsTags)); //$NON-NLS-1$
            for (String one : cmsTags) {
                result = StringUtil.replaceAll(result, one, "");
            }
        }
        if (num < MAX_PARSE_NUM) {
            return parseTime(req, result, num + 1);
        }
        return result;
    }
    private String filterAjaxKey(HttpServletRequest req, String inputKey) {
        //        if (!RequestUtil.isAjax(req)) {
        //            return inputKey;
        //
        //        }
        String key = inputKey;
        if (key != null) {
            key = key.replaceAll("\\\\", "\\\\\\\\");
        }
        return key;
    }
    /**
     * 过滤ajax请求
     * 如果是ajax,则需要把html代码转成json
     * 如果不是,则直接返回即可。
     *
     * @param req
     * @param value
     * @return TODO(这里描述每个参数,如果有返回值描述返回值,如果有异常描述异常)
    */
    private String filterAjaxValue(final HttpServletRequest req, String value) {
        //        if (!RequestUtil.isAjax(req)) {
        //            return value;
        //
        //        }
        String json = JsonUtil.toJson(JsonUtil.toJson(value));
        return json.substring(3, json.length() - 3);
    }
    /**
     * 匹配cms标签
     *
     * @param str 待匹配的字符串
     * @return 匹配到的字符串
     */
    private Set<String> matchCms(final String str) {
        Matcher matcher = PATTERN.matcher(str);
        Set<String> cmsTags = Lang.set();
        while (matcher.find()) {
            cmsTags.add(matcher.group(0));
        }
        return cmsTags;
    }
    /**
     *根据类型清理缓存数据
     *
     * @param type 类型
     * @param id 唯一标示
     */
    public void rmCache(final String type, final String id) {
        if (Util.isEmpty(changes)) {
            return;
        }
        for (IViewTag cms : changes) {
            cms.rmCache(type, id);
        }
    }
    public ViewTag() {
    }
}

DateViewTag

/**
 * vt引擎的日期处理类
 * <p>
 * 处理 <vt:date uid="2013-12-13 12:31:54"/>
 * 转化为 xx秒前,xx分钟前,xx天前
 * 
 * @author   ZhuangJunxiang
 * @Date     2013-12-20      
 */
@Component
public class DateViewTag extends AbstractViewTag {
    /**
    * Logger for this class
    */
    private static final Logger logger = LoggerFactory.getLogger(DateViewTag.class);
    @Override
    protected String getType() {
        return "date";
    }
    @Override
    protected void changeMatch(final HttpServletRequest req, final Set<String> needDeal,
            final Map<String, String> result) {
        long now = DateTimeUtil.millis();
        for (String s : needDeal) {
            Map<String, String> props = ParseUtil.getProps(s);
            String uid = props.get("uid");
            result.put(s, showDate(uid, now));
        }
    }
    /**
     * 显示时间
     *
     * @param date 要处理的时间 
     * @param now 现在的时间戳
     * @return 显示结果
    */
    private String showDate(final String date, final long now) {
        try {
            DateTime dt = DateTimeUtil.string2DateTime(date, null);
            long time = dt.getMillis();
            long diff = now - time;
            if (diff < 60 * 1000) {
                return "刚刚";
            }
            if (diff < 60 * 60 * 1000) {
                return new StringBuilder(diff / (60 * 1000) + "").append("分钟前").toString();
            }
            if (diff < 24 * 60 * 60 * 1000) {
                return new StringBuilder(diff / (60 * 1000 * 60) + "").append("小时前").toString();
            }
            if (diff < (30L * 24L * 60L * 60L * 1000L)) {
                return new StringBuilder(diff / (24L * 60L * 60L * 1000L) + "").append("天前").toString();
            }
            if (diff < (12L * 31L * 24L * 60L * 60L * 1000L)) {
                return new StringBuilder(diff / (30L * 24L * 60L * 60L * 1000L) + "").append("个月前").toString();
            }
            return DateTimeUtil.format(DateTimeUtil.toDate(dt), "yyyy-MM-dd HH:mm:ss");
        } catch (Exception e) {
            logger.error("日期格式异常", e); //$NON-NLS-1$
            return "";
        }
    }
}

集成到spring mvc中去

自定义jsp视图

继承InternalResourceView重写renderMergedOutputModel方法
增长对ViewTag类的调用,添加vt处理逻辑。

/**
 * jsp视图
 *
 * @author   ZhuangJunxiang(529272571@qq.com)
 * @Date     2017年4月10日
 */
public class JspView extends InternalResourceView {
    /**
     * (non-Javadoc)
     * @see
     */
    @Override
    protected void renderMergedOutputModel(final Map<String, Object> model, final HttpServletRequest request,
            final HttpServletResponse response) throws Exception {
        // Expose the model object as request attributes.
        exposeModelAsRequestAttributes(model, request);
        // Expose helpers as request attributes, if any.
        exposeHelpers(request);
        // Determine the path for the request dispatcher.
        String dispatcherPath = prepareForRendering(request, response);
        // Obtain a RequestDispatcher for the target resource (typically a JSP).
        RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
        if (rd == null) {
            throw new ServletException("Could not get RequestDispatcher for [" + getUrl()
                    + "]: Check that the corresponding file exists within your web application archive!");
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
        }
        ResponseWrapper rw = new ResponseWrapper(response);
        rd.include(request, rw);
        String result = "";
        try {
            result = rw.getContent();
        } catch (Throwable e) {
            logger.error(e.getMessage());
        }
        //result = getTransfer().transfer(request, response, result);
        result = getViewTag().parse(request, result);
        PrintWriter writer = response.getWriter();
        writer.write(HtmlCompressorUtil.compress(result));
        response.flushBuffer();
    }
    public ViewTag getViewTag() {
        ViewTag vt = SpringContextUtil.getBean("viewTag");
        return vt;
    }
}

配置spring-mvc.xml

<bean id="viewTag" class="com.we.core.vt.ViewTag">
               <property name="changes">
                   <array>
                       <ref bean="dateViewTag"/>
                   </array>
               </property>
        </bean>
<!-- 配置视图解析器 如何把handler 方法返回值解析为实际的物理视图 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 添加自定义jsp视图 -->
        <property name="viewClass" value="com.we.core.web.view.JspView" />
        <property name="prefix" value="/WEB-INF/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

jsp中使用

<vt:date uid="2016-12-13 12:31:54"/>

相关文章

网站地图xml地图