1.查询笔记
修改图片文件夹为本地的正确的文件夹,请求url是/update/blog
添加BlogController里管理"/{id}" 的功能blogService.queryBlogById(id)
@Override
public Result queryBlogById(Long id) {
//查询blog
Blog blog = getById(id);
if (blog == null) {
return Result.fail("笔记不存在!");
}
//查询用户
queryBlogUser(blog);
return Result.ok(blog);
}
private void queryBlogUser(Blog blog) {
Long userId = blog.getUserId();
User user = userService.getById(userId);
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
}
2.点赞功能
需求:
- 同一个用户只能点赞一次,再次点击则取消点赞
- 如果当前用户已经点赞,则点赞按钮高亮显示
实现步骤:
- 给Blog类中添加一个isLike字段,标识是否被当前用户点赞
- 修改点赞功能,利用redis的set集合
业务逻辑:
- 获取登录用户
- 判断当前登录用户是否已经点赞
- 如果未点赞,可以点赞
3.1 数据库点赞数+1
3.2 保存到redis的set集合 - 如果已点赞,取消点赞
4.1 数据库点赞数-1
4.2 把用户从redis的set集合移除
@Override
public Result likeBlog(Long id) {
// 1.获取登录用户
Long userId = UserHolder.getUser().getId();
// 2. 判断当前登录用户是否已经点赞
String key = RedisConstants.BLOG_LIKED_KEY + id;
Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
// 3. 如果未点赞,可以点赞
if (BooleanUtil.isFalse(isMember)) {
// 3.1 数据库点赞数+1
boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update();
if (isSuccess) {
// 3.2 保存到redis的set集合
stringRedisTemplate.opsForSet().add(key, userId.toString());
}
} else {
// 4. 如果已点赞,取消点赞
// 4.1 数据库点赞数-1
// 4.2 把用户从redis的set集合移除
boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();
if (isSuccess) {
stringRedisTemplate.opsForSet().remove(key, userId.toString());
}
}
return Result.ok();
3.点赞排行榜
使用ZSET,也就是Sorted Set
使用mysql自定义排序方法order by field
//解析id
List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
String idstr = StrUtil.join(",", ids);
//根据id查询用户
List<UserDTO> users = userService.query()
.in("id", ids).last("ORDER BY FIELD(id," + idstr + ")").list()
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
好友关注
1.关注和取关
@Override
public Result follow(Long followUserId, Boolean isFollow) {
//获取用户
Long userId = UserHolder.getUser().getId();
//判断是关注还是取关
if (isFollow) {
//关注,新增
Follow follow = new Follow();
follow.setUserId(userId);
follow.setFollowUserId(followUserId);
save(follow);
} else {
//取关,删除
remove(new QueryWrapper<Follow>().eq("user_id", userId).eq("follow_user_id", followUserId));
}
return Result.ok();
}
2.共同关注
通过redis里面set求交集opsForSet().intersect
@Override
public Result followCommons(Long id) {
//获取当前用户
Long userId = UserHolder.getUser().getId();
String key1 = "follows:" + userId.toString();
//求交集
String key2 = "follows:" + id.toString();
Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key1, key2);
if (intersect == null || intersect.isEmpty()) {
return Result.ok(Collections.emptyList());
}
//解析id
List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
//查询用户
List<UserDTO> users = userService.listByIds(ids)
.stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
return Result.ok(users);
}
3.关注推送
Feed流的模式
- Timeline:不做内容筛选,常用于好友,例如朋友圈
- 优点:信息全面,实现简单
- 缺点:信息噪音较多
- 智能排序:利用智能算法
- 优点:投喂用户感兴趣信息,用户黏度高
- 缺点:算法需要精准
Timeline实现方案:
- 拉模式
- 读扩散
- 推模式
- 推拉结合
在保存笔记的时候把笔记推送给所有粉丝
@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 where follow_user_id = ? List<Follow> follows = followService.query().eq("follow_user_id", user.getId()).list();
//4.推送笔记id给所有粉丝
for (Follow follow : follows) {
Long fansId = follow.getUserId();
String key = "feed:" + fansId;
stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
}
//5.返回id
return Result.ok(blog.getId());
}
分页查询
分页查询参数
-
max:上次查询的最小时间戳 | 当前时间
-
min:0
-
offset: 与上一次最小时间戳一致的所有元素的个数 | 0
-
count: 这里定为3
-
请求参数
- lastId:上一次查询的最小时间戳
- offset:偏移量
-
返回值
- List< Blog >:小于指定时间戳的笔记集
- minTime:本次查询的推送的最小时间戳
- offset:偏移量
使用stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(也就是redis的# Zrevrangebyscore命令)实现滚动排序
@Override
public Result queryBlogOfFollow(Long max, Integer offset) {
//1.获取当前用户
Long userId = UserHolder.getUser().getId();
//2.查询邮件箱
String key = RedisConstants.FEED_KEY + userId;
Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, 0, max, offset, 3);
if (typedTuples == null || typedTuples.isEmpty()) {
return Result.fail("失败!");
}
//3.解析数据:blogId,minTime,offset
List<Long> ids = new ArrayList<>();
long minTime = 0;
int os = 1;
for (ZSetOperations.TypedTuple<String> tuple : typedTuples) {
//获取id
ids.add(Long.valueOf(tuple.getValue()));
//获取时间戳
long time = tuple.getScore().longValue();
if (time == minTime) {
os++;
} else {
minTime = time;
os = 1;
}
}
//4.根据id查询blog
String idStr = StrUtil.join(",", ids);
List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
for (Blog blog : blogs) {
//查询用户
queryBlogUser(blog);
//查询用户是否点赞
isBlogLiked(blog);
}
//5.封装并返回
ScrollResult scrollResult = new ScrollResult();
scrollResult.setList(blogs);
scrollResult.setOffset(os);
scrollResult.setTime(minTime);
return Result.ok(scrollResult);
}