Redis Actual Combat - Daren Exploring Stores

Table of contents

1. Release Notes

1.1 Upload pictures 

1.2. Post blog function:

2. Check the store notes 

 3. Realization of like function

4. Realization of like leaderboard


1. Release Notes

Notes are similar to reviews on review sites, often combining graphics and text.

There are two corresponding tables 

tb_blog: Exploring the store note table, including the title, text, pictures, etc. in the note

tb_blog_comments: Other users' comments on Tandian Notes

Corresponding release process: 

1.1 Upload pictures 

The main logic is to save the file to a static resource, and then get the image by reading the file path

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


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

1.2. Post blog function:

Business logic: save the blog information passed from the front end.

@RestController
@RequestMapping("/blog")
public class BlogController {

    @Resource
    private IBlogService blogService;

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

2. Check the store notes 

The front end will send a blog ID, query the blog information through the blog ID, encapsulate the user information in the blog, and return it to the front end for display.

@Override
public Result queryBlogById(Long id) {
    // 1.查询blog
    Blog blog = getById(id);
    if (blog == null) {
        return Result.fail("笔记不存在!");
    }
    // 2.查询blog有关的用户
    queryBlogUser(blog);
  
    return Result.ok(blog);
}
private void queryBlogUser(Blog blog) {
        Long userId = blog.getUserId();
        User user = userService.getById(userId);
        blog.setName(user.getNickName());
        blog.setIcon(user.getIcon());
    }

 3. Realization of like function

 Problem Analysis: It is obviously unreasonable for a user to like infinitely

Our needs:

  • The same user can only like once, click again to cancel the like

  • If the current user has liked it, the like button will be highlighted (the front end has been implemented, and the isLike attribute of the field Blog class is judged)

Implementation steps:

  • Add an isLike field to the Blog class to indicate whether it is liked by the current user

  • Modify the like function, use the set collection of Redis to judge whether you have liked it, if you have not liked it, the number of likes will be +1, and if you have liked it, the number of likes will be -1

  • Modify the business of querying Blog according to id, determine whether the currently logged-in user has liked it, and assign it to the isLike field

  • Modify pagination to query Blog business, determine whether the currently logged-in user has liked it, and assign it to the isLike field

Here, the set collection in Redis is used to save the user information that has been liked

Why use set collection:

Because our users who have already liked it cannot be repeated. After the user has operated, no matter how he operates, he will like and cancel the like.

Specific steps

1. First add an isLike field to the Blog entity class

@TableField(exist = false)
private Boolean isLike;

2. Execute business logic:

(1) Get the logged-in user first 

(2) Determine whether the currently logged-in user has liked it

(3) If you don't like it, you can like it

         Database likes +1

        Save the user to the set collection of Redis

(4) If you have liked it, cancel the like

        Database likes -1

        Remove the user from the Redis set collection

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

4. Realization of like leaderboard

 

Here our requirement is that the first likes appear first, and the second likes appear last. The set collection here cannot meet our needs because it has no sorting function.

Let's compare the differences between these sets next.

All likes need to be unique, so we should use set or sortedSet

Secondly, we need to sort, so we can directly lock and use sortedSet

Here we use the Zadd method to store key, value and score score values ​​(score values ​​are stored with timestamps) 

Then you can judge whether the user has liked by whether there is a score value

So we have to modify our like logic code:

   @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的set集合  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的set集合移除
            if (isSuccess) {
                stringRedisTemplate.opsForZSet().remove(key, userId.toString());
            }
        }
        return Result.ok();
    }


    private void isBlogLiked(Blog blog) {
        // 1.获取登录用户
        UserDTO user = UserHolder.getUser();
        if (user == null) {
            // 用户未登录,无需查询是否点赞
            return;
        }
        Long userId = user.getId();
        // 2.判断当前登录用户是否已经点赞
        String key = "blog:liked:" + blog.getId();
        Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
        blog.setIsLike(score != null);
    }

Then it is to realize our like list function~

Business logic:

1. Query top5 like users ( zrange key 0 4 )

2. Parse the user id in it

3. Query users based on user id WHERE id IN ( 5 , 1 ) ORDER BY FIELD(id, 5, 1)

4. Return the user collection

@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());
    String idStr = StrUtil.join(",", ids);
    // 3.根据用户id查询用户 WHERE id IN ( 5 , 1 ) ORDER BY FIELD(id, 5, 1)
    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);
}

Note : The reason why the query user does not use the listquery() method here is that our where condition id uses In, which will disrupt our sorting order, so we use a self-built sql statement plus ORDER BY FIELD(id, 5 , 1) for reordering,

 

Guess you like

Origin blog.csdn.net/qq_59212867/article/details/128321757