Redis实战:黑马点评之达人探店
1 发布探店笔记
探店笔记类似点评网站的评价,往往是图文结合。对应的表有两个:
- tb_blog:探店笔记表,包含笔记中的标题、文字、图片等
- tb_blog_comments:其他用户对探店笔记的评价
具体发布流程如下:
UploadController(已完成)
@Slf4j
@RestController
@RequestMapping("upload")
public class UploadController {
@PostMapping("blog")
public Result uploadImage(@RequestParam("file") MultipartFile image) {
try {
// 获取原始文件名称
String originalFilename = image.getOriginalFilename();
// 生成新文件名
String fileName = createNewFileName(originalFilename);
// 保存文件
image.transferTo(new File(SystemConstants.IMAGE_UPLOAD_DIR, fileName));
// 返回结果
log.debug("文件上传成功,{}", fileName);
return Result.ok(fileName);
} catch (IOException e) {
throw new RuntimeException("文件上传失败", e);
}
}
}
这里需要注意一点,将SystemConstants类中的IMAGE_UPLOAD_DIR改成自己nginx中imgs目录的绝对路径
BlogController(已完成)
@RestController
@RequestMapping("/blog")
public class BlogController {
@Resource
private IBlogService blogService;
@PostMapping
public Result saveBlog(@RequestBody Blog blog) {
//获取登录用户
UserDTO user = UserHolder.getUser();
blog.setUpdateTime(user.getId());
//保存探店博文
blogService.saveBlog(blog);
//返回id
return Result.ok(blog.getId());
}
}
2 查看探店笔记
实现查看发布探店笔记的接口
代码具体实现如下:
在BlogController中(queryHotBlog的功能已经完成,但是我们需要将其具体业务逻辑移至service层)
@GetMapping("/hot")
public Result queryHotBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) {
return blogService.queryHotBlog(current);
}
@GetMapping("/{id}")
public Result queryById(@PathVariable("id") Long id){
return blogService.queryById(id);
}
在BlogServiceImpl中
@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 ->{
Long userId = blog.getUserId();
User user = userService.getById(userId);
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
});
return Result.ok(records);
}
@Override
public Result queryById(Long id) {
Blog blog = getById(id);
if(blog==null){
return Result.fail("博客不存在");
}
User user = userService.getById(blog.getUserId());
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
return Result.ok(blog);
}
3 点赞功能
点赞功能在原有项目中提供了一份初始代码:
@GetMapping("/likes/{id}")
public Result queryBlogLikes(@PathVariable("id") Long id) {
//修改点赞数量
blogService.update().setSql("liked = liked +1 ").eq("id",id).update();
return Result.ok();
}
上述代码的问题在于,一个用户是可以无限点赞的,而且用户点赞了之后也不能取消点赞。
不仅如此,当前代码还有一些其他功能尚未实现,比如当前用户为某篇笔记点赞之后,这篇笔记下面的点赞按钮应该是高亮显示的,而当前项目,即使点赞了,点赞按钮仍然显示灰色。
接下来我们需要完善点赞功能,主要是针对两点:
- 同一个用户只能点赞一次,再次点击则取消点赞
- 如果当前用户已经点赞,则点赞按钮高亮显示(前端根据Blog类的isLike是否为true判断点赞按钮是否需要高亮显示)
问题来了,怎样快速判断当前用户是否点赞了呢?我们在这里可以使用redis中的set集合,我们可以以博客id为key,已经点赞的用户id为set集合中的数据,由set集合中的数据是不能重复的,因此同一个用户无论点赞多少次,集合中都只会有一个元素
具体实现步骤如下:
- 给Blog类中添加一个isLike字段,用来标示是否被当前用户点赞
- 修改点赞功能,利用Redis的set集合判断是否点赞过,未点赞过则点赞数+1,已点赞过则点赞数-1
- 修改根据id查询Blog的业务中需要判断当前登录用户是否点赞过,并将判断结果赋值给isLike字段
- 修改分页查询Blog业务中需要判断当前登录用户是否点赞过,并将判断结果赋值给isLike字段
代码编写如下:
1 在Blog 添加一个字段
@TableField(exist = false)
private Boolean isLike;
2 修改UserController中的likeBlog方法
@PutMapping("/like/{id}")
public Result likeBlog(@PathVariable("id") Long id) {
return blogService.likeBlog(id);
}
3 修改BlogServiceImpl
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {
@Autowired
private IUserService userService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@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 ->{
Long userId = blog.getUserId();
User user = userService.getById(userId);
//查看当前用户是否点赞该博客
blog.setIsLike(isLikeBlog(blog.getId()));
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
});
return Result.ok(records);
}
@Override
public Result queryById(Long id) {
Blog blog = getById(id);
if(blog==null){
return Result.fail("博客不存在");
}
User user = userService.getById(blog.getId());
//判断当前用户是否点赞该文章
blog.setIsLike(isLikeBlog(id));
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
return Result.ok(blog);
}
/**
* 博客点赞
* @param id
* @return
*/
@Override
public Result likeBlog(Long id) {
Long userId = UserHolder.getUser().getId();
String blogKey = RedisConstants.BLOG_LIKED_KEY+id;
//判断文章是否已经点赞
boolean member = isLikeBlog(id);
if(!member){
//当前用户未点赞
boolean success = update().setSql("liked = liked + 1").eq("id", id).update();
//保存用户id到set集合
if(success){
stringRedisTemplate.opsForSet().add(blogKey,userId.toString());
}
}else{
//当前用户已点赞
boolean success = update().setSql("liked = liked - 1").eq("id", id).update();
if(success){
stringRedisTemplate.opsForSet().remove(blogKey,userId.toString());
}
}
return Result.ok();
}
/**
* 判断当前用户是否点赞指定文章
* @param id
* @return
*/
public boolean isLikeBlog(Long id){
Long userId = UserHolder.getUser().getId();
//当前用户未登录,直接返回false
if(userId == null){
return false;
}
String blogKey = RedisConstants.BLOG_LIKED_KEY+id;
//查询set集合中是否有用户当前用户id
Boolean member = stringRedisTemplate.opsForSet().isMember(blogKey, userId.toString());
return BooleanUtil.isTrue(member);
}
}
4 点赞排行榜
在探店笔记的详情页面,应该把最早给该笔记点赞的五位用户显示出来,形成点赞排行榜:
之前的点赞用户id是放到set集合,但是set集合是不能排序的,所以这个时候我们可以使用sortedSet
代码编写如下:
修改BlogServiceImpl
/**
* 博客点赞
* @param id
* @return
*/
@Override
public Result likeBlog(Long id) {
Long userId = UserHolder.getUser().getId();
String blogKey = RedisConstants.BLOG_LIKED_KEY+id;
//判断文章是否已经点赞
boolean member = isLikeBlog(id);
if(!member){
//当前用户未点赞
boolean success = update().setSql("liked = liked + 1").eq("id", id).update();
if(success){
//保存用户id到sortedSet集合,按照时间进行排序
stringRedisTemplate.opsForZSet().add(blogKey,userId.toString(),System.currentTimeMillis());
}
}else{
//当前用户已点赞
boolean success = update().setSql("liked = liked - 1").eq("id", id).update();
if(success){
//将用户从sortedSet中移除
stringRedisTemplate.opsForZSet().remove(blogKey,userId.toString());
}
}
return Result.ok();
}
/**
* 判断当前用户是否点赞指定文章
* @param id
* @return
*/
public boolean isLikeBlog(Long id){
Long userId = UserHolder.getUser().getId();
//当前用户未登录,直接返回false
if(userId == null){
return false;
}
String blogKey = RedisConstants.BLOG_LIKED_KEY+id;
//查询sortedSet集合中是否有用户当前用户id
//score()为查询指定用户的分数,如果分数为null说明该用户不存在
Double score = stringRedisTemplate.opsForZSet().score(blogKey, userId.toString());
if(score==null){
return false;
}
return true;
}
在BlogController中编写likes方法
@GetMapping("likes/{id}")
public Result likes(@PathVariable("id") Long id){
return blogService.likes(id);
}
在BlogService中编写likes方法
/**
* 点赞排行榜
* @param id
* @return
*/
@Override
public Result likes(Long id) {
String blogKey = RedisConstants.BLOG_LIKED_KEY+id;
//获取前五位点赞的用户
Set<String> userSet = stringRedisTemplate.opsForZSet().range(blogKey, 0, 5);
if(userSet==null||userSet.isEmpty()){
//说明当前没有用户点赞
return Result.ok();
}
//将set集合解析成用户id集合
List<Long> ids = userSet.stream().map(Long::valueOf).collect(Collectors.toList());
String idStr= StrUtil.join(",",ids);
//根据用户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(Collectors.toList());
return Result.ok(userDTOS);
}