Redis 每日签到功能·双十一预热活动

2024-03-16 17:04:32 浏览数 (2)

Redis 常用于跨进程、跨服务器的数据缓存服务,我们通常会使用 Redis 来存储 Session 会话数据,而不会在程序重启、多进程运行、负载均衡、跨域等情况时,会出现 Session 丢失或多进程、多个负载站点间状态不能共享的情况,而在 Node.js 中,连接 Redis 要使用 node_redis 模块,关于更多的 node_redis 模块的详细介绍,可以戳 node_redis 的 Github 主页

需求分析

双十一预热活动,活动时间一共为 10 天,在活动期间,每位用户每天有一次签到机会,签到成功后,会点亮签到界面中对应的天数,若是当天没有签到,则在第二天显示未签到样式

每日签到每日签到

我们把今天的样式命名为 todayCheck,如上图的第五天样式,把已签到的样式命名为 hasCheck,如上图的第一天样式,把未签到的样式命名为 notCheck,如上图的第三天样式,确定好每种状态的命名之后,我们先来列出需要实现的功能,分别是:a. 用户未登录时,所有天数的样式为 todayCheck;b. 用户登陆之后,将所有已签到的天数,所对应的日期样式更新为 hasCheck,未签到的天数更新为 notCheck;c. 用户今日首次点击添加燃料按钮后,更新今天的日期样式为 hasCheck,之后的点击都将提示 “今日已签到” 的提示语

Model

代码语言:javascript复制
// 构造函数
function act20171027rand () {}

// 继承randBaseModel
act20171027rand.prototype = new randBaseModel({
    actName: 'act20171027rand',
    startTime: 1508774400000, // 测试开始时间 2017-10-24 00:00:00 
    // startTime: 1509379200000, // todo 活动开始时间 2017-10-31 00:00:00
    endTime: 1510243199000, // 活动结束时间  2017-11-09 23:59:59
})

randBaseModel 抽奖模块基类是跟抽奖相关的,在这里没有涉及到,就不详细描述了,在活动正式上线时,需要将测试时间改成正式时间,需要修改的地方比较多,为了避免遗漏,建议大家在注释中加上 todo 进行标致,上线前 Ctrl F 搜索一下所有的 todo 就好了,我们需要在 Model 文件中设置两个新的方法,一个用来记录用户今日签到的天数是第几天,一个用来获取用户的所有签到天数

代码语言:javascript复制
// 记录用户签到天数
act20171027rand.prototype.getGetTimesPromise = function(userId){
    var that = this;
    var redisClient = this.redisClient;
    var userCheckInKey = this.actName   '_check_'   userId;
    
    // todo 测试时间 2017-10-24 00:00:00 1508774400
    // 活动正式开始时间 2017-10-31 00:00:00 1509379200
    var date =  new Date();
    var checkToday = Math.ceil((date - 1508774400000)/86400000)
    // var checkToday = Math.ceil((date - 1509379200000)/86400000)
    // console.log("抽奖1 今天是第几天"   checkToday);

    return new Promise(function(resolve, reject){
        // todo 写入今日签到的天数
        redisClient.rpush(userCheckInKey, checkToday, function(err, reply){
            if(err) return reject({code: 5005, result: 'redis写入签到天数失败'});
            resolve(checkToday);
        })
    })
}

通过时间戳来获取今天是第几天,并将其记录在 key 中,因为签到的天数有多个,所以我们在这里使用队列进行签到天数的记录,该方法通过点击 “添加燃料” 按钮时触发,需要注意的是,当用户点击按钮多次时,将会记录多个数据到队列中去

代码语言:javascript复制
// 获取用户签到的天数
act20171027rand.prototype.getCheckInPromise = function(userId){
    console.log("获取签到天数 getCheckInPromise");
    var that = this;
    var redisClient = this.redisClient;
    var userCheckInKey = this.actName   '_check_'   userId;    
    return new Promise(function(resolve, reject){
        redisClient.lrange(userCheckInKey, 0, -1, function(err, reply){
            if(err) return reject({code: 5004, result: 'redis读取次数失败'});
            console.log("返回的签到天数 "   reply);
            resolve(reply);
        })
    })
}

当用户成功登陆后,router 调用该方法,获取用户的签到天数

运行结果运行结果

Router

在 Model 文件中定义好相关的方法后,我们需要通过 Router 去读取相应的方法,不过在此之前,先来给大家看下签到界面的 HTML 结构

代码语言:javascript复制
<ul class="check clearfix">
    <li class="check_1">
        {{if check_1 == 'todayCheck'}}
       <img src="http://act.cycangcdn.com/20171027/171027/171027_pc_img/todayCheck_1.png">
        {{else if check_1 == 'hasCheck'}}
        <img src="http://act.cycangcdn.com/20171027/171027/171027_pc_img/hasCheck_1.png">
        {{else}}
        <img src="http://act.cycangcdn.com/20171027/171027/171027_pc_img/notCheck_1.png">
        {{/if}}
    </li>
    <li class="check_2">
        {{if check_2 == 'todayCheck'}}
       <img src="http://act.cycangcdn.com/20171027/171027/171027_pc_img/todayCheck_1.png">
        {{else if check_2 == 'hasCheck'}}
        <img src="http://act.cycangcdn.com/20171027/171027/171027_pc_img/hasCheck_1.png">
        {{else}}
        <img src="http://act.cycangcdn.com/20171027/171027/171027_pc_img/notCheck_1.png">
        {{/if}}
    </li>
    
    <!-- 一共10个 -->
    
    <li class="check_10">
        {{if check_10 == 'todayCheck'}}
       <img src="http://act.cycangcdn.com/20171027/171027/171027_pc_img/todayCheck_1.png">
        {{else if check_10 == 'hasCheck'}}
        <img src="http://act.cycangcdn.com/20171027/171027/171027_pc_img/hasCheck_1.png">
        {{else}}
        <img src="http://act.cycangcdn.com/20171027/171027/171027_pc_img/notCheck_1.png">
        {{/if}}
    </li>
</ul>

可以看到,我们是通过 check 的值来更换相应的日期样式,当用户未登录时,我们将所有日期样式设为 todayCheck

代码语言:javascript复制
var events = require('events');
var emitter = new events.EventEmitter();
var userModel = require(MODEL_PATH   '/userModel');
var _userModel = new userModel();
var act20171027RandModel = require(MODEL_PATH   '/act20171027RandModel.js');
var _mod = new act20171027RandModel();

module.exports = function act20171027(req, res) {
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    var data = {};
    data.username = '';
    data.times = '?';
    data.logState = 0;
    data.check_1 = 'todayCheck';
    data.check_2 = 'todayCheck';
    data.check_3 = 'todayCheck';
    data.check_4 = 'todayCheck';
    data.check_5 = 'todayCheck';
    data.check_6 = 'todayCheck';
    data.check_7 = 'todayCheck';
    data.check_8 = 'todayCheck';
    data.check_9 = 'todayCheck';
    data.check_10 = 'todayCheck';
    var checkAllDay = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; //签到的所有天数

    var _userModel = new userModel();
    _userModel.checkLogin(req, function(user_info, err, errCode){
        if (errCode == 200) {
            data.logState = 1;
            data.userName = user_info.username !== '未命名的用户' ? user_info.username : user_info.phone;

            // todo 获取已签到的天数
            // user_info.user_id 测试的时候使用固定的id
            _mod.getCheckInPromise(user_info.user_id).then(function(result){
                var hasCheckDay = result; // 已签到的天数

                // 更新已签到天数的img
                for(var h = 0; h < hasCheckDay.length; h  ){
                    if(hasCheckDay[h] == checkAllDay[0]){
                        data.check_1 = 'hasCheck';
                    }else if(hasCheckDay[h] == checkAllDay[1]){
                        data.check_2 = 'hasCheck';
                    }else if(hasCheckDay[h] == checkAllDay[2]){
                        data.check_3 = 'hasCheck';
                    }else if(hasCheckDay[h] == checkAllDay[3]){
                        data.check_4 = 'hasCheck';
                    }else if(hasCheckDay[h] == checkAllDay[4]){
                        data.check_5 = 'hasCheck';
                    }else if(hasCheckDay[h] == checkAllDay[5]){
                        data.check_6 = 'hasCheck';
                    }else if(hasCheckDay[h] == checkAllDay[6]){
                        data.check_7 = 'hasCheck';
                    }else if(hasCheckDay[h] == checkAllDay[7]){
                        data.check_8 = 'hasCheck';
                    }else if(hasCheckDay[h] == checkAllDay[8]){
                        data.check_9 = 'hasCheck';
                    }else if(hasCheckDay[h] == checkAllDay[9]){
                        data.check_10 = 'hasCheck';
                    }
                }
                
                // 更新未签到天数的img
                // todo 测试时间 2017-10-24 00:00:00 1508774400
                // 活动正式开始时间 2017-10-31 00:00:00 1509379200
                var date =  new Date();
                var Today = Math.ceil((date - 1508774400000)/86400000);
                // var Today = Math.ceil((date - 1509379200000)/86400000);
                
                var pastAllDay = checkAllDay.slice(0, Today - 1);               
                
                function differentNum(arr1, arr2){
                    var C = new Array();
                    var E = new Array();
                    var arrString = ","   arr2.toString()   ",";
                    for(var i in arr1 ){
                        if(arrString.indexOf(","   arr1[i]   ",") >= 0){
                            
                        }else{
                            C.push(arr1[i]);
                        }
                    }
                    return C;
                }
                
                var noCheckDay = differentNum(pastAllDay, hasCheckDay);
                for(var p in noCheckDay) {
                    if(noCheckDay[p] == pastAllDay[0]){
                        data.check_1 = 'notCheck';
                    }else if(noCheckDay[p] == pastAllDay[1]){
                        data.check_2 = 'notCheck';
                    }else if(noCheckDay[p] == pastAllDay[2]){
                        data.check_3 = 'notCheck';
                    }else if(noCheckDay[p] == pastAllDay[3]){
                        data.check_4 = 'notCheck';
                    }else if(noCheckDay[p] == pastAllDay[4]){
                        data.check_5 = 'notCheck';
                    }else if(noCheckDay[p] == pastAllDay[5]){
                        data.check_6 = 'notCheck';
                    }else if(noCheckDay[p] == pastAllDay[6]){
                        data.check_7 = 'notCheck';
                    }else if(noCheckDay[p] == pastAllDay[7]){
                        data.check_8 = 'notCheck';
                    }else if(noCheckDay[p] == pastAllDay[8]){
                        data.check_9 = 'notCheck';
                    }else if(noCheckDay[p] == pastAllDay[9]){
                        data.check_10 = 'notCheck';
                    }
                }
                
                emitter.emit('show', req, res, data);
            }).catch(function(err){
                res.write('发生错误了!~');
                res.end();
            });
        }else{
            data.logState = 0;
            emitter.emit('show', req, res, data);
        }
    });
}

用户登录之后,调用 getCheckInPromise 方法,获取用户已签到天数,并将对应天数的 todayCheck 样式更换为 hasCheck 样式,接下来我们需要判断今天是第几天,假如今天是第三天,我只签了今天,前两天都没有签到,那我们需要将第一第二天的 todayCheck 样式更换为 notCheck 样式,具体实现可以看代码

反复点击签到按钮反复点击签到按钮

而当用户反复点击签到按钮时,redis 的队列中只会记录多个当日天数,不会影响循环判断签到天数的结果

0 人点赞