首页 > 数据库 >Redis实战(黑马点评--点赞关注)

Redis实战(黑马点评--点赞关注)

时间:2024-07-09 17:56:51浏览次数:13  
标签:return -- Redis public blog Result 点赞 id

一、发布、查看探店笔记

保存blog

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

上传图片的代码

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

查看探店笔记

随便点击一张图片,查看发送的请求

请求网址: http://localhost:8080/api/blog/6
请求方法: GET

BlogController下的方法,请求方式为GET,那我们直接来编写对应的方法

@GetMapping("/{id}")
public Result queryById(@PathVariable Integer id){
    return blogService.queryById(id);
}
@Override
public Result queryById(Integer id) {
    Blog blog = getById(id);
    if (blog == null) {
        return Result.fail("评价不存在或已被删除");
    }
    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());
}

将queryHotBlog也修改一下,原始代码将业务逻辑写到了Controller中,修改后的完整代码如下

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

    @Resource
    private IBlogService blogService;

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

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

    @GetMapping("/of/me")
    public Result queryMyBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) {
        // 获取登录用户
        UserDTO user = UserHolder.getUser();
        // 根据用户查询
        Page<Blog> page = blogService.query()
                .eq("user_id", user.getId()).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
        // 获取当前页数据
        List<Blog> records = page.getRecords();
        return Result.ok(records);
    }

    @GetMapping("/hot")
    public Result queryHotBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) {
        return blogService.queryHotBlog(current);
    }

    @GetMapping("/{id}")
    public Result queryById(@PathVariable Integer id){
        return blogService.queryById(id);
    }
}
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {
    @Resource
    private IUserService userService;

    @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(this::queryBlogUser);
        return Result.ok(records);
    }


    @Override
    public Result queryById(Integer id) {
        Blog blog = getById(id);
        if (blog == null) {
            return Result.fail("评价不存在或已被删除");
        }
        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());
    }
}

思考:实体之间由id属性关联(作为查询实体信息入口)。将关联实体id写入自己的属性,将那个实体的给出的属性,定义到类,但不写入表。

当具体要用到某个实体信息时,此时再根据id查询赋给此实体。并进行返回此时的实体。

【举例】:user实体、blog实体。

前端要展示blog实体,同时,发布者(user实体)的icon、nickName。

将userId、userIcon、userName作为属性写入blog结构。

每次查询时,进行涉及关联的实体信息查询,赋值。不查询,则没有必要进行,关联实体的查询与赋值。

注:此处关联指,因为前端展示需求,而关联起来、涉及不同实体信息捆绑返回的关系。

二、点赞功能

点击点赞按钮,查看发送的请求

请求网址: http://localhost:8080/api/blog/like/4
请求方法: PUT

@PutMapping("/like/{id}")
public Result likeBlog(@PathVariable("id") Long id) {
    // 修改点赞数量
    blogService.update().setSql("liked = liked + 1").eq("id", id).update();
    return Result.ok();
}
  • 问题分析:会导致一个用户无限点赞,不合理
  • 问题的原因,我们现在的逻辑,发起请求只是给数据库+1,所以才会出现这个问题
  • 需求:
  • 同一个用户只能对同一篇笔记点赞一次,再次点击则取消点赞
  • 如果当前用户已经点赞,则点赞按钮高亮显示(前端已实现,判断字段Blog类的isLike属性)
  • 实现步骤:
  • 修改点赞功能,利用Redis中的set集合来判断是否点赞过,未点赞则点赞数+1,已点赞则点赞数-1
  • 修改根据id查询的业务,判断当前登录用户是否点赞过,赋值给isLike字段
  • 修改分页查询Blog业务,判断当前登录用户是否点赞过,赋值给isLike字段

修改一下

@PutMapping("/like/{id}")
public Result likeBlog(@PathVariable("id") Long id) {
    return blogService.likeBlog(id);
}
@Override
public Result likeBlog(Long id) {
    //1. 获取当前用户信息
    Long userId = UserHolder.getUser().getId();
    //2. 如果当前用户未点赞,则点赞数 +1,同时将用户加入set集合
    String key = BLOG_LIKED_KEY + id;
    Boolean isLiked = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
    if (BooleanUtil.isFalse(isLiked)) {
        //点赞数 +1
        boolean success = update().setSql("liked = liked + 1").eq("id", id).update();
        //将用户加入set集合
        if (success) {
            stringRedisTemplate.opsForSet().add(key, userId.toString());
        }
    //3. 如果当前用户已点赞,则取消点赞,将用户从set集合中移除
    }else {
        //点赞数 -1
        boolean success = update().setSql("liked = liked - 1").eq("id", id).update();
        if (success){
            //从set集合移除
            stringRedisTemplate.opsForSet().remove(key, userId.toString());
        }
    }
    return Result.ok();
}
View Code

 

 思考:点赞还是取消点赞,对应到更新此博客表的liked字段的加减1操作。是通过过去的状态(数据库保存的数据状态值)进行流程方向的选择的。

如果只有两种状态,可以使用redis的set集合展现存在状态。

 

展示点赞、未点赞状态:页面上还不能立即显示点赞完毕的后果,我们还需要修改查询Blog业务,判断Blog是否被当前用户点赞过   

1点赞功能使用者:当前用户。

2如何展示点赞状态:当请求queryblog的时候,需返回blog的点赞状态。

liked状态根据:通过当前用户id查询redis的set集合,根据是否存在,返回此blog实体前,赋给对应liked值。

@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);
        //追加判断blog是否被当前用户点赞,逻辑封装到isBlogLiked方法中
        isBlogLiked(blog);
    });
    return Result.ok(records);
}

@Override
public Result queryById(Integer id) {
    Blog blog = getById(id);
    if (blog == null) {
        return Result.fail("评价不存在或已被删除");
    }
    queryBlogUser(blog);
    //追加判断blog是否被当前用户点赞,逻辑封装到isBlogLiked方法中
    isBlogLiked(blog);
    return Result.ok(blog);
}

private void isBlogLiked(Blog blog) {
    //1. 获取当前用户信息
    Long userId = UserHolder.getUser().getId();
    //2. 判断当前用户是否点赞
    String key = BLOG_LIKED_KEY + blog.getId();
    Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
    //3. 如果点赞了,则将isLike设置为true
    blog.setIsLike(BooleanUtil.isTrue(isMember));
}
View Code

 

 三、点赞排行榜

  • 当我们点击探店笔记详情页面时,应该按点赞顺序展示点赞用户,比如显示最早点赞的TOP5,形成点赞排行榜,就跟QQ空间发的说说一样,可以看到有哪些人点了赞
  • 之前的点赞是放到Set集合中,但是Set集合又不能排序,所以这个时候,我们就可以改用SortedSet(Zset)

由于ZSet没有isMember方法,所以这里只能通过查询score来判断集合中是否有该元素,如果有该元素,则返回值是对应的score,如果没有该元素,则返回值为null。

逻辑未改变,存储结构变了,对应结构下的能使用的方法变了。

 

 点赞功能前加一个判断逻辑:用户是否登录(从userHolder是否能拿到user),不能则return结束逻辑。

private void isBlogLiked(Blog blog) {
    //1. 获取当前用户信息
    UserDTO userDTO = UserHolder.getUser();
    //当用户未登录时,就不判断了,直接return结束逻辑
    if (userDTO == null) {
        return;
    }
    //2. 判断当前用户是否点赞
    String key = BLOG_LIKED_KEY + blog.getId();
    Double score = stringRedisTemplate.opsForZSet().score(key, userDTO.getId().toString());
    blog.setIsLike(score != null);
}

那我们继续来完善显示点赞列表功能,查看浏览器请求,这个请求目前应该是404的,因为我们还没有写,他需要一个list返回值,显示top5点赞的用户

请求网址: http://localhost:8080/api/blog/likes/4
请求方法: GET

在Controller层中编写对应的方法,点赞查询列表,具体逻辑写到BlogServiceImpl中

@GetMapping("/likes/{id}")
public Result queryBlogLikes(@PathVariable Integer id){
    return blogService.queryBlogLikes(id);
}

 

 

修改BlogServiceImpl
由于ZSet没有isMember方法,所以这里只能通过查询score来判断集合中是否有该元素,如果有该元素,则返回值是对应的score,如果没有该元素,则返回值为null

@Override
public Result queryBlogLikes(Integer id) {
    String key = BLOG_LIKED_KEY + id;
    //zrange key 0 4  查询zset中前5个元素
    Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
    //如果是空的(可能没人点赞),直接返回一个空集合
    if (top5 == null || top5.isEmpty()) {
        return Result.ok(Collections.emptyList());
    }
    List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
    //将ids使用`,`拼接,SQL语句查询出来的结果并不是按照我们期望的方式进行排
    //所以我们需要用order by field来指定排序方式,期望的排序方式就是按照查询出来的id进行排序
    String idsStr = StrUtil.join(",", ids);
    //select * from tb_user where id in (ids[0], ids[1] ...) order by field(id, ids[0], ids[1] ...)
    List<UserDTO> userDTOS = userService.query().in("id", ids)
            .last("order by field(id," + idsStr + ")")
            .list().stream()
            .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
            .collect(Collectors.toList());
    return Result.ok(userDTOS);
}

 四、关注功能

当我们进入到笔记详情页面时,会发送一个请求,判断当前登录用户是否关注了笔记博主

请求网址: http://localhost:8080/api/follow/or/not/2
请求方法: GET

当我们点击关注按钮时,会发送一个请求,实现关注/取关

请求网址: http://localhost:8080/api/follow/2/true
请求方法: PUT

关注是User之间的关系,是博主与粉丝的关系,数据库中有一张tb_follow表来标示

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tb_follow")
public class Follow implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 用户id
     */
    private Long userId;

    /**
     * 关联的用户id
     */
    private Long followUserId;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;
}
View Code

Controller层中编写对应的两个方法

@RestController
@RequestMapping("/follow")
public class FollowController {
    @Resource
    private IFollowService followService;
    //判断当前用户是否关注了该博主
    @GetMapping("/or/not/{id}")
    public Result isFollow(@PathVariable("id") Long followUserId) {
        return followService.isFollow(followUserId);
    }
    //实现取关/关注
    @PutMapping("/{id}/{isFollow}")
    public Result follow(@PathVariable("id") Long followUserId, @PathVariable("isFollow") Boolean isFellow) {
        return followService.follow(followUserId,isFellow);
    }
}

@Service
public class FollowServiceImpl extends ServiceImpl<FollowMapper, Follow> implements IFollowService {

    @Override
    public Result isFollow(Long followUserId) {
        //获取当前登录的userId
        Long userId = UserHolder.getUser().getId();
        LambdaQueryWrapper<Follow> queryWrapper = new LambdaQueryWrapper<>();
        //查询当前用户是否关注了该笔记的博主
        queryWrapper.eq(Follow::getUserId, userId).eq(Follow::getFollowUserId, followUserId);
        //只查询一个count就行了
        int count = this.count(queryWrapper);
        return Result.ok(count > 0);
    }

    @Override
    public Result follow(Long followUserId, Boolean isFellow) {
        //获取当前用户id
        Long userId = UserHolder.getUser().getId();
        //判断是否关注
        if (isFellow) {
            //关注,则将信息保存到数据库
            Follow follow = new Follow();
            follow.setUserId(userId);
            follow.setFollowUserId(followUserId);
            save(follow);
        } else {
            //取关,则将数据从数据库中移除
            LambdaQueryWrapper<Follow> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Follow::getUserId, userId).eq(Follow::getFollowUserId, followUserId);
            remove(queryWrapper);
        }
        return Result.ok();
    }
}
View Code 比较点赞取消点赞、关注取消关注功能,发现都是根据所传数据,1设置查找条件,2根据结果,确定数据记录变更(添/删)操作。 而展示功能,则是对数据的查,最后传回一个boolean值,或封装在实体里,传回实体。

五、共同关注

点击用户头像,进入到用户详情页,可以查看用户发布的笔记,和共同关注列表

查看发送的请求

检测NetWork选项卡,查看发送的请求

查询用户信息

请求网址: http://localhost:8080/api/user/2
请求方法: GET

查看共同关注

请求网址: http://localhost:8080/api/follow/common/undefined
请求方法: GET

编写查询用户信息方法=user转userDTO返回

@GetMapping("/{id}")
public Result queryById(@PathVariable("id") Long userId) {
    // 查询详情
    User user = userService.getById(userId);
    if (user == null) {
        // 没有详情,应该是第一次查看详情
        return Result.ok();
    }
    UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
    // 返回
    return Result.ok(userDTO);
}

重启服务器,现在可以看到用户信息,但是不能看到用户发布的笔记信息,查看NetWork检测的请求,我们还需要完成这个需求

请求网址: http://localhost:8080/api/blog/of/user?&id=2&current=1
请求方法: GET

编写查询用户笔记方法=分页查询

@GetMapping("/of/user")
    public Result queryBlogByUserId(@RequestParam(value = "current", defaultValue = "1") Integer current, @RequestParam("id") Long id) {
        LambdaQueryWrapper<Blog> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Blog::getUserId, id);
        Page<Blog> pageInfo = new Page<>(current, SystemConstants.MAX_PAGE_SIZE);
        blogService.page(pageInfo, queryWrapper);
        List<Blog> records = pageInfo.getRecords();
        return Result.ok(records);
    }


//下面这是老师的代码,上面为另一个方法
// BlogController  根据id查询博主的探店笔记
@GetMapping("/of/user")
public Result queryBlogByUserId(
        @RequestParam(value = "current", defaultValue = "1") Integer current,
        @RequestParam("id") Long id) {
    // 根据用户查询
    Page<Blog> page = blogService.query()
            .eq("user_id", id).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
    // 获取当前页数据
    List<Blog> records = page.getRecords();
    return Result.ok(records);
}
View Code

 

更改关注关系存储结构

@Resource
private StringRedisTemplate stringRedisTemplate;

@Override
public Result follow(Long followUserId, Boolean isFellow) {
    //获取当前用户id
    Long userId = UserHolder.getUser().getId();
    String key = "follows:" + userId;
    //判断是否关注
    if (isFellow) {
        //关注,则将信息保存到数据库
        Follow follow = new Follow();
        follow.setUserId(userId);
        follow.setFollowUserId(followUserId);
        //如果保存成功
        boolean success = save(follow);
        //则将数据也写入Redis
        if (success) {
            stringRedisTemplate.opsForSet().add(key, followUserId.toString());
        }
    } else {
        //取关,则将数据从数据库中移除
        LambdaQueryWrapper<Follow> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Follow::getUserId, userId).eq(Follow::getFollowUserId, followUserId);
        //如果取关成功
        boolean success = remove(queryWrapper);
        //则将数据也从Redis中移除
        if (success){
            stringRedisTemplate.opsForSet().remove(key,followUserId.toString());
        }
    }
    return Result.ok();
}
View Code

实现共同关注代码

@GetMapping("/common/{id}")
public Result followCommons(@PathVariable Long id){
    return followService.followCommons(id);
}
@Override
public Result followCommons(Long id) {
    //获取当前用户id
    Long userId = UserHolder.getUser().getId();
    String key1 = "follows:" + id;
    String key2 = "follows:" + userId;
    //对当前用户和博主用户的关注列表取交集
    Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key1, key2);
    if (intersect == null || intersect.isEmpty()) {
        //无交集就返回个空集合
        return Result.ok(Collections.emptyList());
    }
    //将结果转为list
    List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
    //之后根据ids去查询共同关注的用户,封装成UserDto再返回
    List<UserDTO> userDTOS = userService.listByIds(ids).stream().map(user ->
            BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());
    return Result.ok(userDTOS);
}

 

标签:return,--,Redis,public,blog,Result,点赞,id
From: https://www.cnblogs.com/fengok/p/17877558.html

相关文章

  • 新时代【机器学习】与【Pycharm】:【随机数据生成】与智能【股票市场分析】
    目录第一步:准备工作1.1安装必要的库小李的理解:1.2导入库小李的理解:第二步:生成和准备数据2.1生成随机股票数据小李的理解:2.2数据探索与可视化小李的理解:2.3数据处理小李的理解:2.4选择特征和标签小李的理解:第三步:拆分数据集小李的理解:第四步:训练决策树模......
  • 教你了解八大排序(含代码注释示例java)
    目录1.冒泡排序(BubbleSort)2.选择排序(SelectSort)3.插入排序(InsertionSort)4.希尔排序(ShellSort)5.归并排序(MergeSort)6.快速排序(QuickSort)7.堆排序(HeapSort)8.基数排序(RadixSort)1.冒泡排序(BubbleSort)这是最简单的排序算法之一。它......
  • ESM(ESModule)和CJS(CommonJS)的区别
    ESM和CJS的区别1.CJS2.EMS3ESM与CJS的区别3.1ESM输出的是==值的引用==,CJS输出的是==值得拷贝==3.2CJS的输出是运行时加载,而ESM是编译时输出接口3.3CJS是同步加载,ESM是异步加载1.CJSNode.js模块加载规范.js或.cjs文件,使用require加载,module.exports/exports......
  • 【升压恒流FP7208应用热门市场摄影灯-LED车灯-太阳能方案】如何实现升压恒流一切五调
    方案一:【RGBWY单IC五路调光方案】远翔FP7208满足锂电池1-4串供电、PD电压5-20V供电升压恒流一切五调光调色方案,PWM内部转模拟调光,无频闪顾虑低亮无抖动无频闪RGBWY五路调光芯片FP7208,适用PD+电池供电近年来随着技术的不断进步,越来越多的产品需要适应小型化和便携......
  • gson无法正常将时间戳转化成date
    gson无法正常将时间戳转化成dategson将时间戳转化成date时,报错Failedtoparsedate["1551950239757']:Invalidtimezoneindicator'3'解决办法添加一个long转date的解析器 @Testpublicvoidfun1(){GsonBuilderbuilder=newGsonBuilder();......
  • Redis实战(黑马点评--优惠券秒杀)
    一、redis实现全局唯一订单id1、问题:使用数据库自增id不合适当用户抢购商品时,生成的订单会保存到tb_voucher_order表中,而订单表如果使用数据库自增ID就会存在一些问题id规律性太明显受单表数据量的限制2、解决方法:全局id生成器符号位:1bit,永远为0时间戳:31bit,以秒为单位,可......
  • 探秘odpdx32.dll:核心功能解析与缺失修复指南
    odpdx32.dll是一个动态链接库(DLL)文件,通常与DirectX或OpenGL相关的软件或游戏有关。这个文件可能包含了用于处理图形渲染和多媒体播放的函数和资源,是系统中重要的组件之一。当你的计算机在运行某些应用程序时提示缺少odpdx32.dll文件,这意味着该应用程序依赖于这个文件,但当前系......
  • 各种软件启动方式
    各种软件启动方式1.MySQL#启动servicemysqldstart#关闭servicemysqldstop#重启servicemysqldrestart2.Redis#启动serviceredisdstart#关闭serviceredisdstop3.nginx#启动./nginx#关闭./nginx-squit#快速关闭./nginx-sstop#重新加载......
  • 深入解析:api-ms-win-net-isolation-l1-1-0.dll的角色与丢失修复指南
    api-ms-win-net-isolation-l1-1-0.dll是一个Windows操作系统中的动态链接库(DLL)文件,它与网络隔离功能相关,属于WindowsAppContainer和WindowsSandbox功能的一部分。这个DLL文件负责处理网络请求的隔离,确保应用程序在AppContainer或Sandbox环境中只能访问授权的网络资源,这对于增......
  • 国开大学2024《社会保障基础(统设课)》
    1.养老保险通过强制性的分摊机制,为老年人提供足够收入以维持退休后的基本生活水平,防范老年风险,这是养老保险的()。答案:B.保险功能2.职工达到法定退休年龄,缴费未满15年,则()。答案:D.一次性领取个人账户储存额3.目前我国养老保险基金筹集的主要模......