mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-30 08:16:37 +08:00
249 lines
6.8 KiB
Go
249 lines
6.8 KiB
Go
package handler
|
|
|
|
import (
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
bestExitDisplayStatusApplied = "applied"
|
|
bestExitDisplayStatusWaiting = "waiting"
|
|
bestExitDisplaySummaryMulti = "多个出口"
|
|
bestExitDisplaySummaryWait = "等待探测"
|
|
bestExitUnknownExitName = "未知出口"
|
|
bestExitUnknownEntryName = "未知入口"
|
|
bestExitUnknownChainName = "未知中转"
|
|
)
|
|
|
|
type bestExitDecisionSnapshot struct {
|
|
AppliedExitNodeID int64
|
|
UpdatedAt int64
|
|
Reason string
|
|
Scores []bestExitCandidateScore
|
|
}
|
|
|
|
type bestExitDisplayState struct {
|
|
Enabled bool `json:"enabled"`
|
|
Summary string `json:"summary"`
|
|
Status string `json:"status"`
|
|
UpdatedAt int64 `json:"updatedAt,omitempty"`
|
|
Reason string `json:"reason,omitempty"`
|
|
Items []bestExitDisplayItem `json:"items"`
|
|
}
|
|
|
|
type bestExitDisplayItem struct {
|
|
OwnerNodeID int64 `json:"ownerNodeId"`
|
|
OwnerNodeName string `json:"ownerNodeName"`
|
|
OwnerRole string `json:"ownerRole"`
|
|
ExitNodeID int64 `json:"exitNodeId,omitempty"`
|
|
ExitNodeName string `json:"exitNodeName"`
|
|
UpdatedAt int64 `json:"updatedAt,omitempty"`
|
|
Reason string `json:"reason,omitempty"`
|
|
}
|
|
|
|
type bestExitNodeNameLookup func(nodeID int64) (string, bool)
|
|
|
|
func (m *bestExitManager) snapshot(key bestExitOwnerKey) (bestExitDecisionSnapshot, bool) {
|
|
if m == nil {
|
|
return bestExitDecisionSnapshot{}, false
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
d := m.decisions[key]
|
|
if d == nil {
|
|
return bestExitDecisionSnapshot{}, false
|
|
}
|
|
updatedAt := int64(0)
|
|
if !d.LastSwitchAt.IsZero() {
|
|
updatedAt = d.LastSwitchAt.UnixMilli()
|
|
}
|
|
return bestExitDecisionSnapshot{
|
|
AppliedExitNodeID: d.AppliedExitNodeID,
|
|
UpdatedAt: updatedAt,
|
|
Reason: d.LastReason,
|
|
Scores: cloneBestExitScores(d.Scores),
|
|
}, true
|
|
}
|
|
|
|
func (h *Handler) attachBestExitStates(items []map[string]interface{}) {
|
|
if h == nil || len(items) == 0 {
|
|
return
|
|
}
|
|
lookup := h.bestExitNodeNameLookup()
|
|
for _, item := range items {
|
|
state, ok := buildBestExitDisplayState(item, h.bestExit, lookup)
|
|
if !ok {
|
|
delete(item, "bestExitState")
|
|
continue
|
|
}
|
|
item["bestExitState"] = state
|
|
}
|
|
}
|
|
|
|
func (h *Handler) bestExitNodeNameLookup() bestExitNodeNameLookup {
|
|
cache := map[int64]string{}
|
|
return func(nodeID int64) (string, bool) {
|
|
if nodeID <= 0 || h == nil {
|
|
return "", false
|
|
}
|
|
if name, ok := cache[nodeID]; ok {
|
|
return name, name != ""
|
|
}
|
|
node, err := h.getNodeRecord(nodeID)
|
|
if err != nil || node == nil {
|
|
cache[nodeID] = ""
|
|
return "", false
|
|
}
|
|
name := strings.TrimSpace(node.Name)
|
|
cache[nodeID] = name
|
|
return name, name != ""
|
|
}
|
|
}
|
|
|
|
func buildBestExitDisplayState(tunnel map[string]interface{}, manager *bestExitManager, lookup bestExitNodeNameLookup) (*bestExitDisplayState, bool) {
|
|
if tunnel == nil {
|
|
return nil, false
|
|
}
|
|
tunnelID := asInt64(tunnel["id"], 0)
|
|
outNodes := bestExitDisplayMapSlice(tunnel["outNodeId"])
|
|
if tunnelID <= 0 || len(outNodes) <= 1 {
|
|
return nil, false
|
|
}
|
|
if !isBestTunnelStrategy(asString(outNodes[0]["strategy"])) {
|
|
return nil, false
|
|
}
|
|
|
|
owners, ownerRole := bestExitDisplayOwners(tunnel)
|
|
state := &bestExitDisplayState{
|
|
Enabled: true,
|
|
Summary: bestExitDisplaySummaryWait,
|
|
Status: bestExitDisplayStatusWaiting,
|
|
Items: make([]bestExitDisplayItem, 0, len(owners)),
|
|
}
|
|
|
|
exitsByID := map[int64]map[string]interface{}{}
|
|
for _, exit := range outNodes {
|
|
if id := asInt64(exit["nodeId"], 0); id > 0 {
|
|
exitsByID[id] = exit
|
|
}
|
|
}
|
|
appliedExitIDs := map[int64]string{}
|
|
appliedCount := 0
|
|
latestUpdatedAt := int64(0)
|
|
latestReason := ""
|
|
for _, owner := range owners {
|
|
ownerNodeID := asInt64(owner["nodeId"], 0)
|
|
if ownerNodeID <= 0 {
|
|
continue
|
|
}
|
|
item := bestExitDisplayItem{
|
|
OwnerNodeID: ownerNodeID,
|
|
OwnerNodeName: bestExitDisplayNodeName(owner, ownerNodeID, lookup, bestExitUnknownOwnerName(ownerRole)),
|
|
OwnerRole: ownerRole,
|
|
ExitNodeName: bestExitDisplaySummaryWait,
|
|
Reason: bestExitDisplayStatusWaiting,
|
|
}
|
|
if snapshot, ok := manager.snapshot(bestExitOwnerKey{TunnelID: tunnelID, OwnerNodeID: ownerNodeID}); ok && snapshot.AppliedExitNodeID > 0 {
|
|
exit, ok := exitsByID[snapshot.AppliedExitNodeID]
|
|
if !ok {
|
|
state.Items = append(state.Items, item)
|
|
continue
|
|
}
|
|
item.ExitNodeID = snapshot.AppliedExitNodeID
|
|
item.ExitNodeName = bestExitDisplayNodeName(exit, snapshot.AppliedExitNodeID, lookup, bestExitUnknownExitName)
|
|
item.UpdatedAt = snapshot.UpdatedAt
|
|
item.Reason = snapshot.Reason
|
|
appliedExitIDs[item.ExitNodeID] = item.ExitNodeName
|
|
appliedCount++
|
|
if snapshot.UpdatedAt > latestUpdatedAt {
|
|
latestUpdatedAt = snapshot.UpdatedAt
|
|
latestReason = snapshot.Reason
|
|
}
|
|
}
|
|
state.Items = append(state.Items, item)
|
|
}
|
|
|
|
if appliedCount == 0 {
|
|
return state, true
|
|
}
|
|
if appliedCount < len(state.Items) {
|
|
return state, true
|
|
}
|
|
state.Status = bestExitDisplayStatusApplied
|
|
state.UpdatedAt = latestUpdatedAt
|
|
state.Reason = latestReason
|
|
if len(appliedExitIDs) == 1 {
|
|
for _, name := range appliedExitIDs {
|
|
state.Summary = name
|
|
}
|
|
} else {
|
|
state.Summary = bestExitDisplaySummaryMulti
|
|
}
|
|
return state, true
|
|
}
|
|
|
|
func bestExitDisplayOwners(tunnel map[string]interface{}) ([]map[string]interface{}, string) {
|
|
chainGroups := bestExitDisplayChainGroups(tunnel["chainNodes"])
|
|
if len(chainGroups) > 0 {
|
|
return chainGroups[len(chainGroups)-1], "chain"
|
|
}
|
|
return bestExitDisplayMapSlice(tunnel["inNodeId"]), "entry"
|
|
}
|
|
|
|
func bestExitDisplayMapSlice(v interface{}) []map[string]interface{} {
|
|
switch arr := v.(type) {
|
|
case []map[string]interface{}:
|
|
return arr
|
|
case []interface{}:
|
|
out := make([]map[string]interface{}, 0, len(arr))
|
|
for _, item := range arr {
|
|
if m, ok := item.(map[string]interface{}); ok {
|
|
out = append(out, m)
|
|
}
|
|
}
|
|
return out
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func bestExitDisplayChainGroups(v interface{}) [][]map[string]interface{} {
|
|
switch groups := v.(type) {
|
|
case [][]map[string]interface{}:
|
|
return groups
|
|
case []interface{}:
|
|
out := make([][]map[string]interface{}, 0, len(groups))
|
|
for _, group := range groups {
|
|
items := bestExitDisplayMapSlice(group)
|
|
if len(items) > 0 {
|
|
out = append(out, items)
|
|
}
|
|
}
|
|
return out
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func bestExitDisplayNodeName(source map[string]interface{}, nodeID int64, lookup bestExitNodeNameLookup, fallback string) string {
|
|
if source != nil {
|
|
for _, key := range []string{"nodeName", "name"} {
|
|
if name := strings.TrimSpace(asString(source[key])); name != "" {
|
|
return name
|
|
}
|
|
}
|
|
}
|
|
if lookup != nil {
|
|
if name, ok := lookup(nodeID); ok && strings.TrimSpace(name) != "" {
|
|
return strings.TrimSpace(name)
|
|
}
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func bestExitUnknownOwnerName(role string) string {
|
|
if role == "chain" {
|
|
return bestExitUnknownChainName
|
|
}
|
|
return bestExitUnknownEntryName
|
|
}
|