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

View File

@ -1,26 +1,25 @@
package adapter package adapter
import ( import (
"encoding/json" "strings"
"github.com/perfect-panel/server/internal/model/server" "github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/random"
) )
type Adapter struct { type Adapter struct {
SiteName string // 站点名称 SiteName string // 站点名称
Servers []*server.Server // 服务器列表 Servers []*node.Node // 服务器列表
UserInfo User // 用户信息 UserInfo User // 用户信息
ClientTemplate string // 客户端配置模板 ClientTemplate string // 客户端配置模板
OutputFormat string // 输出格式,默认是 base64 OutputFormat string // 输出格式,默认是 base64
SubscribeName string // 订阅名称 SubscribeName string // 订阅名称
} }
type Option func(*Adapter) type Option func(*Adapter)
// WithServers 设置服务器列表 // WithServers 设置服务器列表
func WithServers(servers []*server.Server) Option { func WithServers(servers []*node.Node) Option {
return func(opts *Adapter) { return func(opts *Adapter) {
opts.Servers = servers opts.Servers = servers
} }
@ -56,7 +55,7 @@ func WithSubscribeName(name string) Option {
func NewAdapter(tpl string, opts ...Option) *Adapter { func NewAdapter(tpl string, opts ...Option) *Adapter {
adapter := &Adapter{ adapter := &Adapter{
Servers: []*server.Server{}, Servers: []*node.Node{},
UserInfo: User{}, UserInfo: User{},
ClientTemplate: tpl, ClientTemplate: tpl,
OutputFormat: "base64", // 默认输出格式 OutputFormat: "base64", // 默认输出格式
@ -87,51 +86,54 @@ func (adapter *Adapter) Client() (*Client, error) {
return client, nil return client, nil
} }
func (adapter *Adapter) Proxies(servers []*server.Server) ([]Proxy, error) { func (adapter *Adapter) Proxies(servers []*node.Node) ([]Proxy, error) {
var proxies []Proxy var proxies []Proxy
for _, srv := range servers {
switch srv.RelayMode {
case server.RelayModeAll:
var relays []server.NodeRelay
if err := json.Unmarshal([]byte(srv.RelayNode), &relays); err != nil {
logger.Errorw("Unmarshal RelayNode", logger.Field("error", err.Error()), logger.Field("node", srv.Name), logger.Field("relayNode", srv.RelayNode))
continue
}
for _, relay := range relays {
proxy, err := adapterProxy(*srv, relay.Host, uint64(relay.Port))
if err != nil {
logger.Errorw("Adapter Proxy", logger.Field("error", err.Error()), logger.Field("node", srv.Name), logger.Field("relayNode", relay))
continue
}
proxies = append(proxies, proxy)
}
case server.RelayModeRandom: for _, item := range servers {
var relays []server.NodeRelay if item.Server == nil {
if err := json.Unmarshal([]byte(srv.RelayNode), &relays); err != nil { logger.Errorf("[Adapter] Server is nil for node ID: %d", item.Id)
logger.Errorw("Unmarshal RelayNode", logger.Field("error", err.Error()), logger.Field("node", srv.Name), logger.Field("relayNode", srv.RelayNode)) continue
continue }
} protocols, err := item.Server.UnmarshalProtocols()
randNum := random.RandomInRange(0, len(relays)-1) if err != nil {
relay := relays[randNum] logger.Errorf("[Adapter] Unmarshal Protocols error: %s; server id : %d", err.Error(), item.ServerId)
proxy, err := adapterProxy(*srv, relay.Host, uint64(relay.Port)) continue
if err != nil { }
logger.Errorw("Adapter Proxy", logger.Field("error", err.Error()), logger.Field("node", srv.Name), logger.Field("relayNode", relay)) for _, protocol := range protocols {
continue if protocol.Type == item.Protocol {
} proxies = append(proxies, Proxy{
proxies = append(proxies, proxy) Name: item.Name,
Server: item.Address,
case server.RelayModeNone: Port: item.Port,
proxy, err := adapterProxy(*srv, srv.ServerAddr, 0) Type: item.Protocol,
if err != nil { Tags: strings.Split(item.Tags, ","),
logger.Errorw("Adapter Proxy", logger.Field("error", err.Error()), logger.Field("node", srv.Name), logger.Field("serverAddr", srv.ServerAddr)) Security: protocol.Security,
continue SNI: protocol.SNI,
} AllowInsecure: protocol.AllowInsecure,
proxies = append(proxies, proxy) Fingerprint: protocol.Fingerprint,
default: RealityServerAddr: protocol.RealityServerAddr,
logger.Errorw("Unknown RelayMode", logger.Field("node", srv.Name), logger.Field("relayMode", srv.RelayMode)) RealityServerPort: protocol.RealityServerPort,
RealityPrivateKey: protocol.RealityPrivateKey,
RealityPublicKey: protocol.RealityPublicKey,
RealityShortId: protocol.RealityShortId,
Transport: protocol.Transport,
Host: protocol.Host,
Path: protocol.Path,
ServiceName: protocol.ServiceName,
Method: protocol.Cipher,
ServerKey: protocol.ServerKey,
Flow: protocol.Flow,
HopPorts: protocol.HopPorts,
HopInterval: protocol.HopInterval,
ObfsPassword: protocol.ObfsPassword,
DisableSNI: protocol.DisableSNI,
ReduceRtt: protocol.ReduceRtt,
UDPRelayMode: protocol.UDPRelayMode,
CongestionController: protocol.CongestionController,
})
}
} }
} }
return proxies, nil return proxies, nil
} }

View File

@ -13,7 +13,7 @@ import (
type Proxy struct { type Proxy struct {
Name string Name string
Server string Server string
Port uint64 Port uint16
Type string Type string
Tags []string Tags []string

View File

@ -1,113 +1 @@
package adapter package adapter
import (
"encoding/json"
"fmt"
"strings"
"github.com/perfect-panel/server/internal/model/server"
"github.com/perfect-panel/server/pkg/tool"
)
func adapterProxy(svr server.Server, host string, port uint64) (Proxy, error) {
tags := strings.Split(svr.Tags, ",")
if len(tags) > 0 {
tags = tool.RemoveDuplicateElements(tags...)
}
node := Proxy{
Name: svr.Name,
Host: host,
Port: port,
Type: svr.Protocol,
Tags: tags,
}
switch svr.Protocol {
case "shadowsocks":
var ss server.Shadowsocks
if err := json.Unmarshal([]byte(svr.Config), &ss); err != nil {
return node, fmt.Errorf("unmarshal shadowsocks config: %v", err.Error())
}
if port == 0 {
node.Port = uint64(ss.Port)
}
node.Method = ss.Method
node.ServerKey = ss.ServerKey
case "vless":
var vless server.Vless
if err := json.Unmarshal([]byte(svr.Config), &vless); err != nil {
return node, fmt.Errorf("unmarshal vless config: %v", err.Error())
}
if port == 0 {
node.Port = uint64(vless.Port)
}
node.Flow = vless.Flow
node.Transport = vless.Transport
tool.DeepCopy(&node, vless.TransportConfig)
node.Security = vless.Security
tool.DeepCopy(&node, vless.SecurityConfig)
case "vmess":
var vmess server.Vmess
if err := json.Unmarshal([]byte(svr.Config), &vmess); err != nil {
return node, fmt.Errorf("unmarshal vmess config: %v", err.Error())
}
if port == 0 {
node.Port = uint64(vmess.Port)
}
node.Flow = vmess.Flow
node.Transport = vmess.Transport
tool.DeepCopy(&node, vmess.TransportConfig)
node.Security = vmess.Security
tool.DeepCopy(&node, vmess.SecurityConfig)
case "trojan":
var trojan server.Trojan
if err := json.Unmarshal([]byte(svr.Config), &trojan); err != nil {
return node, fmt.Errorf("unmarshal trojan config: %v", err.Error())
}
if port == 0 {
node.Port = uint64(trojan.Port)
}
node.Flow = trojan.Flow
node.Transport = trojan.Transport
tool.DeepCopy(&node, trojan.TransportConfig)
node.Security = trojan.Security
tool.DeepCopy(&node, trojan.SecurityConfig)
case "hysteria2":
var hysteria2 server.Hysteria2
if err := json.Unmarshal([]byte(svr.Config), &hysteria2); err != nil {
return node, fmt.Errorf("unmarshal hysteria2 config: %v", err.Error())
}
if port == 0 {
node.Port = uint64(hysteria2.Port)
}
node.HopPorts = hysteria2.HopPorts
node.HopInterval = hysteria2.HopInterval
node.ObfsPassword = hysteria2.ObfsPassword
tool.DeepCopy(&node, hysteria2.SecurityConfig)
case "tuic":
var tuic server.Tuic
if err := json.Unmarshal([]byte(svr.Config), &tuic); err != nil {
return node, fmt.Errorf("unmarshal tuic config: %v", err.Error())
}
if port == 0 {
node.Port = uint64(tuic.Port)
}
node.DisableSNI = tuic.DisableSNI
node.ReduceRtt = tuic.ReduceRtt
node.UDPRelayMode = tuic.UDPRelayMode
node.CongestionController = tuic.CongestionController
case "anytls":
var anytls server.AnyTLS
if err := json.Unmarshal([]byte(svr.Config), &anytls); err != nil {
return node, fmt.Errorf("unmarshal anytls config: %v", err.Error())
}
if port == 0 {
node.Port = uint64(anytls.Port)
}
tool.DeepCopy(&node, anytls.SecurityConfig)
default:
return node, fmt.Errorf("unsupported protocol: %s", svr.Protocol)
}
return node, nil
}

View File

@ -150,6 +150,14 @@ type (
Total int64 `json:"total"` Total int64 `json:"total"`
List []Node `json:"list"` List []Node `json:"list"`
} }
HasMigrateSeverNodeResponse {
HasMigrate bool `json:"has_migrate"`
}
MigrateServerNodeResponse {
Succee uint64 `json:"succee"`
Fail uint64 `json:"fail"`
Message string `json:"message,omitempty"`
}
) )
@server ( @server (
@ -197,5 +205,13 @@ service ppanel {
@doc "Toggle Node Status" @doc "Toggle Node Status"
@handler ToggleNodeStatus @handler ToggleNodeStatus
post /node/status/toggle (ToggleNodeStatusRequest) post /node/status/toggle (ToggleNodeStatusRequest)
@doc "Check if there is any server or node to migrate"
@handler HasMigrateSeverNode
get /migrate/has returns (HasMigrateSeverNodeResponse)
@doc "Migrate server and node data to new database"
@handler MigrateServerNode
post /migrate/run returns (MigrateServerNodeResponse)
} }

View File

@ -46,8 +46,8 @@ type (
DeviceLimit int64 `json:"device_limit"` DeviceLimit int64 `json:"device_limit"`
Quota int64 `json:"quota"` Quota int64 `json:"quota"`
GroupId int64 `json:"group_id"` GroupId int64 `json:"group_id"`
ServerGroup []int64 `json:"server_group"` Nodes []int64 `json:"nodes"`
Server []int64 `json:"server"` NodeTags []string `json:"node_tags"`
Show *bool `json:"show"` Show *bool `json:"show"`
Sell *bool `json:"sell"` Sell *bool `json:"sell"`
DeductionRatio int64 `json:"deduction_ratio"` DeductionRatio int64 `json:"deduction_ratio"`
@ -69,8 +69,8 @@ type (
DeviceLimit int64 `json:"device_limit"` DeviceLimit int64 `json:"device_limit"`
Quota int64 `json:"quota"` Quota int64 `json:"quota"`
GroupId int64 `json:"group_id"` GroupId int64 `json:"group_id"`
ServerGroup []int64 `json:"server_group"` Nodes []int64 `json:"nodes"`
Server []int64 `json:"server"` NodeTags []string `json:"node_tags"`
Show *bool `json:"show"` Show *bool `json:"show"`
Sell *bool `json:"sell"` Sell *bool `json:"sell"`
Sort int64 `json:"sort"` Sort int64 `json:"sort"`

View File

@ -194,8 +194,8 @@ type (
DeviceLimit int64 `json:"device_limit"` DeviceLimit int64 `json:"device_limit"`
Quota int64 `json:"quota"` Quota int64 `json:"quota"`
GroupId int64 `json:"group_id"` GroupId int64 `json:"group_id"`
ServerGroup []int64 `json:"server_group"` Nodes []int64 `json:"nodes"`
Server []int64 `json:"server"` NodeTags []string `json:"node_tags"`
Show bool `json:"show"` Show bool `json:"show"`
Sell bool `json:"sell"` Sell bool `json:"sell"`
Sort int64 `json:"sort"` Sort int64 `json:"sort"`

View File

@ -1 +1,2 @@
DROP TABLE IF EXISTS `nodes`;
DROP TABLE IF EXISTS `servers`; DROP TABLE IF EXISTS `servers`;

View File

@ -12,3 +12,17 @@ CREATE TABLE IF NOT EXISTS `servers` (
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `nodes` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Node Name',
`tags` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Tags',
`port` smallint unsigned NOT NULL DEFAULT '0' COMMENT 'Connect Port',
`address` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Connect Address',
`server_id` bigint NOT NULL DEFAULT '0' COMMENT 'Server ID',
`protocol` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Protocol',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Enabled',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

View File

@ -0,0 +1,5 @@
ALTER TABLE `subscribe`
DROP COLUMN `nodes`,
DROP COLUMN `node_tags`,
ADD COLUMN `server` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Server',
ADD COLUMN `server_group` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Server Group';

View File

@ -0,0 +1,7 @@
ALTER TABLE `subscribe`
ADD COLUMN `nodes` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Node IDs',
ADD COLUMN `node_tags` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Node Tags',
DROP COLUMN `server`,
DROP COLUMN `server_group`;
DROP TABLE IF EXISTS `server_rule_group`;

View File

@ -3,7 +3,10 @@ package migrate
import ( import (
"testing" "testing"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/pkg/orm" "github.com/perfect-panel/server/pkg/orm"
"gorm.io/driver/mysql"
"gorm.io/gorm"
) )
func getDSN() string { func getDSN() string {
@ -30,3 +33,17 @@ func TestMigrate(t *testing.T) {
t.Log("migrate success") t.Log("migrate success")
} }
} }
func TestMysql(t *testing.T) {
db, err := gorm.Open(mysql.New(mysql.Config{
DSN: "root:mylove520@tcp(localhost:3306)/vpnboard",
}))
if err != nil {
t.Fatalf("Failed to connect to MySQL: %v", err)
}
err = db.Migrator().AutoMigrate(&node.Node{})
if err != nil {
t.Fatalf("Failed to auto migrate: %v", err)
return
}
t.Log("MySQL connection and migration successful")
}

View File

@ -18,7 +18,7 @@ func TrafficDataToRedis(svcCtx *svc.ServiceContext) {
} }
var nodeCacheData []cache.NodeTodayTrafficRank var nodeCacheData []cache.NodeTodayTrafficRank
for _, node := range nodeData { for _, node := range nodeData {
serverInfo, err := svcCtx.ServerModel.FindOne(ctx, node.ServerId) serverInfo, err := svcCtx.NodeModel.FindOneServer(ctx, node.ServerId)
if err != nil { if err != nil {
logger.Errorw("查询节点信息失败", logger.Field("error", err.Error())) logger.Errorw("查询节点信息失败", logger.Field("error", err.Error()))
continue continue

View File

@ -0,0 +1,18 @@
package server
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/server"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/result"
)
// Check if there is any server or node to migrate
func HasMigrateSeverNodeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
l := server.NewHasMigrateSeverNodeLogic(c.Request.Context(), svcCtx)
resp, err := l.HasMigrateSeverNode()
result.HttpResult(c, resp, err)
}
}

View File

@ -0,0 +1,18 @@
package server
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/server"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/result"
)
// Migrate server and node data to new database
func MigrateServerNodeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
l := server.NewMigrateServerNodeLogic(c.Request.Context(), svcCtx)
resp, err := l.MigrateServerNode()
result.HttpResult(c, resp, err)
}
}

View File

@ -296,6 +296,12 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
// Filter Server List // Filter Server List
adminServerGroupRouter.GET("/list", adminServer.FilterServerListHandler(serverCtx)) adminServerGroupRouter.GET("/list", adminServer.FilterServerListHandler(serverCtx))
// Check if there is any server or node to migrate
adminServerGroupRouter.GET("/migrate/has", adminServer.HasMigrateSeverNodeHandler(serverCtx))
// Migrate server and node data to new database
adminServerGroupRouter.POST("/migrate/run", adminServer.MigrateServerNodeHandler(serverCtx))
// Create Node // Create Node
adminServerGroupRouter.POST("/node/create", adminServer.CreateNodeHandler(serverCtx)) adminServerGroupRouter.POST("/node/create", adminServer.CreateNodeHandler(serverCtx))

View File

@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/perfect-panel/server/adapter" "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/svc"
"github.com/perfect-panel/server/internal/types" "github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger" "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) { 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 { if err != nil {
l.Errorf("[PreviewSubscribeTemplateLogic] FindAllServer error: %v", err.Error()) l.Errorf("[PreviewSubscribeTemplateLogic] FindAllServer error: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindAllServer error: %v", err.Error()) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindAllServer error: %v", err.Error())

View File

@ -0,0 +1,11 @@
package server
const (
ShadowSocks = "shadowsocks"
Vmess = "vmess"
Vless = "vless"
Trojan = "trojan"
AnyTLS = "anytls"
Tuic = "tuic"
Hysteria2 = "hysteria2"
)

View File

@ -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) { 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, Page: req.Page,
Size: req.Size, Size: req.Size,
Search: req.Search, Search: req.Search,

View File

@ -10,6 +10,7 @@ import (
"github.com/perfect-panel/server/pkg/tool" "github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr" "github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/redis/go-redis/v9"
) )
type FilterServerListLogic struct { type FilterServerListLogic struct {
@ -68,7 +69,9 @@ func (l *FilterServerListLogic) handlerServerStatus(id int64) types.ServerStatus
var result types.ServerStatus var result types.ServerStatus
nodeStatus, err := l.svcCtx.NodeCache.GetNodeStatus(l.ctx, id) nodeStatus, err := l.svcCtx.NodeCache.GetNodeStatus(l.ctx, id)
if err != nil { 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 return result
} }
result = types.ServerStatus{ result = types.ServerStatus{

View File

@ -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
}

View File

@ -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
}

View File

@ -48,8 +48,8 @@ func (l *CreateSubscribeLogic) CreateSubscribe(req *types.CreateSubscribeRequest
DeviceLimit: req.DeviceLimit, DeviceLimit: req.DeviceLimit,
Quota: req.Quota, Quota: req.Quota,
GroupId: req.GroupId, GroupId: req.GroupId,
ServerGroup: tool.Int64SliceToString(req.ServerGroup), Nodes: tool.Int64SliceToString(req.Nodes),
Server: tool.Int64SliceToString(req.Server), NodeTags: tool.StringSliceToString(req.NodeTags),
Show: req.Show, Show: req.Show,
Sell: req.Sell, Sell: req.Sell,
Sort: 0, Sort: 0,

View File

@ -3,6 +3,7 @@ package subscribe
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"strings"
"github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types" "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)) l.Logger.Error("[GetSubscribeDetailsLogic] JSON unmarshal failed: ", logger.Field("error", err.Error()), logger.Field("discount", sub.Discount))
} }
} }
resp.Server = tool.StringToInt64Slice(sub.Server) resp.Nodes = tool.StringToInt64Slice(sub.Nodes)
resp.ServerGroup = tool.StringToInt64Slice(sub.ServerGroup) resp.NodeTags = strings.Split(sub.NodeTags, ",")
return resp, nil return resp, nil
} }

View File

@ -3,6 +3,7 @@ package subscribe
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"strings"
"github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types" "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)) l.Logger.Error("[GetSubscribeListLogic] JSON unmarshal failed: ", logger.Field("error", err.Error()), logger.Field("discount", item.Discount))
} }
} }
sub.Server = tool.StringToInt64Slice(item.Server) sub.Nodes = tool.StringToInt64Slice(item.Nodes)
sub.ServerGroup = tool.StringToInt64Slice(item.ServerGroup) sub.NodeTags = strings.Split(item.NodeTags, ",")
resultList = append(resultList, sub) resultList = append(resultList, sub)
} }

View File

@ -56,8 +56,8 @@ func (l *UpdateSubscribeLogic) UpdateSubscribe(req *types.UpdateSubscribeRequest
DeviceLimit: req.DeviceLimit, DeviceLimit: req.DeviceLimit,
Quota: req.Quota, Quota: req.Quota,
GroupId: req.GroupId, GroupId: req.GroupId,
ServerGroup: tool.Int64SliceToString(req.ServerGroup), Nodes: tool.Int64SliceToString(req.Nodes),
Server: tool.Int64SliceToString(req.Server), NodeTags: tool.StringSliceToString(req.NodeTags),
Show: req.Show, Show: req.Show,
Sell: req.Sell, Sell: req.Sell,
Sort: req.Sort, Sort: req.Sort,

View File

@ -51,7 +51,7 @@ func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (res
Success: loginStatus, Success: loginStatus,
} }
content, _ := loginLog.Marshal() content, _ := loginLog.Marshal()
if err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{ if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
Id: 0, Id: 0,
Type: log.TypeLogin.Uint8(), Type: log.TypeLogin.Uint8(),
Date: time.Now().Format("2006-01-02"), Date: time.Now().Format("2006-01-02"),

View File

@ -59,7 +59,7 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r
Success: loginStatus, Success: loginStatus,
} }
content, _ := loginLog.Marshal() content, _ := loginLog.Marshal()
if err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{ if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
Id: 0, Id: 0,
Type: log.TypeLogin.Uint8(), Type: log.TypeLogin.Uint8(),
Date: time.Now().Format("2006-01-02"), Date: time.Now().Format("2006-01-02"),

View File

@ -110,7 +110,7 @@ func (l *TelephoneResetPasswordLogic) TelephoneResetPassword(req *types.Telephon
Success: token != "", Success: token != "",
} }
content, _ := loginLog.Marshal() content, _ := loginLog.Marshal()
if err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{ if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
Id: 0, Id: 0,
Type: log.TypeLogin.Uint8(), Type: log.TypeLogin.Uint8(),
Date: time.Now().Format("2006-01-02"), Date: time.Now().Format("2006-01-02"),

View File

@ -165,7 +165,7 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR
Success: token != "", Success: token != "",
} }
content, _ := loginLog.Marshal() content, _ := loginLog.Marshal()
if err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{ if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
Id: 0, Id: 0,
Type: log.TypeLogin.Uint8(), Type: log.TypeLogin.Uint8(),
Date: time.Now().Format("2006-01-02"), Date: time.Now().Format("2006-01-02"),
@ -188,7 +188,7 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR
RegisterTime: time.Now().UnixMilli(), RegisterTime: time.Now().UnixMilli(),
} }
content, _ = registerLog.Marshal() content, _ = registerLog.Marshal()
if err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{ if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
Type: log.TypeRegister.Uint8(), Type: log.TypeRegister.Uint8(),
ObjectID: userInfo.Id, ObjectID: userInfo.Id,
Date: time.Now().Format("2006-01-02"), Date: time.Now().Format("2006-01-02"),

View File

@ -49,7 +49,7 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log
Success: loginStatus, Success: loginStatus,
} }
content, _ := loginLog.Marshal() content, _ := loginLog.Marshal()
if err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{ if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
Type: log.TypeLogin.Uint8(), Type: log.TypeLogin.Uint8(),
Date: time.Now().Format("2006-01-02"), Date: time.Now().Format("2006-01-02"),
ObjectID: userInfo.Id, ObjectID: userInfo.Id,

View File

@ -153,7 +153,7 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *
Success: loginStatus, Success: loginStatus,
} }
content, _ := loginLog.Marshal() content, _ := loginLog.Marshal()
if err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{ if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
Id: 0, Id: 0,
Type: log.TypeLogin.Uint8(), Type: log.TypeLogin.Uint8(),
Date: time.Now().Format("2006-01-02"), Date: time.Now().Format("2006-01-02"),

View File

@ -1,3 +1,83 @@
package server package server
const Unchanged = "Unchanged" const (
Unchanged = "Unchanged"
ShadowSocks = "shadowsocks"
Vmess = "vmess"
Vless = "vless"
Trojan = "trojan"
AnyTLS = "anytls"
Tuic = "tuic"
Hysteria2 = "hysteria2"
)
type SecurityConfig struct {
SNI string `json:"sni"`
AllowInsecure *bool `json:"allow_insecure"`
Fingerprint string `json:"fingerprint"`
RealityServerAddress string `json:"reality_server_addr"`
RealityServerPort int `json:"reality_server_port"`
RealityPrivateKey string `json:"reality_private_key"`
RealityPublicKey string `json:"reality_public_key"`
RealityShortId string `json:"reality_short_id"`
RealityMldsa65seed string `json:"reality_mldsa65seed"`
}
type TransportConfig struct {
Path string `json:"path"`
Host string `json:"host"`
ServiceName string `json:"service_name"`
DisableSNI bool `json:"disable_sni"`
ReduceRtt bool `json:"reduce_rtt"`
UDPRelayMode string `json:"udp_relay_mode"`
CongestionController string `json:"congestion_controller"`
}
type VlessNode struct {
Port uint16 `json:"port"`
Flow string `json:"flow"`
Network string `json:"transport"`
TransportConfig *TransportConfig `json:"transport_config"`
Security string `json:"security"`
SecurityConfig *SecurityConfig `json:"security_config"`
}
type VmessNode struct {
Port uint16 `json:"port"`
Network string `json:"transport"`
TransportConfig *TransportConfig `json:"transport_config"`
Security string `json:"security"`
SecurityConfig *SecurityConfig `json:"security_config"`
}
type ShadowsocksNode struct {
Port uint16 `json:"port"`
Cipher string `json:"method"`
ServerKey string `json:"server_key"`
}
type TrojanNode struct {
Port uint16 `json:"port"`
Network string `json:"transport"`
TransportConfig *TransportConfig `json:"transport_config"`
Security string `json:"security"`
SecurityConfig *SecurityConfig `json:"security_config"`
}
type AnyTLSNode struct {
Port uint16 `json:"port"`
SecurityConfig *SecurityConfig `json:"security_config"`
}
type TuicNode struct {
Port uint16 `json:"port"`
SecurityConfig *SecurityConfig `json:"security_config"`
}
type Hysteria2Node struct {
Port uint16 `json:"port"`
HopPorts string `json:"hop_ports"`
HopInterval int `json:"hop_interval"`
ObfsPassword string `json:"obfs_password"`
SecurityConfig *SecurityConfig `json:"security_config"`
}

View File

@ -1,11 +1,11 @@
package server package server
import ( import (
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/config" "github.com/perfect-panel/server/internal/config"
"github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/svc"
@ -51,21 +51,21 @@ func (l *GetServerConfigLogic) GetServerConfig(req *types.GetServerConfigRequest
return resp, nil return resp, nil
} }
} }
nodeInfo, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.ServerId) data, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerId)
if err != nil { if err != nil {
l.Errorw("[GetServerConfig] FindOne error", logger.Field("error", err.Error())) l.Errorw("[GetServerConfig] FindOne error", logger.Field("error", err.Error()))
return nil, err return nil, err
} }
cfg := make(map[string]interface{})
err = json.Unmarshal([]byte(nodeInfo.Config), &cfg) protocols, err := data.UnmarshalProtocols()
if err != nil { if err != nil {
l.Errorw("[GetServerConfig] json unmarshal error", logger.Field("error", err.Error()))
return nil, err return nil, err
} }
var cfg map[string]interface{}
if nodeInfo.Protocol == "shadowsocks" { for _, protocol := range protocols {
if value, ok := cfg["server_key"]; ok && value != "" { if protocol.Type == req.Protocol {
cfg["server_key"] = base64.StdEncoding.EncodeToString([]byte(value.(string))) cfg = l.compatible(protocol)
break
} }
} }
@ -74,18 +74,162 @@ func (l *GetServerConfigLogic) GetServerConfig(req *types.GetServerConfigRequest
PullInterval: l.svcCtx.Config.Node.NodePullInterval, PullInterval: l.svcCtx.Config.Node.NodePullInterval,
PushInterval: l.svcCtx.Config.Node.NodePushInterval, PushInterval: l.svcCtx.Config.Node.NodePushInterval,
}, },
Protocol: nodeInfo.Protocol, Protocol: req.Protocol,
Config: cfg, Config: cfg,
} }
data, err := json.Marshal(resp) c, err := json.Marshal(resp)
if err != nil { if err != nil {
l.Errorw("[GetServerConfig] json marshal error", logger.Field("error", err.Error())) l.Errorw("[GetServerConfig] json marshal error", logger.Field("error", err.Error()))
return nil, err return nil, err
} }
etag := tool.GenerateETag(data) etag := tool.GenerateETag(c)
l.ctx.Header("ETag", etag) l.ctx.Header("ETag", etag)
if err = l.svcCtx.Redis.Set(l.ctx, cacheKey, data, -1).Err(); err != nil { if err = l.svcCtx.Redis.Set(l.ctx, cacheKey, c, -1).Err(); err != nil {
l.Errorw("[GetServerConfig] redis set error", logger.Field("error", err.Error())) l.Errorw("[GetServerConfig] redis set error", logger.Field("error", err.Error()))
} }
// Check If-None-Match header
match := l.ctx.GetHeader("If-None-Match")
if match == etag {
return nil, xerr.StatusNotModified
}
return resp, nil return resp, nil
} }
func (l *GetServerConfigLogic) compatible(config node.Protocol) map[string]interface{} {
var result interface{}
switch config.Type {
case ShadowSocks:
result = ShadowsocksNode{
Port: config.Port,
Cipher: config.Cipher,
ServerKey: config.ServerKey,
}
case Vless:
result = VlessNode{
Port: config.Port,
Flow: config.Flow,
Network: config.Transport,
TransportConfig: &TransportConfig{
Path: config.Path,
Host: config.Host,
ServiceName: config.ServiceName,
DisableSNI: config.DisableSNI,
ReduceRtt: config.ReduceRtt,
UDPRelayMode: config.UDPRelayMode,
CongestionController: config.CongestionController,
},
Security: config.Security,
SecurityConfig: &SecurityConfig{
SNI: config.SNI,
AllowInsecure: &config.AllowInsecure,
Fingerprint: config.Fingerprint,
RealityServerAddress: config.RealityServerAddr,
RealityServerPort: config.RealityServerPort,
RealityPrivateKey: config.RealityPrivateKey,
RealityPublicKey: config.RealityPublicKey,
RealityShortId: config.RealityShortId,
},
}
case Vmess:
result = VmessNode{
Port: config.Port,
Network: config.Transport,
TransportConfig: &TransportConfig{
Path: config.Path,
Host: config.Host,
ServiceName: config.ServiceName,
DisableSNI: config.DisableSNI,
ReduceRtt: config.ReduceRtt,
UDPRelayMode: config.UDPRelayMode,
CongestionController: config.CongestionController,
},
Security: config.Security,
SecurityConfig: &SecurityConfig{
SNI: config.SNI,
AllowInsecure: &config.AllowInsecure,
Fingerprint: config.Fingerprint,
RealityServerAddress: config.RealityServerAddr,
RealityServerPort: config.RealityServerPort,
RealityPrivateKey: config.RealityPrivateKey,
RealityPublicKey: config.RealityPublicKey,
RealityShortId: config.RealityShortId,
},
}
case Trojan:
result = TrojanNode{
Port: config.Port,
Network: config.Transport,
TransportConfig: &TransportConfig{
Path: config.Path,
Host: config.Host,
ServiceName: config.ServiceName,
DisableSNI: config.DisableSNI,
ReduceRtt: config.ReduceRtt,
UDPRelayMode: config.UDPRelayMode,
CongestionController: config.CongestionController,
},
Security: config.Security,
SecurityConfig: &SecurityConfig{
SNI: config.SNI,
AllowInsecure: &config.AllowInsecure,
Fingerprint: config.Fingerprint,
RealityServerAddress: config.RealityServerAddr,
RealityServerPort: config.RealityServerPort,
RealityPrivateKey: config.RealityPrivateKey,
RealityPublicKey: config.RealityPublicKey,
RealityShortId: config.RealityShortId,
},
}
case AnyTLS:
result = AnyTLSNode{
Port: config.Port,
SecurityConfig: &SecurityConfig{
SNI: config.SNI,
AllowInsecure: &config.AllowInsecure,
Fingerprint: config.Fingerprint,
RealityServerAddress: config.RealityServerAddr,
RealityServerPort: config.RealityServerPort,
RealityPrivateKey: config.RealityPrivateKey,
RealityPublicKey: config.RealityPublicKey,
RealityShortId: config.RealityShortId,
},
}
case Tuic:
result = TuicNode{
Port: config.Port,
SecurityConfig: &SecurityConfig{
SNI: config.SNI,
AllowInsecure: &config.AllowInsecure,
Fingerprint: config.Fingerprint,
RealityServerAddress: config.RealityServerAddr,
RealityServerPort: config.RealityServerPort,
RealityPrivateKey: config.RealityPrivateKey,
RealityPublicKey: config.RealityPublicKey,
RealityShortId: config.RealityShortId,
},
}
case Hysteria2:
result = Hysteria2Node{
Port: config.Port,
HopPorts: config.HopPorts,
HopInterval: config.HopInterval,
ObfsPassword: config.ObfsPassword,
SecurityConfig: &SecurityConfig{
SNI: config.SNI,
AllowInsecure: &config.AllowInsecure,
Fingerprint: config.Fingerprint,
RealityServerAddress: config.RealityServerAddr,
RealityServerPort: config.RealityServerPort,
RealityPrivateKey: config.RealityPrivateKey,
RealityPublicKey: config.RealityPublicKey,
RealityShortId: config.RealityShortId,
},
}
}
var resp map[string]interface{}
s, _ := json.Marshal(result)
_ = json.Unmarshal(s, &resp)
return resp
}

View File

@ -3,8 +3,10 @@ package server
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/config" "github.com/perfect-panel/server/internal/config"
"github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/svc"
@ -33,28 +35,46 @@ func NewGetServerUserListLogic(ctx *gin.Context, svcCtx *svc.ServiceContext) *Ge
func (l *GetServerUserListLogic) GetServerUserList(req *types.GetServerUserListRequest) (resp *types.GetServerUserListResponse, err error) { func (l *GetServerUserListLogic) GetServerUserList(req *types.GetServerUserListRequest) (resp *types.GetServerUserListResponse, err error) {
cacheKey := fmt.Sprintf("%s%d", config.ServerUserListCacheKey, req.ServerId) cacheKey := fmt.Sprintf("%s%d", config.ServerUserListCacheKey, req.ServerId)
cache, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result() cache, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result()
if err == nil { if cache != "" {
if cache != "" { etag := tool.GenerateETag([]byte(cache))
etag := tool.GenerateETag([]byte(cache)) resp = &types.GetServerUserListResponse{}
resp := &types.GetServerUserListResponse{} // Check If-None-Match header
// Check If-None-Match header if match := l.ctx.GetHeader("If-None-Match"); match == etag {
if match := l.ctx.GetHeader("If-None-Match"); match == etag { return nil, xerr.StatusNotModified
return nil, xerr.StatusNotModified
}
l.ctx.Header("ETag", etag)
err = json.Unmarshal([]byte(cache), resp)
if err != nil {
l.Errorw("[ServerUserListCacheKey] json unmarshal error", logger.Field("error", err.Error()))
return nil, err
}
return resp, nil
} }
l.ctx.Header("ETag", etag)
err = json.Unmarshal([]byte(cache), resp)
if err != nil {
l.Errorw("[ServerUserListCacheKey] json unmarshal error", logger.Field("error", err.Error()))
return nil, err
}
return resp, nil
} }
server, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.ServerId) server, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
subs, err := l.svcCtx.SubscribeModel.QuerySubscribeIdsByServerIdAndServerGroupId(l.ctx, server.Id, server.GroupId)
_, nodes, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{
Page: 1,
Size: 1000,
ServerId: []int64{server.Id},
Protocol: req.Protocol,
})
if err != nil {
l.Errorw("FilterNodeList error", logger.Field("error", err.Error()))
return nil, err
}
var nodeTag []string
var nodeIds []int64
for _, n := range nodes {
nodeIds = append(nodeIds, n.Id)
if n.Tags != "" {
nodeTag = append(nodeTag, strings.Split(n.Tags, ",")...)
}
}
subs, err := l.svcCtx.SubscribeModel.QuerySubscribeIdsByNodeIdAndNodeTag(l.ctx, nodeIds, nodeTag)
if err != nil { if err != nil {
l.Errorw("QuerySubscribeIdsByServerIdAndServerGroupId error", logger.Field("error", err.Error())) l.Errorw("QuerySubscribeIdsByServerIdAndServerGroupId error", logger.Field("error", err.Error()))
return nil, err return nil, err
@ -76,16 +96,10 @@ func (l *GetServerUserListLogic) GetServerUserList(req *types.GetServerUserListR
return nil, err return nil, err
} }
for _, datum := range data { for _, datum := range data {
speedLimit := server.SpeedLimit
if (int(sub.SpeedLimit) < server.SpeedLimit && sub.SpeedLimit != 0) ||
(int(sub.SpeedLimit) > server.SpeedLimit && sub.SpeedLimit == 0) {
speedLimit = int(sub.SpeedLimit)
}
users = append(users, types.ServerUser{ users = append(users, types.ServerUser{
Id: datum.Id, Id: datum.Id,
UUID: datum.UUID, UUID: datum.UUID,
SpeedLimit: int64(speedLimit), SpeedLimit: sub.SpeedLimit,
DeviceLimit: sub.DeviceLimit, DeviceLimit: sub.DeviceLimit,
}) })
} }
@ -106,5 +120,9 @@ func (l *GetServerUserListLogic) GetServerUserList(req *types.GetServerUserListR
if err != nil { if err != nil {
l.Errorw("[ServerUserListCacheKey] redis set error", logger.Field("error", err.Error())) l.Errorw("[ServerUserListCacheKey] redis set error", logger.Field("error", err.Error()))
} }
// Check If-None-Match header
if match := l.ctx.GetHeader("If-None-Match"); match == etag {
return nil, xerr.StatusNotModified
}
return resp, nil return resp, nil
} }

View File

@ -40,7 +40,7 @@ func (l *PushOnlineUsersLogic) PushOnlineUsers(req *types.OnlineUsersRequest) er
} }
// Find server info // Find server info
_, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.ServerId) _, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerId)
if err != nil { if err != nil {
l.Errorw("[PushOnlineUsers] FindOne error", logger.Field("error", err)) l.Errorw("[PushOnlineUsers] FindOne error", logger.Field("error", err))
return fmt.Errorf("server not found: %w", err) return fmt.Errorf("server not found: %w", err)

View File

@ -27,7 +27,7 @@ func NewServerPushStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *
func (l *ServerPushStatusLogic) ServerPushStatus(req *types.ServerPushStatusRequest) error { func (l *ServerPushStatusLogic) ServerPushStatus(req *types.ServerPushStatusRequest) error {
// Find server info // Find server info
serverInfo, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.ServerId) serverInfo, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerId)
if err != nil || serverInfo.Id <= 0 { if err != nil || serverInfo.Id <= 0 {
l.Errorw("[PushOnlineUsers] FindOne error", logger.Field("error", err)) l.Errorw("[PushOnlineUsers] FindOne error", logger.Field("error", err))
return errors.New("server not found") return errors.New("server not found")

View File

@ -32,7 +32,7 @@ func NewServerPushUserTrafficLogic(ctx context.Context, svcCtx *svc.ServiceConte
func (l *ServerPushUserTrafficLogic) ServerPushUserTraffic(req *types.ServerPushUserTrafficRequest) error { func (l *ServerPushUserTrafficLogic) ServerPushUserTraffic(req *types.ServerPushUserTrafficRequest) error {
// Find server info // Find server info
serverInfo, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.ServerId) serverInfo, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerId)
if err != nil { if err != nil {
l.Errorw("[PushOnlineUsers] FindOne error", logger.Field("error", err)) l.Errorw("[PushOnlineUsers] FindOne error", logger.Field("error", err))
return errors.New("server not found") return errors.New("server not found")

View File

@ -9,7 +9,7 @@ import (
"github.com/perfect-panel/server/adapter" "github.com/perfect-panel/server/adapter"
"github.com/perfect-panel/server/internal/model/client" "github.com/perfect-panel/server/internal/model/client"
"github.com/perfect-panel/server/internal/model/log" "github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/model/server" "github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/model/user" "github.com/perfect-panel/server/internal/model/user"
@ -196,7 +196,7 @@ func (l *SubscribeLogic) logSubscribeActivity(subscribeStatus bool, userSub *use
} }
} }
func (l *SubscribeLogic) getServers(userSub *user.Subscribe) ([]*server.Server, error) { func (l *SubscribeLogic) getServers(userSub *user.Subscribe) ([]*node.Node, error) {
if l.isSubscriptionExpired(userSub) { if l.isSubscriptionExpired(userSub) {
return l.createExpiredServers(), nil return l.createExpiredServers(), nil
} }
@ -207,49 +207,61 @@ func (l *SubscribeLogic) getServers(userSub *user.Subscribe) ([]*server.Server,
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe details error: %v", err.Error()) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe details error: %v", err.Error())
} }
serverIds := tool.StringToInt64Slice(subDetails.Server) nodeIds := tool.StringToInt64Slice(subDetails.Nodes)
groupIds := tool.StringToInt64Slice(subDetails.ServerGroup) tags := strings.Split(subDetails.NodeTags, ",")
l.Debugf("[Generate Subscribe]serverIds: %v, groupIds: %v", serverIds, groupIds) l.Debugf("[Generate Subscribe]nodes: %v, NodeTags: %v", nodeIds, tags)
servers, err := l.svc.ServerModel.FindServerDetailByGroupIdsAndIds(l.ctx.Request.Context(), groupIds, serverIds) _, nodes, err := l.svc.NodeModel.FilterNodeList(l.ctx.Request.Context(), &node.FilterNodeParams{
Page: 1,
Size: 1000,
ServerId: nodeIds,
Tag: tags,
Preload: true,
})
l.Debugf("[Query Subscribe]found servers: %v", len(servers)) l.Debugf("[Query Subscribe]found servers: %v", len(nodes))
if err != nil { if err != nil {
l.Errorw("[Generate Subscribe]find server details error: %v", logger.Field("error", err.Error())) l.Errorw("[Generate Subscribe]find server details error: %v", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find server details error: %v", err.Error()) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find server details error: %v", err.Error())
} }
logger.Debugf("[Generate Subscribe]found servers: %v", len(servers)) logger.Debugf("[Generate Subscribe]found servers: %v", len(nodes))
return servers, nil return nodes, nil
} }
func (l *SubscribeLogic) isSubscriptionExpired(userSub *user.Subscribe) bool { func (l *SubscribeLogic) isSubscriptionExpired(userSub *user.Subscribe) bool {
return userSub.ExpireTime.Unix() < time.Now().Unix() && userSub.ExpireTime.Unix() != 0 return userSub.ExpireTime.Unix() < time.Now().Unix() && userSub.ExpireTime.Unix() != 0
} }
func (l *SubscribeLogic) createExpiredServers() []*server.Server { func (l *SubscribeLogic) createExpiredServers() []*node.Node {
enable := true enable := true
host := l.getFirstHostLine() host := l.getFirstHostLine()
return []*server.Server{ return []*node.Node{
{ {
Name: "Subscribe Expired", Name: "Subscribe Expired",
ServerAddr: "127.0.0.1", Tags: "",
RelayMode: "none", Port: 18080,
Protocol: "shadowsocks", Address: "127.0.0.1",
Config: "{\"method\":\"aes-256-gcm\",\"port\":1}", Server: &node.Server{
Enable: &enable, Name: "Subscribe Expired",
Sort: 0, Protocols: "[{\"type:\"\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]",
},
Protocol: "shadowsocks",
Enabled: &enable,
}, },
{ {
Name: host, Name: host,
ServerAddr: "127.0.0.1", Tags: "",
RelayMode: "none", Port: 18080,
Protocol: "shadowsocks", Address: "127.0.0.1",
Config: "{\"method\":\"aes-256-gcm\",\"port\":1}", Server: &node.Server{
Enable: &enable, Name: "Subscribe Expired",
Sort: 0, Protocols: "[{\"type:\"\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]",
},
Protocol: "shadowsocks",
Enabled: &enable,
}, },
} }
} }

View File

@ -3,6 +3,7 @@ package node
import ( import (
"context" "context"
"github.com/redis/go-redis/v9"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -35,19 +36,21 @@ type (
} }
defaultServerModel struct { defaultServerModel struct {
*gorm.DB *gorm.DB
Cache *redis.Client
} }
) )
func newServerModel(db *gorm.DB) *defaultServerModel { func newServerModel(db *gorm.DB, cache *redis.Client) *defaultServerModel {
return &defaultServerModel{ return &defaultServerModel{
DB: db, DB: db,
Cache: cache,
} }
} }
// NewModel returns a model for the database table. // NewModel returns a model for the database table.
func NewModel(conn *gorm.DB) Model { func NewModel(conn *gorm.DB, cache *redis.Client) Model {
return &customServerModel{ return &customServerModel{
defaultServerModel: newServerModel(conn), defaultServerModel: newServerModel(conn, cache),
} }
} }

View File

@ -1,12 +1,26 @@
package node package node
import "context" import (
"context"
"fmt"
"github.com/perfect-panel/server/pkg/tool"
)
type customServerLogicModel interface { type customServerLogicModel interface {
FilterServerList(ctx context.Context, params *FilterParams) (int64, []*Server, error) FilterServerList(ctx context.Context, params *FilterParams) (int64, []*Server, error)
FilterNodeList(ctx context.Context, params *FilterParams) (int64, []*Node, error) FilterNodeList(ctx context.Context, params *FilterNodeParams) (int64, []*Node, error)
ClearNodeCache(ctx context.Context, params *FilterNodeParams) error
} }
const (
// ServerUserListCacheKey Server User List Cache Key
ServerUserListCacheKey = "server:user_list:id:"
// ServerConfigCacheKey Server Config Cache Key
ServerConfigCacheKey = "server:config:id:"
)
// FilterParams Filter Server Params // FilterParams Filter Server Params
type FilterParams struct { type FilterParams struct {
Page int Page int
@ -14,6 +28,16 @@ type FilterParams struct {
Search string Search string
} }
type FilterNodeParams struct {
Page int // Page Number
Size int // Page Size
ServerId []int64 // Server IDs
Tag []string // Tags
Search string // Search Address or Name
Protocol string // Protocol
Preload bool // Preload Server
}
// FilterServerList Filter Server List // FilterServerList Filter Server List
func (m *customServerModel) FilterServerList(ctx context.Context, params *FilterParams) (int64, []*Server, error) { func (m *customServerModel) FilterServerList(ctx context.Context, params *FilterParams) (int64, []*Server, error) {
var servers []*Server var servers []*Server
@ -29,17 +53,18 @@ func (m *customServerModel) FilterServerList(ctx context.Context, params *Filter
s := "%" + params.Search + "%" s := "%" + params.Search + "%"
query = query.Where("`name` LIKE ? OR `address` LIKE ?", s, s) query = query.Where("`name` LIKE ? OR `address` LIKE ?", s, s)
} }
err := query.Count(&total).Limit(params.Size).Offset((params.Page - 1) * params.Size).Find(&servers).Error err := query.Count(&total).Limit(params.Size).Offset((params.Page - 1) * params.Size).Find(&servers).Error
return total, servers, err return total, servers, err
} }
// FilterNodeList Filter Node List // FilterNodeList Filter Node List
func (m *customServerModel) FilterNodeList(ctx context.Context, params *FilterParams) (int64, []*Node, error) { func (m *customServerModel) FilterNodeList(ctx context.Context, params *FilterNodeParams) (int64, []*Node, error) {
var nodes []*Node var nodes []*Node
var total int64 var total int64
query := m.WithContext(ctx).Model(&Node{}) query := m.WithContext(ctx).Model(&Node{})
if params == nil { if params == nil {
params = &FilterParams{ params = &FilterNodeParams{
Page: 1, Page: 1,
Size: 10, Size: 10,
} }
@ -48,6 +73,40 @@ func (m *customServerModel) FilterNodeList(ctx context.Context, params *FilterPa
s := "%" + params.Search + "%" s := "%" + params.Search + "%"
query = query.Where("`name` LIKE ? OR `address` LIKE ? OR `tags` LIKE ? OR `port` LIKE ? ", s, s, s, s) query = query.Where("`name` LIKE ? OR `address` LIKE ? OR `tags` LIKE ? OR `port` LIKE ? ", s, s, s, s)
} }
if len(params.ServerId) > 0 {
query = query.Where("server_id IN ?", params.ServerId)
}
if len(params.Tag) > 0 {
for _, tag := range params.Tag {
query = query.Or("FIND_IN_SET(?,tags)", tag)
}
}
if params.Protocol != "" {
query = query.Where("protocol = ?", params.Protocol)
}
if params.Preload {
query = query.Preload("Server")
}
err := query.Count(&total).Limit(params.Size).Offset((params.Page - 1) * params.Size).Find(&nodes).Error err := query.Count(&total).Limit(params.Size).Offset((params.Page - 1) * params.Size).Find(&nodes).Error
return total, nodes, err return total, nodes, err
} }
// ClearNodeCache Clear Node Cache
func (m *customServerModel) ClearNodeCache(ctx context.Context, params *FilterNodeParams) error {
_, nodes, err := m.FilterNodeList(ctx, params)
if err != nil {
return err
}
var cacheKeys []string
for _, node := range nodes {
cacheKeys = append(cacheKeys, fmt.Sprintf("%s%d", ServerUserListCacheKey, node.ServerId), fmt.Sprintf("%s%d", ServerConfigCacheKey, node.ServerId))
}
if len(cacheKeys) > 0 {
cacheKeys = tool.RemoveDuplicateElements(cacheKeys...)
return m.Cache.Del(ctx, cacheKeys...).Err()
}
return nil
}

View File

@ -9,6 +9,7 @@ type Node struct {
Port uint16 `gorm:"not null;default:0;comment:Connect Port"` Port uint16 `gorm:"not null;default:0;comment:Connect Port"`
Address string `gorm:"type:varchar(255);not null;default:'';comment:Connect Address"` Address string `gorm:"type:varchar(255);not null;default:'';comment:Connect Address"`
ServerId int64 `gorm:"not null;default:0;comment:Server ID"` ServerId int64 `gorm:"not null;default:0;comment:Server ID"`
Server *Server `gorm:"foreignKey:ServerId;references:Id"`
Protocol string `gorm:"type:varchar(100);not null;default:'';comment:Protocol"` Protocol string `gorm:"type:varchar(100);not null;default:'';comment:Protocol"`
Enabled *bool `gorm:"type:boolean;not null;default:true;comment:Enabled"` Enabled *bool `gorm:"type:boolean;not null;default:true;comment:Enabled"`
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`

View File

@ -8,17 +8,17 @@ import (
) )
type Server struct { type Server struct {
Id int64 `gorm:"primary_key"` Id int64 `gorm:"primary_key"`
Name string `gorm:"type:varchar(100);not null;default:'';comment:Server Name"` Name string `gorm:"type:varchar(100);not null;default:'';comment:Server Name"`
Country string `gorm:"type:varchar(128);not null;default:'';comment:Country"` Country string `gorm:"type:varchar(128);not null;default:'';comment:Country"`
City string `gorm:"type:varchar(128);not null;default:'';comment:City"` City string `gorm:"type:varchar(128);not null;default:'';comment:City"`
Ratio float32 `gorm:"type:DECIMAL(4,2);not null;default:0;comment:Traffic Ratio"` Ratio float32 `gorm:"type:DECIMAL(4,2);not null;default:0;comment:Traffic Ratio"`
Address string `gorm:"type:varchar(100);not null;default:'';comment:Server Address"` Address string `gorm:"type:varchar(100);not null;default:'';comment:Server Address"`
Sort int `gorm:"type:int;not null;default:0;comment:Sort"` Sort int `gorm:"type:int;not null;default:0;comment:Sort"`
Protocols string `gorm:"type:text;default:null;comment:Protocol"` Protocols string `gorm:"type:text;default:null;comment:Protocol"`
LastReportedAt time.Time `gorm:"comment:Last Reported Time"` LastReportedAt *time.Time `gorm:"comment:Last Reported Time"`
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
UpdatedAt time.Time `gorm:"comment:Update Time"` UpdatedAt time.Time `gorm:"comment:Update Time"`
} }
func (*Server) TableName() string { func (*Server) TableName() string {

View File

@ -4,11 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"strconv"
"strings"
"github.com/perfect-panel/server/internal/config"
"github.com/perfect-panel/server/internal/model/server"
"github.com/perfect-panel/server/pkg/cache" "github.com/perfect-panel/server/pkg/cache"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
"gorm.io/gorm" "gorm.io/gorm"
@ -61,42 +57,7 @@ func (m *defaultSubscribeModel) getCacheKeys(data *Subscribe) []string {
if data == nil { if data == nil {
return []string{} return []string{}
} }
SubscribeIdKey := fmt.Sprintf("%s%v", cacheSubscribeIdPrefix, data.Id) return []string{fmt.Sprintf("%s%v", cacheSubscribeIdPrefix, data.Id)}
serverKey := make([]string, 0)
if data.Server != "" {
cacheKey := strings.Split(data.Server, ",")
for _, v := range cacheKey {
if v != "" {
serverKey = append(serverKey, fmt.Sprintf("%s%v", config.ServerUserListCacheKey, v))
}
}
}
// Temporary solution waiting for refactoring
if data.ServerGroup != "" {
cacheKey := strings.Split(data.ServerGroup, ",")
groupIds := make([]int64, 0)
for _, v := range cacheKey {
if v != "" {
id, _ := strconv.ParseInt(v, 10, 64)
if id > 0 {
groupIds = append(groupIds, id)
}
}
}
var ids []int64
_ = m.Transaction(context.Background(), func(tx *gorm.DB) error {
return tx.Model(&server.Server{}).Where("group_id IN ?", groupIds).Pluck("id", &ids).Error
})
for _, id := range ids {
serverKey = append(serverKey, fmt.Sprintf("%s%v", config.ServerUserListCacheKey, id))
}
}
cacheKeys := []string{SubscribeIdKey}
if len(serverKey) > 0 {
cacheKeys = append(cacheKeys, serverKey...)
}
return cacheKeys
} }
func (m *defaultSubscribeModel) Insert(ctx context.Context, data *Subscribe, tx ...*gorm.DB) error { func (m *defaultSubscribeModel) Insert(ctx context.Context, data *Subscribe, tx ...*gorm.DB) error {

View File

@ -7,32 +7,11 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
// type Details struct {
// Id int64 `gorm:"primaryKey"`
// Name string `gorm:"type:varchar(255);not null;default:'';comment:Subscribe Name"`
// Description string `gorm:"type:text;comment:Subscribe Description"`
// UnitPrice int64 `gorm:"type:int;not null;default:0;comment:Unit Price"`
// UnitTime string `gorm:"type:varchar(255);not null;default:'';comment:Unit Time"`
// Discount string `gorm:"type:text;comment:Discount"`
// Replacement int64 `gorm:"type:int;not null;default:0;comment:Replacement"`
// Inventory int64 `gorm:"type:int;not null;default:0;comment:Inventory"`
// Traffic int64 `gorm:"type:int;not null;default:0;comment:Traffic"`
// SpeedLimit int64 `gorm:"type:int;not null;default:0;comment:Speed Limit"`
// DeviceLimit int64 `gorm:"type:int;not null;default:0;comment:Device Limit"`
// GroupId int64 `gorm:"type:bigint;comment:Group Id"`
// Quota int64 `gorm:"type:int;not null;default:0;comment:Quota"`
// Show *bool `gorm:"type:tinyint(1);not null;default:0;comment:Show"`
// Sell *bool `gorm:"type:tinyint(1);not null;default:0;comment:Sell"`
// DeductionRatio int64 `gorm:"type:int;default:0;comment:Deduction Ratio"`
// PurchaseWithDiscount bool `gorm:"type:tinyint(1);default:0;comment:PurchaseWithDiscount"`
// ResetCycle int64 `gorm:"type:int;default:0;comment:Reset Cycle"`
// RenewalReset bool `gorm:"type:tinyint(1);default:0;comment:Renew Reset"`
// }
type customSubscribeLogicModel interface { type customSubscribeLogicModel interface {
QuerySubscribeListByPage(ctx context.Context, page, size int, group int64, search string) (total int64, list []*Subscribe, err error) QuerySubscribeListByPage(ctx context.Context, page, size int, group int64, search string) (total int64, list []*Subscribe, err error)
QuerySubscribeList(ctx context.Context) ([]*Subscribe, error) QuerySubscribeList(ctx context.Context) ([]*Subscribe, error)
QuerySubscribeListByShow(ctx context.Context) ([]*Subscribe, error) QuerySubscribeListByShow(ctx context.Context) ([]*Subscribe, error)
QuerySubscribeIdsByServerIdAndServerGroupId(ctx context.Context, serverId, serverGroupId int64) ([]*Subscribe, error) QuerySubscribeIdsByNodeIdAndNodeTag(ctx context.Context, node []int64, tags []string) ([]*Subscribe, error)
QuerySubscribeMinSortByIds(ctx context.Context, ids []int64) (int64, error) QuerySubscribeMinSortByIds(ctx context.Context, ids []int64) (int64, error)
QuerySubscribeListByIds(ctx context.Context, ids []int64) ([]*Subscribe, error) QuerySubscribeListByIds(ctx context.Context, ids []int64) ([]*Subscribe, error)
ClearCache(ctx context.Context, id ...int64) error ClearCache(ctx context.Context, id ...int64) error
@ -75,10 +54,24 @@ func (m *customSubscribeModel) QuerySubscribeList(ctx context.Context) ([]*Subsc
return list, err return list, err
} }
func (m *customSubscribeModel) QuerySubscribeIdsByServerIdAndServerGroupId(ctx context.Context, serverId, serverGroupId int64) ([]*Subscribe, error) { func (m *customSubscribeModel) QuerySubscribeIdsByNodeIdAndNodeTag(ctx context.Context, node []int64, tags []string) ([]*Subscribe, error) {
var data []*Subscribe var data []*Subscribe
err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error { err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error {
return conn.Model(&Subscribe{}).Where("FIND_IN_SET(?, server)", serverId).Or("FIND_IN_SET(?, server_group)", serverGroupId).Find(v).Error db := conn.Model(&Subscribe{})
if len(node) > 0 {
for _, id := range node {
db = db.Or("FIND_IN_SET(?, nodes)", id)
}
}
if len(tags) > 0 {
// 拼接多个 tag 条件
for _, t := range tags {
db = db.Or("FIND_IN_SET(?, node_tags)", t)
}
}
return db.Find(v).Error
}) })
return data, err return data, err
} }

View File

@ -20,8 +20,8 @@ type Subscribe struct {
DeviceLimit int64 `gorm:"type:int;not null;default:0;comment:Device Limit"` DeviceLimit int64 `gorm:"type:int;not null;default:0;comment:Device Limit"`
Quota int64 `gorm:"type:int;not null;default:0;comment:Quota"` Quota int64 `gorm:"type:int;not null;default:0;comment:Quota"`
GroupId int64 `gorm:"type:bigint;comment:Group Id"` GroupId int64 `gorm:"type:bigint;comment:Group Id"`
ServerGroup string `gorm:"type:varchar(255);comment:Server Group"` Nodes string `gorm:"type:varchar(255);comment:Node Ids"`
Server string `gorm:"type:varchar(255);comment:Server"` NodeTags string `gorm:"type:varchar(255);comment:Node Tags"`
Show *bool `gorm:"type:tinyint(1);not null;default:0;comment:Show portal page"` Show *bool `gorm:"type:tinyint(1);not null;default:0;comment:Show portal page"`
Sell *bool `gorm:"type:tinyint(1);not null;default:0;comment:Sell"` Sell *bool `gorm:"type:tinyint(1);not null;default:0;comment:Sell"`
Sort int64 `gorm:"type:int;not null;default:0;comment:Sort"` Sort int64 `gorm:"type:int;not null;default:0;comment:Sort"`

View File

@ -18,7 +18,6 @@ import (
"github.com/perfect-panel/server/internal/model/log" "github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/model/order" "github.com/perfect-panel/server/internal/model/order"
"github.com/perfect-panel/server/internal/model/payment" "github.com/perfect-panel/server/internal/model/payment"
"github.com/perfect-panel/server/internal/model/server"
"github.com/perfect-panel/server/internal/model/subscribe" "github.com/perfect-panel/server/internal/model/subscribe"
"github.com/perfect-panel/server/internal/model/subscribeType" "github.com/perfect-panel/server/internal/model/subscribeType"
"github.com/perfect-panel/server/internal/model/system" "github.com/perfect-panel/server/internal/model/system"
@ -36,20 +35,20 @@ import (
) )
type ServiceContext struct { type ServiceContext struct {
DB *gorm.DB DB *gorm.DB
Redis *redis.Client Redis *redis.Client
Config config.Config Config config.Config
Queue *asynq.Client Queue *asynq.Client
NodeCache *cache.NodeCacheClient NodeCache *cache.NodeCacheClient
AuthModel auth.Model AuthModel auth.Model
AdsModel ads.Model AdsModel ads.Model
LogModel log.Model LogModel log.Model
NodeModel node.Model NodeModel node.Model
UserModel user.Model UserModel user.Model
OrderModel order.Model OrderModel order.Model
ClientModel client.Model ClientModel client.Model
TicketModel ticket.Model TicketModel ticket.Model
ServerModel server.Model //ServerModel server.Model
SystemModel system.Model SystemModel system.Model
CouponModel coupon.Model CouponModel coupon.Model
PaymentModel payment.Model PaymentModel payment.Model
@ -87,21 +86,21 @@ func NewServiceContext(c config.Config) *ServiceContext {
} }
authLimiter := limit.NewPeriodLimit(86400, 15, rds, config.SendCountLimitKeyPrefix, limit.Align()) authLimiter := limit.NewPeriodLimit(86400, 15, rds, config.SendCountLimitKeyPrefix, limit.Align())
srv := &ServiceContext{ srv := &ServiceContext{
DB: db, DB: db,
Redis: rds, Redis: rds,
Config: c, Config: c,
Queue: NewAsynqClient(c), Queue: NewAsynqClient(c),
NodeCache: cache.NewNodeCacheClient(rds), NodeCache: cache.NewNodeCacheClient(rds),
AuthLimiter: authLimiter, AuthLimiter: authLimiter,
AdsModel: ads.NewModel(db, rds), AdsModel: ads.NewModel(db, rds),
LogModel: log.NewModel(db), LogModel: log.NewModel(db),
NodeModel: node.NewModel(db), NodeModel: node.NewModel(db, rds),
AuthModel: auth.NewModel(db, rds), AuthModel: auth.NewModel(db, rds),
UserModel: user.NewModel(db, rds), UserModel: user.NewModel(db, rds),
OrderModel: order.NewModel(db, rds), OrderModel: order.NewModel(db, rds),
ClientModel: client.NewSubscribeApplicationModel(db), ClientModel: client.NewSubscribeApplicationModel(db),
TicketModel: ticket.NewModel(db, rds), TicketModel: ticket.NewModel(db, rds),
ServerModel: server.NewModel(db, rds), //ServerModel: server.NewModel(db, rds),
SystemModel: system.NewModel(db, rds), SystemModel: system.NewModel(db, rds),
CouponModel: coupon.NewModel(db, rds), CouponModel: coupon.NewModel(db, rds),
PaymentModel: payment.NewModel(db, rds), PaymentModel: payment.NewModel(db, rds),

View File

@ -384,8 +384,8 @@ type CreateSubscribeRequest struct {
DeviceLimit int64 `json:"device_limit"` DeviceLimit int64 `json:"device_limit"`
Quota int64 `json:"quota"` Quota int64 `json:"quota"`
GroupId int64 `json:"group_id"` GroupId int64 `json:"group_id"`
ServerGroup []int64 `json:"server_group"` Nodes []int64 `json:"nodes"`
Server []int64 `json:"server"` NodeTags []string `json:"node_tags"`
Show *bool `json:"show"` Show *bool `json:"show"`
Sell *bool `json:"sell"` Sell *bool `json:"sell"`
DeductionRatio int64 `json:"deduction_ratio"` DeductionRatio int64 `json:"deduction_ratio"`
@ -1117,6 +1117,10 @@ type GoogleLoginCallbackRequest struct {
State string `form:"state"` State string `form:"state"`
} }
type HasMigrateSeverNodeResponse struct {
HasMigrate bool `json:"has_migrate"`
}
type Hysteria2 struct { type Hysteria2 struct {
Port int `json:"port" validate:"required"` Port int `json:"port" validate:"required"`
HopPorts string `json:"hop_ports" validate:"required"` HopPorts string `json:"hop_ports" validate:"required"`
@ -1163,6 +1167,12 @@ type MessageLog struct {
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
} }
type MigrateServerNodeResponse struct {
Succee uint64 `json:"succee"`
Fail uint64 `json:"fail"`
Message string `json:"message,omitempty"`
}
type MobileAuthenticateConfig struct { type MobileAuthenticateConfig struct {
Enable bool `json:"enable"` Enable bool `json:"enable"`
EnableWhitelist bool `json:"enable_whitelist"` EnableWhitelist bool `json:"enable_whitelist"`
@ -1825,8 +1835,8 @@ type Subscribe struct {
DeviceLimit int64 `json:"device_limit"` DeviceLimit int64 `json:"device_limit"`
Quota int64 `json:"quota"` Quota int64 `json:"quota"`
GroupId int64 `json:"group_id"` GroupId int64 `json:"group_id"`
ServerGroup []int64 `json:"server_group"` Nodes []int64 `json:"nodes"`
Server []int64 `json:"server"` NodeTags []string `json:"node_tags"`
Show bool `json:"show"` Show bool `json:"show"`
Sell bool `json:"sell"` Sell bool `json:"sell"`
Sort int64 `json:"sort"` Sort int64 `json:"sort"`
@ -2195,8 +2205,8 @@ type UpdateSubscribeRequest struct {
DeviceLimit int64 `json:"device_limit"` DeviceLimit int64 `json:"device_limit"`
Quota int64 `json:"quota"` Quota int64 `json:"quota"`
GroupId int64 `json:"group_id"` GroupId int64 `json:"group_id"`
ServerGroup []int64 `json:"server_group"` Nodes []int64 `json:"nodes"`
Server []int64 `json:"server"` NodeTags []string `json:"node_tags"`
Show *bool `json:"show"` Show *bool `json:"show"`
Sell *bool `json:"sell"` Sell *bool `json:"sell"`
Sort int64 `json:"sort"` Sort int64 `json:"sort"`

View File

@ -59,6 +59,7 @@ func Int64SliceToString(intSlice []int64) string {
// string slice to string // string slice to string
func StringSliceToString(stringSlice []string) string { func StringSliceToString(stringSlice []string) string {
stringSlice = RemoveDuplicateElements(stringSlice...)
return strings.Join(stringSlice, ",") return strings.Join(stringSlice, ",")
} }

View File

@ -2,14 +2,9 @@ package countrylogic
import ( import (
"context" "context"
"encoding/json"
"github.com/perfect-panel/server/pkg/logger"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
"github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/ip"
"github.com/perfect-panel/server/queue/types"
) )
type GetNodeCountryLogic struct { type GetNodeCountryLogic struct {
@ -22,39 +17,6 @@ func NewGetNodeCountryLogic(svcCtx *svc.ServiceContext) *GetNodeCountryLogic {
} }
} }
func (l *GetNodeCountryLogic) ProcessTask(ctx context.Context, task *asynq.Task) error { func (l *GetNodeCountryLogic) ProcessTask(ctx context.Context, task *asynq.Task) error {
var payload types.GetNodeCountry
if err := json.Unmarshal(task.Payload(), &payload); err != nil {
logger.WithContext(ctx).Error("[GetNodeCountryLogic] Unmarshal payload failed",
logger.Field("error", err.Error()),
logger.Field("payload", task.Payload()),
)
return nil
}
serverAddr := payload.ServerAddr
resp, err := ip.GetRegionByIp(serverAddr)
if err != nil {
logger.WithContext(ctx).Error("[GetNodeCountryLogic] ", logger.Field("error", err.Error()), logger.Field("serverAddr", serverAddr))
return nil
}
servers, err := l.svcCtx.ServerModel.FindNodeByServerAddrAndProtocol(ctx, payload.ServerAddr, payload.Protocol)
if err != nil {
logger.WithContext(ctx).Error("[GetNodeCountryLogic] FindNodeByServerAddrAnd", logger.Field("error", err.Error()), logger.Field("serverAddr", serverAddr))
return err
}
if len(servers) == 0 {
return nil
}
for _, ser := range servers {
ser.Country = resp.Country
ser.City = resp.City
ser.Latitude = resp.Latitude
ser.Longitude = resp.Longitude
err := l.svcCtx.ServerModel.Update(ctx, ser)
if err != nil {
logger.WithContext(ctx).Error("[GetNodeCountryLogic] ", logger.Field("error", err.Error()), logger.Field("id", ser.Id))
}
}
logger.WithContext(ctx).Info("[GetNodeCountryLogic] ", logger.Field("country", resp.Country), logger.Field("city", resp.Country))
return nil return nil
} }

View File

@ -7,16 +7,17 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/perfect-panel/server/internal/model/log" "github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/pkg/constant" "github.com/perfect-panel/server/pkg/constant"
"github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/logger"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
"github.com/perfect-panel/server/internal/config"
"github.com/perfect-panel/server/internal/logic/telegram" "github.com/perfect-panel/server/internal/logic/telegram"
"github.com/perfect-panel/server/internal/model/order" "github.com/perfect-panel/server/internal/model/order"
"github.com/perfect-panel/server/internal/model/subscribe" "github.com/perfect-panel/server/internal/model/subscribe"
@ -429,34 +430,18 @@ func (l *ActivateOrderLogic) calculateCommission(price int64) int64 {
// clearServerCache clears user list cache for all servers associated with the subscription // clearServerCache clears user list cache for all servers associated with the subscription
func (l *ActivateOrderLogic) clearServerCache(ctx context.Context, sub *subscribe.Subscribe) { func (l *ActivateOrderLogic) clearServerCache(ctx context.Context, sub *subscribe.Subscribe) {
serverIds := tool.StringToInt64Slice(sub.Server) nodeIds := tool.StringToInt64Slice(sub.Nodes)
groupServerIds := l.getServerIdsByGroups(ctx, sub.ServerGroup) tags := strings.Split(sub.NodeTags, ",")
allServerIds := append(serverIds, groupServerIds...)
for _, id := range allServerIds { err := l.svc.NodeModel.ClearNodeCache(ctx, &node.FilterNodeParams{
cacheKey := fmt.Sprintf("%s%d", config.ServerUserListCacheKey, id) Page: 1,
if err := l.svc.Redis.Del(ctx, cacheKey).Err(); err != nil { Size: 1000,
logger.WithContext(ctx).Error("Del server user list cache failed", ServerId: nodeIds,
logger.Field("error", err.Error()), Tag: tags,
logger.Field("cache_key", cacheKey), })
)
}
}
}
// getServerIdsByGroups retrieves server IDs from server groups
func (l *ActivateOrderLogic) getServerIdsByGroups(ctx context.Context, serverGroup string) []int64 {
data, err := l.svc.ServerModel.FindServerListByGroupIds(ctx, tool.StringToInt64Slice(serverGroup))
if err != nil { if err != nil {
logger.WithContext(ctx).Error("Find server list failed", logger.Field("error", err.Error())) logger.WithContext(ctx).Error("[Order Queue] Clear node cache failed", logger.Field("error", err.Error()))
return nil
} }
serverIds := make([]int64, len(data))
for i, item := range data {
serverIds[i] = item.Id
}
return serverIds
} }
// Renewal handles subscription renewal including subscription extension, // Renewal handles subscription renewal including subscription extension,

View File

@ -73,7 +73,7 @@ func (l *ServerDataLogic) getRanking(ctx context.Context) (top10ServerToday, top
if s.ServerId == 0 { if s.ServerId == 0 {
continue continue
} }
serverInfo, err := l.svc.ServerModel.FindOne(ctx, s.ServerId) serverInfo, err := l.svc.NodeModel.FindOneServer(ctx, s.ServerId)
if err != nil { if err != nil {
logger.Error("[ServerDataLogic] Find server failed", logger.Field("error", err.Error())) logger.Error("[ServerDataLogic] Find server failed", logger.Field("error", err.Error()))
continue continue
@ -92,7 +92,7 @@ func (l *ServerDataLogic) getRanking(ctx context.Context) (top10ServerToday, top
logger.Error("[ServerDataLogic] Get top servers traffic by day failed", logger.Field("error", err.Error())) logger.Error("[ServerDataLogic] Get top servers traffic by day failed", logger.Field("error", err.Error()))
} else { } else {
for _, s := range serverYesterday { for _, s := range serverYesterday {
serverInfo, err := l.svc.ServerModel.FindOne(ctx, s.ServerId) serverInfo, err := l.svc.NodeModel.FindOneServer(ctx, s.ServerId)
if err != nil { if err != nil {
logger.Error("[ServerDataLogic] Find server failed", logger.Field("error", err.Error())) logger.Error("[ServerDataLogic] Find server failed", logger.Field("error", err.Error()))
continue continue

View File

@ -38,7 +38,7 @@ func (l *TrafficStatisticsLogic) ProcessTask(ctx context.Context, task *asynq.Ta
return nil return nil
} }
// query server info // query server info
serverInfo, err := l.svc.ServerModel.FindOne(ctx, payload.ServerId) serverInfo, err := l.svc.NodeModel.FindOneServer(ctx, payload.ServerId)
if err != nil { if err != nil {
logger.WithContext(ctx).Error("[TrafficStatistics] Find server info failed", logger.WithContext(ctx).Error("[TrafficStatistics] Find server info failed",
logger.Field("serverId", payload.ServerId), logger.Field("serverId", payload.ServerId),
@ -46,23 +46,22 @@ func (l *TrafficStatisticsLogic) ProcessTask(ctx context.Context, task *asynq.Ta
) )
return nil return nil
} }
if serverInfo.TrafficRatio == 0 { var serverRatio float32 = 1.0
logger.WithContext(ctx).Error("[TrafficStatistics] Server log ratio is 0", if serverInfo.Ratio > 0 {
logger.Field("serverId", payload.ServerId), serverRatio = serverInfo.Ratio
)
return nil
} }
now := time.Now() now := time.Now()
realTimeMultiplier := l.svc.NodeMultiplierManager.GetMultiplier(now) realTimeMultiplier := l.svc.NodeMultiplierManager.GetMultiplier(now)
for _, log := range payload.Logs { for _, log := range payload.Logs {
// update user subscribe with log // update user subscribe with log
d := int64(float32(log.Download) * serverInfo.TrafficRatio * realTimeMultiplier) d := int64(float32(log.Download) * serverRatio * realTimeMultiplier)
u := int64(float32(log.Upload) * serverInfo.TrafficRatio * realTimeMultiplier) u := int64(float32(log.Upload) * serverRatio * realTimeMultiplier)
if err := l.svc.UserModel.UpdateUserSubscribeWithTraffic(ctx, log.SID, d, u); err != nil { if err := l.svc.UserModel.UpdateUserSubscribeWithTraffic(ctx, log.SID, d, u); err != nil {
logger.WithContext(ctx).Error("[TrafficStatistics] Update user subscribe with log failed", logger.WithContext(ctx).Error("[TrafficStatistics] Update user subscribe with log failed",
logger.Field("sid", log.SID), logger.Field("sid", log.SID),
logger.Field("download", float32(log.Download)*serverInfo.TrafficRatio), logger.Field("download", float32(log.Download)*serverRatio),
logger.Field("upload", float32(log.Upload)*serverInfo.TrafficRatio), logger.Field("upload", float32(log.Upload)*serverRatio),
logger.Field("error", err.Error()), logger.Field("error", err.Error()),
) )
continue continue
@ -88,8 +87,8 @@ func (l *TrafficStatisticsLogic) ProcessTask(ctx context.Context, task *asynq.Ta
}); err != nil { }); err != nil {
logger.WithContext(ctx).Error("[TrafficStatistics] Create log log failed", logger.WithContext(ctx).Error("[TrafficStatistics] Create log log failed",
logger.Field("uid", log.SID), logger.Field("uid", log.SID),
logger.Field("download", float32(log.Download)*serverInfo.TrafficRatio), logger.Field("download", float32(log.Download)*serverRatio),
logger.Field("upload", float32(log.Upload)*serverInfo.TrafficRatio), logger.Field("upload", float32(log.Upload)*serverRatio),
logger.Field("error", err.Error()), logger.Field("error", err.Error()),
) )
} }