一、基础知识
什么是koa
Koa 基于 Node.js 平台的下一代 Web 开发框架,由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架。和 Express 不同,使用 Koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升错误处理的效率。
阿里基于 Koa 开发了 Egg.js 企业级框架,在 Koa 的模型基础上,进一步对它进行了一些增强,可以帮助开发团队和开发人员降低开发和维护成本。
异步编程模型
Node.js 是以回调函数(callback)形式进行的异步编程模型,这会带来许多问题,例如:回调函数嵌套,同步调用回调等。
为了解决异步编程的问题,可以采用 Promise(ES2015)、Generator、Async/Await(ES2017) 等方案。
Async function
Async function 是一个语法糖,在 async function 中,我们可以通过 await
关键字来等待一个 Promise 被 resolve(或者 reject,此时会抛出异常), Node.js 在 8.x 的 LTS 版本开始原生支持。
const fn = async function() {
const user = await getUser();
const posts = await fetchPosts(user.id);
return { user, posts };
};
fn().then(res => console.log(res)).catch(err => console.error(err.stack));
Koa 的 app.listen()
做了什么?
先看一下 koa 的 application.js
文件里面的源代码:
/**
* Shorthand for:
*
* http.createServer(app.callback()).listen(...)
*
* @param {Mixed} ...
* @return {Server}
* @api public
*/
listen(...args) {
debug('listen');
const server = http.createServer(this.callback()); // 这里传入了koa的 callback()
return server.listen(...args);
}
在上面的代码中可以看到, app.listen(…)
实际上是http.createServer(app.callback()).listen(…)
方法的语法糖。调用 app.listen()
实际上就是去调用 node.js 的 http 模块来创建服务,并且创建成功之后会回调 app 的 callback
。
Koa 的 callback
做了什么?
从上面的代码中,我们看到在创建服务时,koa 使用了this.callback()
,这个 callback
具体做了什么呢?我们先来看源码:
const compose = require('koa-compose');
// ...
/**
* Return a request handler callback
* for node's native http server.
*
* @return {Function}
* @api public
*/
callback() {
const fn = compose(this.middleware);// 合并this.middleware里面的中间件
if (!this.listenerCount('error')) this.on('error', this.onerror);
const handleRequest = (req, res) => {// 这里接收了node.js的原生请求和响应
const ctx = this.createContext(req, res); // 在这里创建了上下文
return this.handleRequest(ctx, fn); // 把上下文交由 handleRequest 处理
};
return handleRequest;
}
首先是使用 compose
将应用的中间件 this.middleware
进行了合并,this.middleware
是一个数组,当我们调用 app.use(function)
来使用中间件的时候,会将中间件方法 push
到这个数据里面。
然后返回一个方法 handleRequest
来处理 node 的 http 请求。在 handleRequest
中接收请求时,不仅创建了上下文 ctx
,而且还调用了应用本身的 handleRequest
函数来处理请求。这其中有几个我们需要关心的东西:
compose
——koa-compose
中间件,用来对中间件进行合并createContext
—— 用来创建上下文handleRequest
——用来处理请求
后面会对其进行介绍。
什么是中间件
Koa 中间件是一个函数,是接收到请求到处理逻辑之间、处理逻辑到发送响应之间执行的一逻辑:
客户端请求到服务端-->中间件1-->中间件2-->服务端业务代码-->中间件2-->中间件1-->服务端响应到客户端
怎么使用中间件
Koa 的 app.use(function)
方法可以使用中间件,首先需要知道中间件是一个 function
,如:
app.use(async (ctx, next) => { // 中间件是一个function,可以接收两个参数:ctx和next
const start = Date.now(); // 请求阶段执行的逻辑
await next(); // 将请求的执行逻辑跳转到下一个中间件
const ms = Date.now() - start; // 响应阶段执行的逻辑
ctx.set('X-Response-Time', `${ms}ms`);
});
中间件使用示例
下面是来自Koa的例子,在页面中返回 “Hello World”,然而当请求开始时,请求先经过 x-response-time
和 logging
中间件,并记录中间件执行起始时间。 然后将控制权交给 reponse 中间件。当一个中间件调用next()
函数时,函数挂起并控件传递给定义的下一个中间件。在没有更多的中间件执行下游之后,堆栈将退出,并且每个中间件被恢复以执行其上游行为。
const Koa = require('koa');
const app = new Koa();
// x-response-time
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
// logger
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});
// response
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
中间件级联
中间件类似于一个过滤器,在客户端和应用程序之间处理请求和响应。
.middleware1 {
// (1) do some stuff
.middleware2 {
// (2) do some other stuff
.middleware3 {
// (3) NO next yield !
// this.body = 'hello world'
}
// (4) do some other stuff later
}
// (5) do some stuff lastest and return
}
中间件的执行很像一个洋葱,但并不是一层一层的执行,而是以next为分界,先执行本层中next以前的部分,当下一层中间件执行完后,再执行本层next以后的部分。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wLhXO7Qr-1629605663209)(C:\Users\89404\AppData\Roaming\Typora\typora-user-images\image-20210822093132753.png)]
let koa = require('koa');
let app = new koa();
app.use((ctx, next) => {
console.log(1)
next(); // next不写会报错
console.log(5)
});
app.use((ctx, next) => {
console.log(2)
next();
console.log(4)
});
app.use((ctx, next) => {
console.log(3)
ctx.body = 'Hello World';
});
app.listen(3000);
// 打印出1、2、3、4、5
上述简单的应用打印出1、2、3、4、5,这就是一个洋葱结构,从上往下一层一层进来,再从下往上一层一层回去,解决复杂应用中频繁的回调而设计的级联代码,并不直接把控制权完全交给下一个中间件,而是碰到next去下一个中间件,等下面都执行完了,还会执行next以下的内容。
app.use(function)
做了什么
app.use(fn)
将给定的中间件方法添加到此应用的 middleware
数组。
当我们执行use()
时,会先判断传递的中间件是否是一个函数,如果不是就报出错误:
middleware must be a function!
再判断中间件是否是旧版的生成器 generator
,如果是,就使用 koa-convert
来转换成新的中间件,最后将中间件 push
到 middleware
数组里面。
/**
* Use the given middleware `fn`.
*
* Old-style middleware will be converted.
*
* @param {Function} fn
* @return {Application} self
* @api public
*/
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
if (isGeneratorFunction(fn)) {
deprecate('Support for generators will be removed in v3. ' +
'See the documentation for examples of how to convert old middleware ' +
'https://github.com/koajs/koa/blob/master/docs/migration.md');
fn = convert(fn);
}
debug('use %s', fn._name || fn.name || '-');
this.middleware.push(fn); // 中间件添加到数组
return this;
}
从上面的源码我们可以看出,当我们在应用里面使用多个中间件时,koa
都会将它们放在自身的一个数组 middleware
中。
二、初始化项目
初始化项目
新建一个文件夹 koa-blog
,先来初始化目录结构:
$ mkdir koa-blog
$ cd koa-blog
$ npm init -y
当然,我们还得安装 Koa:
$ npm install koa --save
执行完上面的命令之后,得到了下面的目录结构:
koa-blog
└──package.json
项目入口
我们来创建一个HTTP服务,实现 “Hello World”:
// app.js
const Koa = require('koa');
const app = new Koa();
// 响应
app.use(ctx => {
ctx.response.body = 'Hello World';
});
app.listen(3000, () => {
console.log('App started on http://localhost:3000')
});
在上面的代码中,app.use()
指定了一个中间件方法,这个中间件接收 Koa 创建的上下文(context),并且修改了 response.body
发送给客户端。
Koa 上下文将 request
和 response
对象封装到单个对象中,为编写 web 应用程序和 API 提供了许多有用的方法。
接下来就是启动服务,执行:
$ node app.js
App started on http://localhost:3000
浏览器访问 http://localhost:3000/ , 可以看到显示 “Hello World” 字样,证明我们服务已经搭建好。
为了方便,我们将这个命令配置在 package.json
的 script
中:
// package.json
{
// ...
"main": "app.js",
"scripts": {
"start": "node app.js"
},
// ...
}
以后只要在命令行执行 npm start
即可启动服务。
三、koa-router
创建Koa应用
下面的代码创建了一个koa web服务,监听了3000端口,如果访问 http://localhost:3000/ 将返回 Not Found
,这是因为代码没有对请求做任何响应。后面将使用 koa-router 在这个基础上进行修改,使其支持不同的路由匹配。
// app.js
const Koa = require('koa'); // 引入koa
const app = new Koa(); // 创建koa应用
// 启动服务监听本地3000端口
app.listen(3000, () => {
console.log('应用已经启动,http://localhost:3000');
})
安装koa-router
$ npm install koa-router --save
使用koa-router
首先,使用 require()
引入 koa-router
,并且对其实例化(支持传递参数),然后使用获取到的路由实例 router
设置一个路径,将 '/'
匹配到相应逻辑,返回一段HTML 。接着还需要分别调用 router.routes()
和 router.allowedMethods()
来得到两个中间件,并且调用 app.use()
使用这两个中间件:
const Koa = require('koa'); // 引入koa
const Router = require('koa-router'); // 引入koa-router
const app = new Koa(); // 创建koa应用
const router = new Router(); // 创建路由,支持传递参数
// 指定一个url匹配
router.get('/', async (ctx) => {
ctx.type = 'html';
ctx.body = '<h1>hello world!</h1>';
})
// 调用router.routes()来组装匹配好的路由,返回一个合并好的中间件
// 调用router.allowedMethods()获得一个中间件,当发送了不符合的请求时,会返回 `405 Method Not Allowed` 或 `501 Not Implemented`
app.use(router.routes());
app.use(router.allowedMethods({
// throw: true, // 抛出错误,代替设置响应头状态
// notImplemented: () => '不支持当前请求所需要的功能',
// methodNotAllowed: () => '不支持的请求方式'
}));
// 启动服务监听本地3000端口
app.listen(3000, () => {
console.log('应用已经启动,http://localhost:3000');
})
使用
不同请求方式
Koa-router 请求方式: get
、 put
、 post
、 patch
、 delete
、 del
,而使用方法就是 router.方式()
,比如 router.get()
和 router.post()
。而 router.all()
会匹配所有的请求方法。
当 URL 匹配成功,router
就会执行对应的中间件来对请求进行处理,下面是使用示例:
// ...
// 指定一个url匹配
router.get('/', async (ctx) => {
ctx.type = 'html';
ctx.body = '<h1>hello world!</h1>';
})
.get("/users", async (ctx) => {
ctx.body = '获取用户列表';
})
.get("/users/:id", async (ctx) => {
const { id } = ctx.params
ctx.body = `获取id为${id}的用户`;
})
.post("/users", async (ctx) => {
ctx.body = `创建用户`;
})
.put("/users/:id", async (ctx) => {
const { id } = ctx.params
ctx.body = `修改id为${id}的用户`;
})
.del("/users/:id", async (ctx) => {
const { id } = ctx.params
ctx.body = `删除id为${id}的用户`;
})
.all("/users/:id", async (ctx) => {
ctx.body = ctx.params;
});
// ...
从请求参数取值
有些时候需要从请求URL上获取特定参数,主要分为两类: params
和 query
。 这两种参数获取的方式如下:
params参数
router.get('/:category/:title', (ctx, next) => {
console.log(ctx.params);
// => { category: 'programming', title: 'how-to-node' }
});
query参数
router.get("/users", async (ctx) => {
console.log('查询参数', ctx.query);
ctx.body = '获取用户列表';
})
路由使用中间件
router
还支持使用中间件,并且可以针对特定的URL或者多个URL使用中间件:
// 先后设置两个中间件
router
.use(session())
.use(authorize());
// 给指定地址使用中间件
router.use('/users', userAuth());
// 给数组里面的地址使用中间件
router.use(['/users', '/admin'], userAuth());
app.use(router.routes());
设置路由前缀
可以通过调用 router.prefix(prefix)
来设置路由的前缀,也可以通过实例化路由的时候传递参数设置路由的前缀,比如在 RESTful 接口里面,往往会为接口设置一个 api
前缀,如:
router.prefix('/api')
// 或者
const router = new Router({
prefix: '/api'
})
当然也支持设置参数:
router.prefix('/路径/:参数')
路由嵌套
有时路由涉及到很多业务模块,可能需要对模块进行拆分和嵌套,koa-router 提供了路由嵌套的功能,使用也很简单,就是创建两个 Router
实例,然后将被嵌套的模块路由作为父级路由的中间件使用:
var forums = new Router();
var posts = new Router();
posts.get('/', (ctx, next) => {...});
posts.get('/:pid', (ctx, next) => {...});
forums.use('/forums/:fid/posts', posts.routes(), posts.allowedMethods());
// responds to "/forums/123/posts" and "/forums/123/posts/123"
app.use(forums.routes());
拆分路由
通过路由嵌套可以对路由进行拆分,不同的模块使用不同的文件,如下面的示例:
app.js
只引入路由入口文件
const Koa = require('koa'); // 引入koa
const router = require('./router');
const app = new Koa(); // 创建koa应用
app.use(router.routes());
app.use(router.allowedMethods());
// 启动服务监听本地3000端口
app.listen(3000, () => {
console.log('应用已经启动,http://localhost:3000');
})
router/user.js
设置了 user
模块的路由,并且导出:
const Router = require('koa-router');
const router = new Router();
router.get("/", async (ctx) => {
console.log('查询参数', ctx.query);
ctx.body = '获取用户列表';
})
.get("/:id", async (ctx) => {
const { id } = ctx.params
ctx.body = `获取id为${id}的用户`;
})
.post("/", async (ctx) => {
ctx.body = `创建用户`;
})
.put("/:id", async (ctx) => {
const { id } = ctx.params
ctx.body = `修改id为${id}的用户`;
})
.del("/:id", async (ctx) => {
const { id } = ctx.params
ctx.body = `删除id为${id}的用户`;
})
.all("/users/:id", async (ctx) => {
ctx.body = ctx.params;
});
module.exports = router;
router/index.js
导出了整个路由模块:
const Router = require('koa-router');
const user = require('./user');
const router = new Router();
// 指定一个url匹配
router.get('/', async (ctx) => {
ctx.type = 'html';
ctx.body = '<h1>hello world!</h1>';
})
router.use('/user', user.routes(), user.allowedMethods());
module.exports = router;
命名路由
router.get('user', '/users/:id', (ctx, next) => {
// ...
});
router.url('user', 3);
// => "/users/3"
通过 ctx._matchedRoute
获得匹配的路由,通过 ctx._matchedRouteName
获得匹配的路由名。
设置多个中间件
router.get(
'/users/:id',
(ctx, next) => {
return User.findOne(ctx.params.id).then(function(user) {
ctx.user = user;
next();
});
},
ctx => {
console.log(ctx.user);
// => { id: 17, name: "Alex" }
}
);
路由重定向
使用 router.redirect(source, destination, [code])
可以对路由进行重定向,例子:
router.redirect('/login', 'sign-in');
等价于:
router.all('/login', ctx => {
ctx.redirect('/sign-in');
ctx.status = 301;
});
四、koa-nunjucks-2
为了便于读取模板和渲染页面,我们将使用中间件 koa-nunjucks-2 来作为模板引擎
首先安装 koa-nunjucks-2 :
$ npm i koa-nunjucks-2 --save
在使用路由中间件之前应用 koa-nunjucks-2:
// app.js
const Koa = require('koa');
const app = new Koa();
// 引入模板引擎
const koaNunjucks = require('koa-nunjucks-2');
const path = require('path');
// 引入路由文件
const router = require('./app/router');
// 使用模板引擎
app.use(koaNunjucks({
ext: 'html',
path: path.join(__dirname, 'app/view'),
nunjucksConfig: { // 这里是nunjucks的配置
/*
* autoescape (默认值: true) 控制输出是否被转义,查看 Autoescaping
* throwOnUndefined (default: false) 当输出为 null 或 undefined 会抛出异常
* trimBlocks (default: false) 自动去除 block/tag 后面的换行符
* lstripBlocks (default: false) 自动去除 block/tag 签名的空格
* watch (默认值: false) 当模板变化时重新加载。使用前请确保已安装可选依赖 chokidar。
* noCache (default: false) 不使用缓存,每次都重新编译
* web 浏览器模块的配置项
* useCache (default: false) 是否使用缓存,否则会重新请求下载模板
* async (default: false) 是否使用 ajax 异步下载模板
* express 传入 express 实例初始化模板设置
* tags: (默认值: see nunjucks syntax) 定义模板语法,查看 Customizing Syntax
*/
trimBlocks: true
}
}));
// 使用中间件 处理404
app.use(async (ctx, next) => {
await next(); // 调用next执行下一个中间件
if(ctx.status === 404) {
await ctx.render('404'); // 当使用了模板引擎后,ctx中会有一个render函数供渲染使用
}
});
// 使用koa-router中间件。此处`app.use(nunjucks({}))` 放在 `app.use(router.routes()).use(router.allowedMethods())` 前面才行,否则会报 `ctx.render()` 不是一个 `function` 。
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, () => {
console.log('App started on http://localhost:3000')
});
// app/router/index.js
const Router = require('koa-router');
const router = new Router();
// koa-router
const index = async (ctx, next) => {
await ctx.render('index', {title: 'Index', link: 'home'});
};
const home = async (ctx, next) => {
await ctx.render('index', {title: 'Home', link: 'index'});
};
router.get('/', index);
router.get('/index', index);
router.get('/home', home);
module.exports = router;
我们的路由统一使用了 app/view/index.html
作为模板文件,因此删除没有用到的文件 app/view/home.html
,在 app/view/index.html
中我们接收了传递的参数:
<!--app/view/index.html-->
<h1>{{title}} Page</h1>
<a href="/{{link}}">home</a>
五、log4js
log4js-node 是经过对 log4js 框架进行转换来支持 node 的,在开始实战之前,先耐心来看下 log4js 的基本使用方式:
const log4js = require("log4js"); // 引入 log4js
const logger = log4js.getLogger(); // 获得 default category
logger.level = "debug"; // 设置 level
logger.debug("调试信息"); // 输出日志
// 输出: [2020-10-31T16:02:24.527] [DEBUG] default - 调试信息
再来看一个示例:
const log4js = require("log4js");
// 对日志进行配置
log4js.configure({
// 指定输出文件类型和文件名
appenders: { cheese: { type: "file", filename: "cheese.log" } },
// appenders 指定了日志追加到 cheese
// level 设置为 error
categories: { default: { appenders: ["cheese"], level: "error" } }
});
const logger = log4js.getLogger(); // 获取到 default 分类
logger.trace("Entering cheese testing");
logger.debug("Got cheese.");
logger.info("Cheese is Comté.");
logger.warn("Cheese is quite smelly.");
logger.error("Cheese is too ripe!"); // 从这里开始写入日志文件
logger.fatal("Cheese was breeding ground for listeria.");
从上面的设置看到 appenders
指定了日志追加到 cheese
(也就是cheese.log)里面去,level
设置为 "error"
,也就是说只有日志等级大于 "error"
的才会添加到 log 文件。
当执行了上面的代码,可以看到项目目录里面多了一个 cheese.log 文件,内如如下:
[2020-10-31T16:26:17.188] [ERROR] default - Cheese is too ripe!
[2020-10-31T16:26:17.194] [FATAL] default - Cheese was breeding ground for listeria.
下面来进入本节的实战…
安装 log4js
前面对 log4js-node 进行了简单介绍,现在来在应用里面使用,首先安装 log4js:
$ npm install log4js --save
设置 log4js
我们修改 config/config.js
,来对 log4js 编写一些配置:
// config/config.js
const CONFIG = {
"API_PREFIX": "/api", // 配置了路由前缀
"LOG_CONFIG":
{
"appenders": {
"error": {
"category": "errorLogger", // logger 名称
"type": "dateFile", // 日志类型为 dateFile
"filename": "logs/error/error", // 日志输出位置
"alwaysIncludePattern": true, // 是否总是有后缀名
"pattern": "yyyy-MM-dd-hh.log" // 后缀,每小时创建一个新的日志文件
},
"response": {
"category": "resLogger",
"type": "dateFile",
"filename": "logs/response/response",
"alwaysIncludePattern": true,
"pattern": "yyyy-MM-dd-hh.log"
}
},
"categories": {
"error": {
"appenders": ["error"], // 指定日志被追加到 error 的 appenders 里面
"level": "error" // 等级大于 error 的日志才会写入
},
"response": {
"appenders": ["response"],
"level": "info"
},
"default": {
"appenders": ["response"],
"level": "info"
}
}
}
};
module.exports = CONFIG;
写好了配置,接下来就是使用配置,先来看一下使用的代码示例:
const log4js = require("log4js");
const CONFIG = require('./config/config');
// 对日志进行配置
log4js.configure(CONFIG);
// 分别获取到 categories 里面的 error 和 response 元素
// 目的是为了输出错误日志和响应日志
const errorLogger = log4js.getLogger('error');
const resLogger = log4js.getLogger('response');
// 输出日志
errorLogger.error('错误日志');
resLogger.info('响应日志');
运行完成之后,可以在 log 目录查看到对应的日志文件,里面的内容分别如下:
错误日志
[2020-10-31T17:12:37.263] [ERROR] error - 错误日志
响应日志
[2020-10-31T17:12:37.265] [INFO] response - 响应日志
到这里一切正常工作,接下来就要将 Koa 应用的每次请求响应、报错等信息以一定的格式存入日志,我们自然想到日志格式和中间件,下面逐一来看怎么实现。
日志格式
我们关心用户请求的信息有哪些?这里列出本节关注的日志内容,包括访问方法、请求原始地址、客户端 IP、响应状态码、响应内容、错误名称、错误信息、错误详情、服务器响应时间。
为了使请求产生的 log 方便查看,新增一个文件 app/util/log_format.js
来统一格式:
// app/util/log_format.js
const log4js = require('log4js');
const { LOG_CONFIG } = require('../../config/config'); //加载配置文件
log4js.configure(LOG_CONFIG);
let logFormat = {};
// 分别获取到 categories 里面的 error 和 response 元素
// 目的是为了输出错误日志和响应日志
let errorLogger = log4js.getLogger('error');
let resLogger = log4js.getLogger('response');
//封装错误日志
logFormat.error = (ctx, error, resTime) => {
if (ctx && error) {
errorLogger.error(formatError(ctx, error, resTime));
}
};
//封装响应日志
logFormat.response = (ctx, resTime) => {
if (ctx) {
resLogger.info(formatRes(ctx, resTime));
}
};
//格式化响应日志
const formatRes = (ctx, resTime) => {
let responserLog = formatReqLog(ctx.request, resTime); // 添加请求日志
responserLog.push(`response status: ${ctx.status}`); // 响应状态码
responserLog.push(`response body: \n${JSON.stringify(ctx.body)}`); // 响应内容
responserLog.push(`------------------------ end\n`); // 响应日志结束
return responserLog.join("\n");
};
//格式化错误日志
const formatError = (ctx, err, resTime) => {
let errorLog = formatReqLog(ctx.request, resTime); // 添加请求日志
errorLog.push(`err name: ${err.name}`); // 错误名称
errorLog.push(`err message: ${err.message}`); // 错误信息
errorLog.push(`err stack: ${err.stack}`); // 错误详情
errorLog.push(`------------------------ end\n`); // 错误信息结束
return errorLog.join("\n");
};
// 格式化请求日志
const formatReqLog = (req, resTime) => {
let method = req.method;
// 访问方法 请求原始地址 客户端ip
let formatLog = [`\n------------------------ ${method} ${req.originalUrl}`, `request client ip: ${req.ip}`];
if (method === 'GET') { // 请求参数
formatLog.push(`request query: ${JSON.stringify(req.query)}\n`)
} else {
formatLog.push(`request body: ${JSON.stringify(req.body)}\n`)
}
formatLog.push(`response time: ${resTime}`); // 服务器响应时间
return formatLog;
};
module.exports = logFormat;
这段 JavaScript 最终返回了一个 logFormat
对象的工具,提供了 response
和 error
记录前面提到的必要信息,接下来就需要在中间件里面去使用。
logger 中间件
有了 log4js 的配置并且统一格式之后,我们需要将它们都做进一个中间件中,这样才能对每次请求和响应生效,下面来创建一个中间件 logger
:
// app/middleware/logger.js
const logFormat = require('../util/log_format');
const logger = () => {
return async (ctx, next) => {
const start = new Date(); //开始时间
let ms; //间隔时间
try {
await next(); // 下一个中间件
ms = new Date() - start;
logFormat.response(ctx, `${ms}ms`); //记录响应日志
} catch (error) {
ms = new Date() - start;
logFormat.error(ctx, error, `${ms}ms`); //记录异常日志
}
}
};
module.exports = logger;
中间件 logger
已经建立好,下面来使用这个中间件:
// ...
// 引入logger
const logger = require('./app/middleware/logger');
// 使用模板引擎
// ...
app.use(logger()); // 处理log的中间件
// ...
app.listen(3000, () => {
console.log('App started on http://localhost:3000/api')
});
都设置好了之后,执行 npm start
,当启动成功之后,我们看到项目多了一个目录 logs
,里面有两个文件,分别是报错日志和响应日志。在浏览器中访问 http://localhost:3000/api ,可以看到响应日志里面添加了刚刚的访问记录。
六、Koa2项目初始化操作
1. koa-generator快速生成koa服务的脚手架工具
1.1 全局安装脚手架
cnpm install -g koa-generator 或者 yarn global add koa-generator
1.2 进入到项目文件夹目录,执行生成命令
# koa2 + 项目名
koa2 manager-server
1.3 安装依赖
npm install
or
cnpm install
or
yarn
1.4 启动服务
yarn start
or
node .bin/ww
#默认访问地址localhost:3000/