koa简述

123 阅读2分钟

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。

  • 一句话简介
    • 基于node.js的下一代框架
    • 基于Node模块
    • 框架
    • 下一代,比第一代express
    • 官网:koa.bootcss.com/
    • web应用和api开发领域
    • 更小,更富有表现力,更健壮

一.初始化项目

1.安装koa

  • 初始化项目
npm init
  • 安装koa
npm i koa -S // 记录到package中
  • 新建index.js文件,编写代码
const Koa = require('koa')
const app = new Koa()
  • 返回hello
// use使用中间件
// 参数是一个函数
// 函数传入一个参数,上下文ctx
app.use((ctx)=>{
    ctx.body = 'hello world'
})
// 监听端口
app.listen(3000)
  • node index.js进行运行
  • 修改后自动重启
npm i nodemon -D

// package.json
script:{
	"start:mon":"nodemon index.js"
}

二.koa的中间件

1.koa多个中间件的执行顺序

  • 中间件洋葱模型
const Koa = require('koa')
const app = new Koa()

app.use(async (ctx,next)=>{
    await next()
    console.log(1)
    ctx.body = 'hello world'
})
app.use(async()=>{
	console.log(2)
})

// 监听端口
app.listen(3000)
// 执行顺序
// 2
// 1

三.路由

  • 决定了不同的URL是如何被不同的执行的
  • 在Koa中,路由的本质就是一个中间件
  • 路由的作用:
    • 区分不同请求,所做的事情不同
    • 处理不同的URL
    • 处理不同的请求方法
    • 解析URL上的参数

1.编写koa路由中间件

  • 作用:

    • 处理不同的URL
const Koa = require('koa')
const app = new Koa()

app.use((ctx)=>{
   if(ctx.url === '/'){
     ctx.body = '这是主页'
   }else if(ctx.url === '/users'){
   	ctx.body = '这是用户列表页
   }else {
   	ctx.status = 404
   }
})


// 监听端口
app.listen(3000)
  • 处理不同的请求方法
const Koa = require('koa')
const app = new Koa()

app.use((ctx)=>{
   if(ctx.method === 'GET'){
     ctx.body = '这是查询列表'
   }else if(ctx.method === 'POST'){
   	ctx.body = '这是新增操作
   }else {
   	ctx.status = 405 // 不允许这个方法
   }
})


// 监听端口
app.listen(3000)
  • 解析URL上的参数
// 用正则表达式来取出参数
const Koa = require('koa')
const app = new Koa()

app.use((ctx)=>{
   if(ctx.url.match(//user/\w+/){
      // 匹配'/user/:id的情况
      const userId = ctx.url.match(//user/(\w+)/)[1]
  		ctx.body = `这是用户${userId}`     
		}else {
			ctx.status = 404
		}
    
})

// 监听端口
app.listen(3000)

2.使用koa-router

  • 路由前缀
const Koa = require('koa')
const app = new Koa()
const Router = require('koa-router')
const router = new Router()
// 路由前缀
const userRouter = new Router({ prefix:'/users'})

router.get('/', (ctx) => {
    ctx.body = "这是主页"
})
// 等价于下面
userRouter.get('/', (ctx) => {
    ctx.body = "这是用户列表页"
})
// router.get('/users', (ctx) => {
//     ctx.body = "这是用户列表"
// })

router.post('/users', (ctx) => {
    ctx.body = "创建用户"
})
// 获取参数
router.post('/users/:id', (ctx) => {
  	// 获取参数
    ctx.body = `这是用户${ctx.params.id}`
})
app.use(router.routes())
app.use(userRouter.routes())
app.listen(3000)
  • 多中间件,比如用户鉴权
const Koa = require('koa')
const app = new Koa()
const Router = require('koa-router')
const router = new Router()
// 路由前缀
const userRouter = new Router({ prefix:'/users'})
// 鉴权
const auth = async(ctx,next)=>{
	if(ctx.url !== '/users'){
		ctx.throw(401)
  }
  await next()
}
router.get('/', (ctx) => {
    ctx.body = "这是主页"
})
// 注意用户鉴权添加的地方
// 可以传递多个,无限传递
userRouter.get('/',auth, (ctx) => {
    ctx.body = "这是用户列表页"
})


userRouter.post('/',auth, (ctx) => {
    ctx.body = "创建用户"
})
// 获取参数
userRouter.post('/:id',auth, (ctx) => {
  	// 获取参数
    ctx.body = `这是用户${ctx.params.id}`
})
app.use(router.routes())
app.use(userRouter.routes())
app.listen(3000)

3.HTTP中的options方法

  • 检测服务器所支持的方法
  • 可以在CORS里面进行预检请求
  • allowedMethods方法的作用
// 加上他,所有的接口都能支持options请求
app.use(userRouter.allowedMethods())
  • 可以相应的返回405(不允许)和501(没实现)两个状态码

4. 增删改查的响应

  • 新增用户应该返回新增的数据
  • 修改应该返回最后修改的哪一项
  • 删除返回204,成功但是没有信息
  • 查询返回一个列表数据

四.控制器

  • 路由是根据不同的URL,分配不同的任务
  • 控制器就是拿到路由分配的任务,并执行
  • 所以控制器也是一个中间件
  • 断点调试
    • F5进行运行
    • postman或者rest发起请求
    • 打上断点
    • 查看断点处的信息
  • 断点一般设置在有断言的地方 设置的是一个变量的赋值 是有可能断不到的 可以尝试 在 if 判断、foreach、类、函数中去断点调试
  • 控制器主要作用有以下三部分

1.获取请求参数

  • Query String,比如?query=keyword,通常是可选的
    • 通过ctx.query(Object)获得,断点调试可以看到
  • Router param,比如/users/:id,通常是必选的
    • ctx.params(Object)上获取
  • Body,比如{ key: "value" }
    • 需要额外的安装中间件koa-bodyparser
const app = new Koa()
const Router = require('koa-router')
// 引入
const bodyParser = require('koa-bodyparser')
const router = new Router()

// 注册到app上
app.use(bodyParser())
    • 然后就可以在ctx.request.body(Object)上看到请求的body了
  • Header,如Accept,Cookie等
    • ctx.header(Object)获取

2.发送HTTP响应

  • 发送状态Status,如200/400等
    • ctx.status = 204
  • 发送Body,如{ key: value }
    • ctx.body = { key: value } 或者是字符串,甚至是html的字符串
  • 发送Header,如Allow,Content-Type
    • ctx.set('Allow','GET,POST')// 通过set设置

3.处理业务逻辑

  • 编写控制器的最佳实践
    • 每个控制器放在不同的文件里
    • 尽量使用类+类方法编写控制器
    • 要有严谨的错误处理

五.合理的目录结构

  • 将路由单独放到一个目录
  • 将控制器单独放到一个目录
  • 使用类+类的方法组织控制器

六.异常/错误处理

  • 编程语言或者计算机硬件中的一种机制
  • 处理软件或者信息系统中的异常情况

1.常见的异常状况

  • 运行时错误,指的是语法没有错,但运行的时候报错。比如求undefined中的属性。返回500,代表服务器内部错误。
  • 常见的逻辑错误
    • 找不到,找不到某个网页,找不到某个接口,404。
    • 先决条件失败,412
    • 无法处理的实体,422

2.为什么要用错误处理

  • 防止程序挂掉
  • 告诉用户错误信息
  • 便于开发者调试

3.koa自带的错误处理

  • 404:404不用做任何处理,404是客户端的问题,只要请求一个不存在的接口,就会报错404(如果是不被允许的方法,也会返回相应的错误)
  • 412:需要手动抛出
findById = (ctx) => {
  if(ctx.param.id * 1 >= this.db.length){
		ctx.throw(412,'返回的文本')
  }
    ctx.body = this.db[ctx.params.id * 1];
  };
  • 500:不用做处理
findById = (ctx) => {
  if(ctx.param.id * 1 >= this.db.length){
		ctx.throw(412,'返回的文本')
  }
  // 设置一个不存在的变量,出现500错误,不用任何抛出
  a.b
    ctx.body = this.db[ctx.params.id * 1];
  };

4.编写错误处理中间件

  • 要放到所有中间件的最前面
  • 把next函数放到trycatch中即可
  • 通过断点调试,查看err中的属性,ctx.throw抛出的错误
app.use(async(ctx,next)=>{
		try {
    	await next()
    } catch(err) {
    	ctx.status = err.status || err.statusCode || 500
      ctx.body = {
        message:err.message
    }
})

5.koa-json-error处理错误

  • 安装koa-json-error
  • 使用其配置处理错误
const error = require('koa-json-error')

app.use(error(
    {
        postFormat:(e,{stack, ...rest})=>process.env.NODE_ENV === 'production' ? rest : { stack, ...rest }
    }
))
  • 生产环境禁用错误堆栈返回
  "scripts": {
    "mon": "nodemon app/index.js",
    "pro":"cross-env NODE_ENV=production node app/index.js"
  },

6.koa-parameter校验参数


const Koa = require("koa");
const app = new Koa();
const routing = require("./routes");
const bodyParser = require("koa-bodyparser");
const error = require('koa-json-error')
const parameter = require('koa-parameter')
const port = 3000;

app.use(error(
    {
        postFormat:(e,{stack, ...rest})=>process.env.NODE_ENV === 'production' ? rest : { stack, ...rest }
    }
))
app.use(bodyParser());
app.use(parameter(app))
routing(app);
app.listen(port,()=>{
    console.log(`程序启动在${port}端口`)
});


class UserCtl {
  db = [];
  find = (ctx) => {
    ctx.body = this.db;
  };
  findById = (ctx) => {
    ctx.body = this.db[ctx.params.id * 1];
  };
  create = (ctx) => {
    ctx.verifyParams({
      name: { type: "string", require: true },
      age: { type: "number", require: false },
    });
    this.db.push(ctx.request.body);
    ctx.body = ctx.request.body;
  };
  update = (ctx) => {
    this.db[ctx.params.id * 1] = ctx.request.body;
    ctx.body = ctx.request.body;
  };
  del = (ctx) => {
    this.db.splice(ctx.params.id * 1, 1);
    ctx.status = 204;
  };
}
module.exports = new UserCtl();

遇到的问题

1.TS中使用const a = require('a')报错

  • 因为在 Commonjs 规范里,没有像 ESModule 能形成闭包的「模块」概念,所有的模块在引用时都默认被抛至全局,因此当再次声明某个模块时,TypeScript 会认为重复声明了两次相同的变量进而抛错。
  • 使用import解决
  • 文件添加export {},欺骗编译器是一个模块解决,但babel会无法编译export{}
  • 参考文档