发布探店笔记
@Resource
private IBlogService blogService;
@PostMapping
public Result saveBlog(@RequestBody Blog blog) {
// 获取登录用户
UserDTO user = UserHolder.getUser();
blog.setUserId(user.getId());
// 保存探店博文
blogService.save(blog);
// 返回id
return Result.ok(blog.getId());
}
查看探店笔记+点赞
@PutMapping("/like/{id}")
public Result likeBlog(@PathVariable("id") Long id) {
// 修改点赞数量
return blogService.likeBlog(id);
}
@GetMapping("/hot")
public Result queryHotBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) {
return blogService.queryHotBlog(current);
}
@GetMapping("/{id}")
public Result queryBlogById(@PathVariable("id") Long id){
return blogService.queryBlogById(id);
}
@Resource
private IUserService userService;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result queryBlogById(Long id) {
// 1.查询blog
Blog blog = getById(id);
if (blog==null) {
return Result.fail("笔记不存在!");
}
// 2.查询blog有关的用户
queryBlogUser(blog);
// 3.查询blog是否被点赞
isBlogLiked(blog);
return Result.ok(blog);
}
private void isBlogLiked(Blog blog) {
// 获取当前登录的用户
Long userId = UserHolder.getUser().getId();
// 判断当前登录的用户是否点赞过了
String key="blog:liked:"+blog.getId();
Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
blog.setIsLike(BooleanUtil.isTrue(isMember));
}
private void queryBlogUser(Blog blog) {
Long userId = blog.getUserId();
User user = userService.getById(userId);
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
}
@Override
public Result queryHotBlog(Integer current) {
// 根据用户查询
Page<Blog> page = query()
.orderByDesc("liked")
.page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
// 获取当前页数据
List<Blog> records = page.getRecords();
// 查询用户
records.forEach(blog ->{
queryBlogUser(blog);
isBlogLiked(blog);
});
return Result.ok(records);
}
@Override
public Result likeBlog(Long id) {
// 获取当前登录的用户
Long userId = UserHolder.getUser().getId();
// 判断当前登录的用户是否点赞过了
String key="blog:liked:"+id;
Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
// 如果未点赞过
if (BooleanUtil.isFalse(isMember)) {
// 点赞数+1
boolean isSuccess = update().setSql("liked=liked+1").eq("id", id).update();
if (isSuccess){
// 存放用户信息到redis中set
stringRedisTemplate.opsForSet().add(key,userId.toString());
}
}else {
// 点赞过
// 点赞数-1
boolean isSuccess = update().setSql("liked=liked-1").eq("id", id).update();
if (isSuccess){
// 将用户信息从redis中移除
stringRedisTemplate.opsForSet().remove(key,userId.toString());
}
}
return Result.ok();
}
点赞排行榜(set集合转为SortedSet集合)
@GetMapping("/likes/{id}")
public Result queryBlogLikes(@PathVariable("id") Long id){
return blogService.queryBlogLikes(id);
}
package com.hmdp.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.Blog;
import com.hmdp.entity.User;
import com.hmdp.mapper.BlogMapper;
import com.hmdp.service.IBlogService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.service.IUserService;
import com.hmdp.utils.SystemConstants;
import com.hmdp.utils.UserHolder;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static com.hmdp.utils.RedisConstants.BLOG_LIKED_KEY;
import static java.util.stream.Collectors.*;
/**
* <p>
* 服务实现类
* </p>
*
* @author 虎哥
* @since 2021-12-22
*/
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {
@Resource
private IUserService userService;
@Resource
private StringRedisTemplate stringRedisTemplate;
// 笔记详情接口(笔记相关用户,笔记点赞)
@Override
public Result queryBlogById(Long id) {
// 1.查询blog
Blog blog = getById(id);
if (blog == null) {
return Result.fail("笔记不存在!");
}
// 2.查询blog有关的用户
queryBlogUser(blog);
// 3.查询blog是否被点赞
isBlogLiked(blog);
return Result.ok(blog);
}
// 首页接口(判断笔记是否被登录用户点赞过)
private void isBlogLiked(Blog blog) {
// 获取当前登录的用户
UserDTO user = UserHolder.getUser();
if (user == null) {
return;
}
Long userId = user.getId();
// 判断当前登录的用户是否点赞过了
String key = BLOG_LIKED_KEY + blog.getId();
Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
blog.setIsLike(score != null);
}
// 首页,查看详情(每篇笔记对应的用户)
private void queryBlogUser(Blog blog) {
Long userId = blog.getUserId();
User user = userService.getById(userId);
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
}
// 首页分页查询
@Override
public Result queryHotBlog(Integer current) {
// 根据用户查询
Page<Blog> page = query()
.orderByDesc("liked")
.page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
// 获取当前页数据
List<Blog> records = page.getRecords();
// 查询用户
records.forEach(blog -> {
queryBlogUser(blog);
isBlogLiked(blog);
});
return Result.ok(records);
}
// 首页(每篇笔记点赞数量)
@Override
public Result likeBlog(Long id) {
// 获取当前登录的用户
Long userId = UserHolder.getUser().getId();
// 判断当前登录的用户是否点赞过了
String key = BLOG_LIKED_KEY + id;
Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
// 如果未点赞过
if (score == null) {
// 点赞数+1
boolean isSuccess = update().setSql("liked=liked+1").eq("id", id).update();
if (isSuccess) {
// 存放用户信息到redis中set
stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
}
} else {
// 点赞过
// 点赞数-1
boolean isSuccess = update().setSql("liked=liked-1").eq("id", id).update();
if (isSuccess) {
// 将用户信息从redis中移除
stringRedisTemplate.opsForZSet().remove(key, userId.toString());
}
}
return Result.ok();
}
// 笔记详情点赞排行榜
@Override
public Result queryBlogLikes(Long id) {
String key = BLOG_LIKED_KEY + id;
// 1.查询top5点赞用户 zrange key 0 4
Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
if (top5 == null || top5.isEmpty()) {
return Result.ok(Collections.emptyList());
}
// 2.解析其中用户的ids
List<Long> ids = top5.stream().map(Long::valueOf).collect(toList());
// 3.根据id查询用户
// 出现点赞顺序排行出错
/*List<UserDTO> userDTOS = userService.listByIds(ids)
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(toList());*/
String idStr = StrUtil.join(",", ids); //[5,1] -> "5,1"
// 根据用户id查询用户 where id in (5,1) order by field (id,5,1)
List<UserDTO> userDTOS = userService.query().in("id", ids)
.last("ORDER BY FIELD(id," + idStr + ")").list()
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(toList());
// 4.返回
return Result.ok(userDTOS);
}
}
好友关注:实现关注和取关功能(隐含一个查询是否关注功能)
@Override
public Result follow(Long followUserId, Boolean isFollow) {
// 获取登陆的用户
Long userId = UserHolder.getUser().getId();
// 1.判断到底是关注还是取关
if (isFollow){
// 2.关注,新增数据
Follow follow = new Follow();
follow.setUserId(userId);
follow.setFollowUserId(followUserId);
save(follow);
}else {
// 3.取关,删除
// delete from tb_follow where user_id=? and follow_user_id=?
remove(new QueryWrapper<Follow>().eq("user_id",userId).eq("follow_user_id",followUserId));
}
return Result.ok();
}
@Override
public Result isFollow(Long followUserId) {
// 获取登陆的用户
Long userId = UserHolder.getUser().getId();
// 1.查询是否关注 select count(*) from tb_follow where user_id=? and follow_user_id=?
Integer count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count();
// 2.判断
return Result.ok(count>0);
}
实现共同关注功能(前面关注和取关数据不仅要保存到数据库中还要保存到redis中set集合)
// 关注
@Override
public Result follow(Long followUserId, Boolean isFollow) {
// 获取登陆的用户
Long userId = UserHolder.getUser().getId();
//
String key="follows:"+userId;
// 1.判断到底是关注还是取关
if (isFollow){
// 2.关注,新增数据
Follow follow = new Follow();
follow.setUserId(userId);
follow.setFollowUserId(followUserId);
boolean isSuccess=save(follow);
if(isSuccess){
// 把关注用户的id,放入redis的set集合 sadd userId followUserId
stringRedisTemplate.opsForSet().add(key,followUserId.toString());
}
}else {
// 3.取关,删除
// delete from tb_follow where user_id=? and follow_user_id=?
boolean isSuccess=remove(new QueryWrapper<Follow>().eq("user_id",userId).eq("follow_user_id",followUserId));
if(isSuccess){
// 把关注用户的id从redis的set集合移除
stringRedisTemplate.opsForSet.remove(key);
}
}
return Result.ok();
}
/**
* 共同关注
* @param id 打算关注的目标用户id
* @return
*/
@Override
public Result followCommons(Long id) {
// 1.获取当前用户
Long userId = UserHolder.getUser().getId();
String key="follows:"+userId;
// 2.求交集
String key2="follows:"+id;
// 登录用户和目标用户交集
Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key, key2);
if (intersect==null||intersect.isEmpty()){
return Result.ok(Collections.emptyList());
}
// 3.解析id集合
List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
// 4.查询用户
List<UserDTO> users = userService.listByIds(ids).stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
return Result.ok(users);
}
feed流推送
拉模式:读扩散即up主张三李四王五发消息都存放到各自发件箱里(msg+时间戳),然后其粉丝关注张三和李四,它就读取张三李四的发件箱并且根据时间戳排序依次存放到收件箱里,如果关注很多人,那么势必会延迟显示信息,不可取(适用于粉丝关注up主少的人群)
推模式:写扩散即up主张三李四就没有发件箱了,每发送消息都会写到各自粉丝的收件箱里,延时低,缺点就是up主本身没有发件箱,那么不得不把每条消息都写给每个粉丝的收件箱里,写了好几份,内存占用较高一个消息要写入n份(适用于被关注少的up主人群)
推模式实现关注推送
@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 tb_follow where follow_user_id=?
List<Follow> follows = followService.query().eq("follow_user_id", user.getId()).list();
// 4.推送笔记id给所有粉丝
for (Follow follow:follows){
// 4.1获取粉丝id
Long followId = follow.getUserId();
// 4.2推送
String key="feed:"+followId;
stringRedisTemplate.opsForZSet().add(key,blog.getId().toString(),System.currentTimeMillis());
}
// 返回id
return Result.ok(blog.getId());
}
滚动分页查询(SortedSet数据结构)参数
max: 当前时间戳 | 上一次查询的最小时间戳
min: 0
offset: 0 | 在上一次结果中与最小时间戳一样的个数
count: 3
package com.hmdp.dto;
import lombok.Data;
import java.util.List;
@Data
public class ScrollResult {
private List<?> list;
private Long minTime;
private Integer offset;
}
/**
* 滚动分页查询
* @param max
* @param offset
* @return
*/
@GetMapping("/of/follow")
public Result queryBlogOfFollow(@RequestParam("lastId") Long max,@RequestParam(value = "offset",defaultValue = "0") Integer offset){
return blogService.queryBlogOfFollow(max,offset);
}
@Override
public Result queryBlogOfFollow(Long max, Integer offset) {
// 1.获取当前用户
Long userId = UserHolder.getUser().getId();
// 2.查询收件箱
// zrevrangebyscore key max min limit offset count
String key = FEED_KEY + userId;
Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
.reverseRangeByScoreWithScores(key, 0, max, offset, 3);
if (typedTuples == null || typedTuples.isEmpty()) {
return Result.ok();
}
// 3.解析数据:blogId、minTime(时间戳)、offset
List<Long> ids = new ArrayList<>(typedTuples.size());
long minTime = 0;
int os = 1;//最小值数量
for (ZSetOperations.TypedTuple tuple : typedTuples) {
// 4.1.获取id
String idStr = (String) tuple.getValue();
ids.add(Long.valueOf(idStr));
// 4.2.获取时间戳
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){
// 查询blog有关的用户
queryBlogUser(blog);
// 查询blog是否被点赞
isBlogLiked(blog);
}
// 5.封装并返回
ScrollResult r = new ScrollResult();
r.setList(blogs);
r.setMinTime(minTime);
r.setOffset(os);
return Result.ok(r);
}
注意:mp里面listByIds()是无序查找的使用in,要想有序得手动编写sql