判断用户有没有点过赞,实现方案非常多,比如数据库里建一张表,记录blogId和给这个blog点赞的userId,机每点赞,这张表里就会记录一次,下次再来就判断下是否存在记录。但用数据库实现,太重了,首先数据库的性能不太好,而且这种点赞的判断可能会比较多,所以对数据库的压力就会比较大。可以使用轻量级的办法,比如redis。
那如果要判断用户有没有点赞过,其实就是记录一下当前blog被谁点赞过,所以可以在Redis里以blog的id为key,用集合去记录给这个blog点过赞的所有用户id。下次再来时,判断用户id在集合中是否存在。由于一个用户只能点赞一次,所以这个集合中用户id是不能重复,所以可以用set集合。
Blog.java
...
// 由于数据库中没这个字段,所以加 TableField(exist = false)注解
@TableField(exist = false)
private Boolean isLike; // 当前用户是否点赞过
...
BlogController.java
...
@PutMapping("/like/{id}")
public Result likeBlog(@PathVariable("id") Long id) {
return blogService.likeBlog(id);// 点赞功能,参数id是blog的id
}
...
IBlogService.java
public interface IBlogService extends IService<Blog> {
...
Result likeBlog(Long id);
...
}
BlogServiceImpl.java
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {
...
@Resource
private StringRedisTemplate stringRedisTemplate;
...
@Override
public Result likeBlog(Long id) {
// 1,获取登陆用户
Long userId = UserHolder.getUser().getId();
// 2,判断当前登陆用户是否已经点过赞
String key = "blog:liked:" + id;// 文章id
Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
// 由于isMember是包装类,所以直接判断可能为空,可以用hutool的工具类(BooleanUtil)判断
if (BooleanUtil.isFalse(isMember)) {
// 3,如果没点过赞,可以点赞
// 3.1 数据库点赞数+1
boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update();
// 3.2 保存用户id到Redis的set集合
if (isSuccess) {
stringRedisTemplate.opsForSet().add(key, userId.toString());
}
}else {
// 4,如果已经点赞,取消点赞
// 4.1 数据库点赞数-1
boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();
// 4.2 把用户id从Redis的set集合里移除
if (isSuccess) {
stringRedisTemplate.opsForSet().remove(key, userId.toString());
}
}
return Result.ok();
}
...
// 查询blog的函数也要修改
@Override
public Result queryBlogById(Long id) {
// 1,查询blog
Blog blog = getById(id);
if (blog == null) return Result.fail("文章不存在");
// 2,查询blog有关用户
queryBlogUser(blog);
// 【添加的部分】3,查询blog是否被点赞
isBlogLiked(blog);// 因为List列表页里也要用到,所以封装到一个函数里
}
// 判断blog是否被当前用户点赞
private void isBlogLiked(Blog blog) {
// 1,获取登陆用户
Long userId = UserHolder.getUser().getId();
// 2,判断当前登陆用户是否已经点过赞
String key = "blog:liked:" + blog.getId();// 文章id
Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
// 由于isMember是包装类,所以直接判断可能为空,可以用hutool的工具类(BooleanUtil)判断
blog.setIsLike(BooleanUtil.isTrue(isMember));// 给blog的相关字段赋值,如果是true,说明点过赞,前端可以根据这个字段,把点赞图标改为高亮
}
}
在文章详情页面,按照点赞时间先后排序,返回TOP5的用户。要排序、唯一,可以使用SortedSet
。
List | Set | SortedSet | |
---|---|---|---|
排序方式 | 按添加顺序排序 | 无法排序 | 根据score值(比如可以用时间戳)排序 |
唯一性 | 不唯一 | 唯一 | 唯一 |
查找方式 | 按索引查找或首尾查找(即想查找元素就得遍历一遍) | 根据元素查找 | 根据元素查找 |
Blog.java
...
// 由于数据库中没这个字段,所以加 TableField(exist = false)注解
@TableField(exist = false)
private Boolean isLike; // 当前用户是否点赞过
...
BlogController.java
...
@PutMapping("/like/{id}")
public Result likeBlog(@PathVariable("id") Long id) {
return blogService.likeBlog(id);// 点赞功能,参数id是blog的id
}
// 查询点赞的人
@GetMapping("/likes/{id}")
public Result queryBlogLikes(@PathVariable("id") Long id) {
return blogService.queryBlogLikes(id);// 点赞功能,参数id是blog的id
}
...
IBlogService.java
public interface IBlogService extends IService<Blog> {
...
Result likeBlog(Long id);
Result queryBlogLikes(Long id);
...
}
BlogServiceImpl.java
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {
...
@Resource
private StringRedisTemplate stringRedisTemplate;
...
@Override
public Result likeBlog(Long id) {
// 1,获取登陆用户
Long userId = UserHolder.getUser().getId();
// 2,判断当前登陆用户是否已经点过赞
String key = "blog:liked:" + id;// 文章id
// 查当前用户的score
/*
由于SortedSet里没有ismember命令,所以可以用zscore来返回元素的score值来判断有没有值。
由于她天然的做从小到大的排序,所以可以用zrange命令查前五个(zrange key 0 4)。
*/
Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
if (score == null) {
// 3,如果没点过赞,可以点赞
// 3.1 数据库点赞数+1
boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update();
// 3.2 保存用户id到Redis的set集合
if (isSuccess) {
// sorted set是opsForZSet(),还需要第三个score参数(这里用时间戳就行,因为会以时间戳排序)
// 相当于命令:zadd key value score
stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
}
}else {
// 4,如果已经点赞,取消点赞
// 4.1 数据库点赞数-1
boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();
// 4.2 把用户id从Redis的set集合里移除
if (isSuccess) {
stringRedisTemplate.opsForZSet().remove(key, userId.toString());
}
}
return Result.ok();
}
...
// 查询blog的函数也要修改
@Override
public Result queryBlogById(Long id) {
// 1,查询blog
Blog blog = getById(id);
if (blog == null) return Result.fail("文章不存在");
// 2,查询blog有关用户
queryBlogUser(blog);
// 【添加的部分】3,查询blog是否被点赞
isBlogLiked(blog);// 因为List列表页里也要用到,所以封装到一个函数里
}
// 判断blog是否被当前用户点赞
private void isBlogLiked(Blog blog) {
// 1,获取登陆用户(当然,这里需要判断用户是否已登陆,这里假设已登录)
Long userId = UserHolder.getUser().getId();
// 2,判断当前登陆用户是否已经点过赞
String key = "blog:liked:" + blog.getId();// 文章id
Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
// score不等于null,就说明该用户点赞了
blog.setIsLike(score != null);// 给blog的相关字段赋值,如果是true,说明点过赞,前端可以根据这个字段,把点赞图标改为高亮
}
@Override
public Result queryBlogLikes(Long id) {
String key = "blog:liked:" + id;// 文章id
// 1,查询top的点赞用户 zrange key 0 4
Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
if (top5 == null || top5.isEmpty()) {
return Result.ok(Collections.emptyList());// 给个空集合
}
// 2,解析其中的用户id,把top5中的id集合变成long后存到list中
List<Long> ids = top5.stream().map((Long::valueOf).collect(Collectors.toList()));
// 3,根据用户id查询用户
/*
List users = userService.listByIds(ids);
这边使用mp的listByIds方法时,由于数据库的问题,你给的id顺序和查出来的id顺序不一致。
因为数据库会根据id排序,而不是in里的id顺序。
所以就算Redis中根据顺序查出来了用户id,但如果在SQL语句使用in语法
来查用户list的话,数据库查询并存到list的顺序可能和Redis里的顺序不一致。比如SQL语句如下:
select id,phone from user where in id(2,1),这里的“2,1”是Redis中的用户id顺序,所以结果
本应该是先查出2的用户,然后再查出1的用户,但执行后可能会是先查出1的用户之后2的用户,
这样在前端显示时顺序可能变反,所以后面加上order by FIELD(id, 2, 1)强制指定顺序。
*/
String idStr = StrUtil.join(",", ids);// 拼接成字符串
// in("id", ids)等于in id(2,1),last是手动在sql语句后面拼接sql语句。
List<User> users = userService.query().in("id", ids).last("order by FIELD(id, "+ids+")").list();
// 4,返回
return Result.ok(users);
}
}
Redis基础 - 基本类型及常用命令
Redis基础 - Java客户端
Redis 基础 - 短信验证码登录
Redis 基础 - 用Redis查询商户信息
Redis 基础 - 优惠券秒杀《非集群》
Redis 基础 - 优惠券秒杀《分布式锁(初级)》
Redis 基础 - 优惠券秒杀《分布式锁(使用Redisson)》
Redis 基础 - 优惠券秒杀《初步优化(异步秒杀)》
Redis 基础 - 优惠券秒杀《基于Redis消息队列实现》