参数路由与路径段解析
简介
从 v1.0.0.420 起,HttpServer 新增了两个重要特性:
- 参数路由 — 在 URL 路径中使用
{param}语法定义动态参数段,类似 Laravel、Express 等现代框架 - PathInfoList — 将 URL 路径按
/分割为可按索引/按键访问的集合对象
两者配合使用,可以轻松构建 RESTful 风格的 API。
一、参数路由
1.1 基本用法
在路由注册时,使用 {参数名} 标记动态段:
vb
' 单参数路由
Call Server.Router.Add("/api/user/{id}", "UserController@Show", OnlyGet)
' 多参数路由
Call Server.Router.Add("/api/user/{userId}/post/{postId}", "PostController@Show", OnlyGet)
' 嵌套资源路由
Call Server.Router.Add("/api/group/{groupId}/user/{userId}", "GroupCtrl@Detail", OnlyGet)1.2 控制器中获取参数
通过 ctx.Request.RouteParams 字典获取匹配到的参数值:
vb
' 单参数示例:GET /api/user/123
Public Sub Show(ctx As cHttpServerContext)
Dim userId As String
userId = ctx.Request.RouteParams("id") ' 值为 "123"
ctx.Response.Json Array("userId" & userId)
End Sub
' 多参数示例:GET /api/user/456/post/789
Public Sub ShowPost(ctx As cHttpServerContext)
Dim userId As String, postId As String
userId = ctx.Request.RouteParams("userId") ' 值为 "456"
postId = ctx.Request.RouteParams("postId") ' 值为 "789"
ctx.Response.Json Array(userId, postId)
End Sub1.3 RouteParams 属性
vb
Public RouteParams As New Scripting.Dictionary| 操作 | 用法 | 说明 |
|---|---|---|
| 获取参数 | ctx.Request.RouteParams("id") | 返回参数值字符串 |
| 检查存在 | ctx.Request.RouteParams.Exists("id") | 返回 Boolean |
| 参数数量 | ctx.Request.RouteParams.Count | 未匹配参数路由时为 0 |
| 遍历参数 | For Each k In RouteParams.Keys | 遍历所有参数名 |
注意:如果没有匹配参数路由,
RouteParams为空字典(Count = 0)。
1.4 匹配规则
| 规则 | 说明 | 示例 |
|---|---|---|
参数段 {name} | 匹配任意非空字符串 | /api/user/{id} 匹配 /api/user/123 |
| 固定段 | 必须精确匹配(忽略大小写) | /api/user/{id} 中 api 和 user 是固定段 |
| 段数一致 | 路径段数必须与模式段数相同 | /api/user/{id} 是3段,不匹配 /api/user/123/edit(4段) |
| 参数非空 | 参数段不能匹配空值 | /api/user/ 不匹配 /api/user/{id} |
匹配示例:
| 路由模式 | 请求路径 | 匹配结果 | RouteParams |
|---|---|---|---|
/api/user/{id} | /api/user/123 | ✅ | {"id": "123"} |
/api/user/{id} | /api/user/abc | ✅ | {"id": "abc"} |
/api/user/{id} | /api/user/ | ❌ 段数不一致 | — |
/api/user/{id} | /api/users/123 | ❌ 固定段不匹配 | — |
/api/user/{userId}/post/{postId} | /api/user/456/post/789 | ✅ | {"userId": "456", "postId": "789"} |
1.5 匹配优先级
路由匹配按以下优先级执行:
- 精确匹配 — O(1) 字典查找,性能最高。如
/api/users直接命中 - ANY 精确匹配 — 在 ANY 方法字典中查找
- 参数路由匹配 — 遍历含
{param}的路由项,逐段模式匹配
精确路由永远优先于参数路由,不会因为参数通配而误匹配。
vb
' 同时注册精确路由和参数路由
Call Server.Router.Add("/api/user/me", "User@Me", OnlyGet) ' 精确匹配优先
Call Server.Router.Add("/api/user/{id}", "User@Show", OnlyGet) ' 参数路由次之
' GET /api/user/me → 命中精确路由,调用 User@Me
' GET /api/user/123 → 命中参数路由,RouteParams("id") = "123"1.6 参数路由 vs 查询参数
| 对比项 | 参数路由 | 查询参数 |
|---|---|---|
| URL 格式 | /api/user/123 | /api/user?id=123 |
| 获取方式 | ctx.Request.RouteParams("id") | ctx.Request.QueryString("id") |
| RESTful 风格 | ✅ 符合 | ❌ 不符合 |
| SEO 友好 | ✅ 更友好 | 一般 |
| 可选参数 | 段数必须一致 | 天然可选 |
| 适用场景 | 资源标识、层级路径 | 筛选、分页、排序 |
两者可以组合使用:
vb
' GET /api/user/123/posts?page=2&limit=10
Call Server.Router.Add("/api/user/{id}/posts", "Post@List", OnlyGet)
Public Sub List(ctx As cHttpServerContext)
Dim userId As String: userId = ctx.Request.RouteParams("id")
Dim page As String: page = ctx.Request.QueryString("page")
Dim limit As String: limit = ctx.Request.QueryString("limit")
End Sub1.7 完整 RESTful API 示例
vb
' 注册控制器
Call Server.Router.Reg("Product", New cProductController)
' 列表:GET /products
Call Server.Router.Add("/products", "Product@List", OnlyGet)
' 详情:GET /products/123
Call Server.Router.Add("/products/{id}", "Product@Detail", OnlyGet)
' 创建:POST /products
Call Server.Router.Add("/products", "Product@Create", OnlyPost)
' 更新:PUT /products/123
Call Server.Router.Add("/products/{id}", "Product@Update", OnlyPut)
' 删除:DELETE /products/123
Call Server.Router.Add("/products/{id}", "Product@Delete", OnlyDelete)
' 子资源:GET /products/123/reviews
Call Server.Router.Add("/products/{id}/reviews", "Product@Reviews", OnlyGet)
' 子资源详情:GET /products/123/reviews/456
Call Server.Router.Add("/products/{productId}/reviews/{reviewId}", "Product@ReviewDetail", OnlyGet)1.8 内部实现
参数路由由 cHttpServerRouteItem 类实现,核心机制:
- 注册时:调用
Init方法,将路由模式拆分为路径段数组,提取{param}参数名 - 匹配时:调用
Match方法,逐段比对——固定段忽略大小写精确匹配,参数段匹配任意非空值 - 参数提取:匹配成功后,参数名和值写入
Request.RouteParams字典
路由模式: /api/user/{id}/post/{postId}
↓ Init() 拆分
段数组: ["api", "user", "{id}", "post", "{postId}"]
参数名: ["id", "postId"]
请求路径: /api/user/123/post/456
↓ Match() 逐段匹配
"api" = "api" ✅ 固定段匹配
"user" = "user" ✅ 固定段匹配
"{id}" = "123" ✅ 参数段 → RouteParams("id") = "123"
"post" = "post" ✅ 固定段匹配
"{postId}" = "456" ✅ 参数段 → RouteParams("postId") = "456"二、PathInfoList 路径段集合
2.1 简介
PathInfoList 是 cCollection 类型的集合对象,在请求解析时自动将 URL 路径按 / 分割,每一段存入集合,键和值都是段名,支持按索引和按键访问。
vb
Public PathInfoList As New cCollection2.2 使用示例
vb
' 请求:GET /api/user/list?page=1
' ── 按索引访问(1-based) ──
MsgBox ctx.Request.PathInfoList(1) ' → "api"
MsgBox ctx.Request.PathInfoList(2) ' → "user"
MsgBox ctx.Request.PathInfoList(3) ' → "list"
' ── 获取路径段数 ──
MsgBox ctx.Request.PathInfoList.Count ' → 3
' ── 按键访问(键=值=段名) ──
MsgBox ctx.Request.PathInfoList("api") ' → "api"
' ── 判断路径中是否包含某段 ──
If ctx.Request.PathInfoList.Exists("api") Then
Debug.Print "路径包含 api 段"
End If
' ── 遍历所有路径段 ──
Dim i As Long
For i = 1 To ctx.Request.PathInfoList.Count
Debug.Print "段" & i & ": " & ctx.Request.PathInfoList(i)
Next i2.3 常见路径的 PathInfoList
| 请求路径 | PathInfoList.Count | PathInfoList(1) | PathInfoList(2) | PathInfoList(3) |
|---|---|---|---|---|
/ | 0 | — | — | — |
/api | 1 | "api" | — | — |
/api/user | 2 | "api" | "user" | — |
/api/user/list | 3 | "api" | "user" | "list" |
/api/user/123 | 3 | "api" | "user" | "123" |
2.4 典型应用场景
vb
' 判断是否是 API 请求
If ctx.Request.PathInfoList.Count > 0 Then
If ctx.Request.PathInfoList(1) = "api" Then
' 处理 API 请求
End If
End If
' 获取路径中的资源 ID(配合参数路由更佳)
If ctx.Request.PathInfoList.Count >= 3 Then
If ctx.Request.PathInfoList(2) = "user" Then
Dim userId As String
userId = ctx.Request.PathInfoList(3) ' 如 "123"
End If
End If
' 建议优先使用参数路由获取 ID:
' userId = ctx.Request.RouteParams("id")提示:对于获取路径中的动态参数(如用户ID),建议优先使用参数路由的
RouteParams,语义更清晰且类型安全。
三、新增文件清单
| 文件 | 类型 | 说明 |
|---|---|---|
cHttpServerRouteItem.cls | 新增 | 路由项类:解析 {param} 模式、匹配路径、提取参数 |
cHttpServerRouter.cls | 修改 | 路由匹配逻辑:精确匹配优先,参数路由次之 |
cHttpServerRequest.cls | 修改 | 新增 RouteParams(Dictionary)和 PathInfoList(cCollection) |
100% 向后兼容——已有的精确路由注册和匹配不受任何影响。
最后更新: 2026-06-11