匹配 URI 深层链接

如需将分层 URI 与模式进行匹配并提取实参,请使用 UriDeepLinkMatcher。它依赖于 kotlinx.serialization 将匹配的实参反序列化为您的关键类。

如需创建 UriDeepLinkMatcher,请提供模式 DeepLinkUri 和相应键的序列化程序:

对于非分层 URI 或自定义架构(例如 tel:),请参阅创建自定义深层链接匹配器

支持的匹配模式

UriDeepLinkMatcher 根据 URI 的五个组成部分(方案、授权、路径、查询和片段)匹配 URI。以下部分介绍了每个组件支持的模式语法、实参占位符和匹配规则。

方案匹配

如果 URI 格式中没有架构,则同时匹配 httphttps。 如需匹配特定方案,请将其纳入模式中。不过,如果模式中包含 http 方案,则既匹配 http 请求 URI,也匹配 https 请求 URI;而如果模式中包含 https,则仅匹配 https 请求。

模式 URI 请求 URI 匹配
www.example.com https://www.example.com
www.example.com http://www.example.com
http://www.example.com http://www.example.com
http://www.example.com https://www.example.com
https://www.example.com http://www.example.com
myapp://www.example.com myapp://www.example.com

授权匹配

UriDeepLinkMatcher 对 URI 授权(主机和可选端口)执行不区分大小写的完全匹配。授权中不支持占位符或通配符,并且不会提取任何实参:

模式 URI 请求 URI 匹配
example.com https://example.com
example.com https://EXAMPLE.COM
example.com https://sub.example.com
example.com https://www.example.com
example.com https://example.com:8080
example.com:8080 https://example.com:8080
example.com:8080 https://example.com

路径匹配

支持以下路径模式:

模式 URI 请求 URI 匹配 提取的实参
www.example.com/users https://www.example.com/users
www.example.com/users/{id} https://www.example.com/users/123 id"123"
www.example.com/users/{first}-{last} https://www.example.com/users/john-doe first: "john", last: "doe"
www.example.com/users/{id}/profile https://www.example.com/users//profile id""(空字符串)
www.example.com/users/user_{id} https://www.example.com/users/user_123 id"123"
www.example.com/users/{userId}/posts/{postId} https://www.example.com/users/123/posts/456 userId: "123", postId: "456"
www.example.com/users/.* https://www.example.com/users/john-doe
www.example.com/users https://www.example.com/users/ ❌(尾部斜杠会创建额外的细分) 不适用

查询匹配

请求 URI 中的查询参数顺序无需与模式 URI 中的顺序一致。此外,系统会忽略请求 URI 中存在但模式 URI 中不存在的参数。

支持以下查询参数模式:

模式 URI 请求 URI 提取的实参
www.example.com/users?name={name} https://www.example.com/users?name=john name"john"
www.example.com/users?name={name} https://www.example.com/users?name= name""(空字符串)
www.example.com/users?{rawQuery} https://www.example.com/users?anything&else rawQuery["anything", "else"]
www.example.com/users?type=user_{id} https://www.example.com/users?type=user_123 id"123"
www.example.com/users?name={first}_{last} https://www.example.com/users?name=john_doe first: "john", last: "doe"
www.example.com/users?list={list} https://www.example.com/users?list=10&list=20 list["10", "20"]
www.example.com/users?name={name}&{other} https://www.example.com/users?name=john&tab=info name: "john", other: ["tab=info"]
www.example.com/users?type=user_.* https://www.example.com/users?type=user_admin type"admin"

fragment 匹配

支持以下 fragment 模式类型:

模式 URI 请求 URI 提取的实参
www.example.com/#section1 https://www.example.com/#section1
www.example.com/#section_{id} https://www.example.com/#section_123 id"123"
www.example.com/#section_.* https://www.example.com/#section_123

受支持的数据类型

UriDeepLinkMatcher 支持将 URI 实参反序列化为原始类型、枚举、集合和自定义对象。序列化分为两类:

  • 标准序列化:使用 kotlinx.serialization 反序列化为:
    • 基元(BooleanIntLongFloatDoubleCharByteShort)和 String
    • 枚举
    • SetListArray 的原初类型、字符串或枚举
    • 嵌套的 @Serializable 类(其属性会扁平化为各个 URI 占位符)
  • 使用 DeepLinkSerializer 进行自定义序列化:在单个 String 与自定义对象、外部类型(例如 java.time.LocalDate)或自定义分隔的集合之间进行转换。

标准序列化

UriDeepLinkMatcher 可直接用于标准类型和扁平化结构,无需实现自定义序列化程序。

原始类型和字符串

UriDeepLinkMatcher 会自动解码基元类型(BooleanIntLongFloatDoubleCharByteShort)和 String

枚举

枚举值与枚举元素名称的匹配区分大小写:

重复的查询集合

具有重复键(例如 ?id=10&id=20)的查询参数会自动反序列化为 List<T>Set<T>Array<T>,其中 T 是原始类型、String 或枚举:

嵌套的 @Serializable 类

NavKey 包含类型为另一个 @Serializable 类的属性时,UriDeepLinkMatcher 会将其属性扁平化,以便嵌套类的每个属性直接映射到同名的各个 URI 参数:

如需反序列化自定义对象(例如 Filter(key = "brand", value = "pixel"))、外部类型(例如 java.time.LocalDate)或自定义分隔字符串(例如以英文逗号分隔的值),请扩展 DeepLinkSerializer<T>

DeepLinkSerializer<T> 是一个抽象 KSerializer<T>,用于在 StringT 之间进行转换:

abstract class DeepLinkSerializer<T : Any> : KSerializer<T> {
    abstract val serialName: String
    abstract fun deserialize(value: String): T
    abstract fun serialize(value: T): String
}

例如,请考虑以下代码段中使用的 FilterFilterSerializer 定义:

单个自定义对象

如需从单个 URI 参数字符串(例如 ?filter=brand:google)解码对象,请使用 @Serializable(with = ...) 为相应属性添加注解:

重复查询参数中的自定义对象

如需将重复的查询参数反序列化为自定义对象 (List<T>Set<T>Array<T>) 的集合,请为元素类型 T 实现 DeepLinkSerializer<T>,并使用 @Serializable(with = ...) 注释属性的类型实参:

单个参数中的分隔集合

如需将以逗号分隔或以自定义分隔符分隔的值(例如 ?ids=1,2,3)解析为集合,请为整个集合类型实现 DeepLinkSerializer,并使用 @Serializable(with = ...) 注解相应属性:

实参验证和匹配结果

UriDeepLinkMatcher 可区分不匹配(返回 null,以便尝试其他匹配器)和不支持的配置(抛出异常)。

不匹配

当传入的请求 URI 不满足模式或类型要求时,就会出现不匹配的情况:

  • 缺少必需参数:没有默认值的不可为 null 的键属性,但请求 URI 中缺少相应的 URI 参数。
  • 类型解析失败:提取的实参值无法解析为预期属性类型(例如,Int 属性的 "abc")。

如果出现不匹配的情况,UriDeepLinkMatcher.match 会返回 null,从而允许评估后续匹配器。

假设有一个使用默认值、嵌套对象和枚举配置的关键类和匹配器:

下表展示了各种请求 URI 的匹配结果:

请求 URI 解读结果 比赛结果
https://www.example.com/map/paris?zoom=15&style=dark&layer=SATELLITE 成功(提供所有参数) UriMatchResult(MapKey("paris", 15, LayerOptions("dark", MapLayer.SATELLITE)))
https://www.example.com/map/paris?style=dark 成功zoom 默认为 12layer 默认为 STANDARD UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD)))
https://www.example.com/map/paris?zoom=&style=dark 成功(空的可选查询参数使用默认值 12 UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD)))
https://www.example.com/map?style=dark 不匹配(缺少必需的 location 参数) null
https://www.example.com/map/paris?zoom=close&style=dark 不匹配"close" 不是 Int null
https://www.example.com/map/paris?style=dark&layer=HYBRID 不匹配(枚举中没有 "HYBRID" null

不受支持的配置

如果您的键类包含不受支持的数据类型,UriDeepLinkMatcher 会在匹配期间抛出异常,而不是返回 null

  • 映射和多维集合UriDeepLinkMatcher 仅支持基元、字符串、枚举或带有 DeepLinkSerializer 注释的自定义类型的单维集合。Map 类型会抛出 IllegalArgumentException,而嵌套集合(例如 List<List<String>>)会抛出 SerializationException
  • 未添加注释的自定义对象集合:自定义类型(例如 List<Filter>)的集合会抛出 SerializationException,除非元素类型添加了 DeepLinkSerializer 注释。
  • 未扁平化的嵌套类:嵌套的 @Serializable 类无法在没有 DeepLinkSerializer 的情况下映射到单个占位符(例如 ?user={user})。

UriMatchResult对比

UriMatchResult 实例按以下标准依次进行排名:

  1. MatchResult 类型UriMatchResult 的排名高于其他 MatchResult 类型。
  2. 完全匹配路径:字面路径匹配的排名高于占位符或通配符匹配。
  3. 路径实参数量:具有更多路径实参的匹配项排名更高。
  4. 实参的存在:捕获实参的匹配项排名高于不捕获实参的匹配项。
  5. 实参总数:实参(路径、查询、fragment)总数是最终的决胜因素。

自定义UriDeepLinkMatcher

UriDeepLinkMatcher 是一个 open 类,您可以对其进行子类化,以自定义 URI 匹配和实参提取行为:

  • matchRequest:传入 DeepLinkRequest 的顶级匹配入口点。替换此方法可在 URI 匹配之前检查请求 extra 或应用自定义前提条件。
  • matchUri:将 DeepLinkUri 与配置的模式进行匹配。替换此方法以在调用 super.matchUri 之前拦截并规范化传入的 URI(例如,重写动态子网域或旧版路径格式)。
  • matchArguments:使用提供的 serializer 将提取的路径、查询和 fragment 实参映射反序列化为导航键实例。替换此方法可在键实例化之前注入动态值或转换实参。

以下示例演示了如何对 UriDeepLinkMatcher 进行子类化,以在匹配之前对旧版网址路径前缀进行规范化: