seckill.js 10.5 KB
/**
 * 秒杀页面
 * @author: 陈峰<feng.chen@yoho.cn>
 * @date: 2016/09/08
 */

'use strict';
const Promise = require('bluebird');
const logger = global.yoho.logger;
const moment = require('moment');
const mRoot = '../models';
const headerModel = require('../../../doraemon/models/header');
const seckillModel = require(`${mRoot}/seckill`);
const _ = require('lodash');

let headerData = headerModel.setNav({
    navTitle: '秒杀活动',
    navBtn: true,
});

/**
 * hotfix ios 6.5.5 版本隐藏设置提醒按钮
 */
const _iosHideRemindBtn = (products) => {
    return _.map(products, goods => {
        goods.hideRemind = true;
        return goods;
    });
};

/**
 * [时间缺0补0]
 */
const _timeFormat = (tick) => {
    return tick < 10 ? `0${tick}` : tick;
};


/**
 *  秒杀商品 添加 自定义属性
 *  ----------------------------------------
 * 已开抢
 *      product.over [APP,H5: 1.已抢光 2.过了endTime]
 *      product.isSeckill [APP,H5 秒杀中, 与over互斥]
 * 抢购中:
 *      product.over
 *      product.isSeckill
 * 即将开抢:
 *      product.wait = true
 *      product.remindFlag = true   [APP: 已添加提醒]
 *      product.remindFalg = false  [APP: 默认提醒状态]
 *      Product.hideRemind = true   [APP: queryRemindList接口失败的情况下]
 */
function _productAddFlag(productList, remindList) {
    let now = Date.now();

    productList
        .sort((a, b) => b.orderBy - a.orderBy)
        .forEach(product => {
            let startTime = product.startTime * 1000; // s to ms
            let endTime = product.endTime * 1000;

            product.remindFlag = false;
            product.readableTime = moment.unix(product.startTime).format('MM月DD日 HH:mm');

            if (now < startTime) { // 未开抢
                product.wait = true;
                product.isSeckill = true;

                if (!remindList) {
                    product.hideRemind = true;
                } else if (remindList.length) {
                    for (let i = 0, j = remindList.length; i < j; i++) {
                        if (remindList[i].productSkn === product.productSkn) { // remind on;
                            product.remindFlag = true;
                            remindList.splice(i, 1);
                            break;
                        }
                    }
                }
            } else if (product.secKillStatus === 0 || (now > endTime)) { // 确实已抢光, 过了秒杀时间
                product.over = true;
            }
        });

    return productList;
}

/**
 *    获取 秒杀产品列表
 */
function fetchProductList(activityInfo, uid, req) {
    let activityId = activityInfo.activityId;
    let hasRemindAttr = activityInfo.isApp && uid && (Date.now() < activityInfo.startTime);

    let productList = req.ctx(seckillModel).queryProductList(activityId);
    let remindList = hasRemindAttr ? req.ctx(seckillModel).queryRemindList(activityId, uid) : {
        code: 200,
        data: []
    };

    return Promise.all([productList, remindList])
        .then(res => {
            let r1 = res[0];
            let r2 = res[1];
            let result = [];

            if (r1.code !== 200) {
                return result;
            }

            result = r2.data ?
                _productAddFlag(r1.data, r2.data) :
                _productAddFlag(r1.data);

            return result;
        });
}


/**
 * [秒杀列表页面]
 */
const index = (req, res) => {
    return res.render('seckill', {
        title: '秒杀活动',
        pageHeader: headerData,
        pageStyle: 'seckill-page',
        pageFooter: true,
        width750: true,
    });
};

/**
 *  [秒杀列表 页面 初始数据]
 */
const indexData = (req, res, next) => {
    let focusTime = Number.parseInt(req.query.time, 10) || 0;
    let result = {};
    let uid = req.user.uid || req.query.uid;

    return req.ctx(seckillModel).queryActivity().then((resultActivity) => {
        // console.log(resultActivity)
        let focusIndex = false;

        // let nowTime = Date.now();
        if (!resultActivity.data) {
            return next();
        }
        result.currentTime = resultActivity.data.currentTime * 1000;
        if (resultActivity.code !== 200) {
            return next();
        }

        if (!(
            resultActivity.data &&
                resultActivity.data.secKillProductVoList &&
                resultActivity.data.secKillProductVoList.length
        )) {
            return res.json({
                title: '秒杀活动',
                pageHeader: headerData,
                error: '来晚啦~秒杀已结束'
            });
        }

        result.activitys = resultActivity.data.secKillProductVoList
            .sort((a, b) => a.startTime - b.startTime); // orderBy startTime ASC

        if (focusTime) {
            focusTime = moment(focusTime, 'YYYYMMDDHHmmss').unix();
            focusIndex = result.activitys.findIndex(activity=> activity.startTime === focusTime);

            if (focusIndex !== -1) {
                result.activitys[focusIndex].focus = true;
                focusIndex = true;
            } else {
                focusIndex = false;
            }
        }


        result.activitys.forEach((activity, i) => {
            let date,
                hour = 0,
                minute = 0;

            activity.startTime *= 1000;
            date = new Date(activity.startTime);
            hour = date.getHours();
            minute = date.getMinutes();
            activity.time = `${_timeFormat(hour)}:${_timeFormat(minute)}`;

            if (result.currentTime > activity.startTime) { // 当前时间大于这个时间段,已经开始和即将开始两种情况
                if (i < result.activitys.length - 1) {
                    let nextTime = result.activitys[i + 1].startTime * 1000;

                    if (result.currentTime < nextTime) { // 下一个时间段与当前时间来区别是否正在抢购
                        activity.now = true;
                        focusIndex || (activity.focus = focusIndex = true);
                    } else {
                        activity.over = true;
                    }
                } else { // 大于这个时间段但是后面没有秒抢时间端了,则依然显示抢购中
                    activity.now = true;
                    focusIndex || (activity.focus = focusIndex = true);
                }
            } else {
                activity.wait = true;
            }
        });
        if (result.activitys.length && result.activitys.findIndex(activity => activity.focus) < 0) {
            let i = result.activitys.length;

            // 没有focus的活动,则focus 最后一个 over 的活动
            while (i) {
                let activity = result.activitys[i - 1];

                if (activity.over) {
                    activity.focus = true;
                    break;
                }

                i = i - 1;
            }

            // 没有over,全部都是未开抢,则focus第一个
            if (!i) {
                result.activitys[0].focus = true;
            }
        }
        let focusActivity = result.activitys.find(activity => activity.focus);
        let activityInfo = {
            activityId: focusActivity.activityId,
            startTime: focusActivity.startTime * 1000,
            isApp: req.yoho.isApp
        };

        return fetchProductList(activityInfo, uid, req).then((resultProducts) => {
            result.products = resultProducts;

            if (/^6.5.5/.test(req.query.app_version) && req.query.client_type === 'iphone') {
                result.products = _iosHideRemindBtn(resultProducts);
            }

            res.json(result);
        });
    });

};

/**
 * [xhr根据活动id获取商品列表]
 */
const getProductList = (req, res, next) => {
    if (!req.xhr) {
        return next();
    }
    let activityId = Number(req.query.activityId);
    let startTime = Number(req.query.startTime);
    let uid = req.user.uid || req.query.uid;

    if (!activityId) {
        return next();
    }

    let activityInfo = {
        isApp: req.yoho.isApp,
        activityId,
        startTime
    };

    return fetchProductList(activityInfo, uid, req).then(resultProducts => {
        if (/^6.5.5/.test(req.query.app_version) && req.query.client_type === 'iphone') {
            resultProducts = _iosHideRemindBtn(resultProducts);
        }
        res.json({products: resultProducts});
    }).catch(error => {
        logger.error(error);
    });
};

// only app
const remind = (req, res) => {
    if (!(req.yoho.isApp && req.xhr)) {
        return res.status(404).json({
            msg: '请求不合法'
        });
    }

    return req.ctx(seckillModel).remind({
        on_off: req.body.on_off === 'true' ? true : false,
        activity_id: Number.parseInt(req.body.activity_id, 10),
        product_skn: Number.parseInt(req.body.product_skn, 10),
        uid: req.user.uid,
        sec_kill_id: Number.parseInt(req.body.sec_kill_id, 10),
        app_type: 0
    }).then(result => {
        return res.json(result);
    });
};

// Middleware:检测 app_version 是否正确
// @param okVersion string  "5.1.0" [master_version, minor_version, patch_version]
// TODO: 可以移到 middleware;
const checkAppVer = function(okVersion) {
    return (req, res, next) => {
        /**
         * [比较app版本]
         * @param okVersion string '5.1.0'
         * @param curVersion string
         * @returns -1: 小于, 0 等于, 1大于
         */

        let compareVersion = (okVersion, curVersion) => { //eslint-disable-line
            let result = 0; // -1: 小于, 0 等于, 1 大于

            if (okVersion === curVersion) {
                return result;
            }

            okVersion = okVersion.split('.');
            curVersion = curVersion.split('.');

            for (let i = 0; i < 3; i++) {
                let standard = Number.parseInt(okVersion[i], 10) || 0;
                let cur = Number.parseInt(curVersion[i], 10) || 0;

                if (cur < standard) {
                    result = -1;
                    break;
                } else if (cur > standard) {
                    result = 1;
                    break;
                }
            }

            return result;
        };


        if (req.yoho.isApp) {
            let curVersion = req.query.app_version;
            let isOld = compareVersion(okVersion, curVersion) === -1;

            if (isOld) {
                return res.redirect('/passport/update');
            }
        }

        next();
    };
};

module.exports = {
    index,
    indexData,
    getProductList,
    remind,
    checkAppVer
};