美文网首页
Section-11 项目实战之话题模块

Section-11 项目实战之话题模块

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

Lesson-1 话题模块需求分析

话题模块功能点

  • 话题的增改查
  • 分页、模糊搜索
  • 用户属性中的话题引用·

Lesson-2&3 RESTful 风格的话题增改查接口

操作步骤

  • 设计 Schema
  • 实现 RESTful 风格的增改查接口

设计 Schema

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

const { Schema, model } = mongoose;

const topicSchema = new Schema({
    __v: { type: Number, select: false },
    name: { type: String, required: true },
    avatar_url: { type: String },
    introduction: { type: String, select: false } // 简介
});

module.exports = model('Topic', topicSchema);

实现 RESTful 风格的增改查接口

// controllers/topics.js
const Topic = require('../models/topics');

class TopicsCtl {
    async find (ctx) {
        ctx.body = await Topic.find();
    }

    async findById (ctx) {
        const { fields = '' } = ctx.query;
        const selectFields = fields.split(';').filter(item => item).map(item => ' +'+item).join('');
        const topic = await Topic.findById(ctx.params.id).select(selectFields);        
        if(!topic) ctx.throw(404, '用户不存在');
        ctx.body = topic;
    }

    async create (ctx) {
        ctx.verifyParams({
            name: { type: 'string', required: true },
            avatar_url: { type: 'string', required: false },
            introduction: { type: 'string', required: false }
        });

        const { name } = ctx.request.body;
        const requesteTopic = await Topic.findOne({ name });

        if(requesteTopic) ctx.throw(409, '话题已经存在');

        const topic = await new Topic(ctx.request.body).save();
        ctx.body = topic;
    }

    async update (ctx) {
        ctx.verifyParams({
            name: { type: 'string', required: false },
            avatar_url: { type: 'string', require: false },
            introduction: { type: 'string', require: false }
        });

        // findByIdAndUpdate,第一个参数为要修改的数据id,第二个参数为修改的内容
        const topic = await Topic.findByIdAndUpdate(ctx.params.id, ctx.request.body);
        if(!topic) ctx.throw(404, '话题不存在');
        ctx.body = topic;
    }
}

module.exports = new TopicsCtl();
// routes/topics.js
const jwt = require('koa-jwt');
const Router = require('koa-router');
const router = new Router({prefix: '/topics'});
const { find, findById, create, update } = require('../controllers/topics');

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

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

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

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

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

// 修改特定话题
router.patch('/:id', auth, checkTopicExist, update);

module.exports = router;

说一下为什么没有删除方法,因为话题涉及的模块特别多,比如普通的话题,用户属性里,用户的回答/收藏/关注等等,一旦删除,对到很多用户而言都不好处理,所以并不存在删除话题


Lesson-4 RESTful API 最佳实践 -- 分页

实现分页逻辑

实现的是查询链接上获取page和pre_page的参数,比如localhost:3000/topics?page=1&per_page=1,这样就是获取第一页第一条,需要用到mongoose的limit和skip这两个方法,具体就不再贴文档了,自行查阅

// controllers/topics.js
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);
    ctx.body = await Topic.find().limit(perPage).skip(page * perPage); // limit: 返回多少数量,skip:跳过多少数量
}

Lesson-5 RESTful API 最佳实践 -- 模糊搜索

实现模糊搜索逻辑

其实mongoose的find方法用个正则表达式匹配就可以实现了

// controllers/topics.js
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);
    ctx.body = await User.find({ name: new RegExp(ctx.query.q) }).limit(perPage).skip(page * perPage);
}

Lesson-6 用户属性中的话题引用

使用话题引用替代部分用户属性

注意一下,因为之前这些用户属性都是设置的String类型,所以需要更改过来,并且之前数据库已经存储过了,所以需要添加话题,手动修改特定用户各项属性,才能验证成功,这里postman就不贴图了

// models/users.js
const userSchema = new Schema({
    __v: { type: Number, select: false },
    name: { type: String, required: true },
    password: { type: String, required: true, select: false },
    avatar_url: { type: String }, // 用户头像
    gender: { type: String, enum: ['male', 'female'], default: 'male', required: true }, // enum 可枚举,性别
    headline: { type: String }, // 一句话简介
    locations: { type: [{ type: Schema.Types.ObjectId, ref: 'Topic' }], select: false }, // 可枚举的字符串数组,居住地
    business: { type: Schema.Types.ObjectId, ref: 'Topic', select: false }, // 公司
    employments: {  // 职业经历
        type: [{
            company: { type: Schema.Types.ObjectId, ref: 'Topic' },
            job: { type: Schema.Types.ObjectId, ref: 'Topic' }
        }],
        select: false
    },
    educations: { // 教育经历
        type: [{
            school: { type: Schema.Types.ObjectId, ref: 'Topic' },
            major: { type: Schema.Types.ObjectId, ref: 'Topic' },
            diploma: { type: Number, enum: [1, 2, 3, 4, 5] }, // 文凭:初中,高中,大专,本科,本科以上
            entrance_year: { type: Number },
            graduation_year: { type: Number }
        }],
        select: false
    },
    following: { // 关注者
        type: [{
            type: Schema.Types.ObjectId, // 用户ID
            ref: 'User' // 引用 User = require('../models/users') 数据库模型
        }],
        select: false
    }
});
// controllers/topics.js
async findById (ctx) {
    const { fields = '' } = ctx.query;
    const selectFields = fields.split(';').filter(item => item).map(item => ' +'+item).join('');
    const populateStr = fields.split(';').filter(item => item).map(item => {
        if (item === 'employments') return 'employments.company employments.job';
        if (item === 'educations') return 'educations.school educations.major';
        return item;
    }).join(' ');

    const user = await User.findById(ctx.params.id).select(selectFields).populate(populateStr);
    if(!user) ctx.throw(404, '用户不存在');
    ctx.body = user;
}

Lesson-7&8 RESTful 风格的关注话题接口

实现关注话题逻辑(用户-话题多对多关系)

与前面粉丝列表,关注某人等操作基本一致,唯一需要说的一点是:比如获取某话题下的关注者,那么这个方法不应该写在users.js里,因为它是属于话题方法的一部分,所以像这种需要写在topics里

// models/users.js
followingTopics: { // 关注的话题
    type: [{
        type: Schema.Types.ObjectId, // 话题ID
        ref: 'Topic' // 引用 Topic = require('../models/topics') 数据库模型
    }],
    select: false
}

四个接口的控制器

// controllers/users.js
// 话题关注列表
async listFollowingTopics (ctx) {
    const user = await User.findById(ctx.params.id).select('+followingTopics').populate('followingTopics');
    console.log(user);
    if(!user) ctx.throw(404);
    ctx.body = user.followingTopics;
}

// 关注某个话题
async followTopic (ctx) {
    const me = await User.findById(ctx.state.user._id).select('+followingTopics');
    if(!me.followingTopics.map(id => id.toString()).includes(ctx.params.id)) {
        me.followingTopics.push(ctx.params.id);
        me.save();
    }
    ctx.status = 204;
}

// 取消关注某个话题
async unfollowTopic (ctx) {
    const me = await User.findById(ctx.state.user._id).select('+followingTopics');
    const index = me.followingTopics.map(id => id.toString()).indexOf(ctx.params.id);
    if(index > -1) {
        me.followingTopics.splice(index, 1);
        me.save();
    }
    ctx.status = 204;
}
// controllers/topics.js
const User = require('../models/users'); // 数据库模型导出

// 话题关注者
async listTopicsFollowers (ctx) {
    const users = await User.find({ followingTopics: ctx.params.id }); // 查找followingTopics包含该话题id的用户
    ctx.body = users;
}

async checkTopicExist (ctx, next) {
    const topic = await Topic.findById(ctx.params.id);
    if(!topic) ctx.throw(404, '话题不存在');

    await next();
}

四个接口对应的路由

// routes/topics.js
const { find, findById, create, update, checkTopicExist, listTopicsFollowers } = require('../controllers/topics');

// 获取当前话题下的关注者
router.get('/:id/followers', checkTopicExist, listTopicsFollowers)
// routes/users.js
const { find, findById, create, update, delete: del, login,
    checkOwner, listFollowing, follow, unfollow, listFollowers, checkUserExist,
    followTopic, unfollowTopic, listFollowingTopics } = require('../controllers/users');
const { checkTopicExist } = require('../controllers/topics');

// 获取关注话题列表
router.get('/:id/listFollowingTopics', listFollowingTopics);

// 关注某话题
router.put('/followingTopic/:id', auth, checkTopicExist, followTopic);

// 取消关注某话题
router.put('/unfollowingTopic/:id', auth, checkTopicExist, unfollowTopic);

相关文章

网友评论

      本文标题:Section-11 项目实战之话题模块

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