Lesson-1 评论模块功能点
- 评论的增删改查
- 答案 - 评论/问题 - 评论/用户 - 评论一对多
- 一级评论与二级评论
- 赞/踩评论(重复功能不实现)
Lesson-2 问题 - 答案 - 评论 模块三级嵌套的增删改查接口
直接上代码吧,都是之前的代码,重点是看路由中的prefix,学习三级嵌套我们应该怎么实现才符合RESTful风格
// models/comments.js
const mongoose = require('mongoose');
const { Schema, model } = mongoose;
const commentSchema = new Schema({
__v: { type: Number, select: false },
content: { type: String, required: true },
commentator: { type: Schema.Types.ObjectId, ref: 'User', required: true, select: false },
questionId: { type: String, select: true },
answerId: { type: String, select: true }
});
module.exports = model('Comment', commentSchema);
// controllers/comments.js
const Comment = require('../models/comments'); // 数据库模型导出
class CommentsCtl {
async find (ctx) {
const { per_page = 10 } = ctx.query;
const page = Math.max(+ctx.query.page, 1) - 1;
const perPage = Math.max(+ctx.query.per_page, 1);
const q = new RegExp(ctx.query.q);
const { questionId, answerId } = ctx.params;
ctx.body = await Comment.find( { content: q, questionId, answerId }).limit(perPage).skip(page * perPage).populate('commentator'); // limit: 返回多少数量,skip:跳过多少数量
}
async findById (ctx) {
const { fields = '' } = ctx.query;
const selectFields = fields.split(';').filter(item => item).map(item => ' +'+item).join('');
const comment = await Comment.findById(ctx.params.id).select(selectFields).populate('commentator'); // populate的内容也会变成select,也就是会被返回回去
if(!comment) ctx.throw(404, '评论不存在');
ctx.body = comment;
}
async create (ctx) {
ctx.verifyParams({
content: { type: 'string', required: true }
});
const commentator = ctx.state.user._id;
const { questionId, answerId } = ctx.params;
const comment = await new Comment({...ctx.request.body, commentator, questionId, answerId }).save();
ctx.body = comment;
}
async update (ctx) {
ctx.verifyParams({
content: { type: 'string', required: false }
});
await ctx.state.comment.update(ctx.request.body);
ctx.body = ctx.state.comment;
}
async delete (ctx) {
await Comment.findByIdAndRemove(ctx.params.id);
ctx.status = 204; // 没有内容,但是成功了
}
async checkCommentExist (ctx, next) {
const comment = await Comment.findById(ctx.params.id).select('+commentator');
if (!comment) ctx.throw(404, '评论不存在');
if(ctx.params.questionId && comment.questionId !== ctx.params.questionId) ctx.throw(404, '该问题下没有此评论');
if(ctx.params.answerId && comment.answerId !== ctx.params.answerId) ctx.throw(404, '该评论下没有此评论');
ctx.state.comment = comment;
await next();
}
async checkCommentator (ctx, next) {
const { comment } = ctx.state;
if (comment.commentator.toString() !== ctx.state.user._id) ctx.throw(403, '没有权限');
await next();
}
}
module.exports = new CommentsCtl();
// routes/comments.js
const jwt = require('koa-jwt');
const Router = require('koa-router');
const router = new Router({prefix: '/questions/:questionId/answers/:answerId/comments'});
const { find, findById, create, update, delete: del,checkCommentExist, checkCommentator } = require('../controllers/comments');
const { secret } = require('../config');
// 认证中间件
const auth = jwt({ secret });
// 获取评论列表
router.get('/', find);
// 增加评论
router.post('/', auth, create);
// 获取特定评论
router.get('/:id',checkCommentExist, findById);
// 修改特定评论
router.patch('/:id', auth, checkCommentExist, checkCommentator, update);
// 删除评论
router.delete('/:id', auth, checkCommentExist, checkCommentator, del);
module.exports = router;
Lesson-3 一级评论与二级评论接口的设计与实现
操作步骤
- 设计数据库Schema
- 实现接口
在上一节的基础上进行更改,这样不需要更改接口,通过新增 rootCommentId(一级评论的Id) 和 replyTo(一级评论的用户)来判断是一级评论还是二级评论
// models/comments.js
const commentSchema = new Schema({
__v: { type: Number, select: false },
content: { type: String, required: true },
commentator: { type: Schema.Types.ObjectId, ref: 'User', required: true, select: false },
questionId: { type: String, select: true },
answerId: { type: String, select: true },
rootCommentId: { type: String, select: true },
replyTo: { type: Schema.Types.ObjectId, ref: 'User' }
});
// controllers/comments.js
// 新增rootCommentId,有该Id证明是二级评论,
async find (ctx) {
const { per_page = 10 } = ctx.query;
const page = Math.max(+ctx.query.page, 1) - 1;
const perPage = Math.max(+ctx.query.per_page, 1);
const q = new RegExp(ctx.query.q);
const { questionId, answerId } = ctx.params;
const { rootomment } = ctx.query;
ctx.body = await Comment.find( { content: q, questionId, answerId, rootomment }).limit(perPage).skip(page * perPage).populate('commentator replyTo'); // limit: 返回多少数量,skip:跳过多少数量
}
// 新增两个验证 rootCommentId 和 replyTo
async create (ctx) {
ctx.verifyParams({
content: { type: 'string', required: true },
rootCommentId: { type: 'string', required: false },
replyTo: { type: 'string', required: false }
});
const commentator = ctx.state.user._id;
const { questionId, answerId } = ctx.params;
const comment = await new Comment({...ctx.request.body, commentator, questionId, answerId }).save();
ctx.body = comment;
}
// 更新的时候只能更新内容,不能像原来那样直接把内容给替换掉,否则二级评论会覆盖一级评论
async update (ctx) {
ctx.verifyParams({
content: { type: 'string', required: false }
});
const { content } = ctx.request.body;
await ctx.state.comment.update({content});
ctx.body = ctx.state.comment;
}
Lesson-4 添加日期
这个功能是直接使用Mongoose自带的,并不需要我们去手动的添加,只需要在Schema中添加timestamps参数即可。不过我只使用了修改评论,结果只看到修改时间,而文档里说是会自动添加两个时间的。如果创建的时候没有带上,也可以根据文档中的说法自行添加即可。这里只写个示例,其余模型自行添加
// models/commons.js
const mongoose = require('mongoose');
const { Schema, model } = mongoose;
const commentSchema = new Schema({
__v: { type: Number, select: false },
content: { type: String, required: true },
commentator: { type: Schema.Types.ObjectId, ref: 'User', required: true, select: false },
questionId: { type: String, select: true },
answerId: { type: String, select: true },
rootCommentId: { type: String, select: true },
replyTo: { type: Schema.Types.ObjectId, ref: 'User' }
}, {timestamps: true}); // 添加时间戳
module.exports = model('Comment', commentSchema);
课程到这里就结束了,上传到服务器那些我就不学也不记录了,毕竟俺是个前端,最后记录一下课程总结,方便有需要的童鞋自行深入学习
回顾与总结
- REST 理论与最佳实践
- Koa2、MongoDB、JWT简介与实践
- 仿知乎 REST API 实战
重点难点
- REST 理论与实践
- JWT 原理及 Node.js 实现
- MongoDB Schema 设计
经验心得
- RESTful API 设计参考 Github API V3
- 使用 Github搜索 Koa2 资源
- 使用 Stack Overflow 搜索问题
拓展建议
- 使用企业级 Node.js 框架 -- Egg.js
- 掌握多进程编程知识
- 学习使用日志和性能监控