【Redis实战篇】达人探店

1. 达人探店-发布探店笔记

发布探店笔记

探店笔记类似点评网站的评价,往往是图文结合。对应的表有两个:
tb_blog:探店笔记表,包含笔记中的标题、文字、图片等
tb_blog_comments:其他用户对探店笔记的评价

在这里插入图片描述
在这里插入图片描述
具体发布流程:
在这里插入图片描述
图片上传接口:


@Slf4j
@RestController
@RequestMapping("upload")
public class UploadController {

    @PostMapping("blog")
    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);
        }
    }

    @GetMapping("/blog/delete")
    public Result deleteBlogImg(@RequestParam("name") String filename) {
        File file = new File(SystemConstants.IMAGE_UPLOAD_DIR, filename);
        if (file.isDirectory()) {
            return Result.fail("错误的文件名称");
        }
        FileUtil.del(file);
        return Result.ok();
    }

    private String createNewFileName(String originalFilename) {
        // 获取后缀
        String suffix = StrUtil.subAfter(originalFilename, ".", true);
        // 生成目录
        String name = UUID.randomUUID().toString();
        int hash = name.hashCode();
        int d1 = hash & 0xF;
        int d2 = (hash >> 4) & 0xF;
        // 判断目录是否存在
        File dir = new File(SystemConstants.IMAGE_UPLOAD_DIR, StrUtil.format("/blogs/{}/{}", d1, d2));
        if (!dir.exists()) {
            dir.mkdirs();
        }
        // 生成文件名
        return StrUtil.format("/blogs/{}/{}/{}.{}", d1, d2, name, suffix);
    }
}

在这里插入图片描述
注意:读者在操作时,需要修改SystemConstants.IMAGE_UPLOAD_DIR 自己图片所在的地址,在实际开发中图片一般会放在nginx上或者是阿里OSS云存储服务上。

保存博客笔记:
BlogController

@RestController
@RequestMapping("/blog")
public class BlogController {
	@Resource
    private IBlogService blogService;
    /**
     * 保存博客
     * @param blog
     * @return
     */
    @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. 达人探店-查看探店笔记

实现查看发布探店笔记的接口
在这里插入图片描述
实现代码:
BlogServiceImpl

    /**
     * 根据博客id查询博客
     * @param id
     * @return
     */
    @Override
    public Result queryBlogById(Long id) {
        //1.查询blog
        Blog blog = getById(id);
        if(blog == null) return Result.fail("blog不存在!");
        //2.根据blog查询相关用户
        queryBlogUser(blog);
        return Result.ok(blog);
    }
    /**
     * 增加博客关联的用户名及用户头像信息
     * @param blog
     */
    private void queryBlogUser(Blog blog) {
        Long userId = blog.getUserId();
        User user = userService.getById(userId);
        blog.setName(user.getNickName());
        blog.setIcon(user.getIcon());
    }

3. 达人探店-点赞功能

初始代码
BlogController类

@GetMapping("/likes/{id}")
public Result likeBlog(@PathVariable("id") Long id) {
    //修改点赞数量
    blogService.update().setSql("liked = liked +1 ").eq("id",id).update();
    return Result.ok();
}

问题分析:这种方式会导致一个用户无限点赞,明显是不合理的

造成这个问题的原因是,我们现在的逻辑,发起请求只是给数据库+1,所以才会出现这个问题。
在这里插入图片描述
完善点赞功能

需求:

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

实现步骤:

  • 给Blog类中添加一个isLike字段,标示是否被当前用户点赞
  • 修改点赞功能,利用Redis的set集合判断是否点赞过,未点赞过则点赞数+1,已点赞过则点赞数-1
  • 修改根据id查询Blog的业务,判断当前登录用户是否点赞过,赋值给isLike字段
  • 修改分页查询Blog业务,判断当前登录用户是否点赞过,赋值给isLike字段

为什么采用set集合:

因为我们的数据是不能重复的,一个用户只能点赞一次,要么在set集合中存在,sadd添加当前用户的userId;要么取消点赞,则srem移除set中的用户userId
具体步骤:
1、在 Blog类 添加一个字段

@TableField(exist = false)
private Boolean isLike;

2、修改代码

   /**
     * 设置点赞功能
     * @param id
     * @return
     */
	 @Override
    public Result likeBlog(Long id) {
        //1.获取当前登录用户
        Long userId = UserHolder.getUser().getId();
        String key = RedisConstants.BLOG_LIKED_KEY + id;
        //2.判断当前登录用户是否已经点赞
        Boolean isMember = stringRedisTemplate.opsForSet()
                .isMember(key, userId.toString());
        if(BooleanUtil.isFalse(isMember)) {
            //3.如果未点赞, 可以点赞
            //3.1数据库字段liked + 1
            boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update();
            //3.2将当前用户保存到set集合当中
            if(isSuccess)
                stringRedisTemplate.opsForSet().add(key, userId.toString());
        }else {
            //4.如果已点赞, 则取消点赞
            //4.1数据库字段liked - 1
            boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();
            //4.2将当前用户从set集合当中移除
            if(isSuccess)
                stringRedisTemplate.opsForSet().remove(key, userId.toString());
        }
        return Result.ok();
    }
/**
     * 博客分页数据
     * @param current
     * @return
     */
    @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 -> {
            this.queryBlogUser(blog);
            this.isBlogLiked(blog);
        });
        return Result.ok(records);
    }

    /**
     * 根据博客id查询博客
     * @param id
     * @return
     */
    @Override
    public Result queryBlogById(Long id) {
        //1.查询blog
        Blog blog = getById(id);
        if(blog == null) return Result.fail("blog不存在!");
        //2.根据blog查询相关用户
        queryBlogUser(blog);
        //3.查询blog是否已被点赞
        isBlogLiked(blog);
        return Result.ok(blog);
    }

    /**
     * 设置博客是否被点赞(blog的IsLike属性)
     * @param blog
     */
    private void isBlogLiked(Blog blog) {
        //1.获取当前登录用户
        UserDTO user = UserHolder.getUser();
        //如果不判空, 对于未登录用户,再去走user.getId()会报空指针异常
        if(user == null) return;//如果用户没有登录, 则不用走该博客是否被点赞的逻辑
        Long userId = user.getId();
        String key = RedisConstants.BLOG_LIKED_KEY + blog.getId();
        //2.判断当前登录用户是否已经点赞
        Boolean isMember = stringRedisTemplate.opsForSet()
                .isMember(key, userId.toString());
        blog.setIsLike(BooleanUtil.isTrue(isMember));
    }


    /**
     * 增加博客关联的用户名及用户头像信息
     * @param blog
     */
    private void queryBlogUser(Blog blog) {
        Long userId = blog.getUserId();
        User user = userService.getById(userId);
        blog.setName(user.getNickName());
        blog.setIcon(user.getIcon());
    }

4. 达人探店-点赞排行榜

在探店笔记的详情页面,应该把给该笔记点赞的人显示出来,比如最早点赞的TOP5,形成点赞排行榜:

之前的点赞是放到set集合,但是set集合是不能排序的,所以这个时候,咱们可以采用一个可以排序的set集合,就是redis中的另一种数据类型sortedSet
在这里插入图片描述
我们接下来来对比一下这些集合的区别是什么
所有点赞的人,需要是唯一的,所以我们应当使用set或者是sortedSet
其次我们需要排序,就可以直接锁定使用sortedSet
在这里插入图片描述
回顾一下zsort的redis命令语法,zsort会按照score这个字段值进行排序,那么我们可以score中存入时间戳,根据时间戳来判断用户先后点赞的顺序。
在这里插入图片描述
另外需要注意的是,zset没有判断成员是否在集合中存在sismember这个方法,但是我们可以使用zscore判断集合中成员的score属性值是否为空,若返回nil则说明没有这个成员。
在这里插入图片描述

修改代码:将之前使用的set集合数据全部改为zset类型数据
BlogServiceImpl
点赞逻辑代码:

    /**
     * 设置点赞功能
     * @param id
     * @return
     */
    @Override
    public Result likeBlog(Long id) {
        //1.获取当前登录用户
        Long userId = UserHolder.getUser().getId();
        String key = RedisConstants.BLOG_LIKED_KEY + id;
        //2.判断当前登录用户是否已经点赞
        Double score = stringRedisTemplate.opsForZSet()
                .score(key, userId.toString());
        if(score == null) {
            //3.如果未点赞, 可以点赞
            //3.1数据库字段liked + 1
            boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update();
            //3.2将当前用户保存到zset集合当中 zadd key score member
            if(isSuccess)
                stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
        }else {
            //4.如果已点赞, 则取消点赞
            //4.1数据库字段liked - 1
            boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();
            //4.2将当前用户从zset集合当中移除
            if(isSuccess)
                stringRedisTemplate.opsForZSet().remove(key, userId.toString());
        }
        return Result.ok();
    }
    
    /**
     * 博客分页数据
     * @param current
     * @return
     */
    @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 -> {
            this.queryBlogUser(blog);
            this.isBlogLiked(blog);
        });
        return Result.ok(records);
    }

    /**
     * 根据博客id查询博客
     * @param id
     * @return
     */
    @Override
    public Result queryBlogById(Long id) {
        //1.查询blog
        Blog blog = getById(id);
        if(blog == null) return Result.fail("blog不存在!");
        //2.根据blog查询相关用户
        queryBlogUser(blog);
        //3.查询blog是否已被点赞
        isBlogLiked(blog);
        return Result.ok(blog);
    }

    /**
     * 设置博客是否被点赞(blog的IsLike属性)
     * @param blog
     */
    private void isBlogLiked(Blog blog) {
        //1.获取当前登录用户
        UserDTO user = UserHolder.getUser();
        //如果不判空, 对于未登录用户,再去走user.getId()会报空指针异常
        if(user == null) return;//如果用户没有登录, 则不用走该博客是否被点赞的逻辑
        Long userId = user.getId();
        String key = RedisConstants.BLOG_LIKED_KEY + blog.getId();
        //2.判断当前登录用户是否已经点赞
        Double score = stringRedisTemplate.opsForZSet()
                .score(key, userId.toString());
        blog.setIsLike(score != null);
    }

点赞排行榜查询列表
BlogController

 /**
     * 点赞排行榜
     * @param id
     * @return
     */
    @GetMapping("/likes/{id}")
    public Result queryBlogLikes(@PathVariable("id") Long id){
        return blogService.queryBlogLikes(id);
    }

BlogService

 /**
     * 点赞排行榜
     * @param id
     * @return
     */
    @Override
    public Result queryBlogLikes(Long id) {
        //1.查询top5的点赞用户 zrange key min max
        String key = RedisConstants.BLOG_LIKED_KEY + id;
        Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
        if(top5 == null || top5.isEmpty()) return Result.ok(Collections.emptyList());
        //2.解析出用户id
        List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
        //3.根据用户id查询用户 where id in(5, 4) order by field(id, 5, 4)
        String idStr = StrUtil.join(",", ids);
        List<UserDTO> userDTOS = userService
                //.query().in("id", ids).last("order by field(id," + idStr + ")") .list()
                .listByIds(ids)
                .stream()
                .map(user -> {
                    return BeanUtil.copyProperties(user, UserDTO.class);
                }).collect(Collectors.toList());
        return Result.ok(userDTOS);
    }

最后我们重启代码,发现点赞头像出现了顺序混乱问题,

在这里插入图片描述
最后我们发现是因为数据库中,系统不会按照in关键字中值的顺序来进行排序并返回。所以我们需要使用order by filed()实现自定义排序。
在这里插入图片描述
修改java中的代码:
在这里插入图片描述
再次刷新,顺序已经被调整过来了。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江枫渔火_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值