5.1 发布和查看探店笔记
5.1.1 发布探店笔记
这块代码黑马已经完成了,在发布探店笔记界面,有两块内容是需要上传的。一是笔记内容,二是笔记配图。其中笔记配图部分黑马使用的是上传到本地前端服务器上面的。我我觉得可以将图片文件发布在阿里云的OSS存储也行,该功能等后续会完成。等黑马点评后端部分完成后、再去分析黑马写的前端代码。然后再将该功能实现。
文件上传功能实现
在系统常量中修改文件上传的地址(改成你自己的)
// 图片上传路径 public static final String IMAGE_UPLOAD_DIR = "D:\\software\\hm-dianping-nginx\\nginx-1.18.0\\html\\hmdp\\imgs";
@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);
}
}
private String createNewFileName(String originalFilename) {
// 获取后缀
String suffix = StrUtil.subAfter(originalFilename, ".", true);
// 生成目录
String name = UUID.randomUUID().toString();
int hash = name.hashCode();
int d1 = hash & 0xF;
int d2 = (hash >> 4) & 0xF;
// 判断目录是否存在
File dir = new File(SystemConstants.IMAGE_UPLOAD_DIR, StrUtil.format("/blogs/{}/{}", d1, d2));
if (!dir.exists()) {
dir.mkdirs();
}
// 生成文件名
return StrUtil.format("/blogs/{}/{}/{}.{}", d1, d2, name, suffix);
}
笔记发布功能实现
@PostMapping
public Result saveBlog(@RequestBody Blog blog) {
// 获取登录用户
UserDTO user = UserHolder.getUser();
blog.setUserId(user.getId());
// 保存探店博文
blogService.save(blog);
// 返回id
return Result.ok(blog.getId());
}
发布功能测试
5.1.2 查看探店笔记
添加TableField注解表示这三个字段不是数据库中的字段。我们查看探店笔记时,需要将创作者的信息也显示出来,但是又不能显示过多暴露,只需要有头像、姓名之类的就好了。
/** * 用户图标 */ @TableField(exist = false) private String icon; /** * 用户姓名 */ @TableField(exist = false) private String name;
查看探店笔记代码实现
/**
* 根据id查询探店笔记
* @param id
* @return
*/
@GetMapping("/{id}")
public Result queryBlogById(@PathVariable("id") Long id) {
return blogService.queryBlogById(id);
}
/**
* 根据id查博客笔记
* @param id
* @return
*/
@Override
public Result queryBlogById(Long id) {
//1. 查询blog
Blog blog = getById(id);
if(blog==null){
return Result.fail("博客不存在");
}
//2.查用户
queryBlogUser(blog);
return Result.ok(blog);
}
/**
* 复用方法封装
* @param blog
*/
private void queryBlogUser(Blog blog){
Long userId = blog.getUserId();
User user = userService.getById(userId);
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
}
查看功能测试
5.2 点赞功能实现
5.2.1 当前点赞功能存在的问题
不校验点赞用户信息,一个人可以无限刷赞
/**
* 修改点赞数量
* @param id
* @return
*/
@PutMapping("/like/{id}")
public Result likeBlog(@PathVariable("id") Long id) {
// 修改点赞数量
// update tb_blog set liked liked + 1 where id = #{id}
blogService.update()
.setSql("liked = liked + 1").eq("id", id).update();
return Result.ok();
}
5.2.2 完善点赞功能需求与实现步骤
需求:
1. 同一用户只能点赞一次,再次点击即取消点赞
2. 如果当前用户已经点赞,则点赞按钮高亮显示(isLike 告知前端即可)
步骤:
1. 给Blog类添加一个isList字段,标记当前用户是否点赞
2. 修改点赞功能,利用Redis的set集合特性存储当前笔记的点赞用户id,用于判断该用户是否给笔记点过赞,为点过则赞 +1 ,否则赞-1
3. 在根据id查询Blog业务时,就判断当前登录用户有无点赞记录,赋值给isList字段.
4. 在分页查询Blog业务时,也去判断并赋值
5.2.3 点赞功能实现
添加一个isList字段
/**
* 是否点赞过了
*/
@TableField(exist = false)
private Boolean isLike;
修改点赞功能
/**
* 点赞
* @param id
* @return
*/
@Override
public Result likeBlog(Long id) {
//1.判断当前用户有没点赞
Long userId = UserHolder.getUser().getId();
String key = RedisConstants.BLOG_LIKED_KEY + id;
Boolean isMember = stringRedisTemplate.opsForSet().isMember(key,userId.toString());
if(BooleanUtil.isFalse(isMember)){
//2.更新数据库信息 点赞+1
boolean isSuccess = update().setSql("liked = liked + 1").eq("id",id).update();
//3. 保存用户到Redis的set集合
if(isSuccess){
stringRedisTemplate.opsForSet().add(key,userId.toString());
}
}else{
//4. 点赞-1
boolean isSuccess = update().setSql("liked = liked - 1").eq("id",id).update();
//5。 移除Redis
if(isSuccess){
stringRedisTemplate.opsForSet().remove(key,userId.toString());
}
}
return Result.ok();
}
添加判断功能
/**
* 查询点赞状态
* @param blog
*/
private void isBlogLiked(Blog blog) {
Long userId = UserHolder.getUser().getId();
String key = RedisConstants.BLOG_LIKED_KEY + blog.getId();
Boolean isMember = stringRedisTemplate.opsForSet().isMember(key,userId.toString());
blog.setIsLike(BooleanUtil.isTrue(isMember));
}
/**
* 查多个
* @param current
* @return
*/
@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 -> {
this.queryBlogUser(blog);
// 查询点赞状态
this.isBlogLiked(blog);
});
return Result.ok(records);
}
/**
* 根据id查博客笔记
* @param id
* @return
*/
@Override
public Result queryBlogById(Long id) {
//1. 查询blog
Blog blog = getById(id);
if(blog==null){
return Result.fail("博客不存在");
}
//2.查用户
queryBlogUser(blog);
//3. 查询点赞状态
isBlogLiked(blog);
return Result.ok(blog);
}
5.2.4 点赞功能测试
5.3 点赞排行榜功能实现
5.3.1 数据结构选型说明
需求是能排序且去重的排行榜功能,因此我们选用zset集合来实现这些需求
5.3.2 排行功能实现
修改点赞及点赞状态逻辑
/**
* 查询点赞状态
* @param blog
*/
private void isBlogLiked(Blog blog) {
Long userId = UserHolder.getUser().getId();
String key = RedisConstants.BLOG_LIKED_KEY + blog.getId();
Double score = stringRedisTemplate.opsForZSet().score(key,userId.toString());
blog.setIsLike(score != null);
}
/**
* 点赞
* @param id
* @return
*/
@Override
public Result likeBlog(Long id) {
//1.判断当前用户有没点赞
Long userId = UserHolder.getUser().getId();
String key = RedisConstants.BLOG_LIKED_KEY + id;
Double score = stringRedisTemplate.opsForZSet().score(key,userId.toString());
if(score == null){
//2.更新数据库信息 点赞+1
boolean isSuccess = update().setSql("liked = liked + 1").eq("id",id).update();
//3. 保存用户到Redis的set集合
if(isSuccess){
stringRedisTemplate.opsForZSet().add(key,userId.toString(),System.currentTimeMillis());
}
}else{
//4. 点赞-1
boolean isSuccess = update().setSql("liked = liked - 1").eq("id",id).update();
//5。 移除Redis
if(isSuccess){
stringRedisTemplate.opsForZSet().remove(key,userId.toString());
}
}
return Result.ok();
}
实现排行榜功能
ZRANGE 查询范围内的元素
/**
* 查询点赞排行榜
* @param id
* @return
*/
@Override
public Result queryBlogLikes(Long id) {
// 使用zrange查询TOP5
Set<String> top5StrIds = stringRedisTemplate.opsForZSet().range(RedisConstants.BLOG_LIKED_KEY + id, 0, 4);
if(top5StrIds==null || top5StrIds.isEmpty()){
return Result.ok(Collections.emptyList());
}
List<Long> ids = top5StrIds.stream().map(Long::valueOf).collect(Collectors.toList());
// 根据用户id查询数据库
List<User> users = userService.listByIds(ids);
// DTO
List<UserDTO> userDTOList = users
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
return Result.ok(userDTOList);
}
5.3.3 排行功能测试
优化
发现查询点赞排行榜的顺序不正确——原因:数据库in使用时,无法保障顺序
添加ORDER BY FIELD 自定义顺序
// 根据用户id查询数据库 // 自定义sql查询 List<User> users = userService.query().in("id", ids).last("ORDER BY FIELD(id," + StrUtil.join(",", ids) + ")").list();
5.4 查看用户笔记列表功能实现
在点击用户头像进入首页时,可以查询该用户的博客列表进行展示


/**
* 根据用户id查询探店笔记列表
* @param current
* @param id
* @return
*/
@GetMapping("/of/user")
public Result queryBlogByUserid(
@RequestParam(value = "current",defaultValue = "1") Integer current,
@RequestParam("id") Long id) {
// 根据用户查询
Page<Blog> page = blogService.query()
.eq("user_id", id)
.page(new Page<>(current,SystemConstants.MAX_PAGE_SIZE));
// 获取当前页数据
List<Blog> records = page.getRecords();
return Result.ok(records);
}