栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

黑马点评项目-好友关注

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

黑马点评项目-好友关注

一、关注和取关

在探店图文的详情页中,可以关注发布笔记的作者:

需求:基于该表数据结构,实现两个接口:
① 关注和取关接口
② 判断是否关注的接口

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

注意:这里需要把主键修改为自增长

FollowController

@RestController
@RequestMapping("/follow")
public class FollowController {

    @Autowired
    private IFollowService followService;

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


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

}

IFollowService

public interface IFollowService extends IService {

    Result follow(Long followUserId, Boolean isFollow);

    Result isFollow(Long followUserId);
}

FollowServiceImpl

@Service
public class FollowServiceImpl extends ServiceImpl implements IFollowService {

    @Override
    public Result follow(Long followUserId, Boolean isFollow) {
        // 获取当前登录用户
        Long userId = UserHolder.getUser().getId();
        // 判断是否关注
        if(isFollow){
            // 关注,新增
            Follow follow = new Follow();
            follow.setFollowUserId(followUserId);
            follow.setUserId(userId);
            save(follow);
        } else {
            // 取消关注,删除
            remove(new QueryWrapper().eq("follow_user_id", followUserId).eq("user_id", userId));
        }
        return Result.ok();
    }

    @Override
    public Result isFollow(Long followUserId) {
        // 获取当前登录用户
        Long userId = UserHolder.getUser().getId();
        Integer count = query().eq("follow_user_id", followUserId).eq("user_id", userId).count();
        return Result.ok(count > 0);
    }
}

二、共同关注

点击博主头像,可以进入博主首页:

博主个人首页依赖两个接口:
① UserController 根据 id 查询 User 信息

 @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 page = blogService.query().
            eq("user_id", id).
            page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
    // 获取当前页数据
    List records = page.getRecords();
    return Result.ok(records);
}

需求:利用 Redis 中的 set 类型的数据结构,实现共同关注功能(set 数据结构可以用来计算指定 key 之间元素的交集)。在博主个人页面展示出当前用户与博主的共同好友。

FollowController

@RequestMapping("/follow")
public class FollowController {

    @Autowired
    private IFollowService followService;

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


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

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

}

IFollowService

public interface IFollowService extends IService {

    Result follow(Long followUserId, Boolean isFollow);

    Result isFollow(Long followUserId);

    Result followCommons(Long id);
}

FollowServiceImpl:对 follow 方法进行改进,在向数据库写入关注信息时,同时保存至 Redis 中,在取消关注时,除了删除数据库中的数据,同时移除 Redis 中的相关数据。followCommons 为查询共同关注的方法,使用 set 数据结构的特性进行查询。

@Service
public class FollowServiceImpl extends ServiceImpl implements IFollowService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private IUserService userService;

    @Override
    public Result follow(Long followUserId, Boolean isFollow) {
        // 获取当前登录用户
        Long userId = UserHolder.getUser().getId();

        String key = "follows:" + userId;
        // 判断是否关注
        if(isFollow){
            // 关注,新增
            Follow follow = new Follow();
            follow.setFollowUserId(followUserId);
            follow.setUserId(userId);
            boolean isSuccess = save(follow);
            if (isSuccess) {
                stringRedisTemplate.opsForSet().add(key, followUserId.toString());
            }
        } else {
            // 取消关注,删除
            boolean isSuccess = remove(new QueryWrapper().eq("follow_user_id", followUserId).eq("user_id", userId));
            if (isSuccess) {
                stringRedisTemplate.opsForSet().remove(key, followUserId);
            }
        }
        return Result.ok();
    }

    @Override
    public Result isFollow(Long followUserId) {
        // 获取当前登录用户
        Long userId = UserHolder.getUser().getId();
        Integer count = query().eq("follow_user_id", followUserId).eq("user_id", userId).count();
        return Result.ok(count > 0);
    }

    @Override
    public Result followCommons(Long id) {
        // 获取当前登录用户
        Long userId = UserHolder.getUser().getId();
        String key = "follows:" + userId;
        String key2 = "follows:" + id;

        Set intersect = stringRedisTemplate.opsForSet().intersect(key, key2);
        if(intersect == null){
            return Result.ok(Collections.emptyList());
        }

        List ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
        List collect = userService.listByIds(ids).stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());

        return Result.ok(collect);
    }
}

三、关注推送 3.1 方案分析

关注推送也叫做 Feed 流,直译为投喂。为用户持续的提供”沉浸式“的体验,通过无限下拉刷新获取新的信息。

Feed 流产品有两种常见模式:
① Timeline:不做内容筛选,简单的按照内容发布时间排序,常用语好友或关注。例如朋友圈
优点:信息全面,不会有缺失,并且实现也相对简单
缺点:信息噪音较多,用户不一定感兴趣,内容获取效率低
② 智能排序:利用智能算法屏蔽掉违规的、用户不感兴趣的内容,推送用户感兴趣的信息来吸引用户
优点:投喂用户感兴趣信息,用户粘度很高,容易沉迷
缺点:如果算法不精准,可能起到反作用

本例中的个人页面,是基于关注的好友来做 Feed 流,因此采用 Timeline 的模式。该模式的实现方案有三种:
① 拉模式
② 推模式
③ 推拉结合

**拉模式:**也叫做读扩散
假设有三个人,分别是张三、李四、王五,这三个人分别会在自己的账号发布自己的笔记或者视频,在这里我们统一称之为消息,这三个人发布的所有的消息都会被发送到发件箱中,发送到发件箱中的消息除了消息本身之外,还需要带有时间戳。粉丝赵六会有一个收件箱,平时收件箱是空的,只有他在读取消息时,才会把赵六关注的所有人的发件箱中的消息拉取到他的收件箱中,拉取到收件箱后,消息会按照携带的时间戳进行排序,然后赵六就可以读取消息了。

优点:节省内存空间。收件箱中的消息在读取完后就会被清空,下次需要读取的时候会重新从所有关注人的发件箱中拉取。消息只保存了一份。
缺点:每次读取消息都要重新拉取发件箱中的消息,然后再做排序,比较耗时。

**推模式:**也叫做写扩散
假设现在有两个 up 主:张三、李四,有三个粉丝:粉丝1、粉丝2、粉丝3,粉丝1关注了张三,粉丝2和3都关注了张三和李四,如果张三此时想要发送消息,那么这条消息会直接推送到张三的所有粉丝的收件箱中,而李四发送的消息也会被推送到粉丝2和3的收件箱中,收件箱收到消息后会对所有的消息进行排序,粉丝在读取消息时,就已经是排序好的消息了。这样的一个好处就是延时低,不必每次读取时都需要重新拉取消息。但这种方式内存占用会比较高,up 主每次发消息都要同步所有的粉丝,如果粉丝数过多,超过几百万,就需要复制几百万份。

推拉结合模式:也叫做读写混合,兼具推和拉两种模式的优点。
普通粉丝人数众多,但是活跃度较低,读取消息的频率也就低,可采用拉模式;
而活跃粉丝人数少,但是活跃度高,读取消息的频率高,可采用推模式。
大 V 发送消息时,会直接将消息推送到活跃粉丝的发件箱中,而针对于普通粉丝,消息会先发送到发件箱中,当普通粉丝读取消息时,会直接从发件箱中拉取。

三种模式对比:

3.2 基于推模式实现关注推送功能

需求:
① 修改新增探店笔记的业务,在保存 Blog 到数据库的同时,推送到粉丝的收件箱
② 收件箱满足可以根据时间戳排序,必须用 Redis 的数据结构实现
③ 查询收件箱数据时,可以实现分页查询

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/872111.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号