Node基础入门-koa(三)

302 阅读9分钟

官网

koa

​ koa是express原班人马打造的轻量、健壮、富有表现力的nodejs框架。目前koa有koa1和koa2两个版本;koa2依赖Node.js 7.6.0或者更高版本;koa不在内核方法中绑定任何中间件,它仅仅是一个轻量级的函数库,几乎所有功能都必须通过第三方插件来实现。

主要核心功能:

  • 处理了请求和响应的基本逻辑
  • 提供一个接口进行扩展 use 进行中间件注册(插件)

安装

npm i koa

koa简单使用

const Koa = require('koa');
const app = new Koa();

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);

Application对象

  • application是koa的实例 简写app
  • app.use 将给定的中间件方法添加到此应用程序,分为同步和异步,异步:通过es7中的async和await来处理
  • app.listen设置服务器端口;
  • app.on 错误处理;

Koa 中间件概念

app.use(ctx,next),Koa 利用中间件 控制"上游",调用"下游“;

app.use(ctx,next) 这里是koa底层中间件的基础模型。一个程序执行下来,它中间经历很多模块函数。这个Application对象 处理一个中间件的时候,会执行里面你定义好的函数就是ctx,当这个ctx执行性完成后,就要调用next。然后继续往下执行下一个中间件。直到最后。

//这里的middleWare函数就是一个中间件
let middleWare = async (ctx,next)=>{
    console.log("first middleWare");
    ctx.body = "hello world";
    next();
}
app.use(middleWare);

中间件的执行流程

yPeCqO.png

上下文(Context)

Koa Context 将 node 的 requestresponse 对象封装到单个对象中,为编写 Web 应用程序和 API 提供了许多有用的方法。 这些操作在 HTTP 服务器开发中频繁使用,它们被添加到此级别而不是更高级别的框架,这将强制中间件重新实现此通用功能。

简单来说就是Context里面封装好了node 的 requestresponse

提供了node原生的res 和 req ,以及它封装过的response 和request

  • ctx 对象下的属性方法

    • ctx.app:应用程序实例引用,等同于app;
    • ctx.req:Node 的 request 对象.
    • ctx.res:Node 的 response 对象.
    • ctx.request:koa中的Request对象;
    • ctx.response:koa中的response对象;
    • ctx.state:对象命名空间,通过中间件传递信息;
    • ctx.throw:抛出错误;
  • request及response别名

    • koa会把ctx.requset上的属性直接挂载到ctx上如:

      • ctx.header //头信息;
      • ctx.headers
      • ctx.method
      • ctx.method=
      • ctx.url
      • ctx.url=

      …...

    • 同样也会把ctx.response上的属性直接挂载到ctx上如:

      • ctx.body
      • ctx.body=
      • ctx.status
      • ctx.status=

      ….

    • ctx.status 获取响应状态。默认情况下,response.status 设置为 404 而不是像 node 的 res.statusCode 那样默认为 200

    • ctx.set 设置响应头 具体可以看官网的api

    • http状态码:1xx(消息)、2xx(成功)、3xx(重定向)、4xx(请求错误)、5xx和6xx(服务器错误)

    • 常见http状态码 (302 location 跳转)

      HTTP状态码 描述
      100 继续。继续响应剩余部分,进行提交请求
      200 成功
      301 永久移动。请求资源永久移动到新位置
      302 临时移动。请求资源零时移动到新位置
      304 未修改。请求资源对比上次未被修改,响应中不包含资源内容
      401 未授权,需要身份验证
      403 禁止。请求被拒绝
      404 未找到,服务器未找到需要资源
      500 服务器内部错误。服务器遇到错误,无法完成请求
      503 服务器不可用。零时服务过载,无法处理请求

koa生态环境之常用中间件

koa-router (处理动态资源)

路由是引导匹配之意,是匹配url到相应处理程序的活动。也就是说,路由是把 一个 url 与 一个函数进行管理。

我们创建这个koa-router的对象后。调用里面的routes()方法,他会返回一个中间件函数。然后用koa的use方法执行这个中间件函数,这个koa-router 就可以起作用了

  • koa-router安装
    • npm i koa-router -S
  • koa-router使用

    router 提供的 get,post,put……这些方法来注册url函数,等同于我们前面使用 switch 对 url 的判断。这个函数也会被作为中间件去执行。说明这些get,post,put 也有ctx函数 (上下文(Context)

    const koaRouters = new koaRouter();
    let content = ''
    
    
    koaRouters.get('/',ctx=>{
        // console.log(ctx.req.url);
        // ctx.body = 'hello koa'
        content = fs.readFileSync('./static/index.html');
        ctx.body = content.toString();
    })
    
    koaRouters.get('/login',ctx=>{
        // console.log(ctx.req.url);
        // ctx.body = 'hello koa'
        content = fs.readFileSync('./static/login.html');
        ctx.body = content.toString();
    })
    
    koaRouters.get('/register',ctx=>{
        // console.log(ctx.req.url);
        // ctx.body = 'hello koa'
        content = fs.readFileSync('./static/register.html');
        ctx.body = content.toString();
    })
    
    server.use(koaRouters.routes());
    
  • koa-router + 模板引擎

    这两个一起使用可以动态传递参数

    安装模板引擎

    npm i -d nunjucks
    

koa-static-cache(处理静态资源)

koa的静态资源代理,前面的node基础中,我们花费了很多switch case 和导入导出了mime文件类型来处理各种静态资源。其实koa生态中这个中间件就很好帮我们处理中间件。

安装
npm i -d koa-static-cache
使用
const koaStc = require('koa-static-cache');

//./static 硬盘的相对目录
server.use(koaStc('./static',{
    //url 请求的开头 相当于之前url.startWith('./static')
    prefix:'/static',
    //启动压缩
    gzip: true,
    //新资源放入内存
    dynamic: true
}))

然后就可以通过地址访问静态非常方便,他还给我们提供了很多配置

staticCache(dir [, options] [, files])

staticCache-options

  • dir (str) - the directory you wish to serve, priority than options.dir.
  • options.dir (str) - the directory you wish to serve, default to process.cwd.
  • options.maxAge (int) - cache control max age for the files, 0 by default.
  • options.cacheControl (str) - optional cache control header. Overrides options.maxAge.
  • options.buffer (bool) - store the files in memory instead of streaming from the filesystem on each request.
  • options.gzip (bool) - when request's accept-encoding include gzip, files will compressed by gzip.
  • options.usePrecompiledGzip (bool) - try use gzip files, loaded from disk, like nginx gzip_static
  • options.alias (obj) - object map of aliases. See below.
  • options.prefix (str) - the url prefix you wish to add, default to ''.
  • options.dynamic (bool) - dynamic load file which not cached on initialization.
  • options.filter (function | array) - filter files at init dir, for example - skip non build (source) files. If array set - allow only listed files
  • options.preload (bool) - caches the assets on initialization or not, default to true. always work together with options.dynamic.
  • options.files (obj) - optional files object. See below.
  • files (obj) - optional files object. See below.

Json 解析

json

JSON具有以下这些形式:

对象是一个无序的“‘名称/值’对”集合。一个对象以 {左括号 开始, }右括号 结束。每个“名称”后跟一个 :冒号 ;“‘名称/值’ 对”之间使用 ,逗号 分隔。

在node中使用require加载一个json文件数据的话,node会自动转成对象

新闻列表案例

  • 使用的模块
    • koa
    • koa-router
    • nunjucks
  • 引入必要的模块创建koa服务
    const server = new koa();
    const koaRouters = new koaRouter();
    let content = ''
    
    server.use(koaRouters.routes());
    
    server.listen(3001,function () {
        console.log('服务启动, http://localhost:3001');
    })
    
  • 利用中间件封装nunjucks 中的render 方法
    • 基本思路,把数据传入模板中,渲染出最终页面

      koaRouters.get('/',(ctx)=>{
          //把数据传入模板中,渲染出最终页面
          ctx.render ('index.html',{
              datas
          });
      })
      
    • 注册一个自己写的中间件

      const  tpls = require('./middleware/tpl');
      
      server.use(tpls;
      
    • 中间件就是一个返回一个函数,这个函数里面要调用next()符合koa对中间件的定义。导出的函数注意是使用commonJs的规范。

      // ./middleware/tpl
      const nunjucks =require('nunjucks');
      //配置模板环境
      
      let tpl = new nunjucks.Environment(
          //自动加载views
          new nunjucks.FileSystemLoader('views')
      )
      
      module.exports = function (ctx, next) {
          //和外面的接口相对应,传入页面名称和数据
          ctx.render = function (filename,data) {
              //这里调用nunjucks render
              ctx.body = tpl.render(filename, data);
          }
          next();
      }
      

      但是这里的views是写死要是我们模板有多个环境,那不是要很多个这样的相同逻辑的中间件,所以我们可以改写一下。不返回render 函数,返回一个中间件函数

      // app.js
      server.use(tplS('views'));
      
      
      // ./middleware/tpl
      
      module.exports = function (dir) {
          return function (ctx ,next) {
              let tpl = new nunjucks.Environment(
                  //自动加载views
                  new nunjucks.FileSystemLoader(dir,{
                      //实时更新数据
                      watch:true
              })
              )
              ctx.render = function (filename,data) {
                  //这里调用nunjucks render
                  ctx.body = tpl.render(filename, data);
              }
      
              next();
          }
      }
      
      
  • 在设置好的模板引擎中修改自己想要的视图
    <ul class="news-list">
        {%for item in data%}
        <li class="news">
            <a href="javascript:;">
                <!--                    配置好静态资源 koa-static-cache-->
                <img src="./static/img/3.jpeg" alt="">
            </a>
            <div>
                <h3>
                    <a href="javascript:;">{{item.title}}</a>
                </h3>
                <div class="info">
                    <span class="tips"><span>{{item.from}}</span></span>
                    <!-- <span class="line"></span> -->
                    <span class="time">| &nbsp;&nbsp;{{item.newTime}}</span>
                </div>
            </div>
        </li>
        {%endfor%}
    </ul>
    
  • 详情页配置

    我们要增加一个路由 /detial。 然后再首页的新闻标题添加路由配置的地址。然后我们有问题,每个详情页需要处理不同的数据。

    //详情页
    koaRouters.get('/detail',(ctx)=>{
        //把数据传入模板中,渲染出最终页面
        ctx.render ('detail.html',{
            //data
        });
    })
    

    这个数据要怎么处理,其实前后端交互中传递数据的方法有很多

    • 请求头
    • url querystring hash
    • 请求正文
    • ....

    这里我们使用url 的那种方式

    动态路由

    可以使用/detail?id ,或者 /detail/:id。:id这里表示的是一个变量

    地址中输入 http://localhost:3001/detail/123 这里自己定义的123会以变量的形式传给我们后端 。这个id后面还能跟正则 '/detail/:id(\d+) 只允许是数字

    //详情页
    koaRouters.get('/detail/:id',(ctx)=>{
        //把数据传入模板中,渲染出最终页面
        console.log('pra',ctx.params)
    
        //console.log(ctx.req);
        ctx.render ('detail.html',{
    
        });
    })
    
    //request 
    pra { id: '123' }
    
    

    拿到id之后转成数字类型,然后在data中找到对应的数据,在首页中地址栏后面要带上数据的id做一个数据的对应

    //首页
    <h3>
        <a href="/detail/{{item.id}}">{{item.title}}</a>
    </h3>
    
    //详情页
    koaRouters.get('/detail/:id(\\d+)',(ctx)=>{
        //把数据传入模板中,渲染出最终页面
        let id = Number(ctx.params.id)
        console.log(id);
        //通过id寻找数据
        let dataItem = data.find(item => item.id === id)
        //没有这条数据的处理
        if(!dataItem){
            ctx.write('404');
            ctx.end();
        }
        //传递数据
        ctx.render ('detail.html',{
            dataItem
        });
    })
    
  • 分页处理

    传递页码的时候如果再用动态路由就有问题,当一个url上的可选动态数据多的时候,用动态路由比较麻烦,就像一个函数如果参数多了,用一个一个的形参比较麻烦,这个情况下用 options 对象传参更方便。

    • url,动态数据少可以使用动态路由

    • url,动态数据多使用 queryString

    queryString 就不要配:id 直接可以在url ?page=1...就可以。然后后台使用ctx.query 可以获得这个?后面的为对象内容的对象。

    http://localhost:3001/?page=1
    
    console.log(ctx.query);
    
    [Object: null prototype] { page: '1' }
    
    

    新闻首页代码

    //新闻首页
    koaRouters.get('/',(ctx)=>{
        //把数据传入模板中,渲染出最终页面
        //分页处理
        //每页数量
        let pageNum = 5;
    
        //起始页
        let page  = ctx.query.page ||1 ;
        page = Number(page)
        //起始点,因为slice 是包头不包尾,
        //page = 1 start = 0 ; page = 2 start = 5 ..
        let start = (page - 1) * pageNum;
        //结束点
        // start = 1 end = 5 , start = 2 end =10
        let end = start + pageNum
        //所以截取的点就出来了
        let dataGroup = data.slice(start,end);
    
        //页码处理
        let pageNumber = Math.ceil((data.length/pageNum));
    
        ctx.render ('index.html',{
            data: dataGroup,
            pageNumber,
            page
        });
    })
    

    分页列表

    <div class="pagination">
        <a href="javascript:;" class="prev"></a>
        {% for i in range(1, pageNumber+1) %}
        {%if i==page %}
        <a href="/?page={{i}}" class="current">{{i}}</a>
        {%else%}
        <a href="/?page={{i}}">{{i}}</a>
        {%endif%}
        {% endfor %}
    
        <a href="javascript:;" class="next"></a>
    </div>
    

数据持久化保存mysql2 插件

上面的数据都是从json读取,我们可以利用mysql2 操作数据库。这里不细说Mysql。只是简单了解这个库是怎么运作的。数据库反面的知识,可以看书了解一下

mysql2npm

  • 安装
    npm install --save mysql2
    
  • 简单的例子
    // get the client
    const mysql = require('mysql2');
     
    // create the connection to database
    const connection = mysql.createConnection({
      host: 'localhost',
      user: 'root',
      database: 'test'
    });
     
    // simple query
    connection.query(
      'SELECT * FROM `table` WHERE `name` = "Page" AND `age` > 45',
      function(err, results, fields) {
        console.log(results); // results contains rows returned by server
        console.log(fields); // fields contains extra meta data about results, if available
      }
    );
     
    // with placeholder
    connection.query(
      'SELECT * FROM `table` WHERE `name` = ? AND `age` > ?',
      ['Page', 45],
      function(err, results) {
        console.log(results);
      }
    );
    
  • 基本的数据库操作

    数据库中的数据操作;

    • 一、增

      `INSERT INTO 表名 (字段一,字段二,字段三) VALUES ("值一","值二","值三");`
      
    • 二、删

      DELETE FROM 表名 WHERE 条件;

    • 三、改

      UPDATE 表名 SET 设置的内容 WHERE 条件语句;

    • 四、查

      SELECT 字段 FROM 表名 WHERE 条件语句;

    • 五、条件语句;

      1.ADN 2 OR 3. ORDER BY (DESC/ASC) 4.LIMIT 5.LIKE 6.JOIN ON 7.AS

github

代码地址1 代码地址2