Files
aaaamoooo 05366d9177 perf(panel): kill per-request full-database clones (item 22, backend)
Data.Clone() was a full JSON marshal/unmarshal round trip (with a
per-record map dance for unknown fields): 6.3ms and ~13.8k allocs per
call on sandbox hardware, and every admin GET paid it at least twice
(RequireAdmin's user lookup + the handler). Clone is now a reflective
deep copy of the plain data graph — byte-identical output, proven
against the JSON clone on the real seed database by
TestCloneEquivalence — at 0.37ms / 538 allocs. CurrentAdmin resolves
the session user under a read-only store View and no longer clones
at all, so an admin GET takes exactly one snapshot. Sandbox
benchmarks (11 servers / 26 customers / 25 nodes): overview
17.2ms -> 0.38ms, servers 13.3 -> 0.85ms, customers 20.5 -> 1.54ms,
nodes 15.8 -> 3.45ms, jobs 13.0 -> 0.39ms per request.
2026-10-21 10:50:00 +00:00

811 lines
21 KiB
Go

package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/url"
"regexp"
"sort"
"strconv"
"strings"
)
// ---------- ordered map (JS object insertion order for Clash proxies) ----------
type OrderedKV struct {
Key string
Value any
}
type OrderedMap []OrderedKV
func (m OrderedMap) Get(key string) (any, bool) {
for _, kv := range m {
if kv.Key == key {
return kv.Value, true
}
}
return nil, false
}
func (m *OrderedMap) Set(key string, value any) {
for i := range *m {
if (*m)[i].Key == key {
(*m)[i].Value = value
return
}
}
*m = append(*m, OrderedKV{Key: key, Value: value})
}
func (m OrderedMap) Str(key string) string {
v, ok := m.Get(key)
if !ok || v == nil {
return ""
}
s, _ := v.(string)
return s
}
func (m OrderedMap) Bool(key string) bool {
v, _ := m.Get(key)
b, _ := v.(bool)
return b
}
// ---------- entries ----------
type SubEntry struct {
NodeID string `json:"nodeId"`
ClientURI string `json:"clientUri"`
}
type SubResult struct {
Body string
ContentType string
}
func decodeURIComponent(s string) string {
out, err := url.PathUnescape(s)
if err != nil {
return s
}
return out
}
func NodeClientURI(d *Data, nc *NodeClient) string {
var node *Node
for _, item := range d.Nodes {
if item.ID == nc.NodeID {
node = item
break
}
}
if node == nil || node.Status != "active" {
return ""
}
var server *Server
for _, item := range d.Servers {
if item.ID == node.ServerID {
server = item
break
}
}
if server == nil {
return ""
}
publicKey := ""
if raw, ok := node.Artifacts["realityPublicKey"]; ok && len(raw) > 0 {
var s string
if err := json.Unmarshal(raw, &s); err == nil {
publicKey = s
}
}
networkMode := node.NetworkMode
if networkMode == "" {
networkMode = "ipv4"
}
creds := Credentials{}
if customer := findCustomer(d, nc.CustomerID); customer != nil && customer.Credentials != nil {
creds = *customer.Credentials
}
// Round-3 item 21: a socks node's link carries the node's fixed
// account, identical for every bound customer; the binding only
// controls whether the node appears in the subscription at all.
socksUser := NodeEmail(nc.CustomerID)
relayPassword := creds.RelayPassword
if node.Protocol == "socks5-auth" {
socksUser, relayPassword = node.SocksUser, node.SocksPassword
if socksUser == "" || relayPassword == "" {
// Legacy record the load migration has not reached (only
// possible on an in-memory fixture): mint into a throwaway
// copy — a read path must never mutate the node record.
probe := *node
EnsureSocksCredentials(&probe)
socksUser, relayPassword = probe.SocksUser, probe.SocksPassword
}
}
uri, ok := BuildClientURI(BuildClientURIParams{
Protocol: node.Protocol,
RelayServer: server,
RelayPort: node.Port,
ClientID: creds.ClientID,
RelayPassword: relayPassword,
ShortID: creds.ShortID,
WSPath: xbDeref(node.WSPath),
SocksUser: socksUser,
Reality: node.Reality,
PublicKey: publicKey,
Name: node.Name,
NetworkMode: networkMode,
TLSDomain: server.TLSDomain,
SSServerKey: xbDeref(node.SSServerKey),
})
if !ok {
return ""
}
return uri
}
// sortIndexOf mirrors sortIndexOf in lib/subscriptions.js.
func sortIndexOf(d *Data, nodeID string) int {
for _, item := range d.Nodes {
if item.ID == nodeID {
return item.SortIndex
}
}
return 1 << 30
}
func Entries(d *Data, customerID string) []SubEntry {
seen := map[string]int{}
items := []SubEntry{}
bindings := []*NodeClient{}
for _, nc := range d.NodeClients {
if nc.CustomerID == customerID && nc.Status == "active" {
bindings = append(bindings, nc)
}
}
sort.SliceStable(bindings, func(i, j int) bool {
return sortIndexOf(d, bindings[i].NodeID) < sortIndexOf(d, bindings[j].NodeID)
})
for _, nc := range bindings {
uri := NodeClientURI(d, nc)
if uri == "" {
continue
}
nodeName := nc.NodeID
for _, item := range d.Nodes {
if item.ID == nc.NodeID {
nodeName = item.Name
break
}
}
index := seen[nodeName] + 1
seen[nodeName] = index
name := nodeName
if index != 1 {
name = fmt.Sprintf("%s (%d)", nodeName, index)
}
separator := strings.LastIndex(uri, "#")
prefix := uri
if separator >= 0 {
prefix = uri[:separator]
}
items = append(items, SubEntry{
NodeID: nc.NodeID,
ClientURI: prefix + "#" + encodeURIComponent(name),
})
}
return items
}
// ---------- Clash ----------
func q(value any) string {
// JSON.stringify(String(value)); String(null) is "null".
b, err := marshalJSON(toJSString(value))
if err != nil {
panic(err)
}
return string(b)
}
func toJSString(value any) string {
switch v := value.(type) {
case string:
return v
case bool:
if v {
return "true"
}
return "false"
case int:
return strconv.Itoa(v)
case int64:
return strconv.FormatInt(v, 10)
case float64:
return strconv.FormatFloat(v, 'g', -1, 64)
case nil:
return "null"
default:
return fmt.Sprintf("%v", v)
}
}
func ClashProxy(item SubEntry) (OrderedMap, error) {
uri := item.ClientURI
hashIdx := strings.LastIndex(uri, "#")
name := decodeURIComponent(uri[hashIdx+1:])
u, err := url.Parse(uri)
if err != nil {
return nil, err
}
port := 0
if p := u.Port(); p != "" {
if n, err := strconv.Atoi(p); err == nil {
port = n
}
}
base := OrderedMap{
{Key: "name", Value: name},
{Key: "server", Value: u.Hostname()},
{Key: "port", Value: port},
}
query := u.Query()
switch u.Scheme + ":" {
case "hysteria2:":
password := ""
if u.User != nil {
password = u.User.Username()
}
out := append(OrderedMap{}, base...)
out = append(out,
OrderedKV{Key: "type", Value: "hysteria2"},
OrderedKV{Key: "password", Value: password},
OrderedKV{Key: "sni", Value: queryGetOrNil(query, "sni")},
OrderedKV{Key: "alpn", Value: []string{"h3"}},
)
return out, nil
case "anytls:":
password := ""
if u.User != nil {
password = u.User.Username()
}
out := append(OrderedMap{}, base...)
out = append(out,
OrderedKV{Key: "type", Value: "anytls"},
OrderedKV{Key: "password", Value: password},
OrderedKV{Key: "sni", Value: queryGetOrNil(query, "sni")},
OrderedKV{Key: "udp", Value: true},
)
return out, nil
case "vless:":
security := query.Get("security")
uuid := ""
if u.User != nil {
uuid = u.User.Username()
}
isTLS := security == "reality" || security == "tls"
network := "tcp"
if query.Get("type") == "ws" {
network = "ws"
}
out := append(OrderedMap{}, base...)
out = append(out,
OrderedKV{Key: "type", Value: "vless"},
OrderedKV{Key: "uuid", Value: uuid},
OrderedKV{Key: "tls", Value: isTLS},
OrderedKV{Key: "network", Value: network},
)
if network == "ws" {
path := query.Get("path")
if path == "" {
path = "/"
}
out = append(out, OrderedKV{Key: "ws-opts", Value: OrderedMap{{Key: "path", Value: path}}})
}
if isTLS {
out = append(out, OrderedKV{Key: "servername", Value: queryGetOrNil(query, "sni")})
if security == "reality" {
fp := query.Get("fp")
if fp == "" {
fp = "chrome"
}
out = append(out,
OrderedKV{Key: "client-fingerprint", Value: fp},
OrderedKV{Key: "reality-opts", Value: OrderedMap{
{Key: "public-key", Value: queryGetOrNil(query, "pbk")},
{Key: "short-id", Value: queryGetOrNil(query, "sid")},
}},
)
}
if _, has := query["flow"]; has {
out = append(out, OrderedKV{Key: "flow", Value: query.Get("flow")})
}
}
return out, nil
case "ss:":
username := ""
if u.User != nil {
username = u.User.Username()
}
raw, err := decodeBase64URL(username)
if err != nil {
return nil, errors.New("Invalid Shadowsocks URI")
}
decoded := string(raw)
separator := strings.Index(decoded, ":")
if separator < 0 {
return nil, errors.New("Invalid Shadowsocks URI")
}
out := append(OrderedMap{}, base...)
out = append(out,
OrderedKV{Key: "type", Value: "ss"},
OrderedKV{Key: "cipher", Value: decoded[:separator]},
OrderedKV{Key: "password", Value: decoded[separator+1:]},
)
return out, nil
case "socks5:":
username, password := "", ""
if u.User != nil {
username = u.User.Username()
password, _ = u.User.Password()
}
out := append(OrderedMap{}, base...)
out = append(out,
OrderedKV{Key: "type", Value: "socks5"},
OrderedKV{Key: "username", Value: username},
OrderedKV{Key: "password", Value: password},
)
return out, nil
}
return nil, errors.New("Unsupported client URI")
}
func queryGetOrNil(query url.Values, key string) any {
vals, ok := query[key]
if !ok || len(vals) == 0 {
return nil
}
return vals[0]
}
func decodeBase64URL(s string) ([]byte, error) {
if raw, err := base64.RawURLEncoding.DecodeString(s); err == nil {
return raw, nil
}
padded := s
for len(padded)%4 != 0 {
padded += "="
}
return base64.URLEncoding.DecodeString(padded)
}
func yamlValue(value any, indent int) string {
switch v := value.(type) {
case []string:
raw, err := marshalJSON(v)
if err != nil {
panic(err)
}
return string(raw)
case []any:
raw, err := marshalJSON(v)
if err != nil {
panic(err)
}
return string(raw)
case OrderedMap:
parts := make([]string, 0, len(v))
pad := strings.Repeat(" ", indent)
for _, kv := range v {
if child, isObj := kv.Value.(OrderedMap); isObj {
parts = append(parts, pad+kv.Key+": "+"\n"+yamlValue(child, indent+2))
} else {
parts = append(parts, pad+kv.Key+": "+yamlValue(kv.Value, 0))
}
}
return strings.Join(parts, "\n")
case bool:
if v {
return "true"
}
return "false"
case int:
return strconv.Itoa(v)
case int64:
return strconv.FormatInt(v, 10)
case float64:
return strconv.FormatFloat(v, 'g', -1, 64)
case json.Number:
return v.String()
default:
return q(value)
}
}
func MakeClash(items []SubEntry, smart bool) string {
reserved := []string{"节点选择", "DIRECT", "REJECT"}
if smart {
reserved = []string{"节点选择", "自动选择", "流媒体", "AI 服务", "Meta 服务", "DIRECT", "REJECT"}
}
used := map[string]bool{}
for _, r := range reserved {
used[r] = true
}
proxies := make([]OrderedMap, 0, len(items))
for _, item := range items {
proxy, err := ClashProxy(item)
if err != nil {
panic(err)
}
name := proxy.Str("name")
finalName := name
if used[finalName] {
index := 1
for {
suffix := ""
if index != 1 {
suffix = fmt.Sprintf(" %d", index)
}
finalName = fmt.Sprintf("%s (节点%s)", name, suffix)
index++
if !used[finalName] {
break
}
}
}
used[finalName] = true
proxy.Set("name", finalName)
proxies = append(proxies, proxy)
}
names := make([]string, 0, len(proxies))
yamlParts := make([]string, 0, len(proxies))
for _, proxy := range proxies {
names = append(names, proxy.Str("name"))
y := yamlValue(proxy, 4)
if strings.HasPrefix(y, " ") {
y = " - " + y[4:]
}
yamlParts = append(yamlParts, y)
}
yaml := strings.Join(yamlParts, "\n")
group := func(name, typ string, members []string) string {
var sb strings.Builder
sb.WriteString(" - name: " + q(name) + "\n")
sb.WriteString(" type: " + typ + "\n")
if typ == "url-test" {
sb.WriteString(" url: \"https://www.gstatic.com/generate_204\"\n interval: 300\n tolerance: 80\n")
}
sb.WriteString(" proxies:\n")
lines := make([]string, len(members))
for i, m := range members {
lines[i] = " - " + q(m)
}
sb.WriteString(strings.Join(lines, "\n"))
sb.WriteString("\n")
return sb.String()
}
var groups string
if smart {
groups = group("节点选择", "select", append([]string{"自动选择"}, append(append([]string{}, names...), "DIRECT")...)) +
group("自动选择", "url-test", names) +
group("流媒体", "select", append([]string{"节点选择", "自动选择"}, names...)) +
group("AI 服务", "select", append([]string{"节点选择", "自动选择"}, names...)) +
group("Meta 服务", "select", append([]string{"节点选择", "自动选择"}, names...))
} else {
groups = group("节点选择", "select", names)
}
rules := []string{"MATCH,节点选择"}
if smart {
rules = []string{
"GEOSITE,category-ads-all,REJECT", "GEOSITE,private,DIRECT",
"GEOSITE,openai,AI 服务", "GEOSITE,netflix,流媒体",
"GEOSITE,facebook,Meta 服务", "DOMAIN-SUFFIX,fbcdn.net,Meta 服务",
"DOMAIN-SUFFIX,instagram.com,Meta 服务", "DOMAIN-SUFFIX,whatsapp.net,Meta 服务",
"GEOSITE,geolocation-!cn,节点选择", "GEOSITE,cn,DIRECT",
"GEOIP,CN,DIRECT", "MATCH,节点选择",
}
}
ruleLines := make([]string, len(rules))
for i, r := range rules {
ruleLines[i] = " - " + q(r)
}
return "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n" + yaml +
"\nproxy-groups:\n" + groups + "rules:\n" + strings.Join(ruleLines, "\n") + "\n"
}
// ---------- Surge ----------
// MakeSurge mirrors makeSurge; ok=false mirrors JS null.
func MakeSurge(items []SubEntry) (string, bool) {
proxies := []OrderedMap{}
for _, item := range items {
proxy, err := ClashProxy(item)
if err != nil {
panic(err)
}
typ := proxy.Str("type")
if typ == "ss" || typ == "socks5" {
proxies = append(proxies, proxy)
}
}
if len(proxies) == 0 {
return "", false
}
safeReplacer := strings.NewReplacer("\r", "_", "\n", "_", ",", "_", "=", "_")
safe := func(v string) string { return safeReplacer.Replace(v) }
policies := make([]string, 0, len(proxies))
names := make([]string, 0, len(proxies))
for _, proxy := range proxies {
prefix := safe(proxy.Str("name")) + " = "
server, _ := proxy.Get("server")
port, _ := proxy.Get("port")
if proxy.Str("type") == "ss" {
policies = append(policies, fmt.Sprintf("%sss, %s, %s, encrypt-method=%s, password=%s",
prefix, toJSString(server), toJSString(port), proxy.Str("cipher"), safe(proxy.Str("password"))))
} else {
policies = append(policies, fmt.Sprintf("%ssocks5, %s, %s, %s, %s",
prefix, toJSString(server), toJSString(port), safe(proxy.Str("username")), safe(proxy.Str("password"))))
}
names = append(names, safe(proxy.Str("name")))
}
joined := strings.Join(names, ", ")
body := "[General]\nloglevel = notify\n[Proxy]\n" + strings.Join(policies, "\n") +
"\n[Proxy Group]\n节点选择 = select, " + joined + ", DIRECT\n自动选择 = url-test, " + joined +
", url=http://www.gstatic.com/generate_204, interval=600\n[Rule]\nGEOIP,CN,DIRECT\nFINAL,节点选择\n"
return body, true
}
// ---------- sing-box ----------
type sbTransport struct {
Type string `json:"type"`
Path string `json:"path"`
}
type sbReality struct {
Enabled bool `json:"enabled"`
PublicKey string `json:"public_key"`
ShortID string `json:"short_id"`
}
type sbTLS struct {
Enabled bool `json:"enabled"`
ServerName string `json:"server_name"`
Alpn []string `json:"alpn,omitempty"`
Reality *sbReality `json:"reality,omitempty"`
}
type sbOutbound struct {
Type string `json:"type"`
Tag string `json:"tag"`
Server string `json:"server,omitempty"`
ServerPort int `json:"server_port,omitempty"`
Method string `json:"method,omitempty"`
Version string `json:"version,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
UUID string `json:"uuid,omitempty"`
Flow string `json:"flow,omitempty"`
Transport *sbTransport `json:"transport,omitempty"`
TLS *sbTLS `json:"tls,omitempty"`
Outbounds []string `json:"outbounds,omitempty"`
}
type sbInbound struct {
Type string `json:"type"`
Tag string `json:"tag"`
Listen string `json:"listen"`
ListenPort int `json:"listen_port"`
}
type sbConfig struct {
Log struct {
Level string `json:"level"`
} `json:"log"`
Inbounds []sbInbound `json:"inbounds"`
Outbounds []sbOutbound `json:"outbounds"`
Route struct {
Final string `json:"final"`
} `json:"route"`
}
func subMarshalIndentJSON(v any) string {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
if err := enc.Encode(v); err != nil {
panic(err)
}
return buf.String()
}
func MakeSingBox(items []SubEntry) string {
proxies := make([]sbOutbound, 0, len(items))
tags := make([]string, 0, len(items))
for i, item := range items {
proxy, err := ClashProxy(item)
if err != nil {
panic(err)
}
tag := fmt.Sprintf("node-%d", i+1)
tags = append(tags, tag)
server := proxy.Str("server")
portVal, _ := proxy.Get("port")
port, _ := portVal.(int)
base := sbOutbound{Tag: tag, Server: server, ServerPort: port}
switch proxy.Str("type") {
case "ss":
base.Type = "shadowsocks"
base.Method = proxy.Str("cipher")
base.Password = proxy.Str("password")
case "hysteria2":
base.Type = "hysteria2"
base.Password = proxy.Str("password")
base.TLS = &sbTLS{Enabled: true, ServerName: proxy.Str("sni"), Alpn: []string{"h3"}}
case "anytls":
base.Type = "anytls"
base.Password = proxy.Str("password")
base.TLS = &sbTLS{Enabled: true, ServerName: proxy.Str("sni")}
case "socks5":
base.Type = "socks"
base.Version = "5"
base.Username = proxy.Str("username")
base.Password = proxy.Str("password")
default: // vless
base.Type = "vless"
base.UUID = proxy.Str("uuid")
if flow := proxy.Str("flow"); flow != "" {
base.Flow = flow
}
if proxy.Str("network") == "ws" {
path := "/"
if wsOpts, ok := proxy.Get("ws-opts"); ok {
if om, isOM := wsOpts.(OrderedMap); isOM {
if p := om.Str("path"); p != "" {
path = p
}
}
}
base.Transport = &sbTransport{Type: "ws", Path: path}
}
if proxy.Bool("tls") {
tls := &sbTLS{Enabled: true, ServerName: proxy.Str("servername")}
if ro, ok := proxy.Get("reality-opts"); ok && ro != nil {
if om, isOM := ro.(OrderedMap); isOM {
tls.Reality = &sbReality{Enabled: true, PublicKey: om.Str("public-key"), ShortID: om.Str("short-id")}
}
}
base.TLS = tls
}
}
proxies = append(proxies, base)
}
cfg := sbConfig{}
cfg.Log.Level = "warn"
cfg.Inbounds = []sbInbound{{Type: "mixed", Tag: "local", Listen: "127.0.0.1", ListenPort: 2080}}
cfg.Outbounds = append([]sbOutbound{{Type: "selector", Tag: "proxy", Outbounds: tags}}, proxies...)
cfg.Outbounds = append(cfg.Outbounds, sbOutbound{Type: "direct", Tag: "direct"})
cfg.Route.Final = "proxy"
return subMarshalIndentJSON(cfg)
}
// ---------- format dispatch ----------
var clashUARe = regexp.MustCompile(`(?i)clash|mihomo`)
// AggregateEntries mirrors aggregateEntries in lib/subscriptions.js:
// own entries (sortIndex order) → manual links → external sources,
// deduped by scheme+host+port+credential fingerprint, first wins.
func AggregateEntries(d *Data, customer *Customer) []SubEntry {
own := Entries(d, customer.ID)
out := []SubEntry{}
seenKeys := map[string]bool{}
seenNames := map[string]int{}
push := func(uri, name string) {
if uri == "" {
return
}
key := DedupKey(uri)
if key != "" && seenKeys[key] {
return
}
if key != "" {
seenKeys[key] = true
}
index := seenNames[name] + 1
seenNames[name] = index
finalName := name
if index != 1 {
finalName = fmt.Sprintf("%s (%d)", name, index)
}
out = append(out, SubEntry{ClientURI: WithName(uri, finalName)})
}
for _, entry := range own {
key := DedupKey(entry.ClientURI)
if key != "" {
seenKeys[key] = true
}
seenNames[NameFromURI(entry.ClientURI)]++
out = append(out, entry)
}
for i, link := range customer.ManualLinks {
if !link.Enabled {
continue
}
name := link.Remark
if name == "" {
name = NameFromURI(link.URI)
}
if name == "" {
name = fmt.Sprintf("手动节点 %d", i+1)
}
push(link.URI, name)
}
for _, source := range customer.ExternalSources {
if !source.Enabled {
continue
}
entry := d.SubSourceCache[source.ID]
if entry == nil || entry.OkAt == nil {
continue
}
for _, item := range entry.Items {
name := item.Name
if name == "" {
name = "未命名节点"
}
if source.Remark != "" {
name = "[" + source.Remark + "] " + name
}
push(item.URI, name)
}
}
return out
}
func FormatSubscription(d *Data, customer *Customer, format, userAgent string) (*SubResult, bool) {
items := AggregateEntries(d, customer)
if len(items) == 0 {
return nil, false
}
selected := format
if format == "auto" {
if clashUARe.MatchString(userAgent) {
selected = "clash"
} else {
selected = "base64"
}
}
joined := func() string {
uris := make([]string, len(items))
for i, item := range items {
uris[i] = item.ClientURI
}
return strings.Join(uris, "\n") + "\n"
}
switch selected {
case "raw":
return &SubResult{Body: joined(), ContentType: "text/plain; charset=utf-8"}, true
case "base64":
return &SubResult{Body: base64.StdEncoding.EncodeToString([]byte(joined())), ContentType: "text/plain; charset=utf-8"}, true
case "clash", "clash-smart":
return &SubResult{Body: MakeClash(items, selected == "clash-smart"), ContentType: "text/yaml; charset=utf-8"}, true
case "singbox":
return &SubResult{Body: MakeSingBox(items), ContentType: "application/json; charset=utf-8"}, true
case "surge":
body, ok := MakeSurge(items)
if !ok {
return nil, false
}
return &SubResult{Body: body, ContentType: "text/plain; charset=utf-8"}, true
}
return nil, false
}