Skip to content

fix(country-rule): 将国家规则正则引擎从 RE2 切换为 PCRE,支持零宽断言 - #289

Merged
ZeroDeng01 merged 1 commit into
ZeroDeng01:devfrom
isdoge:fix-country-rule-regexp
Aug 19, 2026
Merged

fix(country-rule): 将国家规则正则引擎从 RE2 切换为 PCRE,支持零宽断言#289
ZeroDeng01 merged 1 commit into
ZeroDeng01:devfrom
isdoge:fix-country-rule-regexp

Conversation

@isdoge

@isdoge isdoge commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

问题

国家规则的「匹配模式」字段目前使用 Go 标准库 regexp(RE2 引擎),不支持零宽断言(lookahead/lookbehind),导致 mihomo 用户常用的边界匹配写法在 sublinkPro 中编译失败:

(?<![A-Za-z])HK(?![A-Za-z])

RE2 报错error parsing regexp: invalid named capture: (?<...)

这迫使用户使用裸子模式(如 (?i)香港|HK|Hong Kong),无法表达「两侧非字母边界」语义,导致:

  • SHK Premium 误匹配 HK 规则(左侧 S 是字母,应不匹配)
  • HKG(广岛 IATA 代码)误匹配 HK 规则(右侧 G 是字母,应不匹配)

issue #270 报告的正是这个误匹配问题。

修复

将正则引擎从 Go 标准库 regexp(RE2)切换为 github.com/dlclark/regexp2/v2/compat(PCRE):

  • PCRE 引擎:完整支持零宽断言 (?=...)(?!...)(?<=...)(?<!...)
  • compat 适配器:API 与标准库 regexp 完全一致Compile 返回 (*Regexp, error)MatchString 返回 bool),所有调用点零改动
  • 不新增 module:mihomo 已传递依赖 dlclark/regexp2/v2 v2.2.1(见 go.mod indirect),无需在 go.mod 新增条目

关键改动

// 改动前
import "regexp"
re, err := regexp.Compile(pattern)
matched := re.MatchString(name)

// 改动后(仅 import 变化,调用零改动)
import regexp "github.com/dlclark/regexp2/v2/compat"
re, err := regexp.Compile(pattern)
matched := re.MatchString(name)

文件变更

  • models/country_rule.go(+15/-27 行,单文件)
  • models/models_regexp2_test.go(新增回归测试)

兼容性

所有现有合法 pattern 行为不变(包括 issue #270 提到的误匹配场景的旧表现也保留,fix 只放开引擎能力,不自动改写用户配置):

pattern testName 结果 说明
`(?i)香港 HK Hong Kong` SHK Premium
`(?i)香港 HK Hong Kong` HK-001
`(?i)(香港 HK Hong\s*Kong)` Hong Kong Server
`香港 HK Hong Kong 🇭🇰`
`(?i)美国 USA? United\s*States` United States
`(?i)美国 USA? United\s*States` 日本节点

用户如想彻底解决 SHK Premium / HKG 误匹配,需手动把 HK 规则的 pattern 改为 (?<![A-Za-z])HK(?![A-Za-z])|香港|Hong Kong|🇭🇰 之类形式 —— 这是 issue #270 提到的用户配置层面的 fix,本 PR 只是把这个选项打开

实测证据

1. Web UI 编辑规则对话框(NAS sublinkpro 生产实例实测)

部署 fix 镜像 sublink-pro:fix-regexp-compat 后,在国家规则编辑对话框中用零宽断言 pattern (?<![A-Za-z])HK(?![A-Za-z]) 实测:

SHK Premium → ✗ 未匹配(左侧断言生效,fix 解决 baseline 误匹配 bug)
image

HKG → ✗ 未匹配(右侧断言生效)
image

HK → ✓ 匹配成功(纯 HK 两侧都是边界,正常用例不破坏)
image

2. API 实测(baseline vs fix 对比)

baselinezerodeng/sublink-pro:latest,RE2 引擎):

POST /api/v1/country-rules/test
{"pattern": "(?<![A-Za-z])HK(?![A-Za-z])", "testName": "HK"}

→ HTTP 400 "error parsing regexp: invalid named capture: (?<..."

fix(本 PR,PCRE 引擎):

POST /api/v1/country-rules/test
{"pattern": "(?<![A-Za-z])HK(?![A-Za-z])", "testName": "SHK Premium"}
→ 200 {"matched": false}  ✅

POST /api/v1/country-rules/test
{"pattern": "(?<![A-Za-z])HK(?![A-Za-z])", "testName": "HKG"}
→ 200 {"matched": false}  ✅

POST /api/v1/country-rules/test
{"pattern": "(?<![A-Za-z])HK(?![A-Za-z])", "testName": "HK"}
→ 200 {"matched": true}  ✅

3. Go 单元测试(含 16 条现有 pattern 兼容性回归)

=== RUN   TestCountryRulePatternMatching
--- PASS: TestCountryRulePatternMatching (0.00s)
=== RUN   TestRegexp2Lookbehind
--- PASS: TestRegexp2Lookbehind (0.00s)
=== RUN   TestRegexp2InlineFlags
--- PASS: TestRegexp2InlineFlags (0.00s)
PASS
ok      sublink/models  0.016s
  • TestCountryRulePatternMatching(已存在):16 条现有 RE2 pattern 全部仍 PASS(兼容性回归保护)
  • TestRegexp2Lookbehind(新增):7 个零宽断言用例,覆盖 issue [Bug]: 国家规则 对 正则支持不完善 #270SHK Premium 误匹配场景
  • TestRegexp2InlineFlags(新增):4 个 (?i) 内联标志 + \s* + emoji + 分组兼容性用例

4. 真实数据 batch-test(51 条启用规则 × 14 个仿真节点名)

POST /api/v1/country-rules/batch-test 返回符合预期:

  • HK / HK01 / HK Premium 01 / 香港 01 / Hong Kong Server / 🇭🇰 节点 → 全部正确解析为 HK
  • SHK Premium / HKG-001 → 仍匹配 HK(这是预期行为:旧规则 pattern 未改,零宽断言需要用户手动配置)
  • 其他国家规则(JP/US/SG/TW 等)解析正确,无回归

测试

  • go test ./models/... 全部 PASS(NAS golang:1.26.4 容器内)
  • 16 条现有 RE2 pattern 兼容性回归 PASS
  • 零宽断言新用例 7 条 PASS
  • (?i) 内联标志 + \s* + emoji + 分组兼容性 4 条 PASS
  • 部署到生产 NAS sublinkpro 实测 API + Web UI 通过
  • batch-test 51 条启用规则 × 14 节点名解析正确

部署 / 升级说明

  • 无需配置变更、无数据库迁移
  • 现有所有合法 pattern 行为不变(包括 issue [Bug]: 国家规则 对 正则支持不完善 #270 报告的误匹配场景的旧表现,fix 只放开引擎能力,不主动改写用户配置)
  • 用户如想解决 SHK Premium / HKG 误匹配,需手动编辑 HK 规则 pattern 为:
    (?<![A-Za-z])HK(?![A-Za-z])|香港|Hong Kong|🇭🇰
    

技术细节

为什么用 dlclark/regexp2/v2/compat 而不是主包?

v2 主包 MatchString 返回 (bool, error),与标准库 regexp.MatchString 返回 bool 签名不同。所有调用点都要改成 matched, _ := re.MatchString(s) 的形式。

compat 子包提供了与标准库 regexp 完全一致的 API(CompileMustCompileMatchStringFindString 等),调用点零改动,未来切换其他 PCRE 引擎只需改一行 import。

为什么不新增 module?

mihomo(核心依赖)已传递依赖 github.com/dlclark/regexp2/v2 v2.2.1(见 go.mod indirect 块),compat 子包共享同一个 module,无需新增任何 require 条目


🤖 Generated with Claude Code

国家规则的匹配模式字段使用 Go 标准库 regexp(RE2 引擎),不支持零宽断言
(lookahead/lookbehind),导致 mihomo 用户常用的边界匹配写法编译失败:

    (?<![A-Za-z])HK(?![A-Za-z])
    → error parsing regexp: invalid named capture: (?<...

这迫使用户使用裸子模式(如 (?i)香港|HK|Hong Kong),无法表达「两侧非字母边界」
语义,导致 SHK Premium、HKG(广岛 IATA 代码)等节点名误匹配 HK 规则。

改动:
- models/country_rule.go: import 从标准库 regexp 改为
  github.com/dlclark/regexp2/v2/compat(PCRE 引擎的标准库兼容适配器)
- 所有调用点零改动:compat 子包的 Compile 返回 (*Regexp, error)、
  MatchString 返回 bool,签名与标准库完全一致
- 不新增 module:mihomo 已传递依赖 dlclark/regexp2/v2 v2.2.1(go.mod indirect)

兼容性:
- 所有现有合法 pattern 行为不变(含 (?i) 内联标志、\s*、emoji、分组、量词)
- fix 只放开引擎能力,不自动改写用户已有规则;用户如想解决 SHK Premium/HKG
  误匹配,需手动把 HK 规则 pattern 改为 (?<![A-Za-z])HK(?![A-Za-z])|香港|... 形式

测试(models/models_regexp2_test.go 新增):
- TestRegexp2Lookbehind: 7 个零宽断言用例,覆盖 issue ZeroDeng01#270 的 SHK Premium 场景
- TestRegexp2InlineFlags: 4 个老格式兼容性用例
- 现有 TestCountryRulePatternMatching 的 16 条 RE2 pattern 全部仍 PASS

实测验证:
- go test ./models/... 全部 PASS(golang:1.26.4 容器)
- 部署到生产实例实测 API /country-rules/test:
  (?<![A-Za-z])HK(?![A-Za-z]) 对 HK→true、SHK Premium→false、HKG→false
- Web UI 编辑规则对话框零宽断言 pattern 实测通过
- batch-test 51 条启用规则 × 14 个节点名解析正确,无回归

Closes ZeroDeng01#270
@isdoge
isdoge force-pushed the fix-country-rule-regexp branch from f227ed4 to 948a5db Compare August 18, 2026 17:04
@isdoge isdoge closed this Aug 18, 2026
@isdoge isdoge reopened this Aug 18, 2026
@ZeroDeng01
ZeroDeng01 merged commit 6df0c14 into ZeroDeng01:dev Aug 19, 2026
3 checks passed
@isdoge
isdoge deleted the fix-country-rule-regexp branch August 19, 2026 13:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants