仓颉web路由校验工具技术实现详解与完整示例
·
一、工具用途简介
仓颉web路由校验工具 是一个用于路由路径模板解析与正则表达式生成的仓颉语言工具。它的主要用途是:
- 路由匹配:将如
/user/:id/profile这样的路径模板,转换为可用于 HTTP 路由匹配的正则表达式,并自动提取路径参数。 - 路径生成:根据参数数据,自动生成符合模板的路径字符串,适用于前端跳转、后端 API 构建等场景。
- 参数安全处理:支持参数的百分号编码/解码,保证路径安全性和兼容性。
该工具广泛应用于 Web 框架、API 网关、微服务路由等需要灵活路径匹配和生成的场景。
二、技术实现思路详解
1. Token 结构设计与分类
路径模板的解析基础是 Token 化。工具通过 TokenKind 枚举定义了四类 Token:
- TEXT(文本):普通字符串片段,如
/user/。 - PARAM(参数):以冒号
:开头的参数片段,如:id,支持修饰符(?、*、+)和自定义正则。 - WILDCARD(通配符):以星号
*开头,匹配任意片段,如*rest。 - GROUP(分组):用括号或花括号包裹的分组片段,支持嵌套和条件渲染。
每种 Token 都实现了 ToString 接口,便于调试和字符串化。
public enum TokenKind {
TEXT |
PARAM |
WILDCARD |
GROUP
}
public interface Token <: ToString {
func kind(): TokenKind
}
public class Text <: Token {
public let value: String
public init(value: String) { this.value = value }
public func kind(): TokenKind { TokenKind.TEXT }
public func toString(): String { "Text(value: ${this.value})" }
}
public class Parameter <: Token {
public let name: String
public let modifier: String
public let pattern: String
public init(name: String) {
this.name = name
this.modifier = "SINGLE"
this.pattern = ""
}
public init(name: String, modifier: String, pattern: String) {
this.name = name
this.modifier = modifier
this.pattern = pattern
}
public func kind(): TokenKind { TokenKind.PARAM }
public func toString(): String {
"Parameter(name: ${this.name})"
}
}
public class Wildcard <: Token {
public let name: String
public init(name: String) { this.name = name }
public func kind(): TokenKind { TokenKind.WILDCARD }
public func toString(): String { "Wildcard(name: ${this.name})" }
}
public class Group <: Token {
public let tokens: ArrayList<Token>
public init(tokens: ArrayList<Token>) { this.tokens = tokens }
public func kind(): TokenKind { TokenKind.GROUP }
public func toString(): String {
let sb = StringBuilder("Group(tokens: [")
for (i in 0..this.tokens.size) {
let token = this.tokens[i]
if (i > 0) { sb.append(", ") }
sb.append(token.toString())
}
sb.append("])")
sb.toString()
}
}
2. 路径字符串解析(Tokenize)
parse(str: String): TokenData 函数负责将原始路径字符串解析为 Token 列表。其核心流程包括:
- 遍历字符串,识别参数、通配符、分组、文本等片段。
- 支持参数名、修饰符(
?、*、+)、内联正则、分组嵌套、转义字符等复杂语法。 - 利用辅助函数
readName、escapeText等实现对标识符和特殊字符的处理。
完整实现如下:
func parse(str: String): TokenData {
let chars = str.toRuneArray()
var i: Int64 = 0
var current = ArrayList<Token>()
var stack = ArrayList<ArrayList<Token>>()
var buf = ""
func flushText() {
if (buf.size > 0) {
current.add(Text(escapeText(buf)))
buf = ""
}
}
while (i < chars.size) {
let ch = chars[i]
// escape sequence: \X becomes literal X
if (ch == r'\\') {
if ((i + 1) < chars.size) {
buf = "${buf}${chars[i+1]}"
i = i + 2
continue
} else {
buf = "${buf}\\"
i = i + 1
continue
}
}
if (ch == r':') {
flushText()
let tup = readName(chars, i + 1)
let name = tup[0]
i = tup[1]
var modifier = "SINGLE"
var pat = ""
// 内联正则::name(...)
if (i < chars.size && chars[i] == r'(') {
i = i + 1
var depth: Int64 = 1
var pbuf = ""
while (i < chars.size && depth > 0) {
let c2 = chars[i]
if (c2 == r'\\' && (i + 1) < chars.size) {
pbuf = "${pbuf}${chars[i+1]}"
i = i + 2
continue
}
if (c2 == r'(') { depth = depth + 1; pbuf = "${pbuf}${c2}"; i = i + 1; continue }
if (c2 == r')') {
depth = depth - 1
if (depth == 0) { i = i + 1; break }
}
pbuf = "${pbuf}${c2}"
i = i + 1
}
pat = pbuf
}
// 修饰符:?, *, +
if (i < chars.size) {
if (chars[i] == r'?') { modifier = "OPTIONAL"; i = i + 1 }
else if (chars[i] == r'*') { modifier = "ZERO_OR_MORE"; i = i + 1 }
else if (chars[i] == r'+') { modifier = "ONE_OR_MORE"; i = i + 1 }
}
current.add(Parameter(name, modifier, pat))
continue
}
if (ch == r'*') {
flushText()
let tup = readName(chars, i + 1)
let name = tup[0]
i = tup[1]
current.add(Wildcard(name))
continue
}
if (ch == r'{' || ch == r'(') {
flushText()
stack.add(current)
current = ArrayList<Token>()
i = i + 1
continue
}
if (ch == r'}' || ch == r')') {
flushText()
if (stack.size > 0) {
let prev = stack[stack.size - 1]
stack.remove(at: stack.size - 1)
prev.add(Group(current))
current = prev
}
i = i + 1
continue
}
buf = "${buf}${ch}"
i = i + 1
}
flushText()
while (stack.size > 0) {
let prev = stack[stack.size - 1]
stack.remove(at: stack.size - 1)
let gtext = stringifyTokens(current)
prev.add(Text("{${gtext}"))
current = prev
}
return TokenData(current, str)
}
3. Token 转正则表达式源码
tokensToRegExpSourceWithKeys 函数将 Token 列表转换为正则表达式源码,并收集参数 Key。针对不同类型的 Token,生成对应的正则片段,支持参数修饰符和自定义模式。
func tokensToRegExpSourceWithKeys(tokens: ArrayList<Token>, delimiter: String, keys: ArrayList<Token>, originalPath: String): String {
var result = ""
var i: Int64 = 0
while (i < tokens.size) {
let t = tokens[i]
if (t is Text) {
let tt = (t as Text).getOrThrow()
result = "${result}${escapeRegex(tt.value)}"
} else if (t is Parameter) {
let tp = (t as Parameter).getOrThrow()
keys.add(tp)
if (tp.modifier == "ZERO_OR_MORE") {
result = "${result}([\\s\\S]*)"
} else if (tp.modifier == "ONE_OR_MORE") {
result = "${result}([\\s\\S]+)"
} else if (tp.pattern.size > 0) {
result = "${result}(${tp.pattern})"
} else {
result = "${result}([^${delimiter}]+)"
}
} else if (t is Wildcard) {
let tw = (t as Wildcard).getOrThrow()
keys.add(tw)
result = "${result}([\\s\\S]*)"
} else if (t is Group) {
let tg = (t as Group).getOrThrow()
result = "${result}{${tokensToRegExpSourceWithKeys(tg.tokens, delimiter, keys, originalPath)}}"
}
i = i + 1
}
return result
}
4. 路径编译与生成
CompiledPath 类实现了根据参数数据生成路径字符串的能力。支持参数编码、分隔符自定义、分组条件渲染等。
public class CompiledPath {
let tokens: ArrayList<Token>
let delimiter: String
let encodeDisabled: Bool
public init(tokens: ArrayList<Token>, delimiter: String, encodeDisabled: Bool) {
this.tokens = tokens
this.delimiter = delimiter
this.encodeDisabled = encodeDisabled
}
public func build(params: ParamData): String {
var out = ""
var i: Int64 = 0
while (i < this.tokens.size) {
let t = this.tokens[i]
if (t is Text) {
let tt = (t as Text).getOrThrow()
out = "${out}${tt.value}"
} else if (t is Parameter) {
let tp = (t as Parameter).getOrThrow()
let got = params.get(tp.name)
if (got[0]) {
var v = got[1]
if (!this.encodeDisabled) { v = percentEncode(v) }
out = "${out}${v}"
}
} else if (t is Wildcard) {
let tw = (t as Wildcard).getOrThrow()
var parts = ArrayList<String>()
var idx: Int64 = 0
while (true) {
let key = "${tw.name}/${idx.toString()}"
let got = params.get(key)
if (!got[0]) { break }
parts.add(got[1])
idx = idx + 1
}
var j: Int64 = 0
while (j < parts.size) {
var pv = parts[j]
if (!this.encodeDisabled) { pv = percentEncode(pv) }
out = "${out}${pv}"
if ((j + 1) < parts.size) { out = "${out}${this.delimiter}" }
j = j + 1
}
} else if (t is Group) {
let tg = (t as Group).getOrThrow()
var allPresent = true
var k: Int64 = 0
while (k < tg.tokens.size) {
let gt = tg.tokens[k]
if (gt is Parameter) {
let tp2 = (gt as Parameter).getOrThrow()
let present = params.get(tp2.name)
if (!present[0]) { allPresent = false; break }
}
if (gt is Wildcard) {
let tw2 = (gt as Wildcard).getOrThrow()
let present = params.get("${tw2.name}/0")
if (!present[0]) { allPresent = false; break }
}
k = k + 1
}
if (allPresent) {
let inner = CompiledPath(tg.tokens, this.delimiter, this.encodeDisabled).build(params)
out = "${out}${inner}"
}
}
i = i + 1
}
return out
}
}
5. 参数提取与匹配结果
MatchResult 类用于存储路径匹配结果,包括文本参数和通配符参数。支持百分号编码/解码,保证路径安全性。
public class MatchResult <: ToString {
public let path: String
public let paramsText: ArrayList<(String, String)>
public let paramsList: ArrayList<(String, ArrayList<String>)>
public init(path: String, paramsText: ArrayList<(String, String)>, paramsList: ArrayList<(String, ArrayList<String>)>) {
this.path = path
this.paramsText = paramsText
this.paramsList = paramsList
}
public func toString(): String {
let sb = StringBuilder("MatchResult(path: ${this.path}, params: {")
var i: Int64 = 0
while (i < this.paramsText.size) {
let p = this.paramsText[i]
if (i > 0) { sb.append(", ") }
sb.append("${p[0]}=${p[1]}")
i = i + 1
}
var j: Int64 = 0
while (j < this.paramsList.size) {
let p = this.paramsList[j]
if (this.paramsText.size > 0 || j > 0) { sb.append(", ") }
sb.append("${p[0]}=[")
var k: Int64 = 0
while (k < p[1].size) {
if (k > 0) { sb.append(", ") }
sb.append(p[1][k])
k = k + 1
}
sb.append("]")
j = j + 1
}
sb.append("})")
sb.toString()
}
}
6. 完整使用示例
下面给出一个完整的路径编译与生成、参数提取的示例:
import std.collection.*
import cue.router.path_to_regex.*
main() {
// 定义路径模板
let path = "/user/:id/profile"
// 编译路径模板
let compiled = compile(path)
// 构造参数
let params = ParamData()
params.put("id", "42")
// 生成路径
let resultPath = compiled.build(params)
println(resultPath) // 输出:/user/42/profile
// 解析路径并提取参数
let tokens = parse(path).tokens
let keys = ArrayList<Token>()
let regexSource = tokensToRegExpSourceWithKeys(tokens, "/", keys, path)
println("正则表达式源码: ${regexSource}")
println("参数 Keys: ")
for (k in keys) {
println(k.toString())
}
}
三、总结与扩展
仓颉web路由校验工具 通过仓颉语言的强类型和面向对象特性,实现了灵活且高效的路径模板解析、参数提取和路径生成能力。其设计兼顾了易用性、可扩展性和性能,适用于 HarmonyOS 及其他需要路由匹配的场景。
- 易用性:API 设计简洁,支持常见路由模板语法。
- 扩展性:支持自定义正则、分组、通配符等高级特性。
- 性能:底层实现高效
新一代开源开发者平台 GitCode,通过集成代码托管服务、代码仓库以及可信赖的开源组件库,让开发者可以在云端进行代码托管和开发。旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。
更多推荐



所有评论(0)