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

原文地址:https://www.cnblogs.com/Small-Windmill/p/15106578.html