0


Redis实现朋友圈,微博等Feed流功能,实现Feed流微服务(代码实现)

上篇博客讲述了Redis实现朋友圈,微博等Feed流功能,实现Feed流微服务的业务场景、实现思路和环境搭建,本文继续讲解具体的代码实现内容。

文章目录

添加 Feed 信息

FeedsController

/**
     * 添加 Feed
     *
     * @param feeds
     * @param access_token
     * @return
     */@PostMappingpublicResultInfo<String>create(@RequestBodyFeeds feeds,String access_token){
        feedsService.create(feeds, access_token);returnResultInfoUtil.buildSuccess(request.getServletPath(),"添加成功");}

FeedsService

/**
     * 添加 Feed
     *
     * @param feeds
     * @param accessToken
     */@Transactional(rollbackFor =Exception.class)publicvoidcreate(Feeds feeds,String accessToken){// 校验 Feed 内容不能为空,不能太长AssertUtil.isNotEmpty(feeds.getContent(),"请输入内容");AssertUtil.isTrue(feeds.getContent().length()>255,"输入内容太多,请重新输入");// 获取登录用户信息SignInUserInfo userInfo =loadSignInUserInfo(accessToken);// Feed 关联用户信息
        feeds.setFkUserId(userInfo.getId());// 添加 Feedint count = feedsMapper.save(feeds);AssertUtil.isTrue(count ==0,"添加失败");// 推送到粉丝的列表中 -- 后续这里应该采用异步消息队列解决性能问题// 先获取粉丝 id 集合List<Integer> followers =findFollowers(userInfo.getId());// 推送 Feedlong now =System.currentTimeMillis();
        followers.forEach(follower ->{String key =RedisKeyConstant.following_feeds.getKey()+ follower;
            redisTemplate.opsForZSet().add(key, feeds.getId(), now);});}/**
     * 获取粉丝 id 集合
     *
     * @param userId
     * @return
     */privateList<Integer>findFollowers(Integer userId){String url = followServerName +"followers/"+ userId;ResultInfo resultInfo = restTemplate.getForObject(url,ResultInfo.class);if(resultInfo.getCode()!=ApiConstant.SUCCESS_CODE){thrownewParameterException(resultInfo.getCode(), resultInfo.getMessage());}List<Integer> followers =(List<Integer>) resultInfo.getData();return followers;}

FeedsMapper

/**
     * 添加 Feed
     * @param feeds 
     * @return
     */@Insert("insert into t_feeds (content, fk_user_id, praise_amount, "+" comment_amount, fk_restaurant_id, create_date, update_date, is_valid) "+" values (#{content}, #{fkUserId}, #{praiseAmount}, #{commentAmount}, #{fkRestaurantId}, "+" now(), now(), 1)")@Options(useGeneratedKeys =true, keyProperty ="id")intsave(Feeds feeds);

ms-follow 服务新增获取粉丝列表

FollowController

/**
     * 获取粉丝列表
     *
     * @param userId
     * @return
     */@GetMapping("followers/{userId}")publicResultInfofindFollowers(@PathVariableInteger userId){returnResultInfoUtil.buildSuccess(request.getServletPath(),
                followService.findFollowers(userId));}

FollowService

/**
     * 获取粉丝列表
     *
     * @param userId
     * @return
     */publicSet<Integer>findFollowers(Integer userId){AssertUtil.isNotNull(userId,"请选择要查看的用户");Set<Integer> followers = redisTemplate.opsForSet().members(RedisKeyConstant.followers.getKey()+ userId);return followers;}

ms-gateway 服务配置网关路由

spring:application:name: ms-gateway
  cloud:gateway:discovery:locator:enabled:true# 开启配置注册中心进行路由功能lower-case-service-id:true# 将服务名称转小写routes:# Feed服务路由             -id: ms-feeds
          uri: lb://ms-feeds
          predicates:- Path=/feeds/**filters:- StripPrefix=1

启动项目测试

  1. 先让id等于10、9、8的用户关注id等于7的用户。
  2. id等于7的用户登录后发布一条动态。

让 id=10 的用户关注 id=7 的用户:
在这里插入图片描述
在这里插入图片描述
让 id=9 的用户关注 id=7 的用户:
在这里插入图片描述
让 id=8 的用户关注 id=7 的用户:
image.png
id=7 的用户登录系统并发送一条动态:
http://localhost/feeds?access_token=48781f97-1c3a-4737-ae55-984c0944649e
image.png
查看数据库 feeds 信息:
在这里插入图片描述
查看 redis 中粉丝的 feeds 信息:
在这里插入图片描述
可以看到用户id为8、9、10的用户都收到了这条Feed。

删除 Feed 信息

FeedsController

/**
     * 删除 Feed
     *
     * @param id
     * @param access_token
     * @return
     */@DeleteMapping("{id}")publicResultInfodelete(@PathVariableInteger id,String access_token){
        feedsService.delete(id, access_token);returnResultInfoUtil.buildSuccess(request.getServletPath(),"删除成功");}

FeedsService

/**
     * 删除 Feed
     *
     * @param id
     * @param accessToken
     */@Transactional(rollbackFor =Exception.class)publicvoiddelete(Integer id,String accessToken){// 请选择要删除的 FeedAssertUtil.isTrue(id ==null|| id <1,"请选择要删除的Feed");// 获取登录用户SignInUserInfo userInfo =loadSignInUserInfo(accessToken);// 获取 Feed 内容Feeds feeds = feedsMapper.findById(id);// 判断 Feed 是否已经被删除且只能删除自己的 FeedAssertUtil.isTrue(feeds ==null,"该Feed已被删除");AssertUtil.isTrue(!feeds.getFkUserId().equals(userInfo.getId()),"只能删除自己的Feed");// 删除int count = feedsMapper.delete(id);if(count ==0){return;}// 将内容从粉丝的集合中删除 -- 异步消息队列优化// 先获取我的粉丝List<Integer> followers =findFollowers(userInfo.getId());// 移除 Feed
        followers.forEach(follower ->{String key =RedisKeyConstant.following_feeds.getKey()+ follower;
            redisTemplate.opsForZSet().remove(key, feeds.getId());});}

FeedsMapper

在这里插入图片描述

启动项目测试

数据库中的feeds:

用户只能删除自己创建的Feed,测试用id为6的用户删除id为14的Feed(该Feed是id为7的用户创建的):
在这里插入图片描述
在这里插入图片描述

用id为7的用户登陆后,逻辑删除id=14的feeds:
image.png
删除后再次删除:
在这里插入图片描述
查看数据库中的feeds已经逻辑删除:
在这里插入图片描述
查看redis相关Feed也不存在了:
image.png

关注/取关时处理用户 Feed

当A用户关注B用户时,那么要实时的将B的所有Feed推送到A用户的Feed集合中,同样如果A用户取关B用户,那么要将B用户所有的Feed从A用户的Feed集合中移除。

FeedsController

/**
     * 变更 Feed
     *
     * @return
     */@PostMapping("updateFollowingFeeds/{followingDinerId}")publicResultInfoaddFollowingFeeds(@PathVariableInteger followingDinerId,String access_token,@RequestParamint type){
        feedsService.addFollowingFeed(followingDinerId, access_token, type);returnResultInfoUtil.buildSuccess(request.getServletPath(),"操作成功");}

FeedsService

/**
     * 变更 Feed
     *
     * @param followinguserId 关注的好友 ID
     * @param accessToken      登录用户token
     * @param type             1 关注 0 取关
     */@Transactional(rollbackFor =Exception.class)publicvoidaddFollowingFeed(Integer followinguserId,String accessToken,int type){// 请选择关注的好友AssertUtil.isTrue(followinguserId ==null|| followinguserId <1,"请选择关注的好友");// 获取登录用户信息SignInUserInfo userInfo =loadSignInUserInfo(accessToken);// 获取关注/取关的用户的所有 FeedList<Feeds> feedsList = feedsMapper.findByUserId(followinguserId);String key =RedisKeyConstant.following_feeds.getKey()+ userInfo.getId();if(type ==0){// 取关List<Integer> feedIds = feedsList.stream().map(feed -> feed.getId()).collect(Collectors.toList());
            redisTemplate.opsForZSet().remove(key, feedIds.toArray(newInteger[]{}));}else{// 关注Set<ZSetOperations.TypedTuple> typedTuples =
                    feedsList.stream().map(feed ->newDefaultTypedTuple<>(feed.getId(),(double) feed.getUpdateDate().getTime())).collect(Collectors.toSet());
            redisTemplate.opsForZSet().add(key, typedTuples);}}

FeedsMapper

/**
* 根据用户 ID 查询 Feed
* @param userId
* @return
*/@Select("select id, content, update_date from t_feeds "+" where fk_user_id = #{userId} and is_valid = 1")List<Feeds>findByUserId(@Param("userId")Integer userId);

ms-follow 服务关注取关时变更 Feed

添加调用ms-feeds服务的请求地址项目路径
在这里插入图片描述

FollowService新增关注/取关时Feed逻辑

/**
     * 发送请求添加或者移除关注人的Feed列表
     *
     * @param followUserId 关注好友的ID
     * @param accessToken   当前登录用户token
     * @param type          0=取关 1=关注
     */privatevoidsendSaveOrRemoveFeed(Integer followUserId,String accessToken,int type){String feedsUpdateUrl = feedsServerName +"updateFollowingFeeds/"+ followUserId +"?access_token="+ accessToken;// 构建请求头HttpHeaders headers =newHttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);// 构建请求体(请求参数)MultiValueMap<String,Object> body =newLinkedMultiValueMap<>();
        body.add("type", type);HttpEntity<MultiValueMap<String,Object>> entity =newHttpEntity<>(body, headers);
        restTemplate.postForEntity(feedsUpdateUrl, entity,ResultInfo.class);}

启动项目测试

用户8,9,10都关注了用户7

在这里插入图片描述
那么用户7推送一条feeds(朋友圈) 的时候,他的粉丝用户8,9,10应该都可以看到,测试用户7发送feed:
在这里插入图片描述
查看数据库和redis:
image.png
在这里插入图片描述

用户10取消关注用户7

用户10的feeds集合中存储了关注用户的feeds :
image.png
让用户10取消关注用户7:
在这里插入图片描述
用户7的所有feeds(朋友圈) 应该从用户10的feeds集合中移除:
在这里插入图片描述

只剩下用户8、9相关的。

用户11关注用户7

image.png

在这里插入图片描述
用户7的所有feeds(朋友圈) 应该都添加到用户11的feeds集合中:
在这里插入图片描述

分页获取关注的 Feed 数据

当前数据库用户7发布了

构建返回的FeedsVO

/**
 *
 * Feed显示信息
 * @author zjq
 */@Getter@Setter@ApiModel(description ="Feed显示信息")publicclassFeedsVOimplementsSerializable{@ApiModelProperty("主键")privateInteger id;@ApiModelProperty("内容")privateString content;@ApiModelProperty("点赞数")privateint praiseAmount;@ApiModelProperty("评论数")privateint commentAmount;@ApiModelProperty("餐厅id")privateInteger fkRestaurantId;@ApiModelProperty("用户ID")privateInteger fkUserId;@ApiModelProperty("用户信息")privateShortUserInfo userInfo;@ApiModelProperty("显示时间")@JsonFormat(pattern ="yyyy-MM-dd HH:mm")publicDate createDate;}

FeedsController

/**
* 分页获取关注的 Feed 数据
*
* @param page
* @param access_token
* @return
*/@GetMapping("{page}")publicResultInfoselectForPage(@PathVariableInteger page,String access_token){List<FeedsVO> feedsVOS = feedsService.selectForPage(page, access_token);returnResultInfoUtil.buildSuccess(request.getServletPath(), feedsVOS);}

FeedsService

登录用户每次发送朋友圈都会向粉丝的feeds集合中推送这条朋友圈,那么当粉丝就可以获取关注的人的所有feeds。
比如用户8,9,11关注了用户7,那么用户7发的5条朋友圈,用户8,9,11都能看到,用户8同时还跟用户6是好友,那么用户8可以同时看到用户7和用户6发送的3条朋友圈。
在这里插入图片描述

在这里插入图片描述
实现逻辑如下:

  • 获取登录用户信息
  • 构建分页查询的参数start,end
  • 从Redis的sorted sets中按照score的降序进行读取Feed的id
  • 从数据库中获取Feed的信息
  • 构建Feed关联的用户信息(不是循环逐条读取,而是批量获取)
/**
     * 根据时间由近至远,每次查询 6 条 Feed
     *
     * @param page
     * @param accessToken
     * @return
     */publicList<FeedsVO>selectForPage(Integer page,String accessToken){if(page ==null){
            page =1;}// 获取登录用户SignInUserInfo userInfo =loadSignInUserInfo(accessToken);// 我关注的好友的 FeedkeyString key =RedisKeyConstant.following_feeds.getKey()+ userInfo.getId();// SortedSet 的 ZREVRANGE 命令是闭区间long start =(page -1)*ApiConstant.PAGE_SIZE;long end = page *ApiConstant.PAGE_SIZE-1;Set<Integer> feedIds = redisTemplate.opsForZSet().reverseRange(key, start, end);if(feedIds ==null|| feedIds.isEmpty()){returnLists.newArrayList();}// 根据多主键查询 FeedList<Feeds> feeds = feedsMapper.findFeedsByIds(feedIds);// 初始化关注好友 ID 集合List<Integer> followinguserIds =newArrayList<>();// 添加用户 ID 至集合,顺带将 Feeds 转为 Vo 对象List<FeedsVO> feedsVOS = feeds.stream().map(feed ->{FeedsVO feedsVO =newFeedsVO();BeanUtil.copyProperties(feed, feedsVO);// 添加用户 ID
            followinguserIds.add(feed.getFkUserId());return feedsVO;}).collect(Collectors.toList());// 远程调用获取 Feed 中用户信息ResultInfo resultInfo = restTemplate.getForObject(usersServerName +"findByIds?access_token=${accessToken}&ids={ids}",ResultInfo.class, accessToken, followinguserIds);if(resultInfo.getCode()!=ApiConstant.SUCCESS_CODE){thrownewParameterException(resultInfo.getCode(), resultInfo.getMessage());}List<LinkedHashMap> userInfoMaps =(ArrayList) resultInfo.getData();// 构建一个 key 为用户 ID,value 为 ShortuserInfo 的 MapMap<Integer,ShortUserInfo> userInfos = userInfoMaps.stream().collect(Collectors.toMap(// key
                        diner ->(Integer) diner.get("id"),// value
                        diner ->BeanUtil.fillBeanWithMap(diner,newShortUserInfo(),true)));// 循环 VO 集合,根据用户 ID 从 Map 中获取用户信息并设置至 VO 对象
        feedsVOS.forEach(feedsVO ->{
            feedsVO.setUserInfo(userInfos.get(feedsVO.getFkUserId()));});return feedsVOS;}

FeedsMapper

/**
     * 根据多主键查询 Feed
     * @param feedIds
     * @return
     */@Select("<script> "+" select id, content, fk_user_id, praise_amount, "+" comment_amount, fk_restaurant_id, create_date, update_date, is_valid "+" from t_feeds where is_valid = 1 and id in "+" <foreach item=\"id\" collection=\"feedIds\" open=\"(\" separator=\",\" close=\")\">"+"   #{id}"+" </foreach> order by id desc"+" </script>")List<Feeds>findFeedsByIds(@Param("feedIds")Set<Integer> feedIds);

启动项目测试

查询用户8关注好友的feeds列表:
用户8同时和用户7、用户6是好友,那么用户8可以同时看到用户7的5条朋友圈和用户6发送的3条朋友圈。
在这里插入图片描述
调用分页查询接口查询如下:
http://localhost/feeds/1?access_token=1d7eb176-2454-4fd4-96d2-9c2d27c0ace6
image.png
可以看到顺序是按照最新的在最上面。

本文内容到此结束了,
如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问💬欢迎各位指出。
主页:共饮一杯无的博客汇总👨‍💻

保持热爱,奔赴下一场山海。🏃🏃🏃

标签: redis 微服务 java

本文转载自: https://blog.csdn.net/qq_35427589/article/details/128355278
版权归原作者 共饮一杯无 所有, 如有侵权,请联系我们删除。

“Redis实现朋友圈,微博等Feed流功能,实现Feed流微服务(代码实现)”的评论:

还没有评论