day09-达人探店

打印 上一主题 下一主题

主题 621|帖子 621|积分 1863

功能04-达人探店

5.功能04-达人探店

5.1发布&查看探店笔记

5.1.1发布探店笔记

探店笔记类似点评网站的评价,往往是图文结合。对应的表有两个:

  • tb_blog:探店笔记表,包含笔记中的标题、文字、图片等
  • tb_blog_comments:其他用户对探店笔记的评价
  1. /*表: tb_blog*/
  2. CREATE TABLE `tb_blog` (
  3.   `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  4.   `shop_id` bigint(20) NOT NULL COMMENT '商户id',
  5.   `user_id` bigint(20) unsigned NOT NULL COMMENT '用户id',
  6.   `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '标题',
  7.   `images` varchar(2048) NOT NULL COMMENT '探店的照片,最多9张,多张以","隔开',
  8.   `content` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '探店的文字描述',
  9.   `liked` int(8) unsigned DEFAULT '0' COMMENT '点赞数量',
  10.   `comments` int(8) unsigned DEFAULT NULL COMMENT '评论数量',
  11.   `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  12.   `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  13.   PRIMARY KEY (`id`) USING BTREE
  14. ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT
  15. /*表: tb_blog_comments*/
  16. CREATE TABLE `tb_blog_comments` (
  17.   `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  18.   `user_id` bigint(20) unsigned NOT NULL COMMENT '用户id',
  19.   `blog_id` bigint(20) unsigned NOT NULL COMMENT '探店id',
  20.   `parent_id` bigint(20) unsigned NOT NULL COMMENT '关联的1级评论id,如果是一级评论,则值为0',
  21.   `answer_id` bigint(20) unsigned NOT NULL COMMENT '回复的评论id',
  22.   `content` varchar(255) NOT NULL COMMENT '回复的内容',
  23.   `liked` int(8) unsigned DEFAULT NULL COMMENT '点赞数',
  24.   `status` tinyint(1) unsigned DEFAULT NULL COMMENT '状态,0:正常,1:被举报,2:禁止查看',
  25.   `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  26.   `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  27.   PRIMARY KEY (`id`) USING BTREE
  28. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT
复制代码
点击首页最下方菜单栏中的“+”按钮,即可发布探店图文:
需要注意的是:
发布照片和发布笔记这两个功能是分离的。因为上传照片的功能不仅仅是发布笔记时需要用到,其他业务也有需求,因此上传照片是一个独立功能。
我们在发布笔记时,点击上传照片,会先向服务端发出一个请求,实现图片上传。上传成功以后,服务端会返回图片的地址(即上传之后可访问的该图片地址),这个地址将来会作为表单的参数,在发布笔记的时候一起提交到后台(也就是说,在提交笔记的时候,我们提交的就不是照片本身了,而是上传成功后的图片地址)。
(1)上传图片功能
上传图片的功能已经提前实现了,详见UploadController.java及其接口
上传的图片其实是放在放在前端服务器中的,这里为了模拟,放在了D盘的前端项目(nginx-1.18.0)的目录下:
同时,需要将代码中保存的目录修改为对应的目录:
(2)发布笔记功能
发布笔记的功能也提前实现了,详见BlogController.java及其接口
(3)测试
点击+号进入如下页面,点击上传照片,一次可以上传多张图片:
每次上传图片成功,后端都会返回该图片可访问的图片地址:
点击发布后,可以在个人主页中看到发布的文章:
5.1.2查看探店笔记

实现查看笔记的接口。需求:点击首页的笔记,可以进入详情页面,实现该页面的查询接口。

笔记的详情页面需要显示:

  • 笔记信息
  • 发布的用户信息(用户id、用户昵称、用户头像)
代码实现
(1)Blog.java,笔记实体类
  1. package com.hmdp.entity;
  2. import com.baomidou.mybatisplus.annotation.IdType;
  3. import com.baomidou.mybatisplus.annotation.TableField;
  4. import com.baomidou.mybatisplus.annotation.TableId;
  5. import com.baomidou.mybatisplus.annotation.TableName;
  6. import lombok.Data;
  7. import lombok.EqualsAndHashCode;
  8. import lombok.experimental.Accessors;
  9. import java.io.Serializable;
  10. import java.time.LocalDateTime;
  11. /**
  12. * 笔记实体
  13. *
  14. * @author 李
  15. * @version 1.0
  16. */
  17. @Data
  18. @EqualsAndHashCode(callSuper = false)
  19. @Accessors(chain = true)
  20. @TableName("tb_blog")
  21. public class Blog implements Serializable {
  22.     private static final long serialVersionUID = 1L;
  23.    
  24.     //主键
  25.     @TableId(value = "id", type = IdType.AUTO)
  26.     private Long id;
  27.    
  28.     //商户id
  29.     private Long shopId;
  30.    
  31.     //用户id
  32.     private Long userId;
  33.    
  34.     //用户头像
  35.     @TableField(exist = false)
  36.     private String icon;
  37.    
  38.     //用户昵称
  39.     @TableField(exist = false)
  40.     private String name;
  41.    
  42.     //是否点赞过
  43.     @TableField(exist = false)
  44.     private Boolean isLike;
  45.    
  46.     //标题
  47.     private String title;
  48.    
  49.     //探店的照片,最多9张,使用","隔开
  50.     private String images;
  51.    
  52.     //探店的文字描述
  53.     private String content;
  54.    
  55.     //点赞数量
  56.     private Integer liked;
  57.    
  58.     //评论数量
  59.     private Integer comments;
  60.    
  61.     //创建时间
  62.     private LocalDateTime createTime;
  63.    
  64.     //更新时间
  65.     private LocalDateTime updateTime;
  66. }
复制代码
(2)BlogMapper.java
  1. package com.hmdp.mapper;
  2. import com.hmdp.entity.Blog;
  3. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  4. /**
  5. *  Mapper 接口
  6. *
  7. * @author 李
  8. * @version 1.0
  9. */
  10. public interface BlogMapper extends BaseMapper<Blog> {
  11. }
复制代码
(3)IBlogService.java
  1. package com.hmdp.service;
  2. import com.hmdp.dto.Result;
  3. import com.hmdp.entity.Blog;
  4. import com.baomidou.mybatisplus.extension.service.IService;
  5. /**
  6. * 服务类
  7. *
  8. * @author 李
  9. * @version 1.0
  10. */
  11. public interface IBlogService extends IService<Blog> {
  12.    
  13.     Result queryHotBlog(Integer current);//分页查询blog
  14.    
  15.     Result queryBlogById(Long id);//根据id查询blog
  16. }
复制代码
(4)BlogServiceImpl.java
  1. package com.hmdp.service.impl;
  2. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  3. import com.hmdp.dto.Result;
  4. import com.hmdp.entity.Blog;
  5. import com.hmdp.entity.User;
  6. import com.hmdp.mapper.BlogMapper;
  7. import com.hmdp.service.IBlogService;
  8. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  9. import com.hmdp.service.IUserService;
  10. import com.hmdp.utils.SystemConstants;
  11. import org.springframework.stereotype.Service;
  12. import javax.annotation.Resource;
  13. import java.util.List;
  14. /**
  15. * 服务实现类
  16. *
  17. * @author 李
  18. * @version 1.0
  19. */
  20. @Service
  21. public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {
  22.     @Resource
  23.     private IUserService userService;
  24.    
  25.     @Override
  26.     public Result queryHotBlog(Integer current) {
  27.         // 根据用户查询
  28.         Page<Blog> page = query()
  29.                 .orderByDesc("liked")
  30.                 .page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
  31.         // 获取当前页数据
  32.         List<Blog> records = page.getRecords();
  33.         // 查询用户
  34.         records.forEach(this::queryBlogUser);
  35.         return Result.ok(records);
  36.     }
  37.     @Override
  38.     public Result queryBlogById(Long id) {
  39.         //1.查询blog
  40.         Blog blog = getById(id);
  41.         //2.查询blog有关的用户
  42.         if (blog == null) {
  43.             return Result.fail("笔记不存在!");
  44.         }
  45.         queryBlogUser(blog);
  46.         return Result.ok(blog);
  47.     }
  48.     public void queryBlogUser(Blog blog) {
  49.         Long userId = blog.getUserId();
  50.         User user = userService.getById(userId);
  51.         blog.setName(user.getNickName());
  52.         blog.setIcon(user.getIcon());
  53.     }
  54. }
复制代码
(5)BlogController.java
  1. package com.hmdp.controller;
  2. import com.hmdp.dto.Result;
  3. import com.hmdp.service.IBlogService;
  4. import org.springframework.web.bind.annotation.*;
  5. import javax.annotation.Resource;
  6. /**
  7. * 前端控制器
  8. *
  9. * @author 李
  10. * @version 1.0
  11. */
  12. @RestController
  13. @RequestMapping("/blog")
  14. public class BlogController {
  15.     @Resource
  16.     private IBlogService blogService;
  17.    
  18.     @GetMapping("/hot")
  19.     public Result queryHotBlog(
  20.         @RequestParam(value = "current", defaultValue = "1") Integer current) {
  21.         return blogService.queryHotBlog(current);
  22.     }
  23.     @GetMapping("/{id}")
  24.     public Result queryBlogById(@PathVariable("id") Long id){
  25.         return blogService.queryBlogById(id);
  26.     }
  27. }
复制代码
(6)测试:重启项目,点击笔记,可以查看笔记详情
5.2点赞

5.2.1需求分析

在首页的探店笔记排行榜和探店图文详情页面都有点赞的功能:
需求:

  • 同一个用户只能点赞一次,再次点击则取消点赞
  • 如果当前用户已经点赞,则点赞按钮高亮显示(前端已实现,判断字段Blog类的isLike属性)
实现步骤:

  • 给Blog类中添加一个isLike字段,标识是否被当前用户点赞
  • 修改点赞功能,利用Redis的set集合判断是否点赞过,未点赞过则点赞数+1,已点赞过则再次点赞时点赞数-1
    blog的id作为key,点赞的用户id作为value

  • 修改根据id查询Blog的业务,判断当前用户是否点赞过,赋值给isLike字段
    用于blog详情的点赞显示

  • 修改分页查询Blog业务,判断当前登录用户是否点赞过,赋值给isLike字段
    用于一页blog时的所有点赞显示

5.2.2代码实现

(1)给Blog类中添加一个isLike字段,标识是否被当前用户点赞
(2)修改IBlogService,添加方法声明
  1. Result likeBlog(Long id);
复制代码
(3)修改BlogServiceImpl

  • 实现方法likeBlog():修改点赞功能,利用Redis的set集合判断是否点赞过,未点赞过则点赞数+1,已点赞过则再次点赞时点赞数-1
  • 修改BlogServiceImpl的queryBlogById()方法和queryHotBlog(),在查询blog信息的同时,查询当前用户有没有点赞过该blog
注意判断当前用户有没有登录
  1. package com.hmdp.service.impl;
  2. import ...
  3. /**
  4. * 服务实现类
  5. *
  6. * @author 李
  7. * @version 1.0
  8. */
  9. @Service
  10. public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {
  11.     @Resource
  12.     private IUserService userService;
  13.     @Resource
  14.     private StringRedisTemplate stringRedisTemplate;
  15.     @Override
  16.     public Result queryHotBlog(Integer current) {
  17.         // 根据用户查询
  18.         Page<Blog> page = query()
  19.                 .orderByDesc("liked")
  20.                 .page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
  21.         // 获取当前页数据
  22.         List<Blog> records = page.getRecords();
  23.         // 查询用户
  24.         records.forEach(blog -> {
  25.             //查询发布blog的user
  26.             this.queryBlogUser(blog);
  27.             //查询当前用户有没有点赞过该blog
  28.             this.isBlogLiked(blog);
  29.         });
  30.         return Result.ok(records);
  31.     }
  32.     @Override
  33.     public Result queryBlogById(Long id) {
  34.         //1.查询blog
  35.         Blog blog = getById(id);
  36.         //2.查询blog有关的用户
  37.         if (blog == null) {
  38.             return Result.fail("笔记不存在!");
  39.         }
  40.         queryBlogUser(blog);
  41.         //3.查询blog是否被点赞了
  42.         isBlogLiked(blog);
  43.         return Result.ok(blog);
  44.     }
  45.     private void isBlogLiked(Blog blog) {
  46.         //1.获取当前登录用户
  47.         if (UserHolder.getUser() == null) {
  48.         return;//如果当前用户未登录
  49.         }
  50.         Long userId = UserHolder.getUser().getId();
  51.         //2.判断当前登录用户是否已经点赞了
  52.         // (去redis的set集合中判断 SISMEMBER key member)
  53.         String key = "blog:liked:" + blog.getId();
  54.         Boolean isMember =
  55.             stringRedisTemplate.opsForSet().isMember(key, userId.toString());
  56.         blog.setIsLike(BooleanUtil.isTrue(isMember));
  57.     }
  58.     @Override
  59.     public Result likeBlog(Long id) {
  60.         //1.获取当前登录用户
  61.         Long userId = UserHolder.getUser().getId();
  62.         if (userId == null) {
  63.             return Result.fail("用户未登录");
  64.         }
  65.         //2.判断当前登录用户是否已经点赞了
  66.         // (去redis的set集合中判断 SISMEMBER key member)
  67.         String key = "blog:liked:" + id;
  68.         Boolean isMember =
  69.             stringRedisTemplate.opsForSet().isMember(key, userId.toString());
  70.         //3.如果未点赞
  71.         if (BooleanUtil.isFalse(isMember)) {
  72.             //3.1数据库点赞数+1
  73.             boolean isSuccess = update().setSql("liked=liked+1").eq("id", id).update();
  74.             //3.2保存用户到redis的set集合
  75.             if (isSuccess) {
  76.                 stringRedisTemplate.opsForSet().add(key, userId.toString());
  77.             }
  78.         } else {//4.如果已经点赞,则取消点赞
  79.             //4.1数据库点赞数-1
  80.             boolean isSuccess = update().setSql("liked=liked-1").eq("id", id).update();
  81.             //4.2将用户从redis的set集合中移除
  82.             if (isSuccess) {
  83.                 stringRedisTemplate.opsForSet().remove(key, userId.toString());
  84.             }
  85.         }
  86.         return Result.ok();
  87.     }
  88.     public void queryBlogUser(Blog blog) {
  89.         Long userId = blog.getUserId();
  90.         User user = userService.getById(userId);
  91.         blog.setName(user.getNickName());
  92.         blog.setIcon(user.getIcon());
  93.     }
  94. }
复制代码
(4)修改BlogController,添加方法likeBlog()
  1. @PutMapping("/like/{id}")
  2. public Result likeBlog(@PathVariable("id") Long id) {
  3.     return blogService.likeBlog(id);
  4. }
复制代码
(5)测试:启动项目,已登录用户第一次点赞时,点赞数+1,图标高亮;第二次点赞时,点赞数-1,图标变回灰色。

redis中的数据:set结构,key为blogId,value为userId
如果用户未登录,点赞时则会自动跳转到登录页面:
5.3点赞排行榜

5.3.1需求分析

在探店笔记的详情页面,应该把笔记点赞的用户信息显示出来,比如最早点赞的TOP5,形成点赞排行榜:
实现查询点赞排行榜的接口:
需求:按照点赞时间先后排序,返回Top5的用户。
之前我们使用的是redis中的Set结构,对于点赞功能来说,要求数据唯一且可方便查找。在此基础上,点赞排行榜功能还要求对数据进行排序,因此我们选用SortedSet结构实现,并对之前的点赞功能进行改造。
zset的整体结构:key value score(key存储blogId,value存储点赞的userId,score可以存储当前点赞的时间戳)
zset结构没有判断元素是否存在的命令,但可以查找指定元素的score,根据这个命令,判断指定的元素的score,如果score不存在,则该元素不存在。
  1.   ZSCORE key member
  2.   summary: Get the score associated with the given member in a sorted set  
  3.   since: 1.2.0
复制代码
例如:
  1. 127.0.0.1:6379> ZADD z1 1 m1 2 m2 3 m3
  2. (integer) 3
  3. 127.0.0.1:6379> ZSCORE z1 m4 #侧面判断m4不存在
  4. (nil)
  5. 127.0.0.1:6379> ZSCORE z1 m1
  6. "1"
复制代码
查询排行(比较score)则使用 zrange 命令:
  1. 127.0.0.1:6379> ZRANGE z1 0 4 #查询排行前5名
  2. 1) "m1"
  3. 2) "m2"
  4. 3) "m3"
复制代码
5.3.2代码实现

(1)IBlogService增加方法声明queryBlogLikes
  1. public interface IBlogService extends IService<Blog> {
  2.     ...
  3.         
  4.     Result queryBlogLikes(Long id);
  5. }
复制代码
(2)修改BlogServiceImpl:

  • 修改之前的点赞功能,将其用到的set结构改为zset结构(修改isBlogLiked和likeBlog方法)
  • 实现点赞排行功能--queryBlogLikes()
  1. //判断当前用户是否点赞过该blog
  2. private void isBlogLiked(Blog blog) {
  3.     //1.获取当前登录用户
  4.     if (UserHolder.getUser() == null) {
  5.         return;//如果当前用户未登录
  6.     }
  7.     Long userId = UserHolder.getUser().getId();
  8.     //2.判断当前登录用户是否已经点赞了
  9.     String key = BLOG_LIKED_KEY + blog.getId();
  10.     Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
  11.     blog.setIsLike(score != null);
  12. }
  13. //进行点赞操作
  14. @Override
  15. public Result likeBlog(Long id) {
  16.     //1.获取当前登录用户
  17.     Long userId = UserHolder.getUser().getId();
  18.     if (userId == null) {
  19.         return Result.fail("用户未登录");
  20.     }
  21.     //2.判断当前登录用户是否已经点赞了
  22.     String key = BLOG_LIKED_KEY + id;
  23.     Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
  24.     //3.如果未点赞(score为null,证明该用户不存在zset中,即未点赞)
  25.     if (score == null) {
  26.         //3.1数据库点赞数+1
  27.         boolean isSuccess = update().setSql("liked=liked+1").eq("id", id).update();
  28.         //3.2保存用户到redis的zset集合 zadd key value score
  29.         if (isSuccess) {
  30.             stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
  31.         }
  32.     } else {//4.如果已经点赞,则取消点赞
  33.         //4.1数据库点赞数-1
  34.         boolean isSuccess = update().setSql("liked=liked-1").eq("id", id).update();
  35.         //4.2将用户从redis的zset集合中移除
  36.         if (isSuccess) {
  37.             stringRedisTemplate.opsForZSet().remove(key, userId.toString());
  38.         }
  39.     }
  40.     return Result.ok();
  41. }
  42. //根据blogId返回点赞该blog的top5的用户信息
  43. @Override
  44. public Result queryBlogLikes(Long id) {
  45.     String key = BLOG_LIKED_KEY + id;
  46.     //1.查询top5的点赞用户 zrange key 0 4
  47.     Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
  48.     if (top5 == null || top5.isEmpty()) {
  49.         return Result.ok(Collections.emptyList());
  50.     }
  51.     //2.解析出其中的用户id
  52.     List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
  53.     //3.根据用户id查询用户
  54.     List<UserDTO> userDTOS = userService.listByIds(ids)
  55.             .stream()
  56.             .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
  57.             .collect(Collectors.toList());
  58.     //4.返回
  59.     return Result.ok(userDTOS);
  60. }
复制代码
(3)修改 BlogController,增加方法
  1. @GetMapping("/likes/{id}")
  2. public Result queryBlogLikes(@PathVariable("id") Long id) {
  3.      return blogService.queryBlogLikes(id);
  4. }
复制代码
(4)测试:使用不同的用户账号给同一篇探店blog点赞,成功显示点赞的用户信息:
但是显示的顺序出现了问题:如下所示,分别有三个用户。正确的点赞顺序如redis缓存所示:1033,1,2
但是从数据库中查找返回的用户顺序却是:1,2 ,1033,前端显示的用户头像id也是按照1,2,1033的顺序,这显然不符合我们要的顺序。
我们返回看之前的代码:
代码底层发出的sql语句如下:可以发现传入的参数顺序是正确的(1033,1,2),但是返回的数据顺序并不和我们的入参一致:
怎么保证使用IN子句的时候,返回的数据结果和参数的顺序一致呢?
解决方法:使用 order by 指定排序
(5)修改queryBlogLikes()方法,自定义查询语句:
(6)重新启动项目:可以看到之前的顺序已经变为真正的顺序了

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

慢吞云雾缓吐愁

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表