Windows服務(wù)-Office轉(zhuǎn)PDF文件

一. 應(yīng)用場(chǎng)景

開發(fā)一個(gè)課件在線學(xué)習(xí)功能,要求將WORD, EXCEL, PPT類型課件可在線打開學(xué)習(xí);最初設(shè)想使用第三方office插件,無奈價(jià)格太高放棄使用;
我們最終的方案是:利用office自身的另存為功能,在服務(wù)器將上傳的office文件轉(zhuǎn)化為pdf格式,然后網(wǎng)頁打開pdf文件實(shí)現(xiàn)在線學(xué)習(xí)功能;

項(xiàng)目實(shí)現(xiàn)方案:新建一個(gè)windows自啟的service安裝在服務(wù)器,這個(gè)服務(wù)里面會(huì)建立一個(gè)HttpListener,用于監(jiān)聽文件轉(zhuǎn)換的請(qǐng)求,請(qǐng)求來了后交給ServiceHandle處理,ServiceHandle會(huì)調(diào)用ConvertHelper把指定目錄下的office文件轉(zhuǎn)化為pdf,并放在相同目錄下,以供在線學(xué)習(xí)使用;

二. 工具及環(huán)境

  1. VS2019
  2. Office:本地開發(fā)2016版;服務(wù)器2010版
  3. 開發(fā)環(huán)境 win10
  4. 服務(wù)器環(huán)境 windows server 2012 R2
  5. VS開發(fā)使用到的nuget包:NetOffice

三. 創(chuàng)建windows server項(xiàng)目

VS2012下開發(fā)Windows服務(wù), 參考地址:
https://www.cnblogs.com/zhy-1992/p/6515850.html

基本按照這個(gè)思路開發(fā)出一個(gè)基礎(chǔ)service完全沒問題;這里需要提醒下的是:項(xiàng)目的文件路徑不要帶有空格,否則在執(zhí)行bat批處理操作時(shí)會(huì)出現(xiàn)問題。

四. 使用NetOffice實(shí)現(xiàn)文件轉(zhuǎn)換

上面只是創(chuàng)建一個(gè)服務(wù), 真正轉(zhuǎn)換的核心功能還沒開始,這時(shí)我們需要利用NetOffice插件;
項(xiàng)目目錄:


image.png

利用nuget包管理器安裝所需包,這是項(xiàng)目的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> 

轉(zhuǎn)換核心代碼: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("目標(biāo)文件{0}已存在", targetPath));
            }

            LogHelper.Info<Program>("開始轉(zhuǎn)換:" + 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}不支持轉(zhuǎn)換pdf", sourcePath);
                return;
            }

            if (File.Exists(targetPath))
            {
                LogHelper.Info<Program>("轉(zhuǎn)換成功:" + Path.GetFileName(sourcePath));
            }
            else
            {
                LogHelper.Info<Program>("轉(zhuǎn)換失敗:" + Path.GetFileName(sourcePath));
            }
        }

        /// <summary>
        /// 轉(zhuǎn)換ppt文件到pdf文件(不支持超時(shí)終止)
        /// </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}操作超時(shí)", Path.GetFileName(sourcePath));
                    }
                    else
                    {
                        LogHelper.Error<Program>(ex, "ConvertPptToPdf: {0}出現(xiàn)異常", 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>
        /// 轉(zhuǎn)換word文件到pdf文件(支持超時(shí)終止)
        /// </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}操作超時(shí)", Path.GetFileName(sourcePath));
                    }
                    else
                    {
                        LogHelper.Error<Program>(ex, "ConvertDocumentToPdf: {0}出現(xiàn)異常", 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}操作超時(shí)", Path.GetFileName(sourcePath));
                    }
                    else
                    {
                        LogHelper.Error<Program>(ex, "ConvertExcelToPdf: {0}出現(xiàn)異常", 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)
                        {
                            //進(jìn)程已經(jīng)關(guān)閉
                        }
                        else
                        {
                            LogHelper.Error<Program>(ex, "Kill {0} [{1}] Error", appName, runingProcess[i].Id);
                        }
                    }
                }
            }
        }
    }

ServiceListener監(jiān)聽http請(qǐng)求,端口號(hào)默認(rèn)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();
        }

五. 系統(tǒng)調(diào)用http服務(wù),轉(zhuǎn)換文件

        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);
            }
        }

至此,開發(fā)完畢,本地開發(fā)完成;

六. 本地測(cè)試功能

  1. 注冊(cè)服務(wù),執(zhí)行注冊(cè)服務(wù).bat
  2. 啟動(dòng)服務(wù),執(zhí)行啟動(dòng)服務(wù).bat
  3. 查看服務(wù)運(yùn)行情況


    image.png

但是測(cè)試轉(zhuǎn)換時(shí)遇到了問題,日志錯(cuò)誤為 "Office 檢測(cè)到該文件有問題。為幫助保護(hù)您的計(jì)算機(jī),此文件無法打開。":

2020-05-02 09:04:22:248 Info [Thread9] 開始轉(zhuǎn)換:a10a54166224453abdd8d4b809f15449.ppt
2020-05-02 09:04:25:379 Exception [Thread10] ConvertPptToPdf: a10a54166224453abdd8d4b809f15449.ppt出現(xiàn)異常 Exception:
System.Runtime.InteropServices.COMException (0x80004005): See inner exception(s) for details. ---> System.Reflection.TargetInvocationException: 調(diào)用的目標(biāo)發(fā)生了異常。 ---> System.Runtime.InteropServices.COMException: Presentations.Open : Office 檢測(cè)到該文件有問題。為幫助保護(hù)您的計(jì)算機(jī),此文件無法打開。
   --- 內(nèi)部異常堆棧跟蹤的結(jié)尾 ---
   在 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:行號(hào) 75

2020-05-02 09:04:33:477 Info [Thread9] 轉(zhuǎn)換失敗:a10a54166224453abdd8d4b809f15449.ppt

問題原因:service不能沒有權(quán)限進(jìn)行桌面交互;因此需要開啟此權(quán)限;
相關(guān)資料:
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種較簡(jiǎn)單解決方案:

  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程序修改, 允許與桌面進(jìn)行交互
    用批處理的方式實(shí)現(xiàn),加入如下命令到>啟動(dòng)服務(wù).bat文件中
sc config MonitorService type=interact type=own

至此,本地開發(fā)環(huán)境轉(zhuǎn)換服務(wù)成功運(yùn)行。
接下來準(zhǔn)備上服務(wù)器?。?/p>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容