功能:推送 feed

  1. 目标:尽可能高效的将博主发布的文章推送给粉丝

功能点

  1. Feed流 (推送) 功能
    1. TimeLine 模式:不做内容筛选,按照内容发布时间排序,常用于好友或关注
    2. 智能排序:自动屏蔽违规、用户不感兴趣的内容。推送用户感兴趣的信息吸引用户

业务方案

  1. 方案一

    1. 推模式:也叫写扩散,即直接给其粉丝收件箱写入其发布内容
  2. 方案二(最优方案)

    1. 写扩散,推拉结合模式,也叫读写混合模式
    2. 业务逻辑
      1. 普通用户:推模式 (写扩散),直接给其粉丝收件箱写入其发布内容
      2. 大 V:推拉结合,活跃粉丝采用推模式,普通粉丝采用拉模式 (读扩散),等待用户自行读取

业务逻辑

  1. 请求方式:GET
  2. 请求路径:/blog/of/follow
  3. 请求参数
    1. lastId:上一次请求的最小时间戳
    2. offset:偏移量,即当前最小时间戳已经出现过的次数
  4. 返回值
    1. List<Blog>:小于指定时间戳的笔记集合
    2. minTime:此次查询结果的最小时间戳,供下次查询使用
    3. offset:偏移量,供下次查询使用

代码实现

BlogController

@RestController
@RequestMapping("/blog")
public class BlogController {
	
	@Resource
	private IBlogService blogService;
	
	// 当前登录用户发布blog,同时推送给其粉丝
	@PostMapping
	public Result saveBlog(@RequestBody Blog blog) {
		long blogId = blogService.saveBlog(blog);
		return Result.ok(blogId);
	}
	
	// 当前登录用户查询目标用户的blog
	@GetMapping("/of/follow")
	public Result queryBlogOfFollow(@RequestParam("lastId") Long max, @RequestParam("offset", defaultValue = "0") Integer offset) {
		ScrollResult blogPage = blogService.queryBlogOfFollow(max, offset);
		return Result.ok(blogPage);
	}
	
}

BlogServiceImpl

@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {

	@Resource
	private StringRedisTemplate stringRedisTemplate;
	
	@Resource
	private IFollowService followService;
	
	@Override
	public long saveBlog(Blog blog) {
		// 1. 获取登录用户id
		UserDTO user = UserHolder.getUser();
		blog.setUserId(user.getId());
		// 2. 保存blog
		boolean saveSuccess = save(blog);
		if(!saveSuccess)
			return Result.fail("保存笔记失败!");
		// 3. 查询当前用户的所有粉丝
		List<Follow> follows = followService.query().eq("follow_user_id", user.getId()).list();
		// 4. 推送blog给所有粉丝
		for (Follow follow : follows) {
			// 4.1. 获取粉丝id
			Long userId = follow.getUserId();
			// 4.2. 推送blogId到粉丝的redis信箱中
			String feedBoxKey = "feed:" + userId;
			stringRedisTemplate.opsForZSet().add(feedBoxKey, blog.getId().toString(), System.currentTimeMillis());
		}
		// 5. 返回 id
		return blog.getId();
	}
	
	@Override
	public ScrollResult queryBlogOfFollow(Long max, Integer offset) {
	// 1. 获取当前用户
	Long userId = UserHolder.getUser().getId();
	// 2. 查询收件箱 (ZREVRANGESCORE key max min LIMIT offset count)
	String key = "feed:" + userId;
	Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
																														.reverseRangeByScoreWithScores(key, 0, max, offset, 10);
	// 3. 查询结果为空,直接返回
	if(typedTuples == null || typedTuples.isEmpty())
		return Result.ok();
	// 4. 解析数据:blogId & minTime(时间戳) & offset 
	List<Long> ids = new ArrayList<>(typedTuples.size());            // 用于存储目标blogId集合
	long minTime = 0;                                                // 用于存储当前分页的最后一个时间戳
	int os = 1;                                                      // 用于存储当前页面offset个数
	for(ZSetOperations.TypeTuple<String> tuple : typedTyples) {
		// 4.1. 获取id
		ids.add( Long.valueOf(tuple.getValue()) );
		// 4.2. 获取分数(时间戳)
		long lastTime = minTime;
		minTime = tuple.getScore().longValue();
		os = lastTime == minTime ? os+1 : 1;
	}
	// 5. 查询blog
	// 5.1. 根据id查询blog
	String idsStr = StrUtil.join(",", ids);
	List<Blog> blogs = query().in("id", ids).last( "ORDER BY FIELD(id, " + idsStr + ")").list();
	// 5.2. 查询blog详情信息
	for( Blog blog : blogs ) {
		// 5.2.1. 查询给blog点赞的用户列表
		queryBlogUser(blog);
		// 5.2.2. 查询当前用户是否已经给blog点赞
		isBlogLiked(blog);
	}	
	// 6. 封装并返回
	ScrollResult r = new ScrollResult();
	r.setList(blogs);
	r.setOffset(os);
	r.setMinTime(minTime);
	return r;
	}
	
}

/dto/ScrollResult

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值