app.js 7.49 KB
/**
 * yohobuy app
 * @author: xuqi<qi.xu@yoho.cn>
 * @date: 2016/4/25
 */
'use strict';

const config = require('./config/common');

global.Promise = require('bluebird');

const yohoLib = require('yoho-node-lib');

// 全局注册library
yohoLib.global(config);

const express = require('express');
const path = require('path');
const compression = require('compression');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const moment = require('moment');
const uuid = require('uuid');

const pkg = require('./package.json');
const hbsEvent = require('./config/hbsevent');
const _ = require('lodash');
const stringProcess = require('./utils/string-process');

const app = express();

// 向模板注入变量
switch (app.get('env')) {
    case 'development':
        global.devEnv = app.locals.devEnv = true;
        break;
    case 'test':
        global.isTest = app.locals.isTest = true;
        break;
    case 'production':
        global.isProduction = app.locals.isProduction = true;
        break;
    default:
        break;
}

global.version = app.locals.version = pkg.version;
app.locals.startTime = moment().format('YYYYMMDDHH');
app.locals.currentYear = moment().format('YYYY');

const logger = global.yoho.logger;

// zookeeper
if (config.zookeeperServer) {
    const monitor = global.yoho.monitorSender;
    const monitorType = _.get(monitor, 'type.ZOOKEEPER');

    require('yoho-zookeeper')(config.zookeeperServer, 'wap', app.locals.wap = {}, false, {
        onerror: (err) => {
            monitor.tallyFail(monitorType, {
                code: err.code,
                message: err.name
            });
        }
    });
}

// 访问域名层级
app.set('subdomain offset', 3);

app.set('etag', false);

app.enable('trust proxy');

app.disable('x-powered-by');

// docker验证项目是否正常发布
app.use('/node/status.html', (req, res) => {
    return res.status(200).end();
});
app.head('*', (req, res) => {
    res.status(200).end();
});

app.use((req, res, next) => {
    req.url = stringProcess.decodeURIComponent(req.url) || '/404';// 错误的网址编码重定向404
    req.isApmReport = _.get(req.app.locals, 'wap.open.bughd', false); // 把错误上报的开关绑定到上下文,node-lib 库要使用
    let isSpider = /spider/i.test(req.get('User-Agent') || '');

    if (config.aliasDomains.some(d => d === req.hostname) || isSpider) {
        // 如果是非m.yohobuy.com域名(无法设置sessionid到cookie域名不同)或是爬虫访问,session降级使用cookiesession
        req.session = {
            degrage: true
        };
    }
    next();
});

// 添加请求上下文
app.use(global.yoho.httpCtx());

// 指定libray目录
global.utils = path.resolve('./utils');

// 访问域名层级
app.set('subdomain offset', 3);

app.use(global.yoho.hbs({
    extname: '.hbs',
    defaultLayout: 'layout',
    layoutsDir: path.join(__dirname, 'doraemon/views'),
    partialsDir: path.join(__dirname, 'doraemon/views/partial'),
    views: path.join(__dirname, 'doraemon/views'),
    helpers: _.assign(global.yoho.helpers, require('./utils/helpers')),
    cb: hbsEvent.cb
}));


app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
    extended: false
}));
app.use(cookieParser());
app.use(compression());

require('./doraemon/middleware/yoho-session')(app);

app.use((req, res, next) => {
    req.user = {}; // 全局的用户数据
    req.yoho = {}; // req和res绑定yoho对象,用于传递全局数据, 如req.yoho.channel等
    req.app.locals.wap = app.locals.wap; // zookeper对象赋值

    // 判断请求是否来自app
    req.yoho.isMiniApp = /miniProgram/i.test(req.get('User-Agent') || '') ||
        req.query.client_type === 'miniapp';
    req.yoho.isNowApp = /yohonow/i.test(req.get('User-Agent') || '');
    req.yoho.isMarsApp = /yohomars/i.test(req.get('User-Agent') || '');
    req.yoho.isYohoApp = /YohoBuy/i.test(req.get('User-Agent') || '');
    req.yoho.isApp = req.yoho.isMiniApp ||
        req.yoho.isNowApp ||
        req.yoho.isMarsApp ||
        req.yoho.isYohoApp ||
        (req.query.app_version && req.query.app_version !== 'false') ||
        (req.query.appVersion && req.query.appVersion !== 'false');

    // 独立的 UDID
    if (!req.cookies.udid || (req.yoho.isApp && req.query.udid)) {
        let udid;

        if (req.yoho.isApp && req.query.udid) {
            udid = req.query.udid;
        } else {
            udid = uuid.v4();
        }

        res.cookie('udid', udid, {
            domain: 'yohobuy.com',
            expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) // 1Year
        });
        req.cookies.udid = udid;
    }
    next();
});

function isOpenApmRisk(req) {
    return _.get(req.app.locals, 'wap.open.apmrisk', false);
}

// dispatcher
try {
    const tdkUrl = require('./doraemon/middleware/redis-url');
    const user = require('./doraemon/middleware/user');
    const reqParamsFilter = require('./doraemon/middleware/req-params-filter');
    const urlRewrite = require('./doraemon/middleware/url-rewrite');
    const subDomain = require('./doraemon/middleware/sub-domain');
    const itemNameHandler = require('./doraemon/middleware/item-name-handler');
    const setYohoData = require('./doraemon/middleware/set-yoho-data');
    const errorHanlder = require('./doraemon/middleware/error-handler');
    const setPageInfo = require('./doraemon/middleware/set-pageinfo');
    const devtools = require('./doraemon/middleware/devtools');
    const layoutTools = require('./doraemon/middleware/layout-tools');
    const seo = require('./doraemon/middleware/seo');
    const pageCache = require('./doraemon/middleware/page-cache');
    const downloadBar = require('./doraemon/middleware/download-bar');
    const routeEncode = require('./doraemon/middleware/route-encode');
    const ifElseMd = require('./doraemon/middleware/ifElseMd');
    const riskManagementApm = require('./doraemon/middleware/risk-management2');
    const riskManagement = require('./doraemon/middleware/risk-management');
    const statics = require('./doraemon/middleware/statics');

    // YOHO 前置中间件
    app.use(tdkUrl());
    app.use(reqParamsFilter());
    app.use(urlRewrite());
    app.use(subDomain());
    app.use(itemNameHandler);
    app.use(setYohoData());
    app.use(user());

    // 请求限制中间件
    if (!app.locals.devEnv) {
        const limiter = require('./doraemon/middleware/limiter');
        const limiterApm = require('./doraemon/middleware/limiter/index2');

        app.use(ifElseMd(isOpenApmRisk, limiterApm, limiter));
    }

    if (app.locals.devEnv) {
        app.use(devtools());
    }

    app.use(seo());
    app.use(setPageInfo());
    app.use(layoutTools());
    app.use(pageCache());
    app.use(routeEncode.md);
    app.use(downloadBar());
    app.use(ifElseMd(isOpenApmRisk, riskManagementApm(), riskManagement()));
    app.use(statics(app));

    require('./dispatch')(app);
    app.all('*', errorHanlder.notFound()); // 404

    // YOHO 后置中间件
    app.use(errorHanlder.serverError());
} catch (err) {
    logger.error(err);
}

// listener
app.listen(config.port, function() {
    logger.info('yohobuy start');
    hbsEvent.event.on('hbs-complete', () => {
        process.send && process.send('ready');
        logger.info('hbs-all-complete');
        if (!process.env.NODE_ENV || process.env.NODE_ENV === 'dev') {
            let {devHost, port} = require('./config/devtools');

            require('request-promise')({
                url: `http://${devHost}:${port}/event?action=reload`
            }).then(() => {}, () =>{});
        }
    });
});