refactor(routing): 暴露 system_locked_domains + 服务端 follow-up 清理(FK/Validate/dedup/exclude单源/共用Store/Builtin保留)

This commit is contained in:
wangjia
2026-07-29 06:38:52 +08:00
parent 52912268d0
commit 3159dd75c1
11 changed files with 285 additions and 36 deletions
+24 -2
View File
@@ -14,10 +14,28 @@ import (
// user hasn't customized one yet; POST validates and upserts.
type RoutingAPI struct {
store *routing.Store
// lockedDomains are the system-forced-tunnel private-service domains
// (PANGOLIN_PRIVATE_SPLIT_DOMAINS, same slice injected into NodeAPI) —
// read-only, surfaced to GET so clients can warn users that rules against
// these domains silently have no effect. Never written to the persisted
// Profile.
lockedDomains []string
}
// NewRoutingAPI creates a RoutingAPI backed by the given routing.Store.
func NewRoutingAPI(store *routing.Store) *RoutingAPI { return &RoutingAPI{store: store} }
// lockedDomains is the system-forced private-service domain list (may be nil).
func NewRoutingAPI(store *routing.Store, lockedDomains []string) *RoutingAPI {
return &RoutingAPI{store: store, lockedDomains: lockedDomains}
}
// profileResponse wraps routing.Profile for GET /v1/me/routing, adding the
// read-only system_locked_domains list. It deliberately lives here — not on
// routing.Profile itself — so the field can never leak into the writable
// Profile contract that SaveProfile decodes POST bodies into.
type profileResponse struct {
*routing.Profile
SystemLockedDomains []string `json:"system_locked_domains"`
}
// GetProfile handles GET /v1/me/routing.
func (a *RoutingAPI) GetProfile(w http.ResponseWriter, r *http.Request) {
@@ -34,7 +52,11 @@ func (a *RoutingAPI) GetProfile(w http.ResponseWriter, r *http.Request) {
if p == nil {
p = routing.Default()
}
writeJSON(w, http.StatusOK, p)
locked := a.lockedDomains
if locked == nil {
locked = []string{}
}
writeJSON(w, http.StatusOK, profileResponse{Profile: p, SystemLockedDomains: locked})
}
// SaveProfile handles POST /v1/me/routing. On validation failure it returns