feat: HH-773 合集重命名接入 egress geo 缓存、geo 失败降级与每小时补探测
Build and Publish Docker Image / build-and-push (pull_request) Successful in 15m54s

- service: EgressCacheKey 从 handler 下沉复用;MergeCachedEgressGeo 在合集
  重命名前合并缓存 geo 字段(纯缓存读、零网络 I/O);StripEgressGeoFields
  防止 geo 字段泄漏进订阅输出
- filter: geo 检测失败的节点保留 '[别名] 原名'(或纯原名)参与编号/排序,
  不再丢弃
- handler: 每小时 StartEgressRefresher 对启用源补探测缺失/过期的 egress
  缓存(含 TTL 内 error 结果一律跳过);RegisterRoutes 返回 *Deps;
  cmd/server.go 启动定时任务并在 shutdown 时取消
- tests: service 缓存 key 稳定性/合并/管道测试、handler 补探测跳过/TTL/
  过期/取消测试、filter 降级测试;修复 2 个过时测试(mihomo YAML 配置、
  缓存命中路径)
This commit is contained in:
杨豪
2026-08-28 16:40:11 +08:00
parent f3029665ee
commit 5a34e739bb
11 changed files with 543 additions and 49 deletions
+15 -2
View File
@@ -66,7 +66,8 @@ func geoFromNode(proxy model.ProxyNode) util.GeoInfo {
// suffix) and numbered sequentially within each group, starting at 1.
//
// Nodes whose geographic info cannot be determined (no cached data and name
// regex fails) are dropped from the output.
// regex fails) are kept in the output under their original name (alias
// prefixed), so they still participate in numbering and sorting.
//
// opts controls which fields are included in the output. A nil opts means all
// fields are included (full default format).
@@ -99,7 +100,19 @@ func RenameCollectionNodes(proxies []model.ProxyNode, opts *model.RenameOptions)
geo = util.DetectGeo(ToString(proxy["name"]))
}
if geo.CountryName == "" {
// Geo detection failed — skip this node
// Geo detection failed — keep the node under its original name
// (alias-prefixed) so it still participates in numbering and sorting.
var parts []string
if o.Alias && alias != "" {
parts = append(parts, "["+alias+"]")
}
if name := ToString(proxy["name"]); name != "" {
parts = append(parts, name)
}
infos = append(infos, nodeInfo{
proxy: proxy,
baseName: strings.Join(parts, " "),
})
continue
}
+13 -2
View File
@@ -132,10 +132,21 @@ func TestRenameCollectionNodes_NoAlias(t *testing.T) {
func TestRenameCollectionNodes_UnknownCountry(t *testing.T) {
proxies := []model.ProxyNode{
{"name": "Unknown Node", "type": "ss", "_sourceAlias": "A"},
{"name": "Bare Node", "type": "ss"},
}
result := RenameCollectionNodes(proxies, nil)
if len(result) != 0 {
t.Fatalf("expected 0 nodes (no cached geo info, should be filtered), got %d", len(result))
if len(result) != 2 {
t.Fatalf("expected 2 nodes (geo-unknown nodes must be kept), got %d", len(result))
}
// Unknown geo: keep the original name, alias-prefixed when present
if name, _ := result[0]["name"].(string); name != "[A] Unknown Node" {
t.Errorf("node[0] name = %q, want %q", name, "[A] Unknown Node")
}
if name, _ := result[1]["name"].(string); name != "Bare Node" {
t.Errorf("node[1] name = %q, want %q", name, "Bare Node")
}
if _, ok := result[0]["_sourceAlias"]; ok {
t.Error("_sourceAlias should be stripped from rename output")
}
}