自定義視圖標籤

先來描述一下vt(viewTag)問題的來源:

  • 初始需求:
    在開發的過程當中有這樣的一個處理時間顯示的需求:
    若是時間小於1分鐘顯示秒,
    若是時間小於1小時顯示分鐘,
    若是時間小於1天顯示小時,
    若是大於1天顯示日期。
  • 需求演化一:
    每個顯示時間的地方後臺開發人員都要編寫一個處理這樣的一個程序。
    因而想起在後臺建立工具類解決這個問題。
  • 需求演化二:
    可是使用工具類又帶來了另一個問題,每次都要取出這個字段顯示調用工具類
    而後再塞進去,這樣形成代碼重複臃腫。
  • 需求演化三:
    後臺開發不想用代碼實現就能夠替換成功,換句話說顯示和業務不要耦合在一塊兒。
  • 需求演化四:
    若是整站出現相似於時間這種字符串處理的地方不少該如何處理??
  • 最終需求:
    到這裏最終需求就肯定啦,相同概念的字符串在頁面展現時不但願展現的邏輯每一個人都關注,有框架統一提供實現該如何實現???

分析問題:

咱們須要肯定在哪裏進行字符串替換比較合適?

不管是json仍是ftl仍是jsp的字符串替換咱們都必須在沒有真正返回給瀏覽器以前來作字符串替換的工做。
大體思路:
發送一個http請求通過解析域名找到對應的tomcat服務,由tomcat決定調用哪個應用程序響應,
而後應用程序找到對應的jsp或者html,咱們經過某種方式獲取到jsp或者html的內容經過規則匹配
就能夠進行字符串替換。
其實,最簡單的方式咱們能夠建立一個filter,在url請求過來的時候在doFilter()方法中根據某種url規則(好比以.jsp結尾的請求)
獲取請求的文件(jsp,ftl,html), 而後讀取文件內容進行匹配,最終能夠完成替換。html

咱們要肯定哪些內容須要被替換也就是說咱們須要一個標識?

定義一個規則凡是符合規則的就進行字符串替換工做,
能夠定義一個特殊的標籤:好比:
那咱們就規定在jsp或者ftl中以<vt"開頭的標籤都進行字符串替換。web

那麼多種類的vt咱們怎麼肯定使用哪個vt處理類進行處理呢??

其中 中的xxx就是這個處理的類的ioc容器的key,
咱們能夠經過爲每個處理的類添加一個標識,經過正則匹配找到這個vt處理類
而後調用這個處理器的處理方法完成字符串替換。ajax

如何讓使用者用的簡單??

咱們能夠抽取一個vt處理器接口,提供一個針對這個接口的抽象實現類,
讓其處理相同邏輯的實現並約定好必須要實現的方法,
使用者能夠經過實現這個抽象類進行字符串替換邏輯編寫。redis

替換相贊成義的字符串,那咱們怎麼肯定哪些是相贊成義的呢??

這個由使用者去作,去抽象。spring

如何在框架上進行集成??

在框架上使用這個的時候需找兩個點:
1.框架在視圖層有沒有提供可讓咱們對url訪問的資源(文件)進行處理的入口?
2.框架有沒有可讓咱們的vt相關處理類放到ioc容器管理的方法??數據庫

類圖設計

類圖講解:

IViewTag:實現這個接口,便可以根據標籤替換成對應的視圖顯示內容
AbstractCacheViewTag:抽象的緩存視圖標籤
DateViewTag: vt引擎的日期處理類 處理 <vt:date uid="2013-12-13 12:31:54"/> 轉化爲 xx秒前,xx分鐘前,xx天前
ViewTag: vt核心引擎,負責對外提供服務json

具體代碼

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"/>
相關文章
相關標籤/搜索