diff --git a/doc/docs/_sidebar.md b/doc/docs/_sidebar.md index 6ba62b4e..28e90257 100644 --- a/doc/docs/_sidebar.md +++ b/doc/docs/_sidebar.md @@ -1,29 +1,29 @@ -* [首页](/?t=1560480225362) +* [首页](/?t=1560912863431) * 开发文档 - * [快速体验](files/10010_快速体验.md?t=1560480225362) - * [项目接入到SOP](files/10011_项目接入到SOP.md?t=1560480225379) - * [新增接口](files/10020_新增接口.md?t=1560480225379) - * [业务参数校验](files/10030_业务参数校验.md?t=1560480225380) - * [错误处理](files/10040_错误处理.md?t=1560480225380) - * [编写文档](files/10041_编写文档.md?t=1560480225380) - * [接口交互详解](files/10050_接口交互详解.md?t=1560480225380) - * [easyopen支持](files/10070_easyopen支持.md?t=1560480225380) - * [使用签名校验工具](files/10080_使用签名校验工具.md?t=1560480225380) - * [ISV管理](files/10085_ISV管理.md?t=1560480225380) - * [自定义路由](files/10086_自定义路由.md?t=1560480225381) - * [路由授权](files/10090_路由授权.md?t=1560480225381) - * [接口限流](files/10092_接口限流.md?t=1560480225381) - * [监控日志](files/10093_监控日志.md?t=1560480225381) - * [SDK开发](files/10095_SDK开发.md?t=1560480225381) - * [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1560480225382) - * [应用授权](files/10097_应用授权.md?t=1560480225382) - * [更改数据节点名称](files/10099_更改数据节点名称.md?t=1560480225382) - * [对接前端](files/10100_对接前端.md?t=1560480225382) - * [自定义过滤器](files/10102_自定义过滤器.md?t=1560480225382) - * [文件上传](files/10104_文件上传.md?t=1560480225382) + * [快速体验](files/10010_快速体验.md?t=1560912863434) + * [项目接入到SOP](files/10011_项目接入到SOP.md?t=1560912863453) + * [新增接口](files/10020_新增接口.md?t=1560912863453) + * [业务参数校验](files/10030_业务参数校验.md?t=1560912863453) + * [错误处理](files/10040_错误处理.md?t=1560912863453) + * [编写文档](files/10041_编写文档.md?t=1560912863453) + * [接口交互详解](files/10050_接口交互详解.md?t=1560912863453) + * [easyopen支持](files/10070_easyopen支持.md?t=1560912863454) + * [使用签名校验工具](files/10080_使用签名校验工具.md?t=1560912863454) + * [ISV管理](files/10085_ISV管理.md?t=1560912863454) + * [自定义路由](files/10086_自定义路由.md?t=1560912863454) + * [路由授权](files/10090_路由授权.md?t=1560912863454) + * [接口限流](files/10092_接口限流.md?t=1560912863454) + * [监控日志](files/10093_监控日志.md?t=1560912863454) + * [SDK开发](files/10095_SDK开发.md?t=1560912863455) + * [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1560912863455) + * [应用授权](files/10097_应用授权.md?t=1560912863455) + * [更改数据节点名称](files/10099_更改数据节点名称.md?t=1560912863455) + * [对接前端](files/10100_对接前端.md?t=1560912863455) + * [自定义过滤器](files/10102_自定义过滤器.md?t=1560912863455) + * [文件上传](files/10104_文件上传.md?t=1560912863455) * 原理分析 - * [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1560480225382) - * [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1560480225382) - * [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1560480225383) - * [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1560480225383) - * [常见问题](files/90100_常见问题.md?t=1560480225383) + * [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1560912863455) + * [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1560912863456) + * [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1560912863456) + * [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1560912863456) + * [常见问题](files/90100_常见问题.md?t=1560912863456) diff --git a/doc/docs/files/10090_路由授权.md b/doc/docs/files/10090_路由授权.md index e68c1ec9..017dccc4 100644 --- a/doc/docs/files/10090_路由授权.md +++ b/doc/docs/files/10090_路由授权.md @@ -9,7 +9,7 @@ 假设把路由a,b,c分配给了`VIP角色`,那么具有VIP角色的ISV可以访问a,b,c三个路由。 -默认情况下,接口访问时公开的,ISV都能访问。如果要设置某个接口需要授权,在`@ApiMapping`注解中指定permission=true。 +默认情况下,接口访问时公开的,ISV都能访问。如果要设置某个接口访问权限,在`@ApiMapping`注解中指定permission=true。 如:`@ApiMapping(value = "permission.story.get", permission = true)`。这样该接口是需要经过授权给ISV才能访问的。 重启服务后,登录admin,服务管理-路由列表界面中,操作操作列会出现一个授权按钮,点击出现授权窗口,勾选对应的角色即可完成授权。 diff --git a/sop-gateway/src/main/resources/application-dev.yml b/sop-gateway/src/main/resources/application-dev.yml index cf1679e0..02b73a56 100644 --- a/sop-gateway/src/main/resources/application-dev.yml +++ b/sop-gateway/src/main/resources/application-dev.yml @@ -11,7 +11,7 @@ zuul: Servlet30WrapperFilter: pre: disable: true - # 不用改,如果要改,建议全局替换修改 + # 不用改,如果要改,请全局替换修改 secret: MZZOUSTua6LzApIWXCwEgbBmxSzpzC ribbon: diff --git a/sop-website/website-front/assets/css/global.css b/sop-website/website-front/assets/css/global.css index bf1ba9a9..30358ed4 100644 --- a/sop-website/website-front/assets/css/global.css +++ b/sop-website/website-front/assets/css/global.css @@ -463,4 +463,21 @@ body .site-demo-nav .layui-nav-item{line-height: 40px} } .site-out-up{-webkit-animation-duration: 3s; animation-duration: 3s; -webkit-animation-fill-mode: both; animation-fill-mode: both; -webkit-animation-name: site-anim-closeup; animation-name: site-anim-closeup; overflow: hidden;} +.layui-table-input { + display: block; + width: 100%; + padding-left: 10px; + + height: 28px; + line-height: 1.3; + line-height: 38px\9; + border-width: 1px; + border-style: solid; + background-color: #fff; + border-radius: 2px; + border-color: #e6e6e6; +} +.layui-table-cell .layui-input,td .layui-input,th .layui-input { + height: 28px; +} diff --git a/sop-website/website-front/assets/js/inputcache.js b/sop-website/website-front/assets/js/inputcache.js new file mode 100644 index 00000000..50dbdd94 --- /dev/null +++ b/sop-website/website-front/assets/js/inputcache.js @@ -0,0 +1,48 @@ +/** + * 自动保存输入框的值 + * @type {{init: InputCache.init}} + */ +var InputCache = { + init: function () { + var storage = window.localStorage; + if (storage) { + var dataKey = location.host; + + $('body').find('input[type="text"]').unbind().change(function () { + var id = this.id; + if (id) { + var data = getCache(); + data[id] = this.value; + setCache(data); + } + }); + + function getCache() { + var jsonStr = storage.getItem(dataKey); + if (!jsonStr) { + jsonStr = '{}'; + } + return JSON.parse(jsonStr); + } + + function setCache(jsonObj) { + var jsonStr = JSON.stringify(jsonObj); + storage.setItem(dataKey, jsonStr); + } + + // load data + var cache = getCache(); + var docu = document; + + for (var id in cache) { + var dom = docu.getElementById(id); + if (dom) { + var oldVal = dom.value; + var val = cache[id] || oldVal; + dom.value = val; + } + } + } + } +} + diff --git a/sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.js b/sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.js index c95dec07..01c4a7b3 100644 --- a/sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.js +++ b/sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.js @@ -75,10 +75,11 @@ layui.define(['layer', 'table'], function (exports) { count: param.data.length, limit: param.data.length }; - param.cols[0][param.treeColIndex].templet = function (d) { - var mId = d.id; - var mPid = d.pid; - var isDir = d.isParent; + var firstTemplet = param.firstTemplet; + param.cols[0][param.treeColIndex].templet = function (row) { + var mId = row.id; + var mPid = row.pid; + var isDir = row.isParent; var emptyNum = treetable.getEmptyNum(mPid, mData); var iconHtml = ''; for (var i = 0; i < emptyNum; i++) { @@ -92,7 +93,9 @@ layui.define(['layer', 'table'], function (exports) { iconHtml += '  '; var ttype = isDir ? 'dir' : 'file'; var vg = ''; - return vg + iconHtml + d[param.cols[0][param.treeColIndex].field] + '' + var field = param.cols[0][param.treeColIndex].field; + var cellVal = firstTemplet ? firstTemplet(row) : row[field]; + return vg + iconHtml + cellVal + '' }; param.done = function (res, curr, count) { diff --git a/sop-website/website-front/pages/doc/auth.html b/sop-website/website-front/pages/doc/auth.html index e3ef0fe5..429a7ade 100644 --- a/sop-website/website-front/pages/doc/auth.html +++ b/sop-website/website-front/pages/doc/auth.html @@ -59,7 +59,7 @@

第三方应用授权

- 概述 + 概述
@@ -72,7 +72,7 @@
- 快速接入 + 快速接入
diff --git a/sop-website/website-front/pages/doc/code.html b/sop-website/website-front/pages/doc/code.html index 37c3a3f4..5d7d8bf6 100644 --- a/sop-website/website-front/pages/doc/code.html +++ b/sop-website/website-front/pages/doc/code.html @@ -88,7 +88,7 @@
- 公共错误码 + 公共错误码
diff --git a/sop-website/website-front/pages/doc/doc.html b/sop-website/website-front/pages/doc/doc.html index a3ebdc6f..e1ff5c78 100644 --- a/sop-website/website-front/pages/doc/doc.html +++ b/sop-website/website-front/pages/doc/doc.html @@ -58,10 +58,14 @@ 开放平台
@@ -118,7 +122,7 @@
- 公共参数 + 公共参数
@@ -240,7 +244,7 @@
- 请求参数 + 请求参数
@@ -249,7 +253,7 @@
- 公共响应参数 + 公共响应参数
@@ -304,7 +308,7 @@
- 响应参数 + 响应参数
@@ -313,7 +317,7 @@
- 响应示例 + 响应示例
@@ -323,7 +327,7 @@
- 异常示例 + 异常示例
@@ -344,6 +348,8 @@ - + + + \ No newline at end of file diff --git a/sop-website/website-front/pages/doc/doc.js b/sop-website/website-front/pages/doc/doc.js index 601689f0..b6ec10f7 100644 --- a/sop-website/website-front/pages/doc/doc.js +++ b/sop-website/website-front/pages/doc/doc.js @@ -1,3 +1,9 @@ +var docEvent = { + bind: function (fn) { + this.onSelectDocItem = fn; + }, + onSelectDocItem: function () {} +} layui.config({ base: '../../assets/lib/layuiext/module/' }).extend({ @@ -22,6 +28,7 @@ layui.config({ } $('#moduleList').html(html.join('')); $('.url-prod').text(baseInfo.urlProd); + $('.url-sandbox').text(baseInfo.urlSandbox); form.render('select'); if (docInfoList && docInfoList.length > 0) { @@ -54,7 +61,7 @@ layui.config({ */ html.push('
  • '); - html.push(''+docItem.summary+'') + html.push('' + docItem['summary'] + '') } } @@ -77,119 +84,8 @@ layui.config({ function selectDocItem(nameVersion) { var docItem = docItemStore[nameVersion]; - $('.sop-name').text(docItem.name); - $('.sop-version').text(docItem.version); - $('.sop-summary').text(docItem.summary); - $('.sop-description').text(docItem.description || docItem.summary); - - createRequestParameter(docItem); - createResponseParameter(docItem); - createResponseCode(docItem); - - var $li = $('#docItemTree').find('li[nameversion="'+nameVersion+'"]'); - $li.addClass('layui-this').siblings().removeClass('layui-this'); - } - - function createRequestParameter(docItem) { - var data = buildTreeData(docItem.requestParameters); - createTreeTable('treeTableReq', data); - } - - function createResponseParameter(docItem) { - var data = buildTreeData(docItem.responseParameters); - createTreeTable('treeTableResp', data); - } - - function buildTreeData(parameters, parentId) { - var data = []; - parentId = parentId || 0; - for (var i = 0; i < parameters.length; i++) { - var parameter = parameters[i]; - parameter.id = parentId * 100 + (i + 1); - parameter.parentId = parentId; - data.push(parameter); - var refs = parameter.refs; - if (refs && refs.length > 0) { - var childData = buildTreeData(refs, parameter.id); - data = data.concat(childData); - } - } - return data; - } - - function createTreeTable(id, data) { - var el = '#' + id; - $(el).text(''); - treetable.render({ - elem: el, - treeColIndex: 0, - treeSpid: 0, - treeIdName: 'id', - treePidName: 'parentId', - treeDefaultClose: false, - treeLinkage: false, - data: data, - page: false, - cols: [[ - {field: 'name', title: '参数'} - ,{field: 'type', title: '类型', width: 80} - ,{field: 'required', title: '是否必填', width: 100, templet:function (row) { - return row.required ? '' : '否'; - }} - ,{field: 'maxLength', title: '最大长度', width: 100} - ,{field: 'description', title: '描述', width: 200} - ,{field: 'paramExample', title: '示例值', width: 200} - ]] - }); - } - - function createResponseCode(docItem) { - var method = docItem.name.replace(/\./g, '_'); - var responseParameters = docItem.responseParameters; - var bizResult = buildResult(responseParameters); - var json = '{\n' + - ' "'+method+'_response": {\n' + - ' "code": "10000",\n' + - ' "msg": "Success",\n' + - bizResult + - ' }' + - '}'; - json = formatJson(json); - $('#responseExampleJson').text(json); - - var errorJson = '{\n' + - ' "'+method+'_response": {\n' + - ' "code": "20000",\n' + - ' "msg": "Service is temporarily unavailable",\n' + - ' "sub_code": "isp.unknow-error",\n' + - ' "sub_msg": "服务暂不可用"\n' + - ' }' + - '}'; - errorJson = formatJson(errorJson); - $('#responseErrorJson').text(errorJson); - } - - function buildResult(parameters) { - var result = []; - for (var i = 0; i < parameters.length; i++) { - var parameter = parameters[i]; - result.push('\"'+parameter.name+'\": ' + buildExample(parameter)) - } - return result.join(","); - } - - function buildExample(parameter) { - var refs = parameter.refs; - if (refs) { - // {...} - var content = '{' + buildResult(refs) + '}'; - if (parameter.type == 'array') { - // [{...}] - content = '[' + content + ']'; - } - return content; - } else { - return '\"' + parameter.example + '\"'; + if (docItem) { + docEvent.onSelectDocItem(docItem, layui); } } diff --git a/sop-website/website-front/pages/doc/docEvent.js b/sop-website/website-front/pages/doc/docEvent.js new file mode 100644 index 00000000..d8173867 --- /dev/null +++ b/sop-website/website-front/pages/doc/docEvent.js @@ -0,0 +1,124 @@ + +docEvent.bind(function (docItem,layui) { + selectItem(docItem, layui); +}); + +var treetable; + +function selectItem(docItem, layui) { + var nameVersion = docItem.nameVersion; + treetable = treetable || layui.treetable; + $('.sop-name').text(docItem.name); + $('.sop-version').text(docItem.version); + $('.sop-summary').text(docItem['summary']); + $('.sop-description').text(docItem.description || docItem['summary']); + + createRequestParameter(docItem); + createResponseParameter(docItem); + createResponseCode(docItem); + + var $li = $('#docItemTree').find('li[nameversion="'+nameVersion+'"]'); + $li.addClass('layui-this').siblings().removeClass('layui-this'); +} + +function createRequestParameter(docItem) { + var data = buildTreeData(docItem.requestParameters); + createTreeTable('treeTableReq', data); +} + +function createResponseParameter(docItem) { + var data = buildTreeData(docItem.responseParameters); + createTreeTable('treeTableResp', data); +} + +function buildTreeData(parameters, parentId) { + var data = []; + parentId = parentId || 0; + for (var i = 0; i < parameters.length; i++) { + var parameter = parameters[i]; + parameter.id = parentId * 100 + (i + 1); + parameter.parentId = parentId; + data.push(parameter); + var refs = parameter.refs; + if (refs && refs.length > 0) { + var childData = buildTreeData(refs, parameter.id); + data = data.concat(childData); + } + } + return data; +} + +function createTreeTable(id, data) { + var el = '#' + id; + treetable.render({ + elem: el, + treeColIndex: 0, + treeSpid: 0, + treeIdName: 'id', + treePidName: 'parentId', + treeDefaultClose: false, + treeLinkage: false, + data: data, + page: false, + cols: [[ + {field: 'name', title: '参数'} + ,{field: 'type', title: '类型', width: 80} + ,{field: 'required', title: '是否必填', width: 100, templet:function (row) { + return row.required ? '' : '否'; + }} + ,{field: 'maxLength', title: '最大长度', width: 100} + ,{field: 'description', title: '描述', width: 200} + ,{field: 'paramExample', title: '示例值', width: 200} + ]] + }); +} + +function createResponseCode(docItem) { + var method = docItem.name.replace(/\./g, '_'); + var responseParameters = docItem.responseParameters; + var bizResult = buildResult(responseParameters); + var json = '{\n' + + ' "'+method+'_response": {\n' + + ' "code": "10000",\n' + + ' "msg": "Success",\n' + + bizResult + + ' }' + + '}'; + json = formatJson(json); + $('#responseExampleJson').text(json); + + var errorJson = '{\n' + + ' "'+method+'_response": {\n' + + ' "code": "20000",\n' + + ' "msg": "Service is temporarily unavailable",\n' + + ' "sub_code": "isp.unknow-error",\n' + + ' "sub_msg": "服务暂不可用"\n' + + ' }' + + '}'; + errorJson = formatJson(errorJson); + $('#responseErrorJson').text(errorJson); +} + +function buildResult(parameters) { + var result = []; + for (var i = 0; i < parameters.length; i++) { + var parameter = parameters[i]; + result.push('\"'+parameter.name+'\": ' + buildExample(parameter)) + } + return result.join(","); +} + +function buildExample(parameter) { + var refs = parameter.refs; + if (refs) { + // {...} + var content = '{' + buildResult(refs) + '}'; + if (parameter.type == 'array') { + // [{...}] + content = '[' + content + ']'; + } + return content; + } else { + return '\"' + parameter.example + '\"'; + } +} diff --git a/sop-website/website-front/pages/doc/sign.html b/sop-website/website-front/pages/doc/sign.html index e8755f59..1d14e57d 100644 --- a/sop-website/website-front/pages/doc/sign.html +++ b/sop-website/website-front/pages/doc/sign.html @@ -62,7 +62,7 @@

    如果未使用开放平台SDK,需要自行实现签名过程。

    - 如何签名 + 如何签名

    1.筛选并排序

    diff --git a/sop-website/website-front/pages/sandbox/sandbox.html b/sop-website/website-front/pages/sandbox/sandbox.html new file mode 100644 index 00000000..5428c532 --- /dev/null +++ b/sop-website/website-front/pages/sandbox/sandbox.html @@ -0,0 +1,142 @@ + + + + + 文档中心 + + + + + + + + + + + + + + + +

    + +
    + + +
    +
    +
    + +
    +
      + + +
    +
    + +
    +

    () +

    +
    +
    + +
    +

    参数配置

    + + + + + + + + + +
    AppId
    PrivateKey
    +
    + +
    +
    + 请求参数 +
    +
    +
    +
    + +
    + + + +
    +
    + + + + + + + + + + + + \ No newline at end of file diff --git a/sop-website/website-front/pages/sandbox/sandbox.js b/sop-website/website-front/pages/sandbox/sandbox.js new file mode 100644 index 00000000..386a7291 --- /dev/null +++ b/sop-website/website-front/pages/sandbox/sandbox.js @@ -0,0 +1,161 @@ + +docEvent.bind(function (docItem,layui) { + selectItem(docItem, layui); +}); + +layui.use('form', function(){ + var form = layui.form; + + //监听提交 + form.on('submit(formSend)', function(data){ + doTest(); + return false; + }); +}); + +var $body = $('body'); +var treetable; +var method,version; +var nameVersion; + +function selectItem(docItem, layui) { + resetResultDiv(); + nameVersion = docItem.nameVersion; + method = docItem.name; + version = docItem.version; + treetable = treetable || layui.treetable; + $('.sop-name').text(docItem.name); + $('.sop-version').text(docItem.version); + $('.sop-summary').text(docItem['summary']); + $('.sop-description').text(docItem.description || docItem['summary']); + + createRequestParameter(docItem); + + var $li = $('#docItemTree').find('li[nameversion="'+nameVersion+'"]'); + $li.addClass('layui-this').siblings().removeClass('layui-this'); + InputCache.init(); +} + +function createRequestParameter(docItem) { + var data = buildTreeData(docItem.requestParameters); + createTreeTable('treeTableReq', data); +} + +function buildTreeData(parameters, parentId) { + var data = []; + parentId = parentId || 0; + for (var i = 0; i < parameters.length; i++) { + var parameter = parameters[i]; + parameter.id = parentId * 100 + (i + 1); + parameter.parentId = parentId; + data.push(parameter); + var refs = parameter.refs; + if (refs && refs.length > 0) { + var childData = buildTreeData(refs, parameter.id); + data = data.concat(childData); + } + } + return data; +} + +function createTreeTable(id, data) { + var el = '#' + id; + treetable.render({ + elem: el, + treeColIndex: 0, + treeSpid: 0, + treeIdName: 'id', + treePidName: 'parentId', + treeDefaultClose: false, + treeLinkage: false, + data: data, + page: false, + firstTemplet: function (row) { + var required = row.required ? '* ' : ''; + return required + row.name; + }, + cols: [[ + {field: 'name', title: '参数',width: 200} + ,{field: 'val', title: '值', width: 300, templet:function (row) { + var id = nameVersion + '-' + row.name; + var requiredTxt = row.required ? 'required lay-verify="required"' : ''; + return !row.refs ? '' : ''; + }} + ,{field: 'description', title: '描述'} + ]] + }); +} + +function doTest() { + var data = { + appId: $('#appId').val(), + privateKey: $('#privateKey').val(), + method: method, + version: version + }; + var $inputs = $body.find('.test-input'); + var bizContent = {}; + $inputs.each(function () { + bizContent[this.name] = this.value; + }); + data.bizContent = JSON.stringify(bizContent); + $.ajax({ + url: SopConfig.url + '/sandbox/test' + , dataType: 'json' + , data: data + , method: 'post' + , success: function (resp) { + setReqInfo(resp); + showRespnfo(resp.apiResult); + } + , error: function (xhr,status,error) { + // {"timestamp":"2019-06-19 15:57:36","status":500,"error":"Internal Server Error","message":"appId不能为空","path":"/sandbox/test"} + var errorData = xhr.responseJSON; + if (errorData) { + setReqInfo(''); + showRespnfo(errorData.message); + } + } + }); +} + +function showRespnfo(info) { + var json = formatJson(info); + setRespInfo(json); + $('#resultDiv').show(); +} + +function setReqInfo(resp) { + var txt = ''; + if (resp) { + var html = []; + html.push('【请求参数】:' + resp.params); + html.push('【待签名内容】:' + resp.beforeSign); + html.push('【签名(sign)】:' + resp.sign); + txt = html.join('\r\n') + } + $('#req-info-result').val(txt); +} + +function setRespInfo(info) { + $('#resp-info-result').text(info); +} + +function resetResultDiv() { + setReqInfo({ + beforeSign: '', + params: '', + privateKey: '', + sign: '' + }); + setRespInfo(''); + $('.resp-info-content') + .addClass('layui-show') + .siblings().removeClass('layui-show'); + + $('.resp-info') + .addClass('layui-this') + .siblings().removeClass('layui-this'); + + $('#resultDiv').hide(); +} diff --git a/sop-website/website-server/pom.xml b/sop-website/website-server/pom.xml index 2a2f619a..b181b624 100644 --- a/sop-website/website-server/pom.xml +++ b/sop-website/website-server/pom.xml @@ -82,6 +82,11 @@ 1.2.56 compile + + org.apache.httpcomponents + httpclient + 4.5.6 + diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java new file mode 100644 index 00000000..97c8b0bb --- /dev/null +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java @@ -0,0 +1,146 @@ +package com.gitee.sop.websiteserver.controller; + +import com.gitee.sop.websiteserver.sign.AlipayApiException; +import com.gitee.sop.websiteserver.sign.AlipaySignature; +import lombok.Data; +import org.apache.commons.lang.StringUtils; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.apache.tomcat.util.http.fileupload.IOUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 沙箱环境代理类 + * @author tanghc + */ +@RestController +@RequestMapping("sandbox") +public class SandboxController { + + @Value("${api.url-sandbox}") + private String url; + + @RequestMapping("/test") + public SandboxResult proxy( + @RequestParam String appId + , @RequestParam String privateKey + , @RequestParam String method + , @RequestParam String version + , @RequestParam String bizContent) throws AlipayApiException { + + Assert.isTrue(StringUtils.isNotBlank(appId), "appId不能为空"); + Assert.isTrue(StringUtils.isNotBlank(privateKey), "privateKey不能为空"); + Assert.isTrue(StringUtils.isNotBlank(method), "method不能为空"); + + // 公共请求参数 + Map params = new HashMap(); + params.put("app_id", appId); + params.put("method", method); + params.put("format", "json"); + params.put("charset", "utf-8"); + params.put("sign_type", "RSA2"); + params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); + params.put("version", version); + + // 业务参数 + params.put("biz_content", bizContent); + + SandboxResult result = new SandboxResult(); + + result.params = buildParamQuery(params); + + String content = AlipaySignature.getSignContent(params); + result.beforeSign = content; + + String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8"); + result.sign = sign; + + params.put("sign", sign); + + String responseData = get(url, params);// 发送请求 + result.apiResult = responseData; + return result; + } + + @Data + public static class SandboxResult { + private String params; + private String beforeSign; + private String sign; + + private String apiResult; + } + + /** + * 发送get请求 + * @param url + * @return JSON或者字符串 + * @throws Exception + */ + public static String get(String url, Map params) { + CloseableHttpClient client = null; + CloseableHttpResponse response = null; + try{ + /** + * 创建一个httpclient对象 + */ + client = HttpClients.createDefault(); + + List nameValuePairs = params.entrySet() + .stream() + .map(entry -> new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue()))) + .collect(Collectors.toList()); + /** + * 包装成一个Entity对象 + */ + UrlEncodedFormEntity entity = new UrlEncodedFormEntity(nameValuePairs, "UTF-8"); + //参数转换为字符串 + String paramsStr = EntityUtils.toString(entity); + url = url + "?" + paramsStr; + /** + * 创建一个post对象 + */ + HttpGet get = new HttpGet(url); + + /** + * 执行post请求 + */ + response = client.execute(get); + /** + * 通过EntityUitls获取返回内容 + */ + return EntityUtils.toString(response.getEntity(),"UTF-8"); + }catch (Exception e){ + e.printStackTrace(); + }finally { + IOUtils.closeQuietly(client); + IOUtils.closeQuietly(response); + } + return null; + } + + protected String buildParamQuery(Map params) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : params.entrySet()) { + sb.append("&").append(entry.getKey()).append("=").append(entry.getValue()); + } + return sb.toString().substring(1); + } +} diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipayApiException.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipayApiException.java new file mode 100644 index 00000000..a6079c53 --- /dev/null +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipayApiException.java @@ -0,0 +1,49 @@ +/** + * Alipay.com Inc. + * Copyright (c) 2004-2012 All Rights Reserved. + */ +package com.gitee.sop.websiteserver.sign; + + +/** + * + * @author runzhi + */ +public class AlipayApiException extends Exception { + + private static final long serialVersionUID = -238091758285157331L; + + private String errCode; + private String errMsg; + + public AlipayApiException() { + super(); + } + + public AlipayApiException(String message, Throwable cause) { + super(message, cause); + } + + public AlipayApiException(String message) { + super(message); + } + + public AlipayApiException(Throwable cause) { + super(cause); + } + + public AlipayApiException(String errCode, String errMsg) { + super(errCode + ":" + errMsg); + this.errCode = errCode; + this.errMsg = errMsg; + } + + public String getErrCode() { + return this.errCode; + } + + public String getErrMsg() { + return this.errMsg; + } + +} \ No newline at end of file diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipayConstants.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipayConstants.java new file mode 100644 index 00000000..9efd0794 --- /dev/null +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipayConstants.java @@ -0,0 +1,97 @@ +/** + * Alipay.com Inc. + * Copyright (c) 2004-2012 All Rights Reserved. + */ +package com.gitee.sop.websiteserver.sign; + +/** + * + * @author runzhi + */ +public class AlipayConstants { + + public static final String SIGN_TYPE = "sign_type"; + + public static final String SIGN_TYPE_RSA = "RSA"; + + /** + * sha256WithRsa 算法请求类型 + */ + public static final String SIGN_TYPE_RSA2 = "RSA2"; + + public static final String SIGN_ALGORITHMS = "SHA1WithRSA"; + + public static final String SIGN_SHA256RSA_ALGORITHMS = "SHA256WithRSA"; + + public static final String ENCRYPT_TYPE_AES = "AES"; + + public static final String APP_ID = "app_id"; + + public static final String FORMAT = "format"; + + public static final String METHOD = "method"; + + public static final String TIMESTAMP = "timestamp"; + + public static final String VERSION = "version"; + + public static final String SIGN = "sign"; + + public static final String ALIPAY_SDK = "alipay_sdk"; + + public static final String ACCESS_TOKEN = "auth_token"; + + public static final String APP_AUTH_TOKEN = "app_auth_token"; + + public static final String TERMINAL_TYPE = "terminal_type"; + + public static final String TERMINAL_INFO = "terminal_info"; + + public static final String CHARSET = "charset"; + + public static final String NOTIFY_URL = "notify_url"; + + public static final String RETURN_URL = "return_url"; + + public static final String ENCRYPT_TYPE = "encrypt_type"; + + //-----===-------/// + + public static final String BIZ_CONTENT_KEY = "biz_content"; + + /** 默认时间格式 **/ + public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; + + /** Date默认时区 **/ + public static final String DATE_TIMEZONE = "GMT+8"; + + /** UTF-8字符集 **/ + public static final String CHARSET_UTF8 = "UTF-8"; + + /** GBK字符集 **/ + public static final String CHARSET_GBK = "GBK"; + + /** JSON 应格式 */ + public static final String FORMAT_JSON = "json"; + + /** XML 应格式 */ + public static final String FORMAT_XML = "xml"; + + /** SDK版本号 */ + public static final String SDK_VERSION = "alipay-sdk-java-3.6.0.ALL"; + + public static final String PROD_CODE = "prod_code"; + + /** 老版本失败节点 */ + public static final String ERROR_RESPONSE = "error_response"; + + /** 新版本节点后缀 */ + public static final String RESPONSE_SUFFIX = "_response"; + + /** 加密后XML返回报文的节点名字 */ + public static final String RESPONSE_XML_ENCRYPT_NODE_NAME = "response_encrypted"; + + /** 批量请求id **/ + public static final String BATCH_REQUEST_ID = "batch_request_id"; + +} diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipaySignature.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipaySignature.java new file mode 100644 index 00000000..c02240af --- /dev/null +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipaySignature.java @@ -0,0 +1,611 @@ +/** + * Alipay.com Inc. + * Copyright (c) 2004-2012 All Rights Reserved. + */ +package com.gitee.sop.websiteserver.sign; + + +import org.apache.commons.codec.binary.Base64; + +import javax.crypto.Cipher; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * + * @author runzhi + */ +public class AlipaySignature { + + /** RSA最大加密明文大小 */ + private static final int MAX_ENCRYPT_BLOCK = 117; + + /** RSA最大解密密文大小 */ + private static final int MAX_DECRYPT_BLOCK = 128; + + + + /** + * + * @param sortedParams + * @return + */ + public static String getSignContent(Map sortedParams) { + StringBuffer content = new StringBuffer(); + List keys = new ArrayList(sortedParams.keySet()); + Collections.sort(keys); + int index = 0; + for (int i = 0; i < keys.size(); i++) { + String key = keys.get(i); + String value = sortedParams.get(key); + if (StringUtils.areNotEmpty(key, value)) { + content.append((index == 0 ? "" : "&") + key + "=" + value); + index++; + } + } + return content.toString(); + } + + /** + * rsa内容签名 + * + * @param content + * @param privateKey + * @param charset + * @return + * @throws AlipayApiException + */ + public static String rsaSign(String content, String privateKey, String charset, + String signType) throws AlipayApiException { + + if (AlipayConstants.SIGN_TYPE_RSA.equals(signType)) { + + return rsaSign(content, privateKey, charset); + } else if (AlipayConstants.SIGN_TYPE_RSA2.equals(signType)) { + + return rsa256Sign(content, privateKey, charset); + } else { + + throw new AlipayApiException("Sign Type is Not Support : signType=" + signType); + } + + } + + /** + * sha256WithRsa 加签 + * + * @param content + * @param privateKey + * @param charset + * @return + * @throws AlipayApiException + */ + public static String rsa256Sign(String content, String privateKey, + String charset) throws AlipayApiException { + + try { + PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA, + new ByteArrayInputStream(privateKey.getBytes())); + + java.security.Signature signature = java.security.Signature + .getInstance(AlipayConstants.SIGN_SHA256RSA_ALGORITHMS); + + signature.initSign(priKey); + + if (StringUtils.isEmpty(charset)) { + signature.update(content.getBytes()); + } else { + signature.update(content.getBytes(charset)); + } + + byte[] signed = signature.sign(); + + return new String(Base64.encodeBase64(signed)); + } catch (Exception e) { + throw new AlipayApiException("RSAcontent = " + content + "; charset = " + charset, e); + } + + } + + /** + * sha1WithRsa 加签 + * + * @param content + * @param privateKey + * @param charset + * @return + * @throws AlipayApiException + */ + public static String rsaSign(String content, String privateKey, + String charset) throws AlipayApiException { + try { + PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA, + new ByteArrayInputStream(privateKey.getBytes())); + + java.security.Signature signature = java.security.Signature + .getInstance(AlipayConstants.SIGN_ALGORITHMS); + + signature.initSign(priKey); + + if (StringUtils.isEmpty(charset)) { + signature.update(content.getBytes()); + } else { + signature.update(content.getBytes(charset)); + } + + byte[] signed = signature.sign(); + + return new String(Base64.encodeBase64(signed)); + } catch (InvalidKeySpecException ie) { + throw new AlipayApiException("RSA私钥格式不正确,请检查是否正确配置了PKCS8格式的私钥", ie); + } catch (Exception e) { + throw new AlipayApiException("RSAcontent = " + content + "; charset = " + charset, e); + } + } + + public static String rsaSign(Map params, String privateKey, + String charset) throws AlipayApiException { + String signContent = getSignContent(params); + + return rsaSign(signContent, privateKey, charset); + + } + + public static PrivateKey getPrivateKeyFromPKCS8(String algorithm, + InputStream ins) throws Exception { + if (ins == null || StringUtils.isEmpty(algorithm)) { + return null; + } + + KeyFactory keyFactory = KeyFactory.getInstance(algorithm); + + byte[] encodedKey = StreamUtil.readText(ins).getBytes(); + + encodedKey = Base64.decodeBase64(encodedKey); + + return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey)); + } + + public static String getSignCheckContentV1(Map params) { + if (params == null) { + return null; + } + + params.remove("sign"); + params.remove("sign_type"); + + StringBuffer content = new StringBuffer(); + List keys = new ArrayList(params.keySet()); + Collections.sort(keys); + + for (int i = 0; i < keys.size(); i++) { + String key = keys.get(i); + String value = params.get(key); + content.append((i == 0 ? "" : "&") + key + "=" + value); + } + + return content.toString(); + } + + public static String getSignCheckContentV2(Map params) { + if (params == null) { + return null; + } + + params.remove("sign"); + + StringBuffer content = new StringBuffer(); + List keys = new ArrayList(params.keySet()); + Collections.sort(keys); + + for (int i = 0; i < keys.size(); i++) { + String key = keys.get(i); + String value = params.get(key); + content.append((i == 0 ? "" : "&") + key + "=" + value); + } + + return content.toString(); + } + + public static boolean rsaCheckV1(Map params, String publicKey, + String charset) throws AlipayApiException { + String sign = params.get("sign"); + String content = getSignCheckContentV1(params); + + return rsaCheckContent(content, sign, publicKey, charset); + } + + public static boolean rsaCheckV1(Map params, String publicKey, + String charset,String signType) throws AlipayApiException { + String sign = params.get("sign"); + String content = getSignCheckContentV1(params); + + return rsaCheck(content, sign, publicKey, charset,signType); + } + + public static boolean rsaCheckV2(Map params, String publicKey, + String charset) throws AlipayApiException { + String sign = params.get("sign"); + String content = getSignCheckContentV2(params); + + return rsaCheckContent(content, sign, publicKey, charset); + } + + public static boolean rsaCheckV2(Map params, String publicKey, + String charset,String signType) throws AlipayApiException { + String sign = params.get("sign"); + String content = getSignCheckContentV2(params); + + return rsaCheck(content, sign, publicKey, charset,signType); + } + + public static boolean rsaCheck(String content, String sign, String publicKey, String charset, + String signType) throws AlipayApiException { + + if (AlipayConstants.SIGN_TYPE_RSA.equals(signType)) { + + return rsaCheckContent(content, sign, publicKey, charset); + + } else if (AlipayConstants.SIGN_TYPE_RSA2.equals(signType)) { + + return rsa256CheckContent(content, sign, publicKey, charset); + + } else { + + throw new AlipayApiException("Sign Type is Not Support : signType=" + signType); + } + + } + + public static boolean rsa256CheckContent(String content, String sign, String publicKey, + String charset) throws AlipayApiException { + try { + PublicKey pubKey = getPublicKeyFromX509("RSA", + new ByteArrayInputStream(publicKey.getBytes())); + + java.security.Signature signature = java.security.Signature + .getInstance(AlipayConstants.SIGN_SHA256RSA_ALGORITHMS); + + signature.initVerify(pubKey); + + if (StringUtils.isEmpty(charset)) { + signature.update(content.getBytes()); + } else { + signature.update(content.getBytes(charset)); + } + + return signature.verify(Base64.decodeBase64(sign.getBytes())); + } catch (Exception e) { + throw new AlipayApiException( + "RSAcontent = " + content + ",sign=" + sign + ",charset = " + charset, e); + } + } + + public static boolean rsaCheckContent(String content, String sign, String publicKey, + String charset) throws AlipayApiException { + try { + PublicKey pubKey = getPublicKeyFromX509("RSA", + new ByteArrayInputStream(publicKey.getBytes())); + + java.security.Signature signature = java.security.Signature + .getInstance(AlipayConstants.SIGN_ALGORITHMS); + + signature.initVerify(pubKey); + + if (StringUtils.isEmpty(charset)) { + signature.update(content.getBytes()); + } else { + signature.update(content.getBytes(charset)); + } + + return signature.verify(Base64.decodeBase64(sign.getBytes())); + } catch (Exception e) { + throw new AlipayApiException( + "RSAcontent = " + content + ",sign=" + sign + ",charset = " + charset, e); + } + } + + public static PublicKey getPublicKeyFromX509(String algorithm, + InputStream ins) throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance(algorithm); + + StringWriter writer = new StringWriter(); + StreamUtil.io(new InputStreamReader(ins), writer); + + byte[] encodedKey = writer.toString().getBytes(); + + encodedKey = Base64.decodeBase64(encodedKey); + + return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey)); + } + + /** + * 验签并解密 + *

    + * 目前适用于公众号
    + * params参数示例: + *
    { + *
    biz_content=M0qGiGz+8kIpxe8aF4geWJdBn0aBTuJRQItLHo9R7o5JGhpic/MIUjvXo2BLB++BbkSq2OsJCEQFDZ0zK5AJYwvBgeRX30gvEj6eXqXRt16/IkB9HzAccEqKmRHrZJ7PjQWE0KfvDAHsJqFIeMvEYk1Zei2QkwSQPlso7K0oheo/iT+HYE8aTATnkqD/ByD9iNDtGg38pCa2xnnns63abKsKoV8h0DfHWgPH62urGY7Pye3r9FCOXA2Ykm8X4/Bl1bWFN/PFCEJHWe/HXj8KJKjWMO6ttsoV0xRGfeyUO8agu6t587Dl5ux5zD/s8Lbg5QXygaOwo3Fz1G8EqmGhi4+soEIQb8DBYanQOS3X+m46tVqBGMw8Oe+hsyIMpsjwF4HaPKMr37zpW3fe7xOMuimbZ0wq53YP/jhQv6XWodjT3mL0H5ACqcsSn727B5ztquzCPiwrqyjUHjJQQefFTzOse8snaWNQTUsQS7aLsHq0FveGpSBYORyA90qPdiTjXIkVP7mAiYiAIWW9pCEC7F3XtViKTZ8FRMM9ySicfuAlf3jtap6v2KPMtQv70X+hlmzO/IXB6W0Ep8DovkF5rB4r/BJYJLw/6AS0LZM9w5JfnAZhfGM2rKzpfNsgpOgEZS1WleG4I2hoQC0nxg9IcP0Hs+nWIPkEUcYNaiXqeBc=, + *
    sign=rlqgA8O+RzHBVYLyHmrbODVSANWPXf3pSrr82OCO/bm3upZiXSYrX5fZr6UBmG6BZRAydEyTIguEW6VRuAKjnaO/sOiR9BsSrOdXbD5Rhos/Xt7/mGUWbTOt/F+3W0/XLuDNmuYg1yIC/6hzkg44kgtdSTsQbOC9gWM7ayB4J4c=, + * sign_type=RSA, + *
    charset=UTF-8 + *
    } + *

    + * @param params + * @param alipayPublicKey 支付宝公钥 + * @param cusPrivateKey 商户私钥 + * @param isCheckSign 是否验签 + * @param isDecrypt 是否解密 + * @return 解密后明文,验签失败则异常抛出 + * @throws AlipayApiException + */ + public static String checkSignAndDecrypt(Map params, String alipayPublicKey, + String cusPrivateKey, boolean isCheckSign, + boolean isDecrypt) throws AlipayApiException { + String charset = params.get("charset"); + String bizContent = params.get("biz_content"); + if (isCheckSign) { + if (!rsaCheckV2(params, alipayPublicKey, charset)) { + throw new AlipayApiException("rsaCheck failure:rsaParams=" + params); + } + } + + if (isDecrypt) { + return rsaDecrypt(bizContent, cusPrivateKey, charset); + } + + return bizContent; + } + + /** + * 验签并解密 + *

    + * 目前适用于公众号
    + * params参数示例: + *
    { + *
    biz_content=M0qGiGz+8kIpxe8aF4geWJdBn0aBTuJRQItLHo9R7o5JGhpic/MIUjvXo2BLB++BbkSq2OsJCEQFDZ0zK5AJYwvBgeRX30gvEj6eXqXRt16/IkB9HzAccEqKmRHrZJ7PjQWE0KfvDAHsJqFIeMvEYk1Zei2QkwSQPlso7K0oheo/iT+HYE8aTATnkqD/ByD9iNDtGg38pCa2xnnns63abKsKoV8h0DfHWgPH62urGY7Pye3r9FCOXA2Ykm8X4/Bl1bWFN/PFCEJHWe/HXj8KJKjWMO6ttsoV0xRGfeyUO8agu6t587Dl5ux5zD/s8Lbg5QXygaOwo3Fz1G8EqmGhi4+soEIQb8DBYanQOS3X+m46tVqBGMw8Oe+hsyIMpsjwF4HaPKMr37zpW3fe7xOMuimbZ0wq53YP/jhQv6XWodjT3mL0H5ACqcsSn727B5ztquzCPiwrqyjUHjJQQefFTzOse8snaWNQTUsQS7aLsHq0FveGpSBYORyA90qPdiTjXIkVP7mAiYiAIWW9pCEC7F3XtViKTZ8FRMM9ySicfuAlf3jtap6v2KPMtQv70X+hlmzO/IXB6W0Ep8DovkF5rB4r/BJYJLw/6AS0LZM9w5JfnAZhfGM2rKzpfNsgpOgEZS1WleG4I2hoQC0nxg9IcP0Hs+nWIPkEUcYNaiXqeBc=, + *
    sign=rlqgA8O+RzHBVYLyHmrbODVSANWPXf3pSrr82OCO/bm3upZiXSYrX5fZr6UBmG6BZRAydEyTIguEW6VRuAKjnaO/sOiR9BsSrOdXbD5Rhos/Xt7/mGUWbTOt/F+3W0/XLuDNmuYg1yIC/6hzkg44kgtdSTsQbOC9gWM7ayB4J4c=, + * sign_type=RSA, + *
    charset=UTF-8 + *
    } + *

    + * @param params + * @param alipayPublicKey 支付宝公钥 + * @param cusPrivateKey 商户私钥 + * @param isCheckSign 是否验签 + * @param isDecrypt 是否解密 + * @return 解密后明文,验签失败则异常抛出 + * @throws AlipayApiException + */ + public static String checkSignAndDecrypt(Map params, String alipayPublicKey, + String cusPrivateKey, boolean isCheckSign, + boolean isDecrypt, String signType) throws AlipayApiException { + String charset = params.get("charset"); + String bizContent = params.get("biz_content"); + if (isCheckSign) { + if (!rsaCheckV2(params, alipayPublicKey, charset,signType)) { + throw new AlipayApiException("rsaCheck failure:rsaParams=" + params); + } + } + + if (isDecrypt) { + return rsaDecrypt(bizContent, cusPrivateKey, charset); + } + + return bizContent; + } + + /** + * 加密并签名
    + * 目前适用于公众号 + * @param bizContent 待加密、签名内容 + * @param alipayPublicKey 支付宝公钥 + * @param cusPrivateKey 商户私钥 + * @param charset 字符集,如UTF-8, GBK, GB2312 + * @param isEncrypt 是否加密,true-加密 false-不加密 + * @param isSign 是否签名,true-签名 false-不签名 + * @return 加密、签名后xml内容字符串 + *

    + * 返回示例: + * + * 密文 + * RSA + * sign + * RSA + * + *

    + * @throws AlipayApiException + */ + public static String encryptAndSign(String bizContent, String alipayPublicKey, + String cusPrivateKey, String charset, boolean isEncrypt, + boolean isSign) throws AlipayApiException { + StringBuilder sb = new StringBuilder(); + if (StringUtils.isEmpty(charset)) { + charset = AlipayConstants.CHARSET_GBK; + } + sb.append(""); + if (isEncrypt) {// 加密 + sb.append(""); + String encrypted = rsaEncrypt(bizContent, alipayPublicKey, charset); + sb.append("" + encrypted + ""); + sb.append("RSA"); + if (isSign) { + String sign = rsaSign(encrypted, cusPrivateKey, charset); + sb.append("" + sign + ""); + sb.append("RSA"); + } + sb.append(""); + } else if (isSign) {// 不加密,但需要签名 + sb.append(""); + sb.append("" + bizContent + ""); + String sign = rsaSign(bizContent, cusPrivateKey, charset); + sb.append("" + sign + ""); + sb.append("RSA"); + sb.append(""); + } else {// 不加密,不加签 + sb.append(bizContent); + } + return sb.toString(); + } + + /** + * 加密并签名
    + * 目前适用于公众号 + * @param bizContent 待加密、签名内容 + * @param alipayPublicKey 支付宝公钥 + * @param cusPrivateKey 商户私钥 + * @param charset 字符集,如UTF-8, GBK, GB2312 + * @param isEncrypt 是否加密,true-加密 false-不加密 + * @param isSign 是否签名,true-签名 false-不签名 + * @return 加密、签名后xml内容字符串 + *

    + * 返回示例: + * + * 密文 + * RSA + * sign + * RSA + * + *

    + * @throws AlipayApiException + */ + public static String encryptAndSign(String bizContent, String alipayPublicKey, + String cusPrivateKey, String charset, boolean isEncrypt, + boolean isSign,String signType) throws AlipayApiException { + StringBuilder sb = new StringBuilder(); + if (StringUtils.isEmpty(charset)) { + charset = AlipayConstants.CHARSET_GBK; + } + sb.append(""); + if (isEncrypt) {// 加密 + sb.append(""); + String encrypted = rsaEncrypt(bizContent, alipayPublicKey, charset); + sb.append("" + encrypted + ""); + sb.append("RSA"); + if (isSign) { + String sign = rsaSign(encrypted, cusPrivateKey, charset, signType); + sb.append("" + sign + ""); + sb.append(""); + sb.append(signType); + sb.append(""); + } + sb.append(""); + } else if (isSign) {// 不加密,但需要签名 + sb.append(""); + sb.append("" + bizContent + ""); + String sign = rsaSign(bizContent, cusPrivateKey, charset, signType); + sb.append("" + sign + ""); + sb.append(""); + sb.append(signType); + sb.append(""); + sb.append(""); + } else {// 不加密,不加签 + sb.append(bizContent); + } + return sb.toString(); + } + + /** + * 公钥加密 + * + * @param content 待加密内容 + * @param publicKey 公钥 + * @param charset 字符集,如UTF-8, GBK, GB2312 + * @return 密文内容 + * @throws AlipayApiException + */ + public static String rsaEncrypt(String content, String publicKey, + String charset) throws AlipayApiException { + try { + PublicKey pubKey = getPublicKeyFromX509(AlipayConstants.SIGN_TYPE_RSA, + new ByteArrayInputStream(publicKey.getBytes())); + Cipher cipher = Cipher.getInstance(AlipayConstants.SIGN_TYPE_RSA); + cipher.init(Cipher.ENCRYPT_MODE, pubKey); + byte[] data = StringUtils.isEmpty(charset) ? content.getBytes() + : content.getBytes(charset); + int inputLen = data.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // 对数据分段加密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { + cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); + } else { + cache = cipher.doFinal(data, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_ENCRYPT_BLOCK; + } + byte[] encryptedData = Base64.encodeBase64(out.toByteArray()); + out.close(); + + return StringUtils.isEmpty(charset) ? new String(encryptedData) + : new String(encryptedData, charset); + } catch (Exception e) { + throw new AlipayApiException("EncryptContent = " + content + ",charset = " + charset, + e); + } + } + + /** + * 私钥解密 + * + * @param content 待解密内容 + * @param privateKey 私钥 + * @param charset 字符集,如UTF-8, GBK, GB2312 + * @return 明文内容 + * @throws AlipayApiException + */ + public static String rsaDecrypt(String content, String privateKey, + String charset) throws AlipayApiException { + try { + PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA, + new ByteArrayInputStream(privateKey.getBytes())); + Cipher cipher = Cipher.getInstance(AlipayConstants.SIGN_TYPE_RSA); + cipher.init(Cipher.DECRYPT_MODE, priKey); + byte[] encryptedData = StringUtils.isEmpty(charset) + ? Base64.decodeBase64(content.getBytes()) + : Base64.decodeBase64(content.getBytes(charset)); + int inputLen = encryptedData.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // 对数据分段解密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_DECRYPT_BLOCK) { + cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK); + } else { + cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_DECRYPT_BLOCK; + } + byte[] decryptedData = out.toByteArray(); + out.close(); + + return StringUtils.isEmpty(charset) ? new String(decryptedData) + : new String(decryptedData, charset); + } catch (Exception e) { + throw new AlipayApiException("EncodeContent = " + content + ",charset = " + charset, e); + } + } + +} diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/StreamUtil.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/StreamUtil.java new file mode 100644 index 00000000..437a8441 --- /dev/null +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/StreamUtil.java @@ -0,0 +1,139 @@ +/** + * Alipay.com Inc. + * Copyright (c) 2004-2012 All Rights Reserved. + */ +package com.gitee.sop.websiteserver.sign; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; + +/** + * + * @author runzhi + */ +public class StreamUtil { + private static final int DEFAULT_BUFFER_SIZE = 8192; + + public static void io(InputStream in, OutputStream out) throws IOException { + io(in, out, -1); + } + + public static void io(InputStream in, OutputStream out, int bufferSize) throws IOException { + if (bufferSize == -1) { + bufferSize = DEFAULT_BUFFER_SIZE; + } + + byte[] buffer = new byte[bufferSize]; + int amount; + + while ((amount = in.read(buffer)) >= 0) { + out.write(buffer, 0, amount); + } + } + + public static void io(Reader in, Writer out) throws IOException { + io(in, out, -1); + } + + public static void io(Reader in, Writer out, int bufferSize) throws IOException { + if (bufferSize == -1) { + bufferSize = DEFAULT_BUFFER_SIZE >> 1; + } + + char[] buffer = new char[bufferSize]; + int amount; + + while ((amount = in.read(buffer)) >= 0) { + out.write(buffer, 0, amount); + } + } + + public static OutputStream synchronizedOutputStream(OutputStream out) { + return new SynchronizedOutputStream(out); + } + + public static OutputStream synchronizedOutputStream(OutputStream out, Object lock) { + return new SynchronizedOutputStream(out, lock); + } + + public static String readText(InputStream in) throws IOException { + return readText(in, null, -1); + } + + public static String readText(InputStream in, String encoding) throws IOException { + return readText(in, encoding, -1); + } + + public static String readText(InputStream in, String encoding, int bufferSize) + throws IOException { + Reader reader = (encoding == null) ? new InputStreamReader(in) : new InputStreamReader(in, + encoding); + + return readText(reader, bufferSize); + } + + public static String readText(Reader reader) throws IOException { + return readText(reader, -1); + } + + public static String readText(Reader reader, int bufferSize) throws IOException { + StringWriter writer = new StringWriter(); + + io(reader, writer, bufferSize); + return writer.toString(); + } + + private static class SynchronizedOutputStream extends OutputStream { + private OutputStream out; + private Object lock; + + SynchronizedOutputStream(OutputStream out) { + this(out, out); + } + + SynchronizedOutputStream(OutputStream out, Object lock) { + this.out = out; + this.lock = lock; + } + + @Override + public void write(int datum) throws IOException { + synchronized (lock) { + out.write(datum); + } + } + + @Override + public void write(byte[] data) throws IOException { + synchronized (lock) { + out.write(data); + } + } + + @Override + public void write(byte[] data, int offset, int length) throws IOException { + synchronized (lock) { + out.write(data, offset, length); + } + } + + @Override + public void flush() throws IOException { + synchronized (lock) { + out.flush(); + } + } + + @Override + public void close() throws IOException { + synchronized (lock) { + out.close(); + } + } + } +} diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/StringUtils.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/StringUtils.java new file mode 100644 index 00000000..d089b7b5 --- /dev/null +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/StringUtils.java @@ -0,0 +1,171 @@ +package com.gitee.sop.websiteserver.sign; + +/** + * 字符串工具类。 + * + * @author carver.gu + * @since 1.0, Sep 12, 2009 + */ +public abstract class StringUtils { + + private StringUtils() {} + + /** + * 检查指定的字符串是否为空。 + *
      + *
    • SysUtils.isEmpty(null) = true
    • + *
    • SysUtils.isEmpty("") = true
    • + *
    • SysUtils.isEmpty(" ") = true
    • + *
    • SysUtils.isEmpty("abc") = false
    • + *
    + * + * @param value 待检查的字符串 + * @return true/false + */ + public static boolean isEmpty(String value) { + int strLen; + if (value == null || (strLen = value.length()) == 0) { + return true; + } + for (int i = 0; i < strLen; i++) { + if ((Character.isWhitespace(value.charAt(i)) == false)) { + return false; + } + } + return true; + } + + /** + * 检查对象是否为数字型字符串,包含负数开头的。 + */ + public static boolean isNumeric(Object obj) { + if (obj == null) { + return false; + } + char[] chars = obj.toString().toCharArray(); + int length = chars.length; + if(length < 1) { + return false; + } + + int i = 0; + if(length > 1 && chars[0] == '-') { + i = 1; + } + + for (; i < length; i++) { + if (!Character.isDigit(chars[i])) { + return false; + } + } + return true; + } + + /** + * 检查指定的字符串列表是否不为空。 + */ + public static boolean areNotEmpty(String... values) { + boolean result = true; + if (values == null || values.length == 0) { + result = false; + } else { + for (String value : values) { + result &= !isEmpty(value); + } + } + return result; + } + + /** + * 把通用字符编码的字符串转化为汉字编码。 + */ + public static String unicodeToChinese(String unicode) { + StringBuilder out = new StringBuilder(); + if (!isEmpty(unicode)) { + for (int i = 0; i < unicode.length(); i++) { + out.append(unicode.charAt(i)); + } + } + return out.toString(); + } + + /** + * 过滤不可见字符 + */ + public static String stripNonValidXMLCharacters(String input) { + if (input == null || ("".equals(input))) { + return ""; + } + StringBuilder out = new StringBuilder(); + char current; + for (int i = 0; i < input.length(); i++) { + current = input.charAt(i); + if ((current == 0x9) || (current == 0xA) || (current == 0xD) + || ((current >= 0x20) && (current <= 0xD7FF)) + || ((current >= 0xE000) && (current <= 0xFFFD)) + || ((current >= 0x10000) && (current <= 0x10FFFF))) { + out.append(current); + } + } + return out.toString(); + } + + public static String leftPad(String str, int size, char padChar) { + if (str == null) { + return null; + } else { + int pads = size - str.length(); + if (pads <= 0) { + return str; + } else { + return pads > 8192 ? leftPad(str, size, String.valueOf(padChar)) : padding(pads, padChar).concat(str); + } + } + } + + public static String leftPad(String str, int size, String padStr) { + if (str == null) { + return null; + } else { + if (isEmpty(padStr)) { + padStr = " "; + } + + int padLen = padStr.length(); + int strLen = str.length(); + int pads = size - strLen; + if (pads <= 0) { + return str; + } else if (padLen == 1 && pads <= 8192) { + return leftPad(str, size, padStr.charAt(0)); + } else if (pads == padLen) { + return padStr.concat(str); + } else if (pads < padLen) { + return padStr.substring(0, pads).concat(str); + } else { + char[] padding = new char[pads]; + char[] padChars = padStr.toCharArray(); + + for(int i = 0; i < pads; ++i) { + padding[i] = padChars[i % padLen]; + } + + return (new String(padding)).concat(str); + } + } + } + + private static String padding(int repeat, char padChar) throws IndexOutOfBoundsException { + if (repeat < 0) { + throw new IndexOutOfBoundsException("Cannot pad a negative amount: " + repeat); + } else { + char[] buf = new char[repeat]; + + for(int i = 0; i < buf.length; ++i) { + buf[i] = padChar; + } + + return new String(buf); + } + } +} diff --git a/sop-website/website-server/src/main/resources/application-dev.yml b/sop-website/website-server/src/main/resources/application-dev.yml index b6e0b7e0..3ec0b465 100644 --- a/sop-website/website-server/src/main/resources/application-dev.yml +++ b/sop-website/website-server/src/main/resources/application-dev.yml @@ -19,6 +19,8 @@ spring: api: # 测试地址 url-test: http://api-test.yourdomain.com/api + # 沙箱环境 + url-sandbox: http://localhost:8081/api # 正式地址 url-prod: http://open.yourdomain.com/api pwd: doc#123 \ No newline at end of file