Koa2学习

一、基础知识

什么是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-timelogging 中间件,并记录中间件执行起始时间。 然后将控制权交给 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来转换成新的中间件,最后将中间件 pushmiddleware 数组里面。

 /**
   * 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 上下文将 requestresponse 对象封装到单个对象中,为编写 web 应用程序和 API 提供了许多有用的方法。

接下来就是启动服务,执行:

$ node app.js

App started on http://localhost:3000

浏览器访问 http://localhost:3000/ , 可以看到显示 “Hello World” 字样,证明我们服务已经搭建好。

为了方便,我们将这个命令配置在 package.jsonscript 中:

// 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 请求方式: getputpostpatchdeletedel ,而使用方法就是 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上获取特定参数,主要分为两类: paramsquery 。 这两种参数获取的方式如下:

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 对象的工具,提供了 responseerror 记录前面提到的必要信息,接下来就需要在中间件里面去使用。

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/

2.koa-generator创建的koa2框架目录

请添加图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值