[优化] 更新节点类型支持,添加隧道客户端,重构相关路由和配置

This commit is contained in:
ryan
2026-06-01 14:01:05 +08:00
parent a092935623
commit 6fa31cafc7
30 changed files with 317 additions and 1337 deletions
@@ -356,7 +356,6 @@ func PublishConfigVersion(createdBy string, force bool) (*ReleaseResult, error)
Checksum: record.Checksum,
}
BroadcastAgentWSActiveConfig(activeConfig)
BroadcastFlaredWSActiveConfig(activeConfig)
return &ReleaseResult{
Version: record,
Routes: bundle.Routes,
-54
View File
@@ -1,54 +0,0 @@
package service
import (
"log/slog"
)
var DefaultFlaredWSHub = NewWSHub("flared")
func RegisterFlaredWSClient(tunnelID string) *WSClient {
return DefaultFlaredWSHub.Register(tunnelID)
}
func UnregisterFlaredWSClient(client *WSClient) {
DefaultFlaredWSHub.Unregister(client)
}
func IsFlaredWSConnected(tunnelID string) bool {
return DefaultFlaredWSHub.IsConnected(tunnelID)
}
func SendFlaredWSPong(tunnelID string) bool {
return DefaultFlaredWSHub.SendMessage(tunnelID, WSMessage{
Type: "pong",
})
}
func SendFlaredWSActiveConfig(tunnelID string, activeConfig *ActiveConfigMeta) bool {
if activeConfig == nil {
return false
}
return DefaultFlaredWSHub.SendMessage(tunnelID, WSMessage{
Type: "active_config",
Payload: activeConfig,
})
}
func BroadcastFlaredWSActiveConfig(activeConfig *ActiveConfigMeta) WSBroadcastResult {
if activeConfig == nil {
slog.Debug("flared ws broadcast skipped because active config is nil")
return WSBroadcastResult{}
}
res := DefaultFlaredWSHub.Broadcast(WSMessage{
Type: "active_config",
Payload: activeConfig,
})
slog.Debug("flared ws broadcast active config",
"version", activeConfig.Version,
"checksum", activeConfig.Checksum,
"client_count", res.ClientCount,
"success_count", res.SuccessCount,
"failed_tunnels", res.FailedIDs,
)
return res
}
+2
View File
@@ -634,6 +634,8 @@ func normalizeNodeType(raw string) string {
switch strings.ToLower(strings.TrimSpace(raw)) {
case "tunnel_relay":
return "tunnel_relay"
case "tunnel_client":
return "tunnel_client"
default:
return "edge_node"
}
+5 -5
View File
@@ -62,7 +62,7 @@ type ProxyRouteInput struct {
BasicAuthPassword string `json:"basic_auth_password"`
Remark string `json:"remark"`
UpstreamType string `json:"upstream_type"`
TunnelID *uint `json:"tunnel_id"`
TunnelNodeID *uint `json:"tunnel_node_id"`
TunnelTargetAddr string `json:"tunnel_target_addr"`
TunnelTargetProtocol string `json:"tunnel_target_protocol"`
}
@@ -101,7 +101,7 @@ type ProxyRouteView struct {
BasicAuthPassword string `json:"basic_auth_password"`
Remark string `json:"remark"`
UpstreamType string `json:"upstream_type"`
TunnelID *uint `json:"tunnel_id"`
TunnelNodeID *uint `json:"tunnel_node_id"`
TunnelTargetAddr string `json:"tunnel_target_addr"`
TunnelTargetProtocol string `json:"tunnel_target_protocol"`
CreatedAt time.Time `json:"created_at"`
@@ -327,11 +327,11 @@ func buildProxyRoute(route *model.ProxyRoute, input ProxyRouteInput) (*model.Pro
route.Remark = remark
route.UpstreamType = upstreamType
if upstreamType == "tunnel" {
route.TunnelID = input.TunnelID
route.TunnelNodeID = input.TunnelNodeID
route.TunnelTargetAddr = strings.TrimSpace(input.TunnelTargetAddr)
route.TunnelTargetProtocol = normalizeTunnelTargetProtocol(input.TunnelTargetProtocol)
} else {
route.TunnelID = nil
route.TunnelNodeID = nil
route.TunnelTargetAddr = ""
route.TunnelTargetProtocol = ""
}
@@ -421,7 +421,7 @@ func buildProxyRouteView(route *model.ProxyRoute) (*ProxyRouteView, error) {
BasicAuthPassword: route.BasicAuthPassword,
Remark: route.Remark,
UpstreamType: route.UpstreamType,
TunnelID: route.TunnelID,
TunnelNodeID: route.TunnelNodeID,
TunnelTargetAddr: route.TunnelTargetAddr,
TunnelTargetProtocol: route.TunnelTargetProtocol,
CreatedAt: route.CreatedAt,
+5 -38
View File
@@ -1,7 +1,6 @@
package service
import (
"encoding/json"
"fmt"
"log/slog"
"openflare/common"
@@ -162,42 +161,10 @@ type FlaredProxyEntry struct {
CustomDomains []string `json:"custom_domains"`
}
// HeartbeatFlared processes an OpenFlared client heartbeat.
func HeartbeatFlared(tunnel *model.Tunnel, payload FlaredHeartbeatPayload) (*FlaredHeartbeatResponse, error) {
if tunnel == nil {
return nil, fmt.Errorf("tunnel is nil")
}
slog.Debug("flared heartbeat received", "tunnel_id", tunnel.TunnelID)
now := time.Now()
tunnel.ClientVersion = strings.TrimSpace(payload.ClientVersion)
tunnel.FrpVersion = strings.TrimSpace(payload.FrpVersion)
tunnel.Status = "online"
tunnel.LastSeenAt = now
tunnel.CurrentVersion = strings.TrimSpace(payload.CurrentVersion)
tunnel.CurrentChecksum = strings.TrimSpace(payload.CurrentChecksum)
relaysJSON, err := json.Marshal(payload.ConnectedRelays)
if err == nil {
tunnel.ConnectedRelays = string(relaysJSON)
}
if err := tunnel.Update(); err != nil {
return nil, fmt.Errorf("update tunnel heartbeat: %w", err)
}
activeConfig, _ := GetActiveConfigMetaForAgent()
return &FlaredHeartbeatResponse{
ActiveConfig: activeConfig,
TunnelSettings: buildRelaySettings(),
}, nil
}
// GetFlaredTunnelConfig builds the full tunnel routing config for an OpenFlared client.
func GetFlaredTunnelConfig(tunnel *model.Tunnel) (*FlaredTunnelConfigResponse, error) {
if tunnel == nil {
return nil, fmt.Errorf("tunnel is nil")
func GetFlaredTunnelConfig(node *model.Node) (*FlaredTunnelConfigResponse, error) {
if node == nil {
return nil, fmt.Errorf("node is nil")
}
activeVersion, err := model.GetActiveConfigVersion()
@@ -237,7 +204,7 @@ func GetFlaredTunnelConfig(tunnel *model.Tunnel) (*FlaredTunnelConfigResponse, e
// Build proxy entries from routes
proxies := make([]FlaredProxyEntry, 0)
for _, route := range routes {
if route.UpstreamType != "tunnel" || route.TunnelID == nil || *route.TunnelID != tunnel.ID {
if route.UpstreamType != "tunnel" || route.TunnelNodeID == nil || *route.TunnelNodeID != node.ID {
continue
}
if !route.Enabled {
@@ -249,7 +216,7 @@ func GetFlaredTunnelConfig(tunnel *model.Tunnel) (*FlaredTunnelConfigResponse, e
}
localAddr, localPort := parseTunnelTargetAddr(route.TunnelTargetAddr)
proxies = append(proxies, FlaredProxyEntry{
Name: fmt.Sprintf("%s-%s", tunnel.TunnelID, sanitizeProxyName(domains[0])),
Name: fmt.Sprintf("%s-%s", node.NodeID, sanitizeProxyName(domains[0])),
Type: "http",
LocalAddr: localAddr,
LocalPort: localPort,
-183
View File
@@ -1,183 +0,0 @@
package service
import (
"encoding/json"
"errors"
"log/slog"
"openflare/model"
"strings"
"time"
)
type TunnelInput struct {
Name string `json:"name"`
Remark string `json:"remark"`
}
type TunnelView struct {
ID uint `json:"id"`
TunnelID string `json:"tunnel_id"`
Name string `json:"name"`
TunnelToken string `json:"tunnel_token"`
Status string `json:"status"`
ClientVersion string `json:"client_version"`
FrpVersion string `json:"frp_version"`
LastSeenAt time.Time `json:"last_seen_at"`
LastError string `json:"last_error"`
CurrentVersion string `json:"current_version"`
CurrentChecksum string `json:"current_checksum"`
ConnectedRelays []string `json:"connected_relays"`
Remark string `json:"remark"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func ListTunnels() ([]*TunnelView, error) {
tunnels, err := model.ListTunnels()
if err != nil {
return nil, err
}
views := make([]*TunnelView, 0, len(tunnels))
for _, tunnel := range tunnels {
views = append(views, buildTunnelView(tunnel))
}
return views, nil
}
func GetTunnel(id uint) (*TunnelView, error) {
tunnel, err := model.GetTunnelByID(id)
if err != nil {
return nil, err
}
return buildTunnelView(tunnel), nil
}
func CreateTunnel(input TunnelInput) (*TunnelView, error) {
name := strings.TrimSpace(input.Name)
if name == "" {
return nil, errors.New("Tunnel 名称不能为空")
}
tunnelID, err := newTunnelID()
if err != nil {
return nil, err
}
tunnelToken, err := newRandomToken()
if err != nil {
return nil, err
}
tunnel := &model.Tunnel{
TunnelID: tunnelID,
Name: name,
TunnelToken: tunnelToken,
Status: "offline",
Remark: strings.TrimSpace(input.Remark),
}
if err := tunnel.Insert(); err != nil {
if model.IsUniqueConstraintError(err) {
return nil, errors.New("Tunnel 标识生成冲突,请重试")
}
return nil, err
}
slog.Info("tunnel created", "name", tunnel.Name, "tunnel_id", tunnel.TunnelID)
return buildTunnelView(tunnel), nil
}
func UpdateTunnel(id uint, input TunnelInput) (*TunnelView, error) {
tunnel, err := model.GetTunnelByID(id)
if err != nil {
return nil, err
}
name := strings.TrimSpace(input.Name)
if name == "" {
return nil, errors.New("Tunnel 名称不能为空")
}
tunnel.Name = name
tunnel.Remark = strings.TrimSpace(input.Remark)
if err := tunnel.Update(); err != nil {
return nil, err
}
slog.Info("tunnel updated", "name", tunnel.Name, "tunnel_id", tunnel.TunnelID)
return buildTunnelView(tunnel), nil
}
func DeleteTunnel(id uint) error {
tunnel, err := model.GetTunnelByID(id)
if err != nil {
return err
}
slog.Info("tunnel deleted", "name", tunnel.Name, "tunnel_id", tunnel.TunnelID)
return tunnel.Delete()
}
func RotateTunnelToken(id uint) (*TunnelView, error) {
tunnel, err := model.GetTunnelByID(id)
if err != nil {
return nil, err
}
newToken, err := newRandomToken()
if err != nil {
return nil, err
}
tunnel.TunnelToken = newToken
if err := tunnel.Update(); err != nil {
return nil, err
}
slog.Info("tunnel token rotated", "tunnel_id", tunnel.TunnelID)
return buildTunnelView(tunnel), nil
}
func AuthenticateTunnelToken(token string) (*model.Tunnel, error) {
token = strings.TrimSpace(token)
if token == "" {
return nil, errors.New("缺少 Tunnel Token")
}
tunnel, err := model.GetTunnelByTunnelToken(token)
if err != nil {
return nil, errors.New("Tunnel Token 无效")
}
return tunnel, nil
}
func buildTunnelView(tunnel *model.Tunnel) *TunnelView {
if tunnel == nil {
return nil
}
relays := decodeTunnelConnectedRelays(tunnel.ConnectedRelays)
return &TunnelView{
ID: tunnel.ID,
TunnelID: tunnel.TunnelID,
Name: tunnel.Name,
TunnelToken: tunnel.TunnelToken,
Status: tunnel.Status,
ClientVersion: tunnel.ClientVersion,
FrpVersion: tunnel.FrpVersion,
LastSeenAt: tunnel.LastSeenAt,
LastError: tunnel.LastError,
CurrentVersion: tunnel.CurrentVersion,
CurrentChecksum: tunnel.CurrentChecksum,
ConnectedRelays: relays,
Remark: tunnel.Remark,
CreatedAt: tunnel.CreatedAt,
UpdatedAt: tunnel.UpdatedAt,
}
}
func decodeTunnelConnectedRelays(raw string) []string {
raw = strings.TrimSpace(raw)
if raw == "" || raw == "[]" {
return make([]string, 0)
}
var relays []string
if err := json.Unmarshal([]byte(raw), &relays); err != nil {
return make([]string, 0)
}
return relays
}
func newTunnelID() (string, error) {
token, err := newRandomToken()
if err != nil {
return "", err
}
return "tun-" + token, nil
}