error-handler.js 6.65 KB
/**
 * 404 错误
 * @return {[type]}
 */
const headerModel = require('../models/header');
const logger = global.yoho.logger;
const cache = global.yoho.cache.master;
const helpers = global.yoho.helpers;
const sender = global.yoho.apmSender;
const config = global.yoho.config;
const hostname = require('os').hostname();
const routeEncode = require('./route-encode');
const pathWhiteList = require('./limiter/rules/path-white-list');
const ipWhiteList = require('./limiter/rules/ip-white-list');
const _ = require('lodash');

const replaceKey = '${refer}';
const checkRefer = helpers.urlFormat('/3party/check', {refer: replaceKey});

const forceNoCache = (res) => {
    if (res && !res.finished) {
        res.set({
            'Cache-Control': 'no-cache',
            Pragma: 'no-cache',
            Expires: (new Date(1900, 0, 1, 0, 0, 0, 0)).toUTCString()
        });
    }
};

const _err510 = (req, res, code, err) => {
    res.status(code);

    if (req.xhr) {
        return res.json({
            code: err.code,
            message: err.message || '服务器繁忙请稍后重试!'
        });
    }

    return res.render('error/510', {
        err: err,
        module: 'common',
        page: 'error',
        title: '服务器繁忙 | Yoho!Buy有货 | 潮流购物逛不停',
        pageHeader: headerModel.setNav({
            navTitle: err.message || '服务器繁忙请稍后重试!'
        }),
        pageFooter: true,
        isErr: true
    });
};

const _err500 = (req, res, code, err) => {
    res.status(code);

    if (req.xhr) {
        return res.json({
            code: err.code,
            message: '服务器错误!'
        });
    }

    return res.render('error/500', {
        err: err,
        module: 'common',
        page: 'error',
        title: '服务器错误 | Yoho!Buy有货 | 潮流购物逛不停',
        pageHeader: headerModel.setNav({
            navTitle: '服务器错误!'
        }),
        pageFooter: true,
        isErr: true
    });
};

exports.notFound = () => {
    return (req, res) => {
        forceNoCache(res);

        res.status(404);

        if (req.xhr) {
            return res.json({
                code: 404,
                message: '抱歉,页面不存在!'
            });
        }
        return res.render('error/404', {
            module: 'common',
            page: 'error',
            title: '页面不存在 | Yoho!Buy有货 | 潮流购物逛不停',
            pageHeader: headerModel.setNav({
                navTitle: '抱歉,页面不存在!'
            }),
            pageFooter: true,
            isErr: true
        });
    };
};

/**
 * 服务器错误
 * @return {[type]}
 */
exports.serverError = () => {
    return async(err, req, res, next) => {
        const uid = req.user ? req.user.uid : 0;
        const udid = _.get(req, 'cookies.udid', 'yoho');

        forceNoCache(res);

        err = err || {};

        err.code = parseInt(err.code || err.statusCode, 10) || 500;

        if (err.type !== 'entity.parse.failed') { // json 解析失败不上报错误
            logger.error(`error at path: ${req.url}`);
            logger.error(err);
        }

        if (req.isApmReport && err.code !== 401) {
            // 上报服务端错误
            sender.addMessage({
                measurement: 'error-report',
                tags: {
                    app: config.appName, // 应用名称
                    hostname,
                    type: 'server',
                    route: `[${req.method}]${_.get(req, 'route.path', '')}`, // 请求路由
                    reqID: req.reqID,
                    uid,
                    udid,
                    code: err.code || 500,
                    path: `[${req.method}]${routeEncode.getRouter(req)}`,
                    url: encodeURIComponent(req.originalUrl),
                    ip: _.get(req, 'yoho.clientIp', '')
                },
                fields: {
                    message: err.message,
                    stack: err.stack,
                    useragent: req && req.get('user-agent')
                }
            });
        }

        if (err && err.code === 401) {
            logger.warn(`401 warn info:client_type=${req.query.client_type},req.user=${JSON.stringify(req.user)},req.query=${JSON.stringify(req.query)},cookies=${JSON.stringify(req.cookies)}`); // eslint-disable-line

            if (req.xhr) {
                return res.status(401).json(err);
            } else if (req.yoho.isApp) {
                if (err.lowVersion) {
                    return res.render('error/app-auth', {
                        module: 'common',
                        page: 'app-update',
                        localCss: true,
                        message: err.message
                    });
                }
                return res.render('error/app-auth', {
                    module: 'common',
                    page: 'app-redirect-login',
                    message: '请登录',
                    localCss: true,
                    refer: err.refer
                });
            } else {
                return res.redirect(helpers.urlFormat('/signin.html', {
                    refer: req.originalUrl
                }));
            }
        } else if (err.apiRisk || err.code === 9999991 || err.code === 9999992) {
            let remoteIp = req.yoho.clientIp;

            if (!err.apiRisk && (_.includes(pathWhiteList(), req.path) || (await ipWhiteList(remoteIp)))) {
                return _err510(req, res, 510, err);
            }

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

                remoteIp = arr[0];
            }

            let sessionLimitKey;
            let isHuman;

            if (err.apiRisk) {
                sessionLimitKey = 'apiRiskValidate';
            } else {
                sessionLimitKey = 'apiLimitValidate';
                isHuman = await cache.getAsync(`${config.app}:limiter:api:ishuman:${remoteIp}`);
            }

            if (!isHuman) {
                cache.setAsync(`${config.app}:limiter:${remoteIp}`, 1, config.LIMITER_IP_TIME);

                req.session[sessionLimitKey] = true;

                if (req.xhr) {
                    return res.status(510).json({
                        code: err.code,
                        data: {refer: checkRefer.replace(replaceKey, req.get('Referer') || '')}
                    });
                }

                return res.redirect(checkRefer.replace(replaceKey, req.protocol + '://' + req.get('host') + req.originalUrl));
            }

            return _err510(req, res, 510, err);
        }

        if (!res.headersSent) {
            return _err500(req, res, 500, err);
        }

        return next(err);
    };
};