前言
前幾年做了個項目,里面有個需求,需要在瀏覽器中在線瀏覽word excel ppt pdf等文檔。
最近又開始研究並記錄下來
當時方案:
- 因為瀏覽器中閱讀文檔暫時只能通過pdf方式讀取,所以就要想辦法實現 word excel ppt 轉為pdf文件實現在線瀏覽。
- 考慮到文件的安全性問題,一些在線的Saas服務就不考慮了,定制化本地安裝的saas服務又不現實。
- .net 中已有一些組件可以實現word 轉pdf了 如aspose.net , spire.doc for .net 等等,不過這些都是收費的。
- 微軟的Office 也有提供com組件實現文檔轉碼服務,前提是必須在Windows服務器上安裝Office, 但Office同樣需要license
- 考慮到成本問題。
最后采用了開源 OpenOffice +OpenOffice SDK 部署在Windows服務器中實現該需求
必要前提:
- 在windows服務器 framework 4 因為是好些年前的項目了,當時采用的是.net framework 4.6.1, Linux系統倒是沒試過。
- OpenOffice 軟件
- OpenOffice SDK 必須保證版本一致,否則會有問題。
正文:
以下是兩個中間件服務
服務類型 | 服務名稱 | 簡稱 | 描述 |
Windows Service | Convert trigger Service | CTS | 目的是來監控輸入文件夾,當文件夾{InputFolder}中存在文件后,會出發轉碼操作。 |
Windows Console App | Convert Service | CS | 執行轉碼操作,會將{InputFolder}文件夾下的文件進行轉碼,並放置到{OutputFolder}目錄下。 |
CTS服務采用Process類 調用CS 服務
以下是物理架構的關系圖:
下面是CS服務中執行轉碼的核心代碼。

1 public class OpenOfficeHelper : IOpenOffice 2 { 3 // For thread safety 4 private Mutex _openOfficeLock; 5 6 /// <summary> 7 /// constructor 8 /// </summary> 9 public OpenOfficeHelper() 10 { 11 _openOfficeLock = new Mutex(false, "OpenOfficeMutexLock-MiloradCavic"); 12 } 13 14 /// <summary> 15 /// Converts document to PDF 16 /// </summary> 17 /// <param name="sourcePath">Path to document to convert(e.g: C:\test.doc)</param> 18 /// <param name="destinationPath">Path on which to save PDF (e.g: C:\test.pdf)</param> 19 /// <returns>Path to destination file if operation is successful, or Exception text if it is not</returns> 20 public void ConvertDocToPDF(string sourcePath, string destinationPath) 21 { 22 bool obtained = _openOfficeLock.WaitOne(60 * 1000, false); 23 24 XComponent xComponent = null; 25 try 26 { 27 if (!obtained) 28 { 29 throw new System.Exception(string.Format("Request for using OpenOffice wasn't served after {0} seconds. Aborting...", 30)); 30 } 31 32 sourcePath = PathConverter(sourcePath); 33 destinationPath = PathConverter(destinationPath); 34 35 // 載入文件前屬性設定,設定文件開啟時隱藏 36 PropertyValue[] loadDesc = new PropertyValue[1]; 37 loadDesc[0] = new PropertyValue(); 38 loadDesc[0].Name = "Hidden"; 39 loadDesc[0].Value = new uno.Any(true); 40 41 //Get a ComponentContext 42 unoidl.com.sun.star.uno.XComponentContext xLocalContext = uno.util.Bootstrap.bootstrap(); 43 44 //Get MultiServiceFactory 45 unoidl.com.sun.star.lang.XMultiServiceFactory xRemoteFactory = (unoidl.com.sun.star.lang.XMultiServiceFactory)xLocalContext.getServiceManager(); 46 47 //Get a CompontLoader 48 XComponentLoader aLoader = (XComponentLoader)xRemoteFactory.createInstance("com.sun.star.frame.Desktop"); 49 50 //Load the sourcefile 51 xComponent = aLoader.loadComponentFromURL(sourcePath, "_blank", 0, new unoidl.com.sun.star.beans.PropertyValue[0]); 52 53 //Wait for loading 54 while (xComponent == null) 55 { 56 Thread.Sleep(3000); 57 } 58 59 SaveDocument(xComponent, destinationPath); 60 61 xComponent.dispose(); 62 63 } 64 catch (System.Exception ex) 65 { 66 throw ex; 67 } 68 finally 69 { 70 Process[] pt = Process.GetProcessesByName("soffice.bin"); 71 if (pt != null && pt.Length > 0) 72 { 73 foreach (var item in pt) 74 { 75 item.Kill(); 76 } 77 } 78 if (obtained) 79 { 80 _openOfficeLock.ReleaseMutex(); 81 } 82 } 83 } 84 85 /// <summary> 86 /// 執行保存 87 /// </summary> 88 /// <param name="xComponent">The x component.</param> 89 /// <param name="filePath">Name of the file.</param> 90 private void SaveDocument(XComponent xComponent, string filePath) 91 { 92 unoidl.com.sun.star.beans.PropertyValue[] propertyValue = new unoidl.com.sun.star.beans.PropertyValue[1]; 93 94 propertyValue[0] = new unoidl.com.sun.star.beans.PropertyValue(); 95 propertyValue[0].Name = "FilterName"; 96 propertyValue[0].Value = new uno.Any("writer_pdf_Export"); 97 98 ((XStorable)xComponent).storeToURL(filePath, propertyValue); 99 } 100 101 /// <summary> 102 /// Convert into OO file format 103 /// </summary> 104 /// <param name="file">The file.</param> 105 /// <returns>The converted file</returns> 106 private static string PathConverter(string file) 107 { 108 try 109 { 110 file = file.Replace(@"\", "/"); 111 112 return "file:///" + file; 113 } 114 catch (System.Exception ex) 115 { 116 throw ex; 117 } 118 } 119 120 121 }
原理其實就是 調用了OpenOffice 軟件,另存為成PDF文件。
CTS服務的代碼就不放出來,其實就是起一個Timer 定時器,定時監控 {InputFolder}文件夾下是否存在待轉碼文件, 存在,則起一個Process 實例 執行CS應用進行轉碼操作即可。
踩坑記錄:
接下來就是遇到的坑了
- 當執行第一次轉碼操作時,CS服務會調用OpenOffice軟件,界面屏幕會彈出一個彈窗(這個彈出只會彈出一次,不會彈出了),這個彈窗內容是需要填寫的基本名稱,否則會導致OpenOffice一致停留在這個界面
- 但我們CTS服務默認是以Local System 賬戶運行的,而CS服務的啟動是由 Windows Service 觸發的, 所以OpenOffice軟件其實是由Local System用戶打開的,但Local System 打開沒有界面彈窗的,無法填寫,也就導致無法轉碼了。如何證明呢,看第三點。
- 查看任務管理器發現其實 OpenOffice 軟件已經打開(進程為soffice.bin進程),而且運行用戶正好就是Local System。
解決方案有兩種:
- 新建一個Windows用戶DocConverter,將該用戶放到管理員組下,然后以該用戶登錄windows后,打開OpenOffice,第一次彈窗后 填寫對應的基本信息后,將Windows Service 啟動用戶改為DocCoverter用戶,然后再啟動轉碼服務。 這時候會發現已經能夠正常工作了。
- 想辦法以Local System用戶身份打開一次OpenOffice,然后填寫OpenOffice的基本信息即可,怎么打開呢,這里借助PsTools工具,以cmd命令行模式打開即可, 下載PSTools,ps工具包 點我下載
(1)打開壓縮包,將里面的psexec.exe復制到System32文件夾下(64位用戶請將psexec64.exe復制到SysWOW64文件夾下)
(2)以管理員身份運行命令提示符,輸入"psexec -i -d -s cmd.exe"(64位用戶類似),等待1~2秒后,就會出現以system權限運行的命令提示符了
(3)在被啟動的命令提示符里輸入命令"whoami"並回車,會發現返回一條信息為"nt authority\system",說明此命令提示符已以本地系統的身份運行了。
基本上就是這樣。
參考: