feat(routing): connect 读 per-user 档案传入渲染(fail-safe 回退默认)

This commit is contained in:
wangjia
2026-07-28 08:11:48 +08:00
parent f76aa56929
commit 82b3988974
4 changed files with 230 additions and 7 deletions
+23 -5
View File
@@ -16,6 +16,7 @@ import (
"github.com/wangjia/pangolin/server/internal/auth"
"github.com/wangjia/pangolin/server/internal/nodes"
agentv1 "github.com/wangjia/pangolin/server/internal/pb/agentv1"
"github.com/wangjia/pangolin/server/internal/routing"
)
const (
@@ -45,13 +46,17 @@ type NodeAPI struct {
// privateSplitDomains 私有服务域名(PANGOLIN_PRIVATE_SPLIT_DOMAINS,逗号分隔),
// 见 ClientConfigOpts.PrivateSplitDomains;空则不渲染相关规则。
privateSplitDomains []string
// routingStore 读取用户可配置分流档案(Task 1)。nil = 未注入(旧路径/未迁移
// 调用点),ConnectNode 回退到 ?split_cn query 兜底,不产坏配置。
routingStore *routing.Store
}
// NewNodeAPI creates a NodeAPI. load may be nil (then all nodes are treated as
// data-plane healthy — agent gRPC liveness still gates status).
func NewNodeAPI(store nodes.NodeStore, hub *nodes.Hub, load nodeLoadReader, deriveKey, rulesBaseURL string, privateSplitDomains []string) *NodeAPI {
// data-plane healthy — agent gRPC liveness still gates status). routingStore
// may be nil (per-user 路由档案不生效,ConnectNode 回退到 ?split_cn query)。
func NewNodeAPI(store nodes.NodeStore, hub *nodes.Hub, load nodeLoadReader, deriveKey, rulesBaseURL string, privateSplitDomains []string, routingStore *routing.Store) *NodeAPI {
return &NodeAPI{store: store, hub: hub, load: load, deriveKey: deriveKey,
rulesBaseURL: rulesBaseURL, privateSplitDomains: privateSplitDomains}
rulesBaseURL: rulesBaseURL, privateSplitDomains: privateSplitDomains, routingStore: routingStore}
}
// dataPlaneHealthy reports the node's last sing-box health (default true when
@@ -316,10 +321,23 @@ func (a *NodeAPI) ConnectNode(w http.ResponseWriter, r *http.Request) {
}
// 7. Render and return the full sing-box CLIENT config JSON.
// split_cn=1/true → 国内 IP/域名直连(#5);客户端按 smartRoute 偏好传。
// 可配置分流(Phase 1):读取用户路由档案,读取/解析失败一律回退 prof=nil
// (fail-safe,不产坏配置)。有档案时国内分流开关由档案决定;无档案(未迁移
// 用户/store 未注入)保留 ?split_cn query 兜底旧行为。
var prof *routing.Profile
if a.routingStore != nil {
if p, perr := a.routingStore.Get(r.Context(), uid); perr == nil {
prof = p
} else {
slog.Warn("connect: routing profile load failed, falling back to default", "user", uid, "err", perr)
}
}
splitCN := r.URL.Query().Get("split_cn") == "1" || r.URL.Query().Get("split_cn") == "true"
if prof != nil {
splitCN = prof.Mode == "rule" && prof.Builtin.ChinaDirect
}
cfgJSON, renderErr := BuildClientConfig(node, dpUUID, a.deriveKey,
ClientConfigOpts{SplitCN: splitCN, RulesBaseURL: a.rulesBaseURL,
ClientConfigOpts{Profile: prof, SplitCN: splitCN, RulesBaseURL: a.rulesBaseURL,
PrivateSplitDomains: a.privateSplitDomains})
if renderErr != nil {
slog.Error("connect: build client config failed", "node", nodeUUID, "err", renderErr)