一,因爲該 jar 包不是免費的, maven 倉庫通常不會有,須要咱們去官網下載並安裝到本地 maven 倉庫html
1,用地址 https://www-evget-com/product/564 下載 19.4 和 18.1 兩個版本 (不知道爲何這個地址博客園不容許粘貼,請你們將域名的 - 換成 . 後在訪問)java
2,安裝到本地 maven 倉庫,不會安裝的請移步 https://www.cnblogs.com/lovling/p/10122207.htmlgit
3,筆者安裝的命令以下可作參考,這裏兩個 JAR 包放到 E 盤spring
mvn install:install-file -DgroupId=com.aspose -DartifactId=words -Dversion=19.4.jdk17 -Dpackaging=jar -D file=E:\aspose-words-19.4-jdk17.jar數組
mvn install:install-file -DgroupId=com.aspose -DartifactId=words -Dversion=18.1.jdk16 -Dpackaging=jar -D file=E:\aspose-words-18.1-jdk16.jar網絡
二,建立一個 maven 項目,引入剛剛安裝的依賴 common-asponse,這樣命名是爲了以後將該項目封裝成公共工具庫框架
<dependency> <groupId>com.aspose</groupId> <artifactId>words</artifactId> <version>19.4.jdk17</version> <!--<version>18.1.jdk16</vers-ion>--> </dependency>
1,在 resource 目錄下建立一個 export 目錄,準備如下幾個文件,一張圖片,一個證書,分別用 wps 和 office 建立的 word 文檔dom
2,咱們建立一個 域 郵件合併的模板,這是一個有實體類點屬性的,兩層 FOR 循環,帶圖片的模板maven
3,注意域的建立方式,Ctrl + F9 -> 右鍵點擊生成的花括號 -> 選擇編輯域 -> 選擇郵件合併 -> 寫內容 ide
(注意若是用 wps 不要動 MERGEFIELD 這個單詞,空一格在後面寫, office 則沒有 該單詞)
三,模板準備完成後,開始寫 JAVA 代碼,
1,首先該框架 是不支持 Map 類型的數據的,因此咱們須要本身去實現接口 IMailMergeDataSource 具體看註解
package com.hwq.aspose.word; import com.aspose.words.IMailMergeDataSource; import java.util.List; import java.util.ListIterator; import java.util.Map; public class MailMapData implements IMailMergeDataSource { private ListIterator<Map<String, Object>> list; private String name; private Map<String, Object> map; /** * 構造器初始化,將 list 集合轉化爲可迭代的對象,提升性能 * @param name 健 * @param list 值 */ public MailMapData(String name, List<Map<String, Object>> list) { this.name = name; this.list = list.listIterator(); } /** * 獲取字段名的方法 * @return 自定義的字段名 */ @Override public String getTableName() throws Exception { return name; } /** * 移動到下一組數據,並判斷下一組數據是否存在 * @return 是否存在下一個數據 */ @Override public boolean moveNext() throws Exception { map = list.hasNext() ? list.next() : null; return map != null; } /** * 獲取當前字段的的數據,這裏注意 18.1 和 19.4 的版本有必定的區別,下面註釋的 19.4 版本的實現方式 * 在循環一個 Map 的時候,取字段時會運行改方法 * @param s 字段 * @param ref 數據容器 * @return * @throws Exception */ /* @Override public boolean getValue(String key, Ref<Object> ref) throws Exception { Object value = map.get(key); ref.set(value); return value != null; } */ @Override public boolean getValue(String key, Object[] args) throws Exception { Object value = map.get(key); args[0] = value; return value != null; } /** * 所謂數組的循環 * 在循環的時候,若是遇到內部還嵌套了循環,會執行該方法 * @param key 健 * @return 空或者另外一個實現了郵件合併接口的子類 */ @Override public IMailMergeDataSource getChildDataSource(String key) throws Exception { Object object = map.get(key); return object instanceof List ? new MailMapData(key, (List) object) : null; } }
2,咱們實現一個 郵件合併的數據 操做類,用於加載模板 和 把數據填入模板,以及保存文件,具體看註解
package com.hwq.aspose.word; import com.aspose.words.Document; import com.aspose.words.SaveFormat; import java.util.List; import java.util.Map; import java.util.Set; /** * 幾個經常使用標籤,一下都基於域的郵件合併模式 * 普通數據 «key» 或者 «key.kk» * 列表循環 «TableStart:key» ... «TableEnd:key» * 圖片標籤 «Image:key» */ public class WordData { private Document doc; /** * 構造器 * @param doc Word 文檔操做類,操做模板的基礎 */ public WordData(Document doc) { this.doc = doc; } /** * 插入普通數據,即郵件合併的 編輯域內容爲 «key» 這種寫法的數據(少用或者不用) * @param keys 健數組 * @param values 值數組 */ public void setData(String[] keys, Object[] values) { try { doc.getMailMerge().execute(keys, values); } catch (Exception ex) { ex.printStackTrace(); throw new RuntimeException(ex.getMessage()); } } /** * 插入實體數據,即郵件合併的 編輯域內容爲 «key.kk» 這種寫法的數據(經常使用) * @param key 健值 * @param map 單層樹狀的 Map 集合 */ public void setMap(String key, Map<String, Object> map) { Set<String> keySet = map.keySet(); String[] keys = new String[keySet.size()]; Object[] values = new Object[keySet.size()]; int index = 0; for (String kk : keySet) { keys[index] = key + "." + kk; values[index] = map.get(kk); index ++; } try { doc.getMailMerge().execute(keys, values); } catch (Exception ex) { ex.printStackTrace(); throw new RuntimeException(ex.getMessage()); } } /** * 插入列表數據,即郵件合併的 編輯域內容爲 «TableStart:key» ... «TableEnd:key» 這種寫法的數據(經常使用) * 理論上支持和 JSON 樹狀相同格式的無限樹狀 Map List 嵌套 * @param key 健 * @param list 數據列表 */ public void setList(String key, List<Map<String, Object>> list) { try { doc.getMailMerge().executeWithRegions(new MailMapData(key, list)); } catch (Exception ex) { ex.printStackTrace(); throw new RuntimeException(ex.getMessage()); } } /** * 保存文件到本地 * @param path 保存的地址 * @param suffix 保存的後綴名 和 格式 */ public void save(String path, SuffixEnum suffix) { try { doc.save(path + suffix.getSuffix(), suffix.getFormat()); } catch (Exception ex) { ex.printStackTrace(); throw new RuntimeException(ex.getMessage()); } } }
3,咱們建立一個生成數據操做類的工具類
package com.hwq.aspose.word; import com.aspose.words.Document; import com.aspose.words.License; import org.springframework.core.io.ClassPathResource; import java.io.InputStream; public class WordUtil { private static WordUtil asposeUtil = null; private final String TEMP_PATH = "export/license.xml"; /** * 私有化構造器,並在類初始化的時候,執行加載證書的方法 * 若是沒有證書,會有水印,測試階段先註釋 */ private WordUtil() { // loadLicense(); } /** * 單例模式對外暴漏的獲取實體的方法 */ public static WordUtil get() { if (asposeUtil == null) { asposeUtil = new WordUtil(); } return asposeUtil; } /** * 加載簽名證書的方法,購買正版的時候使用該方法加載 * 爲了防止重複加載證書,該類設計爲單例模式 */ private void loadLicense() { boolean result = false; ClassPathResource license = new ClassPathResource(TEMP_PATH); License aposeLic = new License(); try { aposeLic.setLicense(license.getInputStream()); } catch (Exception ex) { ex.printStackTrace(); throw new RuntimeException(ex.getMessage()); } } /** * 初始化 WORD 模板並獲取郵件合併數據操做類 * @param templatePath 模板完整路徑 */ public WordData initWordData(String templatePath) { try ( InputStream in = new ClassPathResource(templatePath).getInputStream(); ) { Document doc = new Document(in); return new WordData(doc); } catch (Exception ex) { ex.printStackTrace(); throw new RuntimeException(ex.getMessage()); } } }
4,咱們建立一個枚舉,用於肯定文件最後生成的格式,該枚舉不完整,後續可能補充
package com.hwq.aspose.word; import com.aspose.words.SaveFormat; /** * 該枚舉主要定義生成的文件的後綴名的內容格式 * 後續根據須要自行添加,具體支持的格式查看 SaveFormat 類的源碼 */ public enum SuffixEnum { DOCX(".docx", SaveFormat.DOCX), DOC(".doc", SaveFormat.DOC), PDF(".pdf", SaveFormat.PDF), PNG(".png", SaveFormat.PDF); public String suffix; public int format; SuffixEnum(String suffix, int saveFormat) { this.suffix = suffix; this.format = saveFormat; } public String getSuffix() { return suffix; } public int getFormat() { return format; } }
5,一切準備就緒,讓咱們寫一個測試類來測試一下
package com.hwq.aspose.word; import org.springframework.core.io.ClassPathResource; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.util.*; public class Tester { public static void main(String[] args) throws Exception { // 模板的地址須要到後綴名爲止 和 最後生成文件的保存地址,後綴名不須要加 String temp = "export/wps_word.docx"; String path = "E:/text/file/" + UUID.randomUUID().toString().replaceAll("-", ""); // 合併模板和數據 WordData word = WordUtil.get().initWordData(temp); word.setMap("user", getMap()); word.setList("data", getList()); // 保存新文件,該方法的第二個參數決定保存文件的類型和格式 word.save(path, SuffixEnum.DOCX); // word.save(file, SuffixEnum.PDF); } /** * 獲取圖片資源,這裏測試就用一張圖片,具體項目應該傳入不一樣地址 * 這裏只要返回二進制數組就行,具體的操做不固定,網絡圖片也是可行的 */ public static byte[] getImage() throws Exception { InputStream in = new ClassPathResource("export/head.png").getInputStream(); ByteArrayOutputStream swapStream = new ByteArrayOutputStream(); byte[] buff = new byte[100]; int rc = 0; while ((rc = in.read(buff, 0, 100)) > 0) { swapStream.write(buff, 0, rc); } return swapStream.toByteArray(); } /** * 獲取單層樹狀結構數據 */ public static Map<String, Object> getMap() { Map<String, Object> map = new HashMap<String, Object>(); map.put("name", "Word"); map.put("sex", "男"); return map; } /** * 獲取多層樹狀結構數據 (2層),理論上支持無限嵌套 */ public static List<Map<String, Object>> getList() throws Exception { List<Map<String, Object>> data = new ArrayList<Map<String, Object>>(); for (int j = 0; j < 2; j++) { Map<String, Object> dataMap = new HashMap<String, Object>(); List<Map<String, Object>> list = new ArrayList<Map<String, Object>>(); for (int i = 0; i < 3; i++) { Map<String, Object> map = new HashMap<String, Object>(); map.put("code", i + 1); map.put("name", "000" + i); map.put("date", "1992071" + i); // 圖片須要的是二進制數組 map.put("head", getImage()); list.add(map); } dataMap.put("list", list); data.add(dataMap); } return data; } }
6,點擊運行,獲得結果,還算是比較順利的
四,一些問題的解釋
1,若是沒有購買使用,測試生成的文件會帶水印,而且有使用時間以及文件大小的侷限性,如上圖,就是測試的文件帶着大大的水印,
若是條件容許,建議去官網進行購買,購買後咱們能夠得到一個 license.xml 的簽名證書,在第三步的第3小步的類中把加載證書的代碼
解開,就能夠享受無水印無限制的結果了
固然,若是我麼只是爲了學習一下,使用破解版也是無可厚非的,不過不建議破解最新版,容易產生糾紛,筆者建議破解舊一點的版本,
這也是一開始爲何要下載兩個版本的緣由,至於破解的過程無非就是用 idea 打斷點,找到簽名的驗證代碼,反編譯修改成跳過驗證,
在打成 jar 包,網上教程不少,這裏再也不綴訴,下面筆者給出本身破譯的版本供你們學習(請勿用於商業用途),點擊前往
切換兩個版本 須要注意修改一下 第三步的 第 1 小步建立類,這是因爲兩個版本的 getValue 方法有所不一樣,具體看上面的代碼註釋
破解後的咱們能夠隨意編寫一個 license.xml 內容以下,直接複製就可使用
<License> <Data> <!-- 這裏放產品名稱 Aspose 的產品名稱,能夠多個 --> <Products> <Product>Aspose.Words for Java</Product> </Products> <!-- 這裏填寫產品類型,我的仍是企業,通常寫 Enterprise --> <EditionType>Enterprise</EditionType> <!-- 這裏是 訂閱到期期限,日期要大於當前 --> <SubscriptionExpiry>20991231</SubscriptionExpiry> <!-- 這裏是 許可證到期期限,日期要大於當前 --> <LicenseExpiry>20991231</LicenseExpiry> <!-- 流水號,隨便填一個,沒有中文就行 --> <SerialNumber>8bfe198c-7f0c-4ef8-8ff0-ac</SerialNumber> </Data> <!-- 簽名參數,隨便填一個,沒有中文那就行 --> <Signature>ZheLiSuiBianXieDianShaDouXing</Signature> </License>
2,咱們會發現生成的文檔,每一行文字都比較靠上,這個困擾了筆者好久,通過反覆測試 發現用 WPS,編寫的模板新生成的文件的
每一段文字只要不設置段後間距,生成的新文件就會莫名多出 10 榜 的段後間距,不知道是程序的緣由仍是 wps 自己的緣由
解決辦法,改用 office 編寫的模板就一切正常了,推薦使用,畢竟 office 和 aspose 都是國外的軟件和產品
若是非要用 wps 編寫模板,那就只能給每一段文字設置段後間距爲 1 榜,這樣就不會,也能夠試試比較新版的 WPS