Authored by 郝肖肖

Merge branch 'feature/risk' into 'master'

风险控制



See merge request !28
... ... @@ -100,6 +100,10 @@ const defaultDegrades = [
path: '/pc/login/closePasswordLogin',
name: '【开关】关闭账号密码登录'
},
{
path: '/pc/close/risk',
name: '【开关】关闭 风控中间件'
},
//wap
{
... ... @@ -185,6 +189,10 @@ const defaultDegrades = [
{
path: '/wap/close/usePwa',
name: '【开关】关闭 PWA 模式'
},
{
path: '/wap/close/risk',
name: '【开关】关闭 风控中间件'
}
];
... ...
'use strict';
const Router = require('koa-router');
const ApiCache = require('../../ci/api_cache');
const _ = require('lodash');
const ZookeeperModel = require('../models/zookeeperModel');
let zookeeperModel = new ZookeeperModel();
let r = new Router();
const APPS = [{name: 'yohobuywap-node', app: 'wap', selected: true}, {name: 'yohobuy-node', app: 'pc'}];
const PATHRISK = '/json/risk';
const risk = {
index: async(ctx, next) => {
let list = [];
let ret = await Promise.all(_.map(APPS, (o) => {
return zookeeperModel.getPath(`/${o.app}${PATHRISK}`);
}));
_.map(ret, (o) => {
if (o) {
return list.push(JSON.parse(o));
}
});
await ctx.render('action/risk_management', {
list: _.flattenDeep(list)
});
},
add: async(ctx, next) => {
var data = {};
var key = ctx.request.query.key;
var oldRet = '';
if (key) {
var app = key.split('/')[0];
var path = formatPath(app);
oldRet = await zookeeperModel.getPath(path);
if (oldRet) {
oldRet = JSON.parse(oldRet);
}
data = _.find(oldRet, function(o) {
return `${o.app}/${o.route}` === key;
});
data.state = data.state === 'off';
_.each(APPS, (o => {
o.selected = o.name === app;
}));
}
await ctx.render('action/add_risk', {
data: data,
apps: APPS
});
},
edit: async(ctx) => {
let params = ctx.request.body;
let path = formatPath(params.param.app);
let str = `${params.param.app}/${params.param.route}`;
let editNewRet = await zookeeperModel.getPath(path);
params.edit = params.edit.split('=')[1];
let editOldPath = formatPath(params.edit.split('/')[0]);
let editOldRet;
if (!editNewRet) {
editNewRet = [];
} else {
editNewRet = JSON.parse(editNewRet);
}
if (path != editOldPath) { // 编辑后 new path != old path
editOldRet = await zookeeperModel.getPath(editOldPath);
editOldRet = JSON.parse(editOldRet);
// 删除原路径原始数据
_.remove(editOldRet, function(o) {
return `${o.app}/${o.route}` === params.edit;
});
let editRet = await zookeeperModel.setPath(editOldPath, JSON.stringify(editOldRet));
if (!editRet) {
ctx.body = {
code: 500,
message: 'update fail,Please retry'
}
}
// 删除 新路径相同的 app/route 数据
_.remove(editNewRet, function(o) {
return `${o.app}/${o.route}` === str;
});
} else {
// 删除原始数据
_.remove(editNewRet, function(o) {
return `${o.app}/${o.route}` === params.edit;
});
}
editNewRet.push(params.param);
let result = await zookeeperModel.setPath(path, JSON.stringify(editNewRet));
if (result) {
ctx.body = {
code: 200,
message: 'update success'
};
} else {
ctx.body = {
code: 500,
message: 'update fail,Please retry'
}
}
},
setter: async(ctx) => {
let params = ctx.request.body;
let path = formatPath(params.app);
let str = `${params.app}/${params.route}`;
let oldRet = await zookeeperModel.getPath(path);
if (!oldRet) {
oldRet = [];
} else {
oldRet = JSON.parse(oldRet);
}
_.remove(oldRet, function(o) {
return `${o.app}/${o.route}` === str;
});
oldRet.push(params);
let result = await zookeeperModel.setPath(path, JSON.stringify(oldRet));
if (result) {
ctx.body = {
code: 200,
message: 'update success'
};
} else {
ctx.body = {
code: 500,
message: 'update fail,Please retry'
}
}
},
remove: async(ctx) => {
let key = ctx.request.body.key;
let path = formatPath(key.split('/')[0]);
let oldRet = await zookeeperModel.getPath(path);
if (oldRet) {
oldRet = JSON.parse(oldRet);
}
_.remove(oldRet, function(o) {
return `${o.app}/${o.route}` === key;
});
let result = await zookeeperModel.setPath(path, JSON.stringify(oldRet));
if (result) {
ctx.body = {
code: 200,
message: 'remove success'
};
} else {
ctx.body = {
code: 500,
message: 'remove fail,Please retry'
}
}
}
};
function formatPath(app) {
return `/${_.find(APPS, (o) => {return o.name === app;}).app}${PATHRISK}`;
}
r.get('/risk_management', risk.index);
r.get('/add_risk', risk.add);
r.post('/setter', risk.setter);
r.post('/edit', risk.edit);
r.post('/remove', risk.remove);
module.exports = r;
\ No newline at end of file
... ...
const model = require('../../../lib/model');
const {DegradeServer} = require('../../models');
const zookeeperHelpers = require('../../zookeeper/zookeeper-helpers');
const _ = require('lodash');
class ZookeeperModel extends model {
constructor(ctx) {
super(ctx);
}
async getConfigAll() {
return await DegradeServer.findAll();
}
async setPath(path, val) {
let degradeServer = await this.getConfigAll();
let results = await Promise.all(_.map(degradeServer, item => {
return zookeeperHelpers.creator(`${item.ip}:${item.port}`, path, val, true);
}));
return results && results[0] || false; // return Boolean
}
async getPath(path) {
let degradeServer = await this.getConfigAll();
if (degradeServer[0]) {
return await zookeeperHelpers.getter(`${degradeServer[0].ip}:${degradeServer[0].port}`, path);
}
return null;
}
async removePath(path) {
let degradeServer = await this.getConfigAll();
if (degradeServer[0]) {
return await zookeeperHelpers.remove(`${degradeServer[0].ip}:${degradeServer[0].port}`, path);
}
}
}
module.exports = ZookeeperModel;
... ...
... ... @@ -26,6 +26,8 @@ const checkcode = require('./actions/checkcode').router;
const noAuth = new Router();
const base = new Router();
const file = require('./actions/file');
const riskManagement = require('./actions/risk_management');
module.exports = function(app) {
... ... @@ -70,6 +72,7 @@ module.exports = function(app) {
base.use('/files', file.routes(), file.allowedMethods());
base.use('', index.routes(), index.allowedMethods());
base.use('/risk_management', riskManagement.routes(), riskManagement.allowedMethods());
app.use(base.routes(), base.allowedMethods());
... ...
{{#data}}
<div class="pageheader">
<div class="media">
<div class="pageicon pull-left">
<i class="fa fa-th-list"></i>
</div>
<div class="media-body">
<ul class="breadcrumb">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
<li><a href="/risk_management/risk_management">风险控制</a></li>
<li>{{#if route}}编辑配置{{else}}新增配置{{/if}}</li>
</ul>
<h4>{{#if route}}编辑配置{{else}}新增配置{{/if}}</h4>
</div>
</div>
<!-- media -->
</div>
<!-- pageheader -->
<div class="contentpanel servers-page form-horizontal">
<div class="form-group">
<label for="app" class="col-sm-2 control-label text-center">应用名称:</label>
<div class="col-sm-6">
<select class="form-control" style="height: 40px; line-height: 40px;" id="app">
{{#each ../apps}}
<option value="{{name}}" data-app="{{app}}" {{#if selected}}selected{{/if}}>{{name}}</option>
{{/each}}
</select>
</div>
</div>
<div class="form-group">
<label for="router" class="col-sm-2 control-label text-center">路由路径:</label>
<div class="col-sm-6">
<input type="text" class="form-control" id="router" placeholder="请输入路由路径" value="{{route}}">
</div>
</div>
<div class="form-group">
<label for="exampleFormControlInput1" class="col-sm-2 control-label text-center">时间间隔:</label>
<div class="col-sm-6">
<input type="text" class="form-control" id="interval" placeholder="请输入时间间隔" value="{{interval}}">
</div>
<div class="col-sm-2" style="line-height: 40px;"></div>
</div>
<div class="form-group">
<label for="exampleFormControlInput1" class="col-sm-2 control-label text-center">请求次数:</label>
<div class="col-sm-6">
<input type="text" class="form-control" id="requests" placeholder="请输入请求次数" value="{{requests}}">
</div>
<div class="col-sm-2" style="line-height: 40px;"></div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label text-center">状  态:</label>
<div class="col-sm-10">
<label class="radio-inline">
<input type="radio" name="status" id="status-on" value="on" {{#unless state}}checked{{/unless}}>开启
</label>
<label class="radio-inline">
<input type="radio" name="status" id="status-off" value="off"{{#if state}}checked{{/if}}>关闭
</label>
</div>
</div>
<div class="row">
<label class="col-sm-2"></label>
<div class="col-sm-10">
{{#if route}}
<button type="button" class="btn btn-success edit">保存</button>
{{else}}
<button type="button" class="btn btn-success save">保存</button>
{{/if}}
<button type="button" class="btn btn-default cancel">取消</button>
</div>
</div>
</div>
{{/data}}
<script>
let $app = $('#app'),
$router = $('#router'),
$interval = $('#interval'),
$requests = $('#requests');
function check() {
if ($.trim($router.val()) === '') {
alert('请输入路由路径!');
return true;
}
if ($.trim($interval.val()) === '') {
alert('请输入时间间隔!');
return true;
}
if (isNaN($interval.val())) {
alert('时间间隔必须为数字!');
return true;
}
if ($.trim($requests.val()) === '') {
alert('请输入请求次数!');
return true;
}
if (isNaN($requests.val())) {
alert('请求次数必须为数字!');
return true;
}
}
$('.save').on('click', function() {
if (check()) {
return;
}
$.post('/risk_management/setter', {
app: $app.val(),
route: $router.val(),
interval: $interval.val(),
requests: $requests.val(),
state: $('input[type=radio]:checked').val()
}, function(ret) {
if (ret.code === 200) {
alert('添加成功!');
location.href = '/risk_management/risk_management';
} else {
alert('添加失败!');
}
});
});
$('.edit').on('click', function() {
if (check()) {
return;
}
$.post('/risk_management/edit', {
param: {
app: $app.val(),
route: $router.val(),
interval: $interval.val(),
requests: $requests.val(),
state: $('input[type=radio]:checked').val()
},
edit: location.href.split('?')[1]
}, function(ret) {
if (ret.code === 200) {
alert('编辑成功!');
location.href = '/risk_management/risk_management';
} else {
alert('编辑失败!');
}
});
});
$('.cancel').on('click', function() {
location.href = '/risk_management/risk_management';
});
</script>
\ No newline at end of file
... ...
<div class="pageheader">
<div class="media">
<div class="pageicon pull-left">
<i class="fa fa-th-list"></i>
</div>
<div class="media-body">
<ul class="breadcrumb">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
<li>风险控制</li>
</ul>
<h4>风险控制</h4>
</div>
</div>
<!-- media -->
</div>
<!-- pageheader -->
<div class="contentpanel servers-page">
<button type="button" class="btn btn-success add">新增配置</button>
<table class="table table-hover table-striped table-bordered" style="margin-top: 2rem">
<thead>
<tr>
<th>应用名称</th>
<th>路由路径</th>
<th>间隔时间</th>
<th>请求次数</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{{#list}}
<tr>
<td class="app">{{app}}</td>
<td class="route">{{route}}</td>
<td>{{interval}}</td>
<td>{{requests}}</td>
<td>{{state}}</td>
<td><button type="button" class="btn btn-success edit">编辑</button><button type="button" class="btn btn-danger ml10 del">删除</button></td>
</tr>
{{/list}}
{{#if noData}}
<tr>
<td colspan="4" class="text-center">无数据</td>
</tr>
{{/if}}
</tbody>
</table>
</div>
<script>
$('.add').on('click', function() {
location.href = '/risk_management/add_risk';
});
$('.del').on('click', function() {
var key = $(this).parents('tr').find('.app').html() + '/' + $(this).parents('tr').find('.route').html();
$.post('/risk_management/remove', {
key: key
}, function(ret) {
if (ret.code === 200) {
alert('删除成功!');
location.reload();
} else {
alert('删除失败!');
}
});
});
$('.edit').on('click', function() {
var key = $(this).parents('tr').find('.app').html() + '/' + $(this).parents('tr').find('.route').html();
location.href = '/risk_management/add_risk?key=' + key;
});
</script>
\ No newline at end of file
... ...
... ... @@ -60,6 +60,7 @@
<span>滥用防护</span>
</a>
</li>
<li><a href="/risk_management/risk_management">风险控制</a></li>
</ul>
</li>
<li class="parent"><a><i class="fa fa-list"></i> <span>静态资源</span></a>
... ...
'usu strict';
const _ = require('lodash');
const zookeeper = require('node-zookeeper-client');
const _createClient = (server) => new Promise((resolve, reject) => {
const client = zookeeper.createClient(server);
let isServerStart = false;
client.once('connected', function (err) {
isServerStart = true;
err ? reject(err) : resolve(client);
});
setTimeout(() => {
!isServerStart && reject({message: 'Failed to authenticate with the server.', code: -1});
}, 8000);
client.connect();
});
const _exists = (client, path) => new Promise((resolve, reject) => {
client.exists(path, (err, stat) => {
if (err) {
console.log('path %s exits error', path, err.stack);
resolve(false);
} else {
resolve(stat ? true : false);
}
});
});
const exists = (server, path) => new Promise((resolve, reject) => {
return _createClient(server).then(client => {
return _exists(client, path).then(stat => {
client.close();
resolve(stat);
});
}).catch(reject);
});
const creator = (server, path, value, iscover) => new Promise((resolve, reject) => {
return _createClient(server).then(client => {
return _exists(client, path).then(stat => {
if (stat && iscover) {
client.setData(path, new Buffer(value.toString()), function(err, data, stat) {
client.close();
if (err) {
console.log('update path %s data error');
resolve(false);
} else {
resolve(true);
}
});
} else if (stat) {
client.close();
resolve(true);
} else {
client.mkdirp(path, new Buffer(value), (err, path) => {
client.close();
if (err) {
console.log('Node %s create err', path, err.stack);
resolve(false);
} else {
console.log('Node %s is created', path);
resolve(true);
}
});
}
});
}).catch(reject);
});
const getter = (server, path) => new Promise((resolve, reject) => {
return _createClient(server).then(client => {
return _exists(client, path).then(stat => {
if (stat) {
client.getData(
path,
(err, data, statData) => {
if (err) {
console.log('Get path %s data error', path, err.stack);
}
client.close();
resolve(data && data.toString('utf8'));
}
)
} else {
// 不存在的路径
console.log('no path %s, we will create it with value "false" automatic', path);
client.close();
resolve();
}
});
}).catch(reject);
});
const setter = (server, path, value) => new Promise((resolve, reject) => {
return _createClient(server).then(client => {
return _exists(client, path).then(stat => {
if (stat) {
client.setData(path, new Buffer(val.toString()), function(err, data, stat) {
client.close();
if (err) {
console.log('update path %s data error');
resolve(false);
} else {
resolve(true);
}
});
} else {
client.close();
resolve(false);
}
});
}).catch(reject);
});
const remove = (server, path) => new Promise((resolve, reject) => {
return _createClient(server).then(client => {
return _exists(client, path).then(stat => {
if (stat) {
client.remove(path, function(err) {
client.close();
err ? resolve(false) : resolve(true);
});
} else {
client.close();
resolve(false);
}
});
}).catch(reject);
});
module.exports = {
exists,
creator,
setter,
getter,
remove
};
... ...