黑马Redis实战项目——黑马点评笔记06 | 好友关注

在这里插入图片描述


1、关注和取关

在这里插入图片描述在这里插入图片描述
1、在FollowController中调用followService.follow和followService.isFollow方法

@RestController
@RequestMapping("/follow")
public class FollowController {
    @Resource
    private IFollowService followService;

    @PutMapping("/{id}/{isFollow}")
    public Result follow(@PathVariable("id") Long followUserId, @PathVariable("isFollow") Boolean isFollow){
        return followService.follow(followUserId,isFollow);
    }
    @GetMapping("/or/not/{id}")
    public Result isFollow(@PathVariable("id") Long followUserId){
        return followService.isFollow(followUserId);
    }
}

2、在IFollowService接口中声明 follow 和 isFollow 方法

public interface IFollowService extends IService<Follow> {
    Result follow(Long followUserId, Boolean isFollow);
    Result isFollow(Long followUserId);
}

3、在FollowServiceImpl类中实现方法

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

    /**
     * 关注和取关
     * @param followUserId
     * @param isFollow
     * @return
     */
    @Override
    public Result follow(Long followUserId, Boolean isFollow) {
        //获取当前用户Id
        Long userId = UserHolder.getUser().getId();
        //1、判断是否已经关注
        if(isFollow) {
            //2 未关注,新增数据
            Follow follow = new Follow();
            follow.setFollowUserId(followUserId);
            follow.setUserId(userId);
            save(follow);
        }else{
            //3 已关注,删除delete from tb_follow where userId = ? and follow_user_id = ?
            QueryWrapper<Follow> followQueryWrapper = new QueryWrapper<>();
            followQueryWrapper.eq("user_id",userId).eq("follow_user_id",followUserId);
            remove(followQueryWrapper);
        }
        return Result.ok();
    }

	/**
     * 查询是否已经关注
     * @param followUserId
     * @return
     */
    @Override
    public Result isFollow(Long followUserId) {
        //获取当前用户Id
        Long userId = UserHolder.getUser().getId();
        //1 查询是否关注
        Integer count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count();
        return Result.ok(count>0);
    }
}


2、共同关注

2.1 查看他人主页

在这里插入图片描述
1、在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);
    }

2、在BlogController新增根据用户id查询blog功能

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

2.2 查询共同关注

利用redis中set数据操作的求交集功能实现共同关注。

在这里插入图片描述

A 改造关注和取关功能

@Service
public class FollowServiceImpl extends ServiceImpl<FollowMapper, Follow> implements IFollowService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    /**
     * 关注和取关
     * @param followUserId
     * @param isFollow
     * @return
     */
    @Override
    public Result follow(Long followUserId, Boolean isFollow) {
        //获取当前用户Id
        Long userId = UserHolder.getUser().getId();
        String key = "follows:"+userId;
        //1、判断是否已经关注
        if(isFollow) {
            //2 未关注,新增数据
            Follow follow = new Follow();
            follow.setFollowUserId(followUserId);
            follow.setUserId(userId);
            boolean isSuccess = save(follow);
            if(isSuccess){
                //把关注用户的id放入Redis的set集合
                stringRedisTemplate.opsForSet().add(key,followUserId.toString());
            }
        }else{
            //3 已关注,删除delete from tb_follow where userId = ? and follow_user_id = ?
            QueryWrapper<Follow> followQueryWrapper = new QueryWrapper<>();
            followQueryWrapper.eq("user_id",userId).eq("follow_user_id",followUserId);
            boolean isSuccess = remove(followQueryWrapper);
            if(isSuccess){
                //把关注用户的id从Redis的set集合中移除
                stringRedisTemplate.opsForSet().remove(key,followUserId.toString());
            }
        }
        return Result.ok();
    }
}

B 求交集

1、FollowController类中调用followService.followCommons

    @GetMapping("/common/{id}")
    public Result followCommons(@PathVariable("id") Long id){
        return followService.followCommons(id);
    }

2、IFollowService接口中声明followCommons方法

    Result followCommons(Long id);

3、FollowServiceImpl中实现followCommons方法

	@Resource
    private IUserService userService;
    
	@Override
    public Result followCommons(Long id) {
        //1 获取当前用户Id
        Long userId = UserHolder.getUser().getId();
        String key = "follows:"+userId;
        //2 求交集
        String key2 = "follows:"+id;
        Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key, key2);
        if(intersect==null || intersect.isEmpty()){
            //无交集
            return Result.ok(Collections.emptyList());
        }
        //3 解析id集合
        List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
        //4 查询用户
        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 流分析

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

3.1.1、拉模式(读扩散)

在这里插入图片描述

3.1.2、推模式(写扩散)

在这里插入图片描述

3.1.3、推拉结合模式(读写混合)

在这里插入图片描述
在这里插入图片描述本案例用户不多且无v,使用推模式.

3.2 推送到粉丝收件箱

在这里插入图片描述
传统角标分页:在这里插入图片描述
滚动分页:在这里插入图片描述如果是数据有变化的情况,尽量不要使用List。因为List不支持滚动分页,只能按照角标查询,而sortedSet虽然根据socre排名查询和按角标查是一样的,但它可以按照score值的范围查询。每次查询记录最小的score(时间戳),下一次查询时查更小的时间戳则实现了滚动分页,不会重复查询。

1、修改新增博客的功能接口,使博客发布就能推送到粉丝

//修改BlogController中saveBlog
    @PostMapping
    public Result saveBlog(@RequestBody Blog blog) {
        return blogService.saveBlog(blog);
    }
    
//IBlogService接口中声明saveBlog
    Result saveBlog(Blog blog);
    
//类中实现saveBlog
    @Resource
    private IFollowService followService;

	@Override
    public Result saveBlog(Blog blog) {
        //1 获取登录用户
        UserDTO user = UserHolder.getUser();
        blog.setUserId(user.getId());
        //2 保存探店笔记
        boolean isSuccess = save(blog);
        if (!isSuccess){
            return Result.fail("发布失败,请检查重试");
        }
        //3 查询博文作者的所有粉丝select * from tb_follow where follow_user_id = ?
        List<Follow> follows = followService.query().eq("follow_user_id", user.getId()).list();
        //4 推送笔记id给所有粉丝
        for (Follow follow : follows) {
            //4.1 获取粉丝id
            Long userId = follow.getUserId();
            //4.2 推送笔记到每个粉丝的收件箱(sortedSet)
            String key = "feed:" + userId;
            stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
        }
        //5 返回id
        return Result.ok(blog.getId());
    }

3.3 滚动分页

展示推送的blog消息,使用的滚动分页,稍许复杂
在这里插入图片描述

基于Redis的sortedSet实现滚动分页所需参数:
在这里插入图片描述基于Redis的sortedSet实现滚动分页代码实现:
1、一个dto类封装滚动分页返回值

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

2、滚动查询,展示博主推送的笔记

//BlogController中调用queryBlogOfFollow方法
	@GetMapping("/of/follow")
    public Result queryBlogOfFollow(//如果offset为空说明是第一次传入,设置默认值0
            @RequestParam("lastId") Long max, @RequestParam(value = "offset",defaultValue = 0) Integer offset){
        return blogService.queryBlogOfFollow(max,offset);
    }

//IBlogService接口声明queryBlogOfFollow方法
    Result queryBlogOfFollow(Long max, Integer offset);

//BlogServiceImpl类实现queryBlogOfFollow方法
    /**
     * 滚动查询,展示博主推送的笔记,
     * 新发布的滚动查询查不到,但是往上滚,前端做了处理,就是刷新重新查询,开始位置在当前最新位置
     * @param max
     * @param offset
     * @return
     */
    @Override
    public Result queryBlogOfFollow(Long max, Integer offset) {
        //1 获取当前用户
        Long userId = UserHolder.getUser().getId();
        //2 查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count
        // limit是小于等于的意思,小于等于查询的最后时间戳
        String key = FEED_KEY + userId;
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
                .reverseRangeByScoreWithScores(key, 0, max, offset, 5);
        //3 非空判断
        if (typedTuples == null || typedTuples.isEmpty()){
            return Result.ok();
        }
        //4 解析数据: blogId,minTime(时间戳),offset
        ArrayList<Long> ids = new ArrayList<>(typedTuples.size());
        long minTime = 0;  //这个minTime是上次查询的最小时间戳,作为当次查询的最大时间戳来开始查
        int os = 1;//os->offset
        for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
            //4.1 获取博客id转换为Long型并存入ids数组中
            ids.add(Long.valueOf(typedTuple.getValue()));
            //4.2 获取分数(时间戳) 判读得到最后一次的时间戳,以及偏移量
            long time = typedTuple.getScore().longValue();
            if (time == minTime){
                os++;
            }else {
                minTime = time;
                os = 1;
            }
        }

        //5 根据id查询blog,先把前面保存id的ids数组转为字符串
        String idStr = StrUtil.join(",", ids); 
        //由于用mp提供的listByIds是用in方法查,不能保证顺序,需要orderby手动排序
        List<Blog> blogs = query().in("id", ids).last("order by field(id," + idStr + ")").list();
        for (Blog blog : blogs) {
            //5.1 查询blog有关用户信息
            queryBlogUser(blog);
            //5.2 查询blog是否已被点赞
            isBlogLiked(blog);
        }

        //6 封装并返回
        ScrollResult r = new ScrollResult();
        r.setList(blogs);
        r.setOffset(os);
        r.setMinTime(minTime);
        return Result.ok(r);
    }

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值