egg.js+mongoose实现二级评论

1、首先我们先在model目录下新建comment.js文件,这里使用egg-mongoose进行配置表以及字段,这里关键需要parent_id作为父级字段,若默认为0则是第一级评论,如果为id,则是二级评论。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
'use strict';

/**
* @description: Mongoose comment Schema,
*/

module.exports = app => {
const mongoose = app.mongoose;
const Schema = mongoose.Schema;
const CommentSchema = new Schema({
from: { /* 评论人id */
type: Schema.Types.ObjectId,
ref: 'User',
},
to: { /* 被评论人id */
type: Schema.Types.ObjectId,
ref: 'User',
},
content: { type: String }, /* 内容 */
create_time: { type: String }, /* 评论时间 */
book: { /* 书籍id */
type: Schema.Types.ObjectId,
ref: 'Book',
},
parent_id: { type: String, required: true, default: 0 } /* 父级 */
});
return mongoose.model('Comment', CommentSchema);
};

2、在controller 目录下新建comment.js 用于编写 评论相关的控制器方法,这里我定义了新增评论和查询评论,使用egg-validate进行请求参数验证如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
'use strict';

const Controller = require('egg').Controller;
// 定义新增评论请求参数规则
const createCommentRule = {
from: 'string',
to: {
type: 'string',
required: false,
},
content: 'string',
book: 'string',
parentId: {
type: 'string',
required: false,
},
}
// 定义查询评论请求参数规则
const findCommentRule = {
book: 'string',
page: 'string',
pageSize: 'string'
}
class CommentController extends Controller {
async create() {
const { ctx } = this;
ctx.validate(createCommentRule, ctx.request.body);
const result = await this.ctx.service.comment.createComment(ctx.request.body);
ctx.body = result;
}
async find() {
const { ctx } = this;
ctx.validate(findCommentRule, ctx.query);
const result = await this.ctx.service.comment.findComment(ctx.query);
ctx.body = result;
}
}

module.exports = CommentController;

在对应的控制器方法调用service层里面的方法,在service的方法进行数据库操作与逻辑处理。

3、在service目录下新建comment.js用于编写评论相关service方法。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
'use strict';

/**
* @description: 评论相关Service
*/
const Service = require('egg').Service;
const mongoose = require('mongoose');
const dayjs = require('dayjs');
module.exports = app => {
class CommentService extends Service {
/**
* 新增评论
* @param {Object} data 包括评论人id,被评论人id,书籍id, 评论父级id,评论内容
*/
async createComment(data) {
const { from, to, content, book, parentId } = data;
const result = await this.ctx.model.Comment.create({
from,
to,
content,
book,
parent_id: parentId ? mongoose.Types.ObjectId(parentId) : 0,
create_time: dayjs().unix(),
});
return result ? app.config.msg.CREARE_SUCCESS : app.config.msg.CREARE_ERR;
}
/**
* 查询评论
* @param {Object} data 包括书籍id,页码,页数
*/
async findComment(data) {
const { book, page, pageSize } = data
const totalNum = await this.ctx.model.Comment.find({ book: mongoose.Types.ObjectId(book), parent_id: 0 }).countDocuments();
const oneList = await this.ctx.model.Comment.find({ book: mongoose.Types.ObjectId(book), parent_id: 0 })
.populate({
path: 'from',
select: { name: 1, image: 1, _id: 1 }
})
.populate({
path: 'to',
select: { name: 1, image: 1, _id: 1 }
})
.sort({ create_time: 1})
.skip((parseInt(page) - 1) * parseInt(pageSize))
.limit(parseInt(pageSize)).lean();
const Comment = this.ctx.model.Comment
var promises = oneList.map(item => {
return Comment.find({
book: mongoose.Types.ObjectId(book),
parent_id: item._id
})
.populate({
path: 'from',
select: { name: 1, image: 1, _id: 1 }
})
.populate({
path: 'to',
select: { name: 1, image: 1, _id: 1 }
})
.sort({ create_time: 1})
.select('-__v').lean()
});
var list = await Promise.all(promises)
oneList.forEach(item => {
item.items = []
list.forEach(code => {
if (code.length > 0 && item._id == code[0].parent_id) {
item.items = code
}
})
})
return oneList ? { bean: {
records: oneList,
current: page,
size: oneList.length,
total: totalNum,
}, ...app.config.msg.GET_SUCCESS } : app.config.msg.GET_ERR;
}
}
return CommentService;
};

注:首先我们需要查询一级评论的分页数据,然后用map遍历一级评论列表,返回一个promises数组,数组的每一项是查询该一级评论下的所有二级评论,我这里没有进行分页,也可以加上分页。使用Promise.all异步执行所有的查询函数,完成后的结果赋值为list,最后遍历一级评论列表与二级评论列表,当一级评论id 等于 二级评论列表中parent_id时候 给当前一级评论列表增加 items属性用于存放它的二级评论。

最后在router.js中定义接口路径与请求类型,如下:

1
2
3
4
// 新建评论
router.post('/api/comment/create', controller.comment.create);
// 查询评论
router.get('/api/comment/find', controller.comment.find);