需求:
实现步骤:
LikedDTO
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LikedDTO {
/**
* 点赞数量
*/
long likedSum;
/**
* 用户是否点过赞
*/
Boolean isLiked;
}
点赞操作
// 点赞操作
@Override
public String doLike() {
String key = "RedisSessionDemo:liked";
String phone = UserHolder.getUser().getPhone();
// 查询是否点赞过
Boolean isLiked = redisTemplate.opsForSet().isMember(key, phone);
if (BooleanUtil.isTrue(isLiked)) {
// 点赞过 -> 取消点赞
redisTemplate.opsForSet().remove(key, phone);
return "取消点赞成功";
}
// 没点赞过 -> 点赞
redisTemplate.opsForSet().add(key, phone);
return "点赞成功";
}
获取点赞数据
// 获取点赞数据
@Override
public LikedDTO getLiked() {
String key = "RedisSessionDemo:liked";
Long likedNum = redisTemplate.opsForSet().size(key);
if (likedNum == null) {
likedNum = 0L;
}
UserDTO user = UserHolder.getUser();
Boolean isLiked = false;
if (user != null) {
isLiked = redisTemplate.opsForSet().isMember(key, user.getPhone());
}
return new LikedDTO(likedNum, isLiked);
}
点赞排行:类似朋友圈的点赞列表,按照点赞的先后顺序展示头像等信息。
使用 sorted set 结构,将点赞的时间戳作为分数值记录。
修改点赞函数
// 获取点赞数据
@Override
public LikedDTO getLiked2() {
String key = "RedisSessionDemo:liked";
Long likedNum = redisTemplate.opsForZSet().size(key);
if (likedNum == null) {
likedNum = 0L;
}
UserDTO user = UserHolder.getUser();
boolean isLiked = false;
if (user != null) {
Double score = redisTemplate.opsForZSet().score(key, user.getPhone());
isLiked = (score != null && score > 0);
}
return new LikedDTO(likedNum, isLiked);
}
// 点赞操作
@Override
public String doLike2() {
String key = "RedisSessionDemo:liked";
String phone = UserHolder.getUser().getPhone();
// 查询是否点赞过
Double isLiked = redisTemplate.opsForZSet().score(key, phone);
if (isLiked != null && isLiked > 0) {
// 点赞过 -> 取消点赞
redisTemplate.opsForZSet().remove(key, phone);
return "取消点赞成功";
}
// 没点赞过 -> 点赞
redisTemplate.opsForZSet().add(key, phone, System.currentTimeMillis());
return "点赞成功";
}
获取点赞列表
// 获取点赞列表
@Override
public List<String> getLikedList() {
String key = "RedisSessionDemo:liked";
// 获取所有元素
Set<String> set = redisTemplate.opsForZSet().range(key, 0, -1);
if (set != null) {
return new ArrayList<>(set);
}
return Collections.emptyList();
}
我们按月来统计用户签到信息,签到记录为1,未签到则记录为0。
把每一个bit位对应当月的每一天,形成了映射关系。用0和1标示业务状态,这种思路就称为位图(BitMap)。
Redis中是利用string类型数据结构实现BitMap,因此最大上限是512M,转换为bit则是 2 32 2^{32} 232 个bit位。
BitMap的操作命令有:
因为BitMap底层是基于String数据结构,因此其操作也都封装在字符串相关操作中了。
public Boolean sign() {
String phone = UserHolder.getUser().getPhone();
Date date = new Date();
String yearAndMonth = new SimpleDateFormat("yyyy:MM").format(date);
String key = "RedisSessionDemo:user:sign:" + phone + ":" + yearAndMonth;
int day = Integer.parseInt(new SimpleDateFormat("DD").format(date));
// 实现签到
redisTemplate.opsForValue().setBit(key, day, true);
return true;
}
连续签到:从最后一次签到开始向前统计,直到遇到第一次未签到为止的签到次数
封装SignData类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SignData {
// 月签到次数
Integer MonthTimes;
// 月连续签到次数
Integer ContinuousTimes;
}
业务实现
@Override
public SignData signdata() {
// 获取 bitmap
String phone = UserHolder.getUser().getPhone();
Date date = new Date();
String yearAndMonth = new SimpleDateFormat("yyyy:MM").format(date);
String key = "RedisSessionDemo:user:sign:" + phone + ":" + yearAndMonth;
int day = Integer.parseInt(new SimpleDateFormat("DD").format(date));
List<Long> list = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(day + 1)).valueAt(0));
if (list == null || list.isEmpty()) {
return new SignData(0, 0);
}
Long sign = list.get(0);
if (sign == null) {
return new SignData(0, 0);
}
// 统计计算
int MonthTimes = 0;
int ContinuousTimes = 0;
boolean isContinuous = true;
while (sign != 0) {
// 连续签到
if (isContinuous) {
if ((sign & 1) == 1) {
ContinuousTimes++;
} else {
isContinuous = false;
}
}
// 月签到次数
if ((sign & 1) == 1) {
MonthTimes++;
}
sign = sign >> 1;
}
return new SignData(MonthTimes, ContinuousTimes);
}
UV统计在服务端做会比较麻烦,因为要判断该用户是否已经统计过了,需要将统计过的用户信息保存。但是如果每个访问的用户都保存到Redis中,数据量会非常恐怖。
Hyperloglog(HLL)是从Loglog算法派生的概率算法,用于确定非常大的集合的基数,而不需要存储其所有值。相关算法
Redis中的HLL是基于string结构实现的,单个HLL的内存永远小于16kb,内存占用低的令人发指!
作为代价,其测量结果是概率性的,有小于0.81%的误差。不过对于UV统计来说,这完全可以忽略。
@Test
void hyperlogTest() {
for (int i = 0; i < 100; i++) {
stringRedisTemplate.opsForHyperLogLog().add("hyperlogTest", "user-" + i);
}
Long size = stringRedisTemplate.opsForHyperLogLog().size("hyperlogTest");
System.out.println(size);
}