黑马点评day06-达人探店和关注业务

达人探店

1、发布探店笔记

BlogServiceImpl.java:

 public Result saveBlog(Blog blog) {
        // 1、获取登录用户        
        UserDTO user = UserHolder.getUser();        
        blog.setUserId(user.getId());        
        // 2、保存探店博文        
        boolean isSave = this.save(blog);        
        if (!isSave) {
            return Result.fail("保存失败");        
            }
//        3、查询粉丝信息        
    LambdaQueryWrapper<Follow> queryWrapper = new LambdaQueryWrapper<>();        
    queryWrapper.eq(Follow::getFollowUserId, user.getId());        
    List<Follow> follows = followService.list(queryWrapper);
//        4、推送博客id给粉丝        
    for (Follow follow : follows) {
//           4.1、获取粉丝id            
    Long userId = follow.getUserId();
    //       4.2、推送博客id给粉丝            
    String key = "feed:" + userId;            
    stringRedisTemplate.opsForZSet().add(key,blog.getId().toString(),System.currentTimeMillis());        
    }
        // 返回id        
        return Result.ok(blog.getId());    
}

2、查看探店笔记

getBlog():

public Result getBlog(Integer id) {
        Blog blog = this.getById(id);        
        if (blog == null) {
            return Result.fail("该博客不存在");        
            }
        User user = userService.getById(blog.getUserId());        
        blog.setName(user.getNickName());        
        blog.setIcon(user.getIcon());        
        UserDTO userlo = UserHolder.getUser();        
        if (userlo != null) {
            //        获取登录用户Id            
            Long userloginId = userlo.getId();            
            //        判断用户是否点赞过使用redis的set集合            
            String key = BLOG_LIKED_KEY + id;//            
            Boolean ismember = stringRedisTemplate.opsForSet().isMember(key, userloginId.toString());            
            Double ismember = stringRedisTemplate.opsForZSet().score(key, userloginId.toString());            
            if (ismember == null) {
//            if (BooleanUtil.isFalse(ismember)) {                
        blog.setIsLike(Boolean.valueOf(false));            
        } else {
                blog.setIsLike(Boolean.valueOf(true));            
                }
      }
        return Result.ok(blog);    
 }

3、点赞功能

需求:

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

实现步骤:

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

采用Redis的Set集合

在Blog实体类新增字段:

@TableField(exist = false)
private Boolean isLike;

likeBlog():使用Redis的set集合进行当前用户是否点赞判断判断

public Result likeBlog(Long id) {
        UserDTO userDTO = UserHolder.getUser();        
        if (userDTO == null) {
            return Result.fail("请先登录");        }
//        获取登录用户Id        
        Long userId = UserHolder.getUser().getId();
//        判断用户是否点赞过使用redis的set集合        
        String key = BLOG_LIKED_KEY + id;        
        Boolean ismember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
        if (BooleanUtil.isFalse(ismember)) {        
//      如果用户未点赞            
//      修改点赞数量            
        boolean isSuccess = this.update()
                    .setSql("liked = liked + 1").eq("id", id).update();            
//      保存点赞记录到redis            
          if (isSuccess) {
                stringRedisTemplate.opsForSet().add(key, userId.toString());                
            }
        } else {
//        如果用户已经点过赞            
//        数据库点赞数量减一            
        boolean isSuccess = this.update().setSql("liked = liked - 1").eq("id", id).update();            
//        把用户从redis中移除            
        if (isSuccess) {
                stringRedisTemplate.opsForSet().remove(key, userId.toString());            
                }
        }
      return Result.ok();    
}

4、点赞排行榜

在探店笔记的详情页面,应该把给该笔记点赞的人显示出来,比如最早点赞的TOP5,形成点赞排行榜:
之前的点赞是放到set集合,但是set集合是不能排序的,所以这个时候,咱们可以采用一个可以排序的set集合,就是咱们的sortedSet

所有点赞的人,需要是唯一的,所以我们应当使用set或者是sortedSet
其次我们需要排序,就可以直接锁定使用sortedSet

修改上述likeBlog()方法:添加zset时score等于当前时间戳

//        获取登录用户Id        
Long userId = UserHolder.getUser().getId();
//        判断用户是否点赞过使用redis的set集合              
Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());          
// 保存点赞记录到redis的sortedSet集合  zadd key value score                        
stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis()); 

添加点赞列表查询getBlogLike方法:使用Redis的sortedSet集合,实现基于点赞时间戳顺序的头像先后展示效果

public Result getBlogLike(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);}

关注业务

1、关注和取消关注

因为查看博客时会判断当前用户是否关注了作者,添加iisfollow()方法:

/**     
* 判断是否关注     
*     
* @param id     
* @return     
*/    
    @GetMapping("/or/not/{id}")
    public Result isfollow(@PathVariable Long id) {
//        获取登录用户id        
    Long userId = UserHolder.getUser().getId();
//        查询是否关注        
    LambdaQueryWrapper<Follow> wrapper = new LambdaQueryWrapper<>();        
    wrapper.eq(Follow::getUserId, userId);        
    wrapper.eq(Follow::getFollowUserId, id);        
    if (this.followService.getOne(wrapper) != null) {
            return Result.ok(true);        }
        return Result.ok(false);    }

关注或取消关注方法follow():

 /**     
 * 关注         
 * @param id     
 * @return     
 */    
    @PutMapping("/{id}/{type}")
    public Result follow(@PathVariable Long id, @PathVariable("type") Boolean isFollow) {
//        获取登录用户id        
    Long userId = UserHolder.getUser().getId();
//        添加到redis中,方便后续查询共同关注的人        
    String key = "follows:" + userId;
//        判断是关注还是取关        
    if (isFollow) {
//        关注,添加关注记录            
    Follow follow = new Follow();            
    follow.setUserId(userId);            
    follow.setFollowUserId(id);            
    boolean isSave = this.followService.save(follow);            
    if (isSave) {
//        添加成功,添加到redis中 sadd userId followerUserId                
    this.stringRedisTemplate.opsForSet().add(key, id.toString());            
}
        } else {
//        取关,删除 delete from tb_follow where user_id = ? and follow_user_id = ?            
    LambdaQueryWrapper<Follow> wrapper = new LambdaQueryWrapper<>();            
    wrapper.eq(Follow::getUserId, userId);            
    wrapper.eq(Follow::getFollowUserId, id);            
    boolean isremove = this.followService.remove(wrapper);            
    if (isremove) {
//       删除成功,删除redis中的数据                
    this.stringRedisTemplate.opsForSet().remove(key, id.toString());       }
        }
        return Result.ok();    }

2、共同关注

想要去看共同关注的好友,需要首先进入到这个页面,这个页面会发起两个请求

1、去查询用户的详情

2、去查询用户的笔记
// UserController 根据id查询用户
@GetMapping("/{id}")
public Result queryUserById(@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);
}

// 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);
}
需求:利用Redis中恰当的数据结构,实现共同关注功能。在博主个人页面展示出当前用户与博主的共同关注呢。

当然是使用我们之前学习过的set集合咯,在set集合中,有交集并集补集的api,我们可以把两人的关注的人分别放入到一个set集合中,然后再通过api去查看这两个set集合中的交集数据。

改进关注功能,在向数据库写入之后,同时需要存入Redis的set集合中,key为: follows:userId,表示当前用户关注的作者

 // 1.获取登录用户
    Long userId = UserHolder.getUser().getId();
    String key = "follows:" + userId;
// 把关注用户的id,放入redis的set集合 sadd userId followerUserId
boolean isSuccess = save(follow);
if (isSuccess){
stringRedisTemplate.opsForSet().add(key, followUserId.toString());
}
        

获取共同关注方法followCommons():

 /**     
 * 查询共同关注的人     
 * @param id     
 * @return     
 */    
 @GetMapping("/common/{id}")
    public Result followCommons(@PathVariable Long id) {
//        获取登录用户id        
    Long loginUserId = UserHolder.getUser().getId();
//        登录用户的redis key        
    String loginKey = "follows:" + loginUserId;
//        作者的redis key        
    String authorKey = "follows:" + id;
//        求交集        
    Set<String> intersect = stringRedisTemplate.opsForSet().intersect(loginKey, authorKey);
//        查询交集的用户信息        
    if (intersect==null||intersect.isEmpty()){
//           无交集            
    return Result.ok(Collections.emptyList());        }
//        有交集,解析交集的用户id,需要获取用户的姓名        
    List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
//        据集合中的每项用户id,查询用户信息        
    List<User> userList = this.userService.listByIds(ids);        
    return Result.ok(userList);        
/*List<UserDTO> users = userService.listByIds(ids)                
        .stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());        
        return Result.ok(users);*/    
}

3、好友关注

3.1、好友关注-Feed流实现方案(关注推送)

1.拉模式(读扩散)

优点:节省内存空间

缺点:每次读取都需要从用户那里重新拉取到本人收件箱,耗时较长,延时较高

2、推模式(写扩散)

优点:延时低

缺点:内存占用高

3、推拉结合模式

大V的活跃用户推送模式,普通用户采用拉模式

普通V采用推送模式

对比:

3.2、好友关注-推送到粉丝收件箱

修改saveBlog方法:

在保存博客之后,需要向Redis的set粉丝集合中存入BlogId

@Override    
public Result saveBlog(Blog blog) {
        // 1、获取登录用户        
        UserDTO user = UserHolder.getUser();        
        blog.setUserId(user.getId());        
        // 2、保存探店博文        
        boolean isSave = this.save(blog);        
        if (!isSave) {
            return Result.fail("保存失败");        
            }
//        3、查询粉丝信息        
    LambdaQueryWrapper<Follow> queryWrapper = new LambdaQueryWrapper<>();        
    queryWrapper.eq(Follow::getFollowUserId, user.getId());        
    List<Follow> follows = followService.list(queryWrapper);
//        4、推送博客id给粉丝        
    for (Follow follow : follows) {
//           4.1、获取粉丝id            
    Long userId = follow.getUserId();
//           4.2、推送博客id给粉丝            
    String key = "feed:" + userId;            
    stringRedisTemplate.opsForZSet().add(key,blog.getId().toString(),System.currentTimeMillis());        
}
        // 返回id        
        return Result.ok(blog.getId());    
        }

3.3、好友关注-实现滚动分页查询收邮箱

需求:在个人主页的“关注”卡片中,查询并展示推送的Blog信息:

具体操作如下:

1、每次查询完成后,我们要分析出查询出数据的最小时间戳,这个值会作为下一次查询的条件

2、我们需要找到与上一次查询相同的查询个数作为偏移量,下次查询时,跳过这些查询过的数据,拿到我们需要的数据

综上:我们的请求参数中就需要携带 lastId:上一次查询的最小时间戳 和偏移量这两个参数。

这两个参数第一次会由前端来指定,以后的查询就根据后台结果作为条件,再次传递到后台。
传统了分页在feed流是不适用的,因为我们的数据会随时发生变化
假设在t1 时刻,我们去读取第一页,此时page = 1 ,size = 5 ,那么我们拿到的就是10~6 这几条记录,假设现在t2时候又发布了一条记录,此时t3 时刻,我们来读取第二页,读取第二页传入的参数是page=2 ,size=5 ,那么此时读取到的第二页实际上是从6 开始,然后是6~2 ,那么我们就读取到了重复的数据,所以feed流的分页,不能采用原始方案来做。
Feed流的滚动分页
我们需要记录每次操作的最后一条,然后从这个位置开始去读取数据
举个例子:我们从t1时刻开始,拿第一页数据,拿到了10~6,然后记录下当前最后一次拿取的记录,就是6,t2时刻发布了新的记录,此时这个11放到最顶上,但是不会影响我们之前记录的6,此时t3时刻来拿第二页,第二页这个时候拿数据,还是从6后一点的5去拿,就拿到了5-1的记录。我们这个地方可以采用sortedSet来做,可以进行范围查询,并且还可以记录当前获取数据时间戳最小值,就可以实现滚动分页了

定义scrollResult包装类,存入BlogIds、最小时间戳和偏移量:

@Data
public class ScrollResult {
    private List<?> list;
    private Long minTime;
    private Integer offset;
}

添加queryBlogOfFollow方法,查找当前登录用户关注作者的博文,且博文按时间戳倒序排序:

/**     
* 查询当前登录用户关注的人的博文     
* @param max     
* @param offset     
* @return     
*/    
@GetMapping("/of/follow")
    public Result queryBlogOfFollow(
            @RequestParam("lastId") Long max, @RequestParam(value = "offset", defaultValue = "0") Integer offset) {
        // 1.获取当前用户        
        Long userId = UserHolder.getUser().getId();        
        // 2.查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count        
        String key = "feed:" + userId;        
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
                .reverseRangeByScoreWithScores(key, 0, max, offset, 2);        
                // 3.非空判断        
                if (typedTuples == null || typedTuples.isEmpty()) {
            return Result.ok();        }
        // 4.解析数据:blogId、minTime(时间戳)、offset        
        List<Long> ids = new ArrayList<>(typedTuples.size());        
        long minTime = 0; // 2        
        int os = 1; // 2        
        for (ZSetOperations.TypedTuple<String> tuple : typedTuples) { // 5 4 4 2 2            
        // 4.1.获取id            
        ids.add(Long.valueOf(tuple.getValue()));            
        // 4.2.获取分数(时间戳)            
        long time = tuple.getScore().longValue();            
        if (time == minTime) {
                os++;  } 
                else {
                minTime = time;                
                os = 1;   }
        }
        os = minTime == max ? os : os + offset;        
        // 5.根据id查询blog        
        String idStr = StrUtil.join(",", ids);        
        List<Blog> blogs = this.blogService.query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();        
        for (Blog blog : blogs) {
            
            // 5.1.查询blog有关的用户,附上用户nickname,图像url            
            User user = userService.getById(blog.getUserId());            
            blog.setName(user.getNickName());            
            blog.setIcon(user.getIcon());
            // 5.2.查询blog是否被当前登录用户点赞            
            Long id = blog.getId();            
            UserDTO userlo = UserHolder.getUser();            
            if (userlo != null) {
                //        获取登录用户Id                
                Long userloginId = userlo.getId();                
                //        判断用户是否点赞过使用redis的set集合                
                String key2 = BLOG_LIKED_KEY + id;                        
                Double ismember = stringRedisTemplate.opsForZSet().score(key2, userloginId.toString());                
                if (ismember == null) {
                    blog.setIsLike(Boolean.valueOf(false));  } 
                    else {
                    blog.setIsLike(Boolean.valueOf(true));  }
            }
     }
        // 6.封装并返回        
        ScrollResult r = new ScrollResult();        
        r.setList(blogs);        
        r.setOffset(os);        
        r.setMinTime(minTime);        
        return Result.ok(r);    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 黑马qt公开课是一套为期5天的课件,主要介绍了Qt框架的基础知识和应用。Qt框架是一个跨平台的开发工具,可以方便地进行应用程序的设计、开发和调试,被广泛应用于图形界面开发、嵌入式系统和移动应用等领域。 在5天的课程中,学习者将对Qt框架的整体架构有一个全面的认识,包括Qt及其常用库的概念、功能和用法,也学会了如何使用Qt Designer进行界面设计和基于信号与槽的事件编程。 此外,课程还将介绍Qt中常用的编程模式和技术,如MVC架构、文件操作、网络编程等,并通过实例让学习者深入理解和应用这些概念和技术。 5天的课件中还提供了大量的实践操作,让学习者通过编写实际案例,深入理解所学知识,并更好地掌握Qt框架的基础和应用,为以后的工作打下坚实的基础。 总之,如果你想快速入门Qt框架的基础知识和应用,那么黑马qt公开课—5天的课件,将是一个非常好的选择。 ### 回答2: 黑马qt公开课的课件共分为5天,内容涵盖了Qt的基础知识、UI设计、绘图系统、多线程编程和网络编程等方面。通过这5天的学习,学员可以全面掌握Qt的开发技能和应用场景,具备开发Qt应用程序的能力。 第一天课程主要介绍了Qt的基础知识,包括Qt窗口和控件、信号与槽机制、事件处理、布局和样式等内容。通过这些基础知识的学习,学员可以了解Qt的基本工作原理和操作方法。 第二天的课程主要讲解了Qt的UI设计,包括UI设计器的使用、自定义控件和样式等内容。学员可以从中学习到如何设计美观、直观的用户界面。 第三天的课程则主题为Qt的绘图系统,包括2D和3D绘图、动画效果和图形转换等内容。在这一天的课程中,学员可以学习到如何使用Qt进行图形绘制和界面效果的优化。 第四天的课程主要介绍了Qt的多线程编程,包括线程的创建和管理、互斥锁和信号量等内容。学员可以从中学习到如何在Qt中实现多线程应用程序。 第五天的课程则主题为Qt的网络编程,包括socket编程、HTTP协议和Web服务等内容。学员可以从中学习到如何使用Qt进行网络编程,实现客户端和服务器的互通。 总体来说,黑马qt公开课的5天课程涵盖了Qt的核心知识点,让学员能够全面掌握Qt的开发技能和应用场景。通过这些课程的学习,学员可以成为一名合格的Qt开发工程师。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值