目录
- 功能描述
- 数据库表设计
- 后端接口设计
- 实体类
- entity 完整实体类
- dto 封装请求数据
- dto 封装分页请求数据
- vo 请求返回数据
- Controller控制层
- Service层
- 接口
- 实现类
- Mapper层
- Mybatis 操作数据库
- 补充:
- 返回的数据结构
- 自动创建实体类
最近毕设做完了,开始来梳理一下功能点,毕竟温故知新嘛
今天先写一下我的多级留言/评论功能的数据库和后端是怎么设计的(前端的设计会再写一篇单独的文章)
删除评论还没做,这个功能还没有思路,目前也没有太多时间来设计了,搜了一下感觉挺复杂的,后面有时间了会补上
接下来文章里说的留言和评论指的都是同一个东西!!!因为我需求一开始就是叫留言功能,但是评论这俩字儿可能比较顺口🤣
如果你能耐心看完这篇文章,应该会有自己的整体思路和大概方向了,接下来就是根据思路去完善你的需求,用代码实现即可!
若有错漏,欢迎指出!
功能描述
想要实现类似bilibili的评论区那样,在我的药材、方剂、文章详情页下都实现多级留言功能,但是不以递进的方式来展现层级关系
解释:
顶级留言 = 一级留言 = 根留言,子留言从二级留言开始,三级,四级…
二级留言直接挂在一级留言的下面,三级及以上留言显示的位置与二级留言是保持 "同级"
,用 @nickname
来区分。我的实现图如下:
数据库表设计
补充:
我这里的 root_comment_id
字段是参考了博主 黄金贼贼 的这篇文章,觉得后面会用上,就也加上了;
reply_comment
、image_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中连接上数据库后,就可以使用这个功能