2.1.点赞探店笔记:
a.接口说明:
- 1.在首页的探店笔记的排行榜和探店图文详情页面都有点赞的功能
b.问题分析:
- 1.现在存在一个问题,一个用户可以无限点赞,这显然是不合理的,所以我们需要对点赞功能进行一个优化,实现一人只能点赞一次
- 2.对于点赞这种高频变化的数据,如果我们使用MySQL是十分不理智的,因为
MySQL慢、并且并发请求MySQL会影响其它重要业务,容易影响整个系统的性能,继而降低了用户体验
。所以选择使用Redis,然后推荐使用Set类型,因为Set类型的数据结构具有- 不重复,符合业务的特点,一个用户只能点赞一次
- 高性能,Set集合内部实现了高效的数据结构(Hash表)
- 灵活性,Set集合可以实现一对多,一个用户可以点赞多个博客,符合实际的业务逻辑
当然也可以选择使用Hash(Hash占用空间比Set更小),如果想要点赞排序也可以选用
Sorted Set
b.功能完善:
c.编码实现:
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {
@Resource
private IUserService userService;
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 根据id查询博客
*
* @param id
* @return
*/
@Override
public Result queryBlogById(Long id) {
// 查询博客信息
Blog blog = this.getById(id);
if (Objects.isNull(blog)) {
return Result.fail("笔记不存在");
}
// 查询blog相关的用户信息
queryUserByBlog(blog);
// 判断当前用户是否点赞该博客
isBlogLiked(blog);
return Result.ok(blog);
}
/**
* 判断当前用户是否点赞该博客
*/
private void isBlogLiked(Blog blog) {
Long userId = ThreadLocalUtls.getUser().getId();
String key = BLOG_LIKED_KEY + blog.getId();
Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
blog.setIsLike(BooleanUtil.isTrue(isMember));
}
/**
* 查询热门博客
*
* @param current
* @return
*/
@Override
public Result queryHotBlog(Integer current) {
// 根据用户查询
Page<Blog> page = this.query()
.orderByDesc("liked")
.page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
// 获取当前页数据
List<Blog> records = page.getRecords();
// 查询用户
records.forEach(blog -> {
this.queryUserByBlog(blog);
this.isBlogLiked(blog);
});
return Result.ok(records);
}
/**
* 点赞
*
* @param id
* @return
*/
@Override
public Result likeBlog(Long id) {
// 判断用户是否点赞
Long userId = ThreadLocalUtls.getUser().getId();
String key = BLOG_LIKED_KEY + blog.getId();
// sismember key value
Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
boolean result;
if (BooleanUtil.isFalse(isMember)) {
// 用户未点赞,点赞数+1
result = this.update(new LambdaUpdateWrapper<Blog>()
.eq(Blog::getId, id)
.setSql("liked = liked + 1"));
if (result) {
// 数据库更新成功,更新缓存 sadd key value
stringRedisTemplate.opsForSet().add(key, userId.toString());
}
} else {
// 用户已点赞,点赞数-1
result = this.update(new LambdaUpdateWrapper<Blog>()
.eq(Blog::getId, id)
.setSql("liked = liked - 1"));
if (result) {
// 数据更新成功,更新缓存 srem key value
stringRedisTemplate.opsForSet().remove(key, userId.toString());
}
}
return Result.ok();
}
/**
* 查询博客相关用户信息
*
* @param blog
*/
private void queryUserByBlog(Blog blog) {
Long userId = blog.getUserId();
User user = userService.getById(userId);
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
}
}
2.2.点赞排行榜笔记:
a.接口说明:
- 1.在探店笔记详情的页面,应该把该笔记点赞的人数显示出来,形成点赞排行榜
b.需求说明:
- 1.返回点赞排名前5的用户:
c.数据类型:
- 1.
SortedSet
实现点赞排行榜
- 1.在平常我们所使用的软件中(比如微信、QQ、抖音)的点赞功能都会
默认按照时间顺序对点赞的用户进行一个排序
,后点赞的用户会排在最前面,而Set是无序的,无法满足这个需求,虽然 List有序,但是不唯一,查找效率也比较低,所以也不推荐使用,此时我们就可以选择使用SortedSet这个数据结构,它完美的满足了我们所有的需求:唯一、有序、查找效率高
- 2.相较于Set集合,SortedList有以下
不同之处
:- 对于Set集合我们可以使用 isMember方法判断用户是否存在,对于SortedList我们可以使用
ZSCORE方法
判断用户是否存在 - Set集合没有提供范围查询,无法获排行榜前几名的数据,SortedList可以使用
ZRANGE方法
实现范围查询
- 对于Set集合我们可以使用 isMember方法判断用户是否存在,对于SortedList我们可以使用
d.点赞逻辑改善:
- 1.由于Redis中存储点赞信息的数据类型发生变化,所以先修改点赞逻辑:
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {
@Resource
private IUserService userService;
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 根据id查询博客
*
* @param id
* @return
*/
@Override
public Result queryBlogById(Long id) {
// 查询博客信息
Blog blog = this.getById(id);
if (Objects.isNull(blog)) {
return Result.fail("笔记不存在");
}
// 查询blog相关的用户信息
queryUserByBlog(blog);
// 判断当前用户是否点赞该博客
isBlogLiked(blog);
return Result.ok(blog);
}
/**
* 判断当前用户是否点赞该博客
*/
private void isBlogLiked(Blog blog) {
UserDTO user = ThreadLocalUtls.getUser();
if (Objects.isNull(user)){
// 当前用户未登录,无需查询点赞
return;
}
Long userId = user.getId();
String key = BLOG_LIKED_KEY + blog.getId();
Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
blog.setIsLike(Objects.nonNull(score));
}
/**
* 查询热门博客
*
* @param current
* @return
*/
@Override
public Result queryHotBlog(Integer current) {
// 根据用户查询
Page<Blog> page = this.query()
.orderByDesc("liked")
.page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
// 获取当前页数据
List<Blog> records = page.getRecords();
// 查询用户
records.forEach(blog -> {
this.queryUserByBlog(blog);
this.isBlogLiked(blog);
});
return Result.ok(records);
}
/**
* 点赞
*
* @param id
* @return
*/
@Override
public Result likeBlog(Long id) {
// 1、判断用户是否点赞
Long userId = ThreadLocalUtls.getUser().getId();
String key = BLOG_LIKED_KEY + id;
// zscore key value
Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
boolean result;
if (score == null) {
// 1.1 用户未点赞,点赞数+1
result = this.update(new LambdaUpdateWrapper<Blog>()
.eq(Blog::getId, id)
.setSql("liked = liked + 1"));
if (result) {
// 数据库更新成功,更新缓存 zadd key value score
stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
}
} else {
// 1.2 用户已点赞,点赞数-1
result = this.update(new LambdaUpdateWrapper<Blog>()
.eq(Blog::getId, id)
.setSql("liked = liked - 1"));
if (result) {
// 数据更新成功,更新缓存 zrem key value
stringRedisTemplate.opsForZSet().remove(key, userId.toString());
}
}
return Result.ok();
}
/**
* 查询所有点赞博客的用户
*
* @param id
* @return
*/
@Override
public Result queryBlogLikes(Long id) {
// 查询Top5的点赞用户 zrange key 0 4
Long userId = ThreadLocalUtls.getUser().getId();
String key = BLOG_LIKED_KEY + id;
Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
if (top5 == null || top5.isEmpty()) {
return Result.ok(Collections.emptyList());
}
List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
List<UserDTO> userDTOList = userService.listByIds(ids).stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
return Result.ok(userDTOList);
}
/**
* 查询博客相关用户信息
*
* @param blog
*/
private void queryUserByBlog(Blog blog) {
Long userId = blog.getUserId();
User user = userService.getById(userId);
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
}
}
e.新的问题说明:
我们点赞后,发现先点赞的那个人的头衔排在了后面,这是不符合逻辑的
- 如果我们的需求是:先点赞的排在前面,后点赞的排在后面该如何实现?这就需要涉及到MySQL的一些相关知识了,
在MySQL中如果我们使用in进行条件查询,我们的查询默认是数据库顺序查询,数据库中的记录默认都是按照ID自增的,所以查出来的结果默认是按照ID自增排序的
- 2.解决办法:在查询的时候,指定按照某个字段的参数的顺序进行查询
select id, phone,password,nick_name,icon,create_time,update_time
from tb_user
where id in(1, 5)
order by field(id, 5, 1)
f.在编码中进行改善:
/**
* 查询所有点赞博客的用户
*
* @param id
* @return
*/
@Override
public Result queryBlogLikes(Long id) {
// 查询Top5的点赞用户 zrange key 0 4
Long userId = ThreadLocalUtls.getUser().getId();
String key = BLOG_LIKED_KEY + id;
Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
if (top5 == null || top5.isEmpty()) {
return Result.ok(Collections.emptyList());
}
List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
String idStr = StrUtil.join(",", ids);
// 根据id降序排序 select * from tb_user where id in(1,5) order by field(id, 1, 5)
List<UserDTO> userDTOList = userService.list(new LambdaQueryWrapper<User>()
.in(User::getId, ids)
.last("order by field (id," + idStr + ")"))
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
return Result.ok(userDTOList);
}
- 2.点赞查询的结果:可以看出是按照顶赞的时间顺序进行排序的了