2
2.2 Context对象
1 | app.use(async ctx => { |
ctx.request
1 | ctx.response.body = { |
随后在浏览器中访问 http://localhost:3000/?search=koa&keywords=context
,便可以看到 一串字符串,如下所示 :
1 | { |
POST请求的参数获取方式和 GET请求不同.
Koa没有封装获取 POST请求参数的方 法,因此需要解析 Context 中的原生 Node.js请求对象 req, 或者使用 koa-bodyparser
中间件
ctx.response
在实际开发中, 除设置一个请求的响应主体外, 往往还需要通过 ctx.response.status
设置请求状态,如 200, 404, 500 等。
通过 ctx.response.type
可以设置响应的 Content-Type
, 如果响应内 容是 HTML 格式,则设置为 ctx.response.type ='html'
;如果响应内容是一张 png 图片,则设置为 ctx.response句pe ='1mage/png'
。显式地设置 Content-Type
是因为浏览器默认 的 Content-Type
是 text/plain
,如果 Content-Type
不对会发生解析错误。
通过ctx.request.accepts()
函数来判断客户端期望的数据类型
通过 ctx.response.body
设置响应体内容
和 ctx.request.accepts()
类似的一个方法是 ctx.response.is(types...)
,它可以用来检查响应类型是否是所提供的类型之一,这对创建操纵响应的中间件特别有用 。
还有一个比较常用的方法是 ctx.response.redirect(url, [alt])
,这个方法用于将状态码 302 重定 向到 URL,例如用 户登录后自动重定向到网站的首页。
ctx.state
ctx.state
是推荐 的命名空间,用于通过中间件传递信息和前端视图。类似 koa-views 这 些渲染 View层的中间件也会默认把 ctx.state里面的属性作为 View 的上下文传入。
ctx.cookies
ctx.cookies 用于获取和设置 Cookie
1 | ctx.cookies.get(name, [optionsJ); //获取Cookie |
ctx.throw
ctx.throw用于抛出错误,把错误信息返回给用户, 代码示例如下:
1 | app.use(async (ctx) => { |
2.3 Koa的中间件
通过 app.use()函数来加载中间件。
中间件函数是一个带有 ctx 和 next 两个参数的简单函数。
ctx 就是之前章节介绍的上下文,封装了 Request 和 Response 等对象; next 用于把中间件的执行权交给下游的中间件。 在 next()之前使用 await 关键字是因为 next()会返回一个 Promise 对象,而在当前中间件中位 于 next()之后的代码会暂停执行,直到最后一个中间件执行完毕后,再自下而上依次执行每 个中间件中 next()之后的代码,类似于一种先进后出的堆枝结构。
下面通过具体代码来演示中间件的执行,如下所示:
1 | app.use (async function (ctx, next) { |
这段代码中有 3个中间件, 执行结果如下:
1 | one start |
如果想将多个中间件组合成一个单一的中间件, 便于重用或导出,可以使用koa-compose
,代码如下:
1 | const all = compose ([middlewarel, middleware2, middleware3]); |
如果一个中间件没有调用 await next()
,又会发生什么情况呢? 答案是:后面的中间件将不会被执行。
常用 Koa 中间件
- koa-bodyparser
- koa-router
- koa-static
- koa-views
3 路由
前端路由主要解决了两个问题:在页面不刷新的前提下实现 URL 的变化,以及捕捉URL的变化并执行相应的页面逻辑。
RESTful 规范
REST 的全称是 Representational State Transfer,即表现层状态转移.
REST设计一般符合以下条件:
- 程序或应用的事物都应该被抽象为资源。
- 每个资源对应唯一的URI。(URI的全称是 UniformResource Identifier,即统一资源标识符,是一个用于标识某一互联网资源名称的字符串)
- 使用统一的接口对资源进行操作。
- 对资源的各种操作不会改变资源标识。
- 所有的操作都是无状态的。
在该场景中,需要完成对网站用户的新增、修改、删除和查看操作。在非RESTful架构中,一般被设计为如下所示:
1 | https://api.test.com/addUser // POST方法,请求发送新增用户信息 |
而基于RESTful架构设计的 API,全局只提供唯一的URI: https://api.test.com/users
1 | https://api.test.com/users // POST方法, 请求发送新增用户信息 |
GitHub的API设计被称为RESTful API教科书的典范,地址为https://developer.github.com/v3/
。 同时,不得不提的是 GitHub v4 的 API 使用了全新的设计风格 GraphQL ,地址为 https://developer.github.com/v4/
。 GraphQL 提供了一套完整描述,使客户端能够没有任何冗余地准确获得需要的数据。
用法
如果一条路由请求在 all()方法和其他方法中同时命中, 只有执行了 await next()
,这条路由请求才会在 all()方法和其他方法中都起作用
在项目中, all()方法一般用来设置请求头,如设置过期时间、CORS (Cross-Origin Resource Sharing ,跨域资源共享)等,
1 | //通过调用路由的名称 user,生成路由==”/users/3” |
命名路由
使用 router.url()
方法可以在代码中根据路由名称和参数(可选)生成具体的 URL,而不用采用宇符串拼接的方式去生成 URL。
嵌套路由
1 | const forums = new Router (); |
路由前缀
1 | let router = new Router ({ |
URL参数
1 | router.get('/:category/:title', function (ctx, next) { |
通过 koa-router实现接口的权限控制
常见鉴别用户权限的方式有两种,一种是广泛使用的 Cookie-Based Authentication
(基于 Cookie 的认证模式),另一种是 Token-Based Authentication
(基于 Token 的认证模式) 。
本案例采用 Token 方式认证。
Token 方式最大的 优点在于采用了无状态机制,在此基础上,可以实现天然的跨域支持、前后端分离等,同时降低了服务端开发和维护的成本 。
Token 方式的缺点在于服务器每次都需要对 Token 进行校验,这一步骤会对服务器产生运算压力 。
另一方面,无状态 API 缺乏对用户流程或异常的控制,为了避免出现一些例如回放攻击的异常情况,应该设置较短的过期时间,且需要对密钥进行严格的保护 。对于具有复杂流程的高危场景(如支付等〉, 则要谨慎选择 Token 认证模式 。
在本案例中,选用 jsonwebtoken (以下简称 JWT)来实现 Token 的生成、校验和解码。Token 的中间件实现选择 koa-jwt
。
1 | const Koa = require('koa'); |
middleware/admin
1 | module.exports = () => { |
4 HTTP
URI 和 URL
- URI: Uniform Resource Identifier (统一资源标识符)
- URL: Uniform Resource Locator (统一资源定位符〉
URL 是 URI的一个子集
一个完整的 URL 一般由7个部分组成,如下所示:
scheme: [// [user [:password]@] host[:port]] [/path] [?query] [#fragment]
- scheme:使用的协议,如 FTP (File Transfer Protocol)、 HTTP 等 。
- user[:password]: 表示访问资源的用户名和密码,常见于 FTP 协议中 。
- host:主机,如 IP 地址或域名。
- port:端口号,如 HTTP 默认为 80 端口 。
- path:访问资源的路径。
- query: 请求数据,以“?”开头。
- fragment: 定位锚点,以“#”开头,可用于快速定位网页对应的段落。
常用的 HTTP 状态码
HTTP 状态码主要包括 1 (消息)、 2(成功)、 3(重定向)、 4(请求错误〉、 5 和 6 (服务器错误)
常用的请求方法
常用的 HTTP 首部字段
5 构建 Koa Web 应用
MVC 全名是 Model View Controller,即模型、视图、 控制器。
- Model: 负责数据访问
- Controller: 负责处理消息
- View: 负责显示数据
三层架构( 3-TierArchitecture )是一个分层式的架构设计理念,如有必要,也可以分为多层。
分层的设计理念契合了“高内聚低藕合” 的思想,在软件体系架构设计中是最常见、也是最重要的一种结构。
通常意义上的三层架构是将整个业务应用划分为 界面层( User Interface Layer)、业务逻辑层( Business Logic Layer)、数据访问层( Data Access Layer)
在 Koa 中实现 MVC
分离Router
路由部分的代码可以分离成独立的文件,并根据项目结构放置在适当的位置,方便管理。本示例中,将路由部分的代码独立在 router.js文件中,并置于项目根目录下
1 | |-- app.js |
router.js
1 | const router = require('koa-router')(); |
app.js
1 | const Koa = require('koa'); |
分离 controller
controller 负责存放响应 HTPP 请求的业务逻辑代码
controller/home.js
1 | const HomeService = require('../service/home'); |
业务层的代码分离成功之后,需要修改 router.js文件,在文件中引入 controller/home.js, 并以 home.js 中的函数作为 HTTP 请求的响应函数
注意: 上面的router.js代码是分离后的
在实际开发中,有时 Node.js 需要进行一些数据访问层的操作,如操作数据库、调用第三方接口获取数据等,因此需要分离出 Service 层来进行相应处理 。相应地,Controller 只需要进行业务逻辑部分的处理即可。
分离 Service
service/home.js
1 | module.exports = { |
注意: 上面的 controller/home.js 代码也是分离后的
至此,项目代码已经完成了基本的结构分离,得到的模块包括单独处理 HTTP 请求的路由文件 router.js、对 HTTP 请求进行响应的 Controller 文件,以及为 Controller 提供 Model 数据的 Service 文件。
模板引擎
目前,可以在 Node.js 中应用且比较成熟的模板引擎有很多,例如 EJS、 Jade (现己改 名为 Pug)、 Handlebars、 Nunjucks、 Swig等
根据自己的需求选择合适的模板引擎
koa-views
静态资源
在访问站点时,浏览器会接收到各种资源,常见的有 HTML、 JavaScript脚本文件、 css 样式表、 GIF 图片资源和 Flash
那么浏览器是通过什么方式区分不同的资源类型, 又是如何决定以什么形式来显示的呢?
答案是根据 MIMEType,也就是该资源的媒体类型。
koa-static
其他常用开发技巧
koa-json
使用 koa-multer 中间件实现文件上传
注意: Multer 不会处理任何非 multipart/form-data 类型的表单数据
1 | canst koa = require('koa'); |
6 数据库
在Koa中应用MySQL数据库
在 Node.js 中, 一般采用 Sequelize 这个 ORM 类库来操作数据库。 Sequelize 支持多种数据库,如 PostgreSQL、 MySQL、 SQLite和 MSSQL。
npm install sequelize -save
1 | const Sequelize = require('sequelize'); |
提示:由于建立数据库连接存在成本,所以通常情况下会通过连接池来提升数据库的连接效率,以避免每次执行数据库操作时重复建立连接。Sequelize默认支持连接池,可以在创建连接时指定 pool 参数来修改默认的连接池设置。
通过 Sequelize,我们可以直接定义数据模型来创建数据表,而不必去数据库中使用 SQL 脚本创建。可以通过 define 方法定义模型
1 | const Categoty = sequelize.define('category', { |
Sequelize 会默认为创建的数据表自动创建 createdAt 和 updatedAt 字段。同时,Sequelize 也提供了配置项来禁用或修改这些字段
在定义模型时,也可以为字段定义 Getter和 Setter, 定义写入数据的规则
1 | const Custom = sequelize.define('custom', { |
同步
可以通过 sync 方法将定义的模型同步到数据库中。既可以同步单个表,也可以同步全部表
1 | sequelize.sync().then(() => { //同步全部模型 |
查询
1 | await Product.findAll(); // 查出 Product表中的所有数据 |
1 | const { Op } = require( 'sequelize' ); |
在 Koa 中应用 MongoDB 数据库
在开发 Node.js 应用时, 一般借助 Mongoose 类库来访问数据库
在Koa中应用Redis数据库
npm install redis -save
7 单元测试
8 优化与部署
日志
npm install log4js -save
根据日志的用途, 一般可以将日志分为访问日志和应用日志
应用日志包括 debug、 info、 warn 和 error 等不同级别
部署
npm install pm2@lastst -g