需求
- 同一个用户只能点赞一次,再次点击则取消点赞
- 如果当前用户已经点赞,则点赞按钮高亮显示(前端已实现,判断字段Blog类的isLike属性)
- 获取最新点赞的前5个用户展示
实现步骤
- 给Blog类中添加一个isLike字段,标示是否被当前用户点赞
- 利用Redis的ZSet集合判断是否点赞过,未点赞过则点赞数+1,已点赞过则点赞数-1
- 修改查询Blog的业务,判断当前登陆用户是否点赞过,赋值给isLike字段
- 使用ZSet的range函数取出最新点赞的前5个用户ID,查询信息。
代码实操
实体类
点击查看实体类
@Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("tb_blog") public class Blog implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO) private Long id;
private Long shopId;
private Long userId;
@TableField(exist = false) private String icon;
@TableField(exist = false) private String name;
@TableField(exist = false) private Boolean isLike;
private String title;
private String images;
private String content;
private Integer liked;
private Integer comments;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
|
点赞Blog
首先从ThreadHolder中获取登陆用户的ID,根据key查询是否点赞,如果没有点赞,更新数据库后,以用户ID和点赞时间为value存入Redis的ZSet中;如果已点赞,先更新数据库,后删除Redis数据。
public static final String BLOG_LIKED_KEY = "blog:liked:";
@Resource StringRedisTemplate stringRedisTempla @Override public void likeBlog(Long id) { Long userId = UserHolder.getUser().getId(); String key = BLOG_LIKED_KEY + id; Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString()); if (score == null) { boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update(); if (isSuccess) { stringRedisTemplate.opsForZSet().add(key, String.valueOf(userId), System.currentTimeMillis()); } } else { boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update(); if (isSuccess) { stringRedisTemplate.opsForZSet().remove(key, String.valueOf(userId)); } } }
|
查询Blog
Controller
@GetMapping("/{id}") public Result queryBlogById(@PathVariable("id") Long id) { try { Blog blog = blogService.queryBlogById(id); return Result.ok(blog); } catch (Exception e) { return Result.fail(e.getMessage()); } }
|
Service实现类
查询Redis中是否有该用户点赞信息,如果有,则设置Blog类中的isLike = true。
public static final String BLOG_LIKED_KEY = "blog:liked:";
@Resource StringRedisTemplate stringRedisTemplate;
@Override public Blog queryBlogById(Long id) throws Exception { Blog blog = getById(id); if (blog == null) { throw new Exception("笔记不存在!"); } queryBlogUser(blog); isBlogLiked(blog); return blog; }
private void queryBlogUser(Blog blog) { User user = userService.getById(blog.getUserId()); blog.setIcon(user.getIcon()); blog.setName(user.getNickName()); }
private void isBlogLiked(Blog blog) { UserDTO user = UserHolder.getUser(); if (user == null) { return; } Long userId = user.getId(); String key = BLOG_LIKED_KEY + blog.getId(); Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString()); blog.setIsLike(score != null); }
|
点赞Top5
根据点赞时间获取点赞的前5个用户
public static final String BLOG_LIKED_KEY = "blog:liked:";
@Resource StringRedisTemplate stringRedisTempla
@Override public List<UserDTO> queryBlogLikes(Long id) { String key = RedisConstants.BLOG_LIKED_KEY + id; Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4); if (top5 == null || top5.isEmpty()) { return Collections.emptyList(); } List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList()); String idStr = StrUtil.join(",", ids); return userService.query() .in("id", ids) .last("ORDER BY FIELD(id," + idStr + ")") .list() .stream() .map(user -> BeanUtil.copyProperties(user, UserDTO.class)) .collect(Collectors.toList()); }
|
ZSet中返回的结果是根据点赞顺序来的,但是使用IN查询的结果却不是。
使用ORDER BY FIELD(id, id1, id2)返回按照原始字段顺序的结果
总结
使用Redis中的可排序集合ZSet进行点赞数据的存储
更加完善的做法应该是从Redis读取数据持久化到MySQL中
文章点赞数量也应该使用Redis来存储,定时持久化。