用户在发布内容(包括博客、想法、日记等等)时,后台数据入库后,要往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);
//写redis
redisTemplate.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
)