Koa实践

2021.2 星期 :

koa-router

##

3. 多个中间件

1
2
3
4
5
6
7
router.get('/admin/:user/*', (ctx, next) => {
ctx.body += ctx._matchedRoute + '--' + ctx.params.user + '\n';
next();
}, (ctx, next) => {
ctx.body += ' get admin/user\n';
next();
})

API

  1. router.routes()
    返回一个中间件,这个中间件根据请求分派路由
  2. router.allowedMethods([options])
    根据不同类型(也就是 options 参数)的请求允许请求头包含的方法,返回不同的中间件,以及响应 405 [不被允许] 和 501 [未实现]
    options => {throw: true} 抛出错误而不是设置返回状态码 statue 和标头 header
    options => {notImplemented: () => returnedValue} 使用这个函数抛出的值代替原来的 notImplemented 501 未实现错误
    options => {methodNotAllowed: () => returnedValue} 使用这个函数抛出的值代替原来的 notImplemented 405 不被允许错误

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 官网例子
    const Koa = require('koa');
    const Router = require('koa-router');
    const Boom = require('boom');

    const app = new Koa();
    const router = new Router();

    app.use(router.routes());
    app.use(router.allowedMethods({
    throw: true,
    notImplemented: () => new Boom.notImplemented(),
    methodNotAllowed: () => new Boom.methodNotAllowed()
    }));
  3. router.use([path], middleware)
    在路由中使用中间件,中间件运行的顺序是 .use() 方法调用的顺序
    path 允许调用中间件的路径,可以是一个路径字符串,也可以是路径组成的数组,不设置表示所有路径都可以使用
    middleware 要使用的中间件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const admin = new Router({
    prefix: '/admin'
    });

    admin.use(['/default/:user', '/test'], (ctx, next) => {
    ctx.body = 'in admin\n';
    ctx.body += ctx._matchedRoute + '--' + ctx.params.user + '\n';
    next();
    })

    admin.get('/**', (ctx) => {
    ctx.body += 'finish\n';
    })

    app.use(admin.routes());
    app.use(admin.allowedMethods());
  4. router.prefix(prefix)
    返回一个子路由,这个路由挂载在 router 上,并且设置了 prefix 前缀

  5. router.redirect(source, destination, code)
    重定向资源 source 到目的地地址 destination 使用 30x 状态码(code 定义)
  6. router.param(param, middleware)
    给路由参数 param 添加中间件,后续 router 路径中 含有 这个参数的,都会首先触发这个中间件,一般用于自动验证等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
admin
.param('user', (id, ctx, next) => {
ctx.state.user = users[id] || null;
if (!ctx.state.user) {
return ctx.status = 404;
}
return next();
})
.get('/abc/:user', (ctx, next) => {
ctx.body = '/admin/abc ' + JSON.stringify(ctx.state.user)
next();
})
.get('/bcd/:user', (ctx, next) => {
ctx.body = '/admin/bcd ' + JSON.stringify(ctx.state.user);
next();
})
.get('/cde/test', (ctx, next) => {
// 没有 user 参数 所以 不经过 param('user') ctx.state.user 不存在
ctx.body = '/admin/cde ' + JSON.stringify(ctx.state.user);
next();
})

其他中间件

## 统一返回格式 & 错误处理
在实现错误处理和统一返回格式之前,我们再做一点小小的改造。前面我们创建了 config 目录,里面存了一些常量配置,
接下来我们还会创建一个 common/utils.js 用来存放工具函数,
如果每个引用到的地方都 require 来引入是比较麻烦的,
所以我们把工具函数和常量配置放到 app.context 的属性上,之后就不用频繁引入了,可以通过 ctx.来访问

登录

koa-passport,koa-session中间件之

compose

这里引入了一个插件 koa-compose,作用是简化引用中间件的写法。到这里准备工作已经做好了,开始处理参数解析的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const Koa = require('koa');

const compose = require('koa-compose');
const MD = require('./middlewares/');

const app = new Koa();

const port = '8082'
const host = '0.0.0.0'

app.use(compose(MD));

app.listen(port, host, () => {
console.log(`API server listening on ${host}:${port}`);
});

koa-bodyparser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const koaBody = require('koa-bodyparser');
const router = require('../router');
/**
* 参数解析
* https://github.com/koajs/bodyparser
*/
const mdKoaBody = koaBody({
enableTypes: [ 'json', 'form', 'text', 'xml' ],
formLimit: '56kb',
jsonLimit: '1mb',
textLimit: '1mb',
xmlLimit: '1mb',
strict: true
});

/**
* 路由处理
*/
const mdRoute = router.routes();
const mdRouterAllowed = router.allowedMethods();

module.exports = [
mdKoaBody,
mdRoute,
mdRouterAllowed
];

cors

1
2
3
4
5
6
7
8
/**
* 跨域处理
*/
const mdCors = cors({
origin: '*',
credentials: true,
allowMethods: [ 'GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH' ]
});

koa-router 是可以添加多个路由级中间件的,我们将参数校验放在这里处理。
然后我们添加新的目录 schema,用来存放参数校验部分的代码,添加两个文件

@hapi/joi

app/middlewares/ 下添加 paramValidator.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
module.exports = paramSchema => {
return async function (ctx, next) {
let body = ctx.request.body;
try {
if (typeof body === 'string' && body.length) body = JSON.parse(body);
} catch (error) {}
const paramMap = {
router: ctx.request.params,
query: ctx.request.query,
body
};

if (!paramSchema) return next();

const schemaKeys = Object.getOwnPropertyNames(paramSchema);
if (!schemaKeys.length) return next();

// eslint-disable-next-line array-callback-return
schemaKeys.some(item => {
const validObj = paramMap[item];

const validResult = paramSchema[item].validate(validObj, {
allowUnknown: true
});

if (validResult.error) {
ctx.utils.assert(false, ctx.utils.throwError(9998, validResult.error.message));
}
});
await next();
};
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// app/index.js
const Koa = require('koa');
const router = require('./router')

const app = new Koa();

const port = '8082'
const host = '0.0.0.0'

app.use(router.routes());
/*
原先当路由存在,请求方式不匹配的时候,会报 404,
加了这个中间件,会报请求方式不被允许
*/
app.use(router.allowedMethods());


app.listen(port, host, () => {
console.log(`API server listening on ${host}:${port}`);
});

// app/router/index.js:
const koaRouter = require('koa-router');
const router = new koaRouter();

const routeList = require('./routes');
const paramValidator = require('../middlewares/paramValidator');

routeList.forEach(item => {
const { method, path, controller, valid } = item;
router[method](path, paramValidator(valid), controller);
});

module.exports = router;

// app/router/routes.js
const { test } = require('../controllers');
const routes = [
{
// 测试
method: 'get',
path: '/a',
controller: test.list
}
];
module.exports = routes;

// app/contronllers/index.js
const test = require('./test');
module.exports = {
test
};

// # validate
// app/schema/index.js
const scmTest = require('./test');
module.exports = {
scmTest
};
// app/schema/test.js

const Joi = require('@hapi/joi');
const list = {
query: Joi.object({
name: Joi.string().required(),
age: Joi.number().required()
})
};

module.exports = {
list
};

// app/router/routes.js
const { test } = require('../controllers');
const { scmTest } = require('../schema/index')

const routes = [
{
// 测试
method: 'get',
path: '/a',
valid: scmTest.list,
controller: test.list
}
];

module.exports = routes;

koa

中间价

中间件使用

中间件是一个函数(异步或者同步)处在 HTTP request(请求)与 HTTP response (响应)之间,用来实现某种中间功能 app.use() 来加载中间件。基本上,Koa 所有功能都是通过中间件来实现的,中间件函数会被传入两个参数:1) ctx context 对象,表示一次对话的上下文(requset和response);2) next 函数,调用 next 函数可以把执行权交给下一个中间件,下一个中间件执行完会把执行权再交回上一个中间件。如果中间件中有异步操作,需要使用 async、await 关键字,将其写成异步函数
————————————————
版权声明:本文为CSDN博主「mjzhang1993」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/mjzhang1993/article/details/78752314

数据库操作

当涉及到数据库操作时,我们可以在 app 下再新增一个 service 目录。 将数据库操作从 controller 目录下分离出来放在 service 目录下,两个目录各司其职,一个专注业务处理,一个专注数据库层面的增删改查。另外再添加一个 model 目录,用来定义数据库表结构,具体的这里暂时不介绍了。

  1. error 事件

运行过程中一旦出错,Koa 会触发一个 error 事件,监听这个事件,可以处理错误,但是中间件中如果错误被 try…catch 捕获,则不会触发 error 事件,这个时候可以调用 ctx.app.emit() 手动释放 error 事件
app.on(‘error’, function (err) {
console.log(‘error Event’, err);
})

app.use(async (ctx, next) => {
    ctx.body = 'in app';
    try {
        await next();    
    } catch (error) {
        ctx.status = 404;
        ctx.app.emit('error', error, ctx);
    }

});

app.use((ctx) => {
    ctx.body = 'error';
    ctx.throw('error here');
})

knowledge is no pay,reward is kindness
0%