day42 达人探店博客与点赞功能

1. 今天做了什么

  • 完善达人探店功能
    • 编写查询笔记(博客)的接口
    • 完善点赞业务
    • 实现点赞排行榜功能

2. 核心知识点

查询笔记接口

  • Blog 类中的 @TableField(exist = false) 是 MyBatis-Plus 中的注解,用于标记实体类中的某个字段在数据库表中不存在,从而让 MyBatis-Plus 在生成 SQL 时忽略该字段
  • 因为 icon 和 name 两个字段仅用于前端展示,数据库没有,故添加该注解
1
2
3
4
5
6
7
@TableField(exist = false)
private String icon;
/**
* 用户姓名
*/
@TableField(exist = false)
private String name;
  • 查询 blog,查询 user,将用户昵称与头像写入 blog 返回即可

点赞业务

  • Redis 用 Set 记录某篇笔记被哪些用户赞过
  • 点赞业务逻辑:
    • 查 Redis,未赞则更新数据库,更新 Redis;赞过则反之
    • 在查询 blog 的业务里,查 Redis,判断是否赞过,将结果写入 blog 返回,这样用户就知道自己有没有赞过了

点赞排行

  • 改用 Sorted Set(有序集合),将时间戳作为分数保存,可以确定点赞顺序
  • 存:stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
  • 删:stringRedisTemplate.opsForZSet().remove(key, userId.toString());
  • 判断是否存在(返回空表示不存在,返回分数则存在):stringRedisTemplate.opsForZSet().score(key, userId.toString());
  • 注意:SQL 语句条件 where in(5,1) 传入是无法保证顺序的,需要 order by FIELD(id,5,1)
1
2
3
4
5
6
7
8
List<UserDTO> userDTOS = userService
// .listByIds(list) 无法保证顺序
.query().in("id", list).last("order by FIELD(id, " + listStr + ")").list()
.stream()
.map(user -> {
return BeanUtil.copyProperties(user, UserDTO.class);
})
.collect(Collectors.toList());

3. 深度思考

  1. 这里的点赞肯定存在线程安全问题未处理,如果线程 1 判断未点赞,尚未更新数据库之前,其他线程也判断为点赞,这些线程都会进入数据库,造成一人多赞。可以使用锁,将判断与更新加锁,让同一用户同一笔记的请求串行

  2. 这里使用 Redis 存储某笔记被谁赞过,可以判断用户是否赞过某博客,如果有展示用户点赞历史的功能,则不能只存储某笔记被谁赞过,查询遍历全部笔记不现实,应该还要存储某用户赞过哪些笔记

  3. 如果并发很高,大量请求修改数据库,会使业务缓慢,数据库压力过大

    1. 可以使用 day41 学的消息队列,将更新数据库操作加入消息队列,异步执行,但要注意同一用户对同一笔记的消息按顺序消费
    2. 或者不每次同步数据库,而是 Redis 维护笔记点赞数,定时/定量同步到数据库
      1. 判断是否点赞,更新点赞数与集合使用 Lua 脚本执行,具有原子性,解决了线程安全问题
      2. 定时同步,解决了数据库压力的问题

4. 踩过的坑

5. 遗留问题 / 想不通