【黑马点评】达人探店
1、发布探店笔记
探店笔记类似点评网站的评价,往往是图文结合。对应的表有两个:
tb_blog:探店笔记表,包含笔记中的标题、文字、图片等
tb_blog_comments:其他用户对探店笔记的评价
具体发布流程
根据找到对应的上传接口
http://localhost:8080/api/blog
对应数据库表:tb_blog
1)写笔记上传图片
对应的类是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);
}
}
这里需要修改文件上传的地址,这里是要上传到前端服务器nginx上
new File(SystemConstants.IMAGE_UPLOAD_DIR, fileName)
找到imgs所在的路径
D:\baidu\Down\nginx-1.18.0\html\hmdp\imgs
IMAGE_UPLOAD_DIR = "D:\\baidu\\Down\\nginx-1.18.0\\html\\hmdp\\imgs\\";
在实际开发中图片一般会放在nginx上或者是云存储上。
图片上传的路径和流程,文件保存路径
2)发布笔记
BlogController
@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());
}
这里注意需要修改shop_id默认值为10,在数据库中修改,不然会报错
发布成功后页面会自动跳转到你的个人页面,笔记会出现在你的个人页面中
还可以在首页中看到自己发布的笔记
数据库中插入成功
2、实现查看发布的笔记
1)Controller层业务转移
BlogController
将controller中的业务转移到service中
@GetMapping("/hot")
public Result queryHotBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) {
return blogService.queryHotBlog(current);
}
@GetMapping("/{id}")
public Result queryBlog(@PathVariable("id") long id){
return blogService.queryBlogById(id);
}
修改处
2)service层实现方法
serviceimpl实现类
实现两个方法
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {
@Resource
private IUserService userService;
@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();
// 查询blog有关用户
records.forEach(this::queryBlogUser);
return Result.ok(records);
}
@Override
public Result queryBlogById(long id) {
//1、查询blog
Blog blog = getById(id);
if (blog==null) {
return Result.fail("笔记不存在!");
}
//2、查询blog有关用户
queryBlogUser(blog);
return Result.ok(blog);
}
}
可以看到这里查询blog有关的用户这段代码逻辑在两个方法中都存在,可以将这段代码逻辑进行封装
3)封装查询blog
快捷键==Ctrl+Alt+M
查询blog相关用户统一由一个功能来实现
// 查询blog有关用户逻辑封装
private void queryBlogUser(Blog blog) {
Long userId = blog.getUserId();
User user = userService.getById(userId);
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
}
查看笔记内容成功
3、点赞功能
实际业务中,对哪个笔记点赞,笔记点赞量最多的,就会将这个笔记推在首页的前面
找到点赞对应的接口
http://localhost:8080/api/blog/like/?
对应的Controller类是BlogController
@PutMapping("/like/{id}")
public Result likeBlog(@PathVariable("id") Long id) {
// 修改点赞数量
blogService.update()
.setSql("liked = liked + 1").eq("id", id).update();
return Result.ok();
}
这里的like就是点赞数量
这里可以看到我对这个订单进行了点赞,但是如果同一个人对一个笔记进行重复的点赞,那么服务器承担的压力非常大,所以我们应该限制一人一赞
完善点赞功能
需求:
- 同一个用户只能点赞一次,再次点击则取消点赞
- 如果当前用户已经点赞,则点赞按钮高亮显示(前端已实现,判断字段Blog类的isLike属性)
实现步骤:
- 给Blog类中添加一个isLike字段,标示是否被当前用户点赞
- 修改点赞功能,利用Redis的set集合判断是否点赞过,未点赞过则点赞数+1,已点赞过则点赞数-1
- 修改根据id查询Blog的业务,判断当前登录用户是否点赞过,赋值给isLike字段
- 修改分页查询Blog业务,判断当前登录用户是否点赞过,赋值给isLike字段
实现步骤
1)实体类Blog加字段
/**
* 是否点赞过了
*/
@TableField(exist = false)
private Boolean isLike;
2)修改Controller层点赞功能
@PutMapping("/like/{id}")
public Result likeBlog(@PathVariable("id") Long id) {
// 修改点赞数量
return blogService.likeBlog(id);
}
3)service中创建实现likeBlog方法
把笔记的id作为redis中的KEY,点赞的用户的ID作为Value存在set集合中
//service接口
Result likeBlog(Long id);
//service实现类
@Override
public Result likeBlog(Long id) {
//1、获取用户
Long userId = UserHolder.getUser().getId();
//2、判断当前用户是否点赞
String key="blog:liked:"+id;
Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
if (BooleanUtil.isFalse(isMember)) {
//3、如未点赞,可以点赞
//3.1、数据库点赞数+1
boolean isSuccess = update().setSql("liked=liked+1").eq("id", id).update();
//3.2、保存用户到Redis的set集合中
if (isSuccess) {
stringRedisTemplate.opsForSet().add(key,userId.toString());
}
}else {
//4、如果已经点赞。则取消点赞
//4.1、数据库点赞数-1
boolean isSuccess = update().setSql("liked=liked-1").eq("id", id).update();
//4.2、把用户从Redis集合中移除
if (isSuccess) {
stringRedisTemplate.opsForSet().remove(key,userId.toString());
}
}
return Result.ok();
}
4)修改前两个方法
在前面两个方法中都需要查询blog是否被点赞
所以我们修改方法
定义这段相同的逻辑
private void isBlogLiked(Blog blog) {
//1、获取用户
Long userId = UserHolder.getUser().getId();
//2、判断当前用户是否点赞
String key="blog:liked:"+blog.getId();
Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
blog.setIsLike(BooleanUtil.isTrue(isMember));
}
修改方法——加上这段逻辑
queryBlogById方法
queryHotBlog方法
5)测试
这里要注意,要先登录,不然直接报空指针异常
点赞后,图标显示高亮,再次点击数字减一,高亮消失,并且缓存中点赞用户id消失
数据库中数据正常
4、点赞排行榜
一、分析
在探店笔记的详情页面,应该把给该笔记点赞的人显示出来,比如最早点赞的TOP5,形成点赞排行榜:
对应实现接口为
http://localhost:8080/api/blog/like/?
我们选定的集合要满足要存多个元素、唯一、排序
之前的点赞是放到set集合,但是set集合是不能排序的,所以这个时候,咱们可以采用一个可以排序的set集合,就是咱们的sortedSet
我们接下来来对比一下这些集合的区别是什么
所有点赞的人,需要是唯一的,所以我们应当使用set或者是sortedSet
其次我们需要排序,就可以直接锁定使用sortedSet啦
二、选用集合
语法:
ZSOCORE key member
实现:
--创建集合z1,并添加集合元素3个
> ZADD z1 1 m1 2 m2 3 m3
3
--查询集合元素
> ZSCORE z1 m1
1
> ZSCORE z1 m2
2
> ZSCORE z1 m3
3
排行榜范围查询
ZRANGE key min max
实现
--查集合z1前5名
> ZRANGE z1 0 4
m1
m2
m3
三、代码实现
修改点赞功能——likeBlog方法
BlogServiceimpl实现类
1)修改集合类型
将set集合改为Zset集合
likeBlog方法
代码逻辑
- 通过
UserHolder.getUser().getId()
获取当前用户的id。- 判断当前用户是否已经点赞该博客,通过Redis中ZSet的
score
方法判断该用户id是否存在。- 如果该用户未点赞,需要执行以下步骤:
- 数据库点赞数+1,使用EasyMybatis中的
update
对象进行操作。- 将当前用户的id和当前时间戳加入到Redis的ZSet集合中,使得可以记录该用户的点赞时间和支持排序。
- 如果该用户已经点赞,需要执行以下步骤:
- 数据库点赞数-1,使用EasyMybatis中的
update
对象进行操作。- 从Redis的ZSet集合中将当前用户id移除。
而返回值是一个
Result
对象,并且返回码为200,即请求成功。
isBlogLiked方法
2)实现点赞排行榜
@Override
public Result queryBlogLikes(long id) {
String key=BLOG_LIKED_KEY+id;
//1、查询top5(前5名)的点赞用户 zrange key 0 4
Set<String> tops5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
if (tops5==null||tops5.isEmpty()) {
return Result.ok(Collections.emptyList());
}
//2、解析出其中的用户id
List<Long> ids = tops5.stream().map(Long::valueOf).collect(Collectors.toList());
//3、根据用户id查询用户
List<UserDTO> userDTOS = userService.listByIds(ids)
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
//4、返回
return Result.ok(userDTOS);
}
3)Controller层实现接口
BlogController类
@GetMapping("likes/{id}")
public Result queryBlogLikes(@PathVariable("id") long id){
return blogService.queryBlogLikes(id);
}
4)登录问题
首页没登录的时候看不到页面内容,原因是我们这里设置了用户点赞,当前用户没登录就无法查看订单的点赞
我们需要用户不用登录也能查看到内容
BlogServiceImpl类
private void isBlogLiked(Blog blog) {
//1、获取用户
UserDTO user=UserHolder.getUser();
if (user==null) {
//用户未登录,无需查询是否点赞
return;
}
Long userId = UserHolder.getUser().getId();
//2、判断当前用户是否点赞
String key="blog:liked:"+blog.getId();
Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
blog.setIsLike(score!=null);
}
5)效果
可以看到点赞图标出现了点赞用户的头像
查看缓存
我使用另一个账号登陆后点赞后
这里可以发现一个问题:
点赞第一用户的位置被排在了后面
四、问题解决
1)原因
这是数据库的sql语句原因
select ... from user where id in(10,7);
这里用IN的时候查询的结果不会按照你提供的id顺序
这里我先使用的是id=10的用户点赞,而这个用户却出现在后面
2)问题解决
手动指定id的顺序
select ... from user where id in(10,7) order by(id,10,7);
修改queryBlogLikes方法
@Override
public Result queryBlogLikes(long id) {
String key=BLOG_LIKED_KEY+id;
//1、查询top5(前5名)的点赞用户 zrange key 0 4
Set<String> tops5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
if (tops5==null||tops5.isEmpty()) {
return Result.ok(Collections.emptyList());
}
//2、解析出其中的用户id
List<Long> ids = tops5.stream().map(Long::valueOf).collect(Collectors.toList());
String idStr = StrUtil.join(",", ids);
//3、根据用户id查询用户 select ... from user where id in(10,7) order by(id,10,7);
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());
//4、返回
return Result.ok(userDTOS);
}
修改部分
结果看出排行榜功能实现
达人探店功能完结!!!😁😁😁😁