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的组装:

cs 复制代码
        /// <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加密算法:

cs 复制代码
        /// <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转换:

cs 复制代码
        /// <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进制字符串:

cs 复制代码
        /// <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();
        }

获取在线预览链接

cs 复制代码
        /// <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方式访问

cs 复制代码
        /// <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的代码:

html 复制代码
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <!-- 建议禁用外框浏览器自带的缩放 -->
        <meta
            name="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=1
            function 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去操作,仅个人见解。

相关推荐
Hello123网站2 天前
WPS智能写作
ai写作·wps·ai工具
lingggggaaaa3 天前
小迪安全v2023学习笔记(八十讲)—— 中间件安全&WPS分析&Weblogic&Jenkins&Jetty&CVE
笔记·学习·安全·web安全·网络安全·中间件·wps
揭老师高效办公5 天前
在Word和WPS文字的表格中快速输入连续的星期、月、日
word·wps
小雪人8288 天前
wps的excel如何转为谷歌在线表格
excel·wps
揭老师高效办公9 天前
在PowerPoint和WPS演示让蝴蝶一直跳8字舞
powerpoint·wps
LAM LAB9 天前
【WPS】WPSPPT 快速抠背景
wps
揭老师高效办公9 天前
PowerPoint和WPS演示如何在放映PPT时用鼠标划重点
powerpoint·wps
揭老师高效办公9 天前
PowerPoint和WPS演示如何循环放映PPT
powerpoint·wps
l1t21 天前
DeepSeek辅助编写的将xlsx格式文件中sheet1.xml按需分别保留或去掉标签的程序
xml·python·excel·wps·xlsx
l1t24 天前
利用DeepSeek辅助WPS电子表格ET格式分析
人工智能·python·wps·插件·duckdb