[轉發]ASP.NET Core2集成Office Online Server(OWAS)實現辦公文檔的在線預覽與編輯(支持word\excel\ppt\pdf等格式)


轉載自:https://www.cnblogs.com/Andre/p/9549874.html

Office Online Server是微軟開發的一套基於Office實現在線文檔預覽編輯的技術框架(支持當前主流的瀏覽器,且瀏覽器上無需安裝任何插件,支持word、excel、ppt、pdf等文檔格式),其客戶端通過WebApi方式可集成到自已的應用中,支持Java、C#等語言。Office Online Server原名為:Office Web Apps Server(簡稱OWAS)。因為近期有ASP.NET Core 2.0的項目中要實現在線文檔預覽與編輯,就想着將Office Online Server集成到項目中來,通過網上查找,發現大部分的客戶端的實現都是基於ASP.NET的,而我在實現到ASP.NET Core 2.0的過程中也遇到了不少的問題,所以就有了今天這篇文章。

 

安裝Office Online Server

微軟的東西在安裝上都是很簡單的,下載安裝包一路”下一步“就可完成。也可參考如下說明來進行安裝:https://docs.microsoft.com/zh-cn/officeonlineserver/deploy-office-online-server

完成安裝后會在服務器上的IIS上自動創建兩個網站,分別為:HTTP80、HTTP809。其中HTTP80站綁定80、443端口,HTTP809站綁定809、810端口。

 

業務關系

1、Office Online Server服務端(WOPI Server),安裝在服務器上用於受理來自客戶端的預覽、編輯請求等。服務端很吃內存的,單機一定不能低於8G內存。

2、Office Online Server客戶端(WOPI Client),這里因為集成在了自已的項目中,所以Office Online Server客戶端也就是自已的項目中的子系統。

用戶通過項目中的業務系統請求客戶端並發起對某一文檔的預覽或編輯請求,客戶端接受請求后再通過調用服務端的WebApi完成一系列約定通訊后,服務端在線輸出文檔並完成預覽與編輯功能。

 

實現原理

可通過如下圖(圖片來自互聯網)能清晰的看出瀏覽器、Office Online Server服務端、Office Online Server客戶端之間的交互順序與關系。在這過程中,Office Online Server客戶端需自行生成Token及身份驗證,這也是為保障Office Online Server客戶端的安全手段。

19092925-56e50ede7a59467d8ba8d9047f5dfcb9

 

實現代碼

客戶端編寫攔截器,攔截器中主要接受來自服務端的請求,並根據服務端的請求類型做出相應動作,請求類型包含如下幾種:CheckFileInfo、GetFile、Lock、GetLock、RefreshLock、Unlock、UnlockAndRelock、PutFile、PutRelativeFile、RenameFile、DeleteFile、PutUserInfo等。具體代碼如下:

using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
//編寫一個處理WOPI請求的客戶端攔截器
namespace Lezhima.Wopi.Base
{
    public class ContentProvider  
    {
        //聲明請求代理
        private readonly RequestDelegate _nextDelegate;


        public ContentProvider(RequestDelegate nextDelegate)
        {
            _nextDelegate = nextDelegate;
        }


        //拉截並接受所有請求
        public async Task Invoke(HttpContext context)
        {
        //判斷是否為來自WOPI服務端的請求
            if (context.Request.Path.ToString().ToLower().IndexOf("files") >= 0)
            {
                WopiRequest requestData = ParseRequest(context.Request);

                switch (requestData.Type)
                {
            //獲取文件信息
                    case RequestType.CheckFileInfo:
                        await HandleCheckFileInfoRequest(context, requestData);
                        break;

                    //嘗試解鎖並重新鎖定
                    case RequestType.UnlockAndRelock:
                        HandleUnlockAndRelockRequest(context, requestData);
                        break;

                    //獲取文件
                    case RequestType.GetFile:
                        await HandleGetFileRequest(context, requestData);
                        break;

                    //寫入文件
                    case RequestType.PutFile:
                        await HandlePutFileRequest(context, requestData);
                        break;

                    default:
                        ReturnServerError(context.Response);
                        break;
                }
            }
            else
            {
                await _nextDelegate.Invoke(context);
            }
        }




        /// <summary>
        /// 接受並處理獲取文件信息的請求
        /// </summary>
        /// <remarks>
        /// </remarks>
        private async Task HandleCheckFileInfoRequest(HttpContext context, WopiRequest requestData)
        {
        //判斷是否有合法token    
            if (!ValidateAccess(requestData, writeAccessRequired: false))
            {
                ReturnInvalidToken(context.Response);
                return;
            }
            //獲取文件           
            IFileStorage storage = FileStorageFactory.CreateFileStorage();
            DateTime? lastModifiedTime = DateTime.Now;
            try
            {
                CheckFileInfoResponse responseData = new CheckFileInfoResponse()
                {
            //獲取文件名稱
                    BaseFileName = Path.GetFileName(requestData.Id),
                    Size = Convert.ToInt32(size),
                    Version = Convert.ToDateTime((DateTime)lastModifiedTime).ToFileTimeUtc().ToString(),
                    SupportsLocks = true,
                    SupportsUpdate = true,
                    UserCanNotWriteRelative = true,

                    ReadOnly = false,
                    UserCanWrite = true
                };

                var jsonString = JsonConvert.SerializeObject(responseData);

                ReturnSuccess(context.Response);

                await context.Response.WriteAsync(jsonString);

            }
            catch (UnauthorizedAccessException ex)
            {
                ReturnFileUnknown(context.Response);
            }
        }

        /// <summary>
        /// 接受並處理獲取文件的請求
        /// </summary>
        /// <remarks>
        /// </remarks>
        private async Task HandleGetFileRequest(HttpContext context, WopiRequest requestData)
        {
         //判斷是否有合法token    
            if (!ValidateAccess(requestData, writeAccessRequired: false))
            {
                ReturnInvalidToken(context.Response);
                return;
            }


            //獲取文件             
            var stream = await storage.GetFile(requestData.FileId);

            if (null == stream)
            {
                ReturnFileUnknown(context.Response);
                return;
            }

            try
            {
                int i = 0;
                List<byte> bytes = new List<byte>();
                do
                {
                    byte[] buffer = new byte[1024];
                    i = stream.Read(buffer, 0, 1024);
                    if (i > 0)
                    {
                        byte[] data = new byte[i];
                        Array.Copy(buffer, data, i);
                        bytes.AddRange(data);
                    }
                }
                while (i > 0);


                ReturnSuccess(context.Response);
            await context.Response.Body.WriteAsync(bytes, bytes.Count);

            }
            catch (UnauthorizedAccessException)
            {
                ReturnFileUnknown(context.Response);
            }
            catch (FileNotFoundException ex)
            {
                ReturnFileUnknown(context.Response);
            }

        }

        /// <summary>
        /// 接受並處理寫入文件的請求
        /// </summary>
        /// <remarks>
        /// </remarks>
        private async Task HandlePutFileRequest(HttpContext context, WopiRequest requestData)
        {
        //判斷是否有合法token    
            if (!ValidateAccess(requestData, writeAccessRequired: true))
            {
                ReturnInvalidToken(context.Response);
                return;
            }

            try
            {
                //寫入文件            
                int result = await storage.UploadFile(requestData.FileId, context.Request.Body);
                if (result != 0)
                {
                    ReturnServerError(context.Response);
                    return;
                }

                ReturnSuccess(context.Response);
            }
            catch (UnauthorizedAccessException)
            {
                ReturnFileUnknown(context.Response);
            }
            catch (IOException ex)
            {
                ReturnServerError(context.Response);
            }
        }



        private static void ReturnServerError(HttpResponse response)
        {
            ReturnStatus(response, 500, "Server Error");
        }

    }
}

攔截器有了后,再到Startup.cs文件中注入即可,具體代碼如下:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();
            app.UseAuthentication();

            //注入中間件攔截器,這是將咱們寫的那個Wopi客戶端攔截器注入進來
            app.UseMiddleware<ContentProvider>();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{name?}");
            });
        }

至止,整個基於Office Online Server技術框架在ASP.NET Core上的文檔預覽/編輯功能就完成了。夠簡單的吧!!

 

總結

1、Office Online Server服務端建議在服務器上獨立部署,不要與其它業務系統混合部署。因為這貨實在是太能吃內存了,其內部用了WebCached緩存機制是導致內存增高的一個因素。

2、Office Online Server很多資料上要求要用AD域,但我實際在集成客戶端時沒有涉及到這塊,也就是說服務端是開放的,但客戶端是通過自行頒發的Token與驗證來保障安全的。

3、利用編寫中間件攔截器,並在Startup.cs文件中注入中間件的方式來截獲來自WOPI服務端的所有請求,並對不同的請求

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM