Authored by 周少峰

Merge branch 'feature/req-limiter'

@@ -51,6 +51,9 @@ if (config.zookeeperServer) { @@ -51,6 +51,9 @@ if (config.zookeeperServer) {
51 require('yoho-zookeeper')(config.zookeeperServer, 'pc', app.locals.pc = {}, global.yoho.cache); 51 require('yoho-zookeeper')(config.zookeeperServer, 'pc', app.locals.pc = {}, global.yoho.cache);
52 } 52 }
53 53
  54 +// 请求限制中间件
  55 +app.use(require('./doraemon/middleware/limiter'));
  56 +
54 app.set('subdomain offset', 2); 57 app.set('subdomain offset', 2);
55 app.use(global.yoho.hbs({ 58 app.use(global.yoho.hbs({
56 extname: '.hbs', 59 extname: '.hbs',
@@ -15,7 +15,7 @@ const specialController = require(`${cRoot}/special`); @@ -15,7 +15,7 @@ const specialController = require(`${cRoot}/special`);
15 const coupon = require(`${cRoot}/coupon`); 15 const coupon = require(`${cRoot}/coupon`);
16 16
17 // 专题活动 17 // 专题活动
18 -router.get(/^\/special\/(\d+)_(.*)\.html$/, specialController.special); 18 +router.get(/^\/special\/(\d+)_/, specialController.special);
19 19
20 // 领券中心 20 // 领券中心
21 router.get('/coupon/index', coupon.index); 21 router.get('/coupon/index', coupon.index);
@@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
9 const router = require('express').Router(); // eslint-disable-line 9 const router = require('express').Router(); // eslint-disable-line
10 const cRoot = './controllers'; 10 const cRoot = './controllers';
11 const auth = require(`${global.middleware}/auth`); 11 const auth = require(`${global.middleware}/auth`);
12 -// const filter = require(`${global.middleware}/filter-qs`); // 参数过滤 12 +const gbk2utf = require(`${global.middleware}/gbk2utf`);
13 13
14 // 商品详情controller 14 // 商品详情controller
15 const detail = require(`${cRoot}/detail`); 15 const detail = require(`${cRoot}/detail`);
@@ -83,13 +83,13 @@ router.post('/detail/notify/add', auth, notify.add); // 增 @@ -83,13 +83,13 @@ router.post('/detail/notify/add', auth, notify.add); // 增
83 router.post('/detail/notify/cancel', auth, notify.cancel); // 删除到货通知 83 router.post('/detail/notify/cancel', auth, notify.cancel); // 删除到货通知
84 84
85 // 搜索 85 // 搜索
86 -router.get('/search/index', search.index); 86 +router.get('/search/index', gbk2utf, search.index);
87 router.get('/search/filter/brands', search.serachFilterBrands); 87 router.get('/search/filter/brands', search.serachFilterBrands);
88 router.get('/search/suggest', search.suggest); // 搜索提示 88 router.get('/search/suggest', search.suggest); // 搜索提示
89 router.get('/api/suggest', search.suggest4Old); 89 router.get('/api/suggest', search.suggest4Old);
90 90
91 // 商品分类列表页 91 // 商品分类列表页
92 -router.get('/list/index', list.index); 92 +router.get('/list/index', gbk2utf, list.index);
93 93
94 // 新品到着 94 // 新品到着
95 router.get('/list/new', list.new); 95 router.get('/list/new', list.new);
  1 +'use strict';
  2 +
  3 +const urlEncode = require('urlencode');
  4 +const _ = require('lodash');
  5 +const helpers = global.yoho.helpers;
  6 +const allowSubDomain = ['list', 'search'];
  7 +
  8 +
  9 +module.exports = (req, res, next) => {
  10 + let query = req.query.query;
  11 + let subDomain = req.subdomains[0];
  12 +
  13 + if (query && _.includes(allowSubDomain, subDomain)) {
  14 + try {
  15 + decodeURIComponent(query);
  16 + return next();
  17 + } catch (e) {
  18 + try {
  19 + query = urlEncode.decode(query, 'gb2312');
  20 + req.query.query = query;
  21 + return res.status(301).redirect(helpers.urlFormat('', req.query, subDomain));
  22 + } catch (e1) {
  23 + return res.redirect(helpers.urlFormat('', null, subDomain));
  24 + }
  25 + }
  26 + }
  27 +
  28 + return next();
  29 +};
  1 +
  2 +'use strict';
  3 +
  4 +const cache = global.yoho.cache.master;
  5 +const _ = require('lodash');
  6 +const logger = global.yoho.logger;
  7 +
  8 +module.exports = (req, res, next) => {
  9 + let remoteIp = req.get('X-Forwarded-For') || '';
  10 +
  11 + if (remoteIp.indexOf(',') > 0) {
  12 + let arr = remoteIp.split(',');
  13 + remoteIp = arr[0];
  14 + }
  15 +
  16 + if (remoteIp && !_.get(req.app.locals, 'pc.sys.noLimiter')) { // 判断获取remoteIp成功,并且开关未关闭
  17 + let key = `pc:limiter:${remoteIp}`;
  18 +
  19 + logger.debug(`request limiter key=${key}`);
  20 +
  21 + cache.getAsync(key).then(result => {
  22 + if (result && _.isNumber(result)) {
  23 + if (result > 30) { // 判断 qps
  24 + res.status(403).end();
  25 + } else {
  26 + cache.incrAsync(key, 1); // qps + 1
  27 + next();
  28 + }
  29 + } else {
  30 + cache.setAsync(key, 1, 1); // 设置key,1s失效
  31 + next();
  32 + }
  33 + }).catch(e => {
  34 + logger.error(`request limiter get key[${key}] from cache error.`, e);
  35 + next();
  36 + });
  37 + } else {
  38 + next();
  39 + }
  40 +};
@@ -59,6 +59,7 @@ @@ -59,6 +59,7 @@
59 "request-ip": "^1.2.2", 59 "request-ip": "^1.2.2",
60 "request-promise": "^3.0.0", 60 "request-promise": "^3.0.0",
61 "serve-favicon": "^2.3.0", 61 "serve-favicon": "^2.3.0",
  62 + "urlencode": "^1.1.0",
62 "uuid": "^2.0.2", 63 "uuid": "^2.0.2",
63 "yoho-express-session": "^2.0.0", 64 "yoho-express-session": "^2.0.0",
64 "yoho-node-lib": "0.2.5", 65 "yoho-node-lib": "0.2.5",