Koa笔记

初始化项目

自建

  • 手动新建项目目录

  • 切换到该目录下,执行npm init -y

  • 下载koa等:npm i koa koa-router koa-bodyparser koa-json koa-logger koa-static --save

使用 koa-generator

  • 全局安装koa-generatornpm i koa-generator -g

  • 测试是否已安装:koa -Vkoa2 -V

  • 生成项目:koa2 demokoa2 demo -e --ejs

app

应用配置是 app 实例属性,目前支持的配置项如下

  • app.env:默认为 NODE_ENV or "development"
  • app.proxy:如果为 true,则解析"Host"header 域,并支持 X-Forwarded-Host
  • app.subdomainOffset:默认为2,表示 .subdomains 所忽略的字符偏移量。

app.listen()

  • 常规写法
const Koa = require('koa') const app = new Koa() app.listen(3000)
  • 实际上是以下代码的语法糖
const http = require('http') const Koa = require('koa') const app = new Koa() http.createServer(app.callback()).listen(3000)
  • 同时支持 HTTPSHTTPS,或者在多个端口监听同一个应用写法
const http = require('http') const https = require('https') const Koa = require('koa') const app = new Koa() http.createServer(app.callback()).listen(3000) https.createServer(app.callback()).listen(3001)

app.callback()

返回一个适合 http.createServer() 方法的回调函数用来处理请求。

也可以使用这个回调函数将您的app挂载在 Connect/Express 应用上

app.use(function)

为应用添加指定的中间件

app.keys

设置签名cookie密钥。

该密钥会被传递给KeyGrip, 当然,您也可以自己生成 KeyGrip

app.keys = ['im a newer secret', 'i like turtle']; app.keys = new KeyGrip(['im a newer secret', 'i like turtle'], 'sha256');

在进行cookie签名时,只有设置 signedtrue 的时候,才会使用密钥进行加密

ctx.cookies.set('name', 'tobi', { signed: true })

app.context

app.context是从中创建ctx的原型。

可以通过编辑app.contextctx添加其他属性。
当需要将ctx添加到整个应用程序中使用的属性或方法时,这将会非常有用。
这可能会更加有效(不需要中间件)和/或更简单(更少的require()),而不必担心更多的依赖于ctx,这可以被看作是一种反向模式。

// 示例:从ctx中添加对数据库的引用 app.context.db = db() app.use(async ctx => { console.log(ctx.db) })

Context

ctx.req

Noderequest 对象。

ctx.res

Noderesponse 对象。

ctx.request

KoaRequest 对象。

ctx.response

KoaResponse 对象。

ctx.state

推荐的命名空间,用于通过中间件传递信息到前端视图

ctx.state.user = await User.find(id)

ctx.app

应用实例引用

ctx.cookies.get(name, [options])

获得 cookie 中名为 name 的值,options 为可选参数:

  • signed 如果为 true,表示请求时 cookie 需要进行签名。

ctx.cookies.set(name, value, [options])

设置 cookie 中名为 name 的值,options 为可选参数:

  • maxAge:一个数字,表示 Date.now()到期的毫秒数
  • signed:是否要做签名
  • expirescookie有效期。用来设置持久化的cookie,当设置了它之后,cookie在指定的时间到达之前都不会过期。
  • domaincookie 的域。用来比较请求URL中服务端的域名。如果域名匹配成功,或这是其子域名,则继续检查path属性
  • pathcookie的路径,默认为 '/'。当域名和路径都匹配时,cookie才会随请求发送
  • securefalse 表示 cookie 通过 HTTP 协议发送,true 表示 cookie 通过 HTTPS 发送。
  • httpOnly:设置这个属性将禁止javascript脚本获取到这个cookie,这可以用来帮助防止跨站脚本攻击。
  • overwrite:一个布尔值,表示是否覆盖以前设置的同名的Cookie(默认为false)。 如果为true,在设置此cookie时,将在同一请求中使用相同名称(不管路径或域)设置的所有Cookie将从Set-Cookie头部中过滤掉。

ctx.throw([status], [msg], [properties])

抛出包含 .status 属性的错误,默认为 500。该方法可以让 Koa 准确的响应处理状态。 Koa支持以下组合:

ctx.throw(400); ctx.throw(400, 'name required'); ctx.throw(400, 'name required', { user: user });

ctx.throw(400, 'name required') 等价于:

const err = new Error('name required'); err.status = 400; err.expose = true; throw err;

ctx.assert(value, [status], [msg], [properties])

!value时, Helper 方法抛出一个类似.throw()的错误

ctx.assert(ctx.state.user, 401, 'User not found. Please login!');

Request

Koa Request 对象是对 noderequest 进一步抽象和封装,提供了日常 HTTP 服务器开发中一些有用的功能。

属性或方法 说明
request.headerrequest.header= 请求头对象读取和设置
request.headersrequest.headers= 等价于 request.header
request.methodrequest.method= 请求方法读取和设置,在实现中间件时非常有用
request.length 以数字的形式返回 request 的内容长度(Content-Length),或者返回 undefined
request.urlrequest.url= 请求url地址读取和重写
request.originalUrl 获取请求原始地址
request.origin 获取URL原始地址, 包含 protocolhost。eg:http://example.com
request.href 获取完整的请求URL, 包含 protocol, hosturl。eg:http://example.com/foo/bar?q=1
request.pathrequest.path= 获取请求路径名;设置请求路径名并保留当前查询字符串
request.querystringrequest.querystring= 获取查询参数字符串(url?后面的部分),不包含?;设置原始查询字符串
request.queryrequest.query= 将查询参数字符串进行解析并以对象的形式返回,如果没有查询参数字字符串则返回一个空对象。根据给定的对象设置查询参数字符串。注意:该方法不支持嵌套解析
request.searchrequest.search= 获取查询参数字符串,包含?;设置原始查询字符串
request.protocol 返回请求协议,"https" 或者 "http"。 当 app.proxy 设置为 true 时,支持 X-Forwarded-Host
request.host 获取 host (hostname:port)。 当 app.proxy 设置为 true 时,支持 X-Forwarded-Host
request.hostname 获取 hostname。当 app.proxy 设置为 true 时,支持 X-Forwarded-Host
request.URL 获取 WHATWG 解析的对象(IPv6).
request.secure 简化版 this.protocol == "https",用来检查请求是否通过 TLS 发送
request.ip 请求远程地址。 当 app.proxy 设置为 true 时,支持 X-Forwarded-Host
request.ips X-Forwarded-For 存在并且 app.proxy 有效,将会返回一个有序(从 upstreamdownstreamip 数组。 否则返回一个空数组。
request.subdomains 以数组形式返回子域名。子域名是在host中逗号分隔的主域名前面的部分。
request.type 获取请求 Content-Type,不包含像 "charset" 这样的参数。eg:"image/png"
request.charset 获取请求 charset,没有则返回 undefined。eg:"utf-8"
request.fresh 检查请求缓存是否 "fresh"(内容没有发生变化)。该方法用于在 If-None-Match / ETag, If-Modified-SinceLast-Modified 中进行缓存协调
request.stale req.fresh 相反。
request.is(types...) 检查请求所包含的 "Content-Type" 是否为给定的 type 值。 如果没有 request body,返回 undefined。 如果没有 content type,或者匹配失败,返回 false。 否则返回匹配的 content-type
request.accepts(types) 检查给定的类型 types(s) 是否可被接受,当为 true 时返回最佳匹配,否则返回 falsetype 的值可以是一个或者多个 mime 类型字符串。 比如 "application/json" 扩展名为 "json",或者数组 ["json", "html", "text/plain"]
request.acceptsEncodings(encodings) 检查 encodings 是否可以被接受,当为 true 时返回最佳匹配,否则返回 false注意:您应该在 encodings 中包含 identity
request.acceptsCharsets(charsets) 检查 charsets 是否可以被接受,如果为 true 则返回最佳匹配, 否则返回 false
request.acceptsLanguages(langs) 检查 langs 是否可以被接受,如果为 true 则返回最佳匹配,否则返回 false
request.idempotent 检查请求是否为幂等(idempotent)
request.socket 返回请求的socket。
request.get(field) 返回请求头
  • request.is(types...)
// With Content-Type: text/html; charset=utf-8 ctx.is('html'); // => 'html' ctx.is('text/html'); // => 'text/html' ctx.is('text/*', 'text/html'); // => 'text/html' // When Content-Type is application/json ctx.is('json', 'urlencoded'); // => 'json' ctx.is('application/json'); // => 'application/json' ctx.is('html', 'application/*'); // => 'application/json' ctx.is('html'); // => false // 比如说希望保证只有图片发送给指定路由 if (ctx.is('image/*')) { // process } else { ctx.throw(415, 'images only!'); }
  • request.accepts(types)
// Accept: text/html ctx.accepts('html'); // => "html" // Accept: text/*, application/json ctx.accepts('html'); // => "html" ctx.accepts('text/html'); // => "text/html" ctx.accepts('json', 'text'); // => "json" ctx.accepts('application/json'); // => "application/json" // Accept: text/*, application/json ctx.accepts('image/png'); ctx.accepts('png'); // => false // Accept: text/*;q=.5, application/json ctx.accepts(['html', 'json']); ctx.accepts('html', 'json'); // => "json" // No Accept header ctx.accepts('html', 'json'); // => "html" ctx.accepts('json', 'html'); // => "json" // this.accepts() 可以被调用多次,或者使用 switch switch (ctx.accepts('json', 'html', 'text')) { case 'json': break; case 'html': break; case 'text': break; default: ctx.throw(406, 'json, html, or text only'); }
  • request.acceptsEncodings(encodings)
// Accept-Encoding: gzip ctx.acceptsEncodings('gzip', 'deflate', 'identity'); // => "gzip" ctx.acceptsEncodings(['gzip', 'deflate', 'identity']); // => "gzip" // 当没有传递参数时,返回包含所有可接受的 encodings 的数组 // Accept-Encoding: gzip, deflate ctx.acceptsEncodings(); // => ["gzip", "deflate", "identity"]
  • request.acceptsCharsets(charsets)
// Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5 ctx.acceptsCharsets('utf-8', 'utf-7'); // => "utf-8" ctx.acceptsCharsets(['utf-7', 'utf-8']); // => "utf-8" // 当没有传递参数时, 返回包含所有可接受的 charsets 的数组 // Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5 ctx.acceptsCharsets(); // => ["utf-8", "utf-7", "iso-8859-1"]
  • request.acceptsLanguages(langs)
// Accept-Language: en;q=0.8, es, pt ctx.acceptsLanguages('es', 'en'); // => "es" ctx.acceptsLanguages(['en', 'es']); // => "es" // 当没有传递参数时,返回包含所有可接受的 langs 的数组 // Accept-Language: en;q=0.8, es, pt ctx.acceptsLanguages(); // => ["es", "pt", "en"]

Response

Koa Response 对象是对 noderesponse 进一步抽象和封装,提供了日常 HTTP 服务器开发中一些有用的功能。

属性或方法 说明
response.header Response header 对象。
response.headers Response header 对象。等价于 response.header
response.socket Request socket
response.statusresponse.status= 获取响应状态。 默认情况下,response.status设置为404,而不像node’的res.statusCode默认为200;通过数字设置响应状态
response.messageresponse.message= 获取响应状态消息。默认情况下, response.message关联response.status;将响应状态消息设置为给定值。
response.lengthresponse.length= 如果 Content-Length 作为数值存在,或者可以通过 ctx.body 来进行计算,则返回相应数值,否则返回 undefined;将响应Content-Length设置为给定值。
response.bodyresponse.body= 获取响应体;设置响应体
response.set(field, value) 设置 response header 字段 field 的值为 value。eg:ctx.set('Cache-Control', 'no-cache');
response.append(field, value) 添加额外的字段 field 的值为 value。eg:ctx.append('Link', '<http://127.0.0.1/>');
response.set(fields) 使用对象同时设置 response header 中多个字段的值。eg:ctx.set({ 'Etag': '1234', 'Last-Modified': date });
response.remove(field) 移除 response header 中字段 filed
response.typeresponse.type= 获取 response Content-Type,不包含像"charset"这样的参数;通过 mime 类型的字符串或者文件扩展名设置 response Content-Type
response.is(types...) ctx.request.is()非常类似。用来检查响应类型是否是所提供的类型之一。这对于创建操作响应的中间件特别有用
response.redirect(url, [alt]) 执行 [302] 重定向到对应 url
response.attachment([filename]) 设置 "attachment"Content-Disposition,用于给客户端发送信号来提示下载。filename 为可选参数,用于指定下载文件名。
response.headerSent 检查 response header 是否已经发送,用于在发生错误时检查客户端是否被通知。
response.lastModifiedresponse.lastModified= 如果存在 Last-Modified,则以 Date 的形式返回;以 UTC 格式设置 Last-Modified。您可以使用 Datedate 字符串来进行设置。
response.etag= 设置 包含 "sETag。注意没有对应的 res.etag 来获取其值。
response.vary(field) 不同于field
response.flushHeaders() 刷新任何设置的响应头,并开始响应体。

response.body=设置响应体为如下值

  • StringContent-Type 默认为 text/html 或者 text/plain,两种默认 charset 均为 utf-8Content-Length 同时会被设置。
  • BufferContent-Type 默认为 application/octet-streamContent-Length同时被设置。
  • StreamContent-Type 默认为 application/octet-stream
  • ObjectContent-Type 默认为 application/json。 这包括普通对象{ foo: 'bar' }和数组['foo', 'bar']
  • nullno content response
  • response.typeresponse.type=
const ct = ctx.type; // => "image/png" ctx.type = 'text/plain; charset=utf-8'; ctx.type = 'image/png'; ctx.type = '.png'; ctx.type = 'png'; // 注意:当为你选择一个合适的charset时,例如response.type = 'html'将默认为"utf-8"。 // 如果需要覆盖charset,请使用ctx.set('Content-Type', 'text/html')直接设置响应头字段值。
  • response.redirect(url, [alt])
// 字符串 "back" 是一个特殊参数,其提供了 Referrer 支持。当没有Referrer时,使用 alt 或者 / 代替。 ctx.redirect('back'); ctx.redirect('back', '/index.html'); ctx.redirect('/login'); ctx.redirect('http://google.com'); // 如果想要修改默认的 [302] 状态,直接在重定向之前或者之后执行即可。如果要修改 body,需要在重定向之前执行。 ctx.status = 301; ctx.redirect('/cart'); ctx.body = 'Redirecting to shopping cart';
  • response.lastModified=
ctx.response.lastModified = new Date();
  • response.etag=
ctx.response.etag = crypto.createHash('md5').update(ctx.body).digest('hex');

路由:koa-router

基本使用

// 引入 koa模块 const Koa = require('koa') // 引入是实例化路由 const router = require('koa-router')() // 实例化 const app = new Koa() router.get('/', async (ctx) => { ctx.body = "首页" }) // 启动路由 app.use(router.routes()) /** * router.allowedMethods()作用: 这是官方文档的推荐用法,我们可以 * 看到 router.allowedMethods()用在了路由匹配 router.routes()之后,所以在当所有 * 路由中间件最后调用.此时根据 ctx.status 设置 response 响应头 */ app.use(router.allowedMethods()) app.listen(3000)

路由分层

  • 在根目录下新建router/目录

  • router/目录下新建post.js文件

const router = require('koa-router')() router.prefix('/post') router.get('/:id', async ctx => { console.log(ctx.params.id) ctx.body = 'post detail' }) router.get('/list', async ctx => { ctx.body = 'post list' }) router.post('/add', async ctx => { const { body } = ctx.request console.log(body) ctx.body = 'post add' }) module.exports = router
  • router/目录下新建user.js文件
const router = require('koa-router')() router.get('/', async ctx => { ctx.body = 'user' }) router.get('/list', async ctx => { ctx.body = 'user/list' }) module.exports = router
  • router/目录下新建index.js文件
const router = require('koa-router')() const userRouter = require('./user') const postRouter = require('./post') const testRouter = require('./test') router.get('/', async (ctx, next) => { ctx.body = 'koa index' }) router.get('/404', async (ctx, next) => { ctx.body = '404 page' }) // 方式一:userRouter,前缀写在这里 // 方式二:postRouter,前缀写在post.js文件里面 router.use('/user', userRouter.routes()) .use(postRouter.routes()) module.exports = router

动态路由参数

// 请求方式 http://域名/product/123 router.get('/product/:aid', async (ctx) => { // 获取动态路由的数据 { aid: '123' } console.log(ctx.params) })

Get请求参数

  • koa2中,GET传值通过request接收,但是接收的方法有两种queryquerystring

  • query:返回的是格式化好的参数对象

  • querystring:返回的是请求字符串

// http://localhost:3000/newscontent?aid=123&name=zhangsan router.get('/newscontent', async (ctx) => { // 从ctx中读取get传值 // { aid: '123', name: 'zhangsan' } 获取的是对象,用的最多的方式 推荐 console.log(ctx.query) // aid=123&name=zhangsan 获取的是一个字符串 console.log(ctx.querystring) // 获取url地址 console.log(ctx.url) // ctx里面的request里面获取get传值 console.log(ctx.request.url); // { aid: '123', name: 'zhangsan' } console.log(ctx.request.query) // aid=123&name=zhangsan console.log(ctx.request.querystring) })

Post请求参数

对于POST请求的处理,koa2没有封装获取参数的方法,需要通过解析上下文context中的原生node.js请求对象req,将POST表单数据解析成query string(例如:a=1&b=2&c=3),再将query string 解析成JSON格式(例如:{"a":"1", "b":"2", "c":"3"}

  • 解析post数据,支持数据格式x-www-form-urlencoded
// 只支持 x-www-form-urlencoded格式 // 解析上下文里node原生请求的POST参数 function parsePostData( ctx ) { return new Promise((resolve, reject) => { try { let postdata = ""; ctx.req.addListener('data', (data) => { postdata += data }) ctx.req.addListener("end",function(){ let parseData = parseQueryStr( postdata ) resolve( parseData ) }) } catch ( err ) { reject(err) } }) } // 将POST请求参数字符串解析成JSON function parseQueryStr( queryStr ) { let queryData = {} let queryStrList = queryStr.split('&') console.log( queryStrList ) for ( let [ index, queryStr ] of queryStrList.entries() ) { let itemList = queryStr.split('=') queryData[ itemList[0] ] = decodeURIComponent(itemList[1]) } return queryData } // 使用 router.post('/test/post', async ctx => { const body = await parsePostData(ctx) console.log(body) ctx.body = body })

一般都会使用中间件处理,比如使用koa-bodyparserkoa-body

koa-bodyparser

koa-bodyparser默认支持application/jsonapplication/x-www-urlencoded类型的数据,但是不支持multipart/form-data类型的数据

上传图片等可以搭配koa-multer使用

  • 下载koa-bodyparsernpm i koa-bodyparser --save

  • 修改app.js文件

const bodyparser = require('koa-bodyparser') // middleware app.use(bodyparser()) // app.use(bodyparser({ // enableTypes: ['json', 'form', 'text'] // }))
  • 在路由文件中使用
router.post('/test/post', async ctx => { const { body } = ctx.request console.log(body) })

koa-body

支持三种类型 multipart/form-dataapplication/x-www-urlencodedapplication/json

  • 下载koa-bodynpm i koa-body --save

  • 修改app.js文件

const koaBody = require('koa-body') // middleware app.use(koaBody())
  • 在路由文件中使用
router.post('/test/post', async ctx => { const { body } = ctx.request console.log(body) })

一个文件上传示例。
文件上传到public/upload/目录下,
文件存放在当前日期命名的目录下,
文件名随机生成。
比如:public/upload/20180621/123123123123.txt

  • 新建public/upload/目录

  • utils/目录下新建upload.js文件

const path = require('path') const fs = require('fs') // 根据日期生成文件夹名称 function getUploadDirName(){ const date = new Date(); let month = Number.parseInt(date.getMonth()) + 1; month = month.toString().length > 1 ? month : `0${month}`; const dir = `${date.getFullYear()}${month}${date.getDate()}`; return dir; } // 检查文件夹路径是否存在,如果不存在则创建文件夹 function checkDirExist(path) { if (!fs.existsSync(path)) { fs.mkdirSync(path) } } // 获取文件的后缀 function getUploadFileExt(name) { let ext = name.split('.') return ext[ext.length - 1] } // 根据日期生成上传的文件名 function getUploadFileName(ext){ return `${Date.now()}${Number.parseInt(Math.random() * 10000)}.${ext}`; } module.exports = { getUploadDirName, checkDirExist, getUploadFileExt, getUploadFileName }
  • 修改app.js文件
const path = require('path') const koaBody = require('koa-body') const { getUploadDirName, checkDirExist, getUploadFileExt, getUploadFileName } = require('./utils/upload') // middleware app.use(koaBody({ multipart: true, // 支持文件上传 encoding:'gzip', formidable:{ uploadDir: path.join(__dirname, 'public/upload/'), // 设置文件上传目录 keepExtensions: true, // 保持文件的后缀 maxFieldsSize: 2 * 1024 * 1024, // 文件上传大小 onFileBegin: (name, file) => { // 文件上传前的设置 // 获取文件后缀 const ext = getUploadFileExt(file.name) // 最终要保存到的文件夹目录 const dirName = getUploadDirName() const dir = path.join(__dirname, `public/upload/${dirName}`) // 检查文件夹是否存在如果不存在则新建文件夹 checkDirExist(dir) // 获取文件名称 const fileName = getUploadFileName(ext) // 重新覆盖 file.path 属性 file.path = `${dir}/${fileName}` app.context.uploadpath = app.context.uploadpath ? app.context.uploadpath : {}; app.context.uploadpath[name] = `${dirName}/${fileName}` }, onError: (err) => { console.log(err) } } }))
  • 在路由文件中使用
router.post('/upload',async (ctx)=>{ // console.log(ctx.request.files); console.log(ctx.uploadpath) ctx.body = { code: 0, data: ctx.uploadpath } })

静态资源:koa-static

  • 下载koa-staticnpm i koa-static --save

  • 修改app.js,引入koa-static

const static = require('koa-static') app.use(static(__dirname + '/public'))

模板引擎

ejs

EJS是一个JavaScript模板库,用来从JSON数据中生成HTML字符串。Koa2框架中ejs可以把数据库查询的数据渲染到模板上面,实现一个动态网站。

  • 下载koa-views ejsnpm i koa-views ejs --save

  • 修改app.js文件,引入koa-views配置

const views = require('koa-views') // 这么配置的话-模板文件扩展名为html // app.use(views(__dirname + '/views', { // map: { html: 'ejs' } // })) // 这么配置的话-模板文件扩展名为ejs app.use(views(__dirname + '/views', { extension: 'ejs' }))
  • 使用ejs
router.get('/', async (ctx, next) => { await ctx.render('index', { title: '标题' }) })
  • views/目录下新建index.html或者index.ejs文件(根据配置)
<!DOCTYPE html> <html> <head> <title><%= title %></title> </head> <body> <h1><%= title %></h1> <p>EJS Welcome to <%= title %></p> <img src="/img/logo.png" alt="" /> </body> </html>

ejs模板引擎基本语法

<!-- 引入模板 --> <%- include header.ejs %> <!-- 绑定数据 --> <%= h %> <!-- 绑定html数据 --> <%- h %> <!-- 模板判断语句 --> <% if (user) { %> <h2><%= user.name %></h2> <% } else { %> <h2>小明</h2> <% } %> <!-- 模板中foreach循环数据 --> <ul> <% items.forEach(function(item){ %> <li><%= item.title %></li> <% }) %> </ul> <!-- 模板中for循环数据 --> <% for (var i =1; i <=10; i++ ) { %> <br/> <%= i %> <%# will output the numbers 1-10 %> <% } %>

art-template

art-template 是一个简约、超快的模板引擎。

采用作用域预声明的技术来优化模板渲染速度,从而获得接近 JavaScript 极限的运行性能,并且同时支持 NodeJS 和浏览器。

art-template支持ejs的语法,也可以用自己的类似angular数据绑定的语法

  • 下载art-template koa-art-templatenpm i art-template koa-art-template --save

  • 修改app.js文件,配置模板

const render = require('koa-art-template') render(app, { root: path.join(__dirname, 'views'), extname: '.html', debug: false // debug: process.env.NODE_ENV !== 'production' })
  • 使用模板
router.get('/', async (ctx, next) => { await ctx.render('index', { title: '标题' }) })
  • views/目录下新建index.html
<!DOCTYPE html> <html> <head> <title>{{ title }}</title> </head> <body> <h1>{{ title }}</h1> <p>EJS Welcome to {{ title }}</p> <img src="/img/logo.png" alt="" /> </body> </html>

art-template模板引擎基本语法

<!-- 引入模板 --> {{ include 'public/footer.html' }} <!-- 绑定数据 --> {{ list.name }} <!-- 绑定html数据 --> {{ @list.h }} <!-- 条件判断 --> {{ if num>20 }} 大于20 {{ else }} 小于20 {{ /if }} <!-- 循环数据 --> {{ each list.data }} {{ $index }}---{{ $value }} {{ /each }}

错误页面

  • 配置未允许的请求方式
// disabled methods app.use(async (ctx, next) => { console.log(ctx.method) if (![ 'GET', 'POST' ].includes(ctx.method)) { ctx.body = '403' return } else { return next() } })
  • 配置错误页面
// 方法一:放在app.use(router.routes()).use(router.allowedMethods())前面 app.use(async (ctx,next) => { try { // 执行后代的代码 await next() if(!ctx.body) { // 没有资源 ctx.status = 404 await ctx.render('error/404') } } catch(e) { // 如果后面的代码报错 返回500 ctx.status = 500 await ctx.render('error/500') } }) // 方法二:放在app.use(router.routes()).use(router.allowedMethods())后面 app.use(async (ctx,next) => { const status = ctx.response.status switch (status) { case 404: await ctx.render('error/404') break case 500: await ctx.render('error/500') break default: await next() } })
  • 代码中可以通过ctx.throw()主动抛出错误
// 抛出500错误 ctx.throw(500)

cookie和session

cookie

cookie是存储于访问者的计算机中的变量。可以让我们用同一个浏览器访问同一个域名的时候共享数据

koa提供了从上下文直接读取、写入cookie的方法

  • ctx.cookies.get(name, [options]):读取上下文请求中的cookie
  • ctx.cookies.set(name, value, [options]):在上下文中写入cookie
options 名称 options 值
maxAge 一个数字表示从 Date.now() 得到的毫秒数
expires 过期的 Date
path 路径, 默认是’/’
domain 域名
secure 安全 cookie 默认false,设置成true表示只有 https可以访问
httpOnly 是否只是服务器可访问 cookie, 默认是 true
overwrite 一个布尔值,表示是否覆盖以前设置的同名的 cookie (默认是 false). 如果是 true, 在同一个请求中设置相同名称的所有 Cookie(不管路径或域)是否在设置此Cookie 时从 Set-Cookie 标头中过滤掉。
router.get('/set', async ctx => { ctx.cookies.set( 'cid', 'hello world', { // domain: 'localhost', // 写cookie所在的域名 // path: '/index', // 写cookie所在的路径 maxAge: 10 * 60 * 1000, // cookie有效时长 // expires: new Date('2017-02-15'), // cookie失效时间 // httpOnly: false, // 是否只用于http请求中获取 overwrite: false // 是否允许重写 } ) ctx.body = 'cookie is ok' }) router.get('/get', async ctx => { const ck = ctx.cookies.get('cid') console.log(ck) ctx.body = ck })
  • Koa中设置中文Cookie
console.log(new Buffer('hello, world!').toString('base64'));// 转换成base64字符串:aGVsbG8sIHdvcmxkIQ== console.log(new Buffer('aGVsbG8sIHdvcmxkIQ==', 'base64').toString());// 还原base64字符串:hello, world!

session

session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而session保存在服务器上

当浏览器访问服务器并发送第一次请求时,服务器端会创建一个session对象,生成一个类似于key,value的键值对, 然后将key(cookie)返回到浏览器(客户)端,
浏览器下次再访问时,携带key(cookie),找到对应的session(value)。 客户的信息都保存在session

koa2原生功能只提供了cookie的操作,但是没有提供session操作。
session就只用自己实现或者通过第三方中间件实现

koa-session

  • 下载koa-sessionnpm i koa-session --save

  • 修改app.js文件,配置session

// appkey:['abcdefghigklmnopqrstuvwsyz'] app.keys = appkey; const CONFIG = { key: 'ysid', //cookie key (default is koa:sess) maxAge: 1000*60*60*24, // cookie的过期时间 maxAge in ms (default is 1 days) overwrite: true, //是否可以overwrite (默认default true) httpOnly: true, //cookie是否只有服务器端可以访问 httpOnly or not (default true) secure: true, //默认false,设置成true表示只有https可以访问 signed: true, //签名默认true rolling: true, //在每次请求时强行设置cookie,这将重置cookie过期时间(默认:false) renew: true, //(boolean) 会话即将过期时更新会话 sameSite: true,//-为Cookie设置相同的站点策略(默认为 false)。这可以被设置到'strict','lax','none',或true (映射到'strict')。 } app.use(convert(session(CONFIG, app)))
  • 读取和设置session
// 设置值 ctx.session.username = "张三" // 获取值 ctx.session.username

koa-session-minimal

  • 下载koa-session-minimalnpm i koa-session-minimal --save

  • 修改app.js文件,配置session

const session = require('koa-session-minimal') // 应用处理 session 的中间件 app.use(session({ key: 'session-id', // cookie 中存储 session-id 时的键名, 默认为 koa:sess cookie: { // 与 cookie 相关的配置 domain: 'localhost', // 写 cookie 所在的域名 path: '/', // 写 cookie 所在的路径 maxAge: 1000 * 30, // cookie 有效时长 httpOnly: true, // 是否只用于 http 请求中获取 overwrite: false // 是否允许重写 } }))
  • 读取和设置session
// 设置值 ctx.session.user = { username, password } // 获取值 ctx.session.user.username

配置全局变量

  • 修改app.js
app.use(async (ctx, next) => { ctx.state.url = 'http://www.wmm66.com' ctx.state.userinfo = ctx.session.userinfo, ctx.state.prevPage = ctx.request.headers['referer'] /*上一页的地址*/ ctx.state.obj = { name: '小明', age: 18 } await next() })
  • 在路由等处获取变量
router.get('/', async (ctx, next) => { console.log(ctx.state) await ctx.render('index', { title: '标题' }) })
  • 在模板文件中使用
<div>{{ url }}</div> <div>{{ obj.name }}</div>

多环境配置

实际开发过程中,因为正式、测试环境数据库等的不同,一般会配置多环境。

这里使用cross-env配置,方便兼容不同的系统

  • 下载cross-envnpm i cross-env --save-dev

  • 修改package.json文件

{ // ... "scripts": { "dev": "cross-env NODE_ENV=development env_config=dev nodemon app.js", "start:sit": "cross-env NODE_ENV=production env_config=sit node app.js", "start:prod": "cross-env NODE_ENV=production env_config=prod node app.js", // ... }, // ... }
  • config/目录下新建env.dev.js文件,保存开发环境独有的配置
const mysql = { host: '127.0.0.1', user: 'root', password: 'root', database: 'blog', port: 3306, prefix: 'wmm_' } const mongodb = { dbUrl: 'mongodb://localhost:27017/', dbName: 'koa' } const test = 'dev test' module.exports = { mysql, mongodb, test }
  • config/目录下新建env.sit.js文件,保存测试环境独有的配置
const mysql = { host: '127.0.0.1', user: 'root', password: 'root', database: 'blog', port: 3306, prefix: 'wmm_' } const mongodb = { dbUrl: 'mongodb://localhost:27017/', dbName: 'koa' } const test = 'sit test' module.exports = { mysql, mongodb, test }
  • config/目录下新建env.prod.js文件,保存正式环境独有的配置
const mysql = { host: '127.0.0.1', user: 'root', password: 'root', database: 'blog', port: 3306, prefix: 'wmm_' } const mongodb = { dbUrl: 'mongodb://localhost:27017/', dbName: 'koa' } const test = 'prod test' module.exports = { mysql, mongodb, test }
  • config/目录下新建index.js文件,保存公共的配置
const envConfig = require(`./env.${process.env.env_config}`) const config = Object.assign({}, envConfig, { debug: process.env.NODE_ENV == 'development', secret: 'dddxxxxxx' }) module.exports = config
  • 在其他文件中引入这个config/index.js文件即可得到相关的配置

返回响应时间

  • 修改app.js,在所有中间件前面添加代码
// x-response-time app.use(async (ctx, next) => { const start = Date.now() await next() const ms = Date.now() - start ctx.set('X-Response-Time', `${ms}ms`) })
  • 每次请求,在返回的响应头中会有X-Response-Time,记录了这个请求的响应时间

使用ES6语法

Babel相关知识参考:Babel7知识梳理

  • 安装:npm install @babel/core @babel/cli @babel/preset-env @babel/register @babel/node @babel/plugin-transform-runtime

  • 在根目录下新建.babelrc文件

{ "presets": ["@babel/preset-env"], "plugins": [ ["@babel/plugin-transform-runtime"] ] }
  • 修改app.js文件,在最前面添加require('@babel/register')

  • 重启工程,然后就可以在项目中使用importexportasync await等语法了

其他相关


创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: