問題由來:
開發個新需求,需要按規定導出word文檔,文檔截圖如下
因為之前沒做過這個,一臉懵B啊,導出excel和txt倒是經常接觸到,對於這個word這種格式不嚴謹的文件怎么處理呢?
技術選型:可協助實現的技術很多,但是本人極力推薦feemarker,簡直太好用了。
具體實施:
步驟一:maven項目 先添加如下依賴:
<!--freemarker 協助word 導出--> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.20</version> </dependency>
不是maven的,去下載feemarker.jar 導入就好了
步驟二:原理就是先做一個word模板, 該模板中變量數據用${xxx}這種方式填寫, 然后再導出時只需讀取模板然后用相應的數據替換其中的${xxx}即可.
我們這里設置了三個變量;
步驟三: 把該word文檔 另存為xml文件(是另存為,不是改擴展名為xml),然后再改擴展名為ftl,步驟不能錯。隨便放在一個地方,我們這里先放在D盤根目錄 名為 測試.ftl;
步驟四:上代碼
try { Map<String,String> dataMap = new HashMap<String,String>(); dataMap.put("account", "123456"); dataMap.put("name", "張三"); dataMap.put("idNumber", "123123"); Configuration configuration = new Configuration(); configuration.setDefaultEncoding("utf-8"); //指定模板路徑的第二種方式,我的路徑是D:/ 還有其他方式 configuration.setDirectoryForTemplateLoading(new File("D:/")); // 輸出文檔路徑及名稱 File outFile = new File("D:/test.doc"); //以utf-8的編碼讀取ftl文件 Template t = configuration.getTemplate("測試.ftl","utf-8"); Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"),10240); t.process(dataMap, out); out.close(); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); }
跑起來,大兄弟。
步驟四:跑完,發現在我的D盤 有個test.doc,點開看看
完美啊,發現數據進去了。
技術擴展(循環列表):
其實到這里,已經可以滿足很多的場景需要了。
但是我們這里還欠缺了點,因為我們的數據列表是循環的,因為可能不止一條數據。
那怎么辦呢?
其實feemarker已經為我們考慮了這一點了, 先上代碼
Map<String,String> map1 = new HashMap<String,String>(); map1.put("account", "123456"); map1.put("name", "張三"); map1.put("idNumber", "123123"); Map<String,String> map2 = new HashMap<String,String>(); map2.put("account", "456789"); map2.put("name", "李四"); map2.put("idNumber", "123"); List<Map<String,String>> newlist = new ArrayList<>(); newlist.add(map1); newlist.add(map2); Map<String, Object> dataMap = new HashMap<>(); dataMap.put("userList", newlist); Configuration configuration = new Configuration(); configuration.setDefaultEncoding("utf-8"); //指定模板路徑的第二種方式,我的路徑是D:/ 還有其他方式 configuration.setDirectoryForTemplateLoading(new File("D:/")); // 輸出文檔路徑及名稱 File outFile = new File("D:/test.doc"); //以utf-8的編碼讀取ftl文件 Template t = configuration.getTemplate("測試.ftl","utf-8"); Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"),10240); t.process(dataMap, out); out.close();
代碼里我們對數據的封裝做了改動,我們傳了list進去,list里面都是key相同的map;
還得對我們的 ftl 文件內容改動一下
搜索 w:tr 可以找到行的起點與結束點(注意第一對w:tr 是表頭,應找第二對 w:tr), 如圖(網上摘錄的圖,因為我自己的ftl文件內容太大,截不了):
用<#list userList as user> </#list>標簽將第二對 w:tr 標簽包圍起來(userList是集合的key, user是集合中的每個元素, 類似<c:forEach items='userList' var='user'>), 如圖:
當然,這里的user.a 要換 user隨便寫,和上面的as user對應起來就好,這里需要改成user.account, user.name,user.idNumber;
現在看看文件變成什么樣了
完美解決list循環讀取的問題;
擴展二(直接導出):
顯然我們這里處理文件的方式是直接生成文件放在了D盤里,更多的時候,我們需要直接導出;
Map<String,String> dataMap = getData(); //獲取數據 //Configuration用於讀取ftl文件 Configuration configuration = new Configuration(); configuration.setDefaultEncoding("utf-8"); //獲取模板所在的路徑 configuration.setDirectoryForTemplateLoading(new File(pathHelper.getWebClassesPath()+"/template")); //以utf-8的編碼讀取ftl文件 Template t = configuration.getTemplate("測試.ftl","utf-8"); String fileName = URLEncoder.encode("客戶信息-","UTF-8") + dataMap.get("formCode")+".doc"; //1.設置文件ContentType類型,這樣設置,會自動判斷下載文件類型 response.setContentType("multipart/form-data"); //2.設置文件頭:最后一個參數是設置下載文件名(假如我們叫a.pdf) response.setHeader("Content-Disposition", "attachment;fileName=" + fileName); Writer out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), "utf-8"),10240); t.process(dataMap, out); out.close();
這樣就可以直接導出了。
可能遇到的bug:
就是獲取模板的那步可能會出問題,提示找不到模板。feemarker為我們提供了三種方法
第一種:基於類路徑,HttpWeb包下的framemaker.ftl文件
configuration.setClassForTemplateLoading(this.getClass(), "/HttpWeb");
第二種:基於文件系統
configuration.setDirectoryForTemplateLoading(new File("/template"))
第三種:基於Servlet Context,指的是基於WebRoot下的template下的framemaker.ftl文件
HttpServletRequest request = ServletActionContext.getRequest();
configuration.setServletContextForTemplateLoading(request.getSession().getServletContext(), "/template");
因為放在項目中,肯定想設置個相對路徑,我就選擇了第一種,
項目存放路徑
注意:configuration.setClassForTemplateLoading(this.getClass(),"/jasperreports");
這樣就可以了,千萬別寫什么 /resources/jasperreports,因為maven項目打包的時候,resources文件夾就不存在了。
好了到這里,介紹完畢,是不是很簡單又美觀呢。