1.前言

我们想构建一个对外提供的 Api 接口的 Web server 肯定是离不开 路由的,路由可以标记每个 Api 的地址,打个比方,你是地主家的儿子,你们家有几百套房子,那需要一个有个本子(路由)记录每套房子(资源)在哪,不然收租都找不大地方,对应下就是,路由就是这个记录的索引,方便我们找到需要的资源,服务。
所以这章我们来学习下路由的使用,包含以下 2 个方面:

  • 原生路由的使用
  • koa-router 中间件

一般我们写完接口肯定是需要测试下的,假设我们写一个页面来调试每个请求肯定是很麻烦的,一般我们会用 Postman 这个工具来调试接口,大家有时间也可以看看Postman使用

2. 原生路由

比如我们现在要实现用户管理的增删改查接口编写,用 Koa 写会有下面一段代码。

  1. /*
  2. * @Author: hucheng
  3. * @Date: 2020-06-22 06:41:21
  4. * @Description: here is des
  5. */
  6. const Koa = require('koa')
  7. const fs = require('fs')
  8. const app = new Koa()
  9. const storeArray = []
  10. app.use( async ( ctx ) => {
  11. let url = ctx.request.url
  12. let html = await route( url )
  13. ctx.body = html
  14. })
  15. async function route( url ) {
  16. let view = '404.html'
  17. switch ( url ) {
  18. case '/':
  19. view = 'index.html'
  20. break
  21. case '/index':
  22. view = 'index.html'
  23. break
  24. case '/todo':
  25. view = 'todo.html'
  26. break
  27. default:
  28. break
  29. }
  30. let html = await render( view )
  31. return html
  32. }
  33. app.use( async ( ctx ) => {
  34. let url = ctx.request.url
  35. let data = await route( url )
  36. ctx.body = data
  37. })
  38. app.listen(3000, () => {
  39. console.log(' starting at port 3000')
  40. })
  41. function render( page ) {
  42. return new Promise(( resolve, reject ) => {
  43. let viewUrl = `./view/${page}`
  44. fs.readFile(viewUrl, "binary", ( err, data ) => {
  45. if ( err ) {
  46. reject( err )
  47. } else {
  48. resolve( data )
  49. }
  50. })
  51. })
  52. }

这里我就实现了一个简单的路由,然后问题来了,真实的项目里面,路由肯定是非常多的,上面这种处理方式,写到最后估计会疯掉,代码可读性也比较差,如是社区就出来个中间件 koa-router 来定义一套路由的处理规范。
Demo 对应github连接,大家可以拉下来本地跑起来,体会下。

3. koa-router

3.1 简单使用

把上面的例子,我们用 koa-router 改下一遍。

  1. /*
  2. * @Author: hucheng
  3. * @Date: 2020-06-22 06:41:21
  4. * @Description: here is des
  5. */
  6. const Koa = require('koa')
  7. const Router = require('koa-router');
  8. const fs = require('fs')
  9. const app = new Koa()
  10. const router = new Router();
  11. router.get('/', async (ctx, next) => {
  12. ctx.body = await render('index.html')
  13. });
  14. router.get('/index', async (ctx, next) => {
  15. ctx.body = await render('index.html')
  16. });
  17. router.get('/todo', async (ctx, next) => {
  18. ctx.body = await render('todo.html')
  19. });
  20. app
  21. .use(router.routes())
  22. .use(router.allowedMethods());
  23. app.listen(3000, () => {
  24. console.log(' starting at port 3000')
  25. })
  26. function render( page ) {
  27. return new Promise(( resolve, reject ) => {
  28. let viewUrl = `./view/${page}`
  29. fs.readFile(viewUrl, "binary", ( err, data ) => {
  30. if ( err ) {
  31. reject( err )
  32. } else {
  33. resolve( data )
  34. }
  35. })
  36. })
  37. }

大家看到我们这里 我们用 koa-router 重写了一遍,代码逻辑是不是清晰了很多,可读行大大增强。

在实际业务开发过程中,我们都是用 koa-router 来做 请求的url管理,接下来讲讲常用的方法。

3.2常用方法

3.2.1 url 带参数

比如有这样的一个场景,我们需要查询某个用户 Id 为 21 的详情信息,请求的url 可以有下面2种组织方式。

  1. 第一种: http://localhost:3000/user/detail?userId=21
  2. 第二种: http://localhost:3000/user/detail/21

那在实际开发过程中,我们更推荐第二种,这种看起来更加清晰,koa-router 支持这种带参数的方式,设置如下:

  1. router
  2. .get('/user/detail/:id', (ctx, next) => {
  3. const id = ctx.params.id
  4. ctx.body = 'Hello World!';
  5. })

这样就可以简单的通过 url 传递参数来。

3.2.2 http 常见method

那我们在开发过程中经常会有 get,post,put 等请求的 method ,koa-router 都有对应设置。

  1. router.get('/', (ctx, next) => {
  2. ctx.body = 'Hello World!';
  3. })
  4. router.post('/users', (ctx, next) => {
  5. // ...
  6. })
  7. router.put('/users/:id', (ctx, next) => {
  8. // ...
  9. })
  10. router.del('/users/:id', (ctx, next) => {
  11. // ...
  12. })

以上就是 koa-router常见使用,在平常开发中,koa-router 会贯穿整个开发流程,这里我们简单的梳理下,它更多的使用是和 request 上下文结合起来使用。

3.3 路由配合中间件使用

有时候我们写的接口,用户登录后,返回个 Token 给前端页面,后面其他请求 都要校验 Request header 是否带了 这个Token,来确保权限,那这个就需要配和中间件了。

login.js

  1. // login.js
  2. function login(ctx) {
  3. const token = '121212-45dfgffgfg'
  4. ctx.res.header.token = token
  5. ctx.body = {
  6. token: token
  7. }
  8. }
  9. module.exports = {
  10. login
  11. }

auth.js ,是认证中间件,中间件我们一般放到 midware 这个目录下面。

  1. // /midware/auth.js
  2. async function auth(ctx, next) {
  3. const whiteList = ['/', '/index', '/login'] // 这里我们标记白名单,不走认证的
  4. if(!whiteList.includes(ctx.request.path) && !ctx.request.headers['token']) {
  5. ctx.throw(401, '没有token,请登录')
  6. }
  7. await next()
  8. }
  9. module.exports = auth
  1. // router.js
  2. const { login } = require('./controller/login')
  3. const auth = require('./midware/auth')
  4. const {list, detail} = require('./controller/user')
  5. router.use(auth)
  6. router.get('/', async (ctx, next) => {
  7. ctx.body = await render('index.html')
  8. });
  9. router.post('/login', login);
  10. router.get('/index', async (ctx, next) => {
  11. ctx.body = await render('index.html')
  12. });
  13. router.get('/todo', async (ctx, next) => {
  14. ctx.body = await render('todo.html')
  15. });

启动项目访问下 http://localhost:3000/todo
image.png

有的时候我们需要对某个接口做些处理,比如有一个接口有包含文件,那我们肯定不想每个这样的接口都处理下文件,这样单独的接口可以用单独的中间件,例如:

  1. // 单独的路由处理用到中间件
  2. router.use('/users', operateFile());
  3. // 多个接口都要用到某个中间件
  4. router.use(['/users', '/admin'], operateFile());
  5. app.use(router.routes());

3.4 常用规范

一般在实际开发过程中,我们会把 路由相关的,会归总放到 router.js,这样统一管理,代码结构性好, 可读性强。

  1. // 目录结构
  2. |__koa-router-demo
  3. |
  4. app.js
  5. |
  6. router.js
  7. |
  8. constroller // constroller层
  9. |
  10. midware // 这个放中间件

app.js

  1. const Koa = require('koa')
  2. const app = new Koa()
  3. const router = require('./router')
  4. app
  5. .use(router.routes())
  6. .use(router.allowedMethods());
  7. app.listen(3000, () => {
  8. console.log(' starting at port 3000')
  9. })

router.js

  1. const Koa = require('koa')
  2. const Router = require('koa-router');
  3. const app = new Koa()
  4. const router = new Router();
  5. const {
  6. list,
  7. detail
  8. } = require('./controller/user')
  9. router.get('/user/list', list)
  10. router.get('/user/detail/:id', detail)
  11. module.exports = router

cotroller/user.js

  1. const array = [{id: 0, name: '王二'}, {id: 1, name: '赵三'}]
  2. function list(ctx) {
  3. ctx.body = {
  4. data: array,
  5. success: true
  6. };
  7. }
  8. function detail(ctx) {
  9. const id = ctx.params.id;
  10. ctx.body = {
  11. data: array[id],
  12. success: true
  13. }
  14. }
  15. module.exports = {
  16. detail,
  17. list
  18. }

这样组织代码结构就清晰了很多。

4.小结

这节我们简单的梳理了路由的概念,使用起来还是比较简单,相关代码在 https://github.com/hucheng91/koa-workshop/tree/master/koa-router 接下来我们会结合 request,response 来使用路由。