[优化] Phase2

This commit is contained in:
ryan
2026-06-01 08:26:45 +08:00
parent 78047d1b38
commit 4196343ad3
26 changed files with 1814 additions and 350 deletions
+1
View File
@@ -34,6 +34,7 @@ func registeredModels() []any {
&ProxyRoute{},
&ConfigVersion{},
&Node{},
&Tunnel{},
&NodeSystemProfile{},
&ApplyLog{},
&NodeMetricSnapshot{},
+82
View File
@@ -0,0 +1,82 @@
// v16 升级内容:新增 Tunnel 隧道表,为 nodes 增加 TunnelRelay 相关字段,为 proxy_routes 增加隧道上游支持字段。
// 背景说明:引入 Tunnel 组件后,边缘节点可通过隧道中继回源,需要区分节点类型(edge_node/relay_node)、记录中继绑定端口,
// 并在代理路由中支持 tunnel 上游类型以替代直连回源,同时需要独立的 tunnels 表管理隧道实例。
package migrate
import (
"fmt"
"gorm.io/gorm"
)
type nodeV16 struct {
NodeType string `gorm:"column:node_type;not null;default:'edge_node'"`
RelayBindPort int `gorm:"column:relay_bind_port"`
}
type tunnelV16 struct{}
type proxyRouteV16 struct {
UpstreamType string `gorm:"column:upstream_type;not null;default:'direct'"`
TunnelID *uint `gorm:"column:tunnel_id"`
}
func init() {
Register(V16())
}
func V16() Migration {
return Migration{
FromVersion: 15,
ToVersion: 16,
Migrate: migrateV16,
Validate: validateV16,
}
}
func (nodeV16) TableName() string {
return "nodes"
}
func (tunnelV16) TableName() string {
return "tunnels"
}
func (proxyRouteV16) TableName() string {
return "proxy_routes"
}
func migrateV16(ctx Context, db *gorm.DB, backend string) error {
if err := ctx.ApplyCurrentSchema(db, backend); err != nil {
return err
}
if err := db.Exec("UPDATE nodes SET node_type = 'edge_node' WHERE node_type = '' OR node_type IS NULL").Error; err != nil {
return fmt.Errorf("backfill nodes.node_type: %w", err)
}
if err := db.Exec("UPDATE proxy_routes SET upstream_type = 'direct' WHERE upstream_type = '' OR upstream_type IS NULL").Error; err != nil {
return fmt.Errorf("backfill proxy_routes.upstream_type: %w", err)
}
return nil
}
func validateV16(ctx Context, db *gorm.DB, backend string) error {
if err := ctx.ValidateDatabaseSchemaVersion(db, backend, 15); err != nil {
return err
}
if db == nil || !db.Migrator().HasTable(&tunnelV16{}) {
return fmt.Errorf("table tunnels is missing")
}
if !db.Migrator().HasColumn(&nodeV16{}, "node_type") {
return fmt.Errorf("column nodes.node_type is missing")
}
if !db.Migrator().HasColumn(&proxyRouteV16{}, "upstream_type") {
return fmt.Errorf("column proxy_routes.upstream_type is missing")
}
if !db.Migrator().HasColumn(&nodeV16{}, "relay_bind_port") {
return fmt.Errorf("column nodes.relay_bind_port is missing")
}
if !db.Migrator().HasColumn(&proxyRouteV16{}, "tunnel_id") {
return fmt.Errorf("column proxy_routes.tunnel_id is missing")
}
return nil
}
+30
View File
@@ -74,6 +74,10 @@ func (databaseSchemaMigrationContext) ValidateDatabaseSchemaVersion(db *gorm.DB,
return validateDatabaseSchemaV13(db, backend)
case 14:
return validateDatabaseSchemaV14(db, backend)
case 15:
return validateDatabaseSchemaV15(db, backend)
case 16:
return validateDatabaseSchemaV16(db, backend)
default:
return fmt.Errorf("database schema validation for v%d is not defined", version)
}
@@ -1140,6 +1144,32 @@ func validateDatabaseSchemaV14(db *gorm.DB, backend string) error {
return nil
}
func validateDatabaseSchemaV15(db *gorm.DB, backend string) error {
if err := validateDatabaseSchemaV14(db, backend); err != nil {
return err
}
if !db.Migrator().HasColumn(&Node{}, "ip_manual_override") {
return fmt.Errorf("column nodes.ip_manual_override is missing")
}
return nil
}
func validateDatabaseSchemaV16(db *gorm.DB, backend string) error {
if err := validateDatabaseSchemaV15(db, backend); err != nil {
return err
}
if !db.Migrator().HasTable(&Tunnel{}) {
return fmt.Errorf("table tunnels is missing")
}
if !db.Migrator().HasColumn(&Node{}, "node_type") {
return fmt.Errorf("column nodes.node_type is missing")
}
if !db.Migrator().HasColumn(&ProxyRoute{}, "upstream_type") {
return fmt.Errorf("column proxy_routes.upstream_type is missing")
}
return nil
}
func databaseSchemaMigrations() []databaseSchemaMigration {
ctx := databaseSchemaMigrationContext{}
migrations := []databaseSchemaMigration{}
+17
View File
@@ -28,6 +28,18 @@ type Node struct {
LastError string `json:"last_error" gorm:"type:text"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Node type: edge_node (default) | tunnel_relay
NodeType string `json:"node_type" gorm:"size:32;not null;default:'edge_node'"`
// TunnelRelay specific fields
RelayBindPort int `json:"relay_bind_port" gorm:"not null;default:0"`
RelayVhostHTTPPort int `json:"relay_vhost_http_port" gorm:"not null;default:0"`
RelayAuthToken string `json:"-" gorm:"size:128"`
RelayAgentAccessAddr string `json:"relay_agent_access_addr" gorm:"size:255"`
RelayClientAccessAddr string `json:"relay_client_access_addr" gorm:"size:255"`
RelayClientProxyURL string `json:"relay_client_proxy_url" gorm:"size:512"`
RelayStatus string `json:"relay_status" gorm:"size:16;not null;default:'unknown'"`
RelayFrpVersion string `json:"relay_frp_version" gorm:"size:64"`
RelayVersion string `json:"relay_version" gorm:"size:64"`
}
func ListNodes() (nodes []*Node, err error) {
@@ -72,3 +84,8 @@ func (node *Node) Update() error {
func (node *Node) Delete() error {
return DB.Delete(node).Error
}
func ListNodesByType(nodeType string) (nodes []*Node, err error) {
err = DB.Where("node_type = ?", nodeType).Order("id desc").Find(&nodes).Error
return nodes, err
}
+63 -55
View File
@@ -3,35 +3,39 @@ package model
import "time"
type ProxyRoute struct {
ID uint `json:"id" gorm:"primaryKey"`
SiteName string `json:"site_name" gorm:"size:255;not null;default:''"`
Domain string `json:"domain" gorm:"uniqueIndex;size:255;not null"`
Domains string `json:"domains" gorm:"type:text;not null;default:'[]'"`
OriginID *uint `json:"origin_id" gorm:"index"`
OriginURL string `json:"origin_url" gorm:"size:2048;not null"`
OriginHost string `json:"origin_host" gorm:"size:255"`
Upstreams string `json:"upstreams" gorm:"type:text;not null;default:'[]'"`
Enabled bool `json:"enabled" gorm:"not null;default:true"`
EnableHTTPS bool `json:"enable_https" gorm:"column:enable_https;not null;default:false"`
CertID *uint `json:"cert_id"`
CertIDs string `json:"cert_ids" gorm:"type:text;not null;default:'[]'"`
DomainCertIDs string `json:"domain_cert_ids" gorm:"type:text;not null;default:'[]'"`
RedirectHTTP bool `json:"redirect_http" gorm:"not null;default:false"`
LimitConnPerServer int `json:"limit_conn_per_server" gorm:"not null;default:0"`
LimitConnPerIP int `json:"limit_conn_per_ip" gorm:"not null;default:0"`
LimitRate string `json:"limit_rate" gorm:"size:32;not null;default:''"`
CacheEnabled bool `json:"cache_enabled" gorm:"not null;default:false"`
CachePolicy string `json:"cache_policy" gorm:"size:32;not null;default:''"`
CacheRules string `json:"cache_rules" gorm:"type:text;not null;default:'[]'"`
CustomHeaders string `json:"custom_headers" gorm:"type:text;not null;default:'[]'"`
PoWEnabled bool `json:"pow_enabled" gorm:"column:pow_enabled;not null;default:false"`
PoWConfig string `json:"pow_config" gorm:"column:pow_config;type:text;not null;default:'{}'"`
BasicAuthEnabled bool `json:"basic_auth_enabled" gorm:"not null;default:false"`
BasicAuthUsername string `json:"basic_auth_username" gorm:"size:255;not null;default:''"`
BasicAuthPassword string `json:"basic_auth_password" gorm:"size:255;not null;default:''"`
Remark string `json:"remark" gorm:"size:255"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID uint `json:"id" gorm:"primaryKey"`
SiteName string `json:"site_name" gorm:"size:255;not null;default:''"`
Domain string `json:"domain" gorm:"uniqueIndex;size:255;not null"`
Domains string `json:"domains" gorm:"type:text;not null;default:'[]'"`
OriginID *uint `json:"origin_id" gorm:"index"`
OriginURL string `json:"origin_url" gorm:"size:2048;not null"`
OriginHost string `json:"origin_host" gorm:"size:255"`
Upstreams string `json:"upstreams" gorm:"type:text;not null;default:'[]'"`
Enabled bool `json:"enabled" gorm:"not null;default:true"`
EnableHTTPS bool `json:"enable_https" gorm:"column:enable_https;not null;default:false"`
CertID *uint `json:"cert_id"`
CertIDs string `json:"cert_ids" gorm:"type:text;not null;default:'[]'"`
DomainCertIDs string `json:"domain_cert_ids" gorm:"type:text;not null;default:'[]'"`
RedirectHTTP bool `json:"redirect_http" gorm:"not null;default:false"`
LimitConnPerServer int `json:"limit_conn_per_server" gorm:"not null;default:0"`
LimitConnPerIP int `json:"limit_conn_per_ip" gorm:"not null;default:0"`
LimitRate string `json:"limit_rate" gorm:"size:32;not null;default:''"`
CacheEnabled bool `json:"cache_enabled" gorm:"not null;default:false"`
CachePolicy string `json:"cache_policy" gorm:"size:32;not null;default:''"`
CacheRules string `json:"cache_rules" gorm:"type:text;not null;default:'[]'"`
CustomHeaders string `json:"custom_headers" gorm:"type:text;not null;default:'[]'"`
PoWEnabled bool `json:"pow_enabled" gorm:"column:pow_enabled;not null;default:false"`
PoWConfig string `json:"pow_config" gorm:"column:pow_config;type:text;not null;default:'{}'"`
BasicAuthEnabled bool `json:"basic_auth_enabled" gorm:"not null;default:false"`
BasicAuthUsername string `json:"basic_auth_username" gorm:"size:255;not null;default:''"`
BasicAuthPassword string `json:"basic_auth_password" gorm:"size:255;not null;default:''"`
Remark string `json:"remark" gorm:"size:255"`
UpstreamType string `json:"upstream_type" gorm:"size:32;not null;default:'direct'"`
TunnelID *uint `json:"tunnel_id" gorm:"index"`
TunnelTargetAddr string `json:"tunnel_target_addr" gorm:"size:512"`
TunnelTargetProtocol string `json:"tunnel_target_protocol" gorm:"size:16"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func ListProxyRoutes() (routes []*ProxyRoute, err error) {
@@ -61,32 +65,36 @@ func (route *ProxyRoute) Insert() error {
func (route *ProxyRoute) Update() error {
return DB.Model(&ProxyRoute{}).Where("id = ?", route.ID).Updates(map[string]any{
"site_name": route.SiteName,
"domain": route.Domain,
"domains": route.Domains,
"origin_id": route.OriginID,
"origin_url": route.OriginURL,
"origin_host": route.OriginHost,
"upstreams": route.Upstreams,
"enabled": route.Enabled,
"enable_https": route.EnableHTTPS,
"cert_id": route.CertID,
"cert_ids": route.CertIDs,
"domain_cert_ids": route.DomainCertIDs,
"redirect_http": route.RedirectHTTP,
"limit_conn_per_server": route.LimitConnPerServer,
"limit_conn_per_ip": route.LimitConnPerIP,
"limit_rate": route.LimitRate,
"cache_enabled": route.CacheEnabled,
"cache_policy": route.CachePolicy,
"cache_rules": route.CacheRules,
"custom_headers": route.CustomHeaders,
"pow_enabled": route.PoWEnabled,
"pow_config": route.PoWConfig,
"basic_auth_enabled": route.BasicAuthEnabled,
"basic_auth_username": route.BasicAuthUsername,
"basic_auth_password": route.BasicAuthPassword,
"remark": route.Remark,
"site_name": route.SiteName,
"domain": route.Domain,
"domains": route.Domains,
"origin_id": route.OriginID,
"origin_url": route.OriginURL,
"origin_host": route.OriginHost,
"upstreams": route.Upstreams,
"enabled": route.Enabled,
"enable_https": route.EnableHTTPS,
"cert_id": route.CertID,
"cert_ids": route.CertIDs,
"domain_cert_ids": route.DomainCertIDs,
"redirect_http": route.RedirectHTTP,
"limit_conn_per_server": route.LimitConnPerServer,
"limit_conn_per_ip": route.LimitConnPerIP,
"limit_rate": route.LimitRate,
"cache_enabled": route.CacheEnabled,
"cache_policy": route.CachePolicy,
"cache_rules": route.CacheRules,
"custom_headers": route.CustomHeaders,
"pow_enabled": route.PoWEnabled,
"pow_config": route.PoWConfig,
"basic_auth_enabled": route.BasicAuthEnabled,
"basic_auth_username": route.BasicAuthUsername,
"basic_auth_password": route.BasicAuthPassword,
"remark": route.Remark,
"upstream_type": route.UpstreamType,
"tunnel_id": route.TunnelID,
"tunnel_target_addr": route.TunnelTargetAddr,
"tunnel_target_protocol": route.TunnelTargetProtocol,
}).Error
}
+56
View File
@@ -0,0 +1,56 @@
package model
import "time"
type Tunnel struct {
ID uint `json:"id" gorm:"primaryKey"`
TunnelID string `json:"tunnel_id" gorm:"uniqueIndex;size:64;not null"`
Name string `json:"name" gorm:"size:128;not null"`
TunnelToken string `json:"-" gorm:"size:128;index"`
Status string `json:"status" gorm:"size:16;not null;default:'offline'"`
ClientVersion string `json:"client_version" gorm:"size:64"`
FrpVersion string `json:"frp_version" gorm:"size:64"`
LastSeenAt time.Time `json:"last_seen_at"`
LastError string `json:"last_error" gorm:"type:text"`
CurrentVersion string `json:"current_version" gorm:"size:32"`
CurrentChecksum string `json:"current_checksum" gorm:"size:64"`
ConnectedRelays string `json:"connected_relays" gorm:"type:text;not null;default:'[]'"`
Remark string `json:"remark" gorm:"size:255"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func ListTunnels() (tunnels []*Tunnel, err error) {
err = DB.Order("id desc").Find(&tunnels).Error
return tunnels, err
}
func GetTunnelByID(id uint) (*Tunnel, error) {
tunnel := &Tunnel{}
err := DB.First(tunnel, id).Error
return tunnel, err
}
func GetTunnelByTunnelID(tunnelID string) (*Tunnel, error) {
tunnel := &Tunnel{}
err := DB.Where("tunnel_id = ?", tunnelID).First(tunnel).Error
return tunnel, err
}
func GetTunnelByTunnelToken(token string) (*Tunnel, error) {
tunnel := &Tunnel{}
err := DB.Where("tunnel_token = ?", token).First(tunnel).Error
return tunnel, err
}
func (tunnel *Tunnel) Insert() error {
return DB.Create(tunnel).Error
}
func (tunnel *Tunnel) Update() error {
return DB.Save(tunnel).Error
}
func (tunnel *Tunnel) Delete() error {
return DB.Delete(tunnel).Error
}