Authored by yyq

limit

@@ -4,7 +4,9 @@ const _ = require('lodash'); @@ -4,7 +4,9 @@ const _ = require('lodash');
4 const logger = global.yoho.logger; 4 const logger = global.yoho.logger;
5 const ip = require('./rules/ip-list'); 5 const ip = require('./rules/ip-list');
6 const userAgent = require('./rules/useragent'); 6 const userAgent = require('./rules/useragent');
  7 +const ipWhiteList = require('./rules/ip-white-list');
7 const qpsLimiter = require('./rules/qps-limit'); 8 const qpsLimiter = require('./rules/qps-limit');
  9 +const co = Promise.coroutine;
8 10
9 // const asynchronous = require('./rules/asynchronous'); 11 // const asynchronous = require('./rules/asynchronous');
10 // const fakerLimiter = require('./rules/faker-limit'); 12 // const fakerLimiter = require('./rules/faker-limit');
@@ -13,12 +15,23 @@ const captchaPolicy = require('./policies/captcha'); @@ -13,12 +15,23 @@ const captchaPolicy = require('./policies/captcha');
13 // const reporterPolicy = require('./policies/reporter'); 15 // const reporterPolicy = require('./policies/reporter');
14 16
15 const IP_WHITE_LIST = [ 17 const IP_WHITE_LIST = [
16 - '106.38.38.146',  
17 - '106.38.38.147',  
18 - '106.39.86.227',  
19 - '218.94.75.58',  
20 - '218.94.75.50',  
21 - '218.94.77.166' 18 + '106.38.38.146', // 北京办公区域
  19 + '106.38.38.147', // 北京办公区域
  20 + '106.39.86.227', // 北京办公区域
  21 + '218.94.75.58', // 南京办公区域
  22 + '218.94.75.50', // 南京办公区域
  23 + '218.94.77.166', // 南京办公区域
  24 +
  25 + // '222.73.196.18', // B站合作方单击次数快加白名单
  26 + '123.206.73.107', // 腾讯云出口IP
  27 + '139.199.35.21', // 腾讯云出口IP
  28 + '139.199.29.44', // 腾讯云出口IP
  29 + '123.206.21.19' // 腾讯云出口IP
  30 +];
  31 +
  32 +const IP_WHITE_SEGMENT = [
  33 + '10.66.', // 内网IP段
  34 + '192.168.' // 内网IP段
22 ]; 35 ];
23 36
24 const PATH_WHITE_LIST = [ 37 const PATH_WHITE_LIST = [
@@ -36,30 +49,45 @@ const limiter = (rule, policy, context) => { @@ -36,30 +49,45 @@ const limiter = (rule, policy, context) => {
36 return rule(context, policy); 49 return rule(context, policy);
37 }; 50 };
38 51
39 -module.exports = (req, res, next) => {  
40 - let remoteIp = req.get('X-Yoho-Real-IP') || req.get('X-Forwarded-For') || req.get('X-Real-IP') || ''; 52 +// 排除条件:ip白名单/路径白名单/异步请求/登录用户
  53 +const _excluded = (req) => {
  54 + let remoteIp = req.yoho.clientIp || '';
  55 + let remoteIpSegment = `${remoteIp.split('.').slice(0, 2).join('.')}.`;
  56 +
  57 + return co(function* () {
  58 + let atWhiteList = yield ipWhiteList(remoteIp);
  59 +
  60 + return Boolean(
  61 + atWhiteList ||
  62 + _.includes(IP_WHITE_LIST, remoteIp) ||
  63 + _.includes(IP_WHITE_SEGMENT, remoteIpSegment) ||
  64 + _.includes(PATH_WHITE_LIST, req.path) ||
  65 + req.xhr ||
  66 + !_.isEmpty(_.get(req, 'user.uid'))
  67 + );
  68 + })();
  69 +};
41 70
42 - if (remoteIp.indexOf(',') > 0) {  
43 - let arr = remoteIp.split(','); 71 +module.exports = (req, res, next) => {
  72 + const remoteIp = req.yoho.clientIp || '';
  73 + const enabled = !_.get(req.app.locals, 'pc.sys.noLimiter');
44 74
45 - remoteIp = arr[arr.length - 1]; 75 + // 开关为关或者未获取到remoteIp,放行
  76 + if (!enabled || !remoteIp) {
  77 + logger.debug(`request remote ip: ${remoteIp}; enabled: ${enabled}`);
  78 + return next();
46 } 79 }
47 80
48 - remoteIp = _.trim(remoteIp); 81 + return co(function* () {
  82 + let excluded = yield _excluded(req);
49 83
50 - if (_.startsWith(remoteIp, '10.66.')) {  
51 - remoteIp = req.get('X-Real-IP');  
52 - } 84 + logger.debug(`request remote ip: ${remoteIp}; excluded: ${excluded}; enabled: ${enabled}`);
53 85
54 - // 排除条件:ip白名单/路径白名单/异步请求/登录用户  
55 - const excluded = _.includes(IP_WHITE_LIST, remoteIp) ||  
56 - _.includes(PATH_WHITE_LIST, req.path) || req.xhr || !_.isEmpty(_.get(req, 'user.uid'));  
57 - const enabled = !_.get(req.app.locals, 'pc.sys.noLimiter');  
58 -  
59 - logger.debug(`request remote ip: ${remoteIp}; excluded: ${excluded}; enabled: ${enabled}`); 86 + // 白名单,放行
  87 + if (excluded) {
  88 + return next();
  89 + }
60 90
61 - // 判断获取remoteIp成功,并且开关未关闭  
62 - if (enabled && remoteIp && !excluded) {  
63 const context = { 91 const context = {
64 req: req, 92 req: req,
65 res: res, 93 res: res,
@@ -67,42 +95,40 @@ module.exports = (req, res, next) => { @@ -67,42 +95,40 @@ module.exports = (req, res, next) => {
67 remoteIp: remoteIp 95 remoteIp: remoteIp
68 }; 96 };
69 97
70 - Promise.all([ 98 +
  99 + let results = yield Promise.all([
71 limiter(userAgent, captchaPolicy, context), 100 limiter(userAgent, captchaPolicy, context),
72 limiter(ip, captchaPolicy, context), 101 limiter(ip, captchaPolicy, context),
73 limiter(qpsLimiter, captchaPolicy, context) 102 limiter(qpsLimiter, captchaPolicy, context)
74 103
75 // limiter(asynchronous, captchaPolicy, context) 104 // limiter(asynchronous, captchaPolicy, context)
76 // limiter(fakerLimiter, reporterPolicy, context) 105 // limiter(fakerLimiter, reporterPolicy, context)
77 - ]).then((results) => {  
78 - let allPass = true, exclusion = false, policy = null;  
79 -  
80 - logger.debug('limiter result: ' + JSON.stringify(results));  
81 -  
82 - _.forEach(results, (result) => {  
83 - if (typeof result === 'object' && !exclusion) {  
84 - exclusion = result.exclusion;  
85 - }  
86 -  
87 - if (typeof result === 'function') {  
88 - allPass = false;  
89 - policy = result;  
90 - }  
91 - });  
92 -  
93 - if (exclusion) {  
94 - return next();  
95 - } else if (!allPass && policy) {  
96 - policy(req, res, next);  
97 - } else {  
98 - return next(); 106 + ]);
  107 +
  108 + let allPass = true, exclusion = false, policy = null;
  109 +
  110 + logger.debug('limiter result: ' + JSON.stringify(results));
  111 +
  112 + _.forEach(results, (result) => {
  113 + if (typeof result === 'object' && !exclusion) {
  114 + exclusion = result.exclusion;
99 } 115 }
100 116
101 - }).catch((err) => {  
102 - logger.error(err);  
103 - return next(); 117 + if (typeof result === 'function') {
  118 + allPass = false;
  119 + policy = result;
  120 + }
104 }); 121 });
105 - } else { 122 +
  123 + if (exclusion) {
  124 + return next();
  125 + } else if (!allPass && policy) {
  126 + policy(req, res, next);
  127 + } else {
  128 + return next();
  129 + }
  130 + })().catch((err) => {
  131 + logger.error(err);
106 return next(); 132 return next();
107 - } 133 + });
108 }; 134 };
  1 +const co = Promise.coroutine;
  2 +const logger = global.yoho.logger;
  3 +const cache = global.yoho.cache.master;
  4 +const WHITE_LIST_KEY = 'whitelist:ip:';
  5 +
  6 +module.exports = (remoteIp) => {
  7 + let key = `${WHITE_LIST_KEY}${remoteIp}`;
  8 +
  9 + return co(function* () {
  10 + let result = Boolean(yield cache.getAsync(key));
  11 +
  12 + logger.debug(key, result);
  13 +
  14 + return result;
  15 + })();
  16 +};
@@ -15,8 +15,23 @@ module.exports = (limiter, policy) => { @@ -15,8 +15,23 @@ module.exports = (limiter, policy) => {
15 cache.getAsync(blackKey), 15 cache.getAsync(blackKey),
16 cache.getAsync(whiteKey) 16 cache.getAsync(whiteKey)
17 ]).then((args) => { 17 ]).then((args) => {
18 - const blacklist = args[0] || [],  
19 - whitelist = args[1] || []; 18 + let blacklist = [];
  19 + let whitelist = [];
  20 +
  21 + try {
  22 + blacklist = JSON.parse(args[0]);
  23 + } catch (error) {
  24 + logger.error(error);
  25 + }
  26 +
  27 + try {
  28 + whitelist = JSON.parse(args[1]);
  29 + } catch (error) {
  30 + logger.error(error);
  31 + }
  32 +
  33 + blacklist = blacklist || [];
  34 + whitelist = whitelist || [];
20 35
21 if (blacklist.length === 0 && whitelist.length === 0) { 36 if (blacklist.length === 0 && whitelist.length === 0) {
22 return Promise.resolve(true); 37 return Promise.resolve(true);