feat(api): migrate server and node data handling, update related structures and logic

This commit is contained in:
Chang lue Tsen
2025-08-26 07:05:59 -04:00
parent 9b3cdbbb4f
commit c7884d94aa
52 changed files with 1079 additions and 458 deletions
@@ -5,6 +5,7 @@ import (
"time"
"github.com/perfect-panel/server/adapter"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
@@ -28,7 +29,10 @@ func NewPreviewSubscribeTemplateLogic(ctx context.Context, svcCtx *svc.ServiceCo
}
func (l *PreviewSubscribeTemplateLogic) PreviewSubscribeTemplate(req *types.PreviewSubscribeTemplateRequest) (resp *types.PreviewSubscribeTemplateResponse, err error) {
servers, err := l.svcCtx.ServerModel.FindAllServer(l.ctx)
_, servers, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{
Page: 1,
Size: 1000,
})
if err != nil {
l.Errorf("[PreviewSubscribeTemplateLogic] FindAllServer error: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindAllServer error: %v", err.Error())
+11
View File
@@ -0,0 +1,11 @@
package server
const (
ShadowSocks = "shadowsocks"
Vmess = "vmess"
Vless = "vless"
Trojan = "trojan"
AnyTLS = "anytls"
Tuic = "tuic"
Hysteria2 = "hysteria2"
)
@@ -28,7 +28,7 @@ func NewFilterNodeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Fi
}
func (l *FilterNodeListLogic) FilterNodeList(req *types.FilterNodeListRequest) (resp *types.FilterNodeListResponse, err error) {
total, data, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterParams{
total, data, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{
Page: req.Page,
Size: req.Size,
Search: req.Search,
@@ -10,6 +10,7 @@ import (
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"github.com/redis/go-redis/v9"
)
type FilterServerListLogic struct {
@@ -68,7 +69,9 @@ func (l *FilterServerListLogic) handlerServerStatus(id int64) types.ServerStatus
var result types.ServerStatus
nodeStatus, err := l.svcCtx.NodeCache.GetNodeStatus(l.ctx, id)
if err != nil {
l.Errorw("[handlerServerStatus] GetNodeStatus Error: ", logger.Field("error", err.Error()), logger.Field("node_id", id))
if !errors.Is(err, redis.Nil) {
l.Errorw("[handlerServerStatus] GetNodeStatus Error: ", logger.Field("error", err.Error()), logger.Field("node_id", id))
}
return result
}
result = types.ServerStatus{
@@ -0,0 +1,52 @@
package server
import (
"context"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/model/server"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type HasMigrateSeverNodeLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewHasMigrateSeverNodeLogic Check if there is any server or node to migrate
func NewHasMigrateSeverNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *HasMigrateSeverNodeLogic {
return &HasMigrateSeverNodeLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *HasMigrateSeverNodeLogic) HasMigrateSeverNode() (resp *types.HasMigrateSeverNodeResponse, err error) {
var oldCount, newCount int64
query := l.svcCtx.DB.WithContext(l.ctx)
err = query.Model(&server.Server{}).Count(&oldCount).Error
if err != nil {
l.Errorw("[HasMigrateSeverNode] Query Old Server Count Error: ", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[HasMigrateSeverNode] Query Old Server Count Error")
}
err = query.Model(&node.Server{}).Count(&newCount).Error
if err != nil {
l.Errorw("[HasMigrateSeverNode] Query New Server Count Error: ", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[HasMigrateSeverNode] Query New Server Count Error")
}
var shouldMigrate bool
if oldCount != 0 && newCount == 0 {
shouldMigrate = true
}
return &types.HasMigrateSeverNodeResponse{
HasMigrate: shouldMigrate,
}, nil
}
@@ -0,0 +1,330 @@
package server
import (
"context"
"encoding/json"
"fmt"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/model/server"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
)
type MigrateServerNodeLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewMigrateServerNodeLogic Migrate server and node data to new database
func NewMigrateServerNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *MigrateServerNodeLogic {
return &MigrateServerNodeLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *MigrateServerNodeLogic) MigrateServerNode() (resp *types.MigrateServerNodeResponse, err error) {
tx := l.svcCtx.DB.WithContext(l.ctx).Begin()
var oldServers []*server.Server
var newServers []*node.Server
var newNodes []*node.Node
err = tx.Model(&server.Server{}).Find(&oldServers).Error
if err != nil {
l.Errorw("[MigrateServerNode] Query Old Server List Error: ", logger.Field("error", err.Error()))
return &types.MigrateServerNodeResponse{
Succee: 0,
Fail: 0,
Message: fmt.Sprintf("Query Old Server List Error: %s", err.Error()),
}, nil
}
for _, oldServer := range oldServers {
data, err := l.adapterServer(oldServer)
if err != nil {
l.Errorw("[MigrateServerNode] Adapter Server Error: ", logger.Field("error", err.Error()))
if resp == nil {
resp = &types.MigrateServerNodeResponse{}
}
resp.Fail++
if resp.Message == "" {
resp.Message = fmt.Sprintf("Adapter Server Error: %s", err.Error())
} else {
resp.Message = fmt.Sprintf("%s; Adapter Server Error: %s", resp.Message, err.Error())
}
continue
}
newServers = append(newServers, data)
newNode, err := l.adapterNode(oldServer)
if err != nil {
l.Errorw("[MigrateServerNode] Adapter Node Error: ", logger.Field("error", err.Error()))
if resp == nil {
resp = &types.MigrateServerNodeResponse{}
}
resp.Fail++
if resp.Message == "" {
resp.Message = fmt.Sprintf("Adapter Node Error: %s", err.Error())
} else {
resp.Message = fmt.Sprintf("%s; Adapter Node Error: %s", resp.Message, err.Error())
}
continue
}
for _, item := range newNode {
if item.Port == 0 {
protocols, _ := data.UnmarshalProtocols()
if len(protocols) > 0 {
item.Port = protocols[0].Port
}
}
newNodes = append(newNodes, item)
}
}
if len(newServers) > 0 {
err = tx.Model(&node.Server{}).CreateInBatches(newServers, 20).Error
if err != nil {
tx.Rollback()
l.Errorw("[MigrateServerNode] Insert New Server List Error: ", logger.Field("error", err.Error()))
return &types.MigrateServerNodeResponse{
Succee: 0,
Fail: uint64(len(newServers)),
Message: fmt.Sprintf("Insert New Server List Error: %s", err.Error()),
}, nil
}
}
if len(newNodes) > 0 {
err = tx.Model(&node.Node{}).CreateInBatches(newNodes, 20).Error
if err != nil {
tx.Rollback()
l.Errorw("[MigrateServerNode] Insert New Node List Error: ", logger.Field("error", err.Error()))
return &types.MigrateServerNodeResponse{
Succee: uint64(len(newServers)),
Fail: uint64(len(newNodes)),
Message: fmt.Sprintf("Insert New Node List Error: %s", err.Error()),
}, nil
}
}
tx.Commit()
return &types.MigrateServerNodeResponse{
Succee: uint64(len(newServers)),
Fail: 0,
Message: fmt.Sprintf("Migrate Success: %d servers and %d nodes", len(newServers), len(newNodes)),
}, nil
}
func (l *MigrateServerNodeLogic) adapterServer(info *server.Server) (*node.Server, error) {
result := &node.Server{
Name: info.Name,
Country: info.Country,
City: info.City,
Ratio: info.TrafficRatio,
Address: info.ServerAddr,
Sort: int(info.Sort),
Protocols: "",
}
var protocols []node.Protocol
switch info.Protocol {
case ShadowSocks:
var src server.Shadowsocks
err := json.Unmarshal([]byte(info.Config), &src)
if err != nil {
return nil, err
}
protocols = append(protocols, node.Protocol{
Type: "shadowsocks",
Cipher: src.Method,
Port: uint16(src.Port),
ServerKey: src.ServerKey,
})
case Vmess:
var src server.Vmess
err := json.Unmarshal([]byte(info.Config), &src)
if err != nil {
return nil, err
}
protocol := node.Protocol{
Type: "vmess",
Port: uint16(src.Port),
Security: src.Security,
SNI: src.SecurityConfig.SNI,
AllowInsecure: src.SecurityConfig.AllowInsecure,
Fingerprint: src.SecurityConfig.Fingerprint,
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
RealityServerPort: src.SecurityConfig.RealityServerPort,
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
RealityShortId: src.SecurityConfig.RealityShortId,
Transport: src.Transport,
Host: src.TransportConfig.Host,
Path: src.TransportConfig.Path,
ServiceName: src.TransportConfig.ServiceName,
Flow: src.Flow,
}
protocols = append(protocols, protocol)
protocols = append(protocols, protocol)
case Vless:
var src server.Vless
err := json.Unmarshal([]byte(info.Config), &src)
if err != nil {
return nil, err
}
protocol := node.Protocol{
Type: "vless",
Port: uint16(src.Port),
Security: src.Security,
SNI: src.SecurityConfig.SNI,
AllowInsecure: src.SecurityConfig.AllowInsecure,
Fingerprint: src.SecurityConfig.Fingerprint,
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
RealityServerPort: src.SecurityConfig.RealityServerPort,
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
RealityShortId: src.SecurityConfig.RealityShortId,
Transport: src.Transport,
Host: src.TransportConfig.Host,
Path: src.TransportConfig.Path,
ServiceName: src.TransportConfig.ServiceName,
Flow: src.Flow,
}
protocols = append(protocols, protocol)
case Trojan:
var src server.Trojan
err := json.Unmarshal([]byte(info.Config), &src)
if err != nil {
return nil, err
}
protocol := node.Protocol{
Type: "trojan",
Port: uint16(src.Port),
Security: src.Security,
SNI: src.SecurityConfig.SNI,
AllowInsecure: src.SecurityConfig.AllowInsecure,
Fingerprint: src.SecurityConfig.Fingerprint,
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
RealityServerPort: src.SecurityConfig.RealityServerPort,
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
RealityShortId: src.SecurityConfig.RealityShortId,
Transport: src.Transport,
Host: src.TransportConfig.Host,
Path: src.TransportConfig.Path,
ServiceName: src.TransportConfig.ServiceName,
Flow: src.Flow,
}
protocols = append(protocols, protocol)
case Hysteria2:
var src server.Hysteria2
err := json.Unmarshal([]byte(info.Config), &src)
if err != nil {
return nil, err
}
protocol := node.Protocol{
Type: "hysteria2",
Port: uint16(src.Port),
HopPorts: src.HopPorts,
HopInterval: src.HopInterval,
ObfsPassword: src.ObfsPassword,
SNI: src.SecurityConfig.SNI,
AllowInsecure: src.SecurityConfig.AllowInsecure,
Fingerprint: src.SecurityConfig.Fingerprint,
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
RealityServerPort: src.SecurityConfig.RealityServerPort,
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
RealityShortId: src.SecurityConfig.RealityShortId,
}
protocols = append(protocols, protocol)
case Tuic:
var src server.Tuic
err := json.Unmarshal([]byte(info.Config), &src)
if err != nil {
return nil, err
}
protocol := node.Protocol{
Type: "tuic",
Port: uint16(src.Port),
DisableSNI: src.DisableSNI,
ReduceRtt: src.ReduceRtt,
UDPRelayMode: src.UDPRelayMode,
CongestionController: src.CongestionController,
SNI: src.SecurityConfig.SNI,
AllowInsecure: src.SecurityConfig.AllowInsecure,
Fingerprint: src.SecurityConfig.Fingerprint,
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
RealityServerPort: src.SecurityConfig.RealityServerPort,
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
RealityShortId: src.SecurityConfig.RealityShortId,
}
protocols = append(protocols, protocol)
case AnyTLS:
var src server.AnyTLS
err := json.Unmarshal([]byte(info.Config), &src)
if err != nil {
return nil, err
}
protocol := node.Protocol{
Type: "anytls",
Port: uint16(src.Port),
SNI: src.SecurityConfig.SNI,
AllowInsecure: src.SecurityConfig.AllowInsecure,
Fingerprint: src.SecurityConfig.Fingerprint,
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
RealityServerPort: src.SecurityConfig.RealityServerPort,
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
RealityShortId: src.SecurityConfig.RealityShortId,
}
protocols = append(protocols, protocol)
}
if len(protocols) > 0 {
err := result.MarshalProtocols(protocols)
if err != nil {
return nil, err
}
}
return result, nil
}
func (l *MigrateServerNodeLogic) adapterNode(info *server.Server) ([]*node.Node, error) {
var nodes []*node.Node
enable := true
switch info.RelayMode {
case server.RelayModeNone:
nodes = append(nodes, &node.Node{
Name: info.Name,
Tags: "",
Port: 0,
Address: info.ServerAddr,
ServerId: info.Id,
Protocol: info.Protocol,
Enabled: &enable,
})
default:
var relays []server.NodeRelay
err := json.Unmarshal([]byte(info.RelayNode), &relays)
if err != nil {
return nil, err
}
for _, relay := range relays {
nodes = append(nodes, &node.Node{
Name: relay.Prefix + info.Name,
Tags: "",
Port: uint16(relay.Port),
Address: relay.Host,
ServerId: info.Id,
Protocol: info.Protocol,
Enabled: &enable,
})
}
}
return nodes, nil
}
@@ -48,8 +48,8 @@ func (l *CreateSubscribeLogic) CreateSubscribe(req *types.CreateSubscribeRequest
DeviceLimit: req.DeviceLimit,
Quota: req.Quota,
GroupId: req.GroupId,
ServerGroup: tool.Int64SliceToString(req.ServerGroup),
Server: tool.Int64SliceToString(req.Server),
Nodes: tool.Int64SliceToString(req.Nodes),
NodeTags: tool.StringSliceToString(req.NodeTags),
Show: req.Show,
Sell: req.Sell,
Sort: 0,
@@ -3,6 +3,7 @@ package subscribe
import (
"context"
"encoding/json"
"strings"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
@@ -41,7 +42,7 @@ func (l *GetSubscribeDetailsLogic) GetSubscribeDetails(req *types.GetSubscribeDe
l.Logger.Error("[GetSubscribeDetailsLogic] JSON unmarshal failed: ", logger.Field("error", err.Error()), logger.Field("discount", sub.Discount))
}
}
resp.Server = tool.StringToInt64Slice(sub.Server)
resp.ServerGroup = tool.StringToInt64Slice(sub.ServerGroup)
resp.Nodes = tool.StringToInt64Slice(sub.Nodes)
resp.NodeTags = strings.Split(sub.NodeTags, ",")
return resp, nil
}
@@ -3,6 +3,7 @@ package subscribe
import (
"context"
"encoding/json"
"strings"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
@@ -47,8 +48,8 @@ func (l *GetSubscribeListLogic) GetSubscribeList(req *types.GetSubscribeListRequ
l.Logger.Error("[GetSubscribeListLogic] JSON unmarshal failed: ", logger.Field("error", err.Error()), logger.Field("discount", item.Discount))
}
}
sub.Server = tool.StringToInt64Slice(item.Server)
sub.ServerGroup = tool.StringToInt64Slice(item.ServerGroup)
sub.Nodes = tool.StringToInt64Slice(item.Nodes)
sub.NodeTags = strings.Split(item.NodeTags, ",")
resultList = append(resultList, sub)
}
@@ -56,8 +56,8 @@ func (l *UpdateSubscribeLogic) UpdateSubscribe(req *types.UpdateSubscribeRequest
DeviceLimit: req.DeviceLimit,
Quota: req.Quota,
GroupId: req.GroupId,
ServerGroup: tool.Int64SliceToString(req.ServerGroup),
Server: tool.Int64SliceToString(req.Server),
Nodes: tool.Int64SliceToString(req.Nodes),
NodeTags: tool.StringSliceToString(req.NodeTags),
Show: req.Show,
Sell: req.Sell,
Sort: req.Sort,