概述
在 silky 框架的运行时,路由系统负责将外部 HTTP 请求或内部 RPC 调用映射到具体的服务条目(ServiceEntry)并执行。整个路由过程分为两个阶段:
- 启动时:根据服务应用接口(AppService)的方法定义,静态生成路由表
- 运行时:接收请求后,在路由表中查找匹配的服务条目并转发执行
路由模板的生成
ServiceRouteAttribute 与路由模板
每个服务应用接口都必须标注 [ServiceRoute] 特性,该特性指定了该接口下所有服务条目的路由前缀模板。
[AttributeUsage(AttributeTargets.Interface)]
public class ServiceRouteAttribute : Attribute, IRouteTemplateProvider
{
// 默认模板:api/{appservice}
public ServiceRouteAttribute() : this("api/{appservice}") { }
public ServiceRouteAttribute(string template)
{
Template = template;
}
public string Template { get; }
}
默认路由模板为 api/{appservice},其中 {appservice} 是一个特殊占位符,框架会自动将其替换为应用服务接口的名称(去除 I 前缀和 AppService 后缀并转换为 kebab-case)。
例如 IOrderAppService → {appservice} = order,生成的路由前缀为 api/order。
可通过 {appservice=customName} 语法自定义名称:
[ServiceRoute("{appservice=orders}")]
public interface IOrderAppService { }
// 生成路由前缀:api/orders
也可以完全自定义路由前缀:
[ServiceRoute("v2/orders")]
public interface IOrderAppServiceV2 { }
// 生成路由前缀:v2/orders
路由段(Segment)类型
框架在解析路由模板时,将模板拆分为若干段(Segment),每个段属于以下三种类型之一:
| 类型 | 枚举值 | 说明 | 示例 |
|---|---|---|---|
| 字面量 | SegmentType.Literal | 固定字符串,直接作为路由段 | api、v2、orders |
| 应用服务名 | SegmentType.AppService | {appservice} 或 {appservice=xxx} 占位符,框架替换为实际服务名 | {appservice} → order |
| 路径参数 | SegmentType.Path | {paramName} 形式,匹配 URL 中的动态值 | {id}、{id:long} |
路径参数段支持类型约束(通过 : 分隔),例如 {id:long} 表示该路径参数必须能转换为 long 类型。
服务条目路由的完整生成规则
每个服务条目(接口方法)的完整路由 = 接口路由模板 + 方法路由段 + 路径参数段。
框架通过 TemplateHelper.GenerateServerEntryTemplate 完成最终模板的拼接,规则如下:
1. 方法显式指定了 HTTP 动词特性([HttpGet]、[HttpPost] 等)
若特性中同时指定了路由模板(如 [HttpGet("detail/{id}")]),则直接使用该模板拼接:
完整路由 = 接口前缀 / 特性指定的模板
若特性中未指定路由模板(如仅 [HttpGet]),则:
启用 RESTful 风格(
Governance:ApiIsRESTfulStyle = true,默认)时:- 自动去除方法名的
Async后缀 - 自动剥离 HTTP 动词约定前缀(见下表)
- 自动追加路径参数段
- 自动去除方法名的
未启用 RESTful 风格时:
- 使用完整方法名(去
Async后缀)
- 使用完整方法名(去
HTTP 动词与方法名前缀的约定对应关系:
| HTTP 方法 | 自动剥离的方法名前缀 |
|---|---|
GET | GetBy、GetFor、GetFrom、Get |
POST | CreateOrUpdate、CreateOrModify、Create、Add |
PUT | CreateOrUpdate、CreateOrModify、Update、Put、Modify |
PATCH | CreateOrUpdate、CreateOrModify、Update、Put、Modify |
DELETE | Delete |
示例:
[ServiceRoute] // 模板:api/{appservice} → api/order
public interface IOrderAppService
{
[HttpGet]
Task<OrderDto> GetByIdAsync(long id);
// 生成路由:GET api/order/{id}
// 去 Async 后缀 → "GetById",剥离 "Get" 前缀 → "ById" → 简化为 "{id}"(路径参数)
// 实际:GET api/order/{id}
[HttpPost]
Task<OrderDto> CreateAsync(CreateOrderInput input);
// 生成路由:POST api/order
// 去 Async → "Create",剥离 "Create" 前缀 → "" → 无方法段
// POST api/order
[HttpPut("{id}")]
Task<OrderDto> UpdateAsync(long id, UpdateOrderInput input);
// 生成路由:PUT api/order/{id}(直接使用特性指定的模板)
[HttpDelete]
Task DeleteAsync(long id);
// 生成路由:DELETE api/order/{id}
}
2. 方法未指定 HTTP 动词特性(约定推断)
框架根据方法名前缀自动推断 HTTP 动词并生成路由,推断逻辑同上表。若方法名不匹配任何约定前缀,则默认映射为 POST。
运行时路由匹配
网关的路由匹配流程
外部 HTTP 请求进入 silky 网关后,路由匹配过程如下:
HTTP 请求
│
▼
ASP.NET Core 路由中间件(UseRouting)
│ 匹配已注册的 Endpoint
▼
SilkyRpcServiceEndpointDataSource(MapSilkyRpcServices 注册)
│ 根据 Method + Path 定位 ServiceEntry
▼
服务条目查找(IServiceEntryLocator)
│ 按 ServiceEntryId 或 WebAPI 路径定位
▼
本地执行 or RPC 远程调用
│
▼
结果封装(ResponseWrapper)→ 返回 HTTP 响应
Endpoint 注册方式对比
silky 在 IEndpointRouteBuilder 上提供了多个扩展方法,用于注册不同场景的终结点:
| 方法 | 用途 |
|---|---|
MapSilkyServiceEntries() | 注册 Web 主机自身提供的服务条目(用于有 HTTP 入口的业务微服务) |
MapSilkyRpcServices() | 注册聚合所有 RPC 服务的路由(网关使用,转发至后端微服务) |
MapSilkyTemplateServices() | 使用服务条目描述符注册(仅包含服务元信息,不执行实际调用) |
MapSilkyDashboardServices() | 注册 Dashboard 管理端的路由 |
典型的网关 Startup.Configure:
app.UseEndpoints(endpoints =>
{
endpoints.MapSilkyRpcServices(); // 聚合所有微服务的 RPC 路由
endpoints.MapSilkyDashboardServices(); // Dashboard 管理端
});
典型的 Web 主机(自身提供 HTTP 服务的业务微服务)Startup.Configure:
app.UseEndpoints(endpoints =>
{
endpoints.MapSilkyServiceEntries(); // 注册本服务的服务条目路由
});
路径参数的解析
路径参数在运行时通过 RoutePathHelper.ParserRouteParameters 解析:
// 路由模板:api/order/{id}
// 实际路径:api/order/42
// 解析结果:{ "id": "42" }
解析逻辑为按段对齐:逐段比对路由模板与实际路径,对 {...} 格式的模板段提取对应位置的路径值,拼装为参数字典后传入服务条目的执行器。
带约束的路径参数(如 {id:long}),约束名在参数名提取时被剥离(取 : 前的部分),约束校验发生在参数绑定阶段。
服务条目 Id 与路由的对应关系
服务条目 Id 是路由系统的另一个关键标识,其生成规则为:
服务条目 Id = 方法完全限定名 + "." + 参数名列表(下划线拼接)+ "_" + HTTP 动词
示例:
接口:Test.ITestAppService
方法:Task<string> Echo(string ping)
HTTP:GET
→ 服务条目 Id:Test.ITestAppService.Echo.ping_Get
该 Id 可以在以下场景中使用:
- 通过
IInvokeTemplate的 API 按 Id 调用远程服务,无需引用接口程序集 - 在 Dashboard 管理端查询和管理服务条目
- 链路追踪(SkyAPM)中标识具体的服务调用节点
