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 客户端的安全手段.
实现代码
客户端编写拦截器, 拦截器中主要接受来自服务端的请求, 并根据服务端的请求类型做出相应动作, 请求类型包含如下几种: 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 服务端的所有请求, 并对不同的请求类型做出相应的处理.
来源: https://www.cnblogs.com/Andre/p/9549874.html