用户在发布内容(包括博客、想法、日记等等)时,后台数据入库后,要往Redis的有序集合添加一条分数为0的记录。这个有序集合是用来对内容点赞量做排序的。同时,可以记录用户操作日志。
@Override
public String insertArticle(ArticleParams articleParams) {String uuid = UUID.randomUUID().toString();Article article = new Article();article.setAuthor(articleParams.getAuthor());article.setContent(articleParams.getContent());article.setTitle(articleParams.getTitle());article.setArticleUuid(uuid);save(article);//写redisredisTemplate.opsForZSet().add(ARTICLE_LIKE_COUNT, uuid, 0);UserLog userLog = new UserLog();userLog.setUserType("publish");userLog.setArticleUuid(uuid);userLog.setUserId(articleParams.getAuthor());userLogMapper.insert(userLog);return uuid;
}
一般地,用户点击点赞图标,如果之前点赞过,就是取消点赞;如果之前没有点赞过,就是点赞。后台怎么判断是否点赞过呢?
点赞时,后台会把用户ID加入到该篇内容的点赞用户集合中,然后内容点击量加一。取消点赞时,后台将用户ID从集合中移除,内容点击量减一。
用户刷新到这篇内容时,可以调用后台接口,根据用户ID是否在该篇内容的点赞用户集合中,判断是否点赞过。
我这里把判断用户ID是否在集合中、添加/移除用户ID、点击量加/减一这三个操作放在一个lua脚本中,作为一个原子性操作。
Redis点赞、取消赞
@Override
public String likeArticle(LikeArticleParams likeArticleParams) {String articleUuid = likeArticleParams.getArticleUuid();int userId = new Random().nextInt(100000);//异步发送到消息队列,限流 保证原子性boolean result = likeArticle(articleUuid, userId);//数据库写操作记录UserLog userLog = new UserLog();userLog.setUserType("like");userLog.setArticleUuid(articleUuid);userLog.setUserId(userId);if(result) {//点赞需要推送消息给文章作者} else {userLog.setUserType("unlike");}userLogMapper.insert(userLog);return "success";
}public boolean likeArticle(String articleUuid, int userId) {String likeArticleByUuidSet = "article:like:set:" + articleUuid;String redisScript = "if redis.call('exists', KEYS[1]) == 1 and redis.call('sismember', KEYS[1], ARGV[2]) == 1" +" then " +" redis.call('zincrby', KEYS[2], -1, ARGV[1])" +" redis.call('srem', KEYS[1], ARGV[2])" +" return 0 " +" else " +" redis.call('zincrby', KEYS[2], 1, ARGV[1])" +" redis.call('sadd', KEYS[1], ARGV[2])" +" return 1 " +" end ";DefaultRedisScript defaultRedisScript = new DefaultRedisScript(redisScript, Boolean.class);List<String> keys = new ArrayList();keys.add(likeArticleByUuidSet);keys.add(ARTICLE_LIKE_COUNT);boolean result = (boolean) redisTemplate.execute(defaultRedisScript, keys, articleUuid, userId);return result;
}
article:like:set是Redis的set集合,scard查询多少用户点赞了该内容,srandmember随机返回一个点赞用户。
article:like:count是Redis的sorted set集合,srange返回指定排序位的成员,zscore返回成员的点赞量。
附上我用的数据库表
CREATE TABLE `article` (`id` int(0) NOT NULL AUTO_INCREMENT,`article_uuid` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文章唯一标识',`title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题',`content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '内容',`author` int(0) NULL DEFAULT NULL COMMENT '用户主键',`create_time` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`enable_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '有效标识 1有效 0无效',`like_count` int(0) NULL DEFAULT NULL COMMENT '点赞数',`comment_count` int(0) NULL DEFAULT NULL COMMENT '评论数',`update_time` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE
)
CREATE TABLE `user_log` (`id` int(0) NOT NULL AUTO_INCREMENT,`user_id` int(0) NULL DEFAULT NULL,`article_uuid` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,`user_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户类型like 点赞 unlike取消点赞 pulish发布文章',`comment` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '评论内容',`create_time` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP,`enable_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '有效1 无效0',PRIMARY KEY (`id`) USING BTREE
)