美文网首页
Windows服务-Office转PDF文件

Windows服务-Office转PDF文件

作者: zxws1009 | 来源:发表于2020-05-02 14:59 被阅读0次

    一. 应用场景

    开发一个课件在线学习功能,要求将WORD, EXCEL, PPT类型课件可在线打开学习;最初设想使用第三方office插件,无奈价格太高放弃使用;
    我们最终的方案是:利用office自身的另存为功能,在服务器将上传的office文件转化为pdf格式,然后网页打开pdf文件实现在线学习功能;

    项目实现方案:新建一个windows自启的service安装在服务器,这个服务里面会建立一个HttpListener,用于监听文件转换的请求,请求来了后交给ServiceHandle处理,ServiceHandle会调用ConvertHelper把指定目录下的office文件转化为pdf,并放在相同目录下,以供在线学习使用;

    二. 工具及环境

    1. VS2019
    2. Office:本地开发2016版;服务器2010版
    3. 开发环境 win10
    4. 服务器环境 windows server 2012 R2
    5. VS开发使用到的nuget包:NetOffice

    三. 创建windows server项目

    VS2012下开发Windows服务, 参考地址:
    https://www.cnblogs.com/zhy-1992/p/6515850.html

    基本按照这个思路开发出一个基础service完全没问题;这里需要提醒下的是:项目的文件路径不要带有空格,否则在执行bat批处理操作时会出现问题。

    四. 使用NetOffice实现文件转换

    上面只是创建一个服务, 真正转换的核心功能还没开始,这时我们需要利用NetOffice插件;
    项目目录:


    image.png

    利用nuget包管理器安装所需包,这是项目的packages.config文件:

    <?xml version="1.0" encoding="utf-8"?>
    <packages>
      <package id="NetOffice.Core" version="1.7.4.4" targetFramework="net452" />
      <package id="NetOffice.Excel" version="1.7.4.4" targetFramework="net452" />
      <package id="NetOffice.PowerPoint" version="1.7.4.4" targetFramework="net452" />
      <package id="NetOffice.Word" version="1.7.4.4" targetFramework="net452" />
      <package id="Newtonsoft.Json" version="12.0.3" targetFramework="net45" />
    </packages> 
    

    转换核心代码:ConvertHelper.cs

    public class ConvertHelper
        {
            public static void ConvetToPdf(string sourcePath, string targetPath)
            {
                if (!File.Exists(sourcePath))
                {
                    throw new Exception(string.Format("文件{0}不存在", sourcePath));
                }
    
                if (File.Exists(targetPath))
                {
                    throw new Exception(string.Format("目标文件{0}已存在", targetPath));
                }
    
                LogHelper.Info<Program>("开始转换:" + Path.GetFileName(sourcePath));
    
                var targetDirectory = Path.GetDirectoryName(targetPath);
                if (!Directory.Exists(targetDirectory))
                {
                    Directory.CreateDirectory(targetDirectory);
                }
    
                var ext = Path.GetExtension(sourcePath).ToLower();
    
                if (ext == ".ppt" || ext == ".pptx")
                {
                    ConvertPptToPdf(sourcePath, targetPath);
                }
                else if (ext == ".doc" || ext == ".docx")
                {
                    ConvertDocumentToPdf(sourcePath, targetPath);
                }
                else if (ext == ".xls" || ext == ".xlsx")
                {
                    ConvertExcelToPdf(sourcePath, targetPath);
                }
                else
                {
                    LogHelper.Info<Program>("{0}不支持转换pdf", sourcePath);
                    return;
                }
    
                if (File.Exists(targetPath))
                {
                    LogHelper.Info<Program>("转换成功:" + Path.GetFileName(sourcePath));
                }
                else
                {
                    LogHelper.Info<Program>("转换失败:" + Path.GetFileName(sourcePath));
                }
            }
    
            /// <summary>
            /// 转换ppt文件到pdf文件(不支持超时终止)
            /// </summary>
            /// <param name="sourcePath"></param>
            /// <param name="targetPath"></param>
            private static void ConvertPptToPdf(string sourcePath, string targetPath)
            {
                NetOffice.PowerPointApi.Application application = null;
                NetOffice.PowerPointApi.Presentation presentation = null;
    
                var cts = new CancellationTokenSource();
                var thread = new Thread(() =>
                {
                    try
                    {
                        application = new NetOffice.PowerPointApi.Application();
                        presentation = application.Presentations.Open(sourcePath, true, NetOffice.OfficeApi.Enums.MsoTriState.msoTrue, false);
                        presentation.SaveCopyAs(targetPath, NetOffice.PowerPointApi.Enums.PpSaveAsFileType.ppSaveAsPDF);
                    }
                    catch (Exception ex)
                    {
                        if (ex.InnerException != null && ex.InnerException is ThreadAbortException)
                        {
                            LogHelper.Info<Program>("ConvertPptToPdf: {0}操作超时", Path.GetFileName(sourcePath));
                        }
                        else
                        {
                            LogHelper.Error<Program>(ex, "ConvertPptToPdf: {0}出现异常", Path.GetFileName(sourcePath));
                        }
                    }
                    finally
                    {
                        if (presentation != null)
                        {
                            presentation.Close();
                            presentation = null;
                        }
    
                        if (application != null)
                        {
                            application.Quit();
                            application.Dispose();
                            application = null;
                        }
    
                        //killProccess("POWERPNT");
                    }
                });
    
                cts.Token.Register(() =>
                {
                    thread.Abort();
                });
                cts.CancelAfter(Program.MaxThreads * 1000);
    
                thread.Start();
                thread.Join();
    
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
    
            /// <summary>
            /// 转换word文件到pdf文件(支持超时终止)
            /// </summary>
            /// <param name="sourcePath"></param>
            /// <param name="targetPath"></param>
            private static void ConvertDocumentToPdf(string sourcePath, string targetPath)
            {
                NetOffice.WordApi.Application application = null;
                NetOffice.WordApi.Document document = null;
    
                var cts = new CancellationTokenSource();
                var thread = new Thread(() =>
                {
                    try
                    {
                        application = new NetOffice.WordApi.Application();
                        document = application.Documents.Open(sourcePath);
                        document.ExportAsFixedFormat(targetPath, NetOffice.WordApi.Enums.WdExportFormat.wdExportFormatPDF);
                    }
                    catch (Exception ex)
                    {
                        if (ex.InnerException != null && ex.InnerException is ThreadAbortException)
                        {
                            LogHelper.Info<Program>("ConvertDocumentToPdf: {0}操作超时", Path.GetFileName(sourcePath));
                        }
                        else
                        {
                            LogHelper.Error<Program>(ex, "ConvertDocumentToPdf: {0}出现异常", Path.GetFileName(sourcePath));
                        }
                    }
                    finally
                    {
    
                        if (document != null)
                        {
                            document.Close();
                            document = null;
                        }
    
                        if (application != null)
                        {
                            application.Quit();
                            application.Dispose();
                            application = null;
                        }
    
                        //killProccess("WINWORD");
                    }
                });
    
                cts.Token.Register(() =>
                {
                    thread.Abort();
                });
                cts.CancelAfter(Program.MaxThreads * 1000);
    
                thread.Start();
                thread.Join();
    
    
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
    
            private static void ConvertExcelToPdf(string sourcePath, string targetPath)
            {
                NetOffice.ExcelApi.Application application = null;
                NetOffice.ExcelApi.Workbook wookbook = null;
    
                var cts = new CancellationTokenSource();
                var thread = new Thread(() =>
                {
                    try
                    {
                        application = new NetOffice.ExcelApi.Application();
                        wookbook = application.Workbooks.Open(sourcePath);
                        wookbook.ExportAsFixedFormat(NetOffice.ExcelApi.Enums.XlFixedFormatType.xlTypePDF, targetPath);
                    }
                    catch (Exception ex)
                    {
                        if (ex.InnerException != null && ex.InnerException is ThreadAbortException)
                        {
                            LogHelper.Info<Program>("ConvertExcelToPdf: {0}操作超时", Path.GetFileName(sourcePath));
                        }
                        else
                        {
                            LogHelper.Error<Program>(ex, "ConvertExcelToPdf: {0}出现异常", Path.GetFileName(sourcePath));
                        }
                    }
                    finally
                    {
                        if (wookbook != null)
                        {
                            wookbook.Close();
                            wookbook = null;
                        }
    
                        if (application != null)
                        {
                            application.Quit();
                            application.Dispose();
                            application = null;
                        }
    
                        //killProccess("EXCEL");
                    }
                });
    
                cts.Token.Register(() =>
                {
                    thread.Abort();
                });
                cts.CancelAfter(Program.MaxThreads * 1000);
    
                thread.Start();
                thread.Join();
    
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
    
    
            public static void CleanProccess()
            {
                LogHelper.Info<Program>("CleanProccess Start");
                killProccess("WINWORD");
                killProccess("EXCEL");
                LogHelper.Info<Program>("CleanProccess Finish");
            }
    
            private static void killProccess(string appName)
            {
                // Store all running process in the system
                Process[] runingProcess = Process.GetProcesses();
                for (int i = 0; i < runingProcess.Length; i++)
                {
                    // compare equivalent process by their name
                    if (string.Equals(runingProcess[i].ProcessName, appName, StringComparison.OrdinalIgnoreCase))
                    {
                        try
                        {
                            // kill  running process
                            runingProcess[i].Kill();
                            LogHelper.Info<Program>("Kill {0} [{1}]", appName, runingProcess[i].Id);
                        }
                        catch (Exception ex)
                        {
                            if (ex is System.InvalidOperationException)
                            {
                                //进程已经关闭
                            }
                            else
                            {
                                LogHelper.Error<Program>(ex, "Kill {0} [{1}] Error", appName, runingProcess[i].Id);
                            }
                        }
                    }
                }
            }
        }
    

    ServiceListener监听http请求,端口号默认8091;

            public ServiceListener()
            {
                _stop = new ManualResetEvent(false);
                _idle = new ManualResetEvent(false);
                _busy = new Semaphore(Program.MaxThreads, Program.MaxThreads);
                _listener = new HttpListener();
                _listenerThread = new Thread(HandleRequests);
            }
    
            public void Start()
            {
                var url = String.Format(@"http://localhost:{0}/", Program.ListenerPort); // Port=8091
                LogHelper.Info<ServiceListener>("Listenning Start:" + url);
                _listener.Prefixes.Add(url);
                _listener.Start();
                _listenerThread.Start();
            }
    

    五. 系统调用http服务,转换文件

            public void ConvertToPdf(string serviceUrl, string sourcePath, string pdfPath)
            {
                var url = $"{serviceUrl}?srcPath={sourcePath}&tarPath={pdfPath}";
                using (var webClient = new WebClient())
                {
                    var result = webClient.DownloadString(url);
                }
            }
    

    至此,开发完毕,本地开发完成;

    六. 本地测试功能

    1. 注册服务,执行注册服务.bat
    2. 启动服务,执行启动服务.bat
    3. 查看服务运行情况


      image.png

    但是测试转换时遇到了问题,日志错误为 "Office 检测到该文件有问题。为帮助保护您的计算机,此文件无法打开。":

    2020-05-02 09:04:22:248 Info [Thread9] 开始转换:a10a54166224453abdd8d4b809f15449.ppt
    2020-05-02 09:04:25:379 Exception [Thread10] ConvertPptToPdf: a10a54166224453abdd8d4b809f15449.ppt出现异常 Exception:
    System.Runtime.InteropServices.COMException (0x80004005): See inner exception(s) for details. ---> System.Reflection.TargetInvocationException: 调用的目标发生了异常。 ---> System.Runtime.InteropServices.COMException: Presentations.Open : Office 检测到该文件有问题。为帮助保护您的计算机,此文件无法打开。
       --- 内部异常堆栈跟踪的结尾 ---
       在 System.RuntimeType.InvokeDispMethod(String name, BindingFlags invokeAttr, Object target, Object[] args, Boolean[] byrefModifiers, Int32 culture, String[] namedParameters)
       在 System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams)
       在 NetOffice.Invoker.MethodReturn(COMObject comObject, String name, Object[] paramsArray)
       在 NetOffice.Invoker.MethodReturn(COMObject comObject, String name, Object[] paramsArray)
       在 NetOffice.PowerPointApi.Presentations.Open(String fileName, Object readOnly, Object untitled, Object withWindow)
       在 LMS.DocumentConvertService.ConvertHelper.LMS.DocumentConvertService\Service\ConvertHelper.cs:行号 75
    
    2020-05-02 09:04:33:477 Info [Thread9] 转换失败:a10a54166224453abdd8d4b809f15449.ppt
    

    问题原因:service不能没有权限进行桌面交互;因此需要开启此权限;
    相关资料:
    https://www.cnblogs.com/94cool/archive/2010/04/12/1710261.html
    https://www.cnblogs.com/ymworkroom/articles/6673989.html
    https://blog.csdn.net/jiangxinyu/article/details/5397060

    我才用了其中2种较简单解决方案:

    1. 修改代码,在ProjectInstaller.cs中ProjectInstaller类中重载OnAfterInstall
    [RunInstaller(true)]
        public partial class ProjectInstaller : System.Configuration.Install.Installer
        {
            public ProjectInstaller()
            {
                InitializeComponent();
            }
    
            protected override void OnAfterInstall(IDictionary savedState)
            {
                try
                {
                    base.OnAfterInstall(savedState);
                    System.Management.ManagementObject myService = new System.Management.ManagementObject(
                        string.Format("Win32_Service.Name='{0}'", this.serviceInstaller1.ServiceName));
                    System.Management.ManagementBaseObject changeMethod = myService.GetMethodParameters("Change");
                    changeMethod["DesktopInteract"] = true;
                    System.Management.ManagementBaseObject OutParam = myService.InvokeMethod("Change", changeMethod, null);
                }
                catch (Exception)
                {
                }
            }
        }
    
    1. SC程序修改, 允许与桌面进行交互
      用批处理的方式实现,加入如下命令到>启动服务.bat文件中
    sc config MonitorService type=interact type=own
    

    至此,本地开发环境转换服务成功运行。
    接下来准备上服务器!!

    相关文章

      网友评论

          本文标题:Windows服务-Office转PDF文件

          本文链接:https://www.haomeiwen.com/subject/hchkghtx.html