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" />
        <!-- 建议禁用外框浏览器自带的缩放 -->
        <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去操作,仅个人见解。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WebOffice是一款由北京点聚信息技术有限公司提供的完全免费(商业用途也免费)且功能强大的在线Word/excel/wps编辑辅助控件,可以实现: 1.在线编辑Word、Excel、PPT、WPS... ... 2.全面支持MS Office的界面定制,包括对于Office2007的全面支持 3.修订留痕 4.限制打印、保存、复制 5.直接保存到服务器,支持标准Http Post协议 6.强大的书签管理 7.套红、文档保护 8.模板管理 9.其他功能扩展 10.提供开发论坛http://www.dianju.cn/forum/,在其中提供软件更新及问题回答服务 本控件不同于其它同类软件,不是基于微软的开放源码DsoFramer,也不基于OLE,可以解决DsoFramer及其派生产品的一些稳定性问题 本控件不同于其它同类收费软件,本控件完全免费和界面完全可控制。 点聚会定期更新本控件,并解答论坛上的询问,免费不代表免服务,每一个使用者都能得到及时服务。 安装包内包含: 1.WebOffice安装包(仅包含WebOffice控件) 2.WebOffice接口SDK 3.WebOffice网页接口调用例子 4.DES手写及签章系统(Office签章系统)试用版。 5.演示章及证书 6.点聚产品白皮书 WebOffice组件可以无缝集成点聚公司的电子签章和手写审批类产品。 点聚信息(http://www.dianju.com.cn)是国内专业的安全和文档中间件软件制造商。公司注重自主知识产权产品的研发,始终坚持技术为本、服务为先的原则,在电子印章、手写签批、安全版式文档和电子表单等领域处于国内领先水平。 点聚自主研发的版式文件系统独创性的将整个系统缩小为1M大小的组件,并在多项重要技术指标上领先PDF和其它版式格式。 点聚支持全系列手写及签章解决方案,可同时提供Office签章系统(Word/Excel/Wps)、网页签章系统、版式签章系统. 点聚是国内唯一一家同时拥有国密和军密资质的电子印章及安全文档产品提供商(国密、军密、公安部销售许可)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值