day09-达人探店

科技资讯 投稿 6400 0 评论

day09-达人探店

功能04-达人探店

5.功能04-达人探店

5.1发布&查看探店笔记

5.1.1发布探店笔记

    tb_blog:探店笔记表,包含笔记中的标题、文字、图片等
  1. tb_blog_comments:其他用户对探店笔记的评价
/*表: tb_blog*/
CREATE TABLE `tb_blog` (
  `id` bigint(20 unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `shop_id` bigint(20 NOT NULL COMMENT '商户id',
  `user_id` bigint(20 unsigned NOT NULL COMMENT '用户id',
  `title` varchar(255 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '标题',
  `images` varchar(2048 NOT NULL COMMENT '探店的照片,最多9张,多张以","隔开',
  `content` varchar(2048 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '探店的文字描述',
  `liked` int(8 unsigned DEFAULT '0' COMMENT '点赞数量',
  `comments` int(8 unsigned DEFAULT NULL COMMENT '评论数量',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id` USING BTREE
 ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT

/*表: tb_blog_comments*/
CREATE TABLE `tb_blog_comments` (
  `id` bigint(20 unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_id` bigint(20 unsigned NOT NULL COMMENT '用户id',
  `blog_id` bigint(20 unsigned NOT NULL COMMENT '探店id',
  `parent_id` bigint(20 unsigned NOT NULL COMMENT '关联的1级评论id,如果是一级评论,则值为0',
  `answer_id` bigint(20 unsigned NOT NULL COMMENT '回复的评论id',
  `content` varchar(255 NOT NULL COMMENT '回复的内容',
  `liked` int(8 unsigned DEFAULT NULL COMMENT '点赞数',
  `status` tinyint(1 unsigned DEFAULT NULL COMMENT '状态,0:正常,1:被举报,2:禁止查看',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id` USING BTREE
 ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT

点击首页最下方菜单栏中的“+”按钮,即可发布探店图文:

发布照片和发布笔记这两个功能是分离的。因为上传照片的功能不仅仅是发布笔记时需要用到,其他业务也有需求,因此上传照片是一个独立功能。

(1)上传图片功能

上传的图片其实是放在放在前端服务器中的,这里为了模拟,放在了D盘的前端项目(nginx-1.18.0)的目录下:

(2)发布笔记功能

(3)测试

每次上传图片成功,后端都会返回该图片可访问的图片地址:

5.1.2查看探店笔记

实现查看笔记的接口。需求:点击首页的笔记,可以进入详情页面,实现该页面的查询接口。

    笔记信息
  1. 发布的用户信息(用户id、用户昵称、用户头像)

代码实现

package com.hmdp.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 笔记实体
 *
 * @author 李
 * @version 1.0
 */
@Data
@EqualsAndHashCode(callSuper = false
@Accessors(chain = true
@TableName("tb_blog"
public class Blog implements Serializable {

    private static final long serialVersionUID = 1L;
    
    //主键
    @TableId(value = "id", type = IdType.AUTO
    private Long id;
    
    //商户id
    private Long shopId;
    
    //用户id
    private Long userId;
    
    //用户头像
    @TableField(exist = false
    private String icon;
    
    //用户昵称
    @TableField(exist = false
    private String name;
    
    //是否点赞过
    @TableField(exist = false
    private Boolean isLike;
    
    //标题
    private String title;
    
    //探店的照片,最多9张,使用","隔开
    private String images;
    
    //探店的文字描述
    private String content;
    
    //点赞数量
    private Integer liked;
    
    //评论数量
    private Integer comments;
    
    //创建时间
    private LocalDateTime createTime;
    
    //更新时间
    private LocalDateTime updateTime;
}

(2)BlogMapper.java

package com.hmdp.mapper;

import com.hmdp.entity.Blog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

/**
 *  Mapper 接口
 *
 * @author 李
 * @version 1.0
 */
public interface BlogMapper extends BaseMapper<Blog> {

}

(3)IBlogService.java

package com.hmdp.service;

import com.hmdp.dto.Result;
import com.hmdp.entity.Blog;
import com.baomidou.mybatisplus.extension.service.IService;

/**
 * 服务类
 *
 * @author 李
 * @version 1.0
 */
public interface IBlogService extends IService<Blog> {
    
    Result queryHotBlog(Integer current;//分页查询blog
    
    Result queryBlogById(Long id;//根据id查询blog
}

(4)BlogServiceImpl.java

package com.hmdp.service.impl;

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 org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * 服务实现类
 *
 * @author 李
 * @version 1.0
 */
@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 queryBlogById(Long id {
        //1.查询blog
        Blog blog = getById(id;
        //2.查询blog有关的用户
        if (blog == null {
            return Result.fail("笔记不存在!";
        }
        queryBlogUser(blog;

        return Result.ok(blog;
    }

    public void queryBlogUser(Blog blog {
        Long userId = blog.getUserId(;
        User user = userService.getById(userId;
        blog.setName(user.getNickName(;
        blog.setIcon(user.getIcon(;
    }
}

(5)BlogController.java

package com.hmdp.controller;

import com.hmdp.dto.Result;
import com.hmdp.service.IBlogService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * 前端控制器
 *
 * @author 李
 * @version 1.0
 */
@RestController
@RequestMapping("/blog"
public class BlogController {

    @Resource
    private IBlogService blogService;
    
    @GetMapping("/hot"
    public Result queryHotBlog(
        @RequestParam(value = "current", defaultValue = "1" Integer current {
        return blogService.queryHotBlog(current;
    }

    @GetMapping("/{id}"
    public Result queryBlogById(@PathVariable("id" Long id{
        return blogService.queryBlogById(id;
    }
}

(6)测试:重启项目,点击笔记,可以查看笔记详情

5.2点赞

5.2.1需求分析

需求:

  • 如果当前用户已经点赞,则点赞按钮高亮显示(前端已实现,判断字段Blog类的isLike属性)

    给Blog类中添加一个isLike字段,标识是否被当前用户点赞

  1. blog的id作为key,点赞的用户id作为value

  2. 用于blog详情的点赞显示

  3. 用于一页blog时的所有点赞显示

5.2.2代码实现

(2)修改IBlogService,添加方法声明

Result likeBlog(Long id;

(3)修改BlogServiceImpl

    实现方法likeBlog(:修改点赞功能,利用Redis的set集合判断是否点赞过,未点赞过则点赞数+1,已点赞过则再次点赞时点赞数-1
  1. 修改BlogServiceImpl的queryBlogById(方法和queryHotBlog(,在查询blog信息的同时,查询当前用户有没有点赞过该blog
package com.hmdp.service.impl;

import ...

/**
 * 服务实现类
 *
 * @author 李
 * @version 1.0
 */
@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 -> {
            //查询发布blog的user
            this.queryBlogUser(blog;
            //查询当前用户有没有点赞过该blog
            this.isBlogLiked(blog;
        };
        return Result.ok(records;
    }

    @Override
    public Result queryBlogById(Long id {
        //1.查询blog
        Blog blog = getById(id;
        //2.查询blog有关的用户
        if (blog == null {
            return Result.fail("笔记不存在!";
        }
        queryBlogUser(blog;
        //3.查询blog是否被点赞了
        isBlogLiked(blog;
        return Result.ok(blog;
    }

    private void isBlogLiked(Blog blog {
        //1.获取当前登录用户 
        if (UserHolder.getUser( == null {
        return;//如果当前用户未登录
        }
        Long userId = UserHolder.getUser(.getId(;
        //2.判断当前登录用户是否已经点赞了
        // (去redis的set集合中判断 SISMEMBER key member
        String key = "blog:liked:" + blog.getId(;
        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(;
        if (userId == null {
            return Result.fail("用户未登录";
        }
        //2.判断当前登录用户是否已经点赞了
        // (去redis的set集合中判断 SISMEMBER key member
        String key = "blog:liked:" + id;
        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(;
            //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的set集合中移除
            if (isSuccess {
                stringRedisTemplate.opsForSet(.remove(key, userId.toString(;
            }
        }
        return Result.ok(;
    }

    public void queryBlogUser(Blog blog {
        Long userId = blog.getUserId(;
        User user = userService.getById(userId;
        blog.setName(user.getNickName(;
        blog.setIcon(user.getIcon(;
    }
}

(4)修改BlogController,添加方法likeBlog(

@PutMapping("/like/{id}"
public Result likeBlog(@PathVariable("id" Long id {
    return blogService.likeBlog(id;
}

(5)测试:启动项目,已登录用户第一次点赞时,点赞数+1,图标高亮;第二次点赞时,点赞数-1,图标变回灰色。

如果用户未登录,点赞时则会自动跳转到登录页面:

5.3点赞排行榜

5.3.1需求分析

实现查询点赞排行榜的接口:

之前我们使用的是redis中的Set结构,对于点赞功能来说,要求数据唯一且可方便查找。在此基础上,点赞排行榜功能还要求对数据进行排序,因此我们选用SortedSet结构实现,并对之前的点赞功能进行改造。

zset结构没有判断元素是否存在的命令,但可以查找指定元素的score,根据这个命令,判断指定的元素的score,如果score不存在,则该元素不存在。

  ZSCORE key member
  summary: Get the score associated with the given member in a sorted set  
  since: 1.2.0

例如:

127.0.0.1:6379> ZADD z1 1 m1 2 m2 3 m3
(integer 3
127.0.0.1:6379> ZSCORE z1 m4 #侧面判断m4不存在
(nil
127.0.0.1:6379> ZSCORE z1 m1
"1"

查询排行(比较score则使用 zrange 命令:

127.0.0.1:6379> ZRANGE z1 0 4 #查询排行前5名
1 "m1"
2 "m2"
3 "m3"

5.3.2代码实现

(1)IBlogService增加方法声明queryBlogLikes

public interface IBlogService extends IService<Blog> {
    ...
        
    Result queryBlogLikes(Long id;
}

(2)修改BlogServiceImpl:

    修改之前的点赞功能,将其用到的set结构改为zset结构(修改isBlogLiked和likeBlog方法)
  1. 实现点赞排行功能--queryBlogLikes(
//判断当前用户是否点赞过该blog
private void isBlogLiked(Blog blog {
    //1.获取当前登录用户
    if (UserHolder.getUser( == null {
        return;//如果当前用户未登录
    }
    Long userId = UserHolder.getUser(.getId(;
    //2.判断当前登录用户是否已经点赞了
    String key = BLOG_LIKED_KEY + blog.getId(;
    Double score = stringRedisTemplate.opsForZSet(.score(key, userId.toString(;
    blog.setIsLike(score != null;
}

//进行点赞操作
@Override
public Result likeBlog(Long id {
    //1.获取当前登录用户
    Long userId = UserHolder.getUser(.getId(;
    if (userId == null {
        return Result.fail("用户未登录";
    }
    //2.判断当前登录用户是否已经点赞了
    String key = BLOG_LIKED_KEY + id;
    Double score = stringRedisTemplate.opsForZSet(.score(key, userId.toString(;
    //3.如果未点赞(score为null,证明该用户不存在zset中,即未点赞
    if (score == null {
        //3.1数据库点赞数+1
        boolean isSuccess = update(.setSql("liked=liked+1".eq("id", id.update(;
        //3.2保存用户到redis的zset集合 zadd key value score
        if (isSuccess {
            stringRedisTemplate.opsForZSet(.add(key, userId.toString(, System.currentTimeMillis(;
        }
    } else {//4.如果已经点赞,则取消点赞
        //4.1数据库点赞数-1
        boolean isSuccess = update(.setSql("liked=liked-1".eq("id", id.update(;
        //4.2将用户从redis的zset集合中移除
        if (isSuccess {
            stringRedisTemplate.opsForZSet(.remove(key, userId.toString(;
        }
    }
    return Result.ok(;
}

//根据blogId返回点赞该blog的top5的用户信息
@Override
public Result queryBlogLikes(Long id {
    String key = BLOG_LIKED_KEY + id;
    //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(;
    //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)修改 BlogController,增加方法

@GetMapping("/likes/{id}"
 public Result queryBlogLikes(@PathVariable("id" Long id {
     return blogService.queryBlogLikes(id;
 }

(4)测试:使用不同的用户账号给同一篇探店blog点赞,成功显示点赞的用户信息:

但是从数据库中查找返回的用户顺序却是:1,2,1033,前端显示的用户头像id也是按照1,2,1033的顺序,这显然不符合我们要的顺序。

代码底层发出的sql语句如下:可以发现传入的参数顺序是正确的(1033,1,2),但是返回的数据顺序并不和我们的入参一致:

解决方法:使用 order by 指定排序

(6)重新启动项目:可以看到之前的顺序已经变为真正的顺序了

编程笔记 » day09-达人探店

赞同 (29) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽