公司開發(fā)新的項(xiàng)目,選擇了分布式系統(tǒng)架構(gòu),同時(shí)項(xiàng)目中有大量圖文展示的需求,考慮到開發(fā)效率和性能問題,需要集成cms的功能,程序?qū)崿F(xiàn)后臺(tái)錄入數(shù)據(jù)靜態(tài)化。
Java開發(fā)、jdk1.8,項(xiàng)目整體的技術(shù)架構(gòu)大致是這樣的:
在這個(gè)項(xiàng)目之前,公司有另一套cms系統(tǒng),做為公司主要經(jīng)營(yíng)業(yè)務(wù)的基礎(chǔ),它有很多優(yōu)點(diǎn),最主要的優(yōu)點(diǎn)是定制性強(qiáng),能適應(yīng)各種前端版式的頁面。另一個(gè)優(yōu)點(diǎn)是開發(fā)速度特別快,基于模版標(biāo)簽開發(fā),屏蔽了代碼級(jí)的復(fù)雜度和開發(fā)人員水平的差距。
弱點(diǎn)有三個(gè)
一、在于標(biāo)簽庫的實(shí)現(xiàn),完全是基于正則替換,然后處理標(biāo)簽表述的邏輯進(jìn)行查庫、處理。標(biāo)簽沒有實(shí)現(xiàn)語法樹,也沒有上下文,導(dǎo)致了多個(gè)標(biāo)簽函數(shù)嵌套的時(shí)候,某些時(shí)候無法正確實(shí)現(xiàn)替換邏輯。
二、沒有應(yīng)用更多的緩存技術(shù)(只實(shí)現(xiàn)了jvm內(nèi)的HashMap緩存),數(shù)據(jù)量大的任務(wù),生成速度在查庫上,有明顯的瓶頸。
三、生成失敗的模版,沒有給出足夠明確的錯(cuò)誤提示,輔助使用者調(diào)試錯(cuò)誤。
整個(gè)大體流程
整個(gè)生成是基于freemarker模版引擎,項(xiàng)目初期,曾經(jīng)對(duì)比過velocity與freemarker,因?yàn)閒reemarker最后一次更新時(shí)間比較近,最終選擇了freemarker,其實(shí)兩者應(yīng)該差不多。
先曬一下目前的類結(jié)構(gòu)
freemarker支持實(shí)現(xiàn)TemplateMethodModelEx接口的自定義函數(shù),及實(shí)現(xiàn)TemplateDirectiveModel的自定義標(biāo)簽。上面代碼,fun包下面的就是自定義函數(shù),tags包下面的是自定義標(biāo)簽。
兩者的區(qū)別,
1、從調(diào)用方法來看,
arts(10, 10, ”, ”)
這種是自定義函數(shù) <@arts menu="15" pagesize="15" sort="" sql="";arts, page></@arts>
這種是自定義標(biāo)簽
2、自定義函數(shù)有返回值,可以再套入別的函數(shù)進(jìn)行計(jì)算或者迭代。自定義標(biāo)簽沒有返回值,它會(huì)返回幾個(gè)變量,作用域只在標(biāo)簽體內(nèi)部。
3、自定義標(biāo)簽傳入的參數(shù)有Environment、TemplateDirectiveBody等等,即可以獲取其他位置定義的模版變量,又可以追加變量,還可以輸出字符串文本到最終生成的頁面上。自定義函數(shù),只能僅僅返回處理后的返回值。
這個(gè)問題,基本上查api或者別的博客就能找到具體的方法,但是有些資料可能不準(zhǔn)確,這里我直接把我實(shí)現(xiàn)了的核心代碼貼出來。我使用的freemarker版本是2.3.23。
//成員變量private static Configuration config = new Configuration(Configuration.VERSION_2_3_25);//config設(shè)置config.setLocale(Locale.CHINA); config.setDefaultEncoding("utf-8"); config.setEncoding(Locale.CHINA, "utf-8"); config.setObjectWrapper(new DefaultObjectWrapper(Configuration.VERSION_2_3_25));StringTemplateLoader stringLoader = new StringTemplateLoader(); //content是模版內(nèi)容的字符串 stringLoader.putTemplate("siteTemplate",content); config.setTemplateLoader(stringLoader);try(FileWriter out = new FileWriter(filePath.toFile());) { Template template = config.getTemplate("siteTemplate","utf-8"); config.setCustomAttribute("customDataModel", dataModel); template.process(dataModel, out); out.flush(); } catch (Exception e) { //異常處理 }
這個(gè)問題包括很多細(xì)節(jié),比如生成后的地址規(guī)則。假設(shè)目標(biāo)地址是news_index.html,那么第二頁就不能用這個(gè)地址,我的處理方式是news_index_x.html,x代表第幾頁,可以是2~n。但是第一頁就是原地址。
最關(guān)鍵
如果你也需要基于freemarker實(shí)現(xiàn)這個(gè)功能,大概機(jī)會(huì)發(fā)現(xiàn),最關(guān)鍵的問題,是如何在生成第一頁的時(shí)候,知道還有第二頁需要生成。因?yàn)楫a(chǎn)生多個(gè)分頁面,一般是在自定義標(biāo)簽里面,比如有一個(gè)循環(huán)數(shù)據(jù)列表的標(biāo)簽,發(fā)現(xiàn)一頁10條,無法顯示完,怎樣告知生成階段的代碼(就是上面那一段)。這是兩個(gè)完全不同的代碼區(qū)域和處理階段。
參考了另一款開源cms,我采取了一種有點(diǎn)技巧性的做法。核心思想就是,上面說到自定義標(biāo)簽,可以在生成后的頁面上,任意輸出字符串。假設(shè)標(biāo)簽中判斷還有下一頁,需要生成,就在頁面上輸出一段注釋,本頁面生成完以后,重新讀取生成的頁面,發(fā)現(xiàn)有包含此種注釋,則繼續(xù)生成下一頁。
下面貼一些代碼
//輸出還有下一頁的標(biāo)識(shí)env.getOut().write(LBLCommon.getHasNextPage(totalRecord, totalPage, pageIndex));public static String getHasNextPage(int recordCount, int totalPage, int pageindex) { return hasNextPage+"_"+recordCount+"_"+totalPage+"_"+pageindex+"-->"; }
//生成靜態(tài)化頁面 this.writeUseFreeMarker(name, content, url, dataModel);
//分頁機(jī)制,查詢是否需要分頁————————————————————
this.morePageProcess(name, dataModel, url, rootPath, content, entry);
//處理分頁private void morePageProcess(String name, Map<String, Object> dataModel, String url, String rootPath, String content, Map.Entry<String, String> entry) throws Exception{ String html = this.readFile(url); LBLPagerModel pager = checkHasNextPage(html); if (pager != null) { //重設(shè)置一個(gè)基礎(chǔ)的總記錄數(shù) dataModel.put("totalRecord", pager.getRecordCount()); for (int i = 2; i<= pager.getTotalPage(); i ++) { //設(shè)置頁碼 setPageIndex(dataModel, i); url = rootPath + LBLCommon.getPageUrl(entry.getValue(), i); this.writeUseFreeMarker(name, content, url, dataModel); } }}
整個(gè)靜態(tài)化的部分,還有很多別的細(xì)節(jié)和坑,比如標(biāo)簽參數(shù)的轉(zhuǎn)型問題,生成靜態(tài)頁過程中模版exception的catch和反饋,查詢生成隊(duì)列情況的監(jiān)控模塊(基于mongo),標(biāo)簽設(shè)計(jì),自動(dòng)生成、定時(shí)生成、過濾生成…
靜態(tài)化的功能,與業(yè)務(wù)結(jié)合的很緊密,好的標(biāo)簽設(shè)計(jì)非常重要,這個(gè)可能得靠多踩坑才能提升一點(diǎn)躲開雷區(qū)的能力。另外,這次的開發(fā)過程,也讓我更深刻的認(rèn)識(shí)到,深思熟慮,先慢后快,不寫一行垃圾代碼,才是最節(jié)約時(shí)間的開發(fā)模式。
聯(lián)系客服