需求

  • 同一个用户只能点赞一次,再次点击则取消点赞
  • 如果当前用户已经点赞,则点赞按钮高亮显示(前端已实现,判断字段Blog类的isLike属性)
  • 获取最新点赞的前5个用户展示

实现步骤

  1. 给Blog类中添加一个isLike字段,标示是否被当前用户点赞
  2. 利用Redis的ZSet集合判断是否点赞过,未点赞过则点赞数+1,已点赞过则点赞数-1
  3. 修改查询Blog的业务,判断当前登陆用户是否点赞过,赋值给isLike字段
  4. 使用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;
/**
* 商户id
*/
private Long shopId;
/**
* 用户id
*/
private Long userId;
/**
* 用户图标
*/
@TableField(exist = false)
private String icon;
/**
* 用户姓名
*/
@TableField(exist = false)
private String name;
/**
* 是否点赞过了
*/
@TableField(exist = false)
private Boolean isLike;

/**
* 标题
*/
private String title;

/**
* 探店的照片,最多9张,多张以","隔开
*/
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);
// 查询blog是否被点赞
isBlogLiked(blog);
return blog;
}

/**
* 查询博客的作者
* @param blog 博客
*/
private void queryBlogUser(Blog blog) {
User user = userService.getById(blog.getUserId());
blog.setIcon(user.getIcon());
blog.setName(user.getNickName());
}

/**
* 查询博客是否被浏览者点赞
* @param blog 博客
*/
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();
}
// 解析出其中的用户id
List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
// 将User转为UserDTO,隐藏信息
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来存储,定时持久化。