「黑马点评」八、达人探店
探店笔记
数据库建表:
- tb_blog 探店笔记表,包含标题、文字、图片等
- tb_blog_comments 对某篇探店笔记的评价
建表 sql

image.png|500
上传图片接口:POST /api/upload/blog
@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.success(fileName);
} catch (IOException e) {
throw new RuntimeException("文件上传失败", e);
}
}
上传笔记接口:POST /api/blog
@PostMapping
public Result saveBlog(@RequestBody Blog blog) {
// 获取登录用户
UserDTO user = UserHolder.getUser();
blog.setUserId(user.getId());
// 保存探店博文
blogService.save(blog);
// 返回id
return Result.success(blog.getId());
}
查看探店笔记
实现查看笔记的接口:GET /api/blog/{blogId}
这里有个设计就是 entity 中,使用 TableFiled 为用户信息字段的 exits 置 false,需要设置信息
@GetMapping("/{id}")
public Result<Blog> queryBlogById(@PathVariable("id") Long id) {
return Result.success(blogService.getById(id));
}
@Override
public Blog getById(Long id) {
Blog blog = blogMapper.selectById(id);
return setUserInfo(blog);
}
@GetMapping("/hot")
public Result<List<Blog>> queryHotBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) {
return Result.success(blogService.queryHotBlog(current, SystemConstants.MAX_PAGE_SIZE));
}
private Blog setUserInfo(Blog blog) {
Long userId = blog.getUserId();
User user = userService.getById(userId);
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
return blog;
}
@Override
public List<Blog> queryHotBlog(int current, int size) {
// 计算偏移量
int offset = (current - 1) * size;
List<Blog> blogs = blogMapper.selectHotBlogs(offset, size);
blogs.forEach(this::setUserInfo);
return blogs;
}
点赞
为某篇图文点赞:PUT /api/blog/like/{blogId}
完善功能:
- 一帖一人只能点赞一次
- 如果点赞过需要标记
实现:
- 给 Blog 添加一个 isLike 字段,用于前端展示
- 利用 Redis 的 SET 判断是否点赞过,点赞数依次加减
- 是否点赞赋值到 isLike 字段
比较粗糙的点赞实现以及根据 Blog id 查询图文及作者及是否点赞
@Override
public Blog getById(Long id) {
Blog blog = blogMapper.selectById(id);
setUserInfo(blog);
setIsLike(blog);
return blog;
}
@Override
public boolean likeBlog(Long id) {
Long userId = UserHolder.getUser().getId();
String key = RedisConstants.BLOG_LIKED_KEY + id;
Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
if(Boolean.FALSE.equals(isMember)) {
// 点赞
boolean isSuccess = blogMapper.increaseLiked(id) > 0;
if(isSuccess) {
stringRedisTemplate.opsForSet().add(key, userId.toString());
}
} else {
// 取消点赞
boolean isSuccess = blogMapper.decreaseLiked(id) > 0;
if(isSuccess) {
stringRedisTemplate.opsForSet().remove(key, userId.toString());
}
}
return !isMember;
}
private void setIsLike(Blog blog) {
blog.setIsLike(Boolean.TRUE.equals(stringRedisTemplate.opsForSet().isMember(RedisConstants.BLOG_LIKED_KEY + blog.getId(), UserHolder.getUser().getId().toString())));
}
private void setUserInfo(Blog blog) {
Long userId = blog.getUserId();
User user = userService.getById(userId);
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
}
点赞排行榜
图文点赞列表中显示最早点赞的 TOP5,形成点赞排行榜
方案选择
根据下图对比,明显 SortedSet 更符合要求,利用时间戳即可实现排行榜

image.png|500
常用命令
ZADD setName score1 member1 score2 member2
ZSCORE setName member
ZRANGE setName minRank maxRank
在 java 中利用 Double score 并根据 redis 返回的 score 是否为空判断存不存在
获取当前笔记点赞的 TopN 用户的集合:GET /api/blog/likes/{blogId}
@Override
public List<UserDTO> queryBlogLikes(Long id) {
String key = RedisConstants.BLOG_LIKED_KEY + id;
Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
if(top5 == null || top5.isEmpty()) {
return Collections.emptyList();
}
List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
List<UserDTO> userDTOs = userService.listByIds(ids)
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
return userDTOs;
}
<select id="findByIds" resultType="com.hmdp.entity.User" parameterType="java.util.List">
select id, phone, password, nick_name, icon, create_time, update_time
from tb_user
where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
order by FIELD(id,
<foreach collection="ids" item="id" separator=",">
#{id}
</foreach>
)
</select>