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 的队列中只会记录多个当日天数,不会影响循环判断签到天数的结果