# koa实现知乎问题模块接口
需求分析:
- 问题的增删改查
- 用户的问题列表(用户-问题:一对多关系)
- 话题的问题列表 + 问题的话题列表(话题-问题:多对多关系)
/models/question.js
const mongoose = require('mongoose');
const { Schema, model } = mongoose;
const questionSchema = new Schema({
// 默认都会有
__v: { type: Number, select: false },
title: { type: String, required: true },
description: { type: String },
// 一个问题对应了一个提问者,这就已经实现了一对多的关系
// 提问者属于User模型里的,所以他是一个 Schema type
questioner: { type: Schema.Types.ObjectId, ref: 'User', required: true, select: false },
// Topic模型
topics: {
type: [{ type: Schema.Types.ObjectId, ref: 'Topic' }],
select: false,
},
}, { timestamps: true });
module.exports = model('Question', questionSchema);
/controllers/question.js
const Question = require('../models/questions');
class QuestionsCtl {
async find(ctx) {
const { per_page = 10 } = ctx.query;
const page = Math.max(ctx.query.page * 1, 1) - 1;
const perPage = Math.max(per_page * 1, 1);
const q = new RegExp(ctx.query.q);
ctx.body = await Question
.find({ $or: [{ title: q }, { description: q }] })
.limit(perPage).skip(page * perPage);
}
async checkQuestionExist(ctx, next) {
const question = await Question.findById(ctx.params.id).select('+questioner');
if (!question) { ctx.throw(404, '问题不存在'); }
// ctx.state 是给中间件保存数据的,ctx.body 是最终的输出
ctx.state.question = question;
await next();
}
async findById(ctx) {
const { fields = '' } = ctx.query;
const selectFields = fields.split(';').filter(f => f).map(f => ' +' + f).join('');
const question = await Question.findById(ctx.params.id).select(selectFields).populate('questioner topics');
ctx.body = question;
}
async create(ctx) {
ctx.verifyParams({
title: { type: 'string', required: true },
description: { type: 'string', required: false },
});
const question = await new Question({ ...ctx.request.body, questioner: ctx.state.user._id }).save();
ctx.body = question;
}
// 对问题的修改和删除,需要鉴权
async checkQuestioner(ctx, next) {
const { question } = ctx.state;
if (question.questioner.toString() !== ctx.state.user._id) { ctx.throw(403, '没有权限'); }
await next();
}
async update(ctx) {
ctx.verifyParams({
title: { type: 'string', required: false },
description: { type: 'string', required: false },
});
// 这里就不用再查一遍question是否存在了
await ctx.state.question.update(ctx.request.body);
ctx.body = ctx.state.question;
}
/**
* 删除问题
*/
async delete(ctx) {
await Question.findByIdAndRemove(ctx.params.id);
ctx.status = 204;
}
}
module.exports = new QuestionsCtl();
提示
ctx.state 是给中间件保存数据的,ctx.body 是最终的输出
/routes/question.js
const jwt = require('koa-jwt');
const Router = require('koa-router');
const router = new Router({ prefix: '/questions' });
const {
find, findById, create, update, delete: del,
checkQuestionExist, checkQuestioner,
} = require('../controllers/questions');
const { secret } = require('../config');
const auth = jwt({ secret });
router.get('/', find);
router.post('/', auth, create);
router.get('/:id', checkQuestionExist, findById);
router.patch('/:id', auth, checkQuestionExist, checkQuestioner, update);
router.delete('/:id', auth, checkQuestionExist, checkQuestioner, del);
module.exports = router;