路由(Router)

路由主要用于确认如何响应客户端的请求,当用户访问一个地址时,需要找到指定的 controller 的指定方法对用户请求进行处理。确认一个请求主要对请求的两部分:路径和特定 HTTP 请求方法,进行判断。

使用

为了方便用户对路由的选择,Router 被作为参数的方式传入到 Uma 中。

import { Router } from '@umajs/router'

const uma = Uma.instance({
  Router,
  ROOT: __dirname,
})

uma.start(8058)
1
2
3
4
5
6
7
8

@Path 修饰器

如果你不想通过默认路由的方式访问时,Uma提供了@Path修饰器来指定 controller 方法被访问的 URI 格式。

可以装饰 class,作为跟路由,只装饰 class 不生效,必须和 method 装饰配合使用。装饰 class 只有能有一个 string 参数 可以装饰 method,没有根路由的时候直接作为路由使用,有根路由的时候和根路由组合使用

export declare function Path(...args: [...string[]] | [TPathObjArgs]): Function;

args 路由参数 eg:

Path() // 仅在 class 有路由装饰时 装饰 method 使用

Path('/p1')

Path('/p1', 'p2')

Path({ value: '/p1' })

Path({ value: '/p1', method: RequestMethod.GET })

Path({ value: ['/p1', '/p2'], method: RequestMethod.GET })

Path({ value: ['/p1', '/p2'], method: [RequestMethod.GET, RequestMethod.POST] })

修饰 class

@Path 修饰器除了能修饰方法外,还可以修饰 controller 的 class,被修饰后的 class,如果内层的方法同时也添加了 @Path 修饰器,那么该方法的访问路径将和 class 上的 @Path 指定路径合并使用。

例如我们对上面创建的 test.controller.ts 文件做以下修改:

import { BaseController, Path } from '@umajs/core'

@Path('/page') // 根路由
export default class Test extends BaseController {
  @Path() // 路由: /page
  @Path('/home') // 路由: /page/home
  index() {
    return this.send('this is page/index router')
  }
  test() {
    return this.send('this is test/index router')
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

在浏览器地址栏通过访问127.0.0.1:端口号/page/home127.0.0.1:端口号/page/就可以看到页面显示出this is page/index router

注意:v2.0 版本之后,框架取消了文件默认路由,即我们不能通过127.0.0.1:端口号/page/test访问到目标函数,接口将会返回 Not Found。请确保所有路由都被@Path装饰。

同时,访问127.0.0.1:端口号/page也可以看到页面显示出this is test/index router

修饰方法

例如我们对上面创建的 test.controller.ts 文件做以下修改:

export default class Test extends BaseController {
  @Path('/home')
  index() {
    return this.send('this is test/index router')
  }
}
1
2
3
4
5
6

在浏览器地址栏通过访问127.0.0.1:端口号/home就可以看到页面显示出this is test/index router

注意:此时,在浏览器地址栏访问127.0.0.1:端口号/test/index就不能访问到 index 方法了,因为在Uma中,被@Path 修饰器修饰过的方法,不能再通过默认路由的方式访问。

MethodType

在很多访问中,我们需要加入 MethodType 的限制,示例如下:

import { BaseController, Path, RequestMethod } from '@umajs/core'

export default class Test extends BaseController {
  @Path({ value: '/home', method: RequestMethod.POST })
  index() {
    return this.send('this is test/index router')
  }
}
1
2
3
4
5
6
7
8

在浏览器地址栏通过访问127.0.0.1:端口号/home会展示 NOT FOUND,这是因为限制了只能采用 POST 访问。

注意:框架内置了 POST 请求处理 koa-body,如果需要开启,实例化 Uma 时需要传入 bodyParse 参数,并且安装 koa-body 依赖

指定多个路径

同一个方法上允许设置多个 Path 路径。

例如我们对上面创建的 test.controller.ts 文件做以下修改:

import { BaseController, Path } from '@umajs/core'

@Path('/page')
export default class Test extends BaseController {
  @Path('/home', '/abc')
  index() {
    return this.send('this is test/index router')
  }
}
1
2
3
4
5
6
7
8
9

在浏览器地址栏通过访问127.0.0.1:端口号/page/home或者127.0.0.1:端口号/page/abc都可以看到页面显示出this is test/index router

也可以通过多个@Path 修饰器修饰的方式指定多个路径。

例如我们对上面创建的 test.controller.ts 文件做以下修改:

import { BaseController, Path } from '@umajs/core'

@Path('/page')
export default class Test extends BaseController {
  @Path('/home', '/abc')
  @Path('/test')
  index() {
    return this.send('this is test/index router')
  }
}
1
2
3
4
5
6
7
8
9
10

在浏览器地址栏通过访问127.0.0.1:端口号/page/home或者127.0.0.1:端口号/page/abc或者127.0.0.1:端口号/page/test都可以看到页面显示出this is test/index router

和方法不同,一个 class 只允许使用一个@Path 修饰器,同时一个@Path 修饰器只接收一个参数。

例如我们对上面创建的 test.controller.ts 文件做以下修改:

import { BaseController, Path } from '@umajs/core'

@Path('/page')
@Path('/tpl')
export default class Test extends BaseController {
  @Path('/home', '/abc')
  @Path('/test')
  index() {
    return this.send('this is test/index router')
  }
}
1
2
3
4
5
6
7
8
9
10
11

此时,只有最上面的@Path('/page')生效了,因为修饰器会先执行最接近自己的,@Path('/tpl')先被作用,但是@Path('/page')又将它覆盖了。

同样,如果你在 class 上设置@Path('/page', '/tpl')这种传入多个参数的格式,程序会报错,因为修饰 class 时,@Path 只接收一个参数。

正则匹配

我们对上面这种直接使用具体字符串形式设置的路由称为静态路由,路径除了使用/home这种字符串的格式外,还支持使用正则形式,我们称之为正则路由

例如我们对上面创建的 test.controller.ts 文件做以下修改:

import { BaseController, Path } from '@umajs/core'

@Path('/page')
export default class Test extends BaseController {
  @Path('/:name')
  index() {
    return this.send('this is test/index router')
  }
}
1
2
3
4
5
6
7
8
9

在浏览器地址栏通过访问127.0.0.1:端口号/page/任意字符都可以看到页面显示出this is test/index router。同时,我们可以通过@Param修饰器或者ctx.params的方式获取到当前name的值,@Param 修饰器会在后面介绍。

正则路由通过path-to-regexp在新窗口中打开库进行匹配,具体格式格式参考 path-to-regexp 库。

匹配顺序

Uma会按照的指定顺序去匹配路由,当命中某一规则时,即进入该 controller 的方法中,了解匹配的顺序当我们设置了重复的路由时很有帮助。

Uma匹配路由的顺序如下:

images

请求进来时,会先从静态路由中查找是否有匹配到的,没有的话会从正则路由中匹配是否有满足的,还未找到的话会按照静态路由的格式查找是否有匹配的,当这几种情况都不满足时,返回 Not Found。

Path 扩展

框架在 @Path 装饰器的基础上还提供了一些其它快捷的路由装饰器 @umajs/path

路由参数处理

在上面的正则路由中我们提到过@Param 修饰器,UMajs中默认只提供了两种修饰器@Param 和@Query 来方便的获取请求中的参数。对于更丰富的参数处理场景,框架提供了一个专门处理参数的装饰器工具包,@umajs/arg-decorator;方便开发者对参数进行接收,校验判断和类型转换。

@Param,@Query 参数处理

例如我们对上面创建的 test.controller.ts 文件做以下修改:

import { BaseController, Path } from '@umajs/core'
import { Param, Query } from '@umajs/arg-decorator'
@Path('/page')
export default class Test extends BaseController {
  @Path('/:name')
  index(@Param('name') name: string, @Query('name') title: string) {
    console.log(`name: ${name}`)
    console.log(`title: ${title}`)
    return this.send('this is test/index router')
  }
}
1
2
3
4
5
6
7
8
9
10
11

在浏览器中访问127.0.0.1:端口号/page/test?name=uma,我们可以看到控制台打印出:

name: test
title: uma
1
2

@Param 和@Query 修饰器可以快捷的获取到请求中的 param 和 query 参数,@Param 传入的名称要和@Path 中设置的名称一致,@Query 传入的名称要和请求的 query 参数名称一致,才能正确获取到。

同时,使用@Param 和@Query 修饰器不区分参数顺序

- index(@Param('name') name: string, @Query('name') title: string)
// 参数顺序变化也可以正确获取
+ index(@Query('name') title: string, @Param('name') name: string)
1
2
3

当然,除了通过@Param 和@Query 修饰器的方式获取参数外,Uma还保留了 koa 的参数获取方式,可以从上下文中获取:

import { BaseController, Path } from '@umajs/core'
import { Param, Query } from '@umajs/arg-decorator'

@Path('/page')
export default class Test extends BaseController {
  @Path('/:name')
  index(@Param('name') name: string, @Query('name') title: string) {
    // ====> 从ctx中获取参数
    console.log(this.ctx.param.name)
    console.log(this.ctx.query.title)
    return this.send('this is test/index router')
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

对于POST类型路由请求,参数获取和处理请查看@Body