node koa2 初体验

1,830 阅读5分钟

一、koa2 初识

1. Think

之前一直用 node 的 express,没有太多框架封装的东西,需要自己手撸 sql 语句,配合 mysql 依赖包进行数据库的连接会话及读写操作。但框架封装的东西少了,自己做的就多了,最近想写一个不知名的 proj,为了进一步减少我写 demo 时的工作量,我决定换个框架试试。

p_001.png

2. 官网解释

1. koa 概念

koa2 是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架。 使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套, 并极大地提升错误处理的效率。koa 不在内核方法中绑定任何中间件, 它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手。

二、koa2 路由及传值

1. get 请求及数据获取

const Koa = require('koa')
const router = require('koa-router')() //注意:引入的方式
const app = new Koa()

router.get('/user/find', function (ctx, next) {
  const { username, sex } = ctx.request.query // 从上下文的 request 对象中获取
  ctx.body = '获取所有用户...'
})

2. post 请求及数据获取

const Koa = require('koa')
const router = require('koa-router')() //注意:引入的方式
const bodyParser = require('koa-bodyparser')
const app = new Koa()

// 对于POST请求的处理,koa-bodyparser中间件可以把koa2上下文的formData数据解析到ctx.request.body中
app.use(bodyParser())

router.post('/user/add', function (ctx, next) {
  const { username, sex, age } = ctx.request.body // 从上下文的 request 对象中获取
  ctx.body = '添加用户...'
})

三、koa2 中间件

中间件就是匹配路由之前或者匹配路由完成做的一系列的操作,我们就可以把它叫做中间件。

1. 概念

在 express 中间件(Middleware) 是一个函数,它可以访问请求对象(request object (req)), 响应对象(response object (res)), 和 web 应用中处理请求-响应循环流程中的中间件,一般被命名为 next 的变量。在 Koa 中中间件和 express 有点类似。

2. 功能

  • 执行任何代码。
  • 修改请求和响应对象。
  • 终结请求-响应循环。
  • 调用堆栈中的下一个中间件。

3. 中间件类型

const Koa = require('koa')
const app = new Koa()

// 01 应用级中间件

app.use(async (ctx, next) => {
  console.log(`Process ${ctx.request.method} ${ctx.request.url}...`)
  await next()
})

// 02 路由中间件
const Router = require('koa-router')
const router = new Router()

router.get('/', async (ctx, next) => {
  console.log(1)
  ctx.body = 'hello koa ~'
})

// 03 错误处理中间件

app.use(async (ctx, next) => {
  next()
  if (ctx.status === 500) {
    ctx.status = 500
    ctx.body = '服务器出了点小意外~'
  }
})

// 04 第三方中间件
const bodyParser = require('koa-bodyparser')

app.use(bodyParser()) // 处理 body 中的参数

4. 执行顺序: 著名的洋葱圈模型。

p_002.png

// 例如在以下的入口 app.js 中测试以下中间件(已省略无关代码)

...

const Koa = require("koa")
const app = new Koa()

// 中间件 01
app.use(async (ctx, next) => {
  console.log('->>>', 1)
  await next()
  console.log('<<<-', 111)
})

// 中间件 02
app.use(async (ctx, next) => {
  console.log('->>>', 2)
  await next()
  console.log('<<<-', 222)
})

// 中间件 03
app.use(async (ctx, next) => {
  console.log('->>>', 3)
  await next()
  console.log('<<<-', 333)
})

app.listen(3000)

...

// 输出:

->>> 1
->>> 2
->>> 3
<<<- 333
<<<- 222
<<<- 111

在以上测试伪代码中,每个中间件都接收了一个 next 参数,在 next 函数运行之前的中间件代码会在一开始就执行,next 函数之后的代码会在内部的中间件全部运行结束之后才执行:

它首先会走中间件 01,进而打印 ->>> 1,继续走碰到 next,会进入下一个中间件 02,进而打印出 ->>> 2,继续走碰到 next,会进入下一个中间件 03,进而打印出 ->>> 3。依此类推,当走到最后一个中间件后因为没有下一个中间件可执行,从最后一个中间件一层一层返回执行,这里假如 03 是最后一个中间件。继续走,它会打印出 <<<- 333,中间件 03 执行完毕,返回上一个中间件 02,打印出 <<<- 222,中间件 02 执行完毕,返回上一个中间件 01,打印出 <<<- 111,至此所有中间件执行完毕。而这里中间件的执行过程就是 洋葱圈模型

四、注解式路由工具 koa-swagger-decorator

koa-swagger-decorator 可以自动生成 swagger json 文档,让我们可以表面使用 MVC 式的注解式路由方式来撸接口及文档。

1. 需引入 babel 支持

// npm install --save-dev babel-plugin-transform-decorators-legacy
// .babelrc
{
  "presets": [["env", { "targets": { "node": "current" } }]],
  "plugins": ["transform-decorators-legacy"]
}

2. 写入配置

// SwaggerRouter.js
import { SwaggerRouter } from 'koa-swagger-decorator'
import * as path from 'path'

const router = new SwaggerRouter()
// swagger 文档地址: http://localhost:3000/api/swagger-html
router.swagger({
  title: 'A project',
  description: 'Api doc',
  version: '1.0.0',
})

// 查找对应目录下的controller类: 会将 controller 文件夹下的注解式接口生成一个个的 router
router.mapDir(path.resolve(__dirname, '../controller/'))

export default router

// app.js
import router from './router/SwaggerRouter'
app.use(router.routes())

p_003.png

3. controller 下接口写法

// UserController.js
import {
  request,
  summary,
  description, // 接口名称下方的描述信息
  query, // get时参数
  path, // post, put, delete 时地址栏参数
  body, // body中的参数
  tags,
} from 'koa-swagger-decorator'
// 引入我的业务操作
import UserService from '../service/UserService'

const userService = new UserService()
const tag = tags(['User'])

export default class UserController {
  @request('post', '/user/findById')
  @summary('根据id查询用户数据')
  @tag
  @body({
    id: { type: 'string', required: true },
  })
  async findById(ctx) {
    const bObj = ctx.request.body
    const data = await userService.findById(bObj)
    ctx.rest(data)
  }
}

五、数据库驱动工具 sequelize

sequelize 是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, MariaDB, SQLite 以及 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能。

1. 安装

$ npm install --save sequelize

# 必须手动为所选数据库安装驱动程序: 选择以下之一:
$ npm install --save pg pg-hstore # Postgres
$ npm install --save mysql2
$ npm install --save mariadb
$ npm install --save sqlite3
$ npm install --save tedious # Microsoft SQL Server

2. 连接数据库

更多 Sequelize 构造函数参数参考 API

// sequelizeMysql.js
import Sequelize from 'sequelize'

const DBConfig = {
  host: 'localhost', // 服务器地址
  port: 3306, // 数据库端口号
  username: 'root', // 数据库用户名
  password: '111111', // 数据库密码
  database: 'demo', // 数据库名称
  prefix: 'api_', // 默认"api_"
}

export default new Sequelize(
  DBConfig.database,
  DBConfig.username,
  DBConfig.password,
  {
    host: DBConfig.host,
    port: DBConfig.port,
    dialect: 'mysql', // 要连接的数据库:mysql、postgres、sqlite 和 mssql 之一
    pool: {
      max: 50, // 池中最大连接数 默认:5
      min: 0, // 池中最小连接数 默认:0
      idle: 10000, // 连接在被释放之前可以空闲的最长时间(以毫秒为单位)默认:10000
    },
    timezone: '+08:00',
  }
)

3. 创建表模型

模型 是 Sequelize 的本质. 模型是代表数据库中表的抽象. 在 Sequelize 中,它是一个 Model 的扩展类

// UserModel.js
import Sequelize from 'sequelize'
import sequelizeMysql from '../utils/sequelizeMysql'

// 创建 model
const User = sequelizeMysql.define(
  'user',
  {
    id: {
      type: Sequelize.UUID,
      defaultValue: Sequelize.UUIDV1,
      primaryKey: true,
    },
    username: {
      type: Sequelize.STRING(255),
      allowNull: false, // allowNull不设置默认为true
    },
    avatarUrl: {
      type: Sequelize.STRING(255),
      field: 'avatar_url', // 自定义表中的列名称
    },
    createtime: {
      type: Sequelize.STRING(255),
      defaultValue: Date.now(),
    },
    isdelete: {
      type: Sequelize.INTEGER,
      defaultValue: 0,
      allowNull: false,
    },
  },
  {
    // true 表名称和 model 相同: user
    // false 创建表名称会是复数: users
    freezeTableName: true,
    // 是否使用默认的 createdAt updatedAt
    timestamps: false,
  }
)

// 创建表
User.sync({ force: false })

export default User

4. 读写操作

此只简单列举查询添加操作,更多请点击 API 文档 查看模型查询

// UserDao.js
import UserModel from '../model/UserModel'

export default class UserDao {
  // 查询用户
  async findById(data) {
    const { id } = data
    return await UserModel.findAll({
      attributes: { exclude: ['isdelete'] }, // exclude: 返回值排除字段
      where: {
        isdelete: 0,
        id,
      },
    })
  }
  // 添加用户
  async add(data) {
    return await UserModel.create(data)
  }
}

部分代码出自个人 gitee ...