Redis实战案例22-博客发布和点赞排行榜问题

基于List的点赞列表
基于SortedSet的点赞排行榜
@TableField(exist = false)字段

1. 博客发布

上传博客

@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);
    }
}

发布博客
blog对象来接收前端发来的信息,调用mp提供的接口向博客表里save一条数据,但是用户id前端没有传递过来,需要去ThreadLocal中去获取

controller层

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

查看博客模块
查看博客不仅需要返回的blog对象,还需要给blog的用户信息(名称、信息);
所以需要两个表的信息,但是避免麻烦可以直接在Blog实体类中,添加用户头像和用户名称两个属性,并加上mp提供的注解@TableField(exist = false) ,表示当前属性不属于表中字段;
这里这个操作非常巧妙,当exist = false 时,表示该字段在数据库表中不存在,仅存在于Java实体类中;

/**
 * 根据id获取blog信息
 * @param id
 * @return
 */
@Override
public Result queryBlogById(Long id) {
    //1. 查询blog
    Blog blog = getById(id);
    if(blog == null) {
        return Result.fail("博客不存在");
    }
    //2. 查询blog有关的用户
    queryBlogUser(blog);
    //3. 查询blog是否被点赞
    isBlogLiked(blog);
    return Result.ok(blog);
}
/**
 * 查询与该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());
}

通过userService中mp方法获取user对象,同时在blog继续设置icon和name两个属性,这样达到不用多设置一个实体类来完成这些查询操作;

在这里插入图片描述
在这里插入图片描述

2. Redis的SortedSet数据结构

在这里插入图片描述

要获取点赞前五的用户,此时的用户ID存储不能使用set集合,因为set集合是无序的;

在这里插入图片描述

一目了然要选择SortedSet,其中的score值我们可以采用时间戳,之后就可以按照时间戳进行点赞的排序;
但是SortedSet并没有SISMEMBER命令,所以选择使用ZSCORE命令;
该命令表示元素存在返回score,不存在返回nil;

在这里插入图片描述

在这里插入图片描述

以下代码均为做了排行榜处理的代码,使用的数据结构是SortedSet而不是Set

点赞功能实现及逻辑代码

/**
 * 点赞博客的功能,只有点赞时要做登录校验
 * 参数博客id
 * @param id
 * @return
 */
@Override
public Result likeBlog(Long id) {
    //1. 获取登录用户
    Long userId = UserHolder.getUser().getId();
    //2. 判断用户是否已经点赞
    String key = BLOG_LIKED_KEY + id;
    Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
    if(score == null) {
        //3. 如果未点赞,可以点赞
        //3.1 数据库点赞数 + 1
        boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update();
        //3.2 保存用户到Redis的SortedSet集合 ZADD key value score
        if(isSuccess) {
            stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
        }
    }else {
        //4. 如果已点赞,取消点赞
        //4.1 数据库点赞数 - 1
        boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();
        //4.2 把用户从Redis的SortedSet集合移除
        if(isSuccess) {
            stringRedisTemplate.opsForZSet().remove(key, userId.toString());
        }
    }
    return Result.ok();
}

分页查询下的点赞业务

/**
 * 主页blog的信息
 * @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查询博客详情页下的点赞业务

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

提取出来的工具方法

此处获取登录用户要做判空处理,否则不登录会出现空指针异常

/**
 * 查询blog是否被点赞,设置实体类blog中的isLike字段值
 * 注意此处要判断是否登录的情况,进行判空操作!!!
 * @param blog
 */
private void isBlogLiked(Blog blog) {
    //1. 获取登录用户
    UserDTO user = UserHolder.getUser();
    if(user == null)
        return;
    Long userId = user.getId();
    //2. 判断用户是否已经点赞
    String key = BLOG_LIKED_KEY + blog.getId();
    Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
    blog.setIsLike(score != null);
}
/**
 * 查询与该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());
}

查询点赞排行榜前五的用户

/**
 * 查询点赞Top5用户
 * @param id
 * @return
 */
@Override
public Result queryBlogLikes(Long id) {
    String key = BLOG_LIKED_KEY + id;
    // 1. 查询top5的点赞用户ZRANGE key 0 4
    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,1) ORDER BY FIELD (id,5,1)
    List<UserDTO> userDTOS = userService.listByIds(ids)
            .stream()
            .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
            .collect(Collectors.toList());
    // 4. 返回
    return Result.ok(userDTOS);
}

这行代码使用了Stream API来处理top5集合中的每个元素。其中,top5是一个集合,它包含了博客点赞用户的信息。
stream()方法用于将集合转换为流,map(Long::valueOf)用于将每个元素转换为Long类型,collect(Collectors.toList())用于将转换后的元素收集到一个新的List中,最终得到用户id列表ids。

根据用户id查询用户也可以写成

// 3. 根据用户id查询用户 WHERE id IN (5,1) ORDER BY FIELD (id,5,1)
List<User> userList = userService.listByIds(ids);
List<UserDTO> userDTOS = userList.stream()
        .map(user -> {
            UserDTO userDTO = new UserDTO();
            BeanUtil.copyProperties(user, userDTO);
            return userDTO;
        })
        .collect(Collectors.toList());
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值