Section-13 项目实战之答案模块

2019-10-28  本文已影响0人  羽晞yose

Lesson-1 答案模块需求分析

答案模块功能点


Lesson-2 问题-答案模块二级嵌套的增删改查接口

操作步骤

设计数据库 Schema

// models/answers.js
const mongoose = require('mongoose');

const { Schema, model } = mongoose;

const AnswerSchema = new Schema({
    __v: { type: Number, select: false },
    content: { type: String, required: true },
    answerer: { type: Schema.Types.ObjectId, ref: 'User', required: true, select: false },
    questionId: { type: String, select: true }
});

module.exports = model('Answer', AnswerSchema);

实现增删改查接口

// controllers/answers.js
const Answer = require('../models/answers'); // 数据库模型导出

class AnswersCtl {
    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);
        ctx.body = await Answer.find( { content: q, questionId: ctx.params.questionId }).limit(perPage).skip(page * perPage); // limit: 返回多少数量,skip:跳过多少数量
    }

    async findById (ctx) {
        const { fields = '' } = ctx.query;
        const selectFields = fields.split(';').filter(item => item).map(item => ' +'+item).join('');
        const answer = await Answer.findById(ctx.params.id).select(selectFields).populate('answerer');
        if(!answer) ctx.throw(404, '答案不存在');
        ctx.body = answer;
    }

    async create (ctx) {
        ctx.verifyParams({
            content: { type: 'string', required: true }
        });

        const answerer = ctx.state.user._id;
        const { questionId } = ctx.params;
        const answer = await new Answer({...ctx.request.body, answerer, questionId }).save();
        ctx.body = answer;
    }

    async update (ctx) {
        ctx.verifyParams({
            content: { type: 'string', required: false }
        });

        await ctx.state.answer.update(ctx.request.body);
        ctx.body = ctx.state.answer;
    }

    async delete (ctx) {
        await Answer.findByIdAndRemove(ctx.params.id);
        ctx.status = 204; // 没有内容,但是成功了
    }

    async checkAnswerExist (ctx, next) {
        const answer = await Answer.findById(ctx.params.id).select('+answerer');
        if(!answer || answer.questionId !== ctx.params.questionId) ctx.throw(404, '答案不存在');
        ctx.state.answer = answer;
        await next();
    }

    async checkAnswerer (ctx, next) {
        const { answer } = ctx.state;
        if (answer.answerer.toString() !== ctx.state.user._id) ctx.throw(403, '没有权限');
        await next();
    }
}

module.exports = new AnswersCtl();
// routes/answers.js
const jwt = require('koa-jwt');
const Router = require('koa-router');
const router = new Router({prefix: '/questions/:questionId/answers'});
const { find, findById, create, update, delete: del,checkAnswerExist, checkAnswerer } = require('../controllers/answers');

const { secret } = require('../config');

// 认证中间件
const auth = jwt({ secret });

// 获取问题列表
router.get('/', find);

// 增加问题
router.post('/', auth, create);

// 获取特定问题
router.get('/:id',checkAnswerExist, findById);

// 修改特定问题
router.patch('/:id', auth, checkAnswerExist, checkAnswerer, update);

// 删除问题
router.delete('/:id', auth, checkAnswerExist, checkAnswerer, del);

module.exports = router;

Lesson-3 互斥关系的赞踩答案接口设计与实现

操作步骤

设计数据库 Schema

首先需要在用户Schema添加喜欢跟踩的属性

// models/users.js
const userSchema = new Schema({
    likingAnswers: {
        type: [{
            type: Schema.Types.ObjectId, // 答案ID
            ref: 'Answer'
        }],
        select: false
    },
    dislikingAnswers: {
        type: [{
            type: Schema.Types.ObjectId, // 答案ID
            ref: 'Answer'
        }],
        select: false
    }
});

然后我们需要对答案增加一个票数,这个用于统计该答案总的点赞数

// models/answers.js
const AnswerSchema = new Schema({
    __v: { type: Number, select: false },
    content: { type: String, required: true },
    answerer: { type: Schema.Types.ObjectId, ref: 'User', required: true, select: false },
    questionId: { type: String, select: true },
    voteCount: { type: Number, required: true, default: 0 }
});

实现接口

// controllers/users.js
// 答案点赞列表
async listLikingAnswers (ctx) {
    const user = await User.findById(ctx.params.id).select('+likingAnswers').populate('likingAnswers');

    if(!user) ctx.throw(404);
    ctx.body = user.likingAnswers;
}

async likeAnswer (ctx, next) {
    const me = await User.findById(ctx.state.user._id).select('+likingAnswers');
    if(!me.likingAnswers.map(id => id.toString()).includes(ctx.params.id)) {
        me.likingAnswers.push(ctx.params.id);
        me.save();
        // 赞同数加1
        await Answer.findByIdAndUpdate(ctx.params.id, { $inc: { voteCount: 1 } });
    }
    ctx.status = 204;
    await next();
}

async unlikeAnswer (ctx) {
    const me = await User.findById(ctx.state.user._id).select('+likingAnswers');
    const index = me.likingAnswers.map(id => id.toString()).indexOf(ctx.params.id);
    if(index > -1) {
        me.likingAnswers.splice(index, 1);
        me.save();
        // 赞同数减1
        await Answer.findByIdAndUpdate(ctx.params.id, { $inc: { voteCount: -1 } });
    }
    ctx.status = 204;
}

// 答案不认同列表(踩)
async listDisLikingAnswers (ctx) {
    const user = await User.findById(ctx.params.id).select('+dislikingAnswers').populate('dislikingAnswers');

    if(!user) ctx.throw(404);
    ctx.body = user.dislikingAnswers;
}

async dislikeAnswer (ctx, next) {
    const me = await User.findById(ctx.state.user._id).select('+dislikingAnswers');
    if(!me.dislikingAnswers.map(id => id.toString()).includes(ctx.params.id)) {
        me.dislikingAnswers.push(ctx.params.id);
        me.save();
    }
    ctx.status = 204;
    await next();
}

async undislikeAnswer (ctx) {
    const me = await User.findById(ctx.state.user._id).select('+dislikingAnswers');
    const index = me.dislikingAnswers.map(id => id.toString()).indexOf(ctx.params.id);
    if(index > -1) {
        me.dislikingAnswers.splice(index, 1);
        me.save();
    }
    ctx.status = 204;
}

修改一下判断答案是否存在的中间件,因为赞和踩的时候并没有questionId

// controllers/answers.js
async checkAnswerExist (ctx, next) {
    const answer = await Answer.findById(ctx.params.id).select('+answerer');
    if (!answer) ctx.throw(404, '答案不存在');
    // 只有删改查答案的时候才检查此逻辑,赞和踩答案不检查
    if(ctx.params.questionId && answer.questionId !== ctx.params.questionId) ctx.throw(404, '该问题下没有此答案');
    ctx.state.answer = answer;
    await next();
}

修改用户路由,添加这六个方法请求

// router/users.js
// 获取喜欢的答案列表
router.get('/:id/likingAnswers', listLikingAnswers);

// 点赞答案
router.put('/likingAnswers/:id', auth, checkAnswerExist, likeAnswer, undislikeAnswer);

// 取消点赞答案
router.delete('/likingAnswers/:id', auth, checkAnswerExist, unlikeAnswer);

// 获取踩的答案列表
router.get('/:id/dislikingAnswers', listDisLikingAnswers);

// 踩答案
router.put('/dislikingAnswers/:id', auth, checkAnswerExist, dislikeAnswer, unlikeAnswer);

// 取消踩答案
router.delete('/dislikingAnswers/:id', auth, checkAnswerExist, undislikeAnswer);

Lesson-4 RESTful 风格的收藏答案接口

这节跟之前的实现还是一样的思路,所以不多说直接上代码

// models/users.js
const userSchema = new Schema({
    collectingAnswers: {  // 收藏答案
        type: [{
            type: Schema.Types.ObjectId, // 答案ID
            ref: 'Answer'
        }],
        select: false
    }
});
// controllers/users.js
// 收藏答案列表
async listCollectAnswers (ctx) {
    const user = await User.findById(ctx.params.id).select('+collectingAnswers').populate('collectingAnswers');

    if(!user) ctx.throw(404, '用户不存在');
    ctx.body = user.collectingAnswers;
}

async collectAnswer (ctx, next) {
    const me = await User.findById(ctx.state.user._id).select('+collectingAnswers');
    if(!me.collectingAnswers.map(id => id.toString()).includes(ctx.params.id)) {
        me.collectingAnswers.push(ctx.params.id);
        me.save();
    }
    ctx.status = 204;
    await next();
}

async uncollectAnswer (ctx) {
    const me = await User.findById(ctx.state.user._id).select('+collectingAnswers');
    const index = me.collectingAnswers.map(id => id.toString()).indexOf(ctx.params.id);
    if(index > -1) {
        me.collectingAnswers.splice(index, 1);
        me.save();
    }
    ctx.status = 204;
}
// router/users.js
// 获取收藏答案列表
router.get('/:id/collectAnswers', listCollectAnswers);

// 收藏答案
router.put('/collectAnswers/:id', auth, checkAnswerExist, collectAnswer);

// 取消收藏答案
router.delete('/collectAnswers/:id', auth, checkAnswerExist, uncollectAnswer);
上一篇下一篇

猜你喜欢

热点阅读