Koa教程之一:路由、Static、Cookie、连接数据库

本教程代码仓库请访问:
https://github.com/chencl1986/koa2-tutorial

Koa与Express的区别

Express是基于回调函数开发。
Koa是基于Promise思想开发。
Koa1基于Generator,Koa2同时支持Generator和Async/await,但使用Generator会收到警告,因为Koa3是完全基于Async/await。
使用Koa创建一个服务器

const Koa = require('koa')

const server = new Koa()  // 使用new创建一个Server

server.listen(8080) // 监听8080端口
1
2
3
4
5
引入Koa路由

请查看server01.js
与Express不同,Koa并不自带路由,需要引入koa-router模块才可以使用路由。

/**
 * 代码请查看:/server01.js
 */

const router = new Router()

// router常用方法有get(匹配get请求)、post(匹配post请求)、all(匹配所有请求)

// ctx为上下文对象,其中包括原生nodejs包含的request/response对象,分别为ctx.req/ctx.res。
// next函数被调用时,会暂停该中间件的运行,并将控制传递给下一个中间件。
// 判断以get方法请求的路由
router.get('/a', async (ctx, next) => {
  console.log(ctx)
  ctx.body = 'aaa' // 通过body属性向前台传输数据
  ctx.body += 'bbb' // body属性的值可以添加或替换,与普通对象的属性相同。
})

server.use(router.routes()) // 使用use方法,将router中间件添加到Server中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
当然在项目中,我们往往会有多级路由,这时候就需要创建嵌套路由。

嵌套路由

请查看server01.js
通过路由嵌套,实现多级路由访问。

/**
 * 代码请查看:/server01.js
 */
 
// 嵌套路由
const router = new Router() // 创建一个主路由
const userRouter = new Router() // 创建一个路由
const companyRouter = new Router()

companyRouter.get('/a', async (ctx, next) => { // 匹配路由后将输出相应内容
  ctx.body = '企业的a'
})

userRouter.use('/company', companyRouter.routes()) // 将company路由添加到父级,表示当访问/company的子路由时,匹配company下的路由。

userRouter.get('/', async (ctx, next) => {
  ctx.body = 'user'
})

userRouter.get('/company', async (ctx, next) => {
  ctx.body = '企业'
})

router.use('/user', userRouter.routes()) // 将userRouter添加到主路由router中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
此时当浏览器访问不同路由时,显示相应内容:

访问路径    显示内容
/user    user
/user/company    企业
/user/company/a    企业的a
Koa之所以选择这样的嵌套方式,更多的原因是为了支持大型项目。在大型项目中,若采用配置JSON的方式配置路由,会因为代码量过大造成阅读困难。
当然,若采用当前的配置方式,将所有路由都配置在一个文件中,对代码阅读毫无帮助,因此我们需要将每个层级的路由都配置到单独文件中,通过互相引用模块的方式,实现路由配置。

路由嵌套时,最终访问的路径是将各层级的路径,按字符串拼接起来的,因此需要注意不要出现拼接错误,例如出现’//user’。

’’ 空字符串代表根路由
’’ 字符串代表匹配所有路由

模块化嵌套路由

请查看server02.js
先在入口js文件中,将根节点的路由添加到server中。

server.use(require('./routers')) // 使用根路由配置
1
在根路由的配置中,引用下级路由的配置,以此类推。

const Router = require('koa-router')

const router = new Router()

// 此时require('./user')实际为router.routes(),为一个中间件,需要使用router.use
router.use('/user', require('./user'))

router.get('/user', async (ctx, next) => {
  ctx.body = 'user'
})

module.exports = router.routes()
1
2
3
4
5
6
7
8
9
10
11
12
这样,我们就完成了整个项目的路由配置。

路由参数

请查看server03.js
在Koa中,支持通过路由传参。

router.get('/news/:id', async (ctx, next) => {
  console.log(ctx.params) // 通过params属性获取参数
  ctx.body = `新闻ID为:${ctx.params.id}`
})
1
2
3
4
同时也可以进行多级传参,此时需要注意的是,只有当传入参数数量与路由数量相同时,才可以正确匹配,同时参数的顺序是固定的。

router.get('/news/:id1/:id2/:id3', async (ctx, next) => {
  console.log(ctx.params)
  ctx.body = `多级新闻ID为:${ctx.params.id1}/${ctx.params.id2}/${ctx.params.id3}`
})
1
2
3
4
假设同时匹配了两层同样的路由,则以先匹配的路由为准,后一级路由不会执行。

router.get('/news/:id')    // 只会执行该路由匹配。
router.get('/news/1')
1
2
如果希望匹配完第一级路由之后,还可以匹配第二级,则可以通过执行next(),将方法传入下一级,但因为此时next()返回值为Promise,则需要使用await。

// 若不执行next,则只会匹配该路由
router.get('/news/1', async (ctx, next) => {
  console.log(ctx.params) // 通过params属性获取参数
  ctx.body = `新闻ID为:固定的1`
  await next()  // 可通过next函数,执行下一级的匹配,同时因为此时next()返回值为Promise,则需要使用await。
})

router.get('/news/:id', async (ctx, next) => {
  console.log(ctx.params) // 通过params属性获取参数
  ctx.body = `新闻ID为:${ctx.params.id}`
  await next()
})

router.get('/news/:key', async (ctx, next) => {
  console.log(ctx.params) // 通过params属性获取参数
  ctx.body = `新闻ID为:${ctx.params.key}`
  await next()
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Urlencoded传参与路由传参的区别

请查看server03.js
Urlencoded:http://localhost:8080/news/1/2/3/user/1/2/3
路由传参:http://localhost:8080/news?id1=1&id2=2&id3=3

Urlencoded传参    路由传参
通过ctx.query获取参数    通过ctx.params获取参数
顺序灵活    顺序固定
可省略    不可省略
动态地址,搜索引擎会认为只是一个地址,不利于SEO    静态地址,搜索引擎会判断为多个地址,利于SEO
参数之后不可挂载路由    参数之后可以挂载下级路由
context上下文

请查看server04.js
context是ctx的原型prototype,通常用来向整个项目添加全局属性或方法。
最经常的用法是添加全局数据库的连接池。

server.context.db = db();

server.use(async ctx => {
  console.log(ctx.db);
});
1
2
3
4
5
添加一个全局属性并打印。

server.context.a = 123

router.get('/', async (ctx, next) => {
  ctx.body = `abc${server.context.a}`
})
1
2
3
4
5
ctx.throw与ctx.assert

请查看server05.js
ctx.throw([status], [msg], [properties])
ctx.throw方法用于向前端抛出一个错误。

router.get('/login', async (ctx, next) => {
  if (!ctx.query.user || !ctx.query.pass) {
    ctx.throw(400, '用户名或密码不存在')
  } else {
    ctx.body = '成功'
  }
})
1
2
3
4
5
6
7
ctx.assert(value, [status], [msg], [properties])
ctx.assert简单来说就是对throw的封装,第一个参数是触发条件。
等价于如下代码:

if (!value) {
  ctx.throw(code, msg)
}
1
2
3
因此前一段throw的代码可以简写为:

router.get('/login', async (ctx, next) => {
  ctx.assert(ctx.query.user, 400, '用户名不存在')
  ctx.assert(ctx.query.pass, 400, '密码不存在')
  ctx.body = '成功'
})
1
2
3
4
5
ctx.redirect

请查看server06.js
ctx.redirect用于重定向,可重定向到站内或站外,同时会向前端传一个302状态码。

router.get('/google', async (ctx, next) => {
  ctx.redirect('https://www.google.com/')
})

router.get('/login', async (ctx, next) => {
  ctx.redirect('/user')
})
1
2
3
4
5
6
7
使用koa-static处理静态文件

请查看server07.js
const Static = require('koa-static')

// 通过路由使用静态文件时,表示路由直接接受访问,所以需要用all方法。
server.use(Static('./static', { // 使用./static文件夹中的静态文件
  maxage: 86400 * 1000, // 告知浏览器缓存时间
  index: '1.html' // 当黄文根路由时,默认渲染的文件,前提是路由未匹配根路由
}))
1
2
3
4
5
6
7
使用路由处理静态文件

请查看server08.js
在项目开发中批量处理静态文件,可以使用路由匹配不同的文件类型,通过koa-static处理并添加不同缓存时间等参数。

const staticRouter = new Router()

staticRouter.all(/(\.jpg|\.png|\.gif)/, Static('./static', {
  maxage: 60 * 86400 * 1000
}))

staticRouter.all(/(\.css)/, Static('./static', {
  maxage: 1 * 86400 * 1000
}))

staticRouter.all(/(\.html|\.htm|\.shtml)/, Static('./static', {
  maxage: 20 * 86400 * 1000
}))

staticRouter.all(/(\.js|\.jsx)/, Static('./static', {
  maxage: 1 * 86400 * 1000
}))

// '' 空字符串代表根路由
// '*' 字符串*代表匹配所有路由
staticRouter.all('*', Static('./static', {
  maxage: 30 * 86400 * 1000
}))

server.use(staticRouter.routes())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
koa-better-body中间件

请查看server09.js
koa-better-body中间件是用来解析post请求,包括数据和文件。
https://github.com/tunnckoCoreLabs/koa-better-body
新建一个post.html,用来上传表单:

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
      名字:<input type="text" name="user" /><br>
      头像:<input type="file" name="f1" /><br>
      <input type="submit" value="提交">
    </form>
  </body>
</html>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值