仿黑马点评项目(四、达人探店 Set)

该文章描述了一个探店笔记系统的实现,包括图片上传、笔记发布、查看笔记、点赞功能和点赞排行榜的设计。图片上传存储在硬盘并返回路径,笔记发布与查看涉及到用户信息和图片地址。点赞功能利用Redis的Set和SortedSet优化性能,确保用户只能点赞一次,点赞排行榜展示最早的五个点赞用户。
摘要由CSDN通过智能技术生成

1.发布探店笔记

  • 上传图片(上传图片和发布笔记分开两步,先上传图片)
    修改图片上传的路径,这里先存放至硬盘上,然后返回图片的路径,前端服务器作保存。
@Slf4j
@RestController
@RequestMapping("upload")
public class UploadController {

    @PostMapping("blog")//当请求的参数名称(页面表单上的name属性)与Controller的业务方法参数名称不一致时,就需要通过@RequestParam注解显示的绑定
    public Result uploadImage(@RequestParam("file") MultipartFile image) {
        try {
            // 获取原始文件名称
            String originalFilename = image.getOriginalFilename();
            // 生成新文件名
            String fileName = createNewFileName(originalFilename);
            // 保存文件
            image.transferTo(new File(SystemConstants.IMAGE_UPLOAD_DIR, fileName));
            // 返回结果
            log.debug("文件上传成功,{}", fileName);
            return Result.ok(fileName);
        } catch (IOException e) {
            throw new RuntimeException("文件上传失败", e);
        }
    }
    ...
}
public class SystemConstants {
    public static final String IMAGE_UPLOAD_DIR = "D:\\aTest\\SpringBootTest\\new_start\\nginx-1.18.0\\html\\hmdp\\imgs";
    ......
}
  • 发布笔记(图片来自步骤1的上传图片后返回的图片地址,地址记录在前端页面猜测是JS的ES6对象属性当中)
@RestController
@RequestMapping("/blog")
public class BlogController {

    @Resource
    private IBlogService blogService;
    @Resource
    private IUserService userService;

    @PostMapping
    public Result saveBlog(@RequestBody Blog blog) {
        // 获取登录用户
        UserDTO user = UserHolder.getUser();
        blog.setUserId(user.getId());
        // 保存探店博文
        blogService.save(blog);
        // 返回id
        return Result.ok(blog.getId());
    }
    ...
}

2.查看探店笔记

需求:点击博客,请求 URL: http://localhost:8080/api/blog/id,请求方法GET,返回博客文章信息以及用户id、icon、姓名信息。可以在Blog类里面增加userId(是数据库字段)、icon和username(非数据库字段)

设计:当查询某个id的博客文章时,首先根据博客id查询博客,得到博客对象,再根据博客对象中里面的userId来查询user的icon和username,再对博客对象中非数据库表中的字段值username和icon进行赋值,返回博客对象。

Controller,

/**
     * 根据id查询Blog
     * */
    @GetMapping("/{id}")
    public Result queryBlogById(@PathVariable("id") Long id){
        return blogService.queryBlogById(id);
    }

Service,

@Override
/**
 * 根据id查询Blog
 * */
public Result queryBlogById(Long id) {
    // 1.查询 blog
    Blog blog = this.getById(id);
    if (blog == null){
        log.error("笔记不存在!"+id);
        return Result.fail("笔记不存在!");
    }
    // 2.查询笔记作者用户
    this.queryBlogUser(blog);

    return Result.ok(blog);
}

/**
 * 查询笔记的作者用户
 * */
public void queryBlogUser(Blog blog){
    Long userId = blog.getUserId();
    User user = userService.getById(userId);
    blog.setName(user.getNickName());
    blog.setIcon(user.getIcon());
}

3.点赞

  • 需求:

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

    • 给Blog类中添加一个isLike字段,标示是否被当前用户点赞
      • 分析:如果在MySQL数据库中新建一张表,记录每个博客文章被哪个用户点赞过,可以,但是数据库的读写速度性能较低,用户体验较差。因此可以利用redis这种轻量级数据库,读写速度快。因为一个用户只能点赞一次,因此可以使用redis中的Set结构,不可重复性。一个Set集合里面包含点赞博客文章的用户id。
    • 修改点赞功能。利用redis的set集合判断是否点赞过,为未点赞过则点赞数+1,点赞过则点赞数-1
    • 修改根据id查询Blog业务,判断当前登录用户是否点赞过,赋值给isLike字段
    • 修改分页查询Blog业务,判断当前登录用户是否点赞过,赋值给isLike字段
  • 思考:但是存在一个问题,如果直接使用上面的private void isBlogLiked(Blog blog)方法,如果用户没有登录,则会报空指针的异常,导致页面的博客文章显示不出来。
    解决方法:在private void isBlogLiked(Blog blog)方法当中,判断用户是否登录,未登录则什么都不做,而且在未登录显示的页面当中,热点文章的点赞需要先登录。

@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {

    @Resource
    private IUserService userService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    //根据分页查询热点文章
    @Override
    public Result queryHotBlog(Integer current) {
        // 根据用户查询
        Page<Blog> page = query()
                .orderByDesc("liked")
                .page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
        // 获取当前页数据
        List<Blog> records = page.getRecords();
        // 查询用户
        records.forEach(blog ->{
            queryBlogUser(blog);
            this.isBlogLiked(blog);
        });
        return Result.ok(records);
    }

    //根据id查询文章
    @Override
    public Result queryBlogById(Long id) {
        // 1.查询blog
        Blog blog = getById(id);
        if(blog == null){
            return Result.fail("博客文章不存在!");
        }
        // 2.查询blog有关的用户
        queryBlogUser(blog);
        // 3.查询blog是否被点赞,前端页面的判断来自与这里blog对象中的isLike字段值
        isBlogLiked(blog);
        return Result.ok(blog);
    }

    //判断文章是否被当前用户点赞,设置blog的isLike字段值
    private void isBlogLiked(Blog blog) {
        try{
            UserDTO userDTO = UserHolder.getUser();
            if(userDTO != null){
                // 1.获取登录用户
                Long userId = userDTO.getId();
                // 2.判断当前登录用户是否已经点赞
                String key = BLOG_LIKED_KEY + blog.getId();
                Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
                blog.setIsLike(BooleanUtil.isTrue(isMember));
            }
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    //点赞文章
    @Override
    public Result likeBlog(Long id) {
        // 1.获取登录用户
        Long userId = UserHolder.getUser().getId();
        // 2.判断当前登录用户是否已经点赞
        String key = BLOG_LIKED_KEY + id;
        Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
        //因为isMember是包装类,所以不能直接判断,使用Boolean包装类
        if(BooleanUtil.isFalse(isMember)){
            // 3.如果未点赞,可以点赞
            // 3.1数据库点赞数 + 1
            boolean isSuccess = update()
                    .setSql("liked = liked + 1")
                    .eq("id", id).update();
            // 3.2保存用户到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.把用户从Redis的set集合移除
            if(isSuccess){
                stringRedisTemplate.opsForSet().remove(key,userId.toString());
            }
        }
        return Result.ok();
    }

    //设置文章的用户相关信息
    private void queryBlogUser(Blog blog) {
        try{
            Long userId = blog.getUserId();
            User user = userService.getById(userId);
            blog.setName(user.getNickName());
            blog.setIcon(user.getIcon());
        }catch (Exception e){
            throw new RuntimeException(e);
        }

    }
}

4.点赞排行榜(SortedSet)

  • 需求:根据时间顺序,展示出最早点赞的五个用户的信息。请求URL:…/blog/likes/{id},请求方式GET,请求参数为blog的id,返回值List给这个笔记点赞的TopN用户集合

  • Redis数据类型对比
    在这里插入图片描述

  • 根据需求决定使用SortedSort,而增添元素的命令是ZADD,但SortedSet没有isMember这个命令,所以用ZSCORE这个命令输入key 和 member来获取member的score来判断是否存在该元素。另一条指令就是ZRANGE key start end,注意redis下标也是从0开始,返回排序从0-4的5个元素member。

  • 修改代码
    修改点赞代码,将点赞的用户id从原来存放的set集合改成sortedset集合,

@Override
   /**
    * 登录用户点赞笔记
    * */
   public Result likeBlog(Long id) {
       // 获取登录用户
       Long userId = UserHolder.getUser().getId();

       // 查看用户是否已点赞
       String key = RedisConstants.BLOG_LIKED_KEY + id;
       // 从 sortedset 集合判断分数是否存在判断用户是否点赞
       Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());

       // 未点赞
       if (score == null){
           // 数据库点赞数+1
           boolean isSuccess = this.update().setSql("liked = liked + 1").eq("id", id).update();
           // 保存登录用户 id 进 redis
           if (isSuccess){
               // zadd key value score
               stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
           }
       }else {
           // 已点赞
           // 数据库点赞数-1
           boolean isSuccess = this.update().setSql("liked = liked - 1").eq("id", id).update();
           // 在 redis 删除登录用户 id
           if (isSuccess){
               stringRedisTemplate.opsForZSet().remove(key, userId.toString());
           }
       }

       return Result.ok();
   }
  • 修改用户是否点赞的代码
/**
     * 查询笔记是否被当前登录用户点赞
     * */
    public void queryIsLike(Blog blog){
        // 1.获取登录用户
        UserDTO user = UserHolder.getUser();
        if (user == null) {
            // 用户未登录,无需查询是否点赞
            return;
        }
        // 查询 redis 的 sortedset 是否存在当前用户
        Long userId = user.getId();
        String key = RedisConstants.BLOG_LIKED_KEY + blog.getId();
        // 存在则已点赞
        // 不存在则未点赞
        Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
//        Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
//        blog.setIsLike(BooleanUtil.isTrue(isMember));
        blog.setIsLike(score != null);
    }
  • 增加查询点赞该id笔记的前五名用户信息

尽管redis中ZRANGE key 0 4是按score排序获得userId,但是对数据库进行读操作时,SELECT id,phone,password,nick_name,icon,create_time,update_time FROM tb_user WHERE id IN ( ? , ? )结果却是反过来的顺序。

解决办法:对field中的id进行排序
SELECT id,phone,password,nick_name,icon,create_time,update_time FROM tb_user WHERE id IN ( 1010 , 1 ) ORDER BY FIELD(id,1010,1);

@Override
/**
 * 查询笔记的点赞排行榜前5的用户
 * */
public Result likesBlog(Long id) {
    String key = RedisConstants.BLOG_LIKED_KEY + id;

    // 1.查询redis sortedset 的前5 zrange key 0 4
    Set<String> range = stringRedisTemplate.opsForZSet().range(key, 0, 4);
    if (range == null || range.isEmpty()){
        return Result.ok(Collections.emptyList());
    }

    // 2.解析排行榜前几的用户 id 数据
    List<Long> ids = range.stream().map(Long::valueOf).collect(Collectors.toList());

    // 3.根据用户id查询用户 WHERE id IN ( 5 , 1 ) ORDER BY FIELD(id, 5, 1)
    String idStr = StrUtil.join(",", ids);
    List<UserDTO> userDTOS = userService.query()
            .in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list()
            .stream()
            .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
            .collect(Collectors.toList());

    // 4.返回数据
    return Result.ok(userDTOS);
}

达人探店流程如下:

  • 热点笔记分页查询、根据id查询笔记 ->(数据库:根据blog表中的liked值降序排序,分页第1页,页面size为5) -> 从IPage(Page)中获取records,然后从records中取出blog,设置blog的用户相关信息 -> 传入blog,根据用户id查询redis,判断该笔记是否被当前用户点赞;
  • 查看blog,根据用户id查询redis判断该笔记是否被当前用户点赞,然后设置blog中的isLike属性,传到前端判断是否设置高亮。
  • 点赞,根据传入的blog的id,读取redis数据判断是否被当前用户点赞,如果没有,则点赞,将用户id存入redis;否则,删除redis中用户id
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值