limiter.js 3.1 KB

'use strict';

const cache = global.yoho.cache.master;
const _ = require('lodash');
const logger = global.yoho.logger;
const config = global.yoho.config;
const helpers = global.yoho.helpers;

let ONE_DAY = 60 * 60 * 24;
const MAX_QPS = config.maxQps;

const PAGES = {
    '/product/\\/p([\\d]+)(.*)/': 5,
    '/product/list/index': 5
};

const IP_WHITE_LIST = [
    '106.38.38.146',
    '218.94.75.58'
];

function urlJoin(a, b) {
    if (_.endsWith(a, '/') && _.startsWith(b, '/')) {
        return a + b.substring(1, b.length);
    } else if (!_.endsWith(a, '/') && !_.startsWith(b, '/')) {
        return a + '/' + b;
    } else {
        return a + b;
    }
}

module.exports = (req, res, next) => {
    let remoteIp = req.get('X-Forwarded-For') || '';

    if (remoteIp.indexOf(',') > 0) {
        let arr = remoteIp.split(',');

        remoteIp = arr[0];
    }

    remoteIp = _.trim(remoteIp);

    if (remoteIp &&
        !_.get(req.app.locals, 'pc.sys.noLimiter') &&
        !_.includes(IP_WHITE_LIST, remoteIp)) {   // 判断获取remoteIp成功,并且开关未关闭

        let key = `pc:limiter:${remoteIp}`;

        res.on('render', function() {
            let route = req.route ? req.route.path : '';
            let appPath = req.app.mountpath;

            if (_.isArray(route) && route.length > 0) {
                route = route[0];
            }

            let pageKey = urlJoin(appPath, route.toString()); // route may be a regexp
            let pageIncr = PAGES[pageKey] || 0;

            if (/^\/p([\d]+)/.test(req.path)) {
                pageIncr = 5;
            }

            if (pageIncr > 0) {
                cache.incrAsync(key, pageIncr);
            }
        });

        const limiterPage = () => {
            let refer = req.method === 'GET' ? req.get('Referer') : '';
            let limitAPI = helpers.urlFormat('/3party/check', {refer: refer});
            let limitPage = helpers.urlFormat('/3party/check', {refer: req.protocol + '://' + req.get('host') + req.originalUrl});

            if (_.indexOf(['/3party/check', '/passport/imagesNode', '/passport/cert/headerTip'], req.path) >= 0) {
                return next();
            }

            if (req.xhr) {
                return res.json({
                    code: 400,
                    data: {refer: limitAPI}
                });
            }

            return res.redirect(limitPage);
        };

        cache.getAsync(key).then((result) => {
            if (result && _.isNumber(result)) {
                if (result > MAX_QPS) {       // 判断 qps
                    cache.touch(key, ONE_DAY);
                    logger.info('req limit', key);
                    return limiterPage();
                } else {
                    cache.incrAsync(key, 1); // qps + 1
                    return next();
                }
            } else {
                cache.setAsync(key, 1, 60);  // 设置key,1m失效
                return next();
            }
        }).catch((err) => {
            logger.error(`request limiter get key[${key}] from cache error.`, err);
            return next();
        });
    } else {
        return next();
    }


};