控制器简介

什么是控制器?

  • 拿到路由分配的任务,并执行
  • 在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上如何获取各个参数。

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

Node.js学习笔记【五】_json

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

​ctx.query​​:Query String

​ctx.params​​:Router Params

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

请求创建新用户

Node.js学习笔记【五】_koa_02

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

Node.js学习笔记【五】_koa_03

​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”Node.js学习笔记【五】_json_04

 

编写控制器最佳实践

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

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

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

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

Node.js学习笔记【五】_错误处理_05

重构首页路由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 端口了'));​

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

Node.js学习笔记【五】_错误信息_06

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

创建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;​

可以看到成功访问主页

Node.js学习笔记【五】_koa_07

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

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

​//获取用户列表​​​​find(ctx){​ ​​​ctx.body = db;​ ​​​}​ ​​​//获取特定用户​ ​​​findById(ctx){​ ​​​ctx.body = db[ctx.params.id 1];
}
//创建用户
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

Node.js学习笔记【五】_json_08

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

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


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

Node.js学习笔记【五】_错误信息_09

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

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

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


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

Node.js学习笔记【五】_koa_10

Node.js学习笔记【五】_koa_11

自己编写错误处理中间件

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格式显示出来了。

使用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格式的错误还返回了堆栈信息

Node.js学习笔记【五】_json_12

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

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

修改代码

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 star​​t就是在​​生产​​环境下;运行​​npm run dev​​就是在​​开发​​环境下。

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

Node.js学习笔记【五】_错误信息_13

使用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状态码​​并且把非常详细的错误信息返回给客户端。

Node.js学习笔记【五】_koa_14