路由
ACE 的路由系统分两个层次:底层 ace-router 提供 Router 对象与洋葱中间件集成;上层 ace-framework 宏层通过 @Controller、@Get、@Post 等注解在编译期将声明自动转换为 registerRoute 调用,无需运行时扫描。
路由注册原理
当编译器处理 @Controller/@Get 等宏时,会在当前编译单元的顶层生成一段等价的 let 初始化代码:
cangjie
// 你写的
@Controller["/tasks"]
class TaskController {
@Get["/:id"]
func getTask(@PathParam id: String): String { ... }
}
// 宏展开后(等价于)
let __ace_reg_TaskController_getTask = Registry.register(
method: "GET",
path: "/tasks/:id",
handler: { ctx => TaskController().getTask(ctx) }
)这段初始化代码在程序启动时自动执行完成注册,全程无反射、无枚举类列表。
可审计展开结果
使用 cjc --debug-macro 可查看宏展开后的实际代码,方便排查注册行为。
路径参数语法
ACE 路由基于 path_to_regex 规则(已内置于 ace-router),支持以下语法:
| 语法 | 含义 | 示例 | 匹配 |
|---|---|---|---|
:name | 必选具名参数 | /users/:id | /users/42 |
:name? | 可选具名参数 | /files/:name? | /files 或 /files/a.txt |
:name+ | 一段或多段 | /path/:rest+ | /path/a/b/c |
:name* | 零段或多段 | /path/:rest* | /path 或 /path/a/b |
:name([0-9]+) | 带内联正则 | /items/:id([0-9]+) | /items/123,拒绝 /items/abc |
*wildcard | 匿名通配符 | /static/*file | /static/css/main.css |
正则语法限制
仓颉 std.regex 使用 POSIX 语法,不支持 \d、\w 等转义类。内联正则请写 [0-9] 而非 \d。
代码示例:
cangjie
@Controller["/files"]
class FileController {
// 匹配 /files/123(仅数字)
@Get["/:id([0-9]+)"]
func getById(@PathParam id: String): String { ... }
// 匹配 /files/docs/2024/report.pdf
@Get["/*path"]
func getByPath(@PathParam path: String): String { ... }
}路由分组
使用 @Controller 同前缀分组
同一 @Controller 下的所有方法自动继承前缀:
cangjie
@Controller["/api/tasks"]
class TaskController {
@Get[""] // GET /api/tasks
func list(): String { ... }
@Get["/:id"] // GET /api/tasks/:id
func get(@PathParam id: String): String { ... }
@Post[""] // POST /api/tasks
func create(@Body body: String): String { ... }
@Delete["/:id"] // DELETE /api/tasks/:id
func remove(@PathParam id: String): String { ... }
}使用 Router 对象嵌套分组
需要更灵活的分组(如动态注册、子模块隔离)时,可直接操作 Router:
cangjie
import ace_router.*
import ace_web.*
let apiRouter = Router()
let v1Router = Router()
v1Router.get("/users") { ctx =>
ctx.json("{\"users\":[]}")
}
// 挂载 v1Router 到 /v1,再挂载到 apiRouter 的 /api
apiRouter.addRouter("/v1", v1Router)
let app = App()
app.use(apiRouter.routes())挂载后访问路径为 /api/v1/users。
分组中间件
@Controller 级别的中间件会在所有方法前执行:
cangjie
@Middleware(order: 1)
class AuthMiddleware <: Middleware {
func handle(ctx: Context, next: Next): Unit { ... }
}
@Controller["/admin"]
@UseMiddleware[AuthMiddleware]
class AdminController {
@Get["/dashboard"]
func dashboard(): String { ... } // 自动经过 AuthMiddleware
}路由匹配优先级
当多条路由都能匹配同一请求时,ACE 按以下顺序决定优先级(由高到低):
- 精确匹配:
/users/me优先于/users/:id - 参数路由:
/users/:id优先于通配符 - 通配符:
*path最后匹配
cangjie
@Controller["/users"]
class UserController {
@Get["/me"] // 优先级 1 —— 精确
func me(): String { ... }
@Get["/:id"] // 优先级 2 —— 参数
func get(@PathParam id: String): String { ... }
@Get["/*rest"] // 优先级 3 —— 通配符
func wildcard(@PathParam rest: String): String { ... }
}路由冲突检测
启动时 ACE Runtime 会对已注册路由做冲突检测,以下情况将抛出 RouteConflictException:
- 相同 HTTP Method + 相同路径模式重复注册
- 两条参数路由在同一位置定义了不同参数名但结构相同(如
/:id与/:name)
冲突将导致启动失败
路由冲突在服务启动阶段即报错,不会静默覆盖,请检查是否有多个 @Controller 注册了相同路径。
bash
# 启动时错误示例
[ACE] RouteConflictException: duplicate route GET /tasks/:id
registered at TaskController.get
conflict with TaskV2Controller.getTask