美文网首页
Section-13 项目实战之答案模块

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

作者: 羽晞yose | 来源:发表于2019-10-28 11:23 被阅读0次

    Lesson-1 答案模块需求分析

    答案模块功能点

    • 答案的增删改查
    • 问题-答案/用户 - 答案一对多
    • 赞/踩答案
    • 收藏答案

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

    操作步骤

    • 设计数据库 Schema
    • 实现增删改查接口

    设计数据库 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

    首先需要在用户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);
    

    相关文章

      网友评论

          本文标题:Section-13 项目实战之答案模块

          本文链接:https://www.haomeiwen.com/subject/dwdluctx.html