一、发布、查看探店笔记
保存blog
@PostMapping public Result saveBlog(@RequestBody Blog blog) { // 获取登录用户 UserDTO user = UserHolder.getUser(); blog.setUserId(user.getId()); // 保存探店博文 blogService.save(blog); // 返回id return Result.ok(blog.getId()); }
上传图片的代码
@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); } }
查看探店笔记
随便点击一张图片,查看发送的请求
请求网址: http://localhost:8080/api/blog/6请求方法: GET
是BlogController
下的方法,请求方式为GET,那我们直接来编写对应的方法
@GetMapping("/{id}") public Result queryById(@PathVariable Integer id){ return blogService.queryById(id); }
@Override public Result queryById(Integer id) { Blog blog = getById(id); if (blog == null) { return Result.fail("评价不存在或已被删除"); } queryBlogUser(blog); return Result.ok(blog); } private void queryBlogUser(Blog blog) { Long userId = blog.getUserId(); User user = userService.getById(userId); blog.setName(user.getNickName()); blog.setIcon(user.getIcon()); }
将queryHotBlog也修改一下,原始代码将业务逻辑写到了Controller中,修改后的完整代码如下
@RestController @RequestMapping("/blog") public class 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()); } @PutMapping("/like/{id}") public Result likeBlog(@PathVariable("id") Long id) { // 修改点赞数量 blogService.update() .setSql("liked = liked + 1").eq("id", id).update(); return Result.ok(); } @GetMapping("/of/me") public Result queryMyBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) { // 获取登录用户 UserDTO user = UserHolder.getUser(); // 根据用户查询 Page<Blog> page = blogService.query() .eq("user_id", user.getId()).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE)); // 获取当前页数据 List<Blog> records = page.getRecords(); return Result.ok(records); } @GetMapping("/hot") public Result queryHotBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) { return blogService.queryHotBlog(current); } @GetMapping("/{id}") public Result queryById(@PathVariable Integer id){ return blogService.queryById(id); } }
@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(); // 查询用户 records.forEach(this::queryBlogUser); return Result.ok(records); } @Override public Result queryById(Integer id) { Blog blog = getById(id); if (blog == null) { return Result.fail("评价不存在或已被删除"); } queryBlogUser(blog); return Result.ok(blog); } private void queryBlogUser(Blog blog) { Long userId = blog.getUserId(); User user = userService.getById(userId); blog.setName(user.getNickName()); blog.setIcon(user.getIcon()); } }
思考:实体之间由id属性关联(作为查询实体信息入口)。将关联实体id写入自己的属性,将那个实体的给出的属性,定义到类,但不写入表。
当具体要用到某个实体信息时,此时再根据id查询赋给此实体。并进行返回此时的实体。
【举例】:user实体、blog实体。
前端要展示blog实体,同时,发布者(user实体)的icon、nickName。
将userId、userIcon、userName作为属性写入blog结构。
每次查询时,进行涉及关联的实体信息查询,赋值。不查询,则没有必要进行,关联实体的查询与赋值。
注:此处关联指,因为前端展示需求,而关联起来、涉及不同实体信息捆绑返回的关系。
二、点赞功能
点击点赞按钮,查看发送的请求
请求网址: http://localhost:8080/api/blog/like/4
请求方法: PUT
@PutMapping("/like/{id}") public Result likeBlog(@PathVariable("id") Long id) { // 修改点赞数量 blogService.update().setSql("liked = liked + 1").eq("id", id).update(); return Result.ok(); }
- 问题分析:会导致一个用户无限点赞,不合理
- 问题的原因,我们现在的逻辑,发起请求只是给数据库+1,所以才会出现这个问题
- 需求:
- 同一个用户只能对同一篇笔记点赞一次,再次点击则取消点赞
- 如果当前用户已经点赞,则点赞按钮高亮显示(前端已实现,判断字段Blog类的isLike属性)
- 实现步骤:
- 修改点赞功能,利用Redis中的set集合来判断是否点赞过,未点赞则点赞数+1,已点赞则点赞数-1
- 修改根据id查询的业务,判断当前登录用户是否点赞过,赋值给isLike字段
- 修改分页查询Blog业务,判断当前登录用户是否点赞过,赋值给isLike字段
修改一下
@PutMapping("/like/{id}") public Result likeBlog(@PathVariable("id") Long id) { return blogService.likeBlog(id); }
@Override public Result likeBlog(Long id) { //1. 获取当前用户信息 Long userId = UserHolder.getUser().getId(); //2. 如果当前用户未点赞,则点赞数 +1,同时将用户加入set集合 String key = BLOG_LIKED_KEY + id; Boolean isLiked = stringRedisTemplate.opsForSet().isMember(key, userId.toString()); if (BooleanUtil.isFalse(isLiked)) { //点赞数 +1 boolean success = update().setSql("liked = liked + 1").eq("id", id).update(); //将用户加入set集合 if (success) { stringRedisTemplate.opsForSet().add(key, userId.toString()); } //3. 如果当前用户已点赞,则取消点赞,将用户从set集合中移除 }else { //点赞数 -1 boolean success = update().setSql("liked = liked - 1").eq("id", id).update(); if (success){ //从set集合移除 stringRedisTemplate.opsForSet().remove(key, userId.toString()); } } return Result.ok(); }View Code
思考:点赞还是取消点赞,对应到更新此博客表的liked字段的加减1操作。是通过过去的状态(数据库保存的数据状态值)进行流程方向的选择的。
如果只有两种状态,可以使用redis的set集合展现存在状态。
展示点赞、未点赞状态:页面上还不能立即显示点赞完毕的后果,我们还需要修改查询Blog业务,判断Blog是否被当前用户点赞过
1点赞功能使用者:当前用户。
2如何展示点赞状态:当请求queryblog的时候,需返回blog的点赞状态。
liked状态根据:通过当前用户id查询redis的set集合,根据是否存在,返回此blog实体前,赋给对应liked值。
@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 -> { queryBlogUser(blog); //追加判断blog是否被当前用户点赞,逻辑封装到isBlogLiked方法中 isBlogLiked(blog); }); return Result.ok(records); } @Override public Result queryById(Integer id) { Blog blog = getById(id); if (blog == null) { return Result.fail("评价不存在或已被删除"); } queryBlogUser(blog); //追加判断blog是否被当前用户点赞,逻辑封装到isBlogLiked方法中 isBlogLiked(blog); return Result.ok(blog); } private void isBlogLiked(Blog blog) { //1. 获取当前用户信息 Long userId = UserHolder.getUser().getId(); //2. 判断当前用户是否点赞 String key = BLOG_LIKED_KEY + blog.getId(); Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString()); //3. 如果点赞了,则将isLike设置为true blog.setIsLike(BooleanUtil.isTrue(isMember)); }View Code
三、点赞排行榜
- 当我们点击探店笔记详情页面时,应该按点赞顺序展示点赞用户,比如显示最早点赞的TOP5,形成点赞排行榜,就跟QQ空间发的说说一样,可以看到有哪些人点了赞
- 之前的点赞是放到Set集合中,但是Set集合又不能排序,所以这个时候,我们就可以改用SortedSet(Zset)
由于ZSet没有isMember方法,所以这里只能通过查询score来判断集合中是否有该元素,如果有该元素,则返回值是对应的score,如果没有该元素,则返回值为null。
逻辑未改变,存储结构变了,对应结构下的能使用的方法变了。
点赞功能前加一个判断逻辑:用户是否登录(从userHolder是否能拿到user),不能则return结束逻辑。
private void isBlogLiked(Blog blog) { //1. 获取当前用户信息 UserDTO userDTO = UserHolder.getUser(); //当用户未登录时,就不判断了,直接return结束逻辑 if (userDTO == null) { return; } //2. 判断当前用户是否点赞 String key = BLOG_LIKED_KEY + blog.getId(); Double score = stringRedisTemplate.opsForZSet().score(key, userDTO.getId().toString()); blog.setIsLike(score != null); }
那我们继续来完善显示点赞列表功能,查看浏览器请求,这个请求目前应该是404的,因为我们还没有写,他需要一个list返回值,显示top5点赞的用户
请求网址: http://localhost:8080/api/blog/likes/4
请求方法: GET
在Controller层中编写对应的方法,点赞查询列表,具体逻辑写到BlogServiceImpl中
@GetMapping("/likes/{id}") public Result queryBlogLikes(@PathVariable Integer id){ return blogService.queryBlogLikes(id); }
修改BlogServiceImpl
由于ZSet没有isMember方法,所以这里只能通过查询score来判断集合中是否有该元素,如果有该元素,则返回值是对应的score,如果没有该元素,则返回值为null
@Override public Result queryBlogLikes(Integer id) { String key = BLOG_LIKED_KEY + id; //zrange key 0 4 查询zset中前5个元素 Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4); //如果是空的(可能没人点赞),直接返回一个空集合 if (top5 == null || top5.isEmpty()) { return Result.ok(Collections.emptyList()); } List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList()); //将ids使用`,`拼接,SQL语句查询出来的结果并不是按照我们期望的方式进行排 //所以我们需要用order by field来指定排序方式,期望的排序方式就是按照查询出来的id进行排序 String idsStr = StrUtil.join(",", ids); //select * from tb_user where id in (ids[0], ids[1] ...) order by field(id, ids[0], ids[1] ...) List<UserDTO> userDTOS = userService.query().in("id", ids) .last("order by field(id," + idsStr + ")") .list().stream() .map(user -> BeanUtil.copyProperties(user, UserDTO.class)) .collect(Collectors.toList()); return Result.ok(userDTOS); }
四、关注功能
当我们进入到笔记详情页面时,会发送一个请求,判断当前登录用户是否关注了笔记博主
请求网址: http://localhost:8080/api/follow/or/not/2
请求方法: GET
当我们点击关注按钮时,会发送一个请求,实现关注/取关
请求网址: http://localhost:8080/api/follow/2/true
请求方法: PUT
关注是User之间的关系,是博主与粉丝的关系,数据库中有一张tb_follow表来标示
@Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("tb_follow") public class Follow implements Serializable { private static final long serialVersionUID = 1L; /** * 主键 */ @TableId(value = "id", type = IdType.AUTO) private Long id; /** * 用户id */ private Long userId; /** * 关联的用户id */ private Long followUserId; /** * 创建时间 */ private LocalDateTime createTime; }View Code
Controller层中编写对应的两个方法
@RestController @RequestMapping("/follow") public class FollowController { @Resource private IFollowService followService; //判断当前用户是否关注了该博主 @GetMapping("/or/not/{id}") public Result isFollow(@PathVariable("id") Long followUserId) { return followService.isFollow(followUserId); } //实现取关/关注 @PutMapping("/{id}/{isFollow}") public Result follow(@PathVariable("id") Long followUserId, @PathVariable("isFollow") Boolean isFellow) { return followService.follow(followUserId,isFellow); } }
@Service public class FollowServiceImpl extends ServiceImpl<FollowMapper, Follow> implements IFollowService { @Override public Result isFollow(Long followUserId) { //获取当前登录的userId Long userId = UserHolder.getUser().getId(); LambdaQueryWrapper<Follow> queryWrapper = new LambdaQueryWrapper<>(); //查询当前用户是否关注了该笔记的博主 queryWrapper.eq(Follow::getUserId, userId).eq(Follow::getFollowUserId, followUserId); //只查询一个count就行了 int count = this.count(queryWrapper); return Result.ok(count > 0); } @Override public Result follow(Long followUserId, Boolean isFellow) { //获取当前用户id Long userId = UserHolder.getUser().getId(); //判断是否关注 if (isFellow) { //关注,则将信息保存到数据库 Follow follow = new Follow(); follow.setUserId(userId); follow.setFollowUserId(followUserId); save(follow); } else { //取关,则将数据从数据库中移除 LambdaQueryWrapper<Follow> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Follow::getUserId, userId).eq(Follow::getFollowUserId, followUserId); remove(queryWrapper); } return Result.ok(); } }View Code 比较点赞取消点赞、关注取消关注功能,发现都是根据所传数据,1设置查找条件,2根据结果,确定数据记录变更(添/删)操作。 而展示功能,则是对数据的查,最后传回一个boolean值,或封装在实体里,传回实体。
五、共同关注
点击用户头像,进入到用户详情页,可以查看用户发布的笔记,和共同关注列表
查看发送的请求
检测NetWork选项卡,查看发送的请求
查询用户信息
请求网址: http://localhost:8080/api/user/2
请求方法: GET
查看共同关注
请求网址: http://localhost:8080/api/follow/common/undefined
请求方法: GET
编写查询用户信息方法=user转userDTO返回
@GetMapping("/{id}") public Result queryById(@PathVariable("id") Long userId) { // 查询详情 User user = userService.getById(userId); if (user == null) { // 没有详情,应该是第一次查看详情 return Result.ok(); } UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class); // 返回 return Result.ok(userDTO); }
重启服务器,现在可以看到用户信息,但是不能看到用户发布的笔记信息,查看NetWork检测的请求,我们还需要完成这个需求
请求网址: http://localhost:8080/api/blog/of/user?&id=2¤t=1
请求方法: GET
编写查询用户笔记方法=分页查询
@GetMapping("/of/user") public Result queryBlogByUserId(@RequestParam(value = "current", defaultValue = "1") Integer current, @RequestParam("id") Long id) { LambdaQueryWrapper<Blog> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Blog::getUserId, id); Page<Blog> pageInfo = new Page<>(current, SystemConstants.MAX_PAGE_SIZE); blogService.page(pageInfo, queryWrapper); List<Blog> records = pageInfo.getRecords(); return Result.ok(records); } //下面这是老师的代码,上面为另一个方法 // BlogController 根据id查询博主的探店笔记 @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); }View Code
更改关注关系存储结构
@Resource private StringRedisTemplate stringRedisTemplate; @Override public Result follow(Long followUserId, Boolean isFellow) { //获取当前用户id Long userId = UserHolder.getUser().getId(); String key = "follows:" + userId; //判断是否关注 if (isFellow) { //关注,则将信息保存到数据库 Follow follow = new Follow(); follow.setUserId(userId); follow.setFollowUserId(followUserId); //如果保存成功 boolean success = save(follow); //则将数据也写入Redis if (success) { stringRedisTemplate.opsForSet().add(key, followUserId.toString()); } } else { //取关,则将数据从数据库中移除 LambdaQueryWrapper<Follow> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Follow::getUserId, userId).eq(Follow::getFollowUserId, followUserId); //如果取关成功 boolean success = remove(queryWrapper); //则将数据也从Redis中移除 if (success){ stringRedisTemplate.opsForSet().remove(key,followUserId.toString()); } } return Result.ok(); }View Code
实现共同关注代码
@GetMapping("/common/{id}") public Result followCommons(@PathVariable Long id){ return followService.followCommons(id); }
@Override public Result followCommons(Long id) { //获取当前用户id Long userId = UserHolder.getUser().getId(); String key1 = "follows:" + id; String key2 = "follows:" + userId; //对当前用户和博主用户的关注列表取交集 Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key1, key2); if (intersect == null || intersect.isEmpty()) { //无交集就返回个空集合 return Result.ok(Collections.emptyList()); } //将结果转为list List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList()); //之后根据ids去查询共同关注的用户,封装成UserDto再返回 List<UserDTO> userDTOS = userService.listByIds(ids).stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList()); return Result.ok(userDTOS); }
标签:return,--,Redis,public,blog,Result,点赞,id From: https://www.cnblogs.com/fengok/p/17877558.html