工具庫-基于LibreOffice實(shí)現(xiàn)文檔操作

基于libreoffice實(shí)現(xiàn)的文檔轉(zhuǎn)換項(xiàng)目岂津,無框架依賴佛舱,即插即用

項(xiàng)目源代碼:github/workable-converter

1. 技術(shù)棧

  • LibreOffice:v6.2.3

  • jodconverter:4.2.2

  • PDFBox:2.0.12

  • cglib動態(tài)代理 + 懶漢工廠模式 + 策略模式 + 裝飾器模式

  • qtools-property管理配置文件(application.yml、bootstrap.yml晌缘、workable-converter.yml三種命名的配置文件任意包含一種即可)

2. 功能

  • 支持doc巍扛、docx、html核蘸、ppt、png啸驯、pdf等等類型的文件互相轉(zhuǎn)換

  • 支持按照文件路徑客扎、字節(jié)輸入輸出流、Base64等不同姿勢轉(zhuǎn)換

  • 不依賴第三方框架罚斗,即插即用徙鱼,支持application.yml、bootstrap.yml针姿、workable-converter.yml三種配置(自己項(xiàng)目中具體配置一個(gè)即可)

3. 使用

3.1 安裝配置LibreOffice6.2.3

CentOS請直接參考這篇文章:CentOS7安裝LibreOffice6.2.3

windows跟Mac同樣可以在上述文章中拿到下載鏈接

安裝完成后袱吆,請記住您的LibreOffice的Home目錄,后面需要用到

默認(rèn)目錄:

  • CentOS: /opt/libreoffice6.2/

  • Mac: /Applications/LibreOffice.app/Contents/

  • Windows: C:\Program Files\LibreOffice\

3.2 獲取依賴

  • Maven
<dependency>
  <groupId>com.liumapp.workable.converter</groupId>
  <artifactId>workable-converter</artifactId>
  <version>v1.2.0</version>
</dependency>
  • Gradle
compile group: 'com.liumapp.workable.converter', name: 'workable-converter', version: 'v1.2.0'

3.3 編輯配置文件

在項(xiàng)目的resources目錄下距淫,創(chuàng)建一個(gè)yml配置文件绞绒,需要確保文件名稱為application.yml、bootstrap.yml或workable-converter.yml三種命名任意一個(gè)即可

添加以下配置:

com:
  liumapp:
    workable-converter:
      libreofficePath: "/Applications/LibreOffice.app/Contents"

libreofficePath的值為LibreOffice:6.2.3的安裝目錄

完整的配置項(xiàng)列表如下

<table>
<tr><th>參數(shù)名</th><th>解釋</th><th>默認(rèn)值</th></tr>
<tr><td>libreofficePath</td><td>LibreOffice安裝目錄</td><td>(String) 無默認(rèn)值榕暇,該項(xiàng)必填</td></tr>
<tr><td>libreofficePort</td><td>LibreOffice監(jiān)聽端口</td><td>(int) 2002</td></tr>
<tr><td>tmpPath</td><td>臨時(shí)存儲目錄</td><td>(String) "./data/"</td></tr>
</table>

3.4 執(zhí)行轉(zhuǎn)換

3.4.1 按照文件路徑轉(zhuǎn)換

以doc轉(zhuǎn)PDF為例

WorkableConverter converter = new WorkableConverter();//實(shí)例化的同時(shí)蓬衡,初始化配置項(xiàng)喻杈,配置項(xiàng)的校驗(yàn)通過Decorator裝飾

ConvertPattern pattern = ConvertPatternManager.getInstance();
pattern.fileToFile("./data/test.doc", "./data/pdf/result1.pdf"); //test.doc為待轉(zhuǎn)換文件路徑,result1.pdf為轉(zhuǎn)換結(jié)果存儲路徑
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.DOC);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);

converter.setConverterType(CommonConverterManager.getInstance());//策略模式狰晚,后續(xù)實(shí)現(xiàn)了新的轉(zhuǎn)換策略后筒饰,在此處更換,圖片轉(zhuǎn)換將考慮使用新的策略來完成
boolean result = converter.convert(pattern.getParameter();

如果要用html轉(zhuǎn)PDF壁晒,將上述代碼的

pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.DOC);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);

改為

pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.HTML);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);

其他類型的同理

3.4.2 按照輸入輸出流轉(zhuǎn)換

以doc轉(zhuǎn)pdf為例

// you can also choice not use proxy
WorkableConverter converter = new WorkableConverter();
ConvertPattern pattern = ConvertPatternManager.getInstance();
pattern.streamToStream(new FileInputStream("./data/test.doc"), new FileOutputStream("./data/pdf/result1_2.pdf"));
// attention !!! convert by stream must set prefix.
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.DOC);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);
converter.setConverterType(CommonConverterManager.getInstance());
boolean result = converter.convert(pattern.getParameter();

跟上例基本相同瓷们,唯一的變化是通過pattern.streamToStream()來設(shè)置輸入輸出流,轉(zhuǎn)換源文件數(shù)據(jù)從輸入流中讀取秒咐,轉(zhuǎn)換結(jié)果會直接寫入輸出流中换棚,

同時(shí)要切換轉(zhuǎn)換格式,跟上例一樣設(shè)置不同的prefix即可

3.4.3 按照文件Base64轉(zhuǎn)換

仍以doc轉(zhuǎn)pdf為例

WorkableConverter converter = new WorkableConverter();
ConvertPattern pattern = ConvertPatternManager.getInstance();
pattern.base64ToBase64(Base64FileTool.FileToBase64(new File("./data/test.doc")));
// attention !!! convert by base64 must set prefix.
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.DOC);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);
converter.setConverterType(CommonConverterManager.getInstance());
boolean result = converter.convert(pattern.getParameter();
String destBase64 = pattern.getBase64Result();

輸入base64執(zhí)行轉(zhuǎn)換反镇,首先通過pattern.base64ToBase64()來設(shè)置轉(zhuǎn)換源的base64值

轉(zhuǎn)換結(jié)果result仍然是一個(gè)boolean類型,通過pattern.getBase64Result來獲取轉(zhuǎn)換結(jié)果的base64值

要切換轉(zhuǎn)換格式娘汞,跟上例一樣設(shè)置不同的prefix即可

3.5 圖片處理

目前對于圖片的處理歹茶,只支持將PDF轉(zhuǎn)PNG圖片(如果1份pdf文件有20頁,那么將會轉(zhuǎn)換為20張png圖片)你弦,該功能的實(shí)現(xiàn)基于PDFBox:2.0.12

3.5.1 按照文件路徑處理

pattern.fileToFiles()第一個(gè)參數(shù)為待轉(zhuǎn)換的pdf文件路徑惊豺,第二個(gè)參數(shù)為轉(zhuǎn)換后的圖片存儲路徑

WorkableConverter converter = new WorkableConverter();
ConvertPattern pattern = ConvertPatternManager.getInstance();
pattern.fileToFiles("./data/test5.pdf", "./data/");
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PNG);
converter.setConverterType(PdfBoxConverterManager.getInstance()); // pdf box converter manager only support pdf to png
assertEquals(true, converter.convert(pattern.getParameter()));
assertEquals(true, FileTool.isFileExists("./data/test5_0.png"));
assertEquals(true, FileTool.isFileExists("./data/test5_1.png"));
assertEquals(true, FileTool.isFileExists("./data/test5_2.png"));
assertEquals(true, FileTool.isFileExists("./data/test5_3.png"));

3.5.2 按照文件Base64處理

pattern.base64ToBase64()的參數(shù)為待轉(zhuǎn)換pdf文件的base64值

轉(zhuǎn)換結(jié)束后,通過List<String> resultBase64 = pattern.getBase64Results()獲取轉(zhuǎn)換后的圖片base64值的集合

WorkableConverter converter = new WorkableConverter();
ConvertPattern pattern = ConvertPatternManager.getInstance();
pattern.base64ToBase64(Base64FileTool.FileToBase64(new File("./data/test5.pdf")));
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PNG);
converter.setConverterType(PdfBoxConverterManager.getInstance()); // pdf box converter manager only support pdf to png
boolean result = converter.convert(pattern.getParameter());
List<String> resultBase64 = pattern.getBase64Results();
assertEquals(true, result);
assertEquals(4, resultBase64.size());

3.6 添加水印

水印的轉(zhuǎn)換策略為WaterMarkConverter

添加水印注意事項(xiàng)

  • 請確保輸入源文件后綴為PDF禽作,輸出源文件后綴也為PDF

  • 水印參數(shù)需要new一個(gè)WaterMarkRequire來設(shè)置

  • setWaterMarkPage(int page)代表在哪一頁上添加水印尸昧,如果為0,則表示所有頁面

  • 水印本身為一個(gè)PDF文件旷偿,該文件只需要一頁烹俗,其第一頁的內(nèi)容將被視為水印添加到源文件中

    比如說,要添加透明度為0.3的文本作為水印的話萍程,自己使用word等工具繪制透明度為0.3的字體(或者上包含透明度的png圖片也可以)并另存為一個(gè)watermark.pdf文件

    然后使用waterMarkRequire.setWaterMarkPDFBase64(Base64FileTool.FileToBase64(new File("./data/watermark.pdf")))

    或者waterMarkRequire.setWaterMarkPDFBytes(FileUtils.readFileToByteArray(new File("./data/watermark.pdf")))將該文件的base64或者bytes值輸入即可

具體使用可以分為三種方式

3.6.1 按照文件路徑添加水印

WorkableConverter converter = new WorkableConverter();
converter.setConverterType(WaterMarkConverterManager.getInstance());//選擇具體的水印轉(zhuǎn)換策略

ConvertPattern pattern = ConvertPatternManager.getInstance();
WaterMarkRequire waterMarkRequire = new WaterMarkRequire();//創(chuàng)建水印所需要的參數(shù)

//指定在具體的哪一頁添加水印幢妄,0的話則在所有頁面添加水印
waterMarkRequire.setWaterMarkPage(0);//0 means all age
waterMarkRequire.setWaterMarkPDFBase64(Base64FileTool.FileToBase64(new File("./data/watermark.pdf")));

pattern.setWaterMarkRequire(waterMarkRequire);
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.fileToFile("./data/test5.pdf", "./data/test5_with_mark01.pdf");//添加水印后的文件保存在./data/目錄下,名為test5_with_mark01.pdf

boolean result = converter.convert(pattern.getParameter());
assertEquals(true, result);

3.6.2 按照流添加水印

WorkableConverter converter = new WorkableConverter();
converter.setConverterType(WaterMarkConverterManager.getInstance());

ConvertPattern pattern = ConvertPatternManager.getInstance();
WaterMarkRequire waterMarkRequire = new WaterMarkRequire();

waterMarkRequire.setWaterMarkPage(0);//0 means all age
waterMarkRequire.setWaterMarkPDFBytes(FileUtils.readFileToByteArray(new File("./data/watermark.pdf")));

pattern.setWaterMarkRequire(waterMarkRequire);
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.streamToStream(new FileInputStream("./data/test5.pdf"), new FileOutputStream("./data/test5_with_mark02.pdf"));

boolean result = converter.convert(pattern.getParameter());
assertEquals(true, result);

3.6.3 按照base64添加水印

WorkableConverter converter = new WorkableConverter();
converter.setConverterType(WaterMarkConverterManager.getInstance());

ConvertPattern pattern = ConvertPatternManager.getInstance();
WaterMarkRequire waterMarkRequire = new WaterMarkRequire();

waterMarkRequire.setWaterMarkPage(0);//0 means all age
waterMarkRequire.setWaterMarkPDFBase64(Base64FileTool.FileToBase64(new File("./data/watermark.pdf")));

pattern.setWaterMarkRequire(waterMarkRequire);
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.base64ToBase64(Base64FileTool.FileToBase64(new File("./data/test5.pdf")));

boolean result = converter.convert(pattern.getParameter());
String base64Result = pattern.getBase64Result();
Base64FileTool.saveBase64File(base64Result, "./data/test5_with_mark03.pdf");
assertEquals(true, result);

4. 待辦事項(xiàng)

  • 已經(jīng)測試通過的有doc茫负、docx蕉鸳、html 按照不同姿勢轉(zhuǎn)PDF,其他類型的并沒有編寫測試單元忍法,后續(xù)考慮增加

  • 目前只支持yml配置潮尝,后續(xù)考慮添加其他類型的配置支持(xml、properties等)

  • 目前Markdown格式很流行饿序,考慮實(shí)現(xiàn)markdown格式的字符串轉(zhuǎn)PDF(markdown -> html -> pdf)

5. 注意事項(xiàng)

  • 因?yàn)樾枰狶ibreOffice的支持勉失,所以不建議在Docker等容器內(nèi)運(yùn)行(LibreOffice暫無Docker穩(wěn)定發(fā)行版的鏡像)

  • 轉(zhuǎn)換亂碼、轉(zhuǎn)換耗時(shí)過長嗤堰,請檢查服務(wù)器是否安裝有中文字體

  • 項(xiàng)目啟動后戴质,在執(zhí)行第一次轉(zhuǎn)換任務(wù)時(shí)度宦,因?yàn)樯婕暗脚cLibreOffice建立連接等操作,所以會耗時(shí)較長告匠,第二次任務(wù)及以后穩(wěn)定在0.5秒以內(nèi)(具體時(shí)間因機(jī)器配置會有所差異)

6. 參考鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末戈抄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子后专,更是在濱河造成了極大的恐慌划鸽,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件戚哎,死亡現(xiàn)場離奇詭異裸诽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)型凳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門丈冬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人甘畅,你說我怎么就攤上這事埂蕊。” “怎么了疏唾?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵蓄氧,是天一觀的道長。 經(jīng)常有香客問我槐脏,道長喉童,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任顿天,我火速辦了婚禮堂氯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘牌废。我一直安慰自己祖灰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布畔规。 她就那樣靜靜地躺著局扶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪叁扫。 梳的紋絲不亂的頭發(fā)上三妈,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音莫绣,去河邊找鬼畴蒲。 笑死,一個(gè)胖子當(dāng)著我的面吹牛对室,可吹牛的內(nèi)容都是我干的模燥。 我是一名探鬼主播咖祭,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蔫骂!你這毒婦竟也來了么翰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤辽旋,失蹤者是張志新(化名)和其女友劉穎浩嫌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體补胚,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡码耐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了溶其。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骚腥。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖瓶逃,靈堂內(nèi)的尸體忽然破棺而出桦沉,到底是詐尸還是另有隱情,我是刑警寧澤金闽,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站剿骨,受9級特大地震影響代芜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浓利,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一挤庇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贷掖,春花似錦嫡秕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至牙甫,卻和暖如春掷酗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背窟哺。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工泻轰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人且轨。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓浮声,卻偏偏與公主長得像虚婿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子泳挥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • feisky云計(jì)算然痊、虛擬化與Linux技術(shù)筆記posts - 1014, comments - 298, trac...
    不排版閱讀 3,843評論 0 5
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,096評論 1 32
  • 讀書要有志有識有恒 原文 士人讀書,第一要有志羡洁,第二要有識玷过,第三要有恒。有志則斷不甘為下流筑煮;有識則知學(xué)問無盡辛蚊,不敢...
    沐心7c閱讀 745評論 5 13
  • 前幾天洗臉時(shí)照了照鏡子,一看里面的人很陌生真仲。我連自己都快不認(rèn)識了袋马,認(rèn)真反思自己有多久沒認(rèn)真看看自己了。有多久沒有很...
    幸福的阿嬌閱讀 584評論 1 11
  • 1.土豆先生的日常 【表情介紹】 土豆先生是個(gè)畫渣秸应,立志成為大觸虑凛,時(shí)而高亢,時(shí)而萎靡软啼,隨時(shí)不忘一顆藝術(shù)之心 2桑谍、 ...
    POPO_c220閱讀 897評論 1 3