多级留言/评论的功能实现——SpringBoot3后端篇

news2024/11/23 22:31:41

目录

  • 功能描述
  • 数据库表设计
  • 后端接口设计
    • 实体类
      • entity 完整实体类
      • dto 封装请求数据
      • dto 封装分页请求数据
      • vo 请求返回数据
    • Controller控制层
    • Service层
      • 接口
      • 实现类
    • Mapper层
    • Mybatis 操作数据库
  • 补充:
    • 返回的数据结构
    • 自动创建实体类

最近毕设做完了,开始来梳理一下功能点,毕竟温故知新嘛

今天先写一下我的多级留言/评论功能数据库和后端是怎么设计的(前端的设计会再写一篇单独的文章)
删除评论还没做,这个功能还没有思路,目前也没有太多时间来设计了,搜了一下感觉挺复杂的,后面有时间了会补上

接下来文章里说的留言和评论指的都是同一个东西!!!因为我需求一开始就是叫留言功能,但是评论这俩字儿可能比较顺口🤣

如果你能耐心看完这篇文章,应该会有自己的整体思路和大概方向了,接下来就是根据思路去完善你的需求,用代码实现即可!

若有错漏,欢迎指出!

功能描述

想要实现类似bilibili的评论区那样,在我的药材、方剂、文章详情页下都实现多级留言功能,但是不以递进的方式来展现层级关系

解释:
顶级留言 = 一级留言 = 根留言,子留言从二级留言开始,三级,四级…
二级留言直接挂在一级留言的下面,三级及以上留言显示的位置与二级留言是保持 "同级" ,用 @nickname来区分。我的实现图如下:
在这里插入图片描述

数据库表设计

在这里插入图片描述
补充:
我这里的 root_comment_id 字段是参考了博主 黄金贼贼 的这篇文章,觉得后面会用上,就也加上了;
reply_commentimage_urls 字段我目前没用到,设计的时候想到啥就写上了,可以先不用关注这两个字段;
status 字段我用上了,但是感觉其实可以不设置,有点冗余,可以看你自己的具体实现决定是否需要该字段;
还有就是,我整个系统除了收藏、认证方剂功能外,用的都是逻辑删除,所以会设置一个is_deleted字段。

附上sql建表语句,根据自己的需求更改:

CREATE TABLE `comments` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '记录id',
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `comment` varchar(3000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '留言内容',
  `moment_id` bigint DEFAULT NULL COMMENT '关联主体ID(药材/方剂/文章ID)',
  `comment_type` int DEFAULT NULL COMMENT '评论类型(1药材;2方剂;3文章)',
  `parent_id` bigint DEFAULT NULL COMMENT '直接父级ID(顶级留言ID;子级留言ID)',
  `root_comment_id` bigint DEFAULT NULL COMMENT '顶级评论ID(区分顶级留言和子留言)',
  `status` int DEFAULT NULL COMMENT '业务状态:1 评论 2 回复',
  `reply_comment` varchar(3000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '回复详情',
  `image_urls` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin COMMENT '留言图片',
  `created_by` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '创建人',
  `created_at` datetime DEFAULT NULL COMMENT '创建时间',
  `updated_by` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '修改人',
  `updated_at` datetime DEFAULT NULL COMMENT '更新时间',
  `is_deleted` tinyint DEFAULT '0' COMMENT '是否删除(0未删除;1已删除)',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=20240451 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=COMPACT COMMENT='评论留言表';

前提:
用户表:需要有用户ID(必须),用户名/昵称/角色身份等字段(根据你需要显示的需求来定)
被关联的主体的表:关联主体ID(我这里的moment_id存放的是三张被关联主体表的ID,可以存药材ID方剂ID文章ID,因为我这三个主体都需要有留言功能)简单点说就是,你要在哪个内容下面用这个留言功能,这个关联的主体就是它

解释: 简单讲一下几个字段
comment_type字段:因为我要将药材、方剂、文章的留言都存在同一张表,需要这个字段进行区分(但是不推荐这么做,因为我数据量小,这样能快速实现功能且省事)
parent_id 字段:每一条评论的直接父留言ID,存放的可能为顶级留言ID,也可能是子留言ID(如,子留言也会有子留言[或者说成回复],那它本身就是自己子留言的直接父亲;这个字段主要用于实现那个 @nickname功能点,被艾特的nickname是被留言[或回复]的二级、三级、四级…等留言的发表人昵称
root_comment_id字段:用于区别顶级留言与子留言。这个字段会用于查出全部顶级留言返回一个列表,根据结果列表可以进一步进行子留言查询(这里我还利用 PageHelper 做了分页)

后端接口设计

实体类

entity 完整实体类

项目使用了lombok工具

package com.ykl.springboot_tcmi.pojo.entity;  // 这是你自己的包名

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

/**
 * @Author: YKL
 * @ClassName: 
 * @Date: 2024/04/28 10:48 
 * @Description: 
 */

@Data
public class Comments  implements Serializable {


	/**
	 * 记录id
	 */
   
	private long id;

	/**
	 * 用户ID
	 */
	private long userId;

	/**
	 * 评论内容
	 */
	private String comment;

	/**
	 * 关联药材/方剂ID
	 */
	private long momentId;

	/**
	 * 评论类型(1药材;2方剂;3文章)
	 */
	private long commentType;

	/**
	 * 直接父级ID(顶级评论ID;子级评论ID)
	 */
	private long parentId;

	/**
	 * 顶级评论ID(区分顶级评论和子评论)
	 */
	private long rootCommentId;

	/**
	 * 回复详情
	 */
	private String replyComment;

	/**
	 * 业务状态:1 评论 2 回复
	 */
	private long status;

	/**
	 * 评论图片
	 */
	private String imageUrls;

	/**
	 * 创建人
	 */
	private String createdBy;

	/**
	 * 创建时间
	 */
	private LocalDateTime createdAt;

	/**
	 * 修改人
	 */
	private String updatedBy;

	/**
	 * 更新时间
	 */
	private LocalDateTime updatedAt;

	/**
	 * 是否删除(0未删除 1已删除)
	 */
	private Integer isDeleted;

}

dto 封装请求数据

在接口开发时,一般不直接使用完整实体类,而是使用 dto 类进行开发进行;
项目中使用了validation进行参数校验

为什么刚刚我的数据库表里定义为bigint类型的字段,这里都改用了long类型呢?
这里解释一下:
在数据库中,bigint类型用于存储较大的整数值,而在某些编程环境中,如Java的IDE(例如IntelliJ IDEA),这个类型通常会被映射为long类型。这是因为long类型在Java中用于表示64位的整数,与数据库中的bigint类型相对应;
long类型在Java中是标准的整数类型之一,用于表示较大的整数值,与数据库中的bigint类型兼容;
IDE为了简化开发过程,会自动(为啥说自动呢,因为我的实体类是在idea中连接了mysql后,直接使用"脚本扩展 groovy"进行创建的,并非我手动创建) 将数据库中的bigint类型映射为Java中最常用的对应类型long,以便开发者可以直接使用而不需要额外的类型转换;
另外,Java提供了BigInteger类,但这通常会增加代码的复杂性。在对性能不是特别敏感的场景下,可以使用BigInteger来处理更大的数值。

package com.ykl.springboot_tcmi.pojo.dto;   // 这是你自己的包名

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

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

/**
 * @Author: YKL
 * @ClassName:
 * @Date: 2024/04/28 10:48
 * @Description:
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommentsDTO implements Serializable {

    /**
     * 用户ID
     */
    private long userId;

    /**
     * 评论内容
     */
    @NotEmpty(message = "评论内容不能为空")
    private String comment;

    /**
     * 关联药材/方剂/文章ID
     */
    @NotNull
    private long momentId;

    /**
     * 评论类型(1药材;2方剂;3文章)
     */
    @NotNull
    private long commentType;

    /**
     * 直接父级ID(顶级评论ID;子级评论ID)
     */
    private long parentId;

    /**
     * 顶级评论ID(区分顶级评论和子评论)
     */
    private long rootCommentId;

    /**
     * 回复详情
     */
    private String replyComment;

    /**
     * 业务状态:1 评论 2 回复
     */
    private long status;

    /**
     * 评论图片
     */
    private String imageUrls;

    /**
     * 创建人
     */
    private String createdBy;

    /**
     * 创建时间
     */
    private LocalDateTime createdAt;
}

dto 封装分页请求数据

如果你没有分页需求,可以不用管这个

package com.ykl.springboot_tcmi.pojo.dto;   // 这是你自己的包名

import jakarta.validation.constraints.NotNull;
import lombok.Data;

import java.io.Serializable;

/**
 * @Author YKL
 * @ClassName
 * @Date: 2024/4/25 0:26
 * @Description:
 */

@Data
public class CollectionsPageQueryDTO implements Serializable {

    // 页码
    @NotNull
    Integer pageNum;

    // 每页显示的记录数
    @NotNull
    Integer pageSize;

    /**
     * 关联的用户ID
     */
    private long userId;


    /**
     * 收藏类型(1药材;2方剂;3文章)
     */
    private long collectType;
}

vo 请求返回数据

封装响应给客户端的数据

package com.ykl.springboot_tcmi.pojo.vo;  // 这是你自己的包名

import lombok.Data;

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

/**
 * @Author: YKL
 * @ClassName: 
 * @Date: 2024/04/28 10:48 
 * @Description: 
 */

@Data
public class CommentsVO implements Serializable {


	/**
	 * 记录id
	 */
   
	private long id;

	/**
	 * 用户ID
	 */
	private long userId;

	/**
	 * 评论内容
	 */
	private String comment;

	/**
	 * 关联药材/方剂/文章ID
	 */
	private long momentId;

	/**
	 * 评论类型(1药材;2方剂;3文章)
	 */
	private long commentType;

	/**
	 * 直接父级ID(顶级评论ID;子级评论ID)
	 */
	private long parentId;

	/**
	 * 顶级评论ID(区分顶级评论和子评论)
	 */
	private long rootCommentId;

	/**
	 * 回复详情
	 */
	private String replyComment;

	/**
	 * 业务状态:1 评论 2 回复
	 */
	private long status;

	/**
	 * 评论图片
	 */
	private String imageUrls;

	/**
	 * 创建人:这里,sql查询时,直接把用户名放在这个字段了
	 */
	private String createdBy;

	/**
	 * 创建时间
	 */
	private LocalDateTime createdAt;

	// 用户头像
	private String userImg;

	// 用户身份
	private String roleName;

	// 子评论列表
	private List<CommentsVO> children;
}

重点提一下最后的三个字段,数据库表中是没有这三个字段的,这是在做sql多表查询时额外返回的字段,前端需要这些数据;若你没有显示用户身份需求的话,可以省略roleName字段。
子评论列表很重要,这里存放了顶级评论下所有的子评论,一层层嵌套的,后面会说怎么用

Controller控制层

package com.ykl.springboot_tcmi.controller;  // 这是你自己的包名

import com.ykl.springboot_tcmi.common.bean.PageBean;
import com.ykl.springboot_tcmi.common.result.ResultCodeEnum;
import com.ykl.springboot_tcmi.common.result.ResultOBJ;
import com.ykl.springboot_tcmi.pojo.dto.CommentsDTO;
import com.ykl.springboot_tcmi.pojo.dto.CommentsPageQueryDTO;
import com.ykl.springboot_tcmi.pojo.vo.CommentsVO;
import com.ykl.springboot_tcmi.service.CommentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * @Author YKL
 * @ClassName
 * @Date: 2024/4/28 10:51
 * @Description:
 */

@RestController
@RequestMapping("/comment")
@CrossOrigin(origins = "http://localhost:5173")
public class CommentController {

    @Autowired
    private CommentService commentService;

    /**
     * 添加评论
     *
     * @param commentsDTO
     * @return
     */
    @PostMapping("/add")
    public ResultOBJ addComment(@RequestBody @Validated CommentsDTO commentsDTO) {
        return commentService.addComment(commentsDTO);
    }

    /**
     * 根据关联的主题ID查评论列表
     *
     * @param commentsPageQueryDTO
     * @return 树形结构的列表
     */
    @GetMapping("/page")
    public ResultOBJ<PageBean<CommentsVO>> getCommentList(@Validated CommentsPageQueryDTO commentsPageQueryDTO) {
        PageBean<CommentsVO> pb = commentService.getCommentListByMomentId(commentsPageQueryDTO);
        return ResultOBJ.SUCCESS(ResultCodeEnum.SUCCESS, pb);
    }
}

解释一哈:

添加评论

  • 需要提交的数据封装在CommentsDTO中传给后端,后端使用@RequestBody接收数据;
  • 其中,评论内容、关联主体ID、评论类型均不能为空

获取评论列表

  • 请求数据时需要提交 CommentsPageQueryDTO 中的数据,并使用 @Validated 进行参数的校验;
  • 这里使用了 PageHelper 进行分页处理(不做展示,网上教程也很多其实,或者私聊我获取这部分代码),所以返回的 CommentsVO 数据需要用 PageBean 包裹返回;
  • ResultOBJ 是我自己封装的通用的结果返回类,你也可以直接写成 return commentService.getCommentListByMomentId(commentsPageQueryDTO); 但是在你的接口实现类(Impl) 处需要返回对应的列表数据给前端

Service层

接口

package com.ykl.springboot_tcmi.service;  // 这是你自己的包名

import com.ykl.springboot_tcmi.common.bean.PageBean;
import com.ykl.springboot_tcmi.common.result.ResultOBJ;
import com.ykl.springboot_tcmi.pojo.dto.CommentsDTO;
import com.ykl.springboot_tcmi.pojo.dto.CommentsPageQueryDTO;
import com.ykl.springboot_tcmi.pojo.vo.CommentsVO;

/**
 * @Author YKL
 * @ClassName
 * @Date: 2024/4/28 10:52
 * @Description:
 */

public interface CommentService {
    ResultOBJ addComment(CommentsDTO commentsDTO);

    PageBean<CommentsVO> getCommentListByMomentId(CommentsPageQueryDTO commentsPageQueryDTO);
}

实现类

package com.ykl.springboot_tcmi.service;  // 这是你自己的包名

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.ykl.springboot_tcmi.common.bean.PageBean;
import com.ykl.springboot_tcmi.common.result.ResultCodeEnum;
import com.ykl.springboot_tcmi.common.result.ResultOBJ;
import com.ykl.springboot_tcmi.dao.CommentMapper;
import com.ykl.springboot_tcmi.pojo.dto.CommentsDTO;
import com.ykl.springboot_tcmi.pojo.dto.CommentsPageQueryDTO;
import com.ykl.springboot_tcmi.pojo.vo.CommentsVO;
import com.ykl.springboot_tcmi.utils.ThreadLocalUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;

/**
 * @Author YKL
 * @ClassName
 * @Date: 2024/4/28 10:52
 * @Description:
 */

@Service
@Transactional
public class CommentServiceImpl implements CommentService {

    @Autowired
    private CommentMapper commentMapper;

    /**
     * 添加评论
     *
     * @param commentsDTO
     * @return
     */
    @Override
    public ResultOBJ addComment(CommentsDTO commentsDTO) {
		
		// 本项目使用了 ThreadLocal 进行用户的信息管理,所以这里直接取了登录用户的id
        Map<String, Object> map = ThreadLocalUtil.get();
        Integer id = (Integer) map.get("id");
        commentsDTO.setUserId(id);  // 补充属性,所以前端请求的时候不需要携带这个数据

        // 判断添加的是顶级评论还是子评论(这里其实没太大必要,我是想着后面有功能点可能要用到这个状态,就设置了)
        // 【注意】这里解释一下为什么是等于0不是等于null。因为前面说过bigint自动映射为long了,long类型没有值则是为0
        if (commentsDTO.getRootCommentId() == 0 && commentsDTO.getParentId() == 0) {    // 顶级评论
            // 设置业务状态 1 评论 2 回复
            commentsDTO.setStatus(1);
        } else {  // 子评论/回复
            commentsDTO.setStatus(2);
        }

        commentMapper.addComment(commentsDTO);

        return ResultOBJ.SUCCESS(ResultCodeEnum.COMMENT_SUCCESS);
    }


    /**
     * 根据关联的主题ID查评论列表
     *
     * @param commentsPageQueryDTO
     * @return 树形结构的列表
     */
    @Override
    public PageBean<CommentsVO> getCommentListByMomentId(CommentsPageQueryDTO commentsPageQueryDTO) {

        // 1.创建PageBean对象
        PageBean<CommentsVO> pb = new PageBean<>();

        // 2.开启分页查询 PageHelper
        PageHelper.startPage(commentsPageQueryDTO.getPageNum(), commentsPageQueryDTO.getPageSize());

        // 3.查所有的根评论
        List<CommentsVO> commentsVOList = commentMapper.getAllRoot(commentsPageQueryDTO);

        // 4.遍历commentsVOList列表,添加对应的子评论(二级评论在一级评论的children字段中,三级评论在二级评论的children字段中,以此类推)
        for (CommentsVO comment : commentsVOList) {
        	// 调用查询子评论的方法,需要该顶级评论自己的 id 与 关联主体 id
        	// 【注意】这里就用到了vo中最后一个子评论列表 private List<CommentsVO> children 字段,设置子孩子的时候也是按照CommentsVO类型来返回数据的
            comment.setChildren(getChildrenComments(comment.getId(), commentsPageQueryDTO.getMomentId()));
        }

		// 强转
        Page<CommentsVO> page = (Page<CommentsVO>) commentsVOList;
        
        // 5.把数据填充到PageBean对象中,getTotal、getResult这两个方法是 pagehelper 提供的
        pb.setTotal(page.getTotal()); // 总数
        pb.setItems(page.getResult()); // 具体内容

		// 如果你在controller层用的我第二种写法,那么这里就还需要返回结果列表 + 处理状态,而不是单纯的一个 pb 对象
        return pb;
    }

    /**
     * 获取子评论的方法
     *
     * @param parentId
     * @param momentId
     * @return
     */
    private List<CommentsVO> getChildrenComments(long parentId, long momentId) {

        // 查所有的子评论(需要的是该子评论的直接父评论ID,一开始从二级评论开始查,也就是调用此方法时传进来的顶级评论id[这就是二级评论的直接父评论ID];还有关联主体id)
        List<CommentsVO> commentsVOList = commentMapper.getChildren(parentId, momentId);

        // 遍历名为commentsVOList的CommentsVO类型的集合
        for (CommentsVO comment : commentsVOList) {
        	// 此处用到了递归,递归查询每一级评论,每次调用本层的id去查子一层
        	// 【注意】每一个子孩子还有子孩子的话,也是按照CommentsVO类型来存放
            comment.setChildren(getChildrenComments(comment.getId(), momentId));
        }

        return commentsVOList;
    }

    // 子评论分页【未实现】
    // 【问题】只能分出二级评论,某条可展示的二级评论的子级评论们仍然可以被查到;需要的是所有子评论只展示3条,再进行分页
}

若这部分有疑问可以移步文章末尾看看返回的数据结构,结合起来再看看这段代码,可能会更清楚,若还有疑问,可以在评论区留言😁

Mapper层

package com.ykl.springboot_tcmi.dao;  // 这是你自己的包名

import com.ykl.springboot_tcmi.pojo.dto.CommentsDTO;
import com.ykl.springboot_tcmi.pojo.dto.CommentsPageQueryDTO;
import com.ykl.springboot_tcmi.pojo.vo.CommentsVO;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
 * @Author YKL
 * @ClassName
 * @Date: 2024/4/28 10:52
 * @Description:
 */

@Mapper
public interface CommentMapper {

    @Insert("insert into comments (user_id, comment, moment_id, comment_type, parent_id, root_comment_id, reply_comment, status, image_urls, created_at) values (#{userId}, #{comment}, #{momentId}, #{commentType}, #{parentId}, #{rootCommentId}, #{replyComment}, #{status}, #{imageUrls}, now())")
    void addComment(CommentsDTO commentsDTO);

	// 不分页的话,可以在这里进行简单的查询返回;但是我使用了分页,需要动态sql,所以不使用此种方法,注释处仅供参考
    // @Select("select * from comments where moment_id = #{momentId} and parent_id = 0 and root_comment_id = 0 and status = 1 and is_deleted = 0")
    List<CommentsVO> getAllRoot(CommentsPageQueryDTO commentsPageQueryDTO);

    // @Select("select * from comments where parent_id = #{parentId} and moment_id = #{momentId} and status = 2 and is_deleted = 0")
    List<CommentsVO> getChildren(long parentId, long momentId);
}

Mybatis 操作数据库

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<!-- @author: ykl -->

<mapper namespace="com.ykl.springboot_tcmi.dao.CommentMapper">


    <select id="getAllRoot" resultType="com.ykl.springboot_tcmi.pojo.vo.CommentsVO">
        select cs.*, u.nickname as createdBy, u.avatar as userImg, r.role_name as roleName
        from comments cs
                 LEFT JOIN users u on u.id = cs.user_id
                 LEFT JOIN roles r on u.user_role = r.role_type
        where cs.moment_id = #{momentId}
          and cs.status = 1
          and cs.parent_id = 0
          and cs.root_comment_id = 0
          and cs.is_deleted = 0
        order by cs.created_at desc
    </select>

    <select id="getChildren" resultType="com.ykl.springboot_tcmi.pojo.vo.CommentsVO">
        select cs.*, u.nickname as createdBy, u.avatar as userImg, r.role_name as roleName
        from comments cs
                 LEFT JOIN users u on u.id = cs.user_id
                 LEFT JOIN roles r on u.user_role = r.role_type
        where cs.parent_id = #{parentId}
          and cs.moment_id = #{momentId}
          and cs.status = 2
          and cs.is_deleted = 0
        order by cs.created_at desc
    </select>

</mapper>

这里使用到了左连接进行多表查询,只是看起来复杂,仔细看看语句应该都能看懂。
主要提一下,这里将查到的昵称、头像、角色名使用as别名对应了前面 vo 那几个字段,返回给前端使用:
u.nickname as createdBy, u.avatar as userImg, r.role_name as roleName

后端的设计到这里就结束了,删除评论的接口还没有设计,后面有时间补上😁

补充:

返回的数据结构

展示一下返回的评论列表是怎么样的,便于理解递归和 children字段。
示例中总共有7条顶级留言,但我是分页查询,本页我只查了3条:

  • 第一条id为 20240437 的顶级留言,没有子留言,故children字段为空。
  • 第二条id为 20240418 的顶级留言,只有1条二级子留言。
  • 第三条id为 20240402 的顶级留言,有3条二级留言。其中第一条二级留言下有一条三级留言(也可以有多条,我这里只是示例数据的结构),该三级留言下有一条四级留言,该四级留言下没有子留言了(后面还有五级、六级也是这个结构),children字段为空

若看着不方便,你可以cv到一些可以展示 json 格式数据的平台或编辑器上(如浏览器插件FeHelper),折叠着看,会更清楚,记得把我的注释去掉

{
    "code": 200,
    "msg": "操作成功",
    "data": {
        "total": 7,		// 这就是查出的顶级数据总条数,通过pb.setTotal(page.getTotal());返回的
        "items": [		// 这里就是整个数据体,通过pb.setItems(page.getResult());返回的
            {	// 第一条顶级评论
                "id": 20240437,
                "userId": 1509187009,
                "comment": "载刷一条",
                "momentId": 1000,
                "commentType": 1,
                "parentId": 0,
                "rootCommentId": 0,
                "replyComment": "",
                "status": 1,
                "imageUrls": null,
                "createdBy": "温壶酒",	// u.nickname as createdBy 返回的
                "createdAt": "2024-04-29 18:53",
                "userImg": "https://pic4.zhimg.com/v2-224f33627212bd952185ab882c377d3b_r.jpg",	//  u.avatar as userImg
                "roleName": "专业用户",	// r.role_name as roleName
                "children": []
            },
            {	// 第二条顶级评论
                "id": 20240418,
                "userId": 1509187011,
                "comment": "李白沉舟将欲行",
                "momentId": 1000,
                "commentType": 1,
                "parentId": 0,
                "rootCommentId": 0,
                "replyComment": "",
                "status": 1,
                "imageUrls": null,
                "createdBy": "诗仙",
                "createdAt": "2024-04-29 13:27",
                "userImg": "https://tse4-mm.cn.bing.net/th/id/OIP-C.cPnuoqXK3Jvbb4E8Y-mANQHaKM?rs=1&pid=ImgDetMain",
                "roleName": "普通用户",
                "children": [	// 子留言的结构还是 vo 结构,这就应用了private List<CommentsVO> children;这个字段
                    {
                        "id": 20240426,
                        "userId": 1509187013,
                        "comment": "破釜沉舟",
                        "momentId": 1000,
                        "commentType": 1,
                        "parentId": 20240418,
                        "rootCommentId": 20240418,
                        "replyComment": "",
                        "status": 2,
                        "imageUrls": null,
                        "createdBy": "诗鬼",
                        "createdAt": "2024-04-29 18:17",
                        "userImg": "https://www.renwuji.com/wp-content/uploads/images/2023/01/11/14bd6725cbe34af98bb2ed12df20c253~noop_dmtcvm25wza.jpg",
                        "roleName": "普通用户",
                        "children": []
                    }
                ]
            },
            {	// 第三条顶级评论
                "id": 20240402,
                "userId": 1509187005,
                "comment": "我是测试的给方剂的父级评论",
                "momentId": 1000,
                "commentType": 1,
                "parentId": 0,
                "rootCommentId": 0,
                "replyComment": null,
                "status": 1,
                "imageUrls": null,
                "createdBy": "北上",
                "createdAt": "2024-04-28 13:03",
                "userImg": "",
                "roleName": "普通用户",
                "children": [
                    {	// 第三条顶级评论的第一条二级评论
                        "id": 20240403,
                        "userId": 1509187005,
                        "comment": "我是给自己的二级评论",
                        "momentId": 1000,
                        "commentType": 1,
                        "parentId": 20240402,
                        "rootCommentId": 20240402,
                        "replyComment": null,
                        "status": 2,
                        "imageUrls": null,
                        "createdBy": "北上",
                        "createdAt": "2024-04-28 13:07",
                        "userImg": "",
                        "roleName": "普通用户",
                        "children": [
                            {
                                "id": 20240404,
                                "userId": 1509187005,
                                "comment": "我是给自己的三级评论",
                                "momentId": 1000,
                                "commentType": 1,
                                "parentId": 20240403,
                                "rootCommentId": 20240402,
                                "replyComment": null,
                                "status": 2,
                                "imageUrls": null,
                                "createdBy": "北上",
                                "createdAt": "2024-04-28 13:08",
                                "userImg": "",
                                "roleName": "普通用户",
                                "children": [
                                    {
                                        "id": 20240405,
                                        "userId": 1509187005,
                                        "comment": "我是给自己的四级评论",
                                        "momentId": 1000,
                                        "commentType": 1,
                                        "parentId": 20240404,
                                        "rootCommentId": 20240402,
                                        "replyComment": null,
                                        "status": 2,
                                        "imageUrls": null,
                                        "createdBy": "北上",
                                        "createdAt": "2024-04-28 13:10",
                                        "userImg": "",
                                        "roleName": "普通用户",
                                        "children": []
                                    }
                                ]
                            }
                        ]
                    },
                    {	// 第三条顶级评论的第二条二级评论
                        "id": 20240407,
                        "userId": 1509187006,
                        "comment": "gao的二级评论",
                        "momentId": 1000,
                        "commentType": 1,
                        "parentId": 20240402,
                        "rootCommentId": 20240402,
                        "replyComment": null,
                        "status": 2,
                        "imageUrls": null,
                        "createdBy": "gao",
                        "createdAt": "2024-04-28 13:07",
                        "userImg": "https://ts1.cn.mm.bing.net/th/id/R-C.66d7b796377883a92aad65b283ef1f84?rik=sQ%2fKoYAcr%2bOwsw&riu=http%3a%2f%2fwww.quazero.com%2fuploads%2fallimg%2f140305%2f1-140305131415.jpg&ehk=Hxl%2fQ9pbEiuuybrGWTEPJOhvrFK9C3vyCcWicooXfNE%3d&risl=&pid=ImgRaw&r=0",
                        "roleName": "超级管理员",
                        "children": []
                    },
                    {	// 第三条顶级评论的第三条二级评论
                        "id": 20240408,
                        "userId": 1509187007,
                        "comment": "小脏的二级评论",
                        "momentId": 1000,
                        "commentType": 1,
                        "parentId": 20240402,
                        "rootCommentId": 20240402,
                        "replyComment": null,
                        "status": 2,
                        "imageUrls": null,
                        "createdBy": "zangzang",
                        "createdAt": "2024-04-28 13:07",
                        "userImg": "https://tcmi.oss-cn-beijing.aliyuncs.com/0f4c453f-40dd-43e4-bee9-03ab995ca0de.png",
                        "roleName": "平台管理员",
                        "children": []
                    }
                ]
            }
        ]
    }
}

FeHelper插件里,整体展示在这里插入图片描述

自动创建实体类

这里简单说一下怎么自动将数据库表导出为我们开发需要的实体类文件,详细过程网上很多,这里就提一下:
在idea中连接上数据库后,就可以使用这个功能
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1641146.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

题目:线性代数

问题描述&#xff1a; 解题思路&#xff1a; 列相乘&#xff0c;然后行相加。 注意点&#xff1a;由于元素数据范围最大为1e6&#xff0c;两个元素相乘乘积最大为1e12&#xff0c;如果元素类型为int则在乘的过程中就会爆炸&#xff0c;所以需要开long long类型。 AC代码…

深度学习500问——Chapter08:目标检测(6)

文章目录 8.3.7 RetinaNet 8.3.7 RetinaNet 研究背景 Two-Stage 检测器&#xff08;如Faster R-CNN、FPN&#xff09;效果好&#xff0c;但速度相对慢。One-Stage 检测器&#xff08;如YOLO、SSD&#xff09;速度快&#xff0c;但效果一般。 作者对one-stage检测器准确率不高…

有限单元法-编程与软件应用(崔济东、沈雪龙)【PDF下载】

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现&#xff0c;并提供所有案例完整源码&#xff1b;2.单元…

JAVA基础之线程池原理与源码简读

线程 线程是调度CPU资源的最小单位&#xff0c;线程模型分为KLT和ULT模型&#xff0c;JVM使用的KLT模型java线程与OS线程保持1:1的映射关系&#xff0c;也就是说每一个java线程对应操作系统一个线程。Java线程有以下几种生命状态&#xff1a; NEW&#xff1a;新建状态RUNNABL…

STM32——IWDG(独立看门狗)

技术笔记&#xff01; 1. IWDG&#xff08;Independent watchdog)&#xff0c;即独立看门狗 本质&#xff1a;能产生系统复位信号的计算器。 特性&#xff1a;递减计算器&#xff1b;时钟有独立的RC振荡器提供&#xff08;可在待机和停止模式下运行&#xff09;&#xff1b…

数据结构与算法---线性表

线性表 1.顺序表 需求分析 /*创建顺序表具体功能&#xff1a;初始化顺序表销毁顺序表获取顺序表元素个数输出顺序表中的内容自动扩容增 --- 插入数据&#xff08;包含了尾部添加功能&#xff09;删 --- 删除数据&#xff08;包含了尾部删除功能&#xff09;改 --- 修改数据查…

UDP编程流程(UDP客户端、服务器互发消息流程)

一、UDP编程流程 1.1、 UDP概述 UDP&#xff0c;即用户数据报协议&#xff0c;是一种面向无连接的传输层协议。相比于TCP协议&#xff0c;UDP具有以下特点&#xff1a; 速度较快&#xff1a;由于UDP不需要建立连接和进行复杂的握手过程&#xff0c;因此在传输数据时速度稍快…

【深度学习】第二门课 改善深层神经网络 Week 2 3 优化算法、超参数调试和BN及其框架

&#x1f680;Write In Front&#x1f680; &#x1f4dd;个人主页&#xff1a;令夏二十三 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd; &#x1f4e3;系列专栏&#xff1a;深度学习 &#x1f4ac;总结&#xff1a;希望你看完之后&#xff0c;能对…

ASV1000视频监控平台:通过SDK接入海康网络摄像机IPC

目录 一、为何要通过SDK接入海康网络摄像机 &#xff08;一&#xff09;海康网络摄像机的SDK的功能 1、视频采集和显示 2、视频存储 3、视频回放 4、报警事件处理 5、PTZ控制 6、自定义设置 7、扩展功能 &#xff08;二&#xff09;通过SDK接入的好处&#xff08;相对…

【1小时掌握速通深度学习面试3】RNN循环神经网络

目录 12.描述循环神经网络的结构及参数更新方式&#xff0c;如何使用神经网络对序列数据建模? 13.循环神经网络为什么容易出现长期依赖问题? 14.LSTM 是如何实现长短期记忆功能的? 15.在循环神经网络中如何使用 Dropout ? 16.如何用循环神经网络实现 Seg2Seq 映射? …

2024新版Java基础从入门到精通全套教程(含视频+配套资料)

前言 Java基础是所有入门java的同学必过的一关&#xff0c;基础学习的牢固与否决定了程序员未来成就的高度。因此&#xff0c;基础学习的重要性不言而喻。 但是很多同学学习java基础知识&#xff0c;要么是学的太“基础”&#xff0c;就是只会各个知识点的简单概念和使用&…

idea 新建spring maven项目、ioc和依赖注入

文章目录 一、新建Spring-Maven项目二、在Spring-context使用IOC和依赖注入 一、新建Spring-Maven项目 在pom.xml文件中添加插件管理依赖 <build><plugins><plugin><artifactId>maven-compiler-plugin</artifactId><version>3.1</ver…

恶补《操作系统》4_2——王道学习笔记

4.1_5 文件存储空间管理 1、存储空间的划分与初始化 文件卷&#xff08;逻辑卷&#xff09;的概念目录区与文件区 2、几种管理方法 空闲表法&#xff1a;首位置长度&#xff0c;回收时注意修改空闲链表法&#xff08;空闲盘块链、空闲盘区链&#xff09;位示图法 成组链接法…

2024年 Java 面试八股文——Mybatis篇

目录 1. 什么是Mybatis&#xff1f; 2. 说说Mybatis的优缺点 3. Xml映射文件中&#xff0c;都有哪些标签 4. #{}和&{}有什么区别 5. Mybatis是如何进行分页的,分页插件的原理是什么 6. Mybatis是如何将sql执行结果封装为目标对象并返回的&#xff1f; 7. Mybatis是怎…

JavaWeb--1.Servlet

Servlet&#xff08;基础&#xff09; 1、配置依赖&#xff1a; ​ 在pom.xml文件中加入相关依赖 <dependencies><dependency><groupId>jakarta.servlet</groupId><artifactId>jakarta.servlet-api</artifactId><version>5.0.0&l…

基于Python的LSTM网络实现单特征预测回归任务(TensorFlow)

目录 一、数据集 二、任务目标 三、代码实现 1、从本地路径中读取数据文件 2、数据归一化 3、创建配置类&#xff0c;将LSTM的各个超参数声明为变量&#xff0c;便于后续使用 4、创建时间序列数据 5、划分数据集 6、定义LSTM网络 &#xff08;1&#xff09;创建顺序模…

【ESP32之旅】合宙ESP32-C3 使用PlatformIO编译和Debug调试

工程创建 首先打开PIO Home窗口&#xff0c;然后点击New Project来创建新的工程&#xff0c;工程配置选择如下图所示&#xff1a; 注&#xff1a; 选择板子型号的时候需要选择ESP32C3&#xff0c;勾选取消Location可以自定义路径。 修改配置文件 工程创建完毕之后在工程根…

模式识别作业:颜色算子的三种阈值分割算法

一、引言&#xff1a; 在图像处理中&#xff0c;我们往往需要提取图像的一些关键信息&#xff0c;比如本篇文章的内容——提取颜色&#xff0c;然而当我们需要提取某一种颜色时&#xff0c;无论图像余下的部分如何“丰富多彩”&#xff0c;他们都不再重要&#xff0c;需要被忽…

C#核心之面向对象-继承

面向对象-继承 文章目录 1、继承的基本规则1、基本概念2、基本语法3、示例4、访问修饰符的影响5、子类和父类的同名成员 2、里氏替换原则1、基本概念2、is和as3、基本实现 3、继承中的构造函数1、基本概念2、父类的无参构造函数3、通过base调用指定父类构造 4、万物之父和装箱拆…

8.k8s中网络资源service

目录 一、service资源概述 二、service资源类型 1.ClusterIP类型 2.service的nodeport类型 3.service的loadbalancer类型&#xff08;了解即可&#xff09; 4.service的externalname类型&#xff08;了解即可&#xff09; 三、nodeport的端口范围设置和svc的endpoint列表 1.修…