初始化项目
自建
-
手动新建项目目录
-
切换到该目录下,执行
npm init -y
-
下载
koa
等:npm i koa koa-router koa-bodyparser koa-json koa-logger koa-static --save
使用 koa-generator
-
全局安装
koa-generator
:npm i koa-generator -g
-
测试是否已安装:
koa -V
、koa2 -V
-
生成项目:
koa2 demo
、koa2 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)
- 同时支持
HTTPS
和HTTPS
,或者在多个端口监听同一个应用写法
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
签名时,只有设置 signed
为 true
的时候,才会使用密钥进行加密
ctx.cookies.set('name', 'tobi', { signed: true })
app.context
app.context
是从中创建ctx
的原型。
可以通过编辑app.context
向ctx
添加其他属性。
当需要将ctx
添加到整个应用程序中使用的属性或方法时,这将会非常有用。
这可能会更加有效(不需要中间件)和/或更简单(更少的require()
),而不必担心更多的依赖于ctx
,这可以被看作是一种反向模式。
// 示例:从ctx中添加对数据库的引用
app.context.db = db()
app.use(async ctx => {
console.log(ctx.db)
})
Context
ctx.req
Node
的 request
对象。
ctx.res
Node
的 response
对象。
ctx.request
Koa
的 Request
对象。
ctx.response
Koa
的 Response
对象。
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
:是否要做签名expires
:cookie
有效期。用来设置持久化的cookie
,当设置了它之后,cookie
在指定的时间到达之前都不会过期。domain
:cookie
的域。用来比较请求URL
中服务端的域名。如果域名匹配成功,或这是其子域名,则继续检查path
属性path
:cookie
的路径,默认为'/'
。当域名和路径都匹配时,cookie
才会随请求发送secure
:false
表示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
对象是对 node
的 request
进一步抽象和封装,提供了日常 HTTP
服务器开发中一些有用的功能。
属性或方法 | 说明 |
---|---|
request.header 、request.header= |
请求头对象读取和设置 |
request.headers 、request.headers= |
等价于 request.header |
request.method 、request.method= |
请求方法读取和设置,在实现中间件时非常有用 |
request.length |
以数字的形式返回 request 的内容长度(Content-Length ),或者返回 undefined |
request.url 、request.url= |
请求url 地址读取和重写 |
request.originalUrl |
获取请求原始地址 |
request.origin |
获取URL原始地址, 包含 protocol 和 host 。eg:http://example.com |
request.href |
获取完整的请求URL, 包含 protocol , host 和 url 。eg:http://example.com/foo/bar?q=1 |
request.path 、request.path= |
获取请求路径名;设置请求路径名并保留当前查询字符串 |
request.querystring 、request.querystring= |
获取查询参数字符串(url 中? 后面的部分),不包含? ;设置原始查询字符串 |
request.query 、request.query= |
将查询参数字符串进行解析并以对象的形式返回,如果没有查询参数字字符串则返回一个空对象。根据给定的对象设置查询参数字符串。注意:该方法不支持嵌套解析 |
request.search 、request.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 有效,将会返回一个有序(从 upstream 到 downstream )ip 数组。 否则返回一个空数组。 |
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-Since 和 Last-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 时返回最佳匹配,否则返回 false 。type 的值可以是一个或者多个 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
对象是对 node
的 response
进一步抽象和封装,提供了日常 HTTP
服务器开发中一些有用的功能。
属性或方法 | 说明 |
---|---|
response.header |
Response header 对象。 |
response.headers |
Response header 对象。等价于 response.header |
response.socket |
Request socket |
response.status 、response.status= |
获取响应状态。 默认情况下,response.status 设置为404,而不像node ’的res.statusCode 默认为200;通过数字设置响应状态 |
response.message 、response.message= |
获取响应状态消息。默认情况下, response.message 关联response.status ;将响应状态消息设置为给定值。 |
response.length 、response.length= |
如果 Content-Length 作为数值存在,或者可以通过 ctx.body 来进行计算,则返回相应数值,否则返回 undefined ;将响应Content-Length 设置为给定值。 |
response.body 、response.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.type 、response.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.lastModified 、response.lastModified= |
如果存在 Last-Modified ,则以 Date 的形式返回;以 UTC 格式设置 Last-Modified 。您可以使用 Date 或 date 字符串来进行设置。 |
response.etag= |
设置 包含 "s 的 ETag 。注意没有对应的 res.etag 来获取其值。 |
response.vary(field) |
不同于field |
response.flushHeaders() |
刷新任何设置的响应头,并开始响应体。 |
response.body=
设置响应体为如下值
String
:Content-Type
默认为text/html
或者text/plain
,两种默认charset
均为utf-8
。Content-Length
同时会被设置。Buffer
:Content-Type
默认为application/octet-stream
,Content-Length
同时被设置。Stream
:Content-Type
默认为application/octet-stream
。Object
:Content-Type
默认为application/json
。 这包括普通对象{ foo: 'bar' }
和数组['foo', 'bar']
。null
:no content response
response.type
、response.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
接收,但是接收的方法有两种query
和querystring
-
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-bodyparser
、koa-body
等
koa-bodyparser
koa-bodyparser
默认支持application/json
、application/x-www-urlencoded
类型的数据,但是不支持multipart/form-data
类型的数据
上传图片等可以搭配koa-multer
使用
-
下载
koa-bodyparser
:npm 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-data
、application/x-www-urlencoded
、application/json
-
下载
koa-body
:npm 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-static
:npm 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 ejs
:npm 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-template
:npm 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-session
:npm 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-minimal
:npm 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-env
:npm 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')
-
重启工程,然后就可以在项目中使用
import
、export
、async await
等语法了
发表评论