WPS本地镜像化在线文档操作以及样例

一个客户项目有引进在线文档操作需求,让我这边做一个demo调研下,给我的对接文档里有相关方法的说明,照着对接即可。但在真正对接过程中还是踩过不少坑,这儿对之前的对接工作做个记录。
按照习惯先来一个效果:

 Demo下载链接:https://download.csdn.net/download/qxyywy/88117444

接入指引


1. 申请授权证书并进行授权。
2. 登录系统后台页面
3. 创建、获取应用信息 access_key (简称ak)、 secret_key (简称 sk )。开发者在接口调用时,使用ak 、 sk 生成WPS-4签名进行鉴权。
4. 在线编辑、在线预览、格式转换接入按照对应开放能力文档接入。在线编辑、在线预览对接过程中需要设置回调地址。其中,在线编辑可通过配置开启历史版本功能。
5. 使用过程中需通过证书查询接口关注授权证书状态,若证书即将过期或者不可用,需进行更新证书操作。
6. 在线编辑或在线预览服务端对接完毕,对接方可使用JSSDK,调用API实现相关需求。

WPS-4签名算法

在对接时,耗费了一定时间在WPS-4签名处,对接文档中有WPS-4的说明和样例,自己在对接转换成NetCore的时候踩了一些坑,签名算法中最主要是:Wps-Docs-Authorization的计算方法
签名格式:WPS-4 {accessKey}:{signature}  注意WPS-4后面有空格。
signature:hmac-sha256(secret_key, Ver + HttpMethod + URI + Content-Type + WpsDocs-Date + sha256(HttpBody))
signature的样例如下:WPS-4POST/callback/path/demoapplication/jsonWed, 20 Apr 2022 01:33:07GMTfc005f51a6e75586d2d5d078b657dxxxdf9c1dfa6a7c0c0ba38c715daeb6ede9

这是文档中对签名算法的解释,对照着格式完成相关算法,具体算法如下:
signature的组装:

        /// <summary>/// 获取签名/// </summary>/// <param name="method">请求方法,如:GET,POST</param>/// <param name="uri">请求url,带querystring</param>/// <param name="body">请求body</param>/// <param name="date">日期</param>/// <param name="contentType">默认:application/json</param>/// <param name="secretKey">应用SK</param>/// <returns></returns>public static string WPS4Signature(string secretKey,string method,string uri, byte[] body=null,DateTime? date=null,string contentType= "application/json"){//获取uri路径string path = uri;//日期格式化if (date == null)date = DateTime.Now;string dateStr = String.Format("{0:r}", date);//open不参与签名,做替换处理if (path.StartsWith("/open")){path = path.Replace("/open", "");}string sha256body;//body为空则为空,否则返回sha256(body)if (body != null && body.Length > 0){sha256body = Sha256(body);}else{sha256body = "";}String signature = null;signature = HmacSHA256Encrypt($"WPS-4{method.ToUpper()}{path}{contentType}{dateStr}{sha256body}", secretKey);return signature;}

HmacSHA256加密算法:

        /// <summary>/// HmacSHA256加密/// </summary>/// <param name="secret"></param>/// <param name="signKey"></param>/// <returns></returns>public static string HmacSHA256Encrypt(string secret, string signKey){string signRet = string.Empty;using (HMACSHA256 mac = new HMACSHA256(Encoding.UTF8.GetBytes(signKey))){byte[] hash = mac.ComputeHash(Encoding.UTF8.GetBytes(secret));//signRet = Convert.ToBase64String(hash);signRet = ToHexStrFromByte(hash); }return signRet;}

Sha256转换:

        /// <summary>/// Sha256转换/// </summary>/// <param name="input">The input.</param>/// <returns>A hash.</returns>public static string Sha256(this byte[] input){if (input == null){return null;}using (var sha = SHA256.Create()){var hash = sha.ComputeHash(input);return ToHexStrFromByte(hash);}}

字节数组转16进制字符串:

        /// <summary>/// 字节数组转16进制字符串:空格分隔/// </summary>/// <param name="byteDatas"></param>/// <returns></returns>public static string ToHexStrFromByte(this byte[] byteDatas){StringBuilder builder = new StringBuilder();for (int i = 0; i < byteDatas.Length; i++){builder.Append(string.Format("{0:X2}", byteDatas[i]));}return builder.ToString().Trim().ToLower();}

获取在线预览链接

        /// <summary>/// 获取在线预览链接/// </summary>/// <param name="request"></param>/// <returns></returns>[Route("api/wps/previewgenarate")][HttpPost]public Task<GenarateResult> GenarateWPSPreviewUrl(GenarateRequest request){return Task.Run(() =>{string wpsHost = "http://10.4.**.**";string uri = $"/api/preview/v1/files/{defaultFileId}/link?type=w&preview_mode=high_definition";string fullUrl = $"{wpsHost}/open{uri}";Dictionary<string, string> headers = new Dictionary<string, string>();DateTime now = DateTime.Now;headers.Add("Content-Type", "application/json");headers.Add("Wps-Docs-Date", String.Format("{0:r}", now));var signature = WPSLocalSIgnatureHelper.WPS4Signature("SKrpaxjdwoetijjv", "get", uri, null, now);string docsAuthorization = WPSLocalSIgnatureHelper.WPS4SignAuthorization("UOMYPEVAHWQLTKJF", signature);headers.Add("Wps-Docs-Authorization", docsAuthorization);HttpHelper httpHelper = new HttpHelper();var resultTemp = httpHelper.Get(fullUrl, headers);var result = JsonConvert.DeserializeObject<ResponseBaseModel<OnlineEditResultModel>>(resultTemp);string url = "";if (result != null && result.data != null){url = result.data.link;}return new GenarateResult { Url = url };});}

这儿比较坑的地方来了,方法参数都组装好了,通过HttpWebRequest后端发起请求获取WPS中台返回的在线预览地址时,始终提示401报错获取不到数据。迫不得已自己通过APIPost组装了相关请求和参数居然又能获取到相关数据。对照了下请求头,又差异后调整了程序的请求头,保证和apiPost里的完全一致,依然返回401,通过查阅相关资料找到一个处理办法,在发起的请求后手动捕获WebException,然后在WebException里解析数据流获取信息。
HttpWebRequest Get方式访问
 

        /// <summary>///  Get方式访问/// </summary>/// <param name="url"></param>/// <param name="encode"></param>/// <param name="referer"></param>/// <param name="headers"></param>/// <returns></returns>public string Get(string url, string encode, string referer, Dictionary<string, string> headers=null){int num = _tryTimes;HttpWebRequest request = null;HttpWebResponse response = null;StreamReader reader = null;while (num-- >= 0){try{DelaySomeTime();ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(CheckValidationResult);//验证服务器证书回调自动验证request = (HttpWebRequest)WebRequest.Create(url);request.Headers.Add("accept", "*/*");request.Headers.Add("accept-encoding", "gzip, deflate, br");request.Headers.Add("accept-language", "zh-CN");request.Headers.Add("connection", "keep-alive");if (headers != null) {foreach (var item in headers){request.Headers.Add(item.Key, item.Value);}}//request.UserAgent = reqUserAgent;request.CookieContainer = _cookie;request.Referer = referer;request.Method = "GET";request.Timeout = _timeOut;if (_proxy != null && _proxy.Credentials != null){request.UseDefaultCredentials = true;}request.Proxy = _proxy;response = (HttpWebResponse)request.GetResponse();reader = new StreamReader(response.GetResponseStream(), Encoding.GetEncoding(encode));return reader.ReadToEnd();}catch (WebException ex){response = (HttpWebResponse)ex.Response; //解析401等错误返回的有效信息var resultTemp = "";Stream stream = response.GetResponseStream();using (StreamReader readers = new StreamReader(stream, Encoding.UTF8)){resultTemp = readers.ReadToEnd();}return resultTemp;}catch (Exception ex){_logger.Error(url + "\r\n" + ex.ToString());continue;}finally{if (request != null){request.Abort();}if (response != null){response.Close();}if (reader != null){reader.Close();}}}return string.Empty;}

DemoHtml


到上面时后端的处理就基本完成,前端这边因为时临时demo就用了一个html来处理,真实项目中需要用VUE等处理也都类似。
以下为 html的代码:

<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><!-- 建议禁用外框浏览器自带的缩放 --><metaname="viewport"content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0,user-scalable=no"/><meta http-equiv="X-UA-Compatible" content="ie=edge" /><title>WPS Web Office(iframe)接入指南</title><style>* {box-sizing: border-box;}html,body {display: flex;flex-direction: column;padding: 0;margin: 0;height: 100%;/* 防止双击缩放 */touch-action: manipulation;}iframe {flex: 1;}</style><!-- cdn引入JQ --><script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.4.1.min.js"></script><script src="./jwps.js"></script><script type="text/javascript">var appHost = "http://10.4.146.19:8890";// 支持 HTTPS// 注意:如果通过postMessage来设置token,请在url参数加上_w_tokentype=1function showWPS(url) {// 初始化var wps = WPS.config({mount: document.querySelector("#wpsPanl"),// 文字wpsUrl: url,headers: {shareBtn: {tooltip: "分享",subscribe: function() {console.log("click callback");}},otherMenuBtn: {tooltip: "其他按钮",items: [{// 自定义, type 固定填 'custom'type: "custom",icon:"http://ep.wps.cn/index/images/logo_white2.png",text: "API 导出 PDF",subscribe: function(wps) {if (wps.WpsApplication) {wps.WpsApplication().ActiveDocument.ExportAsFixedFormatAsync().then(function(result) {console.table(result);});}}},{// 自定义, type 固定填 'custom'type: "custom",icon:"http://ep.wps.cn/index/images/logo_white2.png",text: "API 使用",subscribe: function(wps) {let result;if (wps.WpsApplication) {wps.WpsApplication().ActiveDocument.ExportAsFixedFormatAsync().then(function(result) {console.table(result);});}}}]}}});return wps;}window.onload = function() {$.ajax({url: appHost+"/api/wps/previewgenarate",contentType: "application/json",dataType: "json",data: JSON.stringify({fileId: 'a123',fileName: "test.docx",fileType: 1,userId: 1505340867}),type: "post",success: function(res) {var wpsUrl = res.url;console.log(wpsUrl);var wps = showWPS(wpsUrl);}});var fileInput = document.getElementById("bookimg1");//选择文件fileInput.addEventListener('change', function () {//如果未传入文件则中断if (fileInput.files[0] == undefined) {return;}var file = fileInput.files[0];//FileReader可直接将上传文件转化为二进制流var reader = new FileReader();reader.readAsDataURL(file);//转化二进制流,异步方法reader.onload = function (result) {//完成后this.result为二进制流var base64Str = this.result;$('#uploadImg').attr('src', base64Str);$('#imgPreview').show();} })};function replaceWps() {$('.tdname1').html($('#name1').val());$('.tddept1').html($('#dept1').val());$('.tdage1').html($('#age1').val());$('.tdname2').html($('#name2').val());$('.tddept2').html($('#dept2').val());$('.tdage2').html($('#age2').val());var fileBytes = "";var fileName = "";if ($('#bookimg1')[0].files[0] != undefined) {var imgFile = $('#bookimg1')[0].files[0];fileName = imgFile.name;//FileReader可直接将上传文件转化为二进制流var reader = new FileReader();reader.readAsDataURL(imgFile);//转化二进制流,异步方法reader.onload = function (result) {//完成后this.result为二进制流var base64Str = this.result;var startNum = base64Str.indexOf("base64,");startNum = startNum * 1 + 7;//去除前部格式信息(如果有需求)var baseStr = base64Str.slice(startNum);fileBytes = baseStr;$.ajax({url: appHost + "/api/wps/wrapheader",contentType: "application/json",dataType: "json",data: JSON.stringify({sample_list: [{bookmark: 'bookmark1',type: 'TEXT',text: $('#bookmark1').val()},{bookmark: 'bookmark2',type: 'TEXT',text: $('#bookmark2').val()},{bookmark: 'bookmark3',type: 'TEXT',text: $('#bookmark3').val()},{bookmark: 'bookimg1',type: 'IMAGE',/*sample_url:$('#bookimg1').val(),*/sample_filename: fileName,text: fileBytes,},{bookmark: 'bookform1',type: 'TEXT',text: $('#testForm').prop("outerHTML")}],}),type: "post",success: function (res) {var wpsUrl = res.url;;var wps = showWPS(wpsUrl);}});}} else {$.ajax({url: "http://10.4.146.19:8890/api/wps/wrapheader",contentType: "application/json",dataType: "json",data: JSON.stringify({sample_list: [{bookmark: 'bookmark1',type: 'TEXT',text: $('#bookmark1').val()},{bookmark: 'bookmark2',type: 'TEXT',text: $('#bookmark2').val()},{bookmark: 'bookmark3',type: 'TEXT',text: $('#bookmark3').val()},{bookmark: 'bookform1',type: 'TEXT',text: $('#testForm').prop("outerHTML")}],}),type: "post",success: function (res) {var wpsUrl = res.url;;var wps = showWPS(wpsUrl);}});}}</script></head><body><div style="width:100%;height:700px;"><div id="wpsPanl" style="width:65%;float:left;height:100%;"></div><div id="form" style="width:34%;float:left;height:100%;padding-top:50px;padding-left: 20px;"><div><div class="title">课题名称:</div><input class="input" type="text" id="bookmark1" placeholder="请填写课题名称"></div><dl style="clear:both;"></dl><div><div class="title">课题申报单位:</div><input class="input" type="text" id="bookmark2" placeholder="请填写课题申报单位"></div><dl style="clear:both;"></dl><div><div class="title">课题负责人:</div><input class="input" type="text" id="bookmark3" placeholder="请填写课题负责人"></div><dl style="clear:both;"></dl><div style="height:140px"><div class="title">课题成员:</div><table><thead><tr><td>姓名</td><td>所属部门</td><td>年龄</td></tr></thead><tr><td><input class="forminput" type="text" id="name1" placeholder="请填写姓名"></td><td><input class="forminput" type="text" id="dept1" placeholder="请填写所属部门"></td><td><input class="forminput" type="text" id="age1" placeholder="请填写年龄"></td></tr><tr><td><input class="forminput" type="text" id="name2" placeholder="请填写姓名"></td><td><input class="forminput" type="text" id="dept2" placeholder="请填写所属部门"></td><td><input class="forminput" type="text" id="age2" placeholder="请填写年龄"></td></tr></table><div style="width:100%;display:none;"><table border="1" cellspacing="0" style="width:90%;" id="testForm"><thead><tr><td>姓名</td><td>所属部门</td><td>年龄</td></tr></thead><tr><td class="tdname1"></td><td class="tddept1"></td><td class="tdage1"></td></tr><tr><td class="tdname2"></td><td class="tddept2"></td><td class="tdage2"></td></tr></table></div></div><dl style="clear:both;"></dl><div style="height:20px;margin:0;"><div class="title"></div><span style="color:red">表格替换在第6页</span></div><dl style="clear:both;"></dl><div><div class="title">图片上传:</div><input style="padding:8px 0;" type="file" id="bookimg1"></div><div style="display:none;" id="imgPreview"><img src="" id="uploadImg" width="100" /></div><dl style="clear:both;"></dl><div style="height:20px;margin:0;"><div class="title"></div><span style="color:red">图片替换在第7页</span></div><dl style="clear:both;"></dl><div style="text-align:center;"><input style="margin: 0 150px;height: 40px;background: rgba(2,128,204,1);border-radius: 2px;display: block;width: 80px;border: none;cursor: pointer;font-size: 14px;font-weight: 600;color: rgba(255,255,255,1);" type="button" value="替换" onclick="replaceWps()" id="submit"></div></div><div></body><style>#wpsPanl iframe{width:99%;height:850px;}#form div{margin: 10px 0;height: 40px;color: rgba(51,51,51,1);float: left;}#form div .input{height: 35px;margin: 2px 0;width: 358px;border: solid 1px rgba(193,193,193,.35);}#form div .forminput {height: 35px;margin: 2px 0;width: 94px;border: solid 1px rgba(193,193,193,.35);}#form div .title{width:120px;text-align: right;}td {border: 1px solid rgba(193,193,193,.35);padding: 8px 12px;}tr {background-color: inherit;font-size: 14px;border-top: 1px solid var(--vp-c-divider);transition: background-color .5s;}table {/*width: 100%;*/display: table;border-collapse: collapse;/*margin: 20px 0;*/overflow-x: auto;}table thead {font-weight: bold;}</style>
</html>


个人总结:需要在线编辑等相关操作,其实也可以用免费的组件组合,比如可以用onlyoffice+Aspose.Words去操作,仅个人见解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/16817.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【phaser微信抖音小游戏开发002】hello world!

执行效果&#xff1a; 将以下代码文本内容&#xff0c;放入到game.js中即可。目录结构如下图 import ./js/libs/weapp-adapter import ./js/libs/symbolGameGlobal.window.scrollTo () > { };//防止真机出错 import Phaser from ./js/phaser//引入Phaservar {windowWidth, …

vue项目环境 搭建

1、安装nodejs 2、安装vue-cli, npm i -g vue/cli-init 3、初始化项目 vue init webpack test 4、运行 cd test npm run dev

无人机影像配准并发布(共线方程)

无人机影像 DEM 计算四个角点坐标&#xff08;刚性变换&#xff09; 像空间坐标&#xff08;x,y,-f&#xff09; 像空间坐标畸变纠正 deltax,deltay 已知(x,y)&#xff0c;求解(X,Y, Z)或者(Lat,Lon) 这里的Z是DEM上获取的坐标和Zs为相机坐标的高程&#xff0c;如果均为已…

应用无线鼠标中的2.4GHz无线收发芯片

无线键盘和无线鼠标作为现代办公环境中常见的工具&#xff0c;为我们的工作带来了便利。无线键盘和无线鼠标的工作原理都是基于无线技术实现的&#xff0c;其中常见的是2.4GHz无线技术。让我们一起来详细了解一下它们的工作原理。 无线鼠标的原理非常简单,鼠标部分工作与传统鼠…

html富文本编辑器

接了个单子&#xff0c;需要添加一个文章模块&#xff0c;一看用到的技术这么老&#xff0c;人傻了&#xff0c;纯html css js 。 在普通页面中 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"…

为Win12做准备?微软Win11 23H2将集成AI助手:GPT4免费用

微软日前确认今年4季度推出Win11 23H2&#xff0c;这是Win11第二个年度更新。 Win11 23H2具体有哪些功能升级&#xff0c;现在还不好说&#xff0c;但它会集成微软的Copilot&#xff0c;它很容易让人想到多年前的“曲别针”助手&#xff0c;但这次是AI技术加持的&#xff0c;Co…

一些高频的C++ cache line面试

C那些事之False Sharing与Cache line 最近看到一段代码&#xff0c;手动做的对齐&#xff0c;于是研究一下不对齐又会带来什么影响&#xff1f; template <typename T> class AtomicWithPadding {private:static constexpr int kCacheLineSize 64;uint8_t padding_befor…

牛客网Verilog刷题——VL46

牛客网Verilog刷题——VL46 题目解析答案 题目 根据题目提供的双口RAM代码和接口描述&#xff0c;实现同步FIFO&#xff0c;要求FIFO位宽和深度参数化可配置。电路的接口如下图所示。   双口RAM端口说明&#xff1a; 同步FIFO端口说明&#xff1a; 双口RAM代码如下&#xff…

网络安全 Day24-select高级用法和多表连接

select高级用法和多表连接 1. select 多子句单表高级实践1.1 select 多子句高级语法1.2 聚合函数1.3 group by 实践1.4 having 筛选1.5 order by 排序1.6 limit 2. 多表连接 1. select 多子句单表高级实践 1.1 select 多子句高级语法 where 和 having 区别是后者是分组后进行…

邪恶版ChatGPT来了!

「邪恶版」ChatGPT 出现&#xff1a;每月 60 欧元&#xff0c;毫无道德限制&#xff0c;专为“网络罪犯”而生。 WormGPT 并不是一个人工智能聊天机器人&#xff0c;它的开发目的不是为了有趣地提供无脊椎动物的人工智能帮助&#xff0c;就像专注于猫科动物的CatGPT一样。相反&…

【C++入门到精通】C++入门 —— 类和对象(构造函数、析构函数)

目录 一、类的6个默认成员函数 二、构造函数 ⭕构造函数概念 ⭕构造函数的特点 ⭕常见构造函数的几种类型 三、析构函数 ⭕析构函数概念 ⭕析构函数的特点 ⭕常见析构函数的几种类型 四、温馨提示 前言 这一篇文章是上一篇的续集&#xff08;这里有上篇链接&#xff09;…

Flink非对齐checkpoint原理(Flink Unaligned Checkpoint)

Flink非对齐checkpoint原理&#xff08;Flink Unaligned Checkpoint&#xff09; 为什么提出Unaligned Checkpoint&#xff08;UC&#xff09;&#xff1f; 因为反压严重时会导致Checkpoint失败&#xff0c;可能导致如下问题 恢复时间长-服务效率低非幂等和非事务会导致数据…

5分钟快手入门laravel邮件通知

第一步&#xff1a; 生成一个邮件发送对象 php artisan make:mail TestMail 第二步&#xff1a; 编辑.env 添加/修改&#xff08;没有的key则添加&#xff09; MAIL_DRIVERsmtp MAIL_HOSTsmtp.163.com &#xff08;这里用163邮箱&#xff09; MAIL_PORT25 &#xff08;163邮箱…

C# SourceGenerator 源生成器初探

简介 注意&#xff1a; 坑极多。而且截至2023年&#xff0c;这个东西仅仅是半成品 利用SourceGenerator可以在编译结束前生成一些代码参与编译&#xff0c;比如编译时反射之类的&#xff0c;还有模板代码生成都很好用。 演示仓库传送门-Github-yueh0607 使用 1. 创建项目 …

flutter 导出iOS问题2

问题1:The Swift pod FirebaseCoreInternal depends upon GoogleUtilities, which does not define modules. To opt into those targets generating module maps (which is necessary to import them from Swift when building as static libraries) 参考 正如上图报错第三方…

AI生成式视频技术来临:Runway Gen-2文本生成视频

Runway Gen-2的官方网站提供了一种文本生成视频的工具。以下是对该工具的介绍&#xff1a; 文本生成视频&#xff1a;Runway Gen-2是一个创新的在线工具&#xff0c;可以将文本转化为视频。用户只需输入文本描述或句子&#xff0c;Runway Gen-2就能自动生成相应的视频内容。这…

机器学习-New Optimization

机器学习(New Optimization) 前言&#xff1a; 学习资料 videopptblog 下面的PPT里面有一些符号错误&#xff0c;但是我还是按照PPT的内容编写公式&#xff0c;自己直到符号表示什么含义就好了 Notation 符号解释 θ t \theta_t θt​第 t 步时&#xff0c;模型的参数 Δ L …

数据结构---并查集

目录标题 为什么会有并查集并查集的原理模拟实现并查集准备工作构造函数FindRootUnionSetCount 并查集实战题目一&#xff1a;省份数量题目解析题目二&#xff1a;等式方程的可满足性题目解析 为什么会有并查集 这里可以使用生活中的一个例子来带着大家理解并查集&#xff0c;…

深入理解 SQL:从基本查询到高级聚合

目录 背景理论知识示例1211. 查询结果的质量和占比&#xff08;Round group by&#xff09;1204. 最后一个能进入巴士的人 &#xff08;Having limit order by&#xff09;1193. 每月交易 I&#xff08;if group by&#xff09;1179. 重新格式化部门表1174. 即时食物配送 II&am…

JVM总结笔记

JVM JVM是什么?JVM 的主要组成部分JVM工作流程JVM内存模型直接内存与堆内存的区别&#xff1a;堆栈的区别Java会存在内存泄漏吗&#xff1f;简述Java垃圾回收机制垃圾收集算法轻GC(Minor GC)和重GC(Full GC)新生代gc流程JVM优化与JVM调优 JVM是什么? JVM是Java Virtual Mach…