# typescript-boot 路径规则

本文档描述当前版本 `typescript-boot` 中 `@apiPath()` 的完整规则。

## 1. 完整路径组成

一个接口方法的完整访问路径为：

```text
http://<hostname>:<port>/<service-path>/<method-path>
```

其中：

- `service-path` 定义在 `接口Service` 上
- `method-path` 定义在 `接口方法` 上

示例：

```ts
@apiPath('/account')
class AccountService extends BaseService {
  @apiPath('login')
  async login() {}
}
```

完整路径为：

```text
/account/login
```

## 2. service-path 规则

`service-path` 只能是单段路径。

允许写法：

```ts
@apiPath('/account')
@apiPath('account')
@apiPath('/oauth2')
@apiPath('user-center')
```

最终都会被规范化为带前导 `/` 的单段路径。

例如：

- `account` => `/account`
- `/account/` => `/account`

不允许写法：

```ts
@apiPath('/account/oauth')
@apiPath('account/oauth')
```

这类写法会在服务启动注册阶段直接报错。

## 3. method-path 规则

`method-path` 是相对于 `service-path` 的子路径。

允许：

- 单段路径
- 多段路径
- 变量路径
- 空路径

### 3.1 单段路径

```ts
@apiPath('login')
```

最终路径：

```text
/account/login
```

### 3.2 多段路径

```ts
@apiPath('oauth/github/authorizeUrl')
```

最终路径：

```text
/account/oauth/github/authorizeUrl
```

### 3.3 变量路径

```ts
@apiPath('oauth/:provider/authorizeUrl')
```

最终路径：

```text
/account/oauth/:provider/authorizeUrl
```

调用时可通过 `req.params` 获取变量值：

```ts
async getAuthorizeUrl(@apiRequest() req) {
  const provider = req.params.provider;
  return this.success({provider});
}
```

当前版本不提供 `@apiParamFromPath()`，路径变量统一通过 `@apiRequest()` 获取 `req.params.xxx`。

### 3.4 空路径

```ts
@apiPath('')
```

或：

```ts
@apiPath('/')
```

最终都表示该方法直接挂在 `service-path` 根上。

示例：

```ts
@apiPath('/account')
class AccountService extends BaseService {
  @apiPath('')
  async index() {}
}
```

最终路径：

```text
/account
```

## 4. method-path 默认规则

如果接口方法没有设置 `@apiPath()`，则默认使用方法名作为 `method-path`。

示例：

```ts
@apiPath('/account')
class AccountService extends BaseService {
  async login() {}
}
```

最终路径：

```text
/account/login
```

## 5. 路径规范化规则

框架会对路径做基础规范化：

- `service-path` 自动补前导 `/`
- `service-path` 自动去掉尾部 `/`
- `method-path` 自动去掉前导 `/`
- `method-path` 自动去掉尾部 `/`
- 连续斜杠会被压缩

示例：

- `@apiPath('/account/')` => `/account`
- `@apiPath('/oauth//github/authorizeUrl/')` => `oauth/github/authorizeUrl`

## 6. 请求方法规则

每个接口方法的请求方式来自：

1. 方法上的 `@apiRequestType()`
2. 如果方法未设置，则继承 service 上的 `@apiRequestType()`
3. 如果都未设置，则同时注册 `GET` 和 `POST`

无论该接口最终是 `GET` 还是 `POST`，框架都会为其注册对应路径的 `OPTIONS`，因此多段 `method-path` 的预检请求也能正常命中。

## 7. 路由匹配优先级

当同一个 service 下同时存在静态路径和变量路径时，优先匹配更具体的静态路径。

例如：

```ts
@apiPath('/account')
class AccountService extends BaseService {
  @apiPath('oauth/github/authorizeUrl')
  async githubAuthorizeUrl() {}

  @apiPath('oauth/:provider/authorizeUrl')
  async providerAuthorizeUrl(@apiRequest() req) {}
}
```

访问：

```text
/account/oauth/github/authorizeUrl
```

会优先命中：

```text
oauth/github/authorizeUrl
```

而不是变量路径。

## 8. 冲突规则

如果同一个 `接口Service` 下出现完全相同的路由定义，框架会在启动注册阶段报错。

冲突的判断维度为：

- 同一个 `service-path`
- 同一个 HTTP 方法
- 同一个规范化后的 `method-path`

例如下面会冲突：

```ts
@apiPath('/account')
class AccountService extends BaseService {
  @apiPath('login')
  async login1() {}

  @apiPath('/login/')
  async login2() {}
}
```

因为它们规范化后都是同一个 `POST /account/login` 或 `GET /account/login`。

## 9. 当前不支持的规则

当前版本不支持：

- 在 `service-path` 中使用多重路径
- 用参数装饰器直接声明路径变量

路径变量读取方式固定为：

```ts
@apiRequest() req
req.params.xxx
```

## 10. 推荐写法

推荐始终按下面的方式书写：

```ts
@apiPath('/account')
class AccountService extends BaseService {
  @apiPath('login')
  async login() {}

  @apiPath('oauth/github/authorizeUrl')
  async githubAuthorizeUrl() {}

  @apiPath('oauth/:provider/authorizeUrl')
  async providerAuthorizeUrl(@apiRequest() req) {}
}
```

这样可以保持：

- service 层只负责命名空间
- method 层负责具体资源路径
- 多级路径和变量路径都集中在 method 上表达
