点评项目-12-好友关注

好友关注主要有三个功能:

1.关注和取关

2.共同关注

3.关注推送

关注和取关

涉及到的表,中间表:tb_follow,是博主 User 和粉丝 User 的中间表

请求一,查询是否关注了该用户:

请求路径:follow/or/not/{id}

请求方式:get

携带信息:登录 token

请求二,关注或者取关用户:

请求路径:follow/{id}/true  (最后一个参数表示关注或取关)

请求方式:put

携带信息:登录 token

Pojo:

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

Controller:

@RestController
@RequestMapping("/follow")
public class FollowController {@Resourceprivate FollowService followService;//查询是否关注@GetMapping("/or/not/{id}")public Result isFollow(@PathVariable("id") Long id){return followService.isFollow(id);}//关注或者取关@PutMapping("/{id}/{idFollow}")public Result follow(@PathVariable("id") Long id,@PathVariable("idFollow") Boolean isFollow){return followService.follow(id,isFollow);}}

 Service:

public interface FollowService {Result isFollow(Long id);Result follow(Long id, Boolean isFollow);}
@Service
public class FollowServiceImpl implements FollowService {@Resourceprivate FollowMapper followMapper;@Overridepublic Result isFollow(Long id) {//判断当前登录用户是否关注该博主UserDTO user = UserHolder.getUser();if(user == null){return Result.fail("未登录,请先完成登录");}Integer count = followMapper.judgeFollow(id, user.getId());if(count != null && count > 0){//已关注return Result.ok(true);}else {//未关注return Result.ok(false);}}@Overridepublic Result follow(Long id, Boolean isFollow) {if(isFollow == null){return Result.fail("空指针异常");}UserDTO user = UserHolder.getUser();if(user == null){return Result.fail("未登录,请先完成登录");}if(isFollow){//关注用户Follow follow = new Follow();follow.setUserId(id);follow.setFollowUserId(user.getId());followMapper.insert(follow);return Result.ok(follow);}else {//取关用户followMapper.deleteFan(id,user.getId());return Result.ok("delete:"+user.getId()+" from "+id);}}
}

 Mapper:

@Mapper
public interface FollowMapper extends BaseMapper<Follow> {//查询 a 是否 拥有粉丝 b@Select("select * from tb_follow where user_id = #{a} and follow_user_id = #{b}")Integer judgeFollow(@Param("a") Long a,@Param("b") Long b);//取关@Delete("delete from tb_follow where user_id = #{a} and follow_user_id = #{b}")void deleteFan(@Param("a") Long a,@Param("b") Long b);
}

Mapper 测试:

    @Autowiredprivate FollowMapper followMapper;@Testvoid testIsFollow(){//查询用户1的粉丝有没有用户2Integer integer = followMapper.judgeFollow(1L, 2L);System.out.println(integer);}@Testvoid testRemove(){followMapper.deleteFan(1L,2L);}

 测试:

共同关注

需求:点击博主头像后发送查询博主用户信息,以及查看博主发布的笔记的请求

查询博主用户信息:

请求路径:user/{id}

请求方式:get

携带信息:登录 token

查看博主发布的笔记:

请求路径:blog/of/user/{id}

请求方式:get

携带信息:登录 token,博主的用户 id

    //查看博主的历史博文@GetMapping("/of/user/{id}")public Result getBlogs(@PathVariable("id") Long id){return blogService.getBlogs(id);}
    @Overridepublic Result getBlogs(Long id) {List<Blog> allBlogs = blogMapper.getAllBlogs(id);return Result.ok(allBlogs);}
    //查询 id 下的所有博文@Select("select * from tb_blog where user_id = #{id}")List<Blog> getAllBlogs(@Param("id") Long id);

 测试:

查询共同关注请求:

请求路径:follow/common/{id}

请求方式:get

携带信息,登录 token 

该功能的实现可以使用 redis 中的 set 集合中自带的 sinter set1 set2 方法来求两个集合的交集

具体实现,在关注用户的请求 service 中,将 "follow:"+userId 作为 key 将当前博主挂载到当前用户的关注用户的集合下,在取关时将其移除,再在共同关注请求中使用 opsForSet().intersect(key1,key2) 来得到两个 set 集合的交集,返回值时为 String 类型的集合

在关注和取关时,将博主加入用户的关注列表:

    @Overridepublic Result follow(Long id, Boolean isFollow) {if(isFollow == null){return Result.fail("空指针异常");}UserDTO user = UserHolder.getUser();if(user == null){return Result.fail("未登录,请先完成登录");}String followKey = "user:follows:"+user.getId();if(isFollow){//关注用户Follow follow = new Follow();follow.setUserId(id);follow.setFollowUserId(user.getId());followMapper.insert(follow);//TODO 将当前博主挂在该用户的关注用户下stringRedisTemplate.opsForSet().add(followKey,Long.toString(id));return Result.ok(follow);}else {//取关用户followMapper.deleteFan(id,user.getId());//TODO 将当前博主从该用户的关注列表中移除stringRedisTemplate.opsForSet().remove(followKey,Long.toString(id));return Result.ok("delete:"+user.getId()+" from "+id);}}

我们通过不断让管理员用户(id=1)关注和取关 id=2 的用户,查看 redis 来测试功能是否实现

  

共同关注请求,Controller:

    //查询共同关注的用户的集合@GetMapping("/common/{id}")public Result getSameFollow(@PathVariable("id") Long id){return followService.getSame(id);}

Service:

    @Overridepublic Result getSame(Long id) {UserDTO user = UserHolder.getUser();if(user == null){return Result.fail("未登录,请先完成登录");}String followKey1 = "user:follows:"+user.getId();String followKey2 = "user:follows:"+id;Set<String> intersect = stringRedisTemplate.opsForSet().intersect(followKey1, followKey2);//将所有 userId 下的用户封装成 UserDTO 后以集合的形式返回List<UserDTO> users = new ArrayList<>();if(intersect == null){//没有共同关注用户,直接返回空集合return Result.ok(users);}for(String stringUserId : intersect){Long userId = Long.parseLong(stringUserId);User user1 = userMapper.selectById(userId);UserDTO userDTO = BeanUtil.copyProperties(user1,UserDTO.class);users.add(userDTO);}return Result.ok(users);}

为了方便测试,我们再添加一个管理员,设置其用户 id 为 2,来与 1 号管理员测试共同关注的功能

    //缓存一个永久用的用户登录信息@Testvoid saveUserForever(){Map<String,String> userMap = new HashMap<>();userMap.put("id","2");userMap.put("nickName","管理员2");stringRedisTemplate.opsForHash().putAll("login:token:"+"textUser2",userMap);}

 测试:

先让管理员 2 关注 3号用户

这下两个管理员都关注了 3 号用户

我们查询两个管理员的共同关注用户

 在这里我一开始查询返回结果为 null ,通过 debug 找到是由于没有 3 这个 id 的用户导致的,在数据库添加 3号用户即可

 关注推送

在用户发送笔记时通知所有关注该用户的人,即 feed 流

Feed 流常见模式:

1.TimeLine :类比微信朋友圈

2.智能排序 :类比短视频推送

本项目使用关注列表,以 TimeLine 的方式完成推送

推送方式主要有两种。推模式,在发送博文时,推送给每一个粉丝;拉模式,在查询关注时,拉取所有博主放在收件箱的博文

若是大型项目,可以采用推拉结合模式:

若粉丝量少的博主,我们采用推模式推送消息

若粉丝量多的博主,我们把分为活跃粉丝和非活跃粉丝。对于活跃粉丝我们采用推模式,对于非活跃粉丝我们采用拉模式推送

这里我们采用推模式来完成关注推送,使用 redis 的 SortedSet 集合,使用时间戳作为 score 排序,利用时间戳来实现滚动分页查询

每一个用户都有一个收件箱,是 redis 中的一个唯一的 key ,用于装推送给改用户的博文的 id,在每一次发布博文时,把该博文推送给所有粉丝

推送博文 id 给所有粉丝

followMapper 新写一个查询用户所有粉丝的 sql

@Mapper
public interface FollowMapper extends BaseMapper<Follow> {//查询 a 是否 拥有粉丝 b@Select("select * from tb_follow where user_id = #{a} and follow_user_id = #{b}")Integer judgeFollow(@Param("a") Long a,@Param("b") Long b);//取关@Delete("delete from tb_follow where user_id = #{a} and follow_user_id = #{b}")void deleteFan(@Param("a") Long a,@Param("b") Long b);//拿到对应用户的所有粉丝@Select("select follow_user_id from tb_follow where user_id = #{id}")List<Long> getFans(@Param("id") Long id);
}
    @Overridepublic Result saveBlag(Blog blog) {if(blog == null){return Result.fail("博文状态异常");}int insert = blogMapper.insert(blog);if(insert == 0){return Result.fail("存入数据失败");}//TODO 将博文 id 推送给所有粉丝UserDTO user = UserHolder.getUser();if(user == null){return Result.fail("用户未登录");}//在tb_follow表中查询 user_id 为当前用户的所有粉丝List<Long> fans = followMapper.getFans(user.getId());//将博文 id 发送给所有粉丝的消息箱子(即redis的key)for (Long fan : fans) {String feedKey = "blog:feed:"+fan;stringRedisTemplate.opsForZSet().add(feedKey,Long.toString(user.getId()), System.currentTimeMillis());}return Result.ok(blog);}

 测试是否会成功推送:

我们先登录 3号用户

 然后拿着登录token去发送博文

 再去 redis 中查看是否成功发送给所有粉丝

可以看到最后两条,已经成功给 3 号用户的粉丝,1、2 发送了消息推送 

关注推送页面的分页查询请求

请求路径:/blog/of/follow

请求方式:get

请求参数:上一次查询的最晚时间戳,偏移量(最晚时间戳的博文数量),登录 token

返回值:返回当前查询的最小时间戳以及偏移量给前端

java 中 对应 redis 的 api:

查找在 min 和 max 之间的 count 个元素,偏移量为 offerset:

tuples = stringRedisTemplate.opsForZset().

reverseRangeByScoreWithScores(key,min,max,offerset,count);

tuple.getValue()  拿到博文的 String id

tuple.getScore().longValue()  拿到 Long 类型的 时间戳

完整代码:

controller:

    //关注推送@GetMapping("/of/follow")public Result getFeed(@RequestParam("max") Long max,@RequestParam(value="offset",defaultValue="0") Integer offset ){return blogService.getFeed(max,offset);}

响应类:

@Data
public class BlogsFeedResult {private List<Blog> blogs;private Long max;private Integer offset;
}

seiviceImpl:

    @Overridepublic Result getFeed(Long max,Integer offset) {UserDTO user = UserHolder.getUser();if(user == null){return Result.fail("用户登录信息失效,请登录后重试");}//拿到消息箱分页处理后的博文idString feedKey = "blog:feed:"+user.getId();Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(feedKey, 0, max, offset, 3);//每一个 tuples 都装了 一个 value 和一个 scoreif(typedTuples == null || typedTuples.size() == 0){return Result.fail("未关注任何用户");}long min = System.currentTimeMillis();int save = 1;List<Blog> blogs = new ArrayList<>();for (ZSetOperations.TypedTuple<String> tuple : typedTuples) {//将博文信息封装到集合中,并拿到当前查询的最小时间戳,计算最小时间戳的元素个数Blog blog = blogMapper.selectById(Long.parseLong(tuple.getValue()));blogs.add(blog);long time = tuple.getScore().longValue();if(time == min){save++;}else{min = time;save = 1;}}BlogsFeedResult blogsFeedResult = new BlogsFeedResult();blogsFeedResult.setBlogs(blogs);blogsFeedResult.setMax(min);blogsFeedResult.setOffset(save);return Result.ok(blogsFeedResult);}

测试:

首次

第一次测试时发现返回值中的当前查询时间戳的最小值与预期不符,通过 debug 得知是在封装答案时将 min 写成了 max

在发送 get 请求时要注意:@RequestParam 的注解接收的参数必须在 Param 中传,而不能在 Body 中传

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/58577.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android 在github网站下载项目:各种很慢怎么办?比如gradle下载慢;访问github慢;依赖下载慢

目录 访问github慢gradle下载慢依赖下载慢 前言 大家好&#xff0c;我是前期后期&#xff0c;在网上冲浪的一名程序员。 为什么要看这篇文章呢&#xff1f;问题是什么&#xff1f; 我们在Github上面看到一些好的项目的时候&#xff0c;想下载下来研究学习一下。但经常遇到各…

阿里面试:为什么MySQL不建议使用Delete删除数据?

MySQL有建议过不要使用他们家的DELETE吗&#xff1f;在MySQL 8.0的官方文档里没有找到不建议使用DELETE的文字。 DELETE VS NOT DELETE&#xff0c;这是由来已久的问题 时间回到2009的8月30号。大佬Ayende Rahien——也被称为Oren Eini&#xff0c;Hibernating Rhinos公司的C…

机器学习中的嵌入是什么?

一、说明 嵌入是真实世界对象的数字表示&#xff0c;机器学习&#xff08;ML&#xff09;和人工智能&#xff08;AI&#xff09;系统利用它来像人类一样理解复杂的知识领域。例如&#xff0c;计算算法了解 2 和 3 之间的差为 1&#xff0c;这表明与 2 和 100 相比&#xff0c;2…

Python | Leetcode Python题解之第517题超级洗衣机

题目&#xff1a; 题解&#xff1a; class Solution:def findMinMoves(self, machines: List[int]) -> int:tot sum(machines)n len(machines)if tot % n:return -1avg tot // nans, s 0, 0for num in machines:num - avgs numans max(ans, abs(s), num)return ans

若依框架部署到服务器后头像资源访问404

排错过程 第一开始以为是代理出问题了 官网给出的解决方案 第一种是用代理后端接口&#xff0c;第二种是重写路径直接访问静态文件 接口通过捕获profile开头的路径/profile/avatar…&#xff0c;转为/home…/avatar找到我们在该路径下的文件 但是我想了一下&#xff0c;我ngin…

《手写Spring渐进式源码实践》实践笔记(第十二章 aop融入bean生命周期)

提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 第十二章 将AOP融入Bean生命周期背景目标设计实现代码结构类图实现步骤 测试事先准备自定义拦截方法Spring.xml 配置AOP测试用例测试结果&#xff1a; 总结 第十二章…

Three.js 快速入门构建你的第一个 3D 应用

![ 开发领域&#xff1a;前端开发 | AI 应用 | Web3D | 元宇宙 技术栈&#xff1a;JavaScript、React、Three.js、WebGL、Go 经验经验&#xff1a;6年 前端开发经验&#xff0c;专注于图形渲染和AI技术 开源项目&#xff1a;github 晓智元宇宙、数字孪生引擎、前端面试题 大家好…

C#界面设计--9--fatal error C1083: 无法打开包括文件:“jruparse.h”: No such file or directory

1、VS2008-编译时报错“fatal error C1083: 无法打开包括文件:“jruparse.h”: No such file or directory” 2、问题出现的原因及解决方法 1、如果要引入的这些,h文件跟.cpp在同一个目录下&#xff0c;就不会出现这种问题&#xff0c;检査在工程的include目录下是不是真的存…

算法:排序

排序算法 1. 简单排序1.1 直接插入排序1.2 冒泡排序1.3 简单选择排序 2. 希尔排序3. 快速排序4. 堆排序5. 归并排序 将文件的内容按照某种规则进行排列。 排序算法的稳定判定&#xff1a;若在待排序的一个序列中&#xff0c; R i R_i Ri​和 R j R_j Rj​的关键码相同&#xf…

OpenCV视觉分析之目标跟踪(6)轻量级目标跟踪器类TrackerNano的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 Nano 跟踪器是一个超轻量级的基于深度神经网络&#xff08;DNN&#xff09;的通用目标跟踪器。 由于特殊的模型结构&#xff0c;Nano 跟踪器速度…

【新人系列】Python 入门(六):基础内容 - 上

✍ 个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4dd; 专栏地址&#xff1a;https://blog.csdn.net/newin2020/category_12801353.html &#x1f4e3; 专栏定位&#xff1a;为 0 基础刚入门 Python 的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们…

WPF+MVVM案例实战(七)- 系统初始化界面字体描边效果实现

文章目录 1、案例效果展示2、项目准备3、功能实现1、资源获取2、界面代码3、后台代码4 源代码获取1、案例效果展示 2、项目准备 打开项目 Wpf_Examples,新建系统初始化界面 WelcomeWindow.xmal,如下所示: 3、功能实现 1、资源获取 案例中使用的CSDN文字为路径文字,从字体…

MFC工控项目实例二十七添加产品参数

承接专栏《MFC工控项目实例二十六创建数据库》 在型号参数界面添加三个参数试验时间、最小值、最大值。变量为double m_edit_time; double m_edit_min; double m_edit_max; 1、在SEAL_PRESSURE.h中添加代码 class CProductPara { public:union{struct{...double m_edit_min;…

【02】ZooKeeper经典应用场景实战一

1、ZooKeeper Java客户端实战 ZooKeeper应用的开发主要通过Java客户端API去连接和操作ZooKeeper集群。可供选择的Java客户端API有&#xff1a; ZooKeeper官方的Java客户端API。第三方的Java客户端API&#xff0c;比如&#xff1a;Curator ZooKeeper官方的客户端API提供了基本的…

信息安全工程师(73)网络安全风险评估过程

一、确定评估目标 此阶段需要明确评估的范围、目标和要求。评估目标通常包括特定的网络系统、信息系统或网络基础设施&#xff0c;评估范围可能涉及整个组织或仅特定部门。明确评估要求有助于确保评估过程的针对性和有效性。 二、收集信息 在评估开始之前&#xff0c;需要对目标…

Vmos pro-虚拟机 解锁永久vip

[应用名称] 应用名称&#xff1a;Vmos pro [应用版本] 应用版本&#xff1a;2.99 [软件大小] 软件大小&#xff1a;32.2mb [应用简介] 应用简介&#xff1a;Vmos Pro这款安卓虚拟机平台&#xff0c;提供了多样化的ROM版本选择。用户可根据自身需求更换ROM&#xff0c;调…

华为OD机试 - 最多购买宝石数目 - 滑动窗口(Python/JS/C/C++ 2024 C卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

1:基本电路专题:R(电阻)的介绍

说实话这个其实我不想写的&#xff0c;因为这个是初中的知识&#xff0c;并没有很难&#xff0c;但是为了保持整齐性&#xff0c;我还是写了一下关于这个的知识点。是电子学中三大基本无源元件之一。&#xff08;R&#xff08;电阻&#xff09;,L&#xff08;电感&#xff09;,…

基于SpringBoot的“CSGO赛事管理系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“CSGO赛事管理系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统首页界面图 赛事信息界面图 赛事通知界面…

漏洞挖掘 | 通过域混淆绕过实现账户接管

由于这是一个私有项目&#xff0c;我将使用 example.com 来代替。 很长一段时间以来&#xff0c;我一直想在漏洞赏金项目中找到一个账户接管&#xff08;ATO&#xff09;漏洞。于是&#xff0c;我开始探索项目范围内的 account.example.com。 我做的第一件事就是注册一个新账…