添加沙箱环境

1.x
tanghc 6 years ago
parent 9f2e659523
commit 326edf661e
  1. 54
      doc/docs/_sidebar.md
  2. 2
      doc/docs/files/10090_路由授权.md
  3. 2
      sop-gateway/src/main/resources/application-dev.yml
  4. 17
      sop-website/website-front/assets/css/global.css
  5. 48
      sop-website/website-front/assets/js/inputcache.js
  6. 13
      sop-website/website-front/assets/lib/layuiext/module/treetable-lay/treetable.js
  7. 4
      sop-website/website-front/pages/doc/auth.html
  8. 2
      sop-website/website-front/pages/doc/code.html
  9. 22
      sop-website/website-front/pages/doc/doc.html
  10. 124
      sop-website/website-front/pages/doc/doc.js
  11. 124
      sop-website/website-front/pages/doc/docEvent.js
  12. 2
      sop-website/website-front/pages/doc/sign.html
  13. 142
      sop-website/website-front/pages/sandbox/sandbox.html
  14. 161
      sop-website/website-front/pages/sandbox/sandbox.js
  15. 5
      sop-website/website-server/pom.xml
  16. 146
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java
  17. 49
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipayApiException.java
  18. 97
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipayConstants.java
  19. 611
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/AlipaySignature.java
  20. 139
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/StreamUtil.java
  21. 171
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/sign/StringUtils.java
  22. 2
      sop-website/website-server/src/main/resources/application-dev.yml

@ -1,29 +1,29 @@
* [首页](/?t=1560480225362) * [首页](/?t=1560912863431)
* 开发文档 * 开发文档
* [快速体验](files/10010_快速体验.md?t=1560480225362) * [快速体验](files/10010_快速体验.md?t=1560912863434)
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1560480225379) * [项目接入到SOP](files/10011_项目接入到SOP.md?t=1560912863453)
* [新增接口](files/10020_新增接口.md?t=1560480225379) * [新增接口](files/10020_新增接口.md?t=1560912863453)
* [业务参数校验](files/10030_业务参数校验.md?t=1560480225380) * [业务参数校验](files/10030_业务参数校验.md?t=1560912863453)
* [错误处理](files/10040_错误处理.md?t=1560480225380) * [错误处理](files/10040_错误处理.md?t=1560912863453)
* [编写文档](files/10041_编写文档.md?t=1560480225380) * [编写文档](files/10041_编写文档.md?t=1560912863453)
* [接口交互详解](files/10050_接口交互详解.md?t=1560480225380) * [接口交互详解](files/10050_接口交互详解.md?t=1560912863453)
* [easyopen支持](files/10070_easyopen支持.md?t=1560480225380) * [easyopen支持](files/10070_easyopen支持.md?t=1560912863454)
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1560480225380) * [使用签名校验工具](files/10080_使用签名校验工具.md?t=1560912863454)
* [ISV管理](files/10085_ISV管理.md?t=1560480225380) * [ISV管理](files/10085_ISV管理.md?t=1560912863454)
* [自定义路由](files/10086_自定义路由.md?t=1560480225381) * [自定义路由](files/10086_自定义路由.md?t=1560912863454)
* [路由授权](files/10090_路由授权.md?t=1560480225381) * [路由授权](files/10090_路由授权.md?t=1560912863454)
* [接口限流](files/10092_接口限流.md?t=1560480225381) * [接口限流](files/10092_接口限流.md?t=1560912863454)
* [监控日志](files/10093_监控日志.md?t=1560480225381) * [监控日志](files/10093_监控日志.md?t=1560912863454)
* [SDK开发](files/10095_SDK开发.md?t=1560480225381) * [SDK开发](files/10095_SDK开发.md?t=1560912863455)
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1560480225382) * [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1560912863455)
* [应用授权](files/10097_应用授权.md?t=1560480225382) * [应用授权](files/10097_应用授权.md?t=1560912863455)
* [更改数据节点名称](files/10099_更改数据节点名称.md?t=1560480225382) * [更改数据节点名称](files/10099_更改数据节点名称.md?t=1560912863455)
* [对接前端](files/10100_对接前端.md?t=1560480225382) * [对接前端](files/10100_对接前端.md?t=1560912863455)
* [自定义过滤器](files/10102_自定义过滤器.md?t=1560480225382) * [自定义过滤器](files/10102_自定义过滤器.md?t=1560912863455)
* [文件上传](files/10104_文件上传.md?t=1560480225382) * [文件上传](files/10104_文件上传.md?t=1560912863455)
* 原理分析 * 原理分析
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1560480225382) * [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1560912863455)
* [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1560480225382) * [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1560912863456)
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1560480225383) * [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1560912863456)
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1560480225383) * [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1560912863456)
* [常见问题](files/90100_常见问题.md?t=1560480225383) * [常见问题](files/90100_常见问题.md?t=1560912863456)

@ -9,7 +9,7 @@
假设把路由a,b,c分配给了`VIP角色`,那么具有VIP角色的ISV可以访问a,b,c三个路由。 假设把路由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才能访问的。 如:`@ApiMapping(value = "permission.story.get", permission = true)`。这样该接口是需要经过授权给ISV才能访问的。
重启服务后,登录admin,服务管理-路由列表界面中,操作操作列会出现一个授权按钮,点击出现授权窗口,勾选对应的角色即可完成授权。 重启服务后,登录admin,服务管理-路由列表界面中,操作操作列会出现一个授权按钮,点击出现授权窗口,勾选对应的角色即可完成授权。

@ -11,7 +11,7 @@ zuul:
Servlet30WrapperFilter: Servlet30WrapperFilter:
pre: pre:
disable: true disable: true
# 不用改,如果要改,建议全局替换修改 # 不用改,如果要改,全局替换修改
secret: MZZOUSTua6LzApIWXCwEgbBmxSzpzC secret: MZZOUSTua6LzApIWXCwEgbBmxSzpzC
ribbon: ribbon:

@ -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;} .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;
}

@ -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;
}
}
}
}
}

@ -75,10 +75,11 @@ layui.define(['layer', 'table'], function (exports) {
count: param.data.length, count: param.data.length,
limit: param.data.length limit: param.data.length
}; };
param.cols[0][param.treeColIndex].templet = function (d) { var firstTemplet = param.firstTemplet;
var mId = d.id; param.cols[0][param.treeColIndex].templet = function (row) {
var mPid = d.pid; var mId = row.id;
var isDir = d.isParent; var mPid = row.pid;
var isDir = row.isParent;
var emptyNum = treetable.getEmptyNum(mPid, mData); var emptyNum = treetable.getEmptyNum(mPid, mData);
var iconHtml = ''; var iconHtml = '';
for (var i = 0; i < emptyNum; i++) { for (var i = 0; i < emptyNum; i++) {
@ -92,7 +93,9 @@ layui.define(['layer', 'table'], function (exports) {
iconHtml += '&nbsp;&nbsp;'; iconHtml += '&nbsp;&nbsp;';
var ttype = isDir ? 'dir' : 'file'; var ttype = isDir ? 'dir' : 'file';
var vg = '<span class="treeTable-icon open" lay-tid="' + mId + '" lay-tpid="' + mPid + '" lay-ttype="' + ttype + '">'; var vg = '<span class="treeTable-icon open" lay-tid="' + mId + '" lay-tpid="' + mPid + '" lay-ttype="' + ttype + '">';
return vg + iconHtml + d[param.cols[0][param.treeColIndex].field] + '</span>' var field = param.cols[0][param.treeColIndex].field;
var cellVal = firstTemplet ? firstTemplet(row) : row[field];
return vg + iconHtml + cellVal + '</span>'
}; };
param.done = function (res, curr, count) { param.done = function (res, curr, count) {

@ -59,7 +59,7 @@
<h1>第三方应用授权</h1> <h1>第三方应用授权</h1>
<div class="site-title"> <div class="site-title">
<fieldset class="layui-elem-field layui-field-title site-title"> <fieldset class="layui-elem-field layui-field-title site-title">
<legend><a name="use">概述</a></legend> <legend>概述</legend>
</fieldset> </fieldset>
</div> </div>
<div class="site-text"> <div class="site-text">
@ -72,7 +72,7 @@
<div class="site-title"> <div class="site-title">
<fieldset class="layui-elem-field layui-field-title site-title"> <fieldset class="layui-elem-field layui-field-title site-title">
<legend><a name="use">快速接入</a></legend> <legend>快速接入</legend>
</fieldset> </fieldset>
</div> </div>
<div class="site-text"> <div class="site-text">

@ -88,7 +88,7 @@
<div class="site-content"> <div class="site-content">
<div class="site-title"> <div class="site-title">
<fieldset class="layui-elem-field layui-field-title site-title"> <fieldset class="layui-elem-field layui-field-title site-title">
<legend><a name="use">公共错误码</a></legend> <legend>公共错误码</legend>
</fieldset> </fieldset>
</div> </div>
<div class="site-text"> <div class="site-text">

@ -58,10 +58,14 @@
开放平台 开放平台
</a> </a>
<ul class="layui-nav"> <ul class="layui-nav">
<li class="layui-nav-item">
<a href="../sandbox/sandbox.html">沙箱环境</a>
</li>
<li class="layui-nav-item layui-this"> <li class="layui-nav-item layui-this">
<a href="doc.html">文档中心<!-- --></a> <a href="doc.html">文档中心</a>
</li> </li>
<span class="layui-nav-bar" style="left: 162px; top: 55px; width: 0px; opacity: 0;"></span> <span class="layui-nav-bar" style="left: 162px; top: 55px; width: 0px; opacity: 0;"></span>
</ul> </ul>
</div> </div>
@ -118,7 +122,7 @@
<div class="site-title"> <div class="site-title">
<fieldset class="layui-elem-field layui-field-title site-title"> <fieldset class="layui-elem-field layui-field-title site-title">
<legend><a name="use">公共参数</a></legend> <legend>公共参数</legend>
</fieldset> </fieldset>
</div> </div>
@ -240,7 +244,7 @@
<div class="site-title"> <div class="site-title">
<fieldset class="layui-elem-field layui-field-title site-title"> <fieldset class="layui-elem-field layui-field-title site-title">
<legend><a name="use">请求参数</a></legend> <legend>请求参数</legend>
</fieldset> </fieldset>
</div> </div>
<div class="site-text"> <div class="site-text">
@ -249,7 +253,7 @@
<div class="site-title"> <div class="site-title">
<fieldset class="layui-elem-field layui-field-title site-title"> <fieldset class="layui-elem-field layui-field-title site-title">
<legend><a name="use">公共响应参数</a></legend> <legend>公共响应参数</legend>
</fieldset> </fieldset>
</div> </div>
<div class="site-text"> <div class="site-text">
@ -304,7 +308,7 @@
<div class="site-title"> <div class="site-title">
<fieldset class="layui-elem-field layui-field-title site-title"> <fieldset class="layui-elem-field layui-field-title site-title">
<legend><a name="use">响应参数</a></legend> <legend>响应参数</legend>
</fieldset> </fieldset>
</div> </div>
<div class="site-text"> <div class="site-text">
@ -313,7 +317,7 @@
<div class="site-title"> <div class="site-title">
<fieldset class="layui-elem-field layui-field-title site-title"> <fieldset class="layui-elem-field layui-field-title site-title">
<legend><a name="use">响应示例</a></legend> <legend>响应示例</legend>
</fieldset> </fieldset>
</div> </div>
<div class="site-text"> <div class="site-text">
@ -323,7 +327,7 @@
<div class="site-title"> <div class="site-title">
<fieldset class="layui-elem-field layui-field-title site-title"> <fieldset class="layui-elem-field layui-field-title site-title">
<legend><a name="use">异常示例</a></legend> <legend>异常示例</legend>
</fieldset> </fieldset>
</div> </div>
<div class="site-text"> <div class="site-text">
@ -344,6 +348,8 @@
<script src="../../config/config.js" charset="utf-8"></script> <script src="../../config/config.js" charset="utf-8"></script>
<script src="../../assets/lib/jquery/3.2.1/jquery.min.js" charset="utf-8"></script> <script src="../../assets/lib/jquery/3.2.1/jquery.min.js" charset="utf-8"></script>
<script src="doc.js" charset="utf-8"></script> <script src="doc.js" type="text/javascript"></script>
<script src="docEvent.js" type="text/javascript"></script>
</body> </body>
</html> </html>

@ -1,3 +1,9 @@
var docEvent = {
bind: function (fn) {
this.onSelectDocItem = fn;
},
onSelectDocItem: function () {}
}
layui.config({ layui.config({
base: '../../assets/lib/layuiext/module/' base: '../../assets/lib/layuiext/module/'
}).extend({ }).extend({
@ -22,6 +28,7 @@ layui.config({
} }
$('#moduleList').html(html.join('')); $('#moduleList').html(html.join(''));
$('.url-prod').text(baseInfo.urlProd); $('.url-prod').text(baseInfo.urlProd);
$('.url-sandbox').text(baseInfo.urlSandbox);
form.render('select'); form.render('select');
if (docInfoList && docInfoList.length > 0) { if (docInfoList && docInfoList.length > 0) {
@ -54,7 +61,7 @@ layui.config({
</li> </li>
*/ */
html.push('<li class="site-tree-noicon" nameversion="'+docItem.nameVersion+'">'); html.push('<li class="site-tree-noicon" nameversion="'+docItem.nameVersion+'">');
html.push('<a href="#"><cite>'+docItem.summary+'</cite></a>') html.push('<a href="#"><cite>' + docItem['summary'] + '</cite></a>')
} }
} }
@ -77,119 +84,8 @@ layui.config({
function selectDocItem(nameVersion) { function selectDocItem(nameVersion) {
var docItem = docItemStore[nameVersion]; var docItem = docItemStore[nameVersion];
$('.sop-name').text(docItem.name); if (docItem) {
$('.sop-version').text(docItem.version); docEvent.onSelectDocItem(docItem, layui);
$('.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 ? '<span style="color: red;">是</span>' : '否';
}}
,{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 + '\"';
} }
} }

@ -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 ? '<span style="color: red;">是</span>' : '否';
}}
,{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 + '\"';
}
}

@ -62,7 +62,7 @@
<p>如果未使用开放平台SDK,需要自行实现签名过程。</p> <p>如果未使用开放平台SDK,需要自行实现签名过程。</p>
<fieldset class="layui-elem-field layui-field-title site-title"> <fieldset class="layui-elem-field layui-field-title site-title">
<legend><a name="use">如何签名</a></legend> <legend>如何签名</legend>
</fieldset> </fieldset>
<p>1.筛选并排序</p> <p>1.筛选并排序</p>
<p> <p>

@ -0,0 +1,142 @@
<html data-find="_6">
<head>
<script src="https://www.googletagservices.com/activeview/js/current/osd.js?cb=%2Fr20100101"></script>
<meta charset="utf-8">
<title>文档中心</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="format-detection" content="telephone=no">
<link rel="stylesheet" href="../../assets/lib/layui/css/layui.css" media="all">
<link rel="stylesheet" href="../../assets/css/global.css?v=1" media="all">
<link id="layuicss-skincodecss" rel="stylesheet" href="../../assets/lib/layui/css/modules/code.css" media="all">
<link id="layuicss-layer" rel="stylesheet" href="../../assets/lib/layui/css/modules/layer/default/layer.css?v=3.1.1"
media="all">
<style type="text/css">
.layui-table {
word-wrap: break-word;
word-break: break-all;
}
.layui-table th {white-space: nowrap !important;}
/** 修改文件夹图标:未展开 */
.treeTable-icon .layui-icon-layer:before {content: "";}
/** 修改文件夹图标:展开 */
.treeTable-icon.open .layui-icon-layer:before {content: "";}
/*修改文件图标:*/
.treeTable-icon .layui-icon-file:before {content: "";}
table .table-title {width: 180px;}
.layui-tab-item span {color: #6e6e6e;}
.input-tip {color: #6e6e6e;}
</style>
</head>
<body data-find="_5">
<div class="layui-header header header-doc" spring="">
<div class="layui-main">
<a class="logo" href="#" style="color: #ffffff">
开放平台
</a>
<ul class="layui-nav">
<li class="layui-nav-item layui-this">
<a href="sandbox.html">沙箱环境</a>
</li>
<li class="layui-nav-item">
<a href="../doc/doc.html">文档中心</a>
</li>
<span class="layui-nav-bar" style="left: 162px; top: 55px; width: 0px; opacity: 0;"></span>
</ul>
</div>
</div>
<!-- 让IE8/9支持媒体查询,从而兼容栅格 -->
<!--[if lt IE 9]>
<script src="https://cdn.staticfile.org/html5shiv/r29/html5.min.js"></script>
<script src="https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<div class="layui-main site-inline">
<div class="site-tree">
<div class="layui-form" style="height: 38px; margin: 10px 13px 0 0;">
<select id="moduleList" lay-filter="moduleListFilter">
<!--<option value="故事API" selected="selected">故事API</option>
<option value="书本API">书本API</option>-->
</select>
</div>
<ul id="docItemTree" class="layui-tree">
<!--<li><h2 class="docModule">支付API</h2></li>
<li class="site-tree-noicon layui-this">
<a href="/">
<cite>xxx</cite>
</a>
</li>-->
</ul>
</div>
<div class="site-content">
<h1 class="site-h1"><i class="layui-icon layui-icon-slider"></i> <span class="sop-name"></span>(<span
class="sop-summary"></span>)
</h1>
<blockquote class="layui-elem-quote layui-text sop-description">
</blockquote>
<div class="site-text">
<h3>参数配置</h3>
<table class="layui-table">
<tr>
<td class="table-title">AppId</td>
<td><input id="appId" type="text" name="appId" class="layui-input" /></td>
</tr>
<tr>
<td>PrivateKey</td>
<td><input id="privateKey" type="text" name="privateKey" class="layui-input" /></td>
</tr>
</table>
</div>
<div class="site-title">
<fieldset class="layui-elem-field layui-field-title site-title">
<legend>请求参数</legend>
</fieldset>
</div>
<form class="layui-form" action="">
<table id="treeTableReq" class="layui-table" lay-size="sm"></table>
<button lay-submit lay-filter="formSend" class="layui-btn layui-btn-normal">发送请求</button>
</form>
<div id="resultDiv" class="layui-tab layui-tab-card" style="display: none;">
<ul class="layui-tab-title">
<li class="req-info">请求信息</li>
<li class="resp-info layui-this">响应信息</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item req-info-content" style="overflow: auto;">
<textarea id="req-info-result" wrap="off" class="layui-textarea" readonly="readonly"></textarea>
</div>
<div class="layui-tab-item resp-info-content layui-show">
<pre id="resp-info-result"></pre>
</div>
</div>
</div>
</div>
</div><!-- layui-main end~ -->
<div class="layui-footer footer footer-doc">
<div class="layui-main">
<p>© 2019 <a href="https://gitee.com/durcframework/SOP" target="_blank">SOP</a> MIT license</p>
</div>
</div>
<script src="../../assets/lib/layui/layui.js" charset="utf-8"></script>
<script src="../../assets/js/format.js" charset="utf-8"></script>
<script src="../../config/config.js" charset="utf-8"></script>
<script src="../../assets/lib/jquery/3.2.1/jquery.min.js" charset="utf-8"></script>
<script src="../../assets/js/inputcache.js" type="text/javascript"></script>
<script src="../doc/doc.js" type="text/javascript"></script>
<script src="sandbox.js" type="text/javascript"></script>
</body>
</html>

@ -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 ? '<span style="color: red;">*</span> ' : '';
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 ? '<input id="' + id + '" ' + requiredTxt + ' type="text" name="' + row.name + '" class="layui-input test-input"/>' : '';
}}
,{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();
}

@ -82,6 +82,11 @@
<version>1.2.56</version> <version>1.2.56</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

@ -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<String, String> params = new HashMap<String, String>();
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<String, String> params) {
CloseableHttpClient client = null;
CloseableHttpResponse response = null;
try{
/**
* 创建一个httpclient对象
*/
client = HttpClients.createDefault();
List<NameValuePair> 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<String, String> params) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
sb.append("&").append(entry.getKey()).append("=").append(entry.getValue());
}
return sb.toString().substring(1);
}
}

@ -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;
}
}

@ -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";
}

@ -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<String, String> sortedParams) {
StringBuffer content = new StringBuffer();
List<String> keys = new ArrayList<String>(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<String, String> 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<String, String> params) {
if (params == null) {
return null;
}
params.remove("sign");
params.remove("sign_type");
StringBuffer content = new StringBuffer();
List<String> keys = new ArrayList<String>(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<String, String> params) {
if (params == null) {
return null;
}
params.remove("sign");
StringBuffer content = new StringBuffer();
List<String> keys = new ArrayList<String>(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<String, String> 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<String, String> 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<String, String> 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<String, String> 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));
}
/**
* 验签并解密
* <p>
* <b>目前适用于公众号</b><br>
* params参数示例
* <br>{
* <br>biz_content=M0qGiGz+8kIpxe8aF4geWJdBn0aBTuJRQItLHo9R7o5JGhpic/MIUjvXo2BLB++BbkSq2OsJCEQFDZ0zK5AJYwvBgeRX30gvEj6eXqXRt16/IkB9HzAccEqKmRHrZJ7PjQWE0KfvDAHsJqFIeMvEYk1Zei2QkwSQPlso7K0oheo/iT+HYE8aTATnkqD/ByD9iNDtGg38pCa2xnnns63abKsKoV8h0DfHWgPH62urGY7Pye3r9FCOXA2Ykm8X4/Bl1bWFN/PFCEJHWe/HXj8KJKjWMO6ttsoV0xRGfeyUO8agu6t587Dl5ux5zD/s8Lbg5QXygaOwo3Fz1G8EqmGhi4+soEIQb8DBYanQOS3X+m46tVqBGMw8Oe+hsyIMpsjwF4HaPKMr37zpW3fe7xOMuimbZ0wq53YP/jhQv6XWodjT3mL0H5ACqcsSn727B5ztquzCPiwrqyjUHjJQQefFTzOse8snaWNQTUsQS7aLsHq0FveGpSBYORyA90qPdiTjXIkVP7mAiYiAIWW9pCEC7F3XtViKTZ8FRMM9ySicfuAlf3jtap6v2KPMtQv70X+hlmzO/IXB6W0Ep8DovkF5rB4r/BJYJLw/6AS0LZM9w5JfnAZhfGM2rKzpfNsgpOgEZS1WleG4I2hoQC0nxg9IcP0Hs+nWIPkEUcYNaiXqeBc=,
* <br>sign=rlqgA8O+RzHBVYLyHmrbODVSANWPXf3pSrr82OCO/bm3upZiXSYrX5fZr6UBmG6BZRAydEyTIguEW6VRuAKjnaO/sOiR9BsSrOdXbD5Rhos/Xt7/mGUWbTOt/F+3W0/XLuDNmuYg1yIC/6hzkg44kgtdSTsQbOC9gWM7ayB4J4c=,
* sign_type=RSA,
* <br>charset=UTF-8
* <br>}
* </p>
* @param params
* @param alipayPublicKey 支付宝公钥
* @param cusPrivateKey 商户私钥
* @param isCheckSign 是否验签
* @param isDecrypt 是否解密
* @return 解密后明文验签失败则异常抛出
* @throws AlipayApiException
*/
public static String checkSignAndDecrypt(Map<String, String> 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;
}
/**
* 验签并解密
* <p>
* <b>目前适用于公众号</b><br>
* params参数示例
* <br>{
* <br>biz_content=M0qGiGz+8kIpxe8aF4geWJdBn0aBTuJRQItLHo9R7o5JGhpic/MIUjvXo2BLB++BbkSq2OsJCEQFDZ0zK5AJYwvBgeRX30gvEj6eXqXRt16/IkB9HzAccEqKmRHrZJ7PjQWE0KfvDAHsJqFIeMvEYk1Zei2QkwSQPlso7K0oheo/iT+HYE8aTATnkqD/ByD9iNDtGg38pCa2xnnns63abKsKoV8h0DfHWgPH62urGY7Pye3r9FCOXA2Ykm8X4/Bl1bWFN/PFCEJHWe/HXj8KJKjWMO6ttsoV0xRGfeyUO8agu6t587Dl5ux5zD/s8Lbg5QXygaOwo3Fz1G8EqmGhi4+soEIQb8DBYanQOS3X+m46tVqBGMw8Oe+hsyIMpsjwF4HaPKMr37zpW3fe7xOMuimbZ0wq53YP/jhQv6XWodjT3mL0H5ACqcsSn727B5ztquzCPiwrqyjUHjJQQefFTzOse8snaWNQTUsQS7aLsHq0FveGpSBYORyA90qPdiTjXIkVP7mAiYiAIWW9pCEC7F3XtViKTZ8FRMM9ySicfuAlf3jtap6v2KPMtQv70X+hlmzO/IXB6W0Ep8DovkF5rB4r/BJYJLw/6AS0LZM9w5JfnAZhfGM2rKzpfNsgpOgEZS1WleG4I2hoQC0nxg9IcP0Hs+nWIPkEUcYNaiXqeBc=,
* <br>sign=rlqgA8O+RzHBVYLyHmrbODVSANWPXf3pSrr82OCO/bm3upZiXSYrX5fZr6UBmG6BZRAydEyTIguEW6VRuAKjnaO/sOiR9BsSrOdXbD5Rhos/Xt7/mGUWbTOt/F+3W0/XLuDNmuYg1yIC/6hzkg44kgtdSTsQbOC9gWM7ayB4J4c=,
* sign_type=RSA,
* <br>charset=UTF-8
* <br>}
* </p>
* @param params
* @param alipayPublicKey 支付宝公钥
* @param cusPrivateKey 商户私钥
* @param isCheckSign 是否验签
* @param isDecrypt 是否解密
* @return 解密后明文验签失败则异常抛出
* @throws AlipayApiException
*/
public static String checkSignAndDecrypt(Map<String, String> 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;
}
/**
* 加密并签名<br>
* <b>目前适用于公众号</b>
* @param bizContent 待加密签名内容
* @param alipayPublicKey 支付宝公钥
* @param cusPrivateKey 商户私钥
* @param charset 字符集如UTF-8, GBK, GB2312
* @param isEncrypt 是否加密true-加密 false-不加密
* @param isSign 是否签名true-签名 false-不签名
* @return 加密签名后xml内容字符串
* <p>
* 返回示例
* <alipay>
* <response>密文</response>
* <encryption_type>RSA</encryption_type>
* <sign>sign</sign>
* <sign_type>RSA</sign_type>
* </alipay>
* </p>
* @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("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>");
if (isEncrypt) {// 加密
sb.append("<alipay>");
String encrypted = rsaEncrypt(bizContent, alipayPublicKey, charset);
sb.append("<response>" + encrypted + "</response>");
sb.append("<encryption_type>RSA</encryption_type>");
if (isSign) {
String sign = rsaSign(encrypted, cusPrivateKey, charset);
sb.append("<sign>" + sign + "</sign>");
sb.append("<sign_type>RSA</sign_type>");
}
sb.append("</alipay>");
} else if (isSign) {// 不加密,但需要签名
sb.append("<alipay>");
sb.append("<response>" + bizContent + "</response>");
String sign = rsaSign(bizContent, cusPrivateKey, charset);
sb.append("<sign>" + sign + "</sign>");
sb.append("<sign_type>RSA</sign_type>");
sb.append("</alipay>");
} else {// 不加密,不加签
sb.append(bizContent);
}
return sb.toString();
}
/**
* 加密并签名<br>
* <b>目前适用于公众号</b>
* @param bizContent 待加密签名内容
* @param alipayPublicKey 支付宝公钥
* @param cusPrivateKey 商户私钥
* @param charset 字符集如UTF-8, GBK, GB2312
* @param isEncrypt 是否加密true-加密 false-不加密
* @param isSign 是否签名true-签名 false-不签名
* @return 加密签名后xml内容字符串
* <p>
* 返回示例
* <alipay>
* <response>密文</response>
* <encryption_type>RSA</encryption_type>
* <sign>sign</sign>
* <sign_type>RSA</sign_type>
* </alipay>
* </p>
* @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("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>");
if (isEncrypt) {// 加密
sb.append("<alipay>");
String encrypted = rsaEncrypt(bizContent, alipayPublicKey, charset);
sb.append("<response>" + encrypted + "</response>");
sb.append("<encryption_type>RSA</encryption_type>");
if (isSign) {
String sign = rsaSign(encrypted, cusPrivateKey, charset, signType);
sb.append("<sign>" + sign + "</sign>");
sb.append("<sign_type>");
sb.append(signType);
sb.append("</sign_type>");
}
sb.append("</alipay>");
} else if (isSign) {// 不加密,但需要签名
sb.append("<alipay>");
sb.append("<response>" + bizContent + "</response>");
String sign = rsaSign(bizContent, cusPrivateKey, charset, signType);
sb.append("<sign>" + sign + "</sign>");
sb.append("<sign_type>");
sb.append(signType);
sb.append("</sign_type>");
sb.append("</alipay>");
} 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);
}
}
}

@ -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();
}
}
}
}

@ -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() {}
/**
* 检查指定的字符串是否为空
* <ul>
* <li>SysUtils.isEmpty(null) = true</li>
* <li>SysUtils.isEmpty("") = true</li>
* <li>SysUtils.isEmpty(" ") = true</li>
* <li>SysUtils.isEmpty("abc") = false</li>
* </ul>
*
* @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);
}
}
}

@ -19,6 +19,8 @@ spring:
api: api:
# 测试地址 # 测试地址
url-test: http://api-test.yourdomain.com/api url-test: http://api-test.yourdomain.com/api
# 沙箱环境
url-sandbox: http://localhost:8081/api
# 正式地址 # 正式地址
url-prod: http://open.yourdomain.com/api url-prod: http://open.yourdomain.com/api
pwd: doc#123 pwd: doc#123
Loading…
Cancel
Save