node-koa2+mongoose

874 阅读4分钟

主要是粗略记录一下 koa2的原理koa2的使用mongoose的使用以及项目模板要点构成 方便下次快速上手项目模板

在 node 中,我们知道使用 http 库去构建服务,实际上是经过 node 提供的 api 调用 V8 和 Libuv 去实现的

koa2 作为基于 Node.js 平台的 web 开发框架,那么 koa 具体做了些什么,原理又是什么? => koa2原理


为了熟悉 koa 的使用,我们的服务将实现一个用户模块和用户所属的组模块(以及组所属的设备模块)

搭建

hello-koa2koa2-note的基础上进行改造的

使用经典的分层架构模式:

router -> controller -> service -> model 逻辑较简单可以去掉service层,将逻辑整合到router层

controller service model 他们的交互和koa2的洋葱模型很类似

因为我们使用 koa2+mongoose,而有一些层(路由(用户)层 + 数据持久层)它们已经帮我们实现了,所以我们只需要整合一下就可以了,会有一些修改的步骤遗漏,具体就不校对了,可以直接去产物获取源码

日志

koa-loggerlog4js

静态资源

使用koa-static处理静态资源

路由

koa-router

const router = require('koa-router')()
const groupController = require('../../app/controllers/group_controller')

router.post('/create', groupController.create)
router.get('/query', groupController.query)
router.post('/edit', groupController.edit)
router.get('/remove', groupController.remove)

module.exports = router

数据解析

请求数据

koa-bodyparser解析后的正文将存储在ctx.request.body中,如果没有被解析,body将是一个空对象{}

还可以对请求数据进行验证

响应数据

使用 koa-json 以支持如下写法

ctx.body = {name: 'xlfd'}

验证(基于token验证用户)

使用koa-jwtjsonwebtoken,在登陆成功的时候将用户信息使用jsonwebtoken编译成符合jwt的token,在用户请求的时候,从(authorization) header中获取Bearer {{jwttoken}},然后解析出用户信息存储到公共的区域以供执行接口

生成token
info.token = jsonWebToken.sign({
    data: {'_id': info._id, 'userAccount': info.userAccount}, // token内的信息
    exp: Math.floor(Date.now() / 1000) + (60 * 60), // 过期时间
}, 'x-admin')
验证token
// 中间件对token进行验证
app.use(async (ctx, next) => {
    return next().catch((err) => {
        if (err.status === 401) {
            ctx.status = 401
            ctx.body = {
                code: 401,
                msg: '会话失效,请重新登录' || err.message,
            }
        } else {
            throw err
        }
    })
});

// 开启验证 secretName:'x-admin' 和上面 生成token 参数secretOrPrivateKey需要一致
app.use(koaJwt({secret: secretName})
    .unless({
        path: [/\//, /\/registerUser/, /\/login/, /\/getUser/],
    }))
解析token
// secretName:'x-admin' 和上面 生成token 参数secretOrPrivateKey需要一致
app.use(async (ctx, next) => {
    const token = ctx.headers.authorization
    if (!token) {
        await next()
    } else {
        // 解析token
        const data = jsonWebToken.verify(token.split(' ')[1], secretName)
        ctx.state = {
            data: data,
        }
        await next()
    }
})

会话保持

controller

处理请求数据调用service层

service

拿到controller的请求数据操作model层的数据并且执行具体的业务逻辑后,将结果返回给controller层

model

执行来自service层相应的数据操作请求

mongoose 已经封装好了针对 MongoDB 的一些api,可以将 mongoose 看做 model 层

对于 mongoose,需要先了解几个名词的含义

Schema

规定了数据库document的数据格式

可以理解成一个协议,进出数据库的数据格式都遵循这个协议,每个 schema 都会映射到一个 MongoDB collection

Models

Models 是从 Schema 编译来的构造函数。 它们的实例就代表着可以从数据库保存和读取的 documents。 从数据库创建和读取 document 的所有操作都是通过 model 进行的。

documents

documents 是 Models 的实例

ps

在mongoose中,const umInstance = await userModel.findOne({'_id': ctx.state.data.data._id})加不加await返回的对象不一样,将获得model(加await)和query实例。

因为数据库选择MongoDB,所以各模块数据之间的关系可以设计成嵌套模式:

连接数据库
const mongoose = require('mongoose')
const { mongodbUrl } = require('../config/index')
module.exports = () => {
    mongoose.connect(mongodbUrl,
        {useNewUrlParser: true, useUnifiedTopology: true})
    const db = mongoose.connection
    db.on('open', () => {
        console.log('mongonDB 连接成功')
    })
    db.on('error', () => {
        console.log('mongonDB 连接失败')
    })
}


新建集合和document
const mongoose = require('mongoose')
const groupSchema = require('./group_model')
// 数据格式协议
const userSchema = new mongoose.Schema({
    userName: {type: String},
    userAccount: {type: String},
    userPwd: {type: String},
    groups: [groupSchema],
    token: {type: String},
})
module.exports = mongoose.model('user', userSchema)

增删查改

看这里

产物-源码

这只是一个基础模板,后期可以添加缓存,版本和访问控制,像是mongoose也有更多的功能可以在实践中使用

基于 koa2 + mongoose 的 RESTful API 项目框架

脚本

因为有分层,所以新增一个功能模块的时候,需要去每个层建立对应的文件,比较繁琐。可以不可以使用脚本添加新功能模块的模板文件呢?

按照node-实现命令行工具package.json添加"bin": {"x_cli": "bin/x_cli"}然后sudo npn link(window直接npm link)就可以按照如下使用

x_cli -m 功能模块的名字

也可以不执行上面的步骤,直接使用下面这种方式在两个系统上运行

node bin/x_cli -m 功能模块的名字

脚本内容

#!/usr/bin/env node
// 输入model名字后,在controller、models、routes内创建js模板文件
const fs = require('fs')
const { argv } = require('yargs')
    .usage('x-cli [options]')
    .example('x-cli -m users')
    .help('h')
    .alias('h', 'help')
    .epilog('creat by xlfd')
    .option('m', {
        alias: 'model',
        demand: true,
        type: 'string',
        describe: '功能模块的名字',
    })
const {model} = argv
const modelObjList = [
    {
        path: `app/controllers/${model}_controller.js`,
        code: `const ${model}Model = require('../../models/${model}_model')

module.exports = {
    a: (ctx, next) => {}
}
`,
    },
    {
        // 也可以使用这种方式设定路径
        // path: path.format({
        //     dir: 'F:\\workSpace\\hello-koa2',
        //     base: `models/${model}_model.js`
        // }),
        path: `models/${model}_model.js`,
        code: `const mongoose = require('mongoose')
// 数据格式协议
const ${model}Schema = new mongoose.Schema({
    uid: {type: mongoose.Schema.Types.ObjectId},
    name: {type: String},
    updateTime: { type: Date, default: Date.now },
})
module.exports = ${model}Schema`,
    },
    {
        path: `routes/api/${model}_router.js`,
        code: `const router = require('koa-router')()
const ${model}Controller = require('../../app/controllers/${model}_controller')

router.get('/', ${model}Controller.a)
module.exports = router`,
    },
]
modelObjList.forEach((model) => {
    if (fs.existsSync(model.path)) return
    fs.writeFile(model.path, model.code, 'utf8', function(error) {
        if (error) {
            console.log(error);
            return false;
        }
        console.log('done');
    })
})