最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • koa及常用中间件的使用

    正文概述 掘金(DoubleX)   2021-01-05   793

    Koa 起步

    const http = require('http');
    const server = http.createServer((req, res) => {
      res.writeHead(200);
      res.end('hello world');
    })
    server.listen(3000, () => console.log('server start at 3000'));
    

    相信上面的代码大家都是印象深刻 , 这是Node原生创建一个http服务器的示例 , 这里结合上面介绍, 我们大致猜想一下 koa 应该是什么样的 ?

    • 她应该有 async/await 是异步操作;
    • 她应该有错误处理机制;
    • 她应该有比http模块更优雅的方法;

    那就看 koa 的第一个示例 ; 当然你要先下载她( 保证你的 node 在 7.6.0 以上, 以方便支持async/await 操作 )

    npm install koa --save-dev
    
    const Koa = require('koa');
    const app = new Koa();
    app.use(async ctx => {
      ctx.body = 'hello world'
    });
    app.listen(3000, () => console.log('server start at 3000'));
    

    上面的代码是 koa 的示例代码 ; 这段代码中可以看出

    • async 异步了 虽然没有出现await
    • req res 变成了 ctx 也就是 context 上下文
    • 错误处理还没有看到, 这个后面说

    总结: 底层仍然是http 通过各种封装用着更加顺手了, 当然啦这只是表象, 肯定还是要深入了解的 ;

    那么再看 , app 既然是 Koa 的实例 , 那这个实例里面都有哪些内容呢 ? 这里呢实例的属性我就不说了简单看一下我认为比较重要的几个方法吧

    • app.use() : 将给定的中间件方法添加到应用程序中, 并且这个方法会返回 this 也就是说可以链式调用

    • app.callback() : 返回适用于 http.createServer() 方法的回调函数来处理请求, 可以说是另一种方法创建服务器吧, 为什么这么用不要管, 后面你肯定会用到, 比如创建多个服务器?, 暂时就记住吧

    • app.listen() : Koa 应用程序不是 HTTP 服务器的1对1展现。 可以将一个或多个 Koa 应用程序安装在一起以形成具有单个HTTP服务器的更大应用程序。

      const Koa = require('koa');
      const app = new Koa;
      app.use(async ctx => {
          ctx.body = { msg: 'hello koa' };
      });
      app.listen(3000) // 其实就是下面的 createServer 语法糖
        
      // -------------------------------------------------------
        
      const http = require('http');
      const Koa = require('koa');
      const app = new Koa()
        
      app.use(async ctx => {
        ctx.body = { msg: 'hello koa' };
      });
        
      http.createServer(app.callback()).listen(3000);
      
    • app.on('error', function) : 通过监听 app 上的 error 事件对这些错误做进一步的统一处理和集中管理。

      app.on('error', async (err, ctx) => {
        console.log('err', err)
      })
      app.use(async ctx => {
        // 这时运行程序就会出现错误 err ReferenceError: tx is not defind  
        ctx.body = { msg: tx }; 
      });
      

    中间件 Middleware

    试想我们现在想实现一个登录的功能; 主业务肯定是用户名, 密码 然后对比数据库齐活 ;

    // 伪代码
    app.use(async ctx => {
      const user = 'admin'
      const pwd = '123456'
      const { username, password } = ctx.query
      if (user === username && pwd === password) {
        ctx.body = { status: 200, msg: 'success' }
      }
    });
    
    const Koa = require('koa');
    const app = new Koa();
    
    app.use(async (ctx, next) => {
      ctx.state.user = 'admin'
      ctx.state.pwd = '111'
      console.log(`即将开始对比user: ${ctx.state.user} pwd: ${ctx.state.pwd}`)
      await next()
      console.log(`对比结束username: ${ctx.query.username}, password: ${ctx.query.password}`)
    })
    
    app.use(async ctx => {
      const { username, password } = ctx.query
      if (ctx.state.user === username && ctx.state.pwd === password) {
        ctx.body = { status: 200, msg: 'success' }
      }
    });
    app.listen(3000, () => console.log('server start at 3000'));
    

    koa及常用中间件的使用

    看控制台的结果在理解上面的那段就很好理解了; 至此终于可以将这个用到烂的图拿出来了?真的是都在用,太經典了!!!

    koa及常用中间件的使用

    我要是单纯的给你放个图就太对不起你们了, 万一你们看不懂我的赞就没有了?, so 我又专门给你们盗了一个更直白的

    koa及常用中间件的使用

    当一个中间件调用 next() 则该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为。

    上下文 Context

    app.use(async ctx => {
      const username = ctx.query.name
      ctx.body = { code: 200, msg: 'success' }
    })
    

    相信看到上面的 ctx 新中肯定也有一个疑问 , 为什么 ctx 既可以有操作 request 的能力也有操作 response 的能力 ; 这个肯定是故意为之的, 也是为了我们开发方便 , 比如你也可以这样

    app.use(async ctx => {
      const username = ctx.request.query.name
      ctx.response.body = { code: 200, msg: 'success' }
    })
    

    koa 呢 帮我们把一些很常用的方法放到 ctx 上下文中, 具体有哪些呢 ? 还要去官网查看? 个人觉得太多且没有必要列出来, 大家可以去官网慢慢看, 但是除去 requestresponse 的当然还有ctx 自己的一些属性和方法 ; 那就列举几个个人认为比较重要的吧

    • ctx.request : Koa 的request 对象

    • ctx.response : Koa 的response对象

    • ctx.state : Koa推荐使用的命名空间, 主要用于中间件传递信息

    • ctx.throw : Koa 手动抛出异常的方法, 也可以使用 http-errors 来创建错误

      ctx.throw(404, 'NotFound');
      
    • ctx.assert : 当断言的值不存在或者出现错误的时候, 就会抛出异常

      // 如果 ctx.state.name 没有定义就会出现这个错误
      ctx.assert(ctx.state.name, 401, 'user is not defined');
      
    • ctx.cookies.get ctx.cookies.set : cookie 的操作, 也可以根据官方的推荐使用 cookies 模块 ; 这里我简单示例一下吧, 要深入 cookie 的知识点还是有点多, 而且我又要百度查了?(我也不是很了解)

      ctx.cookies.get(name, [options])
      ctx.cookies.set(name, value, [options])
      
      app.use(async ctx => {
        ctx.cookies.set('login_cookie', '123456', {
          maxAge: 36000 // 这里注意表示从 Date.now() 得到的毫秒数 毫秒级时间戳
        })
        const loginCookie = ctx.cookies.get('login_cookie')
        console.log(loginCookie)
        ctx.response.body = { code: 200, msg: 'success' }
      })
      

    Request Response

    Koa-router(路由)

    app.use(async ctx => {
      if (ctx.url === '/') {
        ctx.body = { msg: 'index page' };
      } else if (ctx.url === '/detail') {
        ctx.body = { msg: 'detail page' };
      }
    });
    

    这...... 真的不是很优雅, 为此社区也为我们提供了一个比较优秀的中间件库 koa-router

    npm install koa-router --save-dev
    

    那我们先来个示例实现上面不优雅的代码

    const Koa = require('koa');
    const Router = require('koa-router');
    const app = new Koa();
    // 实例化路由
    const router = new Router()
    // 注册路由
    router.get('/', async ctx => {
      ctx.body = { msg: 'index page' };
    });
    router.get('/detail', async ctx => {
      ctx.body = { msg: 'detail page' };
    });
    // 注册路由中间件
    app.use(router.routes())
    app.listen(3000);
    

    通过以上代码的查看可读性明显提升不是一个档次, 下面加看一下她的基本使用吧;

    • 路由前缀 ; 在实例化路由的时候添加上prefix 属性就会给路由添加上前缀, 比如上诉代码中如果添加上前缀, 那么再访问 detail 的时候, 完整路径就是 http://localhost:3000/user/detail

      const router = new Router({ prefix: '/user' })
      
    • 请求方式 ; koa-router 支持常用的请求方式, 推荐大家使用 get post put delete patch head options

      // 示例
      router.get(...)
      
    • 中间件注册 ; 上述示例中可以看出, 在app.use 之前 router 和 koa 还是没有关联的, 如果想要他们关联起来就需要使用以下代码

      app.use(router.routes())
      
    • redirect 重定向 ; 方法的第一个参数是请求来源,第二个参数是目的地,两者都可以用路径模式的别名代替,还有第三个参数是状态码,默认是 302 这个方法放到了context 上下文中

      router.get('/home', async ctx => {
        ctx.redirect('http://baidu.com');
      });
      
    • 路由参数 paramsquerystring 熟悉路由的同学一定知道, 一般参数有两种情况

      http://localhost/users/1  // params
      http://localhost/users/id=1 // querystring
      
      • params : 需要注意的是她是一个强路由, 会更改原有的资源地址; 看实例

        // 在:id代表不确定参数
        // 访问时 localhost:3000/user/1
        // id可以是任意值, 需要注意url中写什么样的 params 就需要怎样获取,必须保持一致
        router.get('/:id', async ctx => {
          ctx.body = {
            msg: 'index page',
            params: ctx.params.id // 通过 ctx.params.id 获取到的是 1
          };
        });
        
      • querystring : 此种方式会在获取路由后面携带的参数 user=admin&pwd=234

        router.get('/', async ctx => {
          ctx.body = {
            msg: 'index page',
            query: ctx.query // ctx.query会获取url后面携带的参数对象
          };
        });
        
    • allowedMethod : 对于这个方法来说 , 它的作用就是用于处理请求的错误 , 假设我们现在实现了一个 /user 的接口, 为 get 请求方式

      • router.get('/user', async ctx => {});
        app.use(router.routes());
        // 如果没有这一行, 当使用其他请求方式请求user时, 会提示404
        // 如果有这一行, 当使用其他请求方式请求user时, 提示405 method not allowed
        app.use(router.allowedMethod());
        

    koa-static (静态web托管服务)

    npm install koa-static --save-dev
    
    const staitc = require('koa-static');
    // ./static 相对于你当前目录填写, 使其最终拼接成绝对路径
    app.use(static(path.join(__dirname, './static')));
    

    此时如果我们想访问这些资源 ; 在启动服务之后就可以通过以下方式访问 ;

    http://localhost:3000/xxx.img 或者一些 css js 文件等等

    koa-views (模板渲染引擎中间件)

    npm intsall koa-views --save-dev
    
    const views = require('koa-views');
    // views 相对于当前目录而创建的views目录, 最近内部的文件会作为模板
    app.use(views(path.resolve(__dirname, 'views')));
    
    // 挂载此中间件之后, ctx上下文中会挂载一个render函数
    app.get('/', async ctx => {
      // index 是 views 文件夹内的 index.html 文件
      await ctx.render('index')
    });
    

    上述代码可能解释的不是很清楚或者还有一些额外需要注意地方

    • render 函数是异步函数 一定要加上 await

    • 如果是 .html 的模板可以不用写后缀名 , 当然啦还支持一些其他的模板, 需要注册中间件的时候提供第二个参数 options

      app.use(views(path.resolve(__dirname, 'views')), {
        extension: 'ejs' // 可以是 pug 或者 nunjucks 记得要使用npm安装对应的依赖
      });
      

      如果我们使用的其他的模板那么对应的render 函数中也要做更改

      await ctx.render('index.ejs');
      
    • render 函数也会接受第二个参数, 接收一个对象 ; 然后对应的模板内就可以获取到这个数据 ;

      await ctx.render('index.ejs', { msg: '这是后端传递的数据' });
      

      关于模板语法, 这里就不做讲解了, 大家可以去对应官网查看 pug ejs

    • 模板内引用静态资源, 注意 你已经下载了 koa-static 并且在主文件内注册了这个中间件, 那么static 目录就是你网站的根目录了, 再模板内就不要从 static 开始引入了 ; 假设你的目录是这样的

      +---static
      |       index.css
      |       test.png
      |       
      \---views
      |       index.ejs
      |   index.js
      
      • 错误的示例

        <link rel="stylesheet" href="/static/index.css">
        
      • 正确的示例

        <link rel="stylesheet" href="index.css">
        

    koa-body (请求body数据处理)

    如果我们不使用这个中间件去做一些事情, 比如客户端随便提交点什么数据, 那么我们为了接受这些数据, 服务端的代码可能就是这样的

    router.post('/', async ctx => {
      let bstr = '';
      // 通过 koa 暴露出来的原生方法读取并且合并
      ctx.req.on('data', (data) => {
        bstr += data
      })
      ctx.req.on('end', () => {
        console.log(bstr)
      })
      ctx.body = { code: 200 }
    })
    

    试想一下如果每次为了接收这些数据监听 data end 肯定是不合理的 ;

    npm install koa-body --save-dev
    
    const bodyparser = require('koa-body');
    // 在注册此中间件之后, 会在 ctx 上下文注入 ctx.request.body 属性 用于获取客户端传递来的数据
    app.use(bodyparser());
    
    router.post('/', async ctx => {
      ctx.body = {
        code: 200,
        // body 可以获取form表单数据, json数据
        msg: ctx.request.body
      }
    })
    

    如果我们还需要上传文件, 则需要在注册中间的时候增加一些配置

    app.use(bodyparser({
      multipart: true, // 支持文件上传
      formidable: {
        // 文件默认保存的默认路径
        uploadDir: path.join(__dirname, 'static/uploads'), 
        // 保留文件拓展名
        keepExtensions: true
      }
    }));
    

    接收数据时可以这样操作

    router.post('/', async ctx => {
      // files 是上传文件后的对象集合, file是对应传递的 key
      const fileInfo= ctx.request.files.file;
      // 返回临时路径中的最后一部分作为文件名称
      const filePath = path.basename(fileInfo.path);
      ctx.body = { 
          url: `${ctx.origin}/uploads/${filePath}`, 
          info: ctx.request.files
      }
    })
    

    koa及常用中间件的使用

    @koa/cors (处理跨域中间件)

    npm install @koa/cors --save-dev
    
    const cors = require('@koa/cors');
    app.use(cors());
    

    koa-json-error (错误处理中间件)

    npm install koa-json-error --save-dev
    
    const error = require('koa-json-error');
    app.use(error({
      // 默认会把堆栈信息也给一起发给客户端, 这样很明显是不合理的所以做了一层简单的处理
      postFormat(e, { stack, ...rest }) {
        return process.env.NODE_ENV === 'production' ? rest : { stack, ...rest}
      }
    }));
    

    想要验证此功能, 可以使用 koa 原生提供的抛出错误方法

    app.use(async ctx => {
      if (ctx.request.body.name !== 'admin') {
        // 可以参考下面图片, 信息都是根据状态码生成的, 也可以在第二个参数中自定义一些错误数据
        ctx.throw(401, {msg: '鉴权失败'});
      }
    });
    

    koa及常用中间件的使用

    如果是逻辑错误, 比如 a is not defind 也是可以被捕获并且抛出的, 这里就不演示了 ;

    koa-parameter (参数校验中间件)

    npm install koa-parameter --save-dev
    
    const parameter = require('koa-parameter');
    parameter(app);
    

    在经过以上操作之后, ctx 上下文下面就多出了一个校验函数 verifyParams

    router.post('/', async ctx => {
      // 接收一个对象
      ctx.verifyParams({
        // 校验的简写形式
        username: 'string',
        password: { type: 'string', required: true } // 或者也可以这样
      })
      ctx.body = { code: 1 }
    })
    

    简单参数的校验, 可以参考以下方法

    koa及常用中间件的使用

    当然也提供了一些, 特殊的校验方式

    ctx.verifyParams({
        // 必须输入 url 格式
        address: { type: 'url', required: true },
        // 必须输入 email 格式
        email: { type: 'email', required: true }
    })
    

    如果是比较负责的也是可以校验的 , 当然也是不能覆盖全场景的, 不过这样已经能为我们省去不少力气了

    // 某一项是对象, 并且里面某个字段是数组
    ctx.verifyParams({
       obj: { type: 'object', rule: { children: 'array' } }
    })
    // 某一项是数组, 里面包含对象
    ctx.verifyParams({
       // list 是数组, 里面的每一项是object object中必须包含info字段
       list: { type: 'array', itemType: 'object', rule: {info: 'string'} }
    })
    

    mysql2 (mysql数据库操作)

    npm install mysql2 --save-dev
    

    使用层面相对来说也是比较简单的 ;

    const mysql = require('mysql2');
    // 创建连接
    const connection = mysql.createConnection({
      host: 'xx.xx.xx.xx',
      user: 'root',
      password: '123456',
      port: 3306,
      database: 'test'
    });
    // 基本的增删改查
    const [row] = await connection.promise().query('SELECT * FROM users WHERE username=? AND password=?', ['lisi', 'li']);
    const [info] = await connection.promise().query('UPDATE users SET username=? WHERE password=?', ['hello', 'wang']);
    const [info] = await connection.promise().query('DELETE FROM users WHERE username=?', ['hello']);
    const [info] = await connection.promise().query('INSERT INTO users (id, username, password) VALUES (?, ?, ?)', ['id1', 'username1', 'password1']);
    

    需要注意的是, 当查询的时候你可以使用 [row] 来将结果解构出来, 但是在更新, 修改和删除操作的时候, 并没有返回你删除的某些值, 而是返回一个操作对象, 里面包含了一些必要的信息 ;

    koa及常用中间件的使用

    JWT 鉴权

    npm install koa-jwt jsonwebtoken --save-dev
    

    使用层面主要分两步走, 第一 生成 token, 一般情况下都是登录的时候就返回给用户了 ; 生成 token 使用 sign 方法

    生成 token

    jwt.sign('要加密的内容 string | Buffer | object', '加密的key', {配置对象})
    // 配置对象中 两个比较重要的参数
    {
      algorithm: "HS256" | "HS384" | "HS512" |
        "RS256" | "RS384" | "RS512" |
        "ES256" | "ES384" | "ES512" |
        "PS256" | "PS384" | "PS512" |
        "none"; // 加密方式
      expiresIn: "2d" // 可以是数字, 如果是数组就是秒为单位, 或者支持 2d/10h 2天 10小时
    }
    
    const jwt = require('jsonwebtoken');
    router.post('/', async ctx => {
      // 从请求体中结构用户名和密码
      const { username, password } = ctx.request.body;
      
      const token = jwt.sign(
        { username, password },
      	'auth-key',
        { 
          algorithm: 'HS256', // 可以不写
          expiresIn: '2d'
        } 
      );
      ctx.body = { token };
    })
    

    jwt.verify 校验 token

    jwt.verify('token', '加密的key', {配置对象})
    
    router.post('/auth', async ctx => {
      const token = ctx.header.authorization
      try {
        // 这个token校验要额外处理 具体可以看下图
        jwt.verify(token.split(' ')[1], 'auth-key', { algorithms: 'HS256' });
      } catch (e) {
        ctx.throw(401);
      }
      ctx.body = { code: 1 };
    })
    

    koa及常用中间件的使用

    koa-jwt 校验 token

    const koaJwt = require('koa-jwt');
    // 如果有配置跨域 一定要写到跨域相关配置的下方
    // koaJwt 的第一个参数是一个对象, 与上面介绍到的配置对象内容基本一致, 
    // path 可以接受 字符串, 数组, 正则表达式,用来过滤某些 url 可以不进行鉴权
    app.use(koaJwt({ secret: 'auth-key' }).unless({ path: [/\user/] }))
    

    koa-generator

    npm install koa-generator -g
    

    这是一个命令行工具 , 全局安装之后, 可以在命令行使用 koa2 --help 查看相关帮助信息

    koa及常用中间件的使用

    创建过工程后, 除了 bin 文件夹的下文件有些不解, 其他都还好, 那么咱们就看看吧

    koa及常用中间件的使用

    图中可出大致归为两类 ;

    • 为什么没有后缀 ? 第一行是什么意思 ? 这里引入一篇文章帮助大家解决这个疑问 点这里

    • 看到第二个问题, 这个 port 可以在命令行中设置, 但是 window 和 liunx 中命令又是不太一样的 ; 代码都跨端了, 运行个脚本跨不了端就坑了 ; 这里再次推荐一个包 cross-env

      npm install cross-env --dev
      

      使用也是非常简单的, 比如之前的启动脚本是这样的 nodemon app.js

      那么我们现在改成这样既可 cross-env PORT=3001 nodemon app.js 此时再次运行项目就会在3001端口了

      当然也可以同时去设置环境变量比如

      cross-env NODE_ENV=production PORT=3001 nodemon app.js
      

    完结 撒花?


    起源地下载网 » koa及常用中间件的使用

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元