文章目录
达人探店
达人探店-发布探店笔记
发布探店笔记
探店笔记类似点评网站的评价,往往是图文结合。对应的表有两个:
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上或者是云存储上。
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());
}
}
达人探店-查看探店笔记
实现查看发布探店笔记的接口
实现代码
BlogServiceImpl
@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);
}
//提取代码封装方法快捷键 Ctrl + Alt + M
private void queryBlogUser(Blog blog) {
Long userId = blog.getUserId();
User user = userService.getById(userId);
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
}
解决店铺不存在
-
点击查看笔记未看到店铺:
-
查看请求定位代码问题:
-
找到问题代码:
原因是redis中无店铺信息,直接返回null
-
解决:调用之前的
saveShop2Redis
方法//在 queryById 里调用该方法,将店铺信息保存在redis中 try { saveShop2Redis(id,36000L); } catch (InterruptedException e) { log.error(e.getMessage()); }
达人探店-点赞功能
初始代码
@GetMapping("/likes/{id}")
public Result queryBlogLikes(@PathVariable("id") Long id) {
//修改点赞数量
blogService.update().setSql("liked = liked +1 ").eq("id",id).update();
return Result.ok();
}
问题分析:这种方式会导致一个用户无限点赞,明显是不合理的
造成这个问题的原因是,我们现在的逻辑,发起请求只是给数据库+1,所以才会出现这个问题
完善点赞功能
需求:
- 同一个用户只能点赞一次,再次点击则取消点赞
- 如果当前用户已经点赞,则点赞按钮高亮显示(前端已实现,判断字段Blog类的isLike属性)
实现步骤:
- 给Blog类中添加一个isLike字段,标示是否被当前用户点赞
- 修改点赞功能,利用Redis的set集合判断是否点赞过,未点赞过则点赞数+1,已点赞过则点赞数-1
- 修改根据id查询Blog的业务,判断当前登录用户是否点赞过,赋值给isLike字段
- 修改分页查询Blog业务,判断当前登录用户是否点赞过,赋值给isLike字段
为什么采用set集合
:
因为我们的数据是不能重复的,当用户操作过之后,无论他怎么操作,都是最多点赞一次
-
在Blog 添加一个字段
//注解表明此字段不在数据库表中 @TableField(exist = false) private Boolean isLike;
-
修改代码
BlogServiceImpl
@Override public Result likeBlog(Long id) { // 1.获取登录用户 Long userId = UserHolder.getUser().getId(); // 2.判断当前登录用户是否已经点赞 String key = "blog:liked:" + id.toString(); Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString()); // 3.如果未点赞,可以点赞 if (BooleanUtil.isFalse(isMember)) { // 3.1.数据库点赞数+1 boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update(); if (BooleanUtil.isTrue(isSuccess)) { // 3.2.保存用户到redis set集合 stringRedisTemplate.opsForSet().add(key, userId.toString()); } } else { // 4.如果已点赞,取消点赞 boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update(); // 4.1.数据库点赞数-1 if (BooleanUtil.isTrue(isSuccess)) { // 4.2.把用户从redis set集合移除 stringRedisTemplate.opsForSet().remove(key, userId.toString()); } } return Result.ok(); }
-
判断当前登录用户是否已经点赞
private void isBlogLiked(Blog blog) { // 1.获取登录用户 Long userId = UserHolder.getUser().getId(); // 2.判断当前登录用户是否已经点赞 String key = "blog:liked:" + blog.getId().toString(); Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString()); blog.setIsLike(BooleanUtil.isTrue(isMember)); }
-
修改
queryHotBlog
@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); }
-
到目前为止,完整代码如下
package com.hmdp.service.impl; import cn.hutool.core.util.BooleanUtil; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.hmdp.dto.Result; 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.List; /** * <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 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); } //提取代码封装方法快捷键 Ctrl + Alt + M 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 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) { // 1.获取登录用户 Long userId = UserHolder.getUser().getId(); // 2.判断当前登录用户是否已经点赞 String key = "blog:liked:" + blog.getId().toString(); Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString()); blog.setIsLike(BooleanUtil.isTrue(isMember)); } @Override public Result likeBlog(Long id) { // 1.获取登录用户 Long userId = UserHolder.getUser().getId(); // 2.判断当前登录用户是否已经点赞 String key = "blog:liked:" + id.toString(); Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString()); // 3.如果未点赞,可以点赞 if (BooleanUtil.isFalse(isMember)) { // 3.1.数据库点赞数+1 boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update(); if (BooleanUtil.isTrue(isSuccess)) { // 3.2.保存用户到redis set集合 stringRedisTemplate.opsForSet().add(key, userId.toString()); } } else { // 4.如果已点赞,取消点赞 boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update(); // 4.1.数据库点赞数-1 if (BooleanUtil.isTrue(isSuccess)) { // 4.2.把用户从redis set集合移除 stringRedisTemplate.opsForSet().remove(key, userId.toString()); } } return Result.ok(); } }
达人探店-点赞排行榜
在探店笔记的详情页面,应该把给该笔记点赞的人显示出来,比如最早点赞的TOP5,形成点赞排行榜:
之前的点赞是放到set集合,但是set集合是不能排序的,所以这个时候,咱们可以采用一个可以排序的set集合,就是咱们的sortedSet
我们接下来来对比一下这些集合的区别是什么
-
所有点赞的人,需要是唯一的,所以我们应当使用set或者是sortedSet
-
其次我们需要排序,就可以直接锁定使用sortedSet啦
修改代码
BlogServiceImpl
点赞逻辑代码
@Override
public Result likeBlog(Long id) {
// 1.获取登录用户
Long userId = UserHolder.getUser().getId();
// 2.判断当前登录用户是否已经点赞
String key = BLOG_LIKED_KEY + id.toString();
Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
// Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
// 3.如果未点赞,可以点赞
if (score == null) {
// if (BooleanUtil.isFalse(isMember)) {
// 3.1.数据库点赞数+1
boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update();
if (BooleanUtil.isTrue(isSuccess)) {
// 3.2.保存用户到redis Zset集合,时间戳
stringRedisTemplate.opsForZSet().add(key, userId.toString(),System.currentTimeMillis());
// stringRedisTemplate.opsForSet().add(key, userId.toString());
}
} else {
// 4.如果已点赞,取消点赞
boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();
// 4.1.数据库点赞数-1
if (BooleanUtil.isTrue(isSuccess)) {
// 4.2.把用户从redis set集合移除
stringRedisTemplate.opsForZSet().remove(key, userId.toString());
// stringRedisTemplate.opsForSet().remove(key, userId.toString());
}
}
return Result.ok();
}
private void isBlogLiked(Blog blog) {
// 1.获取登录用户
Long userId = UserHolder.getUser().getId();
// 2.判断当前登录用户是否已经点赞
String key = BLOG_LIKED_KEY + blog.getId().toString();
Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
// Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
blog.setIsLike(score != null);
}
点赞列表查询列表
BlogController
@GetMapping("/likes/{id}")
public Result queryBlogLikes(@PathVariable("id") Long id) {
return blogService.queryBlogLikes(id);
}
BlogService
@Override
public Result queryBlogLikes(Long id) {
String key = BLOG_LIKED_KEY + id.toString();
// 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.解析其中的用户id
List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
String idStr = StrUtil.join(",", ids);
// 3.根据用户id查询用户 WHERE id IN ( 5 , 1 ) ORDER BY FIELD(id, 5, 1)
List<UserDTO> userDTOS = userService.query().in("id", ids).list()
.stream()
.map(
user -> BeanUtil.copyProperties(user, UserDTO.class)
).collect(Collectors.toList());
// 4.返回
return Result.ok(userDTOS);
测试
已登录用户点赞可正常显示
问题
重新打开窗口显示服务器异常(未登录)
定位问题代码
修改bug
private void isBlogLiked(Blog blog) {
// 1.获取登录用户
UserDTO userDTO = UserHolder.getUser();
if(userDTO == null){
// 用户未登录,无需查询是否点赞
return;
}
// Long userId = UserHolder.getUser().getId();
// 2.判断当前登录用户是否已经点赞
Long userId = userDTO.getId();
String key = BLOG_LIKED_KEY + blog.getId().toString();
Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
// Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
blog.setIsLike(score != null);
}
测试通过!
当第二个人点赞后发现排第一个前面
-
问题:
mysql的in查询导致查询顺序和结果不一致
-
原因
使用了’in’
-
mysql测试
SELECT id,phone,password,nick_name,icon,create_time,update_time FROM tb_user WHERE (id IN (5,1))
-
解决办法
SELECT id,phone,password,nick_name,icon,create_time,update_time FROM tb_user WHERE (id IN (5,1)) Order bY FIELD(id,5,1)
-
代码修改
@Override public Result queryBlogLikes(Long id) { String key = BLOG_LIKED_KEY + id.toString(); // 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.解析其中的用户id List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList()); String idStr = StrUtil.join(",", ids); // 3.根据用户id查询用户 WHERE id IN ( 5 , 1 ) ORDER BY FIELD(id, 5, 1) // List<UserDTO> userDTOS = userService.listByIds(ids); 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); }
-
测试
-
完美解决
达人探店-总结
达人探店功能
- 分享探店图文
- 点赞功能
- 点赞排行榜
java stream详解