.NET 采用开源软件OpenOffice 实现文档转码服务(word ppt excel)转PDF

前言

前几年做了个项目,里面有个需求,需要在浏览器中在线浏览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     }
View Code

原理其实就是 调用了OpenOffice 软件,另存为成PDF文件。

CTS服务的代码就不放出来,其实就是起一个Timer 定时器,定时监控 {InputFolder}文件夹下是否存在待转码文件, 存在,则起一个Process 实例 执行CS应用进行转码操作即可。

 

踩坑记录:

接下来就是遇到的坑了

  1. 当执行第一次转码操作时,CS服务会调用OpenOffice软件,界面屏幕会弹出一个弹窗(这个弹出只会弹出一次,不会弹出了),这个弹窗内容是需要填写的基本名称,否则会导致OpenOffice一致停留在这个界面

     

     

  2. 但我们CTS服务默认是以Local System 账户运行的,而CS服务的启动是由 Windows Service 触发的, 所以OpenOffice软件其实是由Local System用户打开的,但Local System 打开没有界面弹窗的,无法填写,也就导致无法转码了。如何证明呢,看第三点。
  3. 查看任务管理器发现其实 OpenOffice 软件已经打开(进程为soffice.bin进程),而且运行用户正好就是Local System。

      

解决方案有两种:

  1. 新建一个Windows用户DocConverter,将该用户放到管理员组下,然后以该用户登录windows后,打开OpenOffice,第一次弹窗后 填写对应的基本信息后,将Windows Service 启动用户改为DocCoverter用户,然后再启动转码服务。 这时候会发现已经能够正常工作了。
  2. 想办法以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",说明此命令提示符已以本地系统的身份运行了。  

                                    

                                   

 

 

 

基本上就是这样。

参考:

如何以system身份运行指定的程序?

OpenOffice

 

posted @ 2022-01-20 16:11  Dyhuang  阅读(1616)  评论(4编辑  收藏  举报