1、要存储用户的签到记录或者活跃记录,实际上就是每天该用户的状态是0还是1
2、页面要展示用户一年的活跃记录图,后端如何设计更加高效?
单纯基于数据库
设计一张签到表,每个用户每天一条数据。
这里我们要分析一点,这张表的数据属于是稳定增长的。
如果数据量比较小,那还可以接受。但是如果用户量比较大,假如有一万用户,那一个月,一年下来可能就有上百万条记录。
CREATE TABLE user_sign_in (
id BIGINT AUTO_INCREMENT PRIMARY KEY, -- 主键,自动递增
userId BIGINT NOT NULL, -- 用户ID,关联用户表
signDate DATE NOT NULL, -- 签到日期
createdTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 记录创建时间
UNIQUE KEY uq_user_date (userId, signDate) -- 用户ID和签到日期的唯一性约束
);
使用redis中的set集合
我们可以为每个用户定义一个set,用来存储用户活跃的日期,活跃一天添加一天
SADD user:signins:123 "2024-09-01"
SADD user:signins:123 "2024-09-02"
这个时候,我们发现,存储的数据中,年份的重复量非常大。我们可以考虑把年份提取到key中
即每个用户每年一个key,进一步减少存储量
SADD user:signins:2024:123 "09-01"
SADD user:signins:2024:123 "10-01"
位图bitmap
我们之前也说过,用户当天是否活跃,代表就是用户在那一天的状态是0还是1, 那么用户当天是否活跃其实只需要1位来存储就够了。
按照刚才redis中key的定义来看。每个用户每年的活跃记录,只需要366位数据就可以存储。
这时候就可以用到redis中的bitmap数据结构。
我们给每个用户每年都设置一个bitmap,大小就是366,然后计算日期是属于年中的第几天。然后作为bitmap中的坐标,给对应位置存储具体的状态。
例如:
-- 表示用户在第 240 天打卡
SETBIT user:signins:2024:123 240 1
-- 表示用户在第 241 天打卡
SETBIT user:signins:2024:123 241 1
实现
引入redisson
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.21.0</version>
</dependency>
编写redis配置
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class RedissonConfig {
private String host;
private Integer port;
private Integer database;
private String password;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://" + host + ":" + port)
.setDatabase(database)
.setPassword(password);
return Redisson.create(config);
}
}
编写签到接口
public interface RedisConstant {
/**
* 用户签到记录的 Redis Key 前缀
*/
String USER_SIGN_IN_REDIS_KEY_PREFIX = "user:signins";
/**
* 获取用户签到记录的 Redis Key
* @param year 年份
* @param userId 用户 id
* @return 拼接好的 Redis Key
*/
static String getUserSignInRedisKey(int year, long userId) {
return String.format("%s:%s:%s", USER_SIGN_IN_REDIS_KEY_PREFIX, year, userId);
}
}
签到业务实现接口
/**
* 添加用户签到记录
*
* @param userId 用户签到
* @return 当前是否已签到成功
*/
public boolean addUserSignIn(long userId) {
LocalDate date = LocalDate.now();
String key = RedisConstant.getUserSignInRedisKey(date.getYear(), userId);
RBitSet signInBitSet = redissonClient.getBitSet(key);
// 获取当前日期是一年中的第几天,作为偏移量(从 1 开始计数)
int offset = date.getDayOfYear();
// 检查当天是否已经签到
if (!signInBitSet.get(offset)) {
// 如果当天还未签到,则设置
return signInBitSet.set(offset, true);
}
// 当天已签到
return true;
}