zoukankan      html  css  js  c++  java
  • Node.js学习笔记【五】

    控制器简介

    什么是控制器?

    • 拿到路由分配的任务,并执行
    • 在Koa中,是一个中间件

    控制器的作用

    1. 获取HTTP请求参数
    2. 处理业务逻辑(如获取数据,计算数据,存储数据等)
    3. 根据不同的情况发送不同的HTTP响应

    获取HTTP请求参数

    请求参数分为好几种:

    • Query String,如?q=keyword(查询字符串参数往往是可选的)
    • Router Params,如/users/:id(id就是路由参数,路由参数是必选的)
    • Body(请求体),如{name:"小风车"}(Body在RESRful api中往往用JSON来表示)
    • Header(请求头),如Accept(客户端可以接受哪种媒体格式)、Cookie(用来认证)

    发送HTTP响应

    发送HTTP响应分为三种方面

    • 发送Status,如200/400等
    • 发送Body,如{name:"小风车"}(这里的Body指的是返回内容)
    • 发送Header(响应头),如Allow(代表允许的HTTP方法)、Content-Type(告诉客户端返回的格式应用哪种方式解析)

    编写控制器最佳实践

    • 每个资源的控制器放在不同的文件里
    • 尽量使用类+类方法的形式编写控制器
    • 严谨的错误处理(比如传入的参数都应校验,不能相信客户端传来的参数,而且一些逻辑上的错误也要检查并报出相应的错误信息)

    学习断点调试获取HTTP请求参数

    学习断点调试,就可以打个断点看一下在koa上如何获取各个参数。

    在想要断点的语句添加断点,运行访问

    image-20210805185238116

    可以看到左边调试栏出现大量参数,这里就有需要的HTTP请求参数。

    ctx.query:Query String

    ctx.params:Router Params

    ctx.request.body(需要安装中间件koa-bodyparser才会解析请求体):Body

    请求创建新用户

    image-20210805191346745

    可以看到在左边变量栏的ctx.request.body有我们请求的Body参数

    image-20210805192314696

    ctx.header:Header

    发送HTTP响应

    其实在之前一些实践中,已经学会了发送HTTP响应

    发送Status:设置ctx.status

    发送Status:设置ctx.status

    发送Body:设置ctx.body

    发送Header:使用ctx.set()

    例如设置头信息"Allow":允许GET、POST请求方法

    usersRouter.get('/',(ctx)=>{
      ctx.set('Allow','GET,POST')
      ctx.body = [{name:'小风车'}];
    });
    

    可以看到我们刚才设置的头信息“Allow”image-20210805193516621

    编写控制器最佳实践

    根据编写控制器最佳实践,重构下RESTful 项目的组织目录结构,使代码变得清爽,可读性更高。

    • 每个资源的控制器放在不同的文件里

    app文件夹——存放代码文件,如果安装了nodemon插件自动重启node.js文件,还需要在package.json修改启动路径:"start": "nodemon app/index.js"

    app->routes文件夹——存放各个路由文件

    image-20210805200507533

    重构首页路由home.js、用户路由users.js

    const Router = require('koa-router');
    const router = new Router();
    

    router.get('/',(ctx)=>{
    ctx.body = '<h1>这是主页</h1>'
    })

    //把实例化的路由导出
    module.exports = router;

    const Router = require('koa-router');
    const router = new Router({prefix:'/users'});
    
    const db = [{name:"小风车"}]
    
    router.get('/',(ctx)=>{
      ctx.body = db
    });
    
    //增
    router.post('/',(ctx)=>{
      db.push(ctx.request.body);
      ctx.body = ctx.request.body;
    });
    //访问特定用户
    router.get('/:id',(ctx)=>{
      ctx.body = db[ctx.params.id*1]
    });
    //修改指定用户整体信息
    router.put('/:id',(ctx)=>{
      db[ctx.params.id*1] = ctx.request.body;
      ctx.body = ctx.request.body;
    });
    //删除指定用户
    router.delete('/:id',(ctx)=>{
      db.splice(ctx.params.id*1,1);
      ctx.status = 204
    });
    
    module.exports = router;
    

    创建routes->index.js:编写代码批量读取一个目录下的文件

    const fs = require('fs');
    module.exports= (app)=>{
      //同步读取目录
      //当前目录表示__dirname
      fs.readdirSync(__dirname).forEach(file=>{
        if(file === 'index.js'){ return; }
        //提取实例
        const route = require(`./${file}`);
        //注册
        app.use(route.routes()).use(route.allowedMethods());
      })
    }
    

    然后在index.js引入该方法批量地注册到app上

    const Koa = require('koa');
    const bodyparser = require('koa-bodyparser');
    const Router = require('koa-router')
    const app = new Koa();
    //引入函数
    const routing = require('./routes');
    

    app.use(bodyparser());

    //将router注册到app里
    //批量读取文件,然后批量注册
    routing(app);
    app.listen(8080,()=>console.log('程序启动在 8080 端口了'));

    可以看到重构路由之后可以正常使用

    image-20210805204659249

    • 尽量使用类+类方法的形式编写控制器

    创建app->controllers文件夹存放控制器。

    控制器本质是中间件,中间件本质是函数,为了更合理组织这些控制器,最好采用类+类方法的形式进行编写。

    以home.js为例

    class HomeCtl {
      index(ctx){
        ctx.body = '<h1>这是主页</h1>';
      }
    }
    //导出实例化的控制器
    module.exports = new HomeCtl();
    

    然后在路由home.js使用它

    const Router = require('koa-router');
    const router = new Router();
    //导出实例化的方法
    const {index} = require('../controllers/home')
    router.get('/',index);
    

    //把实例化的路由导出
    module.exports = router;

    可以看到成功访问主页

    image-20210805210326903

    同理构建用户控制器users.js

    const db = [{name:"小风车"}]
    class UsersCtl{
    

    //获取用户列表
    find(ctx){
    ctx.body = db;
    }
    //获取特定用户
    findById(ctx){
    ctx.body = db[ctx.params.id1];
    }
    //创建用户
    create(ctx){
    db.push(ctx.request.body);
    ctx.body = ctx.request.body;
    }
    //更新用户
    update(ctx){
    db[ctx.params.id
    1] = ctx.request.body;
    ctx.body = ctx.request.body;
    }
    //删除用户
    delete(ctx){
    db.splice(ctx.params.id*1,1);
    ctx.status = 204
    }
    }

    module.exports = new UsersCtl();

    然后在路由users.js使用它

    const Router = require('koa-router');
    const router = new Router({prefix:'/users'});
    //delete是关键字,取别名
    const {find,findById,create,update,delete:del} = require('../controllers/users');
    

    router.get('/',find);

    router.post('/',create);

    router.get('/:id',findById);

    router.put('/:id',update);

    router.delete('/:id',del);

    module.exports = router;

    错误处理简介

    错误处理是编程语言或计算机硬件里的一种机制,用来处理软件或信息系统中出现的异常状况。

    异常状况有哪些?

    • 运行时错误,都返回500(运行时错误是建立在语法没有错的基础上,假如写的程序出现错误就会直接报语法错误了,程序就断掉了。)

    比如,在运行时求一个undefined的属性就会出现运行时错误

    • 逻辑错误,如找不到(404)、先决条件失败(412)、无法处理的实体(参数格式不对,422)等

    找不到某个网页或接口——返回404;

    请求某个特定用户,请求的用户id根本不存在,这个先决条件就失败了——返回412

    请求体里的参数格式不对——返回422,代表无法处理的实体

    使用错误处理的好处

    • 防止程序挂掉(在JavaScript中是使用try catch语法)
    • 告诉用户错误信息
    • 便于开发者调试

    Koa自带的错误处理

    制造404、412、500三种错误,使用Koa自带的错误处理

    404错误:客户端造成的问题,例如尝试请求没有定义的接口/authors,Koa自带的错误处理会自动返回404

    image-20210805215659198

    412错误:例如查找不存在的id

      findById(ctx){
         //请求id大于等于存储用户的数组长度了
        if(ctx.params.id*1 >= db.length){
          //手动报错
           ctx.throw(412,'先决条件失败:id大于等于数组长度了');
        }
        ctx.body = db[ctx.params.id*1];
      }
    

    Koa自带的错误处理会自动返回我们给出的报错状态码和错误信息

    image-20210805220722423

    500错误:通常是运行时错误,我们可以在语法没有错误的基础上构造一个运行时错误

    例如:使用一个未声明undefined的a

      //获取用户列表
      find(ctx){
        a.b
        ctx.body = db;
      }
    

    Koa自带的错误处理会自动返回500,而且还会在程序打印的日志打印出它的错误堆栈

    image-20210805221205099

    image-20210805221535978

    自己编写错误处理中间件

    Koa默认的错误处理有个缺点就是返回的是以文本形式的错误信息。在Restful API的最佳实践中, 要求使用JSON格式返回信息。

    如果我们想要JSON格式的错误信息,我们可以自己编写一个错误处理中间件,放在执行顺序的最前面, 来对后面执行的代码进行错误处理,并且返回JSON格式的错误信息。

    在通过断点调试时,可知404错误没有走过中间件,在最前面就报错了,自定义的错误处理无法捕捉到它;500错误时,err.status和err.statusCode都为undefined,需要手动加短路语法设置状态码,让它返回500错误

    app.use(async(ctx,next)=>{
      try{
        await next();
      }catch(err){//捕获状态
        ctx.status = err.status || err.statusCode || 500;
        ctx.body = {
          message:err.message
        }
      }
    });
    

    制造一个412错误,可以看到错误信息就以JSON格式显示出来了。

    image-20210805223551284

    使用koa-json-error进行错误处理

    koa-json-error是一款比较优秀的错误处理中间件,这个中间件是专门为纯JSON的应用准备的,非常符合RESTful API。它还有许多丰富的功能,比如返回错误堆栈信息到客户端,方便开发调试使用;还可以配置错误信息,哪条错误信息不想返回到客户端时,可以选择禁用;还可以让400错误和404错误也返回JSON。

    • 安装koa-json-error

    npm install koa-json-error --save

    • 使用koa-json-error的默认配置处理错误
    const error = require('koa-json-error')
    app.use(error())![](动画6.gif)
    

    可以看到不仅返回JSON格式的错误还返回了堆栈信息

    动画6

    • 修改配置使其在生产环境下禁用错误堆栈的返回

    返回信息中错误堆栈信息返回到客户端其实是不安全的,只能在开发阶段用一下,应该修改配置使其在生产环境下禁用错误堆栈的返回。

    修改代码

    app.use(error({
      //postFormat定制返回格式
      postFormat:(e,{stack,...rest})=>process.env.NODE_ENV === 'production'? rest : {stack,... rest}
    }))
    

    安装跨平台环境变量:npm i cross-env --save-dev

    然后在package.json中设置运行环境

      "scripts": {
        "start": "cross-env NODE_ENV=production node app",
         "dev":"nodemon app"
      },
    

    运行npm start就是在生产环境下;运行npm run dev就是在开发环境下。

    配置完毕后,在生产环境下,可以看到错误堆栈信息已经没有了

    image-20210805231356950

    使用koa-parameter校验参数

    在前面介绍到有一种异常错误是无法处理的实体(422错误),即参数格式不对。那么怎么校验参数呢?——需要使用koa-parameter校验参数

    • 安装koa-parameter

    npm i koa-parameter --save

    • 使用koa-parameter校验参数

    index.js引入注册

    const parameter = require('koa-parameter');
    //将app传进去就可以全局使用
    app.use(parameter(app));
    

    以创建用户为例

      //创建用户
      create(ctx){
        //校验请求体的name位字符串类型并且是必选的
        ctx.verifyParams({
          //必选:required 删掉也是默认为true
          name:{ type:'string',required:true },
          age:{ type:'number',required:false }
        });
        db.push(ctx.request.body);
        ctx.body = ctx.request.body;
      }
    
    • 制造422错误来测试校验结果

    可以看到如果不满足格式,就会自动返回422状态码并且把非常详细的错误信息返回给客户端。

    image-20210805232720819

  • 相关阅读:
    【Codeforces 933A】A Twisty Movement
    【Codeforces 996B】World Cup
    【Codeforces 469B】Chat Online
    鼠标点击后的CSS3跑马灯效果
    CSS的相对定位和绝对定位
    CSS3制作的一款按钮特效
    单元测试小结
    JS对输入判断变化屏蔽中文输入法输入时连续触发事件的方法
    jquery的load方法
    MVC视图特性
  • 原文地址:https://www.cnblogs.com/Small-Windmill/p/15106578.html
Copyright © 2011-2022 走看看