设计背景
在当今互联网应用中,签到被广泛用于提高应用日活,吸引用户访问网站。用户通过连续登录网站签到,换取积分,可以用来兑换一些奖品。对于提高用户的积极性,系统的活跃度来说,是一个简单且有效的方式。本文就针对签到系统给出了一个简单的实现思路。
技术选型
对于签到系统来说,一般都会统计该用户本月内的签到次数,连续签到天数,并根据不同连续签到的天数给出不同的积分。那么要统计这些数据,我们首先想到的是设计一个数据表,包括用户id,签到的年月日,用户当日签到就向数据库新增一条记录,在统计连续签到天数,也需要逐个记录去查询。对于一个用户来说,一个月就是三十条记录,当我们的系统人数越来越多后,这张表就会产生非常庞大的数据量,不仅占用磁盘空间,也会极大的影响查询效率。
为了解决数据库存储签到记录的局限性,人们想到了使用bit位来表示是否签到,众所周知,一个比特位只有0或1,那么自然就可以使用0表示未签到,使用1表示已签到,想要查询当月签到次数,只需要统计值为1的位的个数就可以了,这种使用位来表示任务的状态的数据结构就叫做bitmap,位图。
redis恰好为我们提供了bitmap的数据结构和相关操作命令,使我们操作bitmap变得十分便捷。redis中的bitmap底层是基于String类型实现的,可以理解为一个二进制位组成的字符串,非常适合处理大量布尔值或小整数的高效存储和快速操作,尤其适用于需要频繁读写的场景。通过合理的应用,可以在保证性能的同时大幅减少内存占用。用来实现签到功能自然再合适不过了。
查询签到
查询用户本月的签到情况。就是使用bitfield (key) get u(days) 0 命令,该命令表示从0号索引取指定个数的位,返回一个十进制数,将十进制数转为二进制,如果二进制数的位数少于天数的话,就需要在起点补0,表示当日未签到。
新增签到
就是使用set命令将对应的天数的bit位置1,要注意起始索引是0,所以使用命令时要将当前天数减一。
获取连续签到天数
获取当天之前的连续签到天数,就是获取到签到情况的十进制数后转化为二进制字符串,再根据位数决定是否补零,之后将字符串反转,获取到第一个不是1的索引位置,该索引就代表连续签到天数。
新增积分
本系统是一个在线学习系统,学员可以通过多种方式获取积分,兑换奖品,积分的获取规则如下:
-
签到规则 签到1天给1分 连续7天额外奖励10分 连续14天额外奖励20分 连续28天额外奖励40分 每月签到进度当月第一天重置
-
学习规则 每学习一小节积分+10,每天获得上限50分
-
交互规则(有效交互数据参与积分规则,无效数据会被删除) 写评价,积分+10 每日获得上限为50分 写问答,积分+5 每日获得上限为20分 写笔记,积分+3,每次被采集+2 每日获得上限为20分
每位学员的积分只在当月有效,下月清零重新统计。
其中,签到,学习,交互都属于不同的微服务,而用户在进行上述这些行为时,都需要向积分表插入一条记录。如果我们在每个服务中都加上统计积分的逻辑,就会使代码的耦合度过高,所以,我采用了rabbitmq进行跨服务间的积分信息收发,实现了异步的方式统计积分,将积分作为一个独立的微服务进行部署,实现了解耦,提高效率。具体实现如下:
在不同的服务中获取积分后,向rabbitmq中投递积分的详情,在积分服务中,监听这些来自不同服务的积分详情,执行保存逻辑,逻辑如下
查询不同类型的积分
用户可以查看当天不同类型的已获得积分和积分上限,效果如下
就是根据积分类型分组,统计每组的分值总和返回给用户。