Authored by yyq

risk

@@ -134,6 +134,7 @@ const logger = global.yoho.logger; @@ -134,6 +134,7 @@ const logger = global.yoho.logger;
134 // dispatcher 134 // dispatcher
135 try { 135 try {
136 const setYohoData = require('./doraemon/middleware/set-yoho-data'); 136 const setYohoData = require('./doraemon/middleware/set-yoho-data');
  137 + const riskManagement = require('./doraemon/middleware/risk-management');
137 const htaccess = require('./doraemon/middleware/htaccess'); 138 const htaccess = require('./doraemon/middleware/htaccess');
138 const subDomain = require('./doraemon/middleware/sub-domain'); 139 const subDomain = require('./doraemon/middleware/sub-domain');
139 const mobileRefer = require('./doraemon/middleware/mobile-refer'); 140 const mobileRefer = require('./doraemon/middleware/mobile-refer');
@@ -149,6 +150,7 @@ try { @@ -149,6 +150,7 @@ try {
149 150
150 // YOHO 前置中间件 151 // YOHO 前置中间件
151 app.use(setYohoData()); 152 app.use(setYohoData());
  153 + app.use(riskManagement());
152 app.use(htaccess()); 154 app.use(htaccess());
153 app.use(subDomain()); 155 app.use(subDomain());
154 app.use(mobileRefer()); 156 app.use(mobileRefer());
@@ -31,7 +31,7 @@ const isHuman = (req, res) => { @@ -31,7 +31,7 @@ const isHuman = (req, res) => {
31 delete req.session.apiLimitValidate; 31 delete req.session.apiLimitValidate;
32 32
33 logger.warn('isHuman', remoteIp); 33 logger.warn('isHuman', remoteIp);
34 - return robotCheckService.removeBlack(remoteIp, apiLimitValidate).then(() => { 34 + return robotCheckService.removeBlack(remoteIp, apiLimitValidate, req.headers.referer).then(() => {
35 return res.json({ 35 return res.json({
36 code: 200 36 code: 200
37 }); 37 });
1 'use strict'; 1 'use strict';
2 2
  3 +const url = require('url');
3 const cache = global.yoho.cache.master; 4 const cache = global.yoho.cache.master;
4 const Promise = require('bluebird'); 5 const Promise = require('bluebird');
5 const co = Promise.coroutine; 6 const co = Promise.coroutine;
@@ -17,9 +18,15 @@ const index = co(function* (channel) { @@ -17,9 +18,15 @@ const index = co(function* (channel) {
17 }; 18 };
18 }); 19 });
19 20
20 -const removeBlack = (remoteIp, apiLimitValidate) => { 21 +const removeBlack = (remoteIp, apiLimitValidate, referer) => {
21 let operations = []; 22 let operations = [];
22 23
  24 + if (referer) {
  25 + let pid = _.get(url.parse(referer, true), 'query.pid');
  26 +
  27 + pid && operations.push(cache.delAsync(`${pid}:${remoteIp}`));
  28 + }
  29 +
23 operations.push(cache.delAsync(`${config.app}:limiter:${remoteIp}`)); 30 operations.push(cache.delAsync(`${config.app}:limiter:${remoteIp}`));
24 31
25 // 验证码之后一小时之内不再限制qps 32 // 验证码之后一小时之内不再限制qps
  1 +/**
  2 + * 控制路由请求次数
  3 + * @date: 2018/03/05
  4 + */
  5 +'use strict';
  6 +
  7 +const _ = require('lodash');
  8 +const cache = global.yoho.cache.master;
  9 +const helpers = global.yoho.helpers;
  10 +const pathToRegexp = require('path-to-regexp');
  11 +const logger = global.yoho.logger;
  12 +
  13 +const statusCode = {
  14 + code: 4403,
  15 + data: {},
  16 + message: '亲,您的访问次数过多,请稍后再试哦...'
  17 +};
  18 +
  19 +const INVALIDTIME = 3600 * 24; // 24h
  20 +const IP_WHITE_LIST = [
  21 + '106.38.38.146',
  22 + '106.38.38.147',
  23 + '106.39.86.227',
  24 + '218.94.75.58',
  25 + '218.94.75.50',
  26 + '218.94.77.166'
  27 +];
  28 +
  29 +const _jumpUrl = (req, res, next, result) => {
  30 + if (result.code === 4403) {
  31 + if (req.xhr) {
  32 + res.set({
  33 + 'Cache-Control': 'no-cache',
  34 + Pragma: 'no-cache',
  35 + Expires: (new Date(1900, 0, 1, 0, 0, 0, 0)).toUTCString()
  36 + });
  37 + return res.status(403).json(result);
  38 + }
  39 + return res.redirect(`${result.data.url}&refer=${req.originalUrl}`);
  40 + }
  41 +
  42 + return next();
  43 +};
  44 +
  45 +module.exports = () => {
  46 + return (req, res, next) => {
  47 + // default open
  48 + if (_.get(req.app.locals.pc, 'close.risk', false)) {
  49 + return next();
  50 + }
  51 +
  52 + let ip = _.get(req.yoho, 'clientIp', '');
  53 + let path = req.path || '';
  54 + let risks = _.get(req.app.locals.pc, 'json.risk', []);
  55 + let router = {};
  56 +
  57 + risks = [{
  58 + route: '/kids-brands/',
  59 + requests: 5
  60 + }];
  61 +
  62 + logger.debug(`risk => risks: ${JSON.stringify(risks)}, path: ${path}, ip: ${ip}`); // eslint-disable-line
  63 + if (_.isEmpty(path) || _.isEmpty(risks) || IP_WHITE_LIST.indexOf(ip) > -1) {
  64 + return next();
  65 + }
  66 +
  67 + _.isArray(risks) && risks.some(item => {
  68 + if (item.state === 'off') {
  69 + return false;
  70 + }
  71 +
  72 + if (!item.regRoute) {
  73 + item.regRoute = pathToRegexp(item.route);
  74 + item.interval = parseInt(item.interval, 10);
  75 + item.requests = parseInt(item.requests, 10);
  76 + }
  77 +
  78 + if (item.regRoute.test(path)) {
  79 + router = item;
  80 + return true;
  81 + }
  82 +
  83 + return false;
  84 + });
  85 +
  86 + logger.debug(`risk => router: ${JSON.stringify(router)}, path: ${path}`); // eslint-disable-line
  87 + if (_.isEmpty(router)) {
  88 + return next();
  89 + }
  90 +
  91 + let keyPath = `${_.trim(path, '/').replace(/\//g, ':')}`;
  92 + let limitKey = `pc:risk:limit:${keyPath}:${ip}`;
  93 + let configKey = `pc:risk:${keyPath}:${ip}`;
  94 + let checkUrl = helpers.urlFormat('/3party/check', {
  95 + pid: `pc:risk:limit:${keyPath}`
  96 + });
  97 +
  98 + return Promise.all([
  99 + cache.getAsync(limitKey),
  100 + cache.getAsync(configKey),
  101 + ]).then(inters => {
  102 + logger.debug(`risk => getCache: ${JSON.stringify(inters)}, path: ${path}`); // eslint-disable-line
  103 + if (inters[0]) {
  104 + return Object.assign({}, statusCode, {data: {url: checkUrl}});
  105 + }
  106 +
  107 + if (typeof inters[1] === 'undefined') {
  108 + cache.setAsync(configKey, 1, router.interval || 300);
  109 + return {code: 200};
  110 + }
  111 +
  112 + inters[1] = parseInt(`0${inters[1]}`, 10);
  113 + if (inters[1] <= router.requests) {
  114 + router = [];
  115 + cache.incrAsync(configKey, 1);
  116 + return {code: 200};
  117 + }
  118 +
  119 + return Promise.all([
  120 + cache.setAsync(limitKey, 1, INVALIDTIME),
  121 + cache.delAsync(configKey)
  122 + ]).then(() => {
  123 + return Object.assign({}, statusCode, {data: {url: checkUrl}});
  124 + });
  125 + }).then(result => {
  126 + logger.debug(`risk => result: ${JSON.stringify(result)}, path: ${path}`); // eslint-disable-line
  127 + return _jumpUrl(req, res, next, result);
  128 + }).catch(e => {
  129 + console.log(`risk => path: ${path}, err: ${e.message}`);
  130 + return next();
  131 + });
  132 + };
  133 +};
@@ -48,6 +48,7 @@ @@ -48,6 +48,7 @@
48 "passport-sina": "^0.1.0", 48 "passport-sina": "^0.1.0",
49 "passport-strategy": "1.x.x", 49 "passport-strategy": "1.x.x",
50 "passport-weixin": "^0.1.0", 50 "passport-weixin": "^0.1.0",
  51 + "path-to-regexp": "^2.2.0",
51 "redis": "^2.7.1", 52 "redis": "^2.7.1",
52 "request": "^2.81.0", 53 "request": "^2.81.0",
53 "request-ip": "^1.2.2", 54 "request-ip": "^1.2.2",
@@ -5475,6 +5475,10 @@ path-to-regexp@0.1.7: @@ -5475,6 +5475,10 @@ path-to-regexp@0.1.7:
5475 version "0.1.7" 5475 version "0.1.7"
5476 resolved "http://npm.yohops.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 5476 resolved "http://npm.yohops.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
5477 5477
  5478 +path-to-regexp@^2.2.0:
  5479 + version "2.2.0"
  5480 + resolved "http://npm.yohops.com/path-to-regexp/-/path-to-regexp-2.2.0.tgz#80f0ff45c1e0e641da74df313644eaf115050972"
  5481 +
5478 path-type@^1.0.0: 5482 path-type@^1.0.0:
5479 version "1.1.0" 5483 version "1.1.0"
5480 resolved "http://npm.yohops.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" 5484 resolved "http://npm.yohops.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"