mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 21:57:56 +08:00
1.1.0
This commit is contained in:
@@ -38,11 +38,12 @@ SOP封装了开放平台大部分功能包括:签名验证、统一异常处
|
|||||||
- 关闭签名校验功能
|
- 关闭签名校验功能
|
||||||
- 整合[easyopen](https://gitee.com/durcframework/easyopen)
|
- 整合[easyopen](https://gitee.com/durcframework/easyopen)
|
||||||
- Admin管理平台,统一管理微服务配置,管理路由管理,微服务上下线
|
- Admin管理平台,统一管理微服务配置,管理路由管理,微服务上下线
|
||||||
|
- 接入方管理+秘钥管理
|
||||||
|
- 接口权限分配
|
||||||
|
|
||||||
## 后期规划
|
## 后期规划
|
||||||
|
|
||||||
- 接入方管理+秘钥管理
|
|
||||||
- 接口权限分配
|
|
||||||
- SDK
|
- SDK
|
||||||
- Spring Cloud Config(Zookeeper)
|
- Spring Cloud Config(Zookeeper)
|
||||||
|
|
||||||
@@ -54,6 +55,8 @@ SOP封装了开放平台大部分功能包括:签名验证、统一异常处
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## 工程说明
|
## 工程说明
|
||||||
|
|
||||||
> 运行环境:JDK8,Maven3,Zookeeper
|
> 运行环境:JDK8,Maven3,Zookeeper
|
||||||
|
10
changelog.md
Normal file
10
changelog.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# changelog
|
||||||
|
|
||||||
|
## 1.1.0
|
||||||
|
|
||||||
|
- 新增接入方管理
|
||||||
|
- 新增接口授权
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- 第一次发布
|
@@ -1,15 +1,17 @@
|
|||||||
* [首页](/?t=1553593844358)
|
* [首页](/?t=1554123435599)
|
||||||
* 开发文档
|
* 开发文档
|
||||||
* [快速体验](files/10010_快速体验.md?t=1553593844358)
|
* [快速体验](files/10010_快速体验.md?t=1554123435601)
|
||||||
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1553593844380)
|
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1554123435620)
|
||||||
* [新增接口](files/10020_新增接口.md?t=1553593844380)
|
* [新增接口](files/10020_新增接口.md?t=1554123435620)
|
||||||
* [业务参数校验](files/10030_业务参数校验.md?t=1553593844380)
|
* [业务参数校验](files/10030_业务参数校验.md?t=1554123435620)
|
||||||
* [错误处理](files/10040_错误处理.md?t=1553593844380)
|
* [错误处理](files/10040_错误处理.md?t=1554123435620)
|
||||||
* [接口交互详解](files/10050_接口交互详解.md?t=1553593844380)
|
* [接口交互详解](files/10050_接口交互详解.md?t=1554123435621)
|
||||||
* [使用SpringCloudGateway](files/10060_使用SpringCloudGateway.md?t=1553593844380)
|
* [使用SpringCloudGateway](files/10060_使用SpringCloudGateway.md?t=1554123435621)
|
||||||
* [easyopen支持](files/10070_easyopen支持.md?t=1553593844380)
|
* [easyopen支持](files/10070_easyopen支持.md?t=1554123435621)
|
||||||
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1553593844380)
|
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1554123435621)
|
||||||
|
* [ISV管理](files/10085_ISV管理.md?t=1554123435621)
|
||||||
|
* [路由授权](files/10090_路由授权.md?t=1554123435621)
|
||||||
* 原理分析
|
* 原理分析
|
||||||
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1553593844380)
|
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1554123435621)
|
||||||
* [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1553593844381)
|
* [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1554123435622)
|
||||||
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1553593844381)
|
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1554123435622)
|
||||||
|
@@ -1,19 +1,21 @@
|
|||||||
# 快速体验
|
# 快速体验
|
||||||
|
|
||||||
> 运行环境:JDK8,Maven3,Zookeeper
|
> 运行环境:JDK8,Maven3,Zookeeper,Mysql
|
||||||
|
|
||||||
- 安装并启动zookeeper,[安装教程](http://zookeeper.apache.org/doc/r3.4.13/zookeeperStarted.html)
|
- 安装并启动zookeeper,[安装教程](http://zookeeper.apache.org/doc/r3.4.13/zookeeperStarted.html)
|
||||||
|
- 执行Mysql脚本`sop.sql`
|
||||||
- IDE打开项目(IDEA下可以打开根pom.xml,然后open as project)
|
- IDE打开项目(IDEA下可以打开根pom.xml,然后open as project)
|
||||||
- 启动注册中心,sop-registry(运行SopRegistryApplication.java)
|
- 启动注册中心,sop-registry(运行SopRegistryApplication.java)
|
||||||
- 启动微服务:sop-story-web(运行SopStoryApplication.java)
|
- 启动微服务:sop-story-web(运行SopStoryApplication.java)
|
||||||
- 启动网关:sop-gateway(运行SopGatewayApplication.java)
|
- 启动网关:打开sop-gateway下的`application.yml`,修改数据库`username/password`,SopGatewayApplication.java
|
||||||
- 找到sop-test,打开测试用例,进行接口调用测试,运行com.gitee.sop.AlipayClientPostTest.testPost()
|
- 找到sop-test,打开测试用例,进行接口调用测试,运行com.gitee.sop.AlipayClientPostTest.testPost()
|
||||||
|
|
||||||
确保注册中心先启动
|
确保注册中心先启动
|
||||||
|
|
||||||
## 使用admin
|
## 使用admin
|
||||||
|
|
||||||
- 找到`sop-admin/sop-admin-server`工程,运行`com.gitee.sop.adminserver.SopAdminServerApplication.java`
|
- 找到`sop-admin/sop-admin-server`工程,打开sop-admin-server下的`application-dev.yml`,修改数据库`username/password`
|
||||||
|
- 运行`com.gitee.sop.adminserver.SopAdminServerApplication.java`
|
||||||
- 找到`sop-admin/sop-admin-front/index.html`文件,在IDEA下直接右键--Run'index.html'
|
- 找到`sop-admin/sop-admin-front/index.html`文件,在IDEA下直接右键--Run'index.html'
|
||||||
- 如果没有用到IDEA,则需要把sop-admin-front放到静态服务器中然后访问index.html
|
- 如果没有用到IDEA,则需要把sop-admin-front放到静态服务器中然后访问index.html
|
||||||
|
|
||||||
|
14
doc/docs/files/10085_ISV管理.md
Normal file
14
doc/docs/files/10085_ISV管理.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# ISV管理
|
||||||
|
|
||||||
|
ISV:独立软体开发商(independent software vendor),即接入方或者说接口调用者,在SOP中称为ISV。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
在1.1.0版本中新增了ISV管理功能,在sop-admin中ISV管理模块下。功能如下:
|
||||||
|
|
||||||
|
- 基本信息的增查改
|
||||||
|
- 设置对应角色
|
||||||
|
|
||||||
|
界面如下图所示:
|
||||||
|
|
||||||
|

|
25
doc/docs/files/10090_路由授权.md
Normal file
25
doc/docs/files/10090_路由授权.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# 路由授权
|
||||||
|
|
||||||
|
1.1.0版本新增了路由授权功能,采用RBAC权限管理方式实现。
|
||||||
|
|
||||||
|
- 每个ISV(appKey)对应一个或多个角色
|
||||||
|
- 每个角色分配多个路由权限
|
||||||
|
|
||||||
|
接口跟角色相关联,ISV拥有哪些角色,就具有角色对应的接口访问权限。
|
||||||
|
|
||||||
|
假设把路由a,b,c分配给了`VIP角色`,那么具有VIP角色的ISV可以访问a,b,c三个路由。
|
||||||
|
|
||||||
|
默认情况下,接口访问时公开的,ISV都能访问。如果要设置某个接口需要授权,在`@ApiMapping`注解中指定permission=true。
|
||||||
|
如:`@ApiMapping(value = "permission.story.get", permission = true)`。这样该接口是需要经过授权给ISV才能访问的。
|
||||||
|
|
||||||
|
重启服务后,登录admin,服务管理-路由列表界面中,操作操作列会出现一个授权按钮,点击出现授权窗口,勾选对应的角色即可完成授权。
|
||||||
|
|
||||||
|
- 点击`授权`按钮,进行角色授权
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- 勾选对应角色,点击保存
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
这里演示的是:具有普通权限的ISV能够访问`permission.story.get`接口,运行`PermissionDemoPostTest`测试用例进行验证
|
@@ -6,7 +6,7 @@ SOP将路由信息存到了zookeeper当中,服务在启动时,将自己的
|
|||||||
zookeeper存储路由的结构如下:
|
zookeeper存储路由的结构如下:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
/com.gitee.sop.route-<profile> 根节点
|
/com.gitee.sop.route 根节点
|
||||||
/serviceId 服务节点,名字为服务名
|
/serviceId 服务节点,名字为服务名
|
||||||
/route1 路由节点,名字为:name+version,存放路由信息
|
/route1 路由节点,名字为:name+version,存放路由信息
|
||||||
/route2
|
/route2
|
||||||
|
BIN
doc/docs/files/images/10085_1.png
Normal file
BIN
doc/docs/files/images/10085_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 233 KiB |
BIN
doc/docs/files/images/10090_1.png
Normal file
BIN
doc/docs/files/images/10090_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 327 KiB |
BIN
doc/docs/files/images/10090_2.png
Normal file
BIN
doc/docs/files/images/10090_2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 94 KiB |
@@ -32,6 +32,9 @@
|
|||||||
<div id="app">加载中 ...</div>
|
<div id="app">加载中 ...</div>
|
||||||
<script>
|
<script>
|
||||||
window.$docsify = {
|
window.$docsify = {
|
||||||
|
alias: {
|
||||||
|
'/.*/_sidebar.md': '/_sidebar.md?t=' + new Date().getTime()
|
||||||
|
},
|
||||||
auto2top: true,
|
auto2top: true,
|
||||||
coverpage: false,
|
coverpage: false,
|
||||||
executeScript: true,
|
executeScript: true,
|
||||||
|
@@ -7,6 +7,7 @@ import java.util.Comparator;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -36,11 +37,15 @@ public class SidebarTest {
|
|||||||
Map<String, List<FileExt>> menuMap = filesStream
|
Map<String, List<FileExt>> menuMap = filesStream
|
||||||
.sorted(Comparator.comparing(File::getName))
|
.sorted(Comparator.comparing(File::getName))
|
||||||
.map(file -> {
|
.map(file -> {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
FileExt fileExt = new FileExt();
|
FileExt fileExt = new FileExt();
|
||||||
fileExt.menu = file.getName().substring(0, 1);
|
fileExt.menu = file.getName().substring(0, 1);
|
||||||
fileExt.file = file;
|
fileExt.file = file;
|
||||||
return fileExt;
|
return fileExt;
|
||||||
})
|
})
|
||||||
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.groupingBy(FileExt::getMenu));
|
.collect(Collectors.groupingBy(FileExt::getMenu));
|
||||||
|
|
||||||
StringBuilder output = new StringBuilder();
|
StringBuilder output = new StringBuilder();
|
||||||
|
@@ -675,3 +675,4 @@ table th, table td {
|
|||||||
color: #393D49;
|
color: #393D49;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.x-win {display: none;padding: 20px;}
|
@@ -1,30 +0,0 @@
|
|||||||
;(function () {
|
|
||||||
var currentProfile = ApiUtil.getParam('profile') || 'default';
|
|
||||||
|
|
||||||
ApiUtil.post('system.profile.list', {}, function (resp) {
|
|
||||||
var profileList = resp.data;
|
|
||||||
var html = ['<div class="layui-tab layui-tab-brief" style="margin-top: 0px;">'];
|
|
||||||
html.push('<ul id="profileList" class="layui-tab-title">')
|
|
||||||
for (var i = 0; i < profileList.length; i++) {
|
|
||||||
var profile = profileList[i];
|
|
||||||
var cls = currentProfile == profile ? 'layui-this' : '';
|
|
||||||
html.push('<li><a class="' + cls + '" href="' + getCurrentPage(profile) + '">' + profile + '</a></li>');
|
|
||||||
}
|
|
||||||
html.push('</ul>')
|
|
||||||
html.push('</div>')
|
|
||||||
$('.x-body').prepend(html.join(''));
|
|
||||||
});
|
|
||||||
|
|
||||||
function getCurrentPage(profile) {
|
|
||||||
var currentUrl = location.href.toString();
|
|
||||||
var indexStart = currentUrl.lastIndexOf('/') + 1;
|
|
||||||
var indexEnd = currentUrl.lastIndexOf('?');
|
|
||||||
var page = indexEnd > -1
|
|
||||||
? currentUrl.substring(indexStart, indexEnd)
|
|
||||||
: currentUrl.substring(indexStart);
|
|
||||||
|
|
||||||
return page + '?q=' + new Date().getTime() + '&profile=' + profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.profile = currentProfile;
|
|
||||||
})();
|
|
22
sop-admin/sop-admin-front/assets/js/routerole.js
Normal file
22
sop-admin/sop-admin-front/assets/js/routerole.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
var RouteRole = {
|
||||||
|
loadAllRole: function (form, contentId, callback) {
|
||||||
|
ApiUtil.post('role.listall', {}, function (resp) {
|
||||||
|
var roles = resp.data;
|
||||||
|
var html = []
|
||||||
|
for (var i = 0; i < roles.length; i++) {
|
||||||
|
if (i > 0 && i % 5 === 0) {
|
||||||
|
html.push('<br>');
|
||||||
|
}
|
||||||
|
var role = roles[i];
|
||||||
|
html.push('<input type="checkbox" name="roleCode" value="'+role.roleCode+'" lay-skin="primary" title="'+role.description+'">');
|
||||||
|
}
|
||||||
|
$('#' + contentId).html(html.join(''));
|
||||||
|
|
||||||
|
// 如果你的HTML是动态生成的,自动渲染就会失效
|
||||||
|
// 因此你需要在相应的地方,执行下述方法来手动渲染,跟这类似的还有 element.init();
|
||||||
|
form.render();
|
||||||
|
|
||||||
|
callback && callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -33,9 +33,11 @@ layui.define(function (exports) {
|
|||||||
, parseForm: function ($form) {
|
, parseForm: function ($form) {
|
||||||
var that = this;
|
var that = this;
|
||||||
this.form = $form[0];
|
this.form = $form[0];
|
||||||
this.$els = $form.find('input,select,textarea');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
, getEls: function () {
|
||||||
|
return this.$form.find('input,select,textarea');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同load(data)
|
* 同load(data)
|
||||||
@@ -80,7 +82,7 @@ layui.define(function (exports) {
|
|||||||
* 清除表单中的值,清除错误信息
|
* 清除表单中的值,清除错误信息
|
||||||
*/
|
*/
|
||||||
, clear: function () {
|
, clear: function () {
|
||||||
this.$els.each(function () {
|
this.getEls().each(function () {
|
||||||
var _$el = $(this);
|
var _$el = $(this);
|
||||||
if (_$el.is(':radio') || _$el.is(':checkbox')) {
|
if (_$el.is(':radio') || _$el.is(':checkbox')) {
|
||||||
this.checked = false;
|
this.checked = false;
|
||||||
@@ -116,7 +118,7 @@ layui.define(function (exports) {
|
|||||||
var that = this;
|
var that = this;
|
||||||
var data = {};
|
var data = {};
|
||||||
|
|
||||||
this.$els.each(function () {
|
this.getEls().each(function () {
|
||||||
var value = that._getInputVal($(this));
|
var value = that._getInputVal($(this));
|
||||||
if (value) {
|
if (value) {
|
||||||
var name = this.name;
|
var name = this.name;
|
||||||
@@ -139,29 +141,6 @@ layui.define(function (exports) {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
, getFormData: function ($form) {
|
|
||||||
var data = {};
|
|
||||||
var dataArr = $form.serializeArray();
|
|
||||||
for (var i = 0, len = dataArr.length; i < len; i++) {
|
|
||||||
var item = dataArr[i];
|
|
||||||
var name = item.name;
|
|
||||||
var itemValue = item.value;
|
|
||||||
var dataValue = data[name];
|
|
||||||
|
|
||||||
if (dataValue) {
|
|
||||||
if ($.isArray(dataValue)) {
|
|
||||||
dataValue.push(itemValue);
|
|
||||||
} else {
|
|
||||||
data[name] = [dataValue, itemValue];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data[name] = itemValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
, _getInputVal: function ($input) {
|
, _getInputVal: function ($input) {
|
||||||
if ($input.is(":radio") || $input.is(":checkbox")) {
|
if ($input.is(":radio") || $input.is(":checkbox")) {
|
||||||
if ($input.is(':checked')) {
|
if ($input.is(':checked')) {
|
||||||
|
@@ -85,6 +85,20 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="open">
|
||||||
|
<a>
|
||||||
|
<i class="layui-icon layui-icon-layer"></i>
|
||||||
|
<cite>ISV管理</cite>
|
||||||
|
</a>
|
||||||
|
<ul class="sub-menu">
|
||||||
|
<li date-refresh="1">
|
||||||
|
<a href="pages/isv/isvList.html">
|
||||||
|
<i class="layui-icon layui-icon-table"></i>
|
||||||
|
<cite>ISV列表</cite>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
96
sop-admin/sop-admin-front/pages/isv/isvAdd.html
Normal file
96
sop-admin/sop-admin-front/pages/isv/isvAdd.html
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html class="x-admin-sm">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>SOP Admin</title>
|
||||||
|
<meta name="renderer" content="webkit">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8"/>
|
||||||
|
<link rel="stylesheet" href="../../assets/css/font.css">
|
||||||
|
<link rel="stylesheet" href="../../assets/css/xadmin.css">
|
||||||
|
<style>
|
||||||
|
.layui-form textarea{min-height: 60px;}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="x-nav">
|
||||||
|
<span class="layui-breadcrumb">
|
||||||
|
<a href="../welcome.html">首页</a>
|
||||||
|
<a><cite>ISV管理</cite></a>
|
||||||
|
<a href="isvList.html">ISV列表</a>
|
||||||
|
<a><cite>添加ISV</cite></a>
|
||||||
|
</span>
|
||||||
|
<a class="layui-btn layui-btn-small layui-btn-normal" style="line-height:1.6em;margin-top:4px;float:right"
|
||||||
|
href="javascript:location.replace(location.href);" title="刷新">
|
||||||
|
<i class="layui-icon layui-icon-refresh" style="line-height:30px"></i></a>
|
||||||
|
</div>
|
||||||
|
<div class="x-body">
|
||||||
|
<fieldset class="layui-elem-field layui-field-title">
|
||||||
|
<legend>添加ISV</legend>
|
||||||
|
</fieldset>
|
||||||
|
<form id="addForm" style="width: 500px" class="layui-form" action="">
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label"></label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<button id="createFormDataBtn" class="layui-btn layui-btn-primary">一键生成数据</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">appKey</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" name="appKey" lay-verify="required" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">签名方式</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="radio" name="signType" value="1" checked="checked" lay-filter="signTypeFilter" title="RSA2[推荐]">
|
||||||
|
<input type="radio" name="signType" value="2" lay-filter="signTypeFilter" title="MD5">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item sign-type-md5" style="display: none;">
|
||||||
|
<label class="layui-form-label">secret</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" name="secret" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item sign-type-rsa2">
|
||||||
|
<label class="layui-form-label">公钥</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<textarea name="pubKey" class="layui-textarea"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item sign-type-rsa2">
|
||||||
|
<label class="layui-form-label">私钥</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<textarea name="priKey" class="layui-textarea"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">角色</label>
|
||||||
|
<div id="roleArea" class="layui-input-block">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">状态</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="radio" name="status" value="1" checked="checked" title="启用">
|
||||||
|
<input type="radio" name="status" value="2" title="<span class='x-red'>禁用</span>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<button class="layui-btn layui-btn-normal" lay-submit="" lay-filter="addFormSubmitFilter">保存</button>
|
||||||
|
<button type="button" class="layui-btn layui-btn-primary" onclick="location.href='isvList.html'">取消</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript" src="../../assets/js/lib.js"></script>
|
||||||
|
<script type="text/javascript" src="isvAdd.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
39
sop-admin/sop-admin-front/pages/isv/isvAdd.js
Normal file
39
sop-admin/sop-admin-front/pages/isv/isvAdd.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
lib.importJs('../../assets/js/routerole.js')
|
||||||
|
.use(['element', 'table', 'form'], function () {
|
||||||
|
var table = layui.table;
|
||||||
|
var layer = layui.layer;
|
||||||
|
var form = layui.form;
|
||||||
|
var $ = layui.jquery;
|
||||||
|
|
||||||
|
var addForm = layui.Form('addForm');
|
||||||
|
|
||||||
|
$('#createFormDataBtn').click(function () {
|
||||||
|
ApiUtil.post('isv.form.gen', {}, function (resp) {
|
||||||
|
addForm.setData(resp.data);
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
form.on('radio(signTypeFilter)', function(data){
|
||||||
|
if (data.value == 1) {
|
||||||
|
$('.sign-type-rsa2').show();
|
||||||
|
$('.sign-type-md5').hide();
|
||||||
|
} else {
|
||||||
|
$('.sign-type-rsa2').hide();
|
||||||
|
$('.sign-type-md5').show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
form.on('submit(addFormSubmitFilter)', function (data) {
|
||||||
|
var param = addForm.getData();
|
||||||
|
ApiUtil.post('isv.info.add', param, function (resp) {
|
||||||
|
layer.alert('添加成功', function () {
|
||||||
|
location.href = 'isvList.html';
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
RouteRole.loadAllRole(form, 'roleArea');
|
||||||
|
|
||||||
|
});
|
51
sop-admin/sop-admin-front/pages/isv/isvList.html
Normal file
51
sop-admin/sop-admin-front/pages/isv/isvList.html
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html class="x-admin-sm">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>SOP Admin</title>
|
||||||
|
<meta name="renderer" content="webkit">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8"/>
|
||||||
|
<link rel="stylesheet" href="../../assets/css/font.css">
|
||||||
|
<link rel="stylesheet" href="../../assets/css/xadmin.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="x-nav">
|
||||||
|
<span class="layui-breadcrumb">
|
||||||
|
<a href="../welcome.html">首页</a>
|
||||||
|
<a><cite>ISV管理</cite></a>
|
||||||
|
<a><cite>ISV列表</cite></a>
|
||||||
|
</span>
|
||||||
|
<a class="layui-btn layui-btn-small layui-btn-normal" style="line-height:1.6em;margin-top:4px;float:right"
|
||||||
|
href="javascript:location.replace(location.href);" title="刷新">
|
||||||
|
<i class="layui-icon layui-icon-refresh" style="line-height:30px"></i></a>
|
||||||
|
</div>
|
||||||
|
<div class="x-body">
|
||||||
|
<div class="layui-row">
|
||||||
|
<form class="layui-form layui-col-md12 x-so" action="" lay-filter="searchFrm">
|
||||||
|
appKey:
|
||||||
|
<input name="appKey" class="layui-input" style="width: 200px;" placeholder="">
|
||||||
|
<button class="layui-btn layui-btn-normal" lay-submit="" lay-filter="searchFilter">
|
||||||
|
<i class="layui-icon layui-icon-search"></i>搜索
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/html" id="toolbar">
|
||||||
|
<div class="layui-btn-container">
|
||||||
|
<button class="layui-btn layui-btn-ms layui-btn-normal" lay-event="add">
|
||||||
|
<i class="layui-icon layui-icon-add-1"></i>添加ISV
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
<table class="layui-hide" id="isvTable" lay-filter="isvTableFilter"></table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript" src="../../assets/js/lib.js"></script>
|
||||||
|
<script type="text/javascript" src="isvList.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
114
sop-admin/sop-admin-front/pages/isv/isvList.js
Normal file
114
sop-admin/sop-admin-front/pages/isv/isvList.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
lib.use(['element', 'table', 'form'], function () {
|
||||||
|
var table = layui.table;
|
||||||
|
var layer = layui.layer;
|
||||||
|
var form = layui.form;
|
||||||
|
var isvTable;
|
||||||
|
|
||||||
|
var STATUS_ENUM = {
|
||||||
|
'1': '<span class="x-green">已启用</span>'
|
||||||
|
,'2': '<span class="x-red">已禁用</span>'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染表格
|
||||||
|
var renderTable = function (postData) {
|
||||||
|
var params = {
|
||||||
|
data: JSON.stringify(postData || {})
|
||||||
|
};
|
||||||
|
layer.load(2);
|
||||||
|
isvTable = table.render({
|
||||||
|
elem: '#isvTable'
|
||||||
|
, toolbar: '#toolbar'
|
||||||
|
, url: ApiUtil.createUrl('isv.info.page')
|
||||||
|
// 对分页请求的参数:page、limit重新设定名称
|
||||||
|
,request: {
|
||||||
|
pageName: 'pageIndex' //页码的参数名称,默认:page
|
||||||
|
,limitName: 'pageSize' //每页数据量的参数名,默认:limit
|
||||||
|
}
|
||||||
|
, page: true
|
||||||
|
, where: params
|
||||||
|
, cellMinWidth: 80 //全局定义常规单元格的最小宽度,layui 2.2.1 新增
|
||||||
|
, cols: [[
|
||||||
|
{field: 'id', title: 'id', width: 80}
|
||||||
|
, {field: 'appKey', title: 'appKey', width: 250}
|
||||||
|
, {field: 'secret', title: 'secret', width: 80, templet: function (row) {
|
||||||
|
return '<button class="layui-btn layui-btn-xs" onclick="View.secret(\''+row.secret+'\')">查看</button>';
|
||||||
|
}}
|
||||||
|
, {field: '', title: '公私钥', width: 80, templet: function (row) {
|
||||||
|
return '<button class="layui-btn layui-btn-xs" onclick="View.pubPriKey(\''+row.pubKey+'\', \''+row.priKey+'\')">查看</button>';
|
||||||
|
}}
|
||||||
|
, {field: 'roleList', title: '角色', templet: function (row) {
|
||||||
|
var html = [];
|
||||||
|
var roleList = row.roleList;
|
||||||
|
for (var i = 0; i < roleList.length; i++) {
|
||||||
|
html.push(roleList[i].description);
|
||||||
|
}
|
||||||
|
return html.join(', ');
|
||||||
|
}}
|
||||||
|
, {field: 'status', title: '状态', width: 80, templet: function (row) {
|
||||||
|
var display = STATUS_ENUM[row.status + ''];
|
||||||
|
return display ? display : 'unknown';
|
||||||
|
}}
|
||||||
|
, {field: 'gmtCreate', title: '添加时间', width: 160}
|
||||||
|
, {field: 'gmtModified', title: '修改时间', width: 160}
|
||||||
|
, {
|
||||||
|
fixed: 'right', title: '操作', width: 100, templet: function (row) {
|
||||||
|
return '<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="edit">修改</a>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]]
|
||||||
|
,parseData: function(res){ //将原始数据解析成 table 组件所规定的数据
|
||||||
|
return {
|
||||||
|
"code": res.code, //解析接口状态
|
||||||
|
"msg": res.msg, //解析提示文本
|
||||||
|
"count": res.data.total, //解析数据长度
|
||||||
|
"data": res.data.list //解析数据列表
|
||||||
|
};
|
||||||
|
}
|
||||||
|
,done: function () {
|
||||||
|
layer.closeAll('loading');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//监听单元格事件
|
||||||
|
table.on('tool(isvTableFilter)', function(obj) {
|
||||||
|
if (obj.event === 'edit') {
|
||||||
|
location.href = 'isvUpdate.html?id=' + obj.data.id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
table.on('toolbar(isvTableFilter)', function(obj) {
|
||||||
|
if (obj.event === 'add') {
|
||||||
|
location.href = 'isvAdd.html';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
renderTable();
|
||||||
|
|
||||||
|
form.on('submit(searchFilter)', function(data){
|
||||||
|
var param = data.field;
|
||||||
|
renderTable(param)
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
window.View = {
|
||||||
|
secret: function (secret) {
|
||||||
|
layer.alert(secret);
|
||||||
|
}
|
||||||
|
,pubPriKey: function (pubKey, priKey) {
|
||||||
|
var content = '<div style="width: 550px;padding: 10px;">公钥:<textarea class="layui-textarea" readonly="readonly">' + pubKey + '</textarea><br>' +
|
||||||
|
'私钥:<textarea class="layui-textarea" readonly="readonly">' + priKey + '</textarea></div>';
|
||||||
|
layer.open({
|
||||||
|
type: 1,
|
||||||
|
area: ['600px', '400px'],
|
||||||
|
fix: false, //不固定
|
||||||
|
shadeClose: true,
|
||||||
|
shade:0.4,
|
||||||
|
title: '公私钥',
|
||||||
|
content: content
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
97
sop-admin/sop-admin-front/pages/isv/isvUpdate.html
Normal file
97
sop-admin/sop-admin-front/pages/isv/isvUpdate.html
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html class="x-admin-sm">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>SOP Admin</title>
|
||||||
|
<meta name="renderer" content="webkit">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8"/>
|
||||||
|
<link rel="stylesheet" href="../../assets/css/font.css">
|
||||||
|
<link rel="stylesheet" href="../../assets/css/xadmin.css">
|
||||||
|
<style>
|
||||||
|
.layui-form textarea{min-height: 60px;}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="x-nav">
|
||||||
|
<span class="layui-breadcrumb">
|
||||||
|
<a href="../welcome.html">首页</a>
|
||||||
|
<a><cite>ISV管理</cite></a>
|
||||||
|
<a href="isvList.html">ISV列表</a>
|
||||||
|
<a><cite>修改ISV</cite></a>
|
||||||
|
</span>
|
||||||
|
<a class="layui-btn layui-btn-small layui-btn-normal" style="line-height:1.6em;margin-top:4px;float:right"
|
||||||
|
href="javascript:location.replace(location.href);" title="刷新">
|
||||||
|
<i class="layui-icon layui-icon-refresh" style="line-height:30px"></i></a>
|
||||||
|
</div>
|
||||||
|
<div class="x-body">
|
||||||
|
<fieldset class="layui-elem-field layui-field-title">
|
||||||
|
<legend>修改ISV</legend>
|
||||||
|
</fieldset>
|
||||||
|
<form id="updateForm" style="width: 500px" class="layui-form" action="">
|
||||||
|
<input name="id" type="hidden">
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label"></label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<button id="createFormDataBtn" class="layui-btn layui-btn-primary">一键生成数据</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">appKey</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" name="appKey" lay-verify="required" class="layui-input" readonly="readonly"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">签名方式</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="radio" name="signType" value="1" checked="checked" lay-filter="signTypeFilter" title="RSA2[推荐]">
|
||||||
|
<input type="radio" name="signType" value="2" lay-filter="signTypeFilter" title="MD5">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item sign-type-md5" style="display: none;">
|
||||||
|
<label class="layui-form-label">secret</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" name="secret" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item sign-type-rsa2">
|
||||||
|
<label class="layui-form-label">公钥</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<textarea name="pubKey" class="layui-textarea"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item sign-type-rsa2">
|
||||||
|
<label class="layui-form-label">私钥</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<textarea name="priKey" class="layui-textarea"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">角色</label>
|
||||||
|
<div id="roleArea" class="layui-input-block">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">状态</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="radio" name="status" value="1" checked="checked" title="启用">
|
||||||
|
<input type="radio" name="status" value="2" title="<span class='x-red'>禁用</span>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<button class="layui-btn layui-btn-normal" lay-submit="" lay-filter="updateFormSubmitFilter">保存</button>
|
||||||
|
<button type="button" class="layui-btn layui-btn-primary" onclick="location.href='isvList.html'">取消</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript" src="../../assets/js/lib.js"></script>
|
||||||
|
<script type="text/javascript" src="isvUpdate.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
62
sop-admin/sop-admin-front/pages/isv/isvUpdate.js
Normal file
62
sop-admin/sop-admin-front/pages/isv/isvUpdate.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
lib.importJs('../../assets/js/routerole.js')
|
||||||
|
.use(['element', 'table', 'form'], function () {
|
||||||
|
var table = layui.table;
|
||||||
|
var layer = layui.layer;
|
||||||
|
var form = layui.form;
|
||||||
|
var $ = layui.jquery;
|
||||||
|
|
||||||
|
var updateForm = layui.Form('updateForm');
|
||||||
|
|
||||||
|
$('#createFormDataBtn').click(function () {
|
||||||
|
ApiUtil.post('isv.form.gen', {}, function (resp) {
|
||||||
|
var data = resp.data;
|
||||||
|
var appKey = updateForm.getData('appKey');
|
||||||
|
data.appKey = appKey;
|
||||||
|
updateForm.setData(data);
|
||||||
|
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
form.on('radio(signTypeFilter)', function(data){
|
||||||
|
if (data.value == 1) {
|
||||||
|
$('.sign-type-rsa2').show();
|
||||||
|
$('.sign-type-md5').hide();
|
||||||
|
} else {
|
||||||
|
$('.sign-type-rsa2').hide();
|
||||||
|
$('.sign-type-md5').show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
form.on('submit(updateFormSubmitFilter)', function (data) {
|
||||||
|
var param = updateForm.getData();
|
||||||
|
ApiUtil.post('isv.info.update', param, function (resp) {
|
||||||
|
layer.alert('修改成功', function () {
|
||||||
|
location.href = 'isvList.html';
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
RouteRole.loadAllRole(form, 'roleArea', function () {
|
||||||
|
loadFormData();
|
||||||
|
})
|
||||||
|
|
||||||
|
function loadFormData() {
|
||||||
|
var id = ApiUtil.getParam('id');
|
||||||
|
if (!id) {
|
||||||
|
alert('id错误');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ApiUtil.post('isv.info.get', {id: id}, function (resp) {
|
||||||
|
var isvInfo = resp.data;
|
||||||
|
var roleList = isvInfo.roleList;
|
||||||
|
var roleCode = [];
|
||||||
|
for (var i = 0; i < roleList.length; i++) {
|
||||||
|
roleCode.push(roleList[i].roleCode);
|
||||||
|
}
|
||||||
|
isvInfo.roleCode = roleCode;
|
||||||
|
updateForm.setData(isvInfo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
@@ -22,9 +22,6 @@
|
|||||||
<i class="layui-icon layui-icon-refresh" style="line-height:30px"></i></a>
|
<i class="layui-icon layui-icon-refresh" style="line-height:30px"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="x-body">
|
<div class="x-body">
|
||||||
<div class="layui-row">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="layui-row">
|
<div class="layui-row">
|
||||||
<div class="layui-col-md2">
|
<div class="layui-col-md2">
|
||||||
<ul id="leftTree"></ul>
|
<ul id="leftTree"></ul>
|
||||||
@@ -57,14 +54,13 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="addWin" style="display: none;padding: 20px;">
|
<div id="addWin" class="x-win">
|
||||||
<form id="addForm" class="layui-form" action="" lay-filter="addWinFilter">
|
<form id="addForm" class="layui-form" action="" lay-filter="addWinFilter">
|
||||||
<input type="hidden" name="serviceId" />
|
<input type="hidden" name="serviceId" />
|
||||||
<input type="hidden" name="profile" />
|
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label">id</label>
|
<label class="layui-form-label">id</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="text" name="id" class="layui-input"/>
|
<input type="text" name="id" lay-verify="required" class="layui-input"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
@@ -109,10 +105,9 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="updateWin" style="display: none;padding: 20px;">
|
<div id="updateWin" class="x-win">
|
||||||
<form id="updateForm" class="layui-form" action="" lay-filter="updateWinFilter">
|
<form id="updateForm" class="layui-form" action="" lay-filter="updateWinFilter">
|
||||||
<input type="hidden" name="serviceId" />
|
<input type="hidden" name="serviceId" />
|
||||||
<input type="hidden" name="profile" />
|
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label">id</label>
|
<label class="layui-form-label">id</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
@@ -160,6 +155,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="authWin" class="x-win">
|
||||||
|
<form id="authForm" style="width: 400px" class="layui-form" action="">
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">id</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" name="routeId" class="layui-input" readonly="readonly"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">角色</label>
|
||||||
|
<div id="roleArea" class="layui-input-block">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<button class="layui-btn layui-btn-normal" lay-submit="" lay-filter="authFormSubmitFilter">保存</button>
|
||||||
|
<button type="button" class="layui-btn layui-btn-primary" onclick="layer.closeAll()">取消</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript" src="../../assets/js/lib.js"></script>
|
<script type="text/javascript" src="../../assets/js/lib.js"></script>
|
||||||
<script type="text/javascript" src="routeManager.js"></script>
|
<script type="text/javascript" src="routeManager.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
lib.importJs('../../assets/js/profile.js').use(['element', 'table', 'tree', 'form'], function () {
|
lib.importJs('../../assets/js/routerole.js')
|
||||||
|
.use(['element', 'table', 'tree', 'form'], function () {
|
||||||
var ROUTE_STATUS = {
|
var ROUTE_STATUS = {
|
||||||
'0': '待审核'
|
'0': '待审核'
|
||||||
,'1': '<span class="x-green">已启用</span>'
|
,'1': '<span class="x-green">已启用</span>'
|
||||||
,'2': '<span class="x-red">已禁用</span>'
|
,'2': '<span class="x-red">已禁用</span>'
|
||||||
}
|
}
|
||||||
var profile = window.profile;
|
|
||||||
var form = layui.form;
|
var form = layui.form;
|
||||||
var updateForm = layui.Form('updateForm');
|
var updateForm = layui.Form('updateForm');
|
||||||
var addForm = layui.Form('addForm');
|
var addForm = layui.Form('addForm');
|
||||||
|
var authForm = layui.Form('authForm');
|
||||||
var table = layui.table;
|
var table = layui.table;
|
||||||
|
|
||||||
var currentServiceId;
|
var currentServiceId;
|
||||||
@@ -40,8 +41,18 @@ lib.importJs('../../assets/js/profile.js').use(['element', 'table', 'tree', 'for
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
form.on('submit(authFormSubmitFilter)', function(data) {
|
||||||
|
var params = authForm.getData();
|
||||||
|
ApiUtil.post('route.role.update', params, function (resp) {
|
||||||
|
layer.closeAll();
|
||||||
|
routeTable.reload();
|
||||||
|
})
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
function initTree() {
|
function initTree() {
|
||||||
ApiUtil.post('service.list', {profile: profile}, function (resp) {
|
ApiUtil.post('service.list', {}, function (resp) {
|
||||||
var serviceList = resp.data;
|
var serviceList = resp.data;
|
||||||
var children = [];
|
var children = [];
|
||||||
for (var i = 0; i < serviceList.length; i++) {
|
for (var i = 0; i < serviceList.length; i++) {
|
||||||
@@ -55,7 +66,7 @@ lib.importJs('../../assets/js/profile.js').use(['element', 'table', 'tree', 'for
|
|||||||
layui.tree({
|
layui.tree({
|
||||||
elem: '#leftTree' //传入元素选择器
|
elem: '#leftTree' //传入元素选择器
|
||||||
, nodes: [{ //节点
|
, nodes: [{ //节点
|
||||||
name: '服务列表(' + profile + ')'
|
name: '服务列表'
|
||||||
, spread: true // 展开
|
, spread: true // 展开
|
||||||
, children: children
|
, children: children
|
||||||
}]
|
}]
|
||||||
@@ -80,7 +91,7 @@ lib.importJs('../../assets/js/profile.js').use(['element', 'table', 'tree', 'for
|
|||||||
searchTable({
|
searchTable({
|
||||||
serviceId: serviceId
|
serviceId: serviceId
|
||||||
});
|
});
|
||||||
smTitle = '[ <strong>profile:</strong>' + profile + ' <strong>serviceId:</strong>' + currentServiceId + ' ]';
|
smTitle = '[ <strong>serviceId:</strong>' + currentServiceId + ' ]';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,11 +101,9 @@ lib.importJs('../../assets/js/profile.js').use(['element', 'table', 'tree', 'for
|
|||||||
function searchTable(params) {
|
function searchTable(params) {
|
||||||
var postData = {
|
var postData = {
|
||||||
data: JSON.stringify(params)
|
data: JSON.stringify(params)
|
||||||
, profile: profile
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!routeTable) {
|
if (!routeTable) {
|
||||||
routeTable = initTable(postData);
|
routeTable = renderTable(postData);
|
||||||
} else {
|
} else {
|
||||||
routeTable.reload({
|
routeTable.reload({
|
||||||
where: postData
|
where: postData
|
||||||
@@ -102,7 +111,7 @@ lib.importJs('../../assets/js/profile.js').use(['element', 'table', 'tree', 'for
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initTable(postData) {
|
function renderTable(postData) {
|
||||||
var routeTable = table.render({
|
var routeTable = table.render({
|
||||||
elem: '#routeTable'
|
elem: '#routeTable'
|
||||||
, toolbar: '#toolbar'
|
, toolbar: '#toolbar'
|
||||||
@@ -110,9 +119,20 @@ lib.importJs('../../assets/js/profile.js').use(['element', 'table', 'tree', 'for
|
|||||||
, where: postData
|
, where: postData
|
||||||
, cellMinWidth: 80 //全局定义常规单元格的最小宽度,layui 2.2.1 新增
|
, cellMinWidth: 80 //全局定义常规单元格的最小宽度,layui 2.2.1 新增
|
||||||
, cols: [[
|
, cols: [[
|
||||||
{field: 'id', title: 'id(接口名+版本号)', width: 250}
|
{field: 'id', title: 'id(接口名+版本号)'}
|
||||||
, {field: 'uri', title: 'uri', width: 200}
|
, {field: 'uri', title: 'uri', width: 200}
|
||||||
, {field: 'path', title: 'path', width: 200}
|
, {field: 'path', title: 'path'}
|
||||||
|
, {field: 'roles', title: '访问权限', width: 100, templet: function (row) {
|
||||||
|
if (!row.permission) {
|
||||||
|
return '(公开)';
|
||||||
|
}
|
||||||
|
var html = [];
|
||||||
|
var roles = row.roles;
|
||||||
|
for (var i = 0; i < roles.length; i++) {
|
||||||
|
html.push(roles[i].description);
|
||||||
|
}
|
||||||
|
return html.length > 0 ? html.join(', ') : '<span class="x-red">未授权</span>';
|
||||||
|
}}
|
||||||
, {
|
, {
|
||||||
field: 'ignoreValidate', width: 80, title: '忽略验证', templet: function (row) {
|
field: 'ignoreValidate', width: 80, title: '忽略验证', templet: function (row) {
|
||||||
return row.ignoreValidate ? '<span class="x-red">是</span>' : '<span>否</span>';
|
return row.ignoreValidate ? '<span class="x-red">是</span>' : '<span>否</span>';
|
||||||
@@ -129,8 +149,12 @@ lib.importJs('../../assets/js/profile.js').use(['element', 'table', 'tree', 'for
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
, {
|
, {
|
||||||
fixed: 'right', title: '操作', width: 100, templet: function (row) {
|
fixed: 'right', title: '操作', width: 150, templet: function (row) {
|
||||||
return '<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="edit">修改</a>';
|
var html = ['<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="edit">修改</a>'];
|
||||||
|
if (row.permission) {
|
||||||
|
html.push('<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="auth">授权</a>');
|
||||||
|
}
|
||||||
|
return html.join('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]]
|
]]
|
||||||
@@ -139,9 +163,9 @@ lib.importJs('../../assets/js/profile.js').use(['element', 'table', 'tree', 'for
|
|||||||
//监听单元格事件
|
//监听单元格事件
|
||||||
table.on('tool(routeTableFilter)', function(obj) {
|
table.on('tool(routeTableFilter)', function(obj) {
|
||||||
var data = obj.data;
|
var data = obj.data;
|
||||||
if(obj.event === 'edit'){
|
var event = obj.event;
|
||||||
|
if(event === 'edit'){
|
||||||
//表单初始赋值
|
//表单初始赋值
|
||||||
data.profile = profile;
|
|
||||||
data.serviceId = currentServiceId;
|
data.serviceId = currentServiceId;
|
||||||
|
|
||||||
updateForm.setData(data);
|
updateForm.setData(data);
|
||||||
@@ -152,12 +176,29 @@ lib.importJs('../../assets/js/profile.js').use(['element', 'table', 'tree', 'for
|
|||||||
,area: ['500px', '460px']
|
,area: ['500px', '460px']
|
||||||
,content: $('#updateWin') //这里content是一个DOM,注意:最好该元素要存放在body最外层,否则可能被其它的相对元素所影响
|
,content: $('#updateWin') //这里content是一个DOM,注意:最好该元素要存放在body最外层,否则可能被其它的相对元素所影响
|
||||||
});
|
});
|
||||||
|
} else if (event === 'auth') {
|
||||||
|
ApiUtil.post('route.role.get', {id: data.id}, function (resp) {
|
||||||
|
var roleList = resp.data;
|
||||||
|
var roleCode = [];
|
||||||
|
for (var i = 0; i < roleList.length; i++) {
|
||||||
|
roleCode.push(roleList[i].roleCode);
|
||||||
|
}
|
||||||
|
authForm.setData({
|
||||||
|
routeId: data.id
|
||||||
|
, roleCode: roleCode
|
||||||
|
})
|
||||||
|
layer.open({
|
||||||
|
type: 1
|
||||||
|
,title: '路由授权'
|
||||||
|
,area: ['500px', '260px']
|
||||||
|
,content: $('#authWin')
|
||||||
|
});
|
||||||
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
table.on('toolbar(routeTableFilter)', function(obj) {
|
table.on('toolbar(routeTableFilter)', function(obj) {
|
||||||
if (obj.event === 'add') {
|
if (obj.event === 'add') {
|
||||||
var data = {};
|
var data = {};
|
||||||
data.profile = profile;
|
|
||||||
data.serviceId = currentServiceId;
|
data.serviceId = currentServiceId;
|
||||||
data.id = '';
|
data.id = '';
|
||||||
// 新加的路由先设置成禁用
|
// 新加的路由先设置成禁用
|
||||||
@@ -178,4 +219,6 @@ lib.importJs('../../assets/js/profile.js').use(['element', 'table', 'tree', 'for
|
|||||||
|
|
||||||
initTree();
|
initTree();
|
||||||
|
|
||||||
|
RouteRole.loadAllRole(form, 'roleArea');
|
||||||
|
|
||||||
});
|
});
|
@@ -43,7 +43,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.oschina.durcframework</groupId>
|
<groupId>net.oschina.durcframework</groupId>
|
||||||
<artifactId>fastmybatis-spring-boot-starter</artifactId>
|
<artifactId>fastmybatis-spring-boot-starter</artifactId>
|
||||||
<version>1.5.1</version>
|
<version>1.6.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>mysql</groupId>
|
<groupId>mysql</groupId>
|
||||||
|
@@ -1,79 +0,0 @@
|
|||||||
/*
|
|
||||||
Navicat Premium Data Transfer
|
|
||||||
|
|
||||||
Source Server : mysql-localhost
|
|
||||||
Source Server Type : MySQL
|
|
||||||
Source Server Version : 50724
|
|
||||||
Source Host : localhost:3306
|
|
||||||
Source Schema : sop
|
|
||||||
|
|
||||||
Target Server Type : MySQL
|
|
||||||
Target Server Version : 50724
|
|
||||||
File Encoding : 65001
|
|
||||||
|
|
||||||
Date: 27/03/2019 20:16:41
|
|
||||||
*/
|
|
||||||
|
|
||||||
SET NAMES utf8mb4;
|
|
||||||
SET FOREIGN_KEY_CHECKS = 0;
|
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Table structure for isv_info
|
|
||||||
-- ----------------------------
|
|
||||||
DROP TABLE IF EXISTS `isv_info`;
|
|
||||||
CREATE TABLE `isv_info` (
|
|
||||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`app_key` varchar(100) NOT NULL COMMENT 'appKey',
|
|
||||||
`secret` varchar(200) NOT NULL COMMENT 'secret',
|
|
||||||
`pub_key` text COMMENT '公钥',
|
|
||||||
`pri_key` text COMMENT '私钥',
|
|
||||||
`status` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '0启用,1禁用',
|
|
||||||
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `uk_app_key` (`app_key`)
|
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='isv信息表';
|
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Table structure for perm_isv_role
|
|
||||||
-- ----------------------------
|
|
||||||
DROP TABLE IF EXISTS `perm_isv_role`;
|
|
||||||
CREATE TABLE `perm_isv_role` (
|
|
||||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`isv_info_id` bigint(20) NOT NULL COMMENT 'isv_info.id',
|
|
||||||
`role_code` varchar(50) NOT NULL COMMENT '角色code',
|
|
||||||
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `uk_user_role` (`isv_info_id`,`role_code`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='isv角色';
|
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Table structure for perm_role
|
|
||||||
-- ----------------------------
|
|
||||||
DROP TABLE IF EXISTS `perm_role`;
|
|
||||||
CREATE TABLE `perm_role` (
|
|
||||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`role_code` varchar(50) NOT NULL COMMENT '角色代码',
|
|
||||||
`description` varchar(50) NOT NULL COMMENT '角色描述',
|
|
||||||
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `uk_code` (`role_code`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色表';
|
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Table structure for perm_role_permission
|
|
||||||
-- ----------------------------
|
|
||||||
DROP TABLE IF EXISTS `perm_role_permission`;
|
|
||||||
CREATE TABLE `perm_role_permission` (
|
|
||||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`role_code` varchar(50) NOT NULL COMMENT '角色表code',
|
|
||||||
`route_id` bigint(20) NOT NULL COMMENT 'api_id',
|
|
||||||
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `uk_role_perm` (`role_code`,`route_id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色权限表';
|
|
||||||
|
|
||||||
SET FOREIGN_KEY_CHECKS = 1;
|
|
@@ -0,0 +1,18 @@
|
|||||||
|
package com.gitee.sop.adminserver.api;
|
||||||
|
|
||||||
|
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class IdParam {
|
||||||
|
@NotNull(message = "id不能为空")
|
||||||
|
@ApiDocField(description = "id")
|
||||||
|
private Long id;
|
||||||
|
}
|
@@ -1,23 +1,29 @@
|
|||||||
package com.gitee.sop.adminserver.api.isv;
|
package com.gitee.sop.adminserver.api.isv;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.gitee.easyopen.annotation.Api;
|
import com.gitee.easyopen.annotation.Api;
|
||||||
import com.gitee.easyopen.annotation.ApiService;
|
import com.gitee.easyopen.annotation.ApiService;
|
||||||
|
import com.gitee.easyopen.doc.DataType;
|
||||||
import com.gitee.easyopen.doc.annotation.ApiDoc;
|
import com.gitee.easyopen.doc.annotation.ApiDoc;
|
||||||
|
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||||
import com.gitee.easyopen.doc.annotation.ApiDocMethod;
|
import com.gitee.easyopen.doc.annotation.ApiDocMethod;
|
||||||
|
import com.gitee.easyopen.exception.ApiException;
|
||||||
import com.gitee.easyopen.util.CopyUtil;
|
import com.gitee.easyopen.util.CopyUtil;
|
||||||
import com.gitee.easyopen.util.KeyStore;
|
import com.gitee.easyopen.util.KeyStore;
|
||||||
import com.gitee.easyopen.util.RSAUtil;
|
import com.gitee.easyopen.util.RSAUtil;
|
||||||
import com.gitee.fastmybatis.core.PageInfo;
|
import com.gitee.fastmybatis.core.PageInfo;
|
||||||
import com.gitee.fastmybatis.core.query.Query;
|
import com.gitee.fastmybatis.core.query.Query;
|
||||||
import com.gitee.fastmybatis.core.support.PageEasyui;
|
|
||||||
import com.gitee.fastmybatis.core.util.MapperUtil;
|
import com.gitee.fastmybatis.core.util.MapperUtil;
|
||||||
|
import com.gitee.sop.adminserver.api.IdParam;
|
||||||
|
import com.gitee.sop.adminserver.api.isv.param.IsvInfoForm;
|
||||||
import com.gitee.sop.adminserver.api.isv.param.IsvInfoFormAdd;
|
import com.gitee.sop.adminserver.api.isv.param.IsvInfoFormAdd;
|
||||||
import com.gitee.sop.adminserver.api.isv.param.IsvInfoFormUpdate;
|
import com.gitee.sop.adminserver.api.isv.param.IsvInfoFormUpdate;
|
||||||
import com.gitee.sop.adminserver.api.isv.param.IsvPageParam;
|
import com.gitee.sop.adminserver.api.isv.param.IsvPageParam;
|
||||||
import com.gitee.sop.adminserver.api.isv.result.AppKeySecretVo;
|
import com.gitee.sop.adminserver.api.isv.result.IsvFormVO;
|
||||||
import com.gitee.sop.adminserver.api.isv.result.IsvVO;
|
import com.gitee.sop.adminserver.api.isv.result.IsvVO;
|
||||||
import com.gitee.sop.adminserver.api.isv.result.PubPriVo;
|
|
||||||
import com.gitee.sop.adminserver.api.isv.result.RoleVO;
|
import com.gitee.sop.adminserver.api.isv.result.RoleVO;
|
||||||
|
import com.gitee.sop.adminserver.bean.ChannelMsg;
|
||||||
|
import com.gitee.sop.adminserver.bean.ZookeeperContext;
|
||||||
import com.gitee.sop.adminserver.common.IdGen;
|
import com.gitee.sop.adminserver.common.IdGen;
|
||||||
import com.gitee.sop.adminserver.entity.IsvInfo;
|
import com.gitee.sop.adminserver.entity.IsvInfo;
|
||||||
import com.gitee.sop.adminserver.entity.PermIsvRole;
|
import com.gitee.sop.adminserver.entity.PermIsvRole;
|
||||||
@@ -25,9 +31,11 @@ import com.gitee.sop.adminserver.entity.PermRole;
|
|||||||
import com.gitee.sop.adminserver.mapper.IsvInfoMapper;
|
import com.gitee.sop.adminserver.mapper.IsvInfoMapper;
|
||||||
import com.gitee.sop.adminserver.mapper.PermIsvRoleMapper;
|
import com.gitee.sop.adminserver.mapper.PermIsvRoleMapper;
|
||||||
import com.gitee.sop.adminserver.mapper.PermRoleMapper;
|
import com.gitee.sop.adminserver.mapper.PermRoleMapper;
|
||||||
import com.gitee.sop.adminserver.service.PermService;
|
import com.gitee.sop.adminserver.service.RoutePermissionService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -40,6 +48,7 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
@ApiService
|
@ApiService
|
||||||
@ApiDoc("ISV管理")
|
@ApiDoc("ISV管理")
|
||||||
|
@Slf4j
|
||||||
public class IsvApi {
|
public class IsvApi {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -52,31 +61,50 @@ public class IsvApi {
|
|||||||
PermRoleMapper permRoleMapper;
|
PermRoleMapper permRoleMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
PermService permService;
|
RoutePermissionService routePermissionService;
|
||||||
|
|
||||||
@Api(name = "isv.info.page")
|
@Api(name = "isv.info.page")
|
||||||
@ApiDocMethod(description = "接入方列表")
|
@ApiDocMethod(description = "isv列表", results = {
|
||||||
PageEasyui pageIsv(IsvPageParam param) {
|
@ApiDocField(name = "pageIndex", description = "第几页", dataType = DataType.INT, example = "1"),
|
||||||
|
@ApiDocField(name = "pageSize", description = "每页几条数据", dataType = DataType.INT, example = "10"),
|
||||||
|
@ApiDocField(name = "total", description = "每页几条数据", dataType = DataType.LONG, example = "100"),
|
||||||
|
@ApiDocField(name = "rows", description = "数据", dataType = DataType.ARRAY, elementClass = IsvVO.class)
|
||||||
|
})
|
||||||
|
PageInfo<IsvVO> pageIsv(IsvPageParam param) {
|
||||||
Query query = Query.build(param);
|
Query query = Query.build(param);
|
||||||
PageInfo<IsvInfo> pageInfo = MapperUtil.query(isvInfoMapper, query);
|
PageInfo<IsvInfo> pageInfo = MapperUtil.query(isvInfoMapper, query);
|
||||||
List<IsvInfo> list = pageInfo.getList();
|
List<IsvInfo> list = pageInfo.getList();
|
||||||
|
|
||||||
List<IsvVO> retList = list.stream()
|
List<IsvVO> retList = list.stream()
|
||||||
.map(isvInfo -> {
|
.map(isvInfo -> {
|
||||||
IsvVO vo = new IsvVO();
|
return buildIsvVO(isvInfo);
|
||||||
CopyUtil.copyProperties(isvInfo, vo);
|
|
||||||
vo.setRoleList(this.buildIsvRole(isvInfo));
|
|
||||||
return vo;
|
|
||||||
})
|
})
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
PageEasyui<IsvVO> pageInfoRet = new PageEasyui<>();
|
PageInfo<IsvVO> pageInfoRet = new PageInfo<>();
|
||||||
pageInfoRet.setTotal(pageInfo.getTotal());
|
pageInfoRet.setTotal(pageInfo.getTotal());
|
||||||
pageInfoRet.setList(retList);
|
pageInfoRet.setList(retList);
|
||||||
|
|
||||||
return pageInfoRet;
|
return pageInfoRet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Api(name = "isv.info.get")
|
||||||
|
@ApiDocMethod(description = "获取isv")
|
||||||
|
IsvVO getIsvVO(IdParam param) {
|
||||||
|
IsvInfo isvInfo = isvInfoMapper.getById(param.getId());
|
||||||
|
return buildIsvVO(isvInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IsvVO buildIsvVO(IsvInfo isvInfo) {
|
||||||
|
if (isvInfo == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
IsvVO vo = new IsvVO();
|
||||||
|
CopyUtil.copyProperties(isvInfo, vo);
|
||||||
|
vo.setRoleList(this.buildIsvRole(isvInfo));
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建ISV拥有的角色
|
* 构建ISV拥有的角色
|
||||||
*
|
*
|
||||||
@@ -84,7 +112,7 @@ public class IsvApi {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
List<RoleVO> buildIsvRole(IsvInfo permClient) {
|
List<RoleVO> buildIsvRole(IsvInfo permClient) {
|
||||||
List<String> roleCodeList = permService.listClientRoleCode(permClient.getId());
|
List<String> roleCodeList = routePermissionService.listClientRoleCode(permClient.getId());
|
||||||
if (CollectionUtils.isEmpty(roleCodeList)) {
|
if (CollectionUtils.isEmpty(roleCodeList)) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
@@ -101,64 +129,99 @@ public class IsvApi {
|
|||||||
|
|
||||||
@Api(name = "isv.info.add")
|
@Api(name = "isv.info.add")
|
||||||
@ApiDocMethod(description = "添加isv")
|
@ApiDocMethod(description = "添加isv")
|
||||||
void addIsv(IsvInfoFormAdd param) {
|
@Transactional
|
||||||
|
void addIsv(IsvInfoFormAdd param) throws Exception {
|
||||||
|
if (isvInfoMapper.getByColumn("app_key", param.getAppKey()) != null) {
|
||||||
|
throw new ApiException("appKey已存在");
|
||||||
|
}
|
||||||
|
formatForm(param);
|
||||||
IsvInfo rec = new IsvInfo();
|
IsvInfo rec = new IsvInfo();
|
||||||
CopyUtil.copyPropertiesIgnoreNull(param, rec);
|
CopyUtil.copyPropertiesIgnoreNull(param, rec);
|
||||||
isvInfoMapper.saveIgnoreNull(rec);
|
isvInfoMapper.saveIgnoreNull(rec);
|
||||||
|
if (CollectionUtils.isNotEmpty(param.getRoleCode())) {
|
||||||
|
this.saveIsvRole(rec, param.getRoleCode());
|
||||||
|
}
|
||||||
|
|
||||||
this.saveClientRole(rec, param.getRoleCode());
|
this.sendChannelMsg(rec);
|
||||||
// TODO:发送消息队列到zookeeper
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Api(name = "isv.info.update")
|
@Api(name = "isv.info.update")
|
||||||
@ApiDocMethod(description = "修改isv")
|
@ApiDocMethod(description = "修改isv")
|
||||||
|
@Transactional
|
||||||
void updateIsv(IsvInfoFormUpdate param) {
|
void updateIsv(IsvInfoFormUpdate param) {
|
||||||
|
formatForm(param);
|
||||||
IsvInfo rec = isvInfoMapper.getById(param.getId());
|
IsvInfo rec = isvInfoMapper.getById(param.getId());
|
||||||
CopyUtil.copyPropertiesIgnoreNull(param, rec);
|
CopyUtil.copyPropertiesIgnoreNull(param, rec);
|
||||||
isvInfoMapper.updateIgnoreNull(rec);
|
isvInfoMapper.updateIgnoreNull(rec);
|
||||||
|
this.saveIsvRole(rec, param.getRoleCode());
|
||||||
|
|
||||||
this.saveClientRole(rec, param.getRoleCode());
|
this.sendChannelMsg(rec);
|
||||||
|
|
||||||
// syncService.syncAppSecretConfig(Sets.newHashSet(param.getApp()));
|
|
||||||
// TODO:发送消息队列到zookeeper
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Api(name = "isv.pubprikey.create")
|
private void formatForm(IsvInfoForm form) {
|
||||||
@ApiDocMethod(description = "生成公私钥")
|
if (form.getSignType() == 1) {
|
||||||
PubPriVo createPubPriKey() throws Exception {
|
form.setSecret("");
|
||||||
KeyStore keyStore = RSAUtil.createKeys();
|
} else {
|
||||||
PubPriVo vo = new PubPriVo();
|
form.setPubKey("");
|
||||||
vo.setPubKey(keyStore.getPublicKey());
|
form.setPriKey("");
|
||||||
vo.setPriKey(keyStore.getPrivateKey());
|
}
|
||||||
return vo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Api(name = "isv.appkeysecret.create")
|
private void sendChannelMsg(IsvInfo rec) {
|
||||||
@ApiDocMethod(description = "生成appkey")
|
ChannelMsg channelMsg = new ChannelMsg("update", rec);
|
||||||
AppKeySecretVo createAppKeySecret() {
|
String path = ZookeeperContext.getIsvInfoChannelPath();
|
||||||
|
String data = JSON.toJSONString(channelMsg);
|
||||||
|
try {
|
||||||
|
log.info("消息推送--ISV信息(update), path:{}, data:{}", path, data);
|
||||||
|
ZookeeperContext.updatePathData(path, data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("发送isvChannelMsg失败, path:{}, msg:{}", path, data, e);
|
||||||
|
throw new ApiException("保存失败,请查看日志");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Api(name = "isv.form.gen")
|
||||||
|
@ApiDocMethod(description = "isv表单内容一键生成")
|
||||||
|
IsvFormVO createIsvForm() throws Exception {
|
||||||
|
IsvFormVO isvFormVO = new IsvFormVO();
|
||||||
String appKey = new SimpleDateFormat("yyyyMMdd").format(new Date()) + IdGen.nextId();
|
String appKey = new SimpleDateFormat("yyyyMMdd").format(new Date()) + IdGen.nextId();
|
||||||
String secret = IdGen.uuid();
|
String secret = IdGen.uuid();
|
||||||
AppKeySecretVo vo = new AppKeySecretVo();
|
|
||||||
vo.setAppKey(appKey);
|
isvFormVO.setAppKey(appKey);
|
||||||
vo.setSecret(secret);
|
isvFormVO.setSecret(secret);
|
||||||
return vo;
|
|
||||||
|
KeyStore keyStore = RSAUtil.createKeys();
|
||||||
|
isvFormVO.setPubKey(keyStore.getPublicKey());
|
||||||
|
isvFormVO.setPriKey(keyStore.getPrivateKey());
|
||||||
|
return isvFormVO;
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveClientRole(IsvInfo isvInfo, List<String> roleCodeList) {
|
|
||||||
|
|
||||||
|
void saveIsvRole(IsvInfo isvInfo, List<String> roleCodeList) {
|
||||||
Query query = new Query();
|
Query query = new Query();
|
||||||
long isvInfoId = isvInfo.getId();
|
long isvInfoId = isvInfo.getId();
|
||||||
query.eq("isv_info_id", isvInfoId);
|
query.eq("isv_id", isvInfoId);
|
||||||
permIsvRoleMapper.deleteByQuery(query);
|
permIsvRoleMapper.deleteByQuery(query);
|
||||||
|
|
||||||
List<PermIsvRole> tobeSaveList = roleCodeList.stream()
|
List<PermIsvRole> tobeSaveList = roleCodeList.stream()
|
||||||
.map(roleCode -> {
|
.map(roleCode -> {
|
||||||
PermIsvRole rec = new PermIsvRole();
|
PermIsvRole rec = new PermIsvRole();
|
||||||
rec.setIsvInfoId(isvInfoId);
|
rec.setIsvId(isvInfoId);
|
||||||
rec.setRoleCode(roleCode);
|
rec.setRoleCode(roleCode);
|
||||||
return rec;
|
return rec;
|
||||||
})
|
})
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (CollectionUtils.isNotEmpty(tobeSaveList)) {
|
||||||
permIsvRoleMapper.saveBatch(tobeSaveList);
|
permIsvRoleMapper.saveBatch(tobeSaveList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
routePermissionService.sendIsvRolePermissionToZookeeper(isvInfo.getAppKey(), roleCodeList);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("保存到zookeeper中失败,isvInfo:{}, roleCodeList:{}", isvInfo, roleCodeList);
|
||||||
|
throw new ApiException("保存失败,请查看日志");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,43 @@
|
|||||||
|
package com.gitee.sop.adminserver.api.isv;
|
||||||
|
|
||||||
|
import com.gitee.easyopen.annotation.Api;
|
||||||
|
import com.gitee.easyopen.annotation.ApiService;
|
||||||
|
import com.gitee.easyopen.doc.annotation.ApiDoc;
|
||||||
|
import com.gitee.easyopen.util.CopyUtil;
|
||||||
|
import com.gitee.fastmybatis.core.PageInfo;
|
||||||
|
import com.gitee.fastmybatis.core.query.Query;
|
||||||
|
import com.gitee.fastmybatis.core.query.Sort;
|
||||||
|
import com.gitee.fastmybatis.core.support.PageEasyui;
|
||||||
|
import com.gitee.fastmybatis.core.util.MapperUtil;
|
||||||
|
import com.gitee.sop.adminserver.api.isv.result.RoleVO;
|
||||||
|
import com.gitee.sop.adminserver.mapper.PermRoleMapper;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@ApiService
|
||||||
|
@ApiDoc("ISV管理")
|
||||||
|
@Slf4j
|
||||||
|
public class RoleApi {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
PermRoleMapper permRoleMapper;
|
||||||
|
|
||||||
|
@Api(name = "role.listall")
|
||||||
|
List<RoleVO> roleListall() {
|
||||||
|
Query query = new Query();
|
||||||
|
query.orderby("id", Sort.ASC);
|
||||||
|
return permRoleMapper.list(query).stream()
|
||||||
|
.map(permRole -> {
|
||||||
|
RoleVO vo = new RoleVO();
|
||||||
|
CopyUtil.copyProperties(permRole, vo);
|
||||||
|
return vo;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
@@ -5,7 +5,7 @@ import lombok.Data;
|
|||||||
import org.hibernate.validator.constraints.Length;
|
import org.hibernate.validator.constraints.Length;
|
||||||
|
|
||||||
import javax.validation.constraints.NotBlank;
|
import javax.validation.constraints.NotBlank;
|
||||||
import javax.validation.constraints.NotEmpty;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,11 +13,13 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class IsvInfoForm {
|
public class IsvInfoForm {
|
||||||
|
|
||||||
|
private int signType = 1;
|
||||||
|
|
||||||
/** secret, 数据库字段:secret */
|
/** secret, 数据库字段:secret */
|
||||||
@ApiDocField(description = "secret", required = true)
|
@ApiDocField(description = "secret")
|
||||||
@NotBlank(message = "secret不能为空")
|
|
||||||
@Length(max = 100,message = "secret长度不能超过100")
|
@Length(max = 100,message = "secret长度不能超过100")
|
||||||
private String secret;
|
private String secret = "";
|
||||||
|
|
||||||
/** 公钥, 数据库字段:pub_key */
|
/** 公钥, 数据库字段:pub_key */
|
||||||
@ApiDocField(description = "pubKey", required = true)
|
@ApiDocField(description = "pubKey", required = true)
|
||||||
@@ -33,6 +35,6 @@ public class IsvInfoForm {
|
|||||||
@ApiDocField(description = "状态:0:启用,1:禁用")
|
@ApiDocField(description = "状态:0:启用,1:禁用")
|
||||||
private Byte status = 0;
|
private Byte status = 0;
|
||||||
|
|
||||||
@NotEmpty(message = "角色不能为空")
|
@ApiDocField(description = "roleCode数组", elementClass = String.class)
|
||||||
private List<String> roleCode;
|
private List<String> roleCode = Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
package com.gitee.sop.adminserver.api.isv.param;
|
package com.gitee.sop.adminserver.api.isv.param;
|
||||||
|
|
||||||
|
import com.gitee.easyopen.doc.DataType;
|
||||||
|
import com.gitee.easyopen.doc.annotation.ApiDocBean;
|
||||||
|
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||||
|
import com.gitee.fastmybatis.core.query.annotation.Condition;
|
||||||
import com.gitee.fastmybatis.core.query.param.PageParam;
|
import com.gitee.fastmybatis.core.query.param.PageParam;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
@@ -9,6 +13,12 @@ import lombok.Setter;
|
|||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
@ApiDocBean(fields = {
|
||||||
|
@ApiDocField(name = "pageIndex", description = "第几页", dataType = DataType.INT, example = "1"),
|
||||||
|
@ApiDocField(name = "pageSize", description = "每页几条数据", dataType = DataType.INT, example = "10"),
|
||||||
|
})
|
||||||
public class IsvPageParam extends PageParam {
|
public class IsvPageParam extends PageParam {
|
||||||
|
@ApiDocField(name = "appKey", description = "appKey", dataType = DataType.STRING, example = "111111")
|
||||||
|
@Condition(ignoreEmptyString = true)
|
||||||
private String appKey;
|
private String appKey;
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
package com.gitee.sop.adminserver.api.isv.result;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class AppKeySecretVo {
|
|
||||||
private String appKey;
|
|
||||||
private String secret;
|
|
||||||
}
|
|
@@ -0,0 +1,23 @@
|
|||||||
|
package com.gitee.sop.adminserver.api.isv.result;
|
||||||
|
|
||||||
|
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IsvFormVO {
|
||||||
|
@ApiDocField(description = "appKey")
|
||||||
|
private String appKey;
|
||||||
|
|
||||||
|
@ApiDocField(description = "secret")
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
@ApiDocField(description = "pubKey")
|
||||||
|
private String pubKey;
|
||||||
|
|
||||||
|
@ApiDocField(description = "priKey")
|
||||||
|
private String priKey;
|
||||||
|
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
package com.gitee.sop.adminserver.api.isv.result;
|
package com.gitee.sop.adminserver.api.isv.result;
|
||||||
|
|
||||||
|
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -11,28 +12,37 @@ import java.util.List;
|
|||||||
@Data
|
@Data
|
||||||
public class IsvVO {
|
public class IsvVO {
|
||||||
/** 数据库字段:id */
|
/** 数据库字段:id */
|
||||||
|
@ApiDocField(description = "id")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/** appKey, 数据库字段:app_key */
|
/** appKey, 数据库字段:app_key */
|
||||||
|
@ApiDocField(description = "appKey")
|
||||||
private String appKey;
|
private String appKey;
|
||||||
|
|
||||||
/** secret, 数据库字段:secret */
|
/** secret, 数据库字段:secret */
|
||||||
|
@ApiDocField(description = "appKey")
|
||||||
private String secret;
|
private String secret;
|
||||||
|
|
||||||
/** 公钥, 数据库字段:pub_key */
|
/** 公钥, 数据库字段:pub_key */
|
||||||
|
@ApiDocField(description = "pubKey")
|
||||||
private String pubKey;
|
private String pubKey;
|
||||||
|
|
||||||
/** 私钥, 数据库字段:pri_key */
|
/** 私钥, 数据库字段:pri_key */
|
||||||
|
@ApiDocField(description = "priKey")
|
||||||
private String priKey;
|
private String priKey;
|
||||||
|
|
||||||
/** 0启用,1禁用, 数据库字段:status */
|
/** 0启用,1禁用, 数据库字段:status */
|
||||||
|
@ApiDocField(description = "状态:0启用,1禁用")
|
||||||
private Byte status;
|
private Byte status;
|
||||||
|
|
||||||
/** 数据库字段:gmt_create */
|
/** 数据库字段:gmt_create */
|
||||||
|
@ApiDocField(description = "添加时间")
|
||||||
private Date gmtCreate;
|
private Date gmtCreate;
|
||||||
|
|
||||||
/** 数据库字段:gmt_modified */
|
/** 数据库字段:gmt_modified */
|
||||||
|
@ApiDocField(description = "修改时间")
|
||||||
private Date gmtModified;
|
private Date gmtModified;
|
||||||
|
|
||||||
|
@ApiDocField(description = "角色列表")
|
||||||
private List<RoleVO> roleList;
|
private List<RoleVO> roleList;
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +0,0 @@
|
|||||||
package com.gitee.sop.adminserver.api.isv.result;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class PubPriVo {
|
|
||||||
private String pubKey;
|
|
||||||
private String priKey;
|
|
||||||
|
|
||||||
}
|
|
@@ -1,5 +1,6 @@
|
|||||||
package com.gitee.sop.adminserver.api.isv.result;
|
package com.gitee.sop.adminserver.api.isv.result;
|
||||||
|
|
||||||
|
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -10,7 +11,9 @@ import java.util.Date;
|
|||||||
@Data
|
@Data
|
||||||
public class RoleVO {
|
public class RoleVO {
|
||||||
private Long id;
|
private Long id;
|
||||||
|
@ApiDocField(description = "角色码")
|
||||||
private String roleCode;
|
private String roleCode;
|
||||||
|
@ApiDocField(description = "描述")
|
||||||
private String description;
|
private String description;
|
||||||
private Date gmtCreate;
|
private Date gmtCreate;
|
||||||
}
|
}
|
||||||
|
@@ -6,16 +6,29 @@ import com.gitee.easyopen.annotation.ApiService;
|
|||||||
import com.gitee.easyopen.doc.annotation.ApiDoc;
|
import com.gitee.easyopen.doc.annotation.ApiDoc;
|
||||||
import com.gitee.easyopen.doc.annotation.ApiDocMethod;
|
import com.gitee.easyopen.doc.annotation.ApiDocMethod;
|
||||||
import com.gitee.easyopen.exception.ApiException;
|
import com.gitee.easyopen.exception.ApiException;
|
||||||
import com.gitee.sop.adminserver.api.service.param.RouteSearchParam;
|
import com.gitee.fastmybatis.core.query.Query;
|
||||||
|
import com.gitee.sop.adminserver.api.isv.result.RoleVO;
|
||||||
import com.gitee.sop.adminserver.api.service.param.RouteParam;
|
import com.gitee.sop.adminserver.api.service.param.RouteParam;
|
||||||
|
import com.gitee.sop.adminserver.api.service.param.RoutePermissionParam;
|
||||||
|
import com.gitee.sop.adminserver.api.service.param.RouteSearchParam;
|
||||||
|
import com.gitee.sop.adminserver.api.service.result.RouteVO;
|
||||||
import com.gitee.sop.adminserver.api.service.result.ServiceInfo;
|
import com.gitee.sop.adminserver.api.service.result.ServiceInfo;
|
||||||
import com.gitee.sop.adminserver.bean.GatewayRouteDefinition;
|
import com.gitee.sop.adminserver.bean.GatewayRouteDefinition;
|
||||||
import com.gitee.sop.adminserver.bean.ZookeeperContext;
|
import com.gitee.sop.adminserver.bean.ZookeeperContext;
|
||||||
|
import com.gitee.sop.adminserver.entity.PermRole;
|
||||||
|
import com.gitee.sop.adminserver.entity.PermRolePermission;
|
||||||
|
import com.gitee.sop.adminserver.mapper.PermRoleMapper;
|
||||||
|
import com.gitee.sop.adminserver.mapper.PermRolePermissionMapper;
|
||||||
|
import com.gitee.sop.adminserver.service.RoutePermissionService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.curator.framework.recipes.cache.ChildData;
|
import org.apache.curator.framework.recipes.cache.ChildData;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -26,20 +39,30 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
@ApiService
|
@ApiService
|
||||||
@ApiDoc("服务管理")
|
@ApiDoc("服务管理")
|
||||||
|
@Slf4j
|
||||||
public class RouteApi {
|
public class RouteApi {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
PermRolePermissionMapper permRolePermissionMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
PermRoleMapper permRoleMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
RoutePermissionService routePermissionService;
|
||||||
|
|
||||||
@Api(name = "route.list")
|
@Api(name = "route.list")
|
||||||
@ApiDocMethod(description = "路由列表", elementClass = GatewayRouteDefinition.class)
|
@ApiDocMethod(description = "路由列表")
|
||||||
List<GatewayRouteDefinition> listRoute(RouteSearchParam param) throws Exception {
|
List<RouteVO> listRoute(RouteSearchParam param) throws Exception {
|
||||||
if (StringUtils.isBlank(param.getServiceId())) {
|
if (StringUtils.isBlank(param.getServiceId())) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
String searchPath = ZookeeperContext.getSopRouteRootPath(param.getProfile()) + "/" + param.getServiceId();
|
String searchPath = ZookeeperContext.getSopRouteRootPath() + "/" + param.getServiceId();
|
||||||
|
|
||||||
List<ChildData> childDataList = ZookeeperContext.getChildrenData(searchPath);
|
List<ChildData> childDataList = ZookeeperContext.getChildrenData(searchPath);
|
||||||
|
|
||||||
List<GatewayRouteDefinition> routeDefinitionList = childDataList.stream()
|
List<RouteVO> routeDefinitionList = childDataList.stream()
|
||||||
.map(childData -> {
|
.map(childData -> {
|
||||||
String serviceNodeData = new String(childData.getData());
|
String serviceNodeData = new String(childData.getData());
|
||||||
GatewayRouteDefinition routeDefinition = JSON.parseObject(serviceNodeData, GatewayRouteDefinition.class);
|
GatewayRouteDefinition routeDefinition = JSON.parseObject(serviceNodeData, GatewayRouteDefinition.class);
|
||||||
@@ -54,6 +77,12 @@ public class RouteApi {
|
|||||||
return isRoute && gatewayRouteDefinition.getId().contains(id);
|
return isRoute && gatewayRouteDefinition.getId().contains(id);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.map(gatewayRouteDefinition->{
|
||||||
|
RouteVO vo = new RouteVO();
|
||||||
|
BeanUtils.copyProperties(gatewayRouteDefinition, vo);
|
||||||
|
vo.setRoles(this.getRouteRole(gatewayRouteDefinition.getId()));
|
||||||
|
return vo;
|
||||||
|
})
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
return routeDefinitionList;
|
return routeDefinitionList;
|
||||||
@@ -62,7 +91,7 @@ public class RouteApi {
|
|||||||
@Api(name = "route.update")
|
@Api(name = "route.update")
|
||||||
@ApiDocMethod(description = "修改路由")
|
@ApiDocMethod(description = "修改路由")
|
||||||
void updateRoute(RouteParam param) throws Exception {
|
void updateRoute(RouteParam param) throws Exception {
|
||||||
String serviceIdPath = ZookeeperContext.getSopRouteRootPath(param.getProfile()) + "/" + param.getServiceId();
|
String serviceIdPath = ZookeeperContext.getSopRouteRootPath() + "/" + param.getServiceId();
|
||||||
String zookeeperRoutePath = serviceIdPath + "/" + param.getId();
|
String zookeeperRoutePath = serviceIdPath + "/" + param.getId();
|
||||||
String data = ZookeeperContext.getData(zookeeperRoutePath);
|
String data = ZookeeperContext.getData(zookeeperRoutePath);
|
||||||
GatewayRouteDefinition routeDefinition = JSON.parseObject(data, GatewayRouteDefinition.class);
|
GatewayRouteDefinition routeDefinition = JSON.parseObject(data, GatewayRouteDefinition.class);
|
||||||
@@ -73,7 +102,7 @@ public class RouteApi {
|
|||||||
@Api(name = "route.add")
|
@Api(name = "route.add")
|
||||||
@ApiDocMethod(description = "新增路由")
|
@ApiDocMethod(description = "新增路由")
|
||||||
void addRoute(RouteParam param) throws Exception {
|
void addRoute(RouteParam param) throws Exception {
|
||||||
String serviceIdPath = ZookeeperContext.getSopRouteRootPath(param.getProfile()) + "/" + param.getServiceId();
|
String serviceIdPath = ZookeeperContext.getSopRouteRootPath() + "/" + param.getServiceId();
|
||||||
String zookeeperRoutePath = serviceIdPath + "/" + param.getId();
|
String zookeeperRoutePath = serviceIdPath + "/" + param.getId();
|
||||||
if (ZookeeperContext.isPathExist(zookeeperRoutePath)) {
|
if (ZookeeperContext.isPathExist(zookeeperRoutePath)) {
|
||||||
throw new ApiException("id已存在");
|
throw new ApiException("id已存在");
|
||||||
@@ -90,4 +119,55 @@ public class RouteApi {
|
|||||||
ZookeeperContext.updatePathData(serviceIdPath, JSON.toJSONString(serviceInfo));
|
ZookeeperContext.updatePathData(serviceIdPath, JSON.toJSONString(serviceInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Api(name = "route.role.get")
|
||||||
|
@ApiDocMethod(description = "获取路由对应的角色", elementClass = RoleVO.class)
|
||||||
|
List<RoleVO> getRouteRole(RouteSearchParam param) {
|
||||||
|
if (StringUtils.isBlank(param.getId())) {
|
||||||
|
throw new ApiException("id不能为空");
|
||||||
|
}
|
||||||
|
return this.getRouteRole(param.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RoleVO> getRouteRole(String id) {
|
||||||
|
return permRolePermissionMapper.listByColumn("route_id", id)
|
||||||
|
.stream()
|
||||||
|
.map(permRolePermission -> {
|
||||||
|
RoleVO vo = new RoleVO();
|
||||||
|
String roleCode = permRolePermission.getRoleCode();
|
||||||
|
PermRole permRole = permRoleMapper.getByColumn("role_code", roleCode);
|
||||||
|
BeanUtils.copyProperties(permRole, vo);
|
||||||
|
return vo;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Api(name = "route.role.update")
|
||||||
|
@ApiDocMethod(description = "更新路由对应的角色")
|
||||||
|
void updateRouteRole(RoutePermissionParam param) {
|
||||||
|
String routeId = param.getRouteId();
|
||||||
|
// 删除所有数据
|
||||||
|
Query delQuery = new Query();
|
||||||
|
delQuery.eq("route_id", routeId);
|
||||||
|
permRolePermissionMapper.deleteByQuery(delQuery);
|
||||||
|
|
||||||
|
List<String> roleCodes = param.getRoleCode();
|
||||||
|
if (CollectionUtils.isNotEmpty(roleCodes)) {
|
||||||
|
List<PermRolePermission> tobeSave = new ArrayList<>(roleCodes.size());
|
||||||
|
for (String roleCode : roleCodes) {
|
||||||
|
PermRolePermission permRolePermission = new PermRolePermission();
|
||||||
|
permRolePermission.setRoleCode(roleCode);
|
||||||
|
permRolePermission.setRouteId(routeId);
|
||||||
|
tobeSave.add(permRolePermission);
|
||||||
|
}
|
||||||
|
// 批量添加
|
||||||
|
permRolePermissionMapper.saveBatch(tobeSave);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
routePermissionService.sendRoutePermissionReloadToZookeeper();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.info("消息推送--路由权限(reload)失败",e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -53,7 +53,7 @@ public class ServiceApi {
|
|||||||
@Api(name = "service.list")
|
@Api(name = "service.list")
|
||||||
@ApiDocMethod(description = "服务列表(旧)", elementClass = ServiceInfo.class)
|
@ApiDocMethod(description = "服务列表(旧)", elementClass = ServiceInfo.class)
|
||||||
List<ServiceInfo> listServiceInfo(ServiceSearchParam param) throws Exception {
|
List<ServiceInfo> listServiceInfo(ServiceSearchParam param) throws Exception {
|
||||||
String routeRootPath = ZookeeperContext.getSopRouteRootPath(param.getProfile());
|
String routeRootPath = ZookeeperContext.getSopRouteRootPath();
|
||||||
List<ChildData> childDataList = ZookeeperContext.getChildrenData(routeRootPath);
|
List<ChildData> childDataList = ZookeeperContext.getChildrenData(routeRootPath);
|
||||||
List<ServiceInfo> serviceInfoList = childDataList.stream()
|
List<ServiceInfo> serviceInfoList = childDataList.stream()
|
||||||
.filter(childData -> childData.getData() != null && childData.getData().length > 0)
|
.filter(childData -> childData.getData() != null && childData.getData().length > 0)
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
package com.gitee.sop.adminserver.api.service.param;
|
package com.gitee.sop.adminserver.api.service.param;
|
||||||
|
|
||||||
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||||
import lombok.Data;
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
import javax.validation.constraints.NotBlank;
|
import javax.validation.constraints.NotBlank;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
@@ -9,13 +10,10 @@ import javax.validation.constraints.NotNull;
|
|||||||
/**
|
/**
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
@Data
|
@Getter
|
||||||
|
@Setter
|
||||||
public class RouteParam {
|
public class RouteParam {
|
||||||
|
|
||||||
@NotBlank(message = "profile不能为空")
|
|
||||||
@ApiDocField(description = "profile")
|
|
||||||
private String profile;
|
|
||||||
|
|
||||||
@NotBlank(message = "serviceId不能为空")
|
@NotBlank(message = "serviceId不能为空")
|
||||||
@ApiDocField(description = "serviceId")
|
@ApiDocField(description = "serviceId")
|
||||||
private String serviceId;
|
private String serviceId;
|
||||||
|
@@ -0,0 +1,22 @@
|
|||||||
|
package com.gitee.sop.adminserver.api.service.param;
|
||||||
|
|
||||||
|
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class RoutePermissionParam {
|
||||||
|
@ApiDocField(description = "routeId", required = true)
|
||||||
|
@NotBlank(message = "routeId不能为空")
|
||||||
|
private String routeId;
|
||||||
|
|
||||||
|
@ApiDocField(description = "角色")
|
||||||
|
private List<String> roleCode;
|
||||||
|
}
|
@@ -1,17 +1,16 @@
|
|||||||
package com.gitee.sop.adminserver.api.service.param;
|
package com.gitee.sop.adminserver.api.service.param;
|
||||||
|
|
||||||
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||||
import lombok.Data;
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
@Data
|
@Getter
|
||||||
|
@Setter
|
||||||
public class ServiceSearchParam {
|
public class ServiceSearchParam {
|
||||||
|
|
||||||
@ApiDocField(description = "profile")
|
|
||||||
private String profile;
|
|
||||||
|
|
||||||
@ApiDocField(description = "serviceId")
|
@ApiDocField(description = "serviceId")
|
||||||
private String serviceId;
|
private String serviceId;
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,17 @@
|
|||||||
|
package com.gitee.sop.adminserver.api.service.result;
|
||||||
|
|
||||||
|
import com.gitee.sop.adminserver.api.isv.result.RoleVO;
|
||||||
|
import com.gitee.sop.adminserver.bean.GatewayRouteDefinition;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class RouteVO extends GatewayRouteDefinition {
|
||||||
|
private List<RoleVO> roles;
|
||||||
|
}
|
@@ -17,18 +17,5 @@ import java.util.stream.Stream;
|
|||||||
@ApiDoc("系统接口")
|
@ApiDoc("系统接口")
|
||||||
public class SystemApi {
|
public class SystemApi {
|
||||||
|
|
||||||
@Value("${sop-admin.profiles}")
|
|
||||||
private String profiles;
|
|
||||||
|
|
||||||
private List<String> profileList;
|
|
||||||
|
|
||||||
@ApiDocMethod(description = "获取profile列表")
|
|
||||||
@Api(name = "system.profile.list")
|
|
||||||
public List<String> listProfiles() {
|
|
||||||
if (profileList == null) {
|
|
||||||
String[] arr = profiles.split("\\,");
|
|
||||||
profileList = Stream.of(arr).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
return profileList;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,18 @@
|
|||||||
|
package com.gitee.sop.adminserver.bean;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ChannelMsg {
|
||||||
|
|
||||||
|
public ChannelMsg(String operation, Object data) {
|
||||||
|
this.operation = operation;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String operation;
|
||||||
|
private Object data;
|
||||||
|
}
|
@@ -54,4 +54,9 @@ public class GatewayRouteDefinition {
|
|||||||
* 合并结果
|
* 合并结果
|
||||||
*/
|
*/
|
||||||
private int mergeResult = 1;
|
private int mergeResult = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要授权才能访问
|
||||||
|
*/
|
||||||
|
private int permission;
|
||||||
}
|
}
|
@@ -0,0 +1,16 @@
|
|||||||
|
package com.gitee.sop.adminserver.bean;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* isv授权过的路由
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IsvRoutePermission {
|
||||||
|
private String appKey;
|
||||||
|
private List<String> routeIdList;
|
||||||
|
private String routeIdListMd5;
|
||||||
|
}
|
@@ -9,4 +9,14 @@ public class SopAdminConstants {
|
|||||||
*/
|
*/
|
||||||
public static final String SOP_SERVICE_ROUTE_PATH = "/com.gitee.sop.route";
|
public static final String SOP_SERVICE_ROUTE_PATH = "/com.gitee.sop.route";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* zookeeper存放路由授权信息根目录
|
||||||
|
*/
|
||||||
|
public static final String SOP_ROUTE_PERMISSION_PATH = "/com.gitee.sop.isv.route.permission";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息监听路径
|
||||||
|
*/
|
||||||
|
public static final String SOP_MSG_CHANNEL_PATH = "/com.gitee.sop.channel";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -16,14 +16,15 @@ import javax.annotation.PostConstruct;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.gitee.sop.adminserver.bean.SopAdminConstants.SOP_MSG_CHANNEL_PATH;
|
||||||
|
import static com.gitee.sop.adminserver.bean.SopAdminConstants.SOP_ROUTE_PERMISSION_PATH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class ZookeeperContext {
|
public class ZookeeperContext {
|
||||||
|
|
||||||
public static final String SOP_ROUTE_ROOT_PATH = SopAdminConstants.SOP_SERVICE_ROUTE_PATH + "-%s";
|
|
||||||
|
|
||||||
private static CuratorFramework client;
|
private static CuratorFramework client;
|
||||||
|
|
||||||
@Value("${spring.cloud.zookeeper.connect-string}")
|
@Value("${spring.cloud.zookeeper.connect-string}")
|
||||||
@@ -37,6 +38,9 @@ public class ZookeeperContext {
|
|||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
protected void after() {
|
protected void after() {
|
||||||
|
if (StringUtils.isBlank(zookeeperServerAddr)) {
|
||||||
|
throw new IllegalArgumentException("未指定spring.cloud.zookeeper.connect-string参数");
|
||||||
|
}
|
||||||
CuratorFramework client = CuratorFrameworkFactory.builder()
|
CuratorFramework client = CuratorFrameworkFactory.builder()
|
||||||
.connectString(zookeeperServerAddr)
|
.connectString(zookeeperServerAddr)
|
||||||
.retryPolicy(new ExponentialBackoffRetry(NumberUtils.toInt(baseSleepTimeMs, 3000), NumberUtils.toInt(maxRetries, 3)))
|
.retryPolicy(new ExponentialBackoffRetry(NumberUtils.toInt(baseSleepTimeMs, 3000), NumberUtils.toInt(maxRetries, 3)))
|
||||||
@@ -47,11 +51,20 @@ public class ZookeeperContext {
|
|||||||
setClient(client);
|
setClient(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getSopRouteRootPath(String profile) {
|
public static String getSopRouteRootPath() {
|
||||||
if (StringUtils.isBlank(profile)) {
|
return SopAdminConstants.SOP_SERVICE_ROUTE_PATH;
|
||||||
profile = "default";
|
|
||||||
}
|
}
|
||||||
return String.format(SOP_ROUTE_ROOT_PATH, profile);
|
|
||||||
|
public static String getRoutePermissionPath() {
|
||||||
|
return SOP_ROUTE_PERMISSION_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getIsvInfoChannelPath() {
|
||||||
|
return SOP_MSG_CHANNEL_PATH + "/isvinfo";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getIsvRoutePermissionChannelPath() {
|
||||||
|
return SOP_MSG_CHANNEL_PATH + "/isv-route-permission";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CuratorFramework getClient() {
|
public static CuratorFramework getClient() {
|
||||||
@@ -71,7 +84,7 @@ public class ZookeeperContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对已存在的path赋值
|
* 对已存在的path赋值。如果path不存在抛异常
|
||||||
*
|
*
|
||||||
* @param path 已存在的
|
* @param path 已存在的
|
||||||
* @param data
|
* @param data
|
||||||
@@ -86,7 +99,7 @@ public class ZookeeperContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建新的path,并赋值
|
* 创建新的path,并赋值。如果path已存在抛异常
|
||||||
* @param path 待创建的path
|
* @param path 待创建的path
|
||||||
* @param data 值
|
* @param data 值
|
||||||
*/
|
*/
|
||||||
@@ -100,6 +113,22 @@ public class ZookeeperContext {
|
|||||||
.forPath(path, data.getBytes());
|
.forPath(path, data.getBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新建或保存节点
|
||||||
|
* @param path
|
||||||
|
* @param data
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static String createOrUpdateData(String path, String data) throws Exception {
|
||||||
|
return getClient().create()
|
||||||
|
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
|
||||||
|
.orSetData()
|
||||||
|
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
||||||
|
.creatingParentContainersIfNeeded()
|
||||||
|
.forPath(path, data.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
public static String getData(String path) throws Exception {
|
public static String getData(String path) throws Exception {
|
||||||
if (!isPathExist(path)) {
|
if (!isPathExist(path)) {
|
||||||
return null;
|
return null;
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
package com.gitee.sop.adminserver.config;
|
package com.gitee.sop.adminserver.config;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||||
|
import com.gitee.easyopen.ApiConfig;
|
||||||
|
import com.gitee.easyopen.ResultSerializer;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
@@ -14,6 +19,14 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupp
|
|||||||
public class WebConfig extends WebMvcConfigurationSupport {
|
public class WebConfig extends WebMvcConfigurationSupport {
|
||||||
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
ApiConfig apiConfig() {
|
||||||
|
ApiConfig apiConfig = new ApiConfig();
|
||||||
|
apiConfig.setJsonResultSerializer(obj -> {
|
||||||
|
return JSON.toJSONString(obj, SerializerFeature.WriteDateUseDateFormat);
|
||||||
|
});
|
||||||
|
return apiConfig;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
|
@@ -26,8 +26,8 @@ public class PermIsvRole {
|
|||||||
/** 数据库字段:id */
|
/** 数据库字段:id */
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/** isv_info.id, 数据库字段:isv_info_id */
|
/** isv_info表id, 数据库字段:isv_id */
|
||||||
private Long isvInfoId;
|
private Long isvId;
|
||||||
|
|
||||||
/** 角色code, 数据库字段:role_code */
|
/** 角色code, 数据库字段:role_code */
|
||||||
private String roleCode;
|
private String roleCode;
|
||||||
|
@@ -30,7 +30,7 @@ public class PermRolePermission {
|
|||||||
private String roleCode;
|
private String roleCode;
|
||||||
|
|
||||||
/** api_id, 数据库字段:route_id */
|
/** api_id, 数据库字段:route_id */
|
||||||
private Long routeId;
|
private String routeId;
|
||||||
|
|
||||||
/** 数据库字段:gmt_create */
|
/** 数据库字段:gmt_create */
|
||||||
private Date gmtCreate;
|
private Date gmtCreate;
|
||||||
|
@@ -1,34 +0,0 @@
|
|||||||
package com.gitee.sop.adminserver.service;
|
|
||||||
|
|
||||||
import com.gitee.sop.adminserver.entity.PermIsvRole;
|
|
||||||
import com.gitee.sop.adminserver.mapper.PermIsvRoleMapper;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author thc
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
public class PermService {
|
|
||||||
@Autowired
|
|
||||||
PermIsvRoleMapper permClientRoleMapper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取客户端角色码列表
|
|
||||||
*
|
|
||||||
* @param clientId
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public List<String> listClientRoleCode(Long clientId) {
|
|
||||||
List<PermIsvRole> list = permClientRoleMapper.listByColumn("client_id", clientId);
|
|
||||||
List<String> retList = new ArrayList<>(list.size());
|
|
||||||
for (PermIsvRole permClientRole : list) {
|
|
||||||
retList.add(permClientRole.getRoleCode());
|
|
||||||
}
|
|
||||||
return retList;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -0,0 +1,104 @@
|
|||||||
|
package com.gitee.sop.adminserver.service;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.gitee.fastmybatis.core.query.Query;
|
||||||
|
import com.gitee.sop.adminserver.bean.ChannelMsg;
|
||||||
|
import com.gitee.sop.adminserver.bean.IsvRoutePermission;
|
||||||
|
import com.gitee.sop.adminserver.bean.ZookeeperContext;
|
||||||
|
import com.gitee.sop.adminserver.entity.PermIsvRole;
|
||||||
|
import com.gitee.sop.adminserver.entity.PermRolePermission;
|
||||||
|
import com.gitee.sop.adminserver.mapper.IsvInfoMapper;
|
||||||
|
import com.gitee.sop.adminserver.mapper.PermIsvRoleMapper;
|
||||||
|
import com.gitee.sop.adminserver.mapper.PermRolePermissionMapper;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 路由权限业务类
|
||||||
|
*
|
||||||
|
* @author thc
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class RoutePermissionService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
IsvInfoMapper isvInfoMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
PermIsvRoleMapper permIsvRoleMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
PermRolePermissionMapper permRolePermissionMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取客户端角色码列表
|
||||||
|
*
|
||||||
|
* @param isvId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<String> listClientRoleCode(long isvId) {
|
||||||
|
List<PermIsvRole> list = permIsvRoleMapper.listByColumn("isv_id", isvId);
|
||||||
|
return list.stream()
|
||||||
|
.map(PermIsvRole::getRoleCode)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送isv路由权限到zookeeper
|
||||||
|
*
|
||||||
|
* @param appKey
|
||||||
|
* @param roleCodeList
|
||||||
|
*/
|
||||||
|
public void sendIsvRolePermissionToZookeeper(String appKey, List<String> roleCodeList) throws Exception {
|
||||||
|
Collections.sort(roleCodeList);
|
||||||
|
List<String> routeIdList = this.getRouteIdList(roleCodeList);
|
||||||
|
String roleCodeListMd5 = DigestUtils.md5Hex(JSON.toJSONString(routeIdList));
|
||||||
|
IsvRoutePermission isvRoutePermission = new IsvRoutePermission();
|
||||||
|
isvRoutePermission.setAppKey(appKey);
|
||||||
|
isvRoutePermission.setRouteIdList(routeIdList);
|
||||||
|
isvRoutePermission.setRouteIdListMd5(roleCodeListMd5);
|
||||||
|
ChannelMsg channelMsg = new ChannelMsg("update", isvRoutePermission);
|
||||||
|
String jsonData = JSON.toJSONString(channelMsg);
|
||||||
|
String path = ZookeeperContext.getIsvRoutePermissionChannelPath();
|
||||||
|
log.info("消息推送--路由权限(update), path:{}, data:{}", path, jsonData);
|
||||||
|
ZookeeperContext.createOrUpdateData(path, jsonData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取角色对应的路由
|
||||||
|
*
|
||||||
|
* @param roleCodeList
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<String> getRouteIdList(List<String> roleCodeList) {
|
||||||
|
if (CollectionUtils.isEmpty(roleCodeList)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
Query query = new Query();
|
||||||
|
query.in("role_code", roleCodeList);
|
||||||
|
List<PermRolePermission> rolePermissionList = permRolePermissionMapper.list(query);
|
||||||
|
return rolePermissionList.stream()
|
||||||
|
.map(PermRolePermission::getRouteId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送所有路由权限到zookeeper
|
||||||
|
*/
|
||||||
|
public void sendRoutePermissionReloadToZookeeper() throws Exception {
|
||||||
|
ChannelMsg channelMsg = new ChannelMsg("reload", null);
|
||||||
|
String jsonData = JSON.toJSONString(channelMsg);
|
||||||
|
String path = ZookeeperContext.getIsvRoutePermissionChannelPath();
|
||||||
|
log.info("消息推送--路由权限(reload), path:{}, data:{}", path, jsonData);
|
||||||
|
ZookeeperContext.createOrUpdateData(path, jsonData);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
server:
|
server:
|
||||||
port: 8082
|
port: 8082
|
||||||
|
|
||||||
|
# 不用改
|
||||||
spring:
|
spring:
|
||||||
application:
|
application:
|
||||||
name: sop-admin
|
name: sop-admin
|
||||||
@@ -27,11 +27,7 @@ easyopen:
|
|||||||
mono: false
|
mono: false
|
||||||
ignore-validate: true
|
ignore-validate: true
|
||||||
|
|
||||||
# 默认profile列表,新增可以在后面加
|
# 注册中心地址,根据实际情况改,这里只是参数,并不会去注册
|
||||||
sop-admin:
|
|
||||||
profiles: default,prod,dev,test
|
|
||||||
|
|
||||||
# 注册中心地址,这里只是参数,并不会去注册
|
|
||||||
eureka:
|
eureka:
|
||||||
port: 1111
|
port: 1111
|
||||||
host: localhost
|
host: localhost
|
||||||
@@ -39,8 +35,13 @@ eureka:
|
|||||||
serviceUrl:
|
serviceUrl:
|
||||||
defaultZone: http://${eureka.host}:${eureka.port}/eureka/
|
defaultZone: http://${eureka.host}:${eureka.port}/eureka/
|
||||||
|
|
||||||
|
# 根据实际情况改
|
||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
com:
|
com:
|
||||||
gitee: debug
|
gitee: debug
|
||||||
|
|
||||||
|
# 不用改
|
||||||
|
mybatis:
|
||||||
|
fill: {com.gitee.fastmybatis.core.support.DateFillInsert: gmt_create,
|
||||||
|
com.gitee.fastmybatis.core.support.DateFillUpdate: gmt_modified}
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
<artifactId>sop-common</artifactId>
|
<artifactId>sop-common</artifactId>
|
||||||
<version>1.0.0-SNAPSHOT</version>
|
<version>1.1.0-SNAPSHOT</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
<artifactId>sop-common</artifactId>
|
<artifactId>sop-common</artifactId>
|
||||||
<version>1.0.0-SNAPSHOT</version>
|
<version>1.1.0-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
|
@@ -2,11 +2,13 @@ package com.gitee.sop.gatewaycommon.bean;
|
|||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.param.GatewayParamBuilder;
|
import com.gitee.sop.gatewaycommon.gateway.param.GatewayParamBuilder;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.result.GatewayResult;
|
import com.gitee.sop.gatewaycommon.gateway.result.GatewayResult;
|
||||||
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.result.GatewayResultExecutor;
|
import com.gitee.sop.gatewaycommon.gateway.result.GatewayResultExecutor;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.DefaultIsvRoutePermissionManager;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
|
||||||
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
|
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
|
||||||
import com.gitee.sop.gatewaycommon.secret.AppSecretManager;
|
import com.gitee.sop.gatewaycommon.secret.CacheIsvManager;
|
||||||
import com.gitee.sop.gatewaycommon.secret.CacheAppSecretManager;
|
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
||||||
import com.gitee.sop.gatewaycommon.session.ApiSessionManager;
|
import com.gitee.sop.gatewaycommon.session.ApiSessionManager;
|
||||||
import com.gitee.sop.gatewaycommon.session.SessionManager;
|
import com.gitee.sop.gatewaycommon.session.SessionManager;
|
||||||
import com.gitee.sop.gatewaycommon.validate.ApiEncrypter;
|
import com.gitee.sop.gatewaycommon.validate.ApiEncrypter;
|
||||||
@@ -48,9 +50,9 @@ public class ApiConfig {
|
|||||||
private ResultExecutor<RequestContext, String> zuulResultExecutor = new ZuulResultExecutor();
|
private ResultExecutor<RequestContext, String> zuulResultExecutor = new ZuulResultExecutor();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* app秘钥管理
|
* isv管理
|
||||||
*/
|
*/
|
||||||
private AppSecretManager appSecretManager = new CacheAppSecretManager();
|
private IsvManager isvManager = new CacheIsvManager();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加密工具
|
* 加密工具
|
||||||
@@ -87,12 +89,18 @@ public class ApiConfig {
|
|||||||
*/
|
*/
|
||||||
private ZuulErrorController zuulErrorController = new ZuulErrorController();
|
private ZuulErrorController zuulErrorController = new ZuulErrorController();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* isv路由权限
|
||||||
|
*/
|
||||||
|
private IsvRoutePermissionManager isvRoutePermissionManager = new DefaultIsvRoutePermissionManager();
|
||||||
|
|
||||||
|
// -------- fields ---------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 错误模块
|
* 错误模块
|
||||||
*/
|
*/
|
||||||
private List<String> i18nModules = new ArrayList<>();
|
private List<String> i18nModules = new ArrayList<>();
|
||||||
|
|
||||||
// -------- fields ---------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 忽略验证,设置true,则所有接口不会进行签名校验
|
* 忽略验证,设置true,则所有接口不会进行签名校验
|
||||||
@@ -111,7 +119,9 @@ public class ApiConfig {
|
|||||||
private int timeoutSeconds = 60 * 5;
|
private int timeoutSeconds = 60 * 5;
|
||||||
|
|
||||||
public void addAppSecret(Map<String, String> appSecretPair) {
|
public void addAppSecret(Map<String, String> appSecretPair) {
|
||||||
this.appSecretManager.addAppSecret(appSecretPair);
|
for (Map.Entry<String, String> entry : appSecretPair.entrySet()) {
|
||||||
|
this.isvManager.update(new IsvDefinition(entry.getKey(), entry.getValue()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ApiConfig getInstance() {
|
public static ApiConfig getInstance() {
|
||||||
|
@@ -44,6 +44,11 @@ public class BaseRouteDefinition {
|
|||||||
*/
|
*/
|
||||||
private int mergeResult;
|
private int mergeResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接口是否需要授权才能访问
|
||||||
|
*/
|
||||||
|
private int permission;
|
||||||
|
|
||||||
public boolean enable() {
|
public boolean enable() {
|
||||||
return status == RouteStatus.ENABLE.getStatus();
|
return status == RouteStatus.ENABLE.getStatus();
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.bean;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ChannelMsg {
|
||||||
|
private String operation = "_unknown_";
|
||||||
|
private String data = "{}";
|
||||||
|
}
|
@@ -0,0 +1,25 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.bean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public interface Isv {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* appKey
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String getAppKey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 秘钥
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String getSecretInfo();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 0启用,1禁用
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Byte getStatus();
|
||||||
|
}
|
@@ -0,0 +1,55 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.bean;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class IsvDefinition implements Isv {
|
||||||
|
|
||||||
|
public IsvDefinition() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public IsvDefinition(String appKey, String secret) {
|
||||||
|
this.appKey = appKey;
|
||||||
|
this.secret = secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String appKey;
|
||||||
|
|
||||||
|
/** 秘钥,如果是支付宝开放平台,对应的pubKey */
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
private String pubKey;
|
||||||
|
|
||||||
|
/** 0启用,1禁用 */
|
||||||
|
private Byte status;
|
||||||
|
|
||||||
|
private Date gmtCreate;
|
||||||
|
|
||||||
|
private Date gmtModified;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSecretInfo() {
|
||||||
|
return StringUtils.isBlank(pubKey) ? secret : pubKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "IsvDefinition{" +
|
||||||
|
"id=" + id +
|
||||||
|
", appKey='" + appKey + '\'' +
|
||||||
|
", status=" + status +
|
||||||
|
", gmtCreate=" + gmtCreate +
|
||||||
|
", gmtModified=" + gmtModified +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,18 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.bean;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* isv授权过的路由
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IsvRoutePermission {
|
||||||
|
private String appKey;
|
||||||
|
private List<String> routeIdList = Collections.emptyList();
|
||||||
|
private String routeIdListMd5;
|
||||||
|
|
||||||
|
}
|
@@ -38,4 +38,16 @@ public class SopConstants {
|
|||||||
*/
|
*/
|
||||||
public static final String SOP_SERVICE_ROUTE_PATH = "/com.gitee.sop.route";
|
public static final String SOP_SERVICE_ROUTE_PATH = "/com.gitee.sop.route";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* zookeeper存放路由授权信息根目录
|
||||||
|
*/
|
||||||
|
public static final String SOP_ROUTE_PERMISSION_PATH = "/com.gitee.sop.isv.route.permission";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息监听路径
|
||||||
|
*/
|
||||||
|
public static final String SOP_MSG_CHANNEL_PATH = "/com.gitee.sop.channel";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,9 @@ public class AbstractConfiguration {
|
|||||||
if (RouteRepositoryContext.getRouteRepository() == null) {
|
if (RouteRepositoryContext.getRouteRepository() == null) {
|
||||||
throw new IllegalArgumentException("RouteRepositoryContext.setRouteRepository()方法未使用");
|
throw new IllegalArgumentException("RouteRepositoryContext.setRouteRepository()方法未使用");
|
||||||
}
|
}
|
||||||
|
EnvironmentContext.setEnvironment(environment);
|
||||||
|
ZookeeperContext.setEnvironment(environment);
|
||||||
|
|
||||||
initMessage();
|
initMessage();
|
||||||
apiMetaManager.refresh();
|
apiMetaManager.refresh();
|
||||||
doAfter();
|
doAfter();
|
||||||
|
@@ -6,19 +6,14 @@ import com.gitee.sop.gatewaycommon.bean.BaseServiceRouteInfo;
|
|||||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.curator.framework.CuratorFramework;
|
import org.apache.curator.framework.CuratorFramework;
|
||||||
import org.apache.curator.framework.CuratorFrameworkFactory;
|
|
||||||
import org.apache.curator.framework.recipes.cache.ChildData;
|
import org.apache.curator.framework.recipes.cache.ChildData;
|
||||||
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
|
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
|
||||||
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
|
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
|
||||||
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
|
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
|
||||||
import org.apache.curator.retry.ExponentialBackoffRetry;
|
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_SERVICE_ROUTE_PATH;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 路由管理,采用zookeeper实现,监听路由的增删改,并适时更新到本地。路由的存储格式为:
|
* 路由管理,采用zookeeper实现,监听路由的增删改,并适时更新到本地。路由的存储格式为:
|
||||||
@@ -70,38 +65,26 @@ public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E exte
|
|||||||
public BaseRouteManager(Environment environment, RouteRepository<T> routeRepository) {
|
public BaseRouteManager(Environment environment, RouteRepository<T> routeRepository) {
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
this.routeRepository = routeRepository;
|
this.routeRepository = routeRepository;
|
||||||
String profile = environment.getProperty("spring.profiles.active", "default");
|
this.routeRootPath = ZookeeperContext.getRouteRootPath();
|
||||||
this.routeRootPath = SOP_SERVICE_ROUTE_PATH + "-" + profile;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
try {
|
this.refreshRouteInfo();
|
||||||
String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string");
|
|
||||||
String profile = environment.getProperty("spring.profiles.active", "default");
|
|
||||||
if (StringUtils.isEmpty(zookeeperServerAddr)) {
|
|
||||||
throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数");
|
|
||||||
}
|
}
|
||||||
CuratorFramework client = CuratorFrameworkFactory.builder()
|
|
||||||
.connectString(zookeeperServerAddr)
|
|
||||||
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
client.start();
|
|
||||||
|
|
||||||
client.create()
|
|
||||||
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
|
|
||||||
.orSetData()
|
|
||||||
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
|
||||||
.creatingParentContainersIfNeeded()
|
|
||||||
.forPath(routeRootPath, "".getBytes());
|
|
||||||
|
|
||||||
|
protected void refreshRouteInfo() {
|
||||||
|
try {
|
||||||
|
ZookeeperContext.createOrUpdateData(routeRootPath, "");
|
||||||
|
CuratorFramework client = ZookeeperContext.getClient();
|
||||||
this.watchServiceChange(client, routeRootPath);
|
this.watchServiceChange(client, routeRootPath);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error("刷新路由配置失败", e);
|
||||||
|
throw new IllegalStateException("刷新路由配置失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 监听微服务更改
|
* 监听微服务更改
|
||||||
*
|
*
|
||||||
|
@@ -0,0 +1,47 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.manager;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.IsvRoutePermission;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public class DefaultIsvRoutePermissionManager implements IsvRoutePermissionManager {
|
||||||
|
|
||||||
|
/** key: appKey */
|
||||||
|
protected static Map<String, IsvRoutePermission> isvRoutePermissionMap = new ConcurrentHashMap<>(64);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void load() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(IsvRoutePermission isvRoutePermission) {
|
||||||
|
IsvRoutePermission routePermission = isvRoutePermissionMap.get(isvRoutePermission.getAppKey());
|
||||||
|
if (routePermission == null) {
|
||||||
|
isvRoutePermissionMap.put(isvRoutePermission.getAppKey(), isvRoutePermission);
|
||||||
|
} else {
|
||||||
|
if (!StringUtils.equals(isvRoutePermission.getRouteIdListMd5(), routePermission.getRouteIdListMd5())) {
|
||||||
|
isvRoutePermissionMap.put(isvRoutePermission.getAppKey(), isvRoutePermission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPermission(String appKey, String routeId) {
|
||||||
|
IsvRoutePermission routePermission = isvRoutePermissionMap.get(appKey);
|
||||||
|
if (routePermission == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return routePermission.getRouteIdList().contains(routeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(String appKey) {
|
||||||
|
isvRoutePermissionMap.remove(appKey);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,26 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.manager;
|
||||||
|
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public class EnvironmentContext {
|
||||||
|
private static Environment environment;
|
||||||
|
|
||||||
|
public static Environment getEnvironment() {
|
||||||
|
return environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setEnvironment(Environment environment) {
|
||||||
|
EnvironmentContext.environment = environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getProfile(Environment env) {
|
||||||
|
return env.getProperty("spring.profiles.active", "default");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getProfile() {
|
||||||
|
return getProfile(environment);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,34 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.manager;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.IsvRoutePermission;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public interface IsvRoutePermissionManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载路由权限信息
|
||||||
|
*/
|
||||||
|
void load();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载权限
|
||||||
|
* @param isvRoutePermission
|
||||||
|
*/
|
||||||
|
void update(IsvRoutePermission isvRoutePermission);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否有权限
|
||||||
|
* @param appKey
|
||||||
|
* @param routeId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean hasPermission(String appKey, String routeId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除权限
|
||||||
|
* @param appKey
|
||||||
|
*/
|
||||||
|
void remove(String appKey);
|
||||||
|
}
|
@@ -0,0 +1,12 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.manager;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.IsvRoutePermission;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public interface RoutePermissionManager {
|
||||||
|
void load();
|
||||||
|
|
||||||
|
void update(IsvRoutePermission isvRoutePermission);
|
||||||
|
}
|
@@ -0,0 +1,194 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.manager;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.commons.lang.math.NumberUtils;
|
||||||
|
import org.apache.curator.framework.CuratorFramework;
|
||||||
|
import org.apache.curator.framework.CuratorFrameworkFactory;
|
||||||
|
import org.apache.curator.framework.recipes.cache.ChildData;
|
||||||
|
import org.apache.curator.framework.recipes.cache.NodeCache;
|
||||||
|
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
|
||||||
|
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
|
||||||
|
import org.apache.curator.retry.ExponentialBackoffRetry;
|
||||||
|
import org.apache.zookeeper.data.Stat;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_MSG_CHANNEL_PATH;
|
||||||
|
import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_SERVICE_ROUTE_PATH;
|
||||||
|
import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_ROUTE_PERMISSION_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class ZookeeperContext {
|
||||||
|
|
||||||
|
private static CuratorFramework client;
|
||||||
|
|
||||||
|
public static void setEnvironment(Environment environment) {
|
||||||
|
Assert.notNull(environment, "environment不能为null");
|
||||||
|
initZookeeperClient(environment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized static void initZookeeperClient(Environment environment) {
|
||||||
|
if (client != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string");
|
||||||
|
if (StringUtils.isBlank(zookeeperServerAddr)) {
|
||||||
|
throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数");
|
||||||
|
}
|
||||||
|
String baseSleepTimeMs = environment.getProperty("spring.cloud.zookeeper.baseSleepTimeMs");
|
||||||
|
String maxRetries = environment.getProperty("spring.cloud.zookeeper.maxRetries");
|
||||||
|
log.info("初始化zookeeper客户端,zookeeperServerAddr:{}, baseSleepTimeMs:{}, maxRetries:{}",
|
||||||
|
zookeeperServerAddr, baseSleepTimeMs, maxRetries);
|
||||||
|
CuratorFramework client = CuratorFrameworkFactory.builder()
|
||||||
|
.connectString(zookeeperServerAddr)
|
||||||
|
.retryPolicy(new ExponentialBackoffRetry(NumberUtils.toInt(baseSleepTimeMs, 3000), NumberUtils.toInt(maxRetries, 3)))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.start();
|
||||||
|
|
||||||
|
setClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getRouteRootPath() {
|
||||||
|
return SOP_SERVICE_ROUTE_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getRoutePermissionPath() {
|
||||||
|
return SOP_ROUTE_PERMISSION_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getIsvInfoChannelPath() {
|
||||||
|
return SOP_MSG_CHANNEL_PATH + "/isvinfo";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getIsvRoutePermissionChannelPath() {
|
||||||
|
return SOP_MSG_CHANNEL_PATH + "/isv-route-permission";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CuratorFramework getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setClient(CuratorFramework client) {
|
||||||
|
ZookeeperContext.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPathExist(String path) {
|
||||||
|
try {
|
||||||
|
return client.checkExists().forPath(path) != null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对已存在的path赋值。如果path不存在抛异常
|
||||||
|
*
|
||||||
|
* @param path 已存在的
|
||||||
|
* @param data
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static Stat updatePathData(String path, String data) throws Exception {
|
||||||
|
if (!isPathExist(path)) {
|
||||||
|
throw new IllegalStateException("path " + path + " 不存在");
|
||||||
|
}
|
||||||
|
return getClient().setData().forPath(path, data.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建新的path,并赋值。如果path已存在抛异常
|
||||||
|
* @param path 待创建的path
|
||||||
|
* @param data 值
|
||||||
|
*/
|
||||||
|
public static String createNewData(String path, String data) throws Exception {
|
||||||
|
if (isPathExist(path)) {
|
||||||
|
throw new IllegalStateException("path " + path + " 已存在");
|
||||||
|
}
|
||||||
|
return getClient().create()
|
||||||
|
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
||||||
|
.creatingParentContainersIfNeeded()
|
||||||
|
.forPath(path, data.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新建或保存节点
|
||||||
|
* @param path
|
||||||
|
* @param data
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static String createOrUpdateData(String path, String data) throws Exception {
|
||||||
|
return getClient().create()
|
||||||
|
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
|
||||||
|
.orSetData()
|
||||||
|
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
||||||
|
.creatingParentContainersIfNeeded()
|
||||||
|
.forPath(path, data.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听一个节点
|
||||||
|
* @param path
|
||||||
|
* @param onChange 节点修改后触发
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static String listenPath(String path, Consumer<NodeCache> onChange) throws Exception {
|
||||||
|
String ret = createOrUpdateData(path, "{}");
|
||||||
|
final NodeCache cache = new NodeCache(client, path, false);
|
||||||
|
cache.getListenable().addListener(new NodeCacheListener() {
|
||||||
|
@Override
|
||||||
|
public void nodeChanged() throws Exception {
|
||||||
|
onChange.accept(cache);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cache.start();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getData(String path) throws Exception {
|
||||||
|
if (!isPathExist(path)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] data = getClient().getData().forPath(path);
|
||||||
|
return new String(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取子节点数据
|
||||||
|
*
|
||||||
|
* @param parentPath 父节点
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static List<ChildData> getChildrenData(String parentPath) throws Exception {
|
||||||
|
PathChildrenCache pathChildrenCache = buildPathChildrenCache(parentPath);
|
||||||
|
if (pathChildrenCache == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return pathChildrenCache.getCurrentData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PathChildrenCache buildPathChildrenCache(String path) throws Exception {
|
||||||
|
if (!isPathExist(path)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
|
||||||
|
// 且第三个参数要设置为true,不然ChildData对象中的getData返回null
|
||||||
|
PathChildrenCache childrenCache = new PathChildrenCache(client, path, true);
|
||||||
|
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
|
||||||
|
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
|
||||||
|
return childrenCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -93,6 +93,10 @@ public enum ErrorEnum {
|
|||||||
ISV_INSUFFICIENT_ISV_PERMISSIONS(Codes.CODE_ISV_PERM, "isv.insufficient-isv-permissions"),
|
ISV_INSUFFICIENT_ISV_PERMISSIONS(Codes.CODE_ISV_PERM, "isv.insufficient-isv-permissions"),
|
||||||
/** 代理的商户没有当前接口权限 */
|
/** 代理的商户没有当前接口权限 */
|
||||||
ISV_INSUFFICIENT_USER_PERMISSIONS(Codes.CODE_ISV_PERM, "isv.insufficient-user-permissions"),
|
ISV_INSUFFICIENT_USER_PERMISSIONS(Codes.CODE_ISV_PERM, "isv.insufficient-user-permissions"),
|
||||||
|
/** 没有当前接口权限 */
|
||||||
|
ISV_ROUTE_NO_PERMISSIONS(Codes.CODE_ISV_PERM, "isv.route-no-permissions"),
|
||||||
|
/** 禁止访问 */
|
||||||
|
ISV_ACCESS_FORBIDDEN(Codes.CODE_ISV_PERM, "isv.access-forbidden"),
|
||||||
|
|
||||||
;
|
;
|
||||||
private ErrorMeta errorMeta;
|
private ErrorMeta errorMeta;
|
||||||
|
@@ -0,0 +1,43 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.secret;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.IsvDefinition;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public class CacheIsvManager implements IsvManager<IsvDefinition> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key: appKey
|
||||||
|
*/
|
||||||
|
private Map<String, IsvDefinition> isvCache = new ConcurrentHashMap<>(64);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void load(Function<Object, String> secretGetter) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(IsvDefinition isvInfo) {
|
||||||
|
isvCache.put(isvInfo.getAppKey(), isvInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(String appKey) {
|
||||||
|
isvCache.remove(appKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IsvDefinition getIsv(String appKey) {
|
||||||
|
return isvCache.get(appKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, IsvDefinition> getIsvCache() {
|
||||||
|
return isvCache;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,24 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.secret;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.Isv;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Isv管理
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public interface IsvManager<T extends Isv> {
|
||||||
|
/**
|
||||||
|
* 加载isv信息
|
||||||
|
* @param secretGetter secret获取
|
||||||
|
*/
|
||||||
|
void load(Function<Object, String> secretGetter);
|
||||||
|
|
||||||
|
void update(T t);
|
||||||
|
|
||||||
|
void remove(String appKey);
|
||||||
|
|
||||||
|
T getIsv(String appKey);
|
||||||
|
|
||||||
|
}
|
@@ -2,12 +2,12 @@ package com.gitee.sop.gatewaycommon.validate;
|
|||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.Isv;
|
||||||
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
||||||
import com.gitee.sop.gatewaycommon.param.UploadContext;
|
import com.gitee.sop.gatewaycommon.param.UploadContext;
|
||||||
import com.gitee.sop.gatewaycommon.secret.AppSecretManager;
|
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
||||||
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
|
||||||
import org.apache.commons.codec.digest.DigestUtils;
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -31,6 +31,7 @@ public class ApiValidator implements Validator {
|
|||||||
private static final Logger logger = LoggerFactory.getLogger(ApiValidator.class);
|
private static final Logger logger = LoggerFactory.getLogger(ApiValidator.class);
|
||||||
|
|
||||||
private static final int MILLISECOND_OF_ONE_SECOND = 1000;
|
private static final int MILLISECOND_OF_ONE_SECOND = 1000;
|
||||||
|
public static final int STATUS_FORBIDDEN = 2;
|
||||||
|
|
||||||
|
|
||||||
private static List<String> FORMAT_LIST = Arrays.asList("json", "xml");
|
private static List<String> FORMAT_LIST = Arrays.asList("json", "xml");
|
||||||
@@ -50,7 +51,7 @@ public class ApiValidator implements Validator {
|
|||||||
logger.debug("忽略签名验证, name:{}, version:{}", param.fetchName(), param.fetchVersion());
|
logger.debug("忽略签名验证, name:{}, version:{}", param.fetchName(), param.fetchVersion());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 需要验证签名
|
// 需要验证签名,先校验appKey,后校验签名顺序不能变
|
||||||
checkAppKey(param);
|
checkAppKey(param);
|
||||||
checkSign(param);
|
checkSign(param);
|
||||||
}
|
}
|
||||||
@@ -112,12 +113,17 @@ public class ApiValidator implements Validator {
|
|||||||
if (StringUtils.isEmpty(param.fetchAppKey())) {
|
if (StringUtils.isEmpty(param.fetchAppKey())) {
|
||||||
throw ErrorEnum.ISV_MISSING_APP_ID.getErrorMeta().getException();
|
throw ErrorEnum.ISV_MISSING_APP_ID.getErrorMeta().getException();
|
||||||
}
|
}
|
||||||
AppSecretManager appSecretManager = ApiContext.getApiConfig().getAppSecretManager();
|
IsvManager isvManager = ApiContext.getApiConfig().getIsvManager();
|
||||||
Assert.notNull(appSecretManager, "appSecretManager未初始化");
|
Assert.notNull(isvManager, "isvManager未初始化");
|
||||||
boolean isTrueAppKey = appSecretManager.isValidAppKey(param.fetchAppKey());
|
Isv isv = isvManager.getIsv(param.fetchAppKey());
|
||||||
if (!isTrueAppKey) {
|
// 没有用户
|
||||||
|
if (isv == null) {
|
||||||
throw ErrorEnum.ISV_INVALID_APP_ID.getErrorMeta().getException();
|
throw ErrorEnum.ISV_INVALID_APP_ID.getErrorMeta().getException();
|
||||||
}
|
}
|
||||||
|
// 禁止访问
|
||||||
|
if (isv.getStatus() == null || isv.getStatus() == STATUS_FORBIDDEN) {
|
||||||
|
throw ErrorEnum.ISV_ACCESS_FORBIDDEN.getErrorMeta().getException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void checkSign(ApiParam param) {
|
protected void checkSign(ApiParam param) {
|
||||||
@@ -127,9 +133,10 @@ public class ApiValidator implements Validator {
|
|||||||
throw ErrorEnum.ISV_MISSING_SIGNATURE.getErrorMeta().getException(param.fetchNameVersion(), ParamNames.SIGN_NAME);
|
throw ErrorEnum.ISV_MISSING_SIGNATURE.getErrorMeta().getException(param.fetchNameVersion(), ParamNames.SIGN_NAME);
|
||||||
}
|
}
|
||||||
ApiConfig apiConfig = ApiContext.getApiConfig();
|
ApiConfig apiConfig = ApiContext.getApiConfig();
|
||||||
AppSecretManager appSecretManager = apiConfig.getAppSecretManager();
|
IsvManager isvManager = apiConfig.getIsvManager();
|
||||||
// 根据appId获取秘钥
|
// 根据appId获取秘钥
|
||||||
String secret = appSecretManager.getSecret(param.fetchAppKey());
|
Isv isvInfo = isvManager.getIsv(param.fetchAppKey());
|
||||||
|
String secret = isvInfo.getSecretInfo();
|
||||||
if (StringUtils.isEmpty(secret)) {
|
if (StringUtils.isEmpty(secret)) {
|
||||||
throw ErrorEnum.ISV_MISSING_SIGNATURE_CONFIG.getErrorMeta().getException();
|
throw ErrorEnum.ISV_MISSING_SIGNATURE_CONFIG.getErrorMeta().getException();
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import com.gitee.sop.gatewaycommon.manager.AbstractConfiguration;
|
|||||||
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
||||||
import com.gitee.sop.gatewaycommon.zuul.filter.ErrorFilter;
|
import com.gitee.sop.gatewaycommon.zuul.filter.ErrorFilter;
|
||||||
import com.gitee.sop.gatewaycommon.zuul.filter.PostResultFilter;
|
import com.gitee.sop.gatewaycommon.zuul.filter.PostResultFilter;
|
||||||
|
import com.gitee.sop.gatewaycommon.zuul.filter.PreRoutePermissionFilter;
|
||||||
import com.gitee.sop.gatewaycommon.zuul.filter.PreValidateFilter;
|
import com.gitee.sop.gatewaycommon.zuul.filter.PreValidateFilter;
|
||||||
import com.gitee.sop.gatewaycommon.zuul.route.SopRouteLocator;
|
import com.gitee.sop.gatewaycommon.zuul.route.SopRouteLocator;
|
||||||
import com.gitee.sop.gatewaycommon.zuul.route.ZuulRouteRepository;
|
import com.gitee.sop.gatewaycommon.zuul.route.ZuulRouteRepository;
|
||||||
@@ -76,6 +77,15 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
|
|||||||
return new PreValidateFilter();
|
return new PreValidateFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限校验
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
PreRoutePermissionFilter preRoutePermissionFilter() {
|
||||||
|
return new PreRoutePermissionFilter();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 错误处理扩展
|
* 错误处理扩展
|
||||||
* @return
|
* @return
|
||||||
|
@@ -15,6 +15,8 @@ public abstract class BaseZuulFilter extends ZuulFilter {
|
|||||||
|
|
||||||
protected Logger log = LoggerFactory.getLogger(getClass());
|
protected Logger log = LoggerFactory.getLogger(getClass());
|
||||||
|
|
||||||
|
public static final int PRE_VALIDATE_FILTER_ORDER = -1000;
|
||||||
|
|
||||||
private Integer filterOrder;
|
private Integer filterOrder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -0,0 +1,49 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.zuul.filter;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition;
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
||||||
|
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
|
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
||||||
|
import com.netflix.zuul.context.RequestContext;
|
||||||
|
import com.netflix.zuul.exception.ZuulException;
|
||||||
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 路由权限校验,有些接口需要配置权限才能访问。
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public class PreRoutePermissionFilter extends BaseZuulFilter {
|
||||||
|
@Override
|
||||||
|
protected FilterType getFilterType() {
|
||||||
|
return FilterType.PRE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getFilterOrder() {
|
||||||
|
// 放在签名验证后面
|
||||||
|
return PRE_VALIDATE_FILTER_ORDER + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object doRun(RequestContext requestContext) throws ZuulException {
|
||||||
|
ApiParam apiParam = ZuulContext.getApiParam();
|
||||||
|
String routeId = apiParam.fetchNameVersion();
|
||||||
|
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(routeId);
|
||||||
|
BaseRouteDefinition routeDefinition = targetRoute.getRouteDefinition();
|
||||||
|
boolean needCheckPermission = BooleanUtils.toBoolean(routeDefinition.getPermission());
|
||||||
|
if (needCheckPermission) {
|
||||||
|
IsvRoutePermissionManager isvRoutePermissionManager = ApiConfig.getInstance().getIsvRoutePermissionManager();
|
||||||
|
String appKey = apiParam.fetchAppKey();
|
||||||
|
boolean hasPermission = isvRoutePermissionManager.hasPermission(appKey, routeId);
|
||||||
|
if (!hasPermission) {
|
||||||
|
throw ErrorEnum.ISV_ROUTE_NO_PERMISSIONS.getErrorMeta().getException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@@ -22,8 +22,7 @@ public class PreValidateFilter extends BaseZuulFilter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getFilterOrder() {
|
protected int getFilterOrder() {
|
||||||
// 在org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter前面
|
return PRE_VALIDATE_FILTER_ORDER;
|
||||||
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -53,3 +53,5 @@ open.error_40004_=Business processing failure
|
|||||||
open.error_40006=Insufficient permissions
|
open.error_40006=Insufficient permissions
|
||||||
open.error_40006_isv.insufficient-isv-permissions=Insufficient ISV permissions
|
open.error_40006_isv.insufficient-isv-permissions=Insufficient ISV permissions
|
||||||
open.error_40006_isv.insufficient-user-permissions=Insufficient user permissions
|
open.error_40006_isv.insufficient-user-permissions=Insufficient user permissions
|
||||||
|
open.error_40006_isv.route-no-permissions=No api permissions
|
||||||
|
open.error_40006_isv.access-forbidden=Access forbidden
|
@@ -51,6 +51,8 @@
|
|||||||
#open.error_40006=权限不足
|
#open.error_40006=权限不足
|
||||||
#open.error_40006_isv.insufficient-isv-permissions=请检查配置的账户是否有当前接口权限
|
#open.error_40006_isv.insufficient-isv-permissions=请检查配置的账户是否有当前接口权限
|
||||||
#open.error_40006_isv.insufficient-user-permissions=代理的商户没有当前接口权限
|
#open.error_40006_isv.insufficient-user-permissions=代理的商户没有当前接口权限
|
||||||
|
#open.error_40006_isv.route-no-permissions=没有当前接口权限
|
||||||
|
#open.error_40006_isv.access-forbidden=无权访问
|
||||||
|
|
||||||
open.error_10000=Success
|
open.error_10000=Success
|
||||||
|
|
||||||
@@ -105,3 +107,5 @@ open.error_40004_=\u4e1a\u52a1\u5904\u7406\u5931\u8d25
|
|||||||
open.error_40006=\u6743\u9650\u4e0d\u8db3
|
open.error_40006=\u6743\u9650\u4e0d\u8db3
|
||||||
open.error_40006_isv.insufficient-isv-permissions=\u8bf7\u68c0\u67e5\u914d\u7f6e\u7684\u8d26\u6237\u662f\u5426\u6709\u5f53\u524d\u63a5\u53e3\u6743\u9650
|
open.error_40006_isv.insufficient-isv-permissions=\u8bf7\u68c0\u67e5\u914d\u7f6e\u7684\u8d26\u6237\u662f\u5426\u6709\u5f53\u524d\u63a5\u53e3\u6743\u9650
|
||||||
open.error_40006_isv.insufficient-user-permissions=\u4ee3\u7406\u7684\u5546\u6237\u6ca1\u6709\u5f53\u524d\u63a5\u53e3\u6743\u9650
|
open.error_40006_isv.insufficient-user-permissions=\u4ee3\u7406\u7684\u5546\u6237\u6ca1\u6709\u5f53\u524d\u63a5\u53e3\u6743\u9650
|
||||||
|
open.error_40006_isv.route-no-permissions=\u6ca1\u6709\u5f53\u524d\u63a5\u53e3\u6743\u9650
|
||||||
|
open.error_40006_isv.access-forbidden=\u65e0\u6743\u8bbf\u95ee
|
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
<artifactId>sop-common</artifactId>
|
<artifactId>sop-common</artifactId>
|
||||||
<version>1.0.0-SNAPSHOT</version>
|
<version>1.1.0-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
|
@@ -30,4 +30,9 @@ public @interface ApiAbility {
|
|||||||
* 告诉网关是否对结果进行合并,默认合并。设置为false,客户端将直接收到微服务端的结果。
|
* 告诉网关是否对结果进行合并,默认合并。设置为false,客户端将直接收到微服务端的结果。
|
||||||
*/
|
*/
|
||||||
boolean mergeResult() default true;
|
boolean mergeResult() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定接口是否需要授权才能访问,可在admin中进行修改
|
||||||
|
*/
|
||||||
|
boolean permission() default false;
|
||||||
}
|
}
|
||||||
|
@@ -21,6 +21,8 @@ import java.lang.annotation.Target;
|
|||||||
@RequestMapping
|
@RequestMapping
|
||||||
public @interface ApiMapping {
|
public @interface ApiMapping {
|
||||||
|
|
||||||
|
// ------------ 自定义属性 ------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 版本号,默认版本号是""<br>
|
* 版本号,默认版本号是""<br>
|
||||||
* 改默认版本号:<code>ServiceContext.getSopServerConfig().setDefaultVersion("1.0");</code>
|
* 改默认版本号:<code>ServiceContext.getSopServerConfig().setDefaultVersion("1.0");</code>
|
||||||
@@ -37,6 +39,17 @@ public @interface ApiMapping {
|
|||||||
*/
|
*/
|
||||||
boolean mergeResult() default true;
|
boolean mergeResult() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定接口是否需要授权才能访问,可在admin中进行修改
|
||||||
|
*/
|
||||||
|
boolean permission() default false;
|
||||||
|
|
||||||
|
// ------------ 自定义属性 end ------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ============ 以下是springmvc自带的属性 ============
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 接口名
|
* 接口名
|
||||||
* Alias for {@link RequestMapping#value}.
|
* Alias for {@link RequestMapping#value}.
|
||||||
|
@@ -26,6 +26,8 @@ public class ServiceApiInfo {
|
|||||||
private int ignoreValidate;
|
private int ignoreValidate;
|
||||||
/** 是否合并结果 */
|
/** 是否合并结果 */
|
||||||
private int mergeResult;
|
private int mergeResult;
|
||||||
|
/** 是否需要授权才能访问 */
|
||||||
|
private int permission;
|
||||||
|
|
||||||
public ApiMeta() {
|
public ApiMeta() {
|
||||||
}
|
}
|
||||||
|
@@ -65,4 +65,9 @@ public class ServiceConfig {
|
|||||||
*/
|
*/
|
||||||
private boolean mergeResult = true;
|
private boolean mergeResult = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要授权才能访问
|
||||||
|
*/
|
||||||
|
private boolean permission;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -89,6 +89,7 @@ public class DefaultRequestMappingEvent implements RequestMappingEvent {
|
|||||||
ServiceApiInfo.ApiMeta apiMeta = new ServiceApiInfo.ApiMeta(name, path, version);
|
ServiceApiInfo.ApiMeta apiMeta = new ServiceApiInfo.ApiMeta(name, path, version);
|
||||||
apiMeta.setIgnoreValidate(BooleanUtils.toInteger(apiMappingInfo.isIgnoreValidate()));
|
apiMeta.setIgnoreValidate(BooleanUtils.toInteger(apiMappingInfo.isIgnoreValidate()));
|
||||||
apiMeta.setMergeResult(BooleanUtils.toInteger(apiMappingInfo.isMergeResult()));
|
apiMeta.setMergeResult(BooleanUtils.toInteger(apiMappingInfo.isMergeResult()));
|
||||||
|
apiMeta.setPermission(BooleanUtils.toInteger(apiMappingInfo.isPermission()));
|
||||||
return apiMeta;
|
return apiMeta;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@@ -11,6 +11,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.apache.curator.framework.CuratorFramework;
|
import org.apache.curator.framework.CuratorFramework;
|
||||||
import org.apache.curator.framework.CuratorFrameworkFactory;
|
import org.apache.curator.framework.CuratorFrameworkFactory;
|
||||||
import org.apache.curator.retry.ExponentialBackoffRetry;
|
import org.apache.curator.retry.ExponentialBackoffRetry;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
@@ -50,8 +51,7 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
|
|||||||
|
|
||||||
public ServiceZookeeperApiMetaManager(Environment environment) {
|
public ServiceZookeeperApiMetaManager(Environment environment) {
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
String profile = environment.getProperty("spring.profiles.active", "default");
|
this.routeRootPath = SOP_SERVICE_ROUTE_PATH;
|
||||||
this.routeRootPath = SOP_SERVICE_ROUTE_PATH + "-" + profile;
|
|
||||||
zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string");
|
zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string");
|
||||||
if (StringUtils.isEmpty(zookeeperServerAddr)) {
|
if (StringUtils.isEmpty(zookeeperServerAddr)) {
|
||||||
throw new IllegalArgumentException("未指定spring.cloud.zookeeper.connect-string参数");
|
throw new IllegalArgumentException("未指定spring.cloud.zookeeper.connect-string参数");
|
||||||
@@ -107,17 +107,15 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
|
|||||||
protected GatewayRouteDefinition buildGatewayRouteDefinition(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) {
|
protected GatewayRouteDefinition buildGatewayRouteDefinition(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) {
|
||||||
GatewayRouteDefinition gatewayRouteDefinition = new GatewayRouteDefinition();
|
GatewayRouteDefinition gatewayRouteDefinition = new GatewayRouteDefinition();
|
||||||
// 唯一id规则:接口名 + 版本号
|
// 唯一id规则:接口名 + 版本号
|
||||||
|
BeanUtils.copyProperties(apiMeta, gatewayRouteDefinition);
|
||||||
gatewayRouteDefinition.setId(apiMeta.fetchNameVersion());
|
gatewayRouteDefinition.setId(apiMeta.fetchNameVersion());
|
||||||
gatewayRouteDefinition.setFilters(Collections.emptyList());
|
gatewayRouteDefinition.setFilters(Collections.emptyList());
|
||||||
gatewayRouteDefinition.setOrder(0);
|
|
||||||
List<GatewayPredicateDefinition> predicates = Arrays.asList(this.buildNameVersionPredicateDefinition(apiMeta));
|
List<GatewayPredicateDefinition> predicates = Arrays.asList(this.buildNameVersionPredicateDefinition(apiMeta));
|
||||||
gatewayRouteDefinition.setPredicates(predicates);
|
gatewayRouteDefinition.setPredicates(predicates);
|
||||||
String uri = this.buildUri(serviceApiInfo, apiMeta);
|
String uri = this.buildUri(serviceApiInfo, apiMeta);
|
||||||
String path = this.buildServletPath(serviceApiInfo, apiMeta);
|
String path = this.buildServletPath(serviceApiInfo, apiMeta);
|
||||||
gatewayRouteDefinition.setUri(uri);
|
gatewayRouteDefinition.setUri(uri);
|
||||||
gatewayRouteDefinition.setPath(path);
|
gatewayRouteDefinition.setPath(path);
|
||||||
gatewayRouteDefinition.setIgnoreValidate(apiMeta.getIgnoreValidate());
|
|
||||||
gatewayRouteDefinition.setMergeResult(apiMeta.getMergeResult());
|
|
||||||
return gatewayRouteDefinition;
|
return gatewayRouteDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -45,18 +45,21 @@ public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping imple
|
|||||||
String version;
|
String version;
|
||||||
boolean ignoreValidate = false;
|
boolean ignoreValidate = false;
|
||||||
boolean mergeResult = true;
|
boolean mergeResult = true;
|
||||||
|
boolean permission = false;
|
||||||
ApiMapping apiMapping = method.getAnnotation(ApiMapping.class);
|
ApiMapping apiMapping = method.getAnnotation(ApiMapping.class);
|
||||||
if (apiMapping != null) {
|
if (apiMapping != null) {
|
||||||
name = apiMapping.value()[0];
|
name = apiMapping.value()[0];
|
||||||
version = apiMapping.version();
|
version = apiMapping.version();
|
||||||
ignoreValidate = apiMapping.ignoreValidate();
|
ignoreValidate = apiMapping.ignoreValidate();
|
||||||
mergeResult = apiMapping.mergeResult();
|
mergeResult = apiMapping.mergeResult();
|
||||||
|
permission = apiMapping.permission();
|
||||||
} else {
|
} else {
|
||||||
ApiAbility apiAbility = this.findApiAbilityAnnotation(method);
|
ApiAbility apiAbility = this.findApiAbilityAnnotation(method);
|
||||||
if (apiAbility != null) {
|
if (apiAbility != null) {
|
||||||
version = apiAbility.version();
|
version = apiAbility.version();
|
||||||
ignoreValidate = apiAbility.ignoreValidate();
|
ignoreValidate = apiAbility.ignoreValidate();
|
||||||
mergeResult = apiAbility.mergeResult();
|
mergeResult = apiAbility.mergeResult();
|
||||||
|
permission = apiAbility.permission();
|
||||||
} else {
|
} else {
|
||||||
return super.getCustomMethodCondition(method);
|
return super.getCustomMethodCondition(method);
|
||||||
}
|
}
|
||||||
@@ -69,11 +72,17 @@ public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping imple
|
|||||||
if (!ignoreValidate) {
|
if (!ignoreValidate) {
|
||||||
ignoreValidate = ServiceConfig.getInstance().isIgnoreValidate();
|
ignoreValidate = ServiceConfig.getInstance().isIgnoreValidate();
|
||||||
}
|
}
|
||||||
|
// 如果是默认配置,则采用全局配置
|
||||||
if (mergeResult) {
|
if (mergeResult) {
|
||||||
mergeResult = ServiceConfig.getInstance().isMergeResult();
|
mergeResult = ServiceConfig.getInstance().isMergeResult();
|
||||||
}
|
}
|
||||||
|
// 如果是默认配置,则采用全局配置
|
||||||
|
if (!permission) {
|
||||||
|
permission = ServiceConfig.getInstance().isPermission();
|
||||||
|
}
|
||||||
apiMappingInfo.setIgnoreValidate(ignoreValidate);
|
apiMappingInfo.setIgnoreValidate(ignoreValidate);
|
||||||
apiMappingInfo.setMergeResult(mergeResult);
|
apiMappingInfo.setMergeResult(mergeResult);
|
||||||
|
apiMappingInfo.setPermission(permission);
|
||||||
logger.info("注册接口,method:" + method + ", version:" + version);
|
logger.info("注册接口,method:" + method + ", version:" + version);
|
||||||
return new ApiMappingRequestCondition(apiMappingInfo);
|
return new ApiMappingRequestCondition(apiMappingInfo);
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,7 @@ public class ApiMappingInfo {
|
|||||||
private String version;
|
private String version;
|
||||||
private boolean ignoreValidate;
|
private boolean ignoreValidate;
|
||||||
private boolean mergeResult;
|
private boolean mergeResult;
|
||||||
|
private boolean permission;
|
||||||
|
|
||||||
public ApiMappingInfo(String name, String version) {
|
public ApiMappingInfo(String name, String version) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
@@ -54,4 +54,9 @@ public class GatewayRouteDefinition {
|
|||||||
* 是否合并结果
|
* 是否合并结果
|
||||||
*/
|
*/
|
||||||
private int mergeResult;
|
private int mergeResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要授权才能访问
|
||||||
|
*/
|
||||||
|
private int permission;
|
||||||
}
|
}
|
@@ -23,7 +23,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
<artifactId>sop-service-common</artifactId>
|
<artifactId>sop-service-common</artifactId>
|
||||||
<version>1.0.0-SNAPSHOT</version>
|
<version>1.1.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
|
@@ -0,0 +1,23 @@
|
|||||||
|
package com.gitee.sop.bookweb.controller;
|
||||||
|
|
||||||
|
import com.gitee.sop.servercommon.annotation.ApiMapping;
|
||||||
|
import com.gitee.sop.story.api.domain.Story;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付宝服务端,假设签名验证通过后,到达这里进行具体的业务处理。
|
||||||
|
* 这里演示如何接受业务参数。
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
public class PermissionDemoController {
|
||||||
|
|
||||||
|
@ApiMapping(value = "permission.story.get", permission = true)
|
||||||
|
public Story getStory() {
|
||||||
|
Story story = new Story();
|
||||||
|
story.setId(1);
|
||||||
|
story.setName("海底小纵队(permission.story.get)");
|
||||||
|
return story;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -23,7 +23,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
<artifactId>sop-gateway-common</artifactId>
|
<artifactId>sop-gateway-common</artifactId>
|
||||||
<version>1.0.0-SNAPSHOT</version>
|
<version>1.1.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- ↓↓↓ 使用spring cloud zuul ↓↓↓ -->
|
<!-- ↓↓↓ 使用spring cloud zuul ↓↓↓ -->
|
||||||
@@ -50,6 +50,17 @@
|
|||||||
-->
|
-->
|
||||||
<!-- ↑↑↑ 使用spring cloud gateway ↑↑↑ -->
|
<!-- ↑↑↑ 使用spring cloud gateway ↑↑↑ -->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.oschina.durcframework</groupId>
|
||||||
|
<artifactId>fastmybatis-spring-boot-starter</artifactId>
|
||||||
|
<version>1.6.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.cloud</groupId>
|
<groupId>org.springframework.cloud</groupId>
|
||||||
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
|
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
|
||||||
@@ -60,6 +71,12 @@
|
|||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.4</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
|
@@ -1,8 +1,14 @@
|
|||||||
package com.gitee.sop.gateway.config;
|
package com.gitee.sop.gateway.config;
|
||||||
|
|
||||||
|
import com.gitee.sop.gateway.entity.IsvInfo;
|
||||||
|
import com.gitee.sop.gateway.manager.ManagerInitializer;
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration;
|
import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
|
||||||
|
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
||||||
import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration;
|
import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -30,6 +36,14 @@ public class GatewayConfig extends AlipayGatewayConfiguration {
|
|||||||
appSecretStore.put("2019032617262200001", "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlyb9aUBaljQP/vjmBFe1mF8HsWSvyfC2NTlpT/V9E+sBxTr8TSkbzJCeeeOEm4LCaVXL0Qz63MZoT24v7AIXTuMdj4jyiM/WJ4tjrWAgnmohNOegfntTto16C3l234vXz4ryWZMR/7W+MXy5B92wPGQEJ0LKFwNEoLspDEWZ7RdE53VH7w6y6sIZUfK+YkXWSwehfKPKlx+lDw3zRJ3/yvMF+U+BAdW/MfECe1GuBnCFKnlMRh3UKczWyXWkL6ItOpYHHJi/jx85op5BWDje2pY9QowzfN94+0DB3T7UvZeweu3zlP6diwAJDzLaFQX8ULfWhY+wfKxIRgs9NoiSAQIDAQAB");
|
appSecretStore.put("2019032617262200001", "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlyb9aUBaljQP/vjmBFe1mF8HsWSvyfC2NTlpT/V9E+sBxTr8TSkbzJCeeeOEm4LCaVXL0Qz63MZoT24v7AIXTuMdj4jyiM/WJ4tjrWAgnmohNOegfntTto16C3l234vXz4ryWZMR/7W+MXy5B92wPGQEJ0LKFwNEoLspDEWZ7RdE53VH7w6y6sIZUfK+YkXWSwehfKPKlx+lDw3zRJ3/yvMF+U+BAdW/MfECe1GuBnCFKnlMRh3UKczWyXWkL6ItOpYHHJi/jx85op5BWDje2pY9QowzfN94+0DB3T7UvZeweu3zlP6diwAJDzLaFQX8ULfWhY+wfKxIRgs9NoiSAQIDAQAB");
|
||||||
ApiContext.getApiConfig().addAppSecret(appSecretStore);
|
ApiContext.getApiConfig().addAppSecret(appSecretStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ManagerInitializer managerInitializer;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doAfter() {
|
||||||
|
managerInitializer.init();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,9 +1,15 @@
|
|||||||
package com.gitee.sop.gateway.config;
|
package com.gitee.sop.gateway.config;
|
||||||
|
|
||||||
|
import com.gitee.sop.gateway.entity.IsvInfo;
|
||||||
|
import com.gitee.sop.gateway.manager.ManagerInitializer;
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||||
import com.gitee.sop.gatewaycommon.easyopen.EasyopenZuulConfiguration;
|
import com.gitee.sop.gatewaycommon.easyopen.EasyopenZuulConfiguration;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
|
||||||
|
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
||||||
import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration;
|
import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration;
|
||||||
import com.gitee.sop.gatewaycommon.zuul.configuration.TaobaoZuulConfiguration;
|
import com.gitee.sop.gatewaycommon.zuul.configuration.TaobaoZuulConfiguration;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -30,6 +36,14 @@ public class ZuulConfig extends AlipayZuulConfiguration {
|
|||||||
appSecretStore.put("2019032617262200001", "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlyb9aUBaljQP/vjmBFe1mF8HsWSvyfC2NTlpT/V9E+sBxTr8TSkbzJCeeeOEm4LCaVXL0Qz63MZoT24v7AIXTuMdj4jyiM/WJ4tjrWAgnmohNOegfntTto16C3l234vXz4ryWZMR/7W+MXy5B92wPGQEJ0LKFwNEoLspDEWZ7RdE53VH7w6y6sIZUfK+YkXWSwehfKPKlx+lDw3zRJ3/yvMF+U+BAdW/MfECe1GuBnCFKnlMRh3UKczWyXWkL6ItOpYHHJi/jx85op5BWDje2pY9QowzfN94+0DB3T7UvZeweu3zlP6diwAJDzLaFQX8ULfWhY+wfKxIRgs9NoiSAQIDAQAB");
|
appSecretStore.put("2019032617262200001", "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlyb9aUBaljQP/vjmBFe1mF8HsWSvyfC2NTlpT/V9E+sBxTr8TSkbzJCeeeOEm4LCaVXL0Qz63MZoT24v7AIXTuMdj4jyiM/WJ4tjrWAgnmohNOegfntTto16C3l234vXz4ryWZMR/7W+MXy5B92wPGQEJ0LKFwNEoLspDEWZ7RdE53VH7w6y6sIZUfK+YkXWSwehfKPKlx+lDw3zRJ3/yvMF+U+BAdW/MfECe1GuBnCFKnlMRh3UKczWyXWkL6ItOpYHHJi/jx85op5BWDje2pY9QowzfN94+0DB3T7UvZeweu3zlP6diwAJDzLaFQX8ULfWhY+wfKxIRgs9NoiSAQIDAQAB");
|
||||||
ApiContext.getApiConfig().addAppSecret(appSecretStore);
|
ApiContext.getApiConfig().addAppSecret(appSecretStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ManagerInitializer managerInitializer;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doAfter() {
|
||||||
|
managerInitializer.init();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -0,0 +1,48 @@
|
|||||||
|
package com.gitee.sop.gateway.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表名:isv_info
|
||||||
|
* 备注:isv信息表
|
||||||
|
*
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Table(name = "isv_info")
|
||||||
|
@Data
|
||||||
|
public class IsvInfo {
|
||||||
|
@Id
|
||||||
|
@Column(name = "id")
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
/** 数据库字段:id */
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/** appKey, 数据库字段:app_key */
|
||||||
|
private String appKey;
|
||||||
|
|
||||||
|
/** secret, 数据库字段:secret */
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
/** 公钥, 数据库字段:pub_key */
|
||||||
|
private String pubKey;
|
||||||
|
|
||||||
|
/** 私钥, 数据库字段:pri_key */
|
||||||
|
private String priKey;
|
||||||
|
|
||||||
|
/** 0启用,1禁用, 数据库字段:status */
|
||||||
|
private Byte status;
|
||||||
|
|
||||||
|
/** 数据库字段:gmt_create */
|
||||||
|
private Date gmtCreate;
|
||||||
|
|
||||||
|
/** 数据库字段:gmt_modified */
|
||||||
|
private Date gmtModified;
|
||||||
|
}
|
@@ -0,0 +1,39 @@
|
|||||||
|
package com.gitee.sop.gateway.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表名:perm_isv_role
|
||||||
|
* 备注:isv角色
|
||||||
|
*
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Table(name = "perm_isv_role")
|
||||||
|
@Data
|
||||||
|
public class PermIsvRole {
|
||||||
|
@Id
|
||||||
|
@Column(name = "id")
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
/** 数据库字段:id */
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/** isv_info表id, 数据库字段:isv_id */
|
||||||
|
private Long isvId;
|
||||||
|
|
||||||
|
/** 角色code, 数据库字段:role_code */
|
||||||
|
private String roleCode;
|
||||||
|
|
||||||
|
/** 数据库字段:gmt_create */
|
||||||
|
private Date gmtCreate;
|
||||||
|
|
||||||
|
/** 数据库字段:gmt_modified */
|
||||||
|
private Date gmtModified;
|
||||||
|
}
|
@@ -0,0 +1,39 @@
|
|||||||
|
package com.gitee.sop.gateway.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表名:perm_role_permission
|
||||||
|
* 备注:角色权限表
|
||||||
|
*
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Table(name = "perm_role_permission")
|
||||||
|
@Data
|
||||||
|
public class PermRolePermission {
|
||||||
|
@Id
|
||||||
|
@Column(name = "id")
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
/** 数据库字段:id */
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/** 角色表code, 数据库字段:role_code */
|
||||||
|
private String roleCode;
|
||||||
|
|
||||||
|
/** api_id, 数据库字段:route_id */
|
||||||
|
private String routeId;
|
||||||
|
|
||||||
|
/** 数据库字段:gmt_create */
|
||||||
|
private Date gmtCreate;
|
||||||
|
|
||||||
|
/** 数据库字段:gmt_modified */
|
||||||
|
private Date gmtModified;
|
||||||
|
}
|
@@ -0,0 +1,70 @@
|
|||||||
|
package com.gitee.sop.gateway.manager;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.gitee.fastmybatis.core.query.Query;
|
||||||
|
import com.gitee.sop.gateway.entity.IsvInfo;
|
||||||
|
import com.gitee.sop.gateway.mapper.IsvInfoMapper;
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.IsvDefinition;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.ZookeeperContext;
|
||||||
|
import com.gitee.sop.gatewaycommon.secret.CacheIsvManager;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class DbIsvManager extends CacheIsvManager {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
IsvInfoMapper isvInfoMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Environment environment;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void load(Function<Object, String> secretGetter) {
|
||||||
|
log.info("从数据库读取ISV信息,保存到本地");
|
||||||
|
List<IsvInfo> isvInfoList = isvInfoMapper.list(new Query());
|
||||||
|
isvInfoList.stream()
|
||||||
|
.forEach(isvInfo -> {
|
||||||
|
IsvDefinition isvDefinition = new IsvDefinition();
|
||||||
|
BeanUtils.copyProperties(isvInfo, isvDefinition);
|
||||||
|
String secret = secretGetter.apply(isvInfo);
|
||||||
|
isvDefinition.setSecret(secret);
|
||||||
|
this.getIsvCache().put(isvDefinition.getAppKey(), isvDefinition);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
protected void after() throws Exception {
|
||||||
|
ZookeeperContext.setEnvironment(environment);
|
||||||
|
String isvChannelPath = ZookeeperContext.getIsvInfoChannelPath();
|
||||||
|
ZookeeperContext.listenPath(isvChannelPath, nodeCache -> {
|
||||||
|
String nodeData = new String(nodeCache.getCurrentData().getData());
|
||||||
|
ChannelMsg channelMsg = JSON.parseObject(nodeData, ChannelMsg.class);
|
||||||
|
final IsvDefinition isvDefinition = JSON.parseObject(channelMsg.getData(), IsvDefinition.class);
|
||||||
|
switch (channelMsg.getOperation()) {
|
||||||
|
case "update":
|
||||||
|
log.info("更新ISV信息,isvDefinition:{}", isvDefinition);
|
||||||
|
update(isvDefinition);
|
||||||
|
break;
|
||||||
|
case "remove":
|
||||||
|
log.info("删除ISV,isvDefinition:{}", isvDefinition);
|
||||||
|
remove(isvDefinition.getAppKey());
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user