“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情”
前言
在最近负责的项目中,有针对每一个项目进行项目跟进,如下图有接触、交流、poc、立项推动、招投标、交付六项跟进,每个跟进下都有评论模块
实现
数据库表字段设计
base.schema.ts
comments.schema.ts
基本逻辑
- 数据库表字段设计如上(这是根据此次需求设计的字段,),在获取评论数据时,需要确定是在哪一个项目跟进下获取的,需要定义出当前
跟进id(followId)以便做关联,并且评论可以回复,那便需要确定当前评论有无上一级或下一级,定义parentId作为关联,如果没有上一级就是第一级,parentId设置为0,回复评论的parentId为被回复评论的id值,举个例子如下:
| id | followType | followId | content | parentId | userInfo | createTime | updateTime |
|---|---|---|---|---|---|---|---|
| 1 | concat | 1 | 评论 | 0 | xxx | xxx | xxx |
| 2 | concat | 1 | 回复评论 | 1 | xxx | xxx | xxx |
接口实现及对获取的评论数据进行数组转树形结构
- 根据
followId在评论表中进行查全部数据- 前端获取当前
followId并传递参数followId给后端进行查询
- 前端获取当前
comments.controller.ts
// 根据跟进id查评论
@Get('/getComments')
async getComments(@Request() req, @Response() res) {
const followId = req.body.followId;
try {
const data = await this.commentsService.getComments({
followId
});
return res.success(data);
} catch (e) {
this.logger.error(`获取评论数据失败: ${e.message}`);
return res.error('获取评论数据失败');
}
}
comments.service.ts
// 将数组转化为树形结构的方法
const arrayToTree = (items) => {
const result = []; // 结果集
const itemMap = {};
// 先转成map存储
for (const item of items) {
itemMap[item.id] = { ...item };
itemMap[item.id]._doc.children = [];
const id = item.id;
const parentId = item.parentId;
const treeItem = itemMap[id]._doc;
// 第一层
if (parentId == 0) {
result.push(treeItem);
} else {
if (!itemMap[parentId]) {
itemMap[parentId]._doc.children = [];
}
itemMap[parentId]._doc.children.push(treeItem);
}
}
return result;
};
// 跟进项目id查接触信息
async getComments({ followId }) {
const comments = await this.CommentsModel.find({
followId
});
const commentsList = this.arrayToTree(comments);
return {
total: commentsList.length,
record: commentsList
};
}
-
在看方法时,会注意到
itemMap[item.id]._doc.children = [],这个_doc是什么呢?为什么要这样做呢?接着往下看:- 将根据
followId从数据库中获取的数据转化为树形结构时,我们在给查询结果直接添加了额外的children属性,那就导致了在返回的数据中出现了一些“多余”的字符(那“多余”字符怎么来的,稍后解释),我们可以看到,在_doc中存的才是我们需要的数据 - 在这先了解一下
Object.getOwnPropertyDescriptors(),这个方法返回一个对象,所有原来的对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象。 Object.getOwnPropertyDescriptors(comments[0])返回的结果如下所示,代码结构和postman返回的一致,由此可见上文提到“多余”字符是这么来的,返回的都是属性描述符
- 将根据
-
postman返回结果: -
使用
Object.getOwnPropertyDescriptors()返回结果:
{
'$__': {
value: InternalCache { activePaths: [StateMachine], skipId: true },
writable: true,
enumerable: true,
configurable: true
},
'$isNew': {
value: false,
writable: true,
enumerable: true,
configurable: true
},
_doc: {
value: {
_id: new ObjectId("63ddf2db5b3b37578a4077ab"),
userInfo: [Object],
salesProject: '63c4ea6457cfeec648276086',
followType: 'concat',
followId: '63c4ea8257cfeec6482760c8',
content: 'ceshipinglun',
parentId: '0',
createTime: 2023-02-04T05:53:31.188Z,
updateTime: 2023-02-04T05:53:31.188Z,
__v: 0
},
writable: true,
enumerable: true,
configurable: true
}
}
find()或者findOne()得到的结果,是有一层封装的,在获取这个结果的时候,会自动解构,但是在我们自己在这个返回的数据中添加属性的时候,不会自动解构。主要数据就存储在_doc中
发布评论
- 前端传递评论数据至后端,后端需要判断是否具有用户信息
// 发布评论
@Post('/publishComment')
async publishComment(@Request() req, @Response() res) {
try {
const data = req.body as Comments;
if (!data.userInfo.user_id || !data.userInfo.user_name) {
return res.error('无效的支持人员');
}
const result = await this.commentsService.publishComment(data);
if (!result) {
return res.error('评论不存在');
}
return res.success(result);
} catch (e) {
if (e.message.includes('duplicate key error')) {
return res.error('评论已存在');
}
this.logger.error(`发布评论失败: ${e.message}`);
return res.error(e.message || '发布评论失败');
}
}
删除评论
// 删除评论
@Post('/deleteComment')
async deleteComment(@Body('id') id, @Response() res) {
try {
await this.commentsService.deleteComment(id);
return res.success();
} catch (err) {
this.logger.error(`删除评论数据失败: ${err.stack}`);
return res.error(err.message || '删除评论数据失败');
}
}