发布探店笔记

数据库表

  • tb_blog:探店笔记表,包含笔记中的标题、文字、图片等
  • tb_blog_comments:其他用户对探店笔记的评价

image.png
点击首页最下方+,发布探店图文
image.png

发笔记中的上传照片单独的功能,点击上传照片后请求发上传到后端/upload接口,获取到照片后将其fileName返回前端。

两个功能的逻辑代码:

  1. @PostMapping("blog")
  2. public Result uploadImage(@RequestParam("file") MultipartFile image) {
  3. try {
  4. // 获取原始文件名称
  5. String originalFilename = image.getOriginalFilename();
  6. // 生成新文件名
  7. String fileName = createNewFileName(originalFilename);
  8. // 保存文件
  9. image.transferTo(new File(SystemConstants.IMAGE_UPLOAD_DIR, fileName));
  10. // 返回结果
  11. log.debug("文件上传成功,{}", fileName);
  12. return Result.ok(fileName);
  13. } catch (IOException e) {
  14. throw new RuntimeException("文件上传失败", e);
  15. }
  16. }
  1. @PostMapping
  2. public Result saveBlog(@RequestBody Blog blog) {
  3. // 获取登录用户
  4. UserDTO user = UserHolder.getUser();
  5. blog.setUserId(user.getId());
  6. // 保存探店博文
  7. blogService.save(blog);
  8. // 返回id
  9. return Result.ok(blog.getId());
  10. }

需要修改图片路径
image.png

查看探店笔记

需求:点击首页探店笔记进入详情界面,实现该查询接口
image.png
查看探店笔记Controller相关代码:

  1. @GetMapping("/hot")
  2. public Result queryHotBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) {
  3. return blogService.queryHotBlog(current);
  4. }
  5. @GetMapping("/{id}")
  6. public Result queryBlogById(@PathVariable("id")Long id){
  7. return blogService.queryBlogById(id);
  8. }

查看探店笔记ServiceImpl代码:

  1. @Service
  2. public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {
  3. @Resource
  4. private IUserService userService;
  5. @Override
  6. public Result queryBlogById(Long id) {
  7. //1.查询blog
  8. Blog blog = getById(id);
  9. if (blog == null) {
  10. return Result.fail("博客不存在!");
  11. }
  12. //2.查询blog有关的用户
  13. queryBlogUser(blog);
  14. return Result.ok(blog);
  15. }
  16. @Override
  17. public Result queryHotBlog(Integer current) {
  18. // 根据用户查询
  19. Page<Blog> page = query()
  20. .orderByDesc("liked")
  21. .page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
  22. // 获取当前页数据
  23. List<Blog> records = page.getRecords();
  24. // 查询用户
  25. records.forEach(this::queryBlogUser);
  26. return Result.ok(records);
  27. }
  28. private void queryBlogUser(Blog blog) {
  29. Long userId = blog.getUserId();
  30. User user = userService.getById(userId);
  31. blog.setName(user.getNickName());
  32. blog.setIcon(user.getIcon());
  33. }
  34. }

点赞

首页的探店笔记排行榜和探店图文详情页面都有点赞的功能:
image.png
原点赞逻辑代码:

  1. @PutMapping("/like/{id}")
  2. public Result likeBlog(@PathVariable("id") Long id) {
  3. // 修改点赞数量
  4. blogService.update()
  5. .setSql("liked = liked + 1").eq("id", id).update();
  6. return Result.ok();
  7. }

点赞bug:一个用户可以无限次点赞
需求:

  • 一个用户只能点赞一次,再次点击取消点赞
  • 当前用户已点赞,高亮显示

实现:

  • 给Blog添加isLike字段,判断是否被当前用户点赞
  • 修改点赞功能,利用Redis的set集合判断是否点赞过,未点赞+1,已点赞-1
  • 修改根据id查询Blog业务,判断是否点赞,赋值isLike字段
  • 修改分页查询Blog业务,判断是否点赞,赋值isLike字段
  1. /**
  2. * 是否点赞过了
  3. */
  4. @TableField(exist = false)
  5. private Boolean isLike;

修改点赞功能

  1. @Override
  2. public void likeBlog(Long id) {
  3. //1.获取登录用户
  4. Long userId = UserHolder.getUser().getId();
  5. //2.判断当前用户是否点赞
  6. String key = "blog:liked:" + id;
  7. Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
  8. if(BooleanUtil.isFalse(isMember)){
  9. //3.如果未点赞,可以点赞
  10. //3.1数据库点赞数+1
  11. boolean isSuccess = update().setSql("like = like + 1").eq("id", id).update();
  12. //3.2保存用户到Redis的set集合
  13. if(isSuccess){
  14. stringRedisTemplate.opsForSet().add(key, userId.toString());
  15. }
  16. }else {
  17. //4.如果已点赞,取消点赞
  18. //4.1数据库点赞-1
  19. boolean isSuccess = update().setSql("like = like + 1").eq("id", id).update();
  20. //4.2把用户从Redis的set集合移除
  21. if(isSuccess){
  22. stringRedisTemplate.opsForSet().remove(key, userId.toString());
  23. }
  24. }
  25. }

查询Blog是否点赞,修改两处:
在queryBlogById和queryHotBlog里接口添加

  1. //3.查询blog是否被点赞
  2. isBlogLiked(blog);
  3. private void isBlogLiked(Blog blog) {
  4. //1.获取登录用户
  5. Long userId = UserHolder.getUser().getId();
  6. //2.判断当前用户是否点赞
  7. String key = "blog:liked:" + blog.getId();
  8. Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
  9. blog.setIsLike(BooleanUtil.isTrue(isMember));
  10. }

image.png
image.png

点赞排行榜

在探店笔记详情页面,显示给该笔记点赞的Top5,形成点赞排行榜。
需求:按照点赞先后排序,返回Top5
image.png
对比后选择用SortedSet替代Set实现点赞排行榜功能,需要用到
修改原先点赞逻辑:

  1. @Override
  2. public void likeBlog(Long id) {
  3. //1.获取登录用户
  4. Long userId = UserHolder.getUser().getId();
  5. //2.判断当前用户是否点赞
  6. String key = "blog:liked:" + id;
  7. // Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
  8. Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
  9. if(score == null){
  10. //3.如果未点赞,可以点赞
  11. //3.1数据库点赞数+1
  12. boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update();
  13. //3.2保存用户到Redis的set集合
  14. if(isSuccess){
  15. // stringRedisTemplate.opsForSet().add(key, userId.toString());
  16. stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
  17. }
  18. }else {
  19. //4.如果已点赞,取消点赞
  20. //4.1数据库点赞-1
  21. boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();
  22. //4.2把用户从Redis的set集合移除
  23. if(isSuccess){
  24. // stringRedisTemplate.opsForSet().remove(key, userId.toString());
  25. stringRedisTemplate.opsForZSet().remove(key, userId.toString());
  26. }
  27. }
  28. }

查询点赞Top5

  1. @Override
  2. public Result queryBlogLikes(Long id) {
  3. //1.查询top5点赞用户
  4. String key = BLOG_LIKED_KEY + id;
  5. Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
  6. if(top5 == null || top5.isEmpty()){
  7. return Result.ok(Collections.emptyList());
  8. }
  9. //2.解析其中用户id
  10. List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
  11. //3.根据用户id查询用户
  12. List<UserDTO> userDTOS = userService.listByIds(ids).stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());
  13. //4.返回
  14. return Result.ok(userDTOS);
  15. }

此处发现之前isBlogLiked代码存在bug,未登录无需判断是否点赞,所以在isBlogLiked里添加如下代码

  1. private void isBlogLiked(Blog blog) {
  2. //1.获取登录用户
  3. UserDTO user = UserHolder.getUser();
  4. if (user == null) {
  5. //用户未登录,无需查询是否点赞
  6. return;
  7. }
  8. ...
  9. }

排序Bug:使用ORDER BY FIELD

  1. @Override
  2. public Result queryBlogLikes(Long id) {
  3. ...
  4. String idStr = StrUtil.join(",", ids);
  5. //3.根据用户id查询用户
  6. List<UserDTO> userDTOS = userService.query()
  7. .in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list()
  8. .stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());
  9. //4.返回
  10. return Result.ok(userDTOS);
  11. }