node基础复习

node api 三种调用方式

  1. // 1.一般 node调用api使用的是callback方式
  2. fun('./index1.js', (err, data) => {
  3. console.log(err ? 'read err' : data)
  4. })
  5. // 模拟实现
  6. function fun(arg, callback) {
  7. try {
  8. aaa() // 执行一些内部操作
  9. callback(null, 'result') // 如果执行成功,err设置为null, 结果通过第二参数返回
  10. } catch(e) {
  11. callback(e)
  12. }
  13. }
  14. // 通过 promisify 改造后的fun函数
  15. const { promisify } = require('util')
  16. const promisefun = promisify(fun)
  17. // 2.promise方式调用
  18. promisefun('./index1.js').then((data) => {
  19. console.log(data)
  20. }, (err) => {
  21. // 如果后面有.catch 这里的优先级会高一点
  22. console.log(err)
  23. })
  24. // 或者
  25. promisefun('./index1.js').then((data) => {
  26. console.log(data)
  27. }).catch(err => {
  28. console.log('read err')
  29. })
  30. // 3.通过async/await 调用promise函数
  31. // await 需要用 async 函数包裹
  32. setTimeout(async () => {
  33. try {
  34. let data = await promisefun('./index1.js')
  35. console.log(data)
  36. } catch(e) {
  37. console.log('read err')
  38. }
  39. }, 0)

util模块内置 promisify 实现

promisify 可以把老的callback方式,转换为promise函数,怎么实现的呢?

  1. // 普通callback方式
  2. function fun(arg, callback) {
  3. try {
  4. aaa() // 执行一些内部操作
  5. callback(null, 'result', 'result2') // 如果执行成功,err设置为null, 结果通过第二参数返回
  6. } catch(e) {
  7. callback(e)
  8. }
  9. }
  10. // promisify模拟实现
  11. function promisify(fun) {
  12. // 生成的函数,会接收一个参数arg,数据和错误,需要我们在promise内部用reject或resolve传出结果
  13. return function(...args) {
  14. // 将传入的参数保存到args数组
  15. return new Promise((resolve, reject) => {
  16. // 将callback函数push到参数数组里,再间接调用fun
  17. args.push((err, result) => {
  18. // 如果fun函数执行成功会执行该函数并传入 (null, result)
  19. // 如果fun函数执行错误会执行该函数并传入 (err)
  20. // resolve() 只能接受并处理一个参数,多余的参数会被忽略掉。 spec上就是这样规定。
  21. // 如果回调函数,传出了多个参数,可以将该函数result换为 ...result
  22. // 然后resove时判断下,如果 result数组长度为0 直接resolve(result[0]),否则resove(result数组),接收参数时需要注意
  23. err ? reject(err) : resolve(result)
  24. })
  25. fun.apply(null, args)
  26. })
  27. }
  28. }
  29. // 测试
  30. let promisefun = promisify(fun)
  31. promisefun('./index1.js').then((data) => {
  32. console.log(data)
  33. }, (err) => {
  34. // 如果后面有.catch 这里的优先级会高一点
  35. console.log('read err')
  36. })

Koa

Koa是由 Express 原班人马打造的致力于成为一个更小、更富有表现力、更健壮的 web 开发框架。

官方解释:Expressive middleware for node.js using ES2017 async functions

github: koajs/koa

特点

中间件机制、请求、响应处理

  • 轻量、无捆绑
  • 中间件构架
  • 优雅的API设计
  • 增强的错误处理

Koa1与Koa2的区别

Koa1使用generate,yield next方式执行promise异步操作,而Koa开始,使用aysnc/await来处理异步

node的不足

  • 令人困惑的req和res
    • res.end()
    • res.writeHeader、res.setHeader
  • 描述复杂业务逻辑时不够优雅
    • 流程描述:比如a账号扣钱、b账号加钱
    • 切面描述(AOP) 比如鉴权、日志、加判断在某个时间开始打折促销,axios里的拦截。AOP实现分为语言级、框架级
  1. // 利用fs,渲染静态html、JSON字符串返回
  2. const http = require('http')
  3. const fs = require('fs')
  4. const server = http.createServer((req, res)=> {
  5. const { url, method } = req
  6. console.log('url, method: ', url, method)
  7. if (url === '/' && method === 'GET') {
  8. fs.readFile('index.html', (err, data) => {
  9. if (err) throw err
  10. res.statusCode = 200
  11. res.setHeader('Content-Type', 'text/html')
  12. res.end(data)
  13. })
  14. } else if (url === '/users' && method === 'GET') {
  15. res.writeHead(200, {
  16. 'Content-Type': 'application/json'
  17. })
  18. res.end(JSON.stringify({
  19. name: 'guoqzuo'
  20. }))
  21. }
  22. })
  23. server.listen(3003)

koa优雅处理http

运行下面的代码,访问http://127.0.0.1 就可以看到 {name: ‘Tom’} 内容

  1. // 需要先 npm install koa --save
  2. const Koa = require('koa')
  3. const app = new Koa()
  4. app.use((ctx, next) => {
  5. ctx.body = {
  6. name: 'Tom'
  7. }
  8. })
  9. app.listen(3000)

ctx与next

下面的例子访问 http://127.0.0.1 为 {name: ‘Tom’},访问 http://127.0.0.1/html 内容为 ‘你的名字是Tom’

  1. const Koa = require('koa')
  2. const app = new Koa()
  3. app.use((ctx, next) => {
  4. ctx.body = {
  5. name: 'Tom'
  6. }
  7. next() // 执行下一个中间件
  8. })
  9. app.use((ctx, next) => {
  10. console.log(ctx.url)
  11. if (ctx.url === '/html') {
  12. ctx.body = `你的名字是${ctx.body.name}`
  13. }
  14. })
  15. app.listen(3000)

await next()

  1. const Koa = require('koa')
  2. const app = new Koa()
  3. // 也会被请求 /favicon.ico
  4. app.use(async (ctx, next) => {
  5. // log日志
  6. let dateS = +(new Date())
  7. await next() // 先去处理后面的中间件,都处理完后再向下执行
  8. let dateE = +(new Date())
  9. console.log(`请求耗时${dateE - dateS}ms`)
  10. })
  11. app.use((ctx, next) => {
  12. ctx.body = {
  13. name: 'Tom'
  14. }
  15. next()
  16. })
  17. app.use((ctx, next) => {
  18. console.log(ctx.url)
  19. if (ctx.url === '/html') {
  20. ctx.body = `你的名字是${ctx.body.name}`
  21. }
  22. })
  23. app.listen(3000)

Koa原理

node与koa开启http服务方法

  1. // node http服务
  2. const http = require('http')
  3. const server = http.createServer(() => {
  4. res.writeHead(200)
  5. res.end('hello')
  6. })
  7. server.llsten(3000, () => {
  8. console.log('监听端口3000')
  9. })
  10. // koa http服务
  11. const Koa = require('koa')
  12. const app = new Koa()
  13. app.use((ctx, next) => {
  14. ctx.body = {
  15. name: 'Tom'
  16. }
  17. })
  18. app.listen(3000)

创建mykoa.js来模拟实现koa

先写好使用demo

  1. const MyKoa = require('./myKoa')
  2. const app = new MyKoa()
  3. // koa调用
  4. // app.use((ctx, next) => {
  5. // ctx.body = {
  6. // name: 'Tom'
  7. // }
  8. // })
  9. // 先暂时简单点
  10. app.use((req, res) => {
  11. console.log('执行了app.use')
  12. res.end('hello')
  13. })
  14. app.listen(3000, (err, data) => {
  15. console.log('监听端口3000')
  16. })

myKoa.js实现

  1. // myKoa.js
  2. const http = require('http')
  3. class MyKoa {
  4. // app.use 调用 app.use(callback)
  5. use(callback) {
  6. this.callback = callback
  7. }
  8. listen(...args) {
  9. console.log(args)
  10. const server = http.createServer((req, res) => {
  11. this.callback(req, res)
  12. })
  13. server.listen(...args)
  14. }
  15. }
  16. module.exports = MyKoa

简化API:ctx参数(context)

一般app.use回调函数参数为 ctx和next,这里的ctx是context上下文的简写,主要是为了简化API而引入的。将原始请求对象req和响应对象res封装并挂载到context上,并在context上设置getter和setter属性,从而简化操作

getter和setter作用

  1. echarts中对于对象层级很深的属性,options.a.b.c,可以直接创建一个getter,这样写-法更优雅
  2. vue2.0双向绑定
  1. // 更近一步 将app.use((req, res) => {}) => app.use(ctx => {})
  2. app.use(ctx => {
  3. ctx.body = 'hello'
  4. })

先看看koa源码 koa -response源码

response最核心的一个方法是 set body方法,ctx.body 默认接收是json数据,如果传入了buffer、string、流都会有相应的处理

封装request、response、context

  1. // demo app.js
  2. const MyKoa = require('./myKoa')
  3. const app = new MyKoa()
  4. app.use((ctx) => {
  5. // console.log(ctx)
  6. ctx.body = 'hello'
  7. })
  8. app.listen(3000, (err, data) => {
  9. console.log('监听端口3000')
  10. })
  11. // myKoa.js
  12. const http = require('http')
  13. const context = require('./context')
  14. const request = require('./request')
  15. const response = require('./response')
  16. class MyKoa {
  17. // app.use 调用 app.use(callback)
  18. use(callback) {
  19. this.callback = callback
  20. }
  21. listen(...args) {
  22. console.log(args)
  23. const server = http.createServer((req, res) => {
  24. //this.callback(req, res)
  25. // 需要先创建上下文
  26. let ctx = this.createContext(req, res)
  27. this.callback(ctx)
  28. res.end(ctx.body)
  29. })
  30. server.listen(...args)
  31. }
  32. // 将res和req封装到contxt
  33. createContext(req, res) {
  34. // 先继承一些我们写的对象
  35. const ctx = Object.create(context)
  36. ctx.request = Object.create(request)
  37. ctx.response = Object.create(response)
  38. ctx.req = ctx.request.req = req
  39. ctx.res = ctx.response.res = res
  40. return ctx
  41. }
  42. }
  43. module.exports = MyKoa

request.js

  1. module.exports = {
  2. get url() {
  3. return this.req.url
  4. },
  5. get method() {
  6. return this.req.method.toLowerCase()
  7. }
  8. }

response.js

  1. module.exports = {
  2. get body() {
  3. return this._body
  4. },
  5. set body(val) {
  6. this._body = val
  7. }
  8. }

context.js

  1. module.exports = {
  2. get rul() {
  3. return this.request.url
  4. },
  5. get body() {
  6. return this.response.body
  7. },
  8. set body(val) {
  9. this.response.body = val
  10. },
  11. get method() {
  12. return this.request.method
  13. }
  14. }

优雅的流程描述与切面描述(中间件机制)

koa中间件机制是:利用compose函数组合,将一组需要顺序执行的函数复合为一个函数,外层函数的参数是内层函数的返回值。洋葱圈模型可以形象的表示这种机制机制,是koa源码的精髓和难点。

2_koa中间件洋葱圈模型.png

compose是函数式编程里的一个概念,是多个函数的组合。

compose函数合成

  1. const add = (x, y) => x + y
  2. const square = z => z * z
  3. const fn = (x, y) => square(add(x, y)) // 将两个函数合成一个函数
  4. console.log(fn(1, 2)) // 9
  5. // 更好的写法 => 封装成一个通用方法
  6. const compose = (fn1, fn2) => (...args) => fn2(fn1(...args))
  7. const fn = compose(add, square)
  8. console.log(fn(1, 2)) // 9
  9. // 再次扩展,不固定个数的函数封装
  10. const compose = (...fns) => (...args) => {
  11. let ret
  12. // 依次执行每个函数
  13. fns.forEach((fn, index) => {
  14. ret = index === 0 ? fn(...args) : fn(ret)
  15. })
  16. return ret
  17. }
  18. const fn = compose(add, square)
  19. console.log(fn(1, 2)) // 9

compose异步洋葱圈

先来看测试demo,怎么实现下面的compose函数呢?

  1. async function fn1(next) {
  2. console.log('start fn1')
  3. await next()
  4. console.log('end fn1')
  5. }
  6. async function fn2(next) {
  7. console.log('start fn2')
  8. await next()
  9. console.log('end fn2')
  10. }
  11. function fn3(next) {
  12. console.log('start fn3')
  13. }
  14. const finalFn = compose([fn1, fn2, fn3]) // [fn1, fn2, fn3] middlewares
  15. finalFn()
  16. // 打印结果
  17. // start fn1
  18. // start fn2
  19. // start fn3
  20. // end fn2
  21. // end fn1

compose函数实现

  1. async function fn1(next) {
  2. console.log('start fn1')
  3. await delay()
  4. await next()
  5. console.log('end fn1')
  6. }
  7. async function fn2(next) {
  8. console.log('start fn2')
  9. await delay()
  10. await next()
  11. console.log('end fn2')
  12. }
  13. async function fn3(next) {
  14. console.log('start fn3')
  15. await delay()
  16. await next()
  17. console.log('end fn3')
  18. }
  19. function delay() {
  20. return new Promise((resolve, reject) => {
  21. setTimeout(() => {
  22. resolve()
  23. }, 2000)
  24. })
  25. }
  26. function compose(fns) {
  27. return function() {
  28. return dispatch(0)
  29. function dispatch(i) {
  30. let fn = fns[i]
  31. if (!fn) {
  32. return Promise.resolve()
  33. }
  34. return Promise.resolve(
  35. fn(() => {
  36. // dispatch(i + 1)
  37. return dispatch(i + 1)
  38. })
  39. )
  40. }
  41. }
  42. }
  43. const finalFn = compose([fn1, fn2, fn3]) // [fn1, fn2, fn3] middlewares
  44. finalFn()
  45. // 执行结果
  46. // start fn1
  47. // 2s
  48. // start fn2
  49. // 2s
  50. // start fn3
  51. // 2s
  52. // end fn3
  53. // end fn2
  54. // end fn1
  55. // 思考:将next的函数里面 return dispatch(i + 1) 改为 dispatch(i + 1)
  56. // await next() 时,dispatch(i + 1) 一开始执行,await就向下执行了,并没有等到dispatch(i + 1)完全执行完
  57. // 执行结果
  58. // start fn1
  59. // 2s
  60. // start fn2
  61. // end fn1
  62. // 2s
  63. // start fn3
  64. // end fn2
  65. // 2s
  66. // end fn3

await/async 执行顺序问题

在上面的例子中,我们发现将next的函数里面 return dispatch(i + 1) 改为 dispatch(i + 1),会导致await没有按预期等待。这里用一个demo来理解async/await的执行顺序问题,await 后面的内容如果函数返回值为promise,则等待promise执行完再向下执行,如果返回值非promise,await不会等待(await下面的代码和await等待的函数会同步执行)

  1. (async () => {
  2. await test() // await fn()
  3. console.log('异步执行完成')
  4. })()
  5. async function test() {
  6. fn() // return fn() 或 await fn()
  7. }
  8. async function fn(next) {
  9. console.log('start fn')
  10. await delay()
  11. console.log('end fn')
  12. }
  13. function delay() {
  14. return new Promise((resolve, reject) => {
  15. setTimeout(() => {
  16. resolve()
  17. }, 2000)
  18. })
  19. }
  20. // return fn() 或 await fn() 结果
  21. // start fn
  22. // end fn
  23. // 异步执行完成
  24. // fn() 结果
  25. // start fn
  26. // 异步执行完成
  27. // end fn

参考:async/await函数的执行顺序的理解 - csdn

将compose应用到myKoa中

  1. const http = require('http')
  2. const context = require('./context')
  3. const request = require('./request')
  4. const response = require('./response')
  5. class MyKoa {
  6. // app.use 调用 app.use(callback)
  7. constructor() {
  8. this.middlewares = []
  9. }
  10. use(middleware) {
  11. this.middlewares.push(middleware)
  12. return this // 支持链式调用 app.use().use()
  13. }
  14. listen(...args) {
  15. console.log(args)
  16. const server = http.createServer(async (req, res) => {
  17. // 需要先创建上下文
  18. let ctx = this.createContext(req, res)
  19. // 组合函数
  20. let fn = this.compose(this.middlewares)
  21. await fn(ctx)
  22. // 这里简单的处理了下ctx.body 但实际要有很多处理
  23. let bodyType = typeof ctx.body
  24. let result = bodyType === 'object' ? JSON.stringify(ctx.body) : ctx.body
  25. // 解决中文乱码的问题
  26. res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
  27. res.end(result)
  28. })
  29. server.listen(...args)
  30. }
  31. createContext(req, res) {
  32. // 先继承一些我们写的对象
  33. const ctx = Object.create(context)
  34. ctx.request = Object.create(request)
  35. ctx.response = Object.create(response)
  36. ctx.req = ctx.request.req = req
  37. ctx.res = ctx.response.res = res
  38. return ctx
  39. }
  40. compose(fns) {
  41. return function(ctx) {
  42. return dispatch(0)
  43. function dispatch(i) {
  44. let fn = fns[i]
  45. if (!fn) {
  46. return Promise.resolve()
  47. }
  48. return Promise.resolve(
  49. fn(ctx, () => {
  50. // dispatch(i + 1)
  51. return dispatch(i + 1)
  52. })
  53. )
  54. }
  55. }
  56. }
  57. }
  58. module.exports = MyKoa

用一个demo来测试下,也可以使用上面的 koa优雅处理http - await next() 里面的例子来测试

  1. const delay = () => Promise.resolve(resolve => setTimeout(() => resolve(), 2000))
  2. const Koa = require('./myKoa2')
  3. const app = new Koa()
  4. app.use(async (ctx, next) => {
  5. ctx.body = '1'
  6. await next()
  7. ctx.body += '5'
  8. })
  9. app.use(async (ctx, next) => {
  10. ctx.body += '2'
  11. await next()
  12. ctx.body += '4'
  13. })
  14. app.use((ctx, next) => {
  15. ctx.body += '3'
  16. next()
  17. }).use((ctx, next) => {
  18. // 试试链式调用
  19. ctx.body += 'end'
  20. })
  21. app.listen(3000)
  22. // 访问网页内容为 123end45

koa compose源码

源码地址: koa compose - github

常见koa中间件的实现

我们可以自己来实现一个中间件,koa中间件规范:

  • 一个async函数
  • 接收ctx和next两个参数
  • 任务结束需要执行next
  1. const mid = async (ctx, next) => {
  2. // 来到中间件,洋葱圈左边
  3. next() // 进入其他中间件
  4. // 再次来到中间件,洋葱圈右边
  5. }

中间件常见任务

  • 请求拦截
  • 路由
  • 日志
  • 静态文件服务

请求拦截中间件

现在动手实现一个请求拦截的中间件

  1. const Koa = require('koa')
  2. cosnt app = new Koa()
  3. cosnt intercept = require('./intercept')
  4. // 请求拦截中间件
  5. app.use(intercept)
  6. app.use((ctx, next) => {
  7. ctx.body = 'hello'
  8. })
  9. app.listen(3000)

来看看intercept.js的实现

  1. async function intercept(ctx, next) {
  2. let { res, req } = ctx
  3. const blacklist = [
  4. '127.0.0.1',
  5. '192.168.1.2'
  6. ]
  7. const ip = getClientIp(req)
  8. if (blacklist.includes(ip)) {
  9. ctx.body = '您无权限访问'
  10. // 如果不执行next,就无法进入到下一个中间件
  11. } else {
  12. await next()
  13. }
  14. }
  15. // 获取当前IP
  16. function getClientIp(req) {
  17. let curIp = (
  18. req.headers['x-forwarded-for'] || // 是否有反向代理 IP
  19. req.connection.remoteAddress || // 判断 connection 的远程 IP
  20. req.socket.remoteAddress || // 判断后端的 socket 的 IP
  21. req.connection.socket.remoteAddress
  22. )
  23. curIp.startsWith('::ffff:') && (curIp = curIp.split('::ffff:')[1])
  24. console.log('当前ip是', curIp)
  25. return curIp
  26. }
  27. module.exports = intercept

路由中间件 router

来实现一个路由中间件,先来看一个测试demo

  1. const Koa = require('koa')
  2. cosnt app = new Koa()
  3. cosnt Router = require('./router')
  4. const router = new Router()
  5. router.get('/', aysnc ctx => { ctx.body = 'home page'} )
  6. router.get('/index', aysnc ctx => { ctx.body = 'index page'} )
  7. router.get('/post', aysnc ctx => { ctx.body = 'post page'} )
  8. router.get('/list', aysnc ctx => { ctx.body = 'list page'} )
  9. router.post('/config', aysnc ctx => {
  10. ctx.body = {
  11. code: 200,
  12. msg: 'ok',
  13. data: { a: 1 }
  14. }
  15. )
  16. // 请求拦截中间件
  17. app.use(router.routes())
  18. app.use((ctx, next) => {
  19. ctx.body = '404'
  20. })
  21. app.listen(3000)

router.js 实现 router.routes()函数返回一个中间件函数

  1. class Router {
  2. constructor() {
  3. this.stack = []
  4. }
  5. register(path, methods, middleware) {
  6. let route = { path, methods, middleware }
  7. this.stack.push(route)
  8. }
  9. get(path, middleware) {
  10. // 注册路由
  11. this.register(path, 'get', middleware)
  12. }
  13. post(path, middleware) {
  14. // 注册路由
  15. this.register(path, 'post', middleware)
  16. }
  17. routes() {
  18. // 返回一个中间件回调函数 (ctx, next) => { 进行路由处理 }
  19. let stock = this.stack
  20. return async (ctx, next) => {
  21. if (ctx.url === '/favicon.ico') {
  22. await next()
  23. return
  24. }
  25. const len = stock.length
  26. let route
  27. for(let i = 0; i < len; i++) {
  28. let item = stock[i]
  29. console.log(ctx.url, item, ctx.method)
  30. if (ctx.url === item.path && item.methods.includes(ctx.method.toLowerCase())) {
  31. route = item.middleware
  32. break
  33. }
  34. }
  35. console.log('route', route)
  36. if (typeof route === 'function') {
  37. // 如果匹配到了路由
  38. route(ctx, next)
  39. } else {
  40. await next()
  41. }
  42. }
  43. }
  44. }
  45. module.exports = Router

静态文件服务中间件

koa-staic,配置静态文件目录,默认为static获取文件或目录信息,静态文件读取,先来看看使用demo

  1. const Koa = require('koa')
  2. const app = new Koa()
  3. const static = require('./static')
  4. app.use(static(__dirname + '/public'))
  5. app.listen(3000, () => {
  6. console.log('服务已开启,端口号3000')
  7. })

static.js 实现

  1. const fs = require('fs')
  2. const path = require('path')
  3. // console.log(path, '*' + path.resolve)
  4. function static(dirPath = './pbulic') {
  5. return async (ctx, next) => {
  6. // 校验是否是static目录
  7. if (ctx.url.startsWith('/public')) {
  8. // 将当前路径和用户指定的路径合并为一个绝对路径
  9. let url = path.resolve(__dirname, dirPath)
  10. console.log(url)
  11. // /Users/kevin/Desktop/feclone/fedemo/src/node/node视频教程笔记/1_koa/静态文件服务中间件/public
  12. console.log(ctx.url) // /public/2sdf/323
  13. let filePath = url + ctx.url.replace('/public', '')
  14. try {
  15. let stat = fs.statSync(filePath) // https://nodejs.org/docs/latest/api/fs.html#fs_fs_statsync_path_options
  16. if (stat.isDirectory()) {
  17. // 如果是目录,列出文件
  18. let dir = fs.readdirSync(filePath)
  19. console.log(dir)
  20. if (dir.length === 0) {
  21. ctx.body = '目录为空'
  22. return
  23. }
  24. let htmlArr = ['<div style="margin:30px;">']
  25. dir.forEach(filename => {
  26. htmlArr.push(
  27. filename.includes('.') ?
  28. `<p><a style="color:black" href="${ctx.url}/${filename}">${filename}</a></p>` :
  29. `<p><a href="${ctx.url}/${filename}">${filename}</a></p>`
  30. )
  31. })
  32. htmlArr.push('</di>')
  33. ctx.body = htmlArr.join('')
  34. } else {
  35. // 如果是文件
  36. let content = fs.readFileSync(filePath)
  37. console.log(content)
  38. ctx.body = content.toString()
  39. }
  40. } catch(e) {
  41. console.error(e)
  42. // ctx.body = ctx.url + '文件或目录不存在'
  43. ctx.body = 'Not Found'
  44. }
  45. } else {
  46. // 非静态资源,执行下一个中间件
  47. await next()
  48. }
  49. }
  50. }
  51. module.exports = static

日志服务中间件

基于上面的例子,我们增加一个日志服务中间件,用于记录访问记录

  1. const Koa = require('koa')
  2. const app = new Koa()
  3. const static = require('./static')
  4. const Logger = require('./log')
  5. const logger = new Logger()
  6. // 日志中间件
  7. app.use(logger.log())
  8. app.use(static(__dirname + '/public'))
  9. app.listen(3000, () => {
  10. console.log('服务已开启,端口号3000')
  11. })

log.js 实现demo

  1. class Logger {
  2. constructor() {
  3. this.logs = []
  4. }
  5. log() {
  6. return async (ctx, next) => {
  7. // 记录进入时间
  8. let temp = {}
  9. let startTime = +(new Date())
  10. let endTime
  11. await next()
  12. endTime = +(new Date()) // 结束时间
  13. Object.assign(temp, {
  14. startTime,
  15. endTime,
  16. url: ctx.url,
  17. resTime: (endTime - startTime) + 'ms'
  18. })
  19. this.logs.push(temp)
  20. console.log(this.showLogs())
  21. }
  22. }
  23. showLogs() {
  24. console.log(this.logs)
  25. }
  26. }
  27. module.exports = Logger