From 80ee9a6acf633cb132ad666287ab607b134835c0 Mon Sep 17 00:00:00 2001 From: EUForest Date: Fri, 2 Jan 2026 12:51:55 +0800 Subject: [PATCH] Merge upstream/master into develop Sync upstream changes from perfect-panel/server Includes updates from v1.0.1 to v1.2.5: - Currency configuration support - Subscribe improvements (short token, inventory check, etc.) - Node management enhancements - Database migrations - Bug fixes and optimizations --- adapter/adapter.go | 121 ++++--- adapter/client.go | 14 +- apis/admin/server.api | 8 - apis/admin/subscribe.api | 86 ++--- apis/admin/user.api | 19 + apis/public/subscribe.api | 8 +- apis/public/user.api | 16 +- apis/types.api | 71 ++-- go.mod | 2 +- initialize/currency.go | 34 ++ initialize/init.go | 1 + .../02123_subscribe_original.down.sql | 2 + .../database/02123_subscribe_original.up.sql | 2 + .../02124_server_group_delete.down.sql | 0 .../database/02124_server_group_delete.up.sql | 1 + .../database/02125_subscribe_stock.down.sql | 5 + .../database/02125_subscribe_stock.up.sql | 4 + internal/config/config.go | 7 + .../server/hasMigrateSeverNodeHandler.go | 18 - .../admin/server/migrateServerNodeHandler.go | 18 - .../user/resetUserSubscribeTokenHandler.go | 26 ++ .../user/resetUserSubscribeTrafficHandler.go | 26 ++ .../user/toggleUserSubscribeStatusHandler.go | 26 ++ internal/handler/routes.go | 24 +- internal/handler/subscribe.go | 18 +- .../logic/admin/server/createNodeLogic.go | 1 + .../admin/server/hasMigrateSeverNodeLogic.go | 52 --- .../admin/server/migrateServerNodeLogic.go | 338 ------------------ .../admin/subscribe/createSubscribeLogic.go | 45 +-- .../admin/subscribe/updateSubscribeLogic.go | 45 +-- .../logic/admin/system/getNodeConfigLogic.go | 1 + .../admin/system/setNodeMultiplierLogic.go | 6 +- internal/logic/admin/user/getUserListLogic.go | 1 + .../logic/admin/user/getUserSubscribeLogic.go | 1 + .../user/resetUserSubscribeTokenLogic.go | 55 +++ .../user/resetUserSubscribeTrafficLogic.go | 53 +++ .../user/toggleUserSubscribeStatusLogic.go | 63 ++++ internal/logic/auth/bindDeviceLogic.go | 81 +++++ internal/logic/auth/userLoginLogic.go | 4 + internal/logic/auth/userRegisterLogic.go | 18 +- internal/logic/common/getGlobalConfigLogic.go | 2 + internal/logic/common/getStatLogic.go | 24 +- .../logic/public/order/closeOrderLogic.go | 22 ++ internal/logic/public/order/purchaseLogic.go | 19 + .../public/portal/purchaseCheckoutLogic.go | 65 ++-- internal/logic/public/portal/purchaseLogic.go | 16 + .../server/serverPushUserTrafficLogic.go | 2 +- internal/logic/subscribe/subscribeLogic.go | 36 +- internal/model/announcement/model.go | 3 + internal/model/server/default.go | 141 -------- internal/model/server/model.go | 292 --------------- internal/model/server/server.go | 219 ------------ internal/model/subscribe/subscribe.go | 49 +-- internal/model/user/default.go | 25 +- internal/model/user/model.go | 4 + internal/model/user/user.go | 50 +-- internal/svc/serviceContext.go | 2 +- internal/types/subscribe.go | 8 +- internal/types/types.go | 147 ++++---- pkg/logger/gorm.go | 6 +- pkg/xerr/errCode.go | 1 + pkg/xerr/errMsg.go | 1 + queue/logic/traffic/trafficStatisticsLogic.go | 1 + 63 files changed, 947 insertions(+), 1509 deletions(-) create mode 100644 initialize/currency.go create mode 100644 initialize/migrate/database/02123_subscribe_original.down.sql create mode 100644 initialize/migrate/database/02123_subscribe_original.up.sql create mode 100644 initialize/migrate/database/02124_server_group_delete.down.sql create mode 100644 initialize/migrate/database/02124_server_group_delete.up.sql create mode 100644 initialize/migrate/database/02125_subscribe_stock.down.sql create mode 100644 initialize/migrate/database/02125_subscribe_stock.up.sql delete mode 100644 internal/handler/admin/server/hasMigrateSeverNodeHandler.go delete mode 100644 internal/handler/admin/server/migrateServerNodeHandler.go create mode 100644 internal/handler/admin/user/resetUserSubscribeTokenHandler.go create mode 100644 internal/handler/admin/user/resetUserSubscribeTrafficHandler.go create mode 100644 internal/handler/admin/user/toggleUserSubscribeStatusHandler.go delete mode 100644 internal/logic/admin/server/hasMigrateSeverNodeLogic.go delete mode 100644 internal/logic/admin/server/migrateServerNodeLogic.go create mode 100644 internal/logic/admin/user/resetUserSubscribeTokenLogic.go create mode 100644 internal/logic/admin/user/resetUserSubscribeTrafficLogic.go create mode 100644 internal/logic/admin/user/toggleUserSubscribeStatusLogic.go delete mode 100644 internal/model/server/default.go delete mode 100644 internal/model/server/model.go delete mode 100644 internal/model/server/server.go diff --git a/adapter/adapter.go b/adapter/adapter.go index da5d049..78de534 100644 --- a/adapter/adapter.go +++ b/adapter/adapter.go @@ -8,16 +8,24 @@ import ( ) type Adapter struct { - SiteName string // 站点名称 - Servers []*node.Node // 服务器列表 - UserInfo User // 用户信息 - ClientTemplate string // 客户端配置模板 - OutputFormat string // 输出格式,默认是 base64 - SubscribeName string // 订阅名称 + Type string // 协议类型 + SiteName string // 站点名称 + Servers []*node.Node // 服务器列表 + UserInfo User // 用户信息 + ClientTemplate string // 客户端配置模板 + OutputFormat string // 输出格式,默认是 base64 + SubscribeName string // 订阅名称 + Params map[string]string // 其他参数 } type Option func(*Adapter) +func WithParams(params map[string]string) Option { + return func(opts *Adapter) { + opts.Params = params + } +} + // WithServers 设置服务器列表 func WithServers(servers []*node.Node) Option { return func(opts *Adapter) { @@ -101,55 +109,58 @@ func (adapter *Adapter) Proxies(servers []*node.Node) ([]Proxy, error) { } for _, protocol := range protocols { if protocol.Type == item.Protocol { - proxies = append(proxies, Proxy{ - Sort: item.Sort, - Name: item.Name, - Server: item.Address, - Port: item.Port, - Type: item.Protocol, - Tags: strings.Split(item.Tags, ","), - Security: protocol.Security, - SNI: protocol.SNI, - AllowInsecure: protocol.AllowInsecure, - Fingerprint: protocol.Fingerprint, - RealityServerAddr: protocol.RealityServerAddr, - 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, - UpMbps: protocol.UpMbps, - DownMbps: protocol.DownMbps, - DisableSNI: protocol.DisableSNI, - ReduceRtt: protocol.ReduceRtt, - UDPRelayMode: protocol.UDPRelayMode, - CongestionController: protocol.CongestionController, - PaddingScheme: protocol.PaddingScheme, - Multiplex: protocol.Multiplex, - XhttpMode: protocol.XhttpMode, - XhttpExtra: protocol.XhttpExtra, - Encryption: protocol.Encryption, - EncryptionMode: protocol.EncryptionMode, - EncryptionRtt: protocol.EncryptionRtt, - EncryptionTicket: protocol.EncryptionTicket, - EncryptionServerPadding: protocol.EncryptionServerPadding, - EncryptionPrivateKey: protocol.EncryptionPrivateKey, - EncryptionClientPadding: protocol.EncryptionClientPadding, - EncryptionPassword: protocol.EncryptionPassword, - Ratio: protocol.Ratio, - CertMode: protocol.CertMode, - CertDNSProvider: protocol.CertDNSProvider, - CertDNSEnv: protocol.CertDNSEnv, - }) + proxies = append( + proxies, + Proxy{ + Sort: item.Sort, + Name: item.Name, + Server: item.Address, + Port: item.Port, + Type: item.Protocol, + Tags: strings.Split(item.Tags, ","), + Security: protocol.Security, + SNI: protocol.SNI, + AllowInsecure: protocol.AllowInsecure, + Fingerprint: protocol.Fingerprint, + RealityServerAddr: protocol.RealityServerAddr, + 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, + UpMbps: protocol.UpMbps, + DownMbps: protocol.DownMbps, + DisableSNI: protocol.DisableSNI, + ReduceRtt: protocol.ReduceRtt, + UDPRelayMode: protocol.UDPRelayMode, + CongestionController: protocol.CongestionController, + PaddingScheme: protocol.PaddingScheme, + Multiplex: protocol.Multiplex, + XhttpMode: protocol.XhttpMode, + XhttpExtra: protocol.XhttpExtra, + Encryption: protocol.Encryption, + EncryptionMode: protocol.EncryptionMode, + EncryptionRtt: protocol.EncryptionRtt, + EncryptionTicket: protocol.EncryptionTicket, + EncryptionServerPadding: protocol.EncryptionServerPadding, + EncryptionPrivateKey: protocol.EncryptionPrivateKey, + EncryptionClientPadding: protocol.EncryptionClientPadding, + EncryptionPassword: protocol.EncryptionPassword, + Ratio: protocol.Ratio, + CertMode: protocol.CertMode, + CertDNSProvider: protocol.CertDNSProvider, + CertDNSEnv: protocol.CertDNSEnv, + }, + ) } } } diff --git a/adapter/client.go b/adapter/client.go index e456898..d267c12 100644 --- a/adapter/client.go +++ b/adapter/client.go @@ -93,12 +93,13 @@ type User struct { } type Client struct { - SiteName string // Name of the site - SubscribeName string // Name of the subscription - ClientTemplate string // Template for the entire client configuration - OutputFormat string // json, yaml, etc. - Proxies []Proxy // List of proxy configurations - UserInfo User // User information + SiteName string // Name of the site + SubscribeName string // Name of the subscription + ClientTemplate string // Template for the entire client configuration + OutputFormat string // json, yaml, etc. + Proxies []Proxy // List of proxy configurations + UserInfo User // User information + Params map[string]string // Additional parameters } func (c *Client) Build() ([]byte, error) { @@ -119,6 +120,7 @@ func (c *Client) Build() ([]byte, error) { "OutputFormat": c.OutputFormat, "Proxies": proxies, "UserInfo": c.UserInfo, + "Params": c.Params, }) if err != nil { return nil, err diff --git a/apis/admin/server.api b/apis/admin/server.api index 2877427..87d1f7e 100644 --- a/apis/admin/server.api +++ b/apis/admin/server.api @@ -189,14 +189,6 @@ service ppanel { @handler ToggleNodeStatus 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) - @doc "Reset server sort" @handler ResetSortWithServer post /server/sort (ResetSortRequest) diff --git a/apis/admin/subscribe.api b/apis/admin/subscribe.api index bea205d..a832b3a 100644 --- a/apis/admin/subscribe.api +++ b/apis/admin/subscribe.api @@ -34,50 +34,52 @@ type ( Ids []int64 `json:"ids" validate:"required"` } CreateSubscribeRequest { - Name string `json:"name" validate:"required"` - Language string `json:"language"` - Description string `json:"description"` - UnitPrice int64 `json:"unit_price"` - UnitTime string `json:"unit_time"` - Discount []SubscribeDiscount `json:"discount"` - Replacement int64 `json:"replacement"` - Inventory int64 `json:"inventory"` - Traffic int64 `json:"traffic"` - SpeedLimit int64 `json:"speed_limit"` - DeviceLimit int64 `json:"device_limit"` - Quota int64 `json:"quota"` - Nodes []int64 `json:"nodes"` - NodeTags []string `json:"node_tags"` - Show *bool `json:"show"` - Sell *bool `json:"sell"` - DeductionRatio int64 `json:"deduction_ratio"` - AllowDeduction *bool `json:"allow_deduction"` - ResetCycle int64 `json:"reset_cycle"` - RenewalReset *bool `json:"renewal_reset"` + Name string `json:"name" validate:"required"` + Language string `json:"language"` + Description string `json:"description"` + UnitPrice int64 `json:"unit_price"` + UnitTime string `json:"unit_time"` + Discount []SubscribeDiscount `json:"discount"` + Replacement int64 `json:"replacement"` + Inventory int64 `json:"inventory"` + Traffic int64 `json:"traffic"` + SpeedLimit int64 `json:"speed_limit"` + DeviceLimit int64 `json:"device_limit"` + Quota int64 `json:"quota"` + Nodes []int64 `json:"nodes"` + NodeTags []string `json:"node_tags"` + Show *bool `json:"show"` + Sell *bool `json:"sell"` + DeductionRatio int64 `json:"deduction_ratio"` + AllowDeduction *bool `json:"allow_deduction"` + ResetCycle int64 `json:"reset_cycle"` + RenewalReset *bool `json:"renewal_reset"` + ShowOriginalPrice bool `json:"show_original_price"` } UpdateSubscribeRequest { - Id int64 `json:"id" validate:"required"` - Name string `json:"name" validate:"required"` - Language string `json:"language"` - Description string `json:"description"` - UnitPrice int64 `json:"unit_price"` - UnitTime string `json:"unit_time"` - Discount []SubscribeDiscount `json:"discount"` - Replacement int64 `json:"replacement"` - Inventory int64 `json:"inventory"` - Traffic int64 `json:"traffic"` - SpeedLimit int64 `json:"speed_limit"` - DeviceLimit int64 `json:"device_limit"` - Quota int64 `json:"quota"` - Nodes []int64 `json:"nodes"` - NodeTags []string `json:"node_tags"` - Show *bool `json:"show"` - Sell *bool `json:"sell"` - Sort int64 `json:"sort"` - DeductionRatio int64 `json:"deduction_ratio"` - AllowDeduction *bool `json:"allow_deduction"` - ResetCycle int64 `json:"reset_cycle"` - RenewalReset *bool `json:"renewal_reset"` + Id int64 `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + Language string `json:"language"` + Description string `json:"description"` + UnitPrice int64 `json:"unit_price"` + UnitTime string `json:"unit_time"` + Discount []SubscribeDiscount `json:"discount"` + Replacement int64 `json:"replacement"` + Inventory int64 `json:"inventory"` + Traffic int64 `json:"traffic"` + SpeedLimit int64 `json:"speed_limit"` + DeviceLimit int64 `json:"device_limit"` + Quota int64 `json:"quota"` + Nodes []int64 `json:"nodes"` + NodeTags []string `json:"node_tags"` + Show *bool `json:"show"` + Sell *bool `json:"sell"` + Sort int64 `json:"sort"` + DeductionRatio int64 `json:"deduction_ratio"` + AllowDeduction *bool `json:"allow_deduction"` + ResetCycle int64 `json:"reset_cycle"` + RenewalReset *bool `json:"renewal_reset"` + ShowOriginalPrice bool `json:"show_original_price"` } SubscribeSortRequest { Sort []SortItem `json:"sort"` diff --git a/apis/admin/user.api b/apis/admin/user.api index dc0c5e8..923f197 100644 --- a/apis/admin/user.api +++ b/apis/admin/user.api @@ -19,6 +19,7 @@ type ( Size int `form:"size"` Search string `form:"search,omitempty"` UserId *int64 `form:"user_id,omitempty"` + Unscoped bool `form:"unscoped,omitempty"` SubscribeId *int64 `form:"subscribe_id,omitempty"` UserSubscribeId *int64 `form:"user_subscribe_id,omitempty"` } @@ -183,6 +184,12 @@ type ( GetUserSubscribeByIdRequest { Id int64 `form:"id" validate:"required"` } + ToggleUserSubscribeStatusRequest { + UserSubscribeId int64 `json:"user_subscribe_id"` + } + ResetUserSubscribeTrafficRequest { + UserSubscribeId int64 `json:"user_subscribe_id"` + } ) @server ( @@ -291,5 +298,17 @@ service ppanel { @doc "Get user login logs" @handler GetUserLoginLogs get /login/logs (GetUserLoginLogsRequest) returns (GetUserLoginLogsResponse) + + @doc "Reset user subscribe token" + @handler ResetUserSubscribeToken + post /subscribe/reset/token (ResetUserSubscribeTokenRequest) + + @doc "Stop user subscribe" + @handler ToggleUserSubscribeStatus + post /subscribe/toggle (ToggleUserSubscribeStatusRequest) + + @doc "Reset user subscribe traffic" + @handler ResetUserSubscribeTraffic + post /subscribe/reset/traffic (ResetUserSubscribeTrafficRequest) } diff --git a/apis/public/subscribe.api b/apis/public/subscribe.api index 55e9c07..4c0d2aa 100644 --- a/apis/public/subscribe.api +++ b/apis/public/subscribe.api @@ -14,9 +14,11 @@ type ( QuerySubscribeListRequest { Language string `form:"language"` } - QueryUserSubscribeNodeListResponse { - List []UserSubscribeInfo `json:"list"` - } + + QueryUserSubscribeNodeListResponse { + List []UserSubscribeInfo `json:"list"` + } + UserSubscribeInfo { Id int64 `json:"id"` UserId int64 `json:"user_id"` diff --git a/apis/public/user.api b/apis/public/user.api index 82c89b2..425a3d3 100644 --- a/apis/public/user.api +++ b/apis/public/user.api @@ -66,9 +66,7 @@ type ( UnbindOAuthRequest { Method string `json:"method"` } - ResetUserSubscribeTokenRequest { - UserSubscribeId int64 `json:"user_subscribe_id"` - } + GetLoginLogRequest { Page int `form:"page"` Size int `form:"size"` @@ -282,15 +280,3 @@ service ppanel { get /withdrawal_log (QueryWithdrawalLogListRequest) returns (QueryWithdrawalLogListResponse) } - -@server( - prefix: v1/public/user - group: public/user/ws - middleware: AuthMiddleware -) - -service ppanel { - @doc "Webosocket Device Connect" - @handler DeviceWsConnect - get /device_ws_connect -} \ No newline at end of file diff --git a/apis/types.api b/apis/types.api index defa923..7f4bc5a 100644 --- a/apis/types.api +++ b/apis/types.api @@ -32,7 +32,6 @@ type ( CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` DeletedAt int64 `json:"deleted_at,omitempty"` - IsDel bool `json:"is_del,omitempty"` } Follow { Id int64 `json:"id"` @@ -136,12 +135,14 @@ type ( EnableDomainSuffix bool `json:"enable_domain_suffix"` DomainSuffixList string `json:"domain_suffix_list"` } - DeviceAuthticateConfig { - Enable bool `json:"enable"` - ShowAds bool `json:"show_ads"` - EnableSecurity bool `json:"enable_security"` - OnlyRealDevice bool `json:"only_real_device"` - } + + DeviceAuthticateConfig { + Enable bool `json:"enable"` + ShowAds bool `json:"show_ads"` + EnableSecurity bool `json:"enable_security"` + OnlyRealDevice bool `json:"only_real_device"` + } + RegisterConfig { StopRegister bool `json:"stop_register"` EnableTrial bool `json:"enable_trial"` @@ -209,30 +210,31 @@ type ( Discount float64 `json:"discount"` } Subscribe { - Id int64 `json:"id"` - Name string `json:"name"` - Language string `json:"language"` - Description string `json:"description"` - UnitPrice int64 `json:"unit_price"` - UnitTime string `json:"unit_time"` - Discount []SubscribeDiscount `json:"discount"` - Replacement int64 `json:"replacement"` - Inventory int64 `json:"inventory"` - Traffic int64 `json:"traffic"` - SpeedLimit int64 `json:"speed_limit"` - DeviceLimit int64 `json:"device_limit"` - Quota int64 `json:"quota"` - Nodes []int64 `json:"nodes"` - NodeTags []string `json:"node_tags"` - Show bool `json:"show"` - Sell bool `json:"sell"` - Sort int64 `json:"sort"` - DeductionRatio int64 `json:"deduction_ratio"` - AllowDeduction bool `json:"allow_deduction"` - ResetCycle int64 `json:"reset_cycle"` - RenewalReset bool `json:"renewal_reset"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` + Id int64 `json:"id"` + Name string `json:"name"` + Language string `json:"language"` + Description string `json:"description"` + UnitPrice int64 `json:"unit_price"` + UnitTime string `json:"unit_time"` + Discount []SubscribeDiscount `json:"discount"` + Replacement int64 `json:"replacement"` + Inventory int64 `json:"inventory"` + Traffic int64 `json:"traffic"` + SpeedLimit int64 `json:"speed_limit"` + DeviceLimit int64 `json:"device_limit"` + Quota int64 `json:"quota"` + Nodes []int64 `json:"nodes"` + NodeTags []string `json:"node_tags"` + Show bool `json:"show"` + Sell bool `json:"sell"` + Sort int64 `json:"sort"` + DeductionRatio int64 `json:"deduction_ratio"` + AllowDeduction bool `json:"allow_deduction"` + ResetCycle int64 `json:"reset_cycle"` + RenewalReset bool `json:"renewal_reset"` + ShowOriginalPrice bool `json:"show_original_price"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` } SubscribeGroup { Id int64 `json:"id"` @@ -656,7 +658,7 @@ type ( // public announcement QueryAnnouncementRequest { Page int `form:"page"` - Size int `form:"size"` + Size int `form:"size,default=15"` Pinned *bool `form:"pinned"` Popup *bool `form:"popup"` } @@ -673,6 +675,7 @@ type ( List []SubscribeGroup `json:"list"` Total int64 `json:"total"` } + GetUserSubscribeTrafficLogsRequest { Page int `form:"page"` Size int `form:"size"` @@ -845,5 +848,9 @@ type ( CertDNSProvider string `json:"cert_dns_provider,omitempty"` // DNS provider for certificate CertDNSEnv string `json:"cert_dns_env,omitempty"` // Environment for DNS provider } + // reset user subscribe token + ResetUserSubscribeTokenRequest { + UserSubscribeId int64 `json:"user_subscribe_id"` + } ) diff --git a/go.mod b/go.mod index 9d0b191..aa17478 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/jinzhu/copier v0.4.0 github.com/klauspost/compress v1.17.7 github.com/nyaruka/phonenumbers v1.5.0 - github.com/pkg/errors v0.9.1 + github.com/pkg/errors v0.9.1 github.com/redis/go-redis/v9 v9.7.2 github.com/smartwalle/alipay/v3 v3.2.23 github.com/spf13/cast v1.7.0 // indirect diff --git a/initialize/currency.go b/initialize/currency.go new file mode 100644 index 0000000..25dc52f --- /dev/null +++ b/initialize/currency.go @@ -0,0 +1,34 @@ +package initialize + +import ( + "context" + "fmt" + + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/tool" +) + +func Currency(ctx *svc.ServiceContext) { + // Retrieve system currency configuration + currency, err := ctx.SystemModel.GetCurrencyConfig(context.Background()) + if err != nil { + logger.Errorf("[INIT] Failed to get currency configuration: %v", err.Error()) + panic(fmt.Sprintf("[INIT] Failed to get currency configuration: %v", err.Error())) + } + // Parse currency configuration + configs := struct { + CurrencyUnit string + CurrencySymbol string + AccessKey string + }{} + tool.SystemConfigSliceReflectToStruct(currency, &configs) + + ctx.Config.Currency = config.Currency{ + Unit: configs.CurrencyUnit, + Symbol: configs.CurrencySymbol, + AccessKey: configs.AccessKey, + } + logger.Infof("[INIT] Currency configuration: %v", ctx.Config.Currency) +} diff --git a/initialize/init.go b/initialize/init.go index 8023ce5..2333b5e 100644 --- a/initialize/init.go +++ b/initialize/init.go @@ -15,6 +15,7 @@ func StartInitSystemConfig(svc *svc.ServiceContext) { Subscribe(svc) Register(svc) Mobile(svc) + Currency(svc) if !svc.Config.Debug { Telegram(svc) } diff --git a/initialize/migrate/database/02123_subscribe_original.down.sql b/initialize/migrate/database/02123_subscribe_original.down.sql new file mode 100644 index 0000000..2527d48 --- /dev/null +++ b/initialize/migrate/database/02123_subscribe_original.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE `subscribe` +DROP COLUMN `show_original_price`; diff --git a/initialize/migrate/database/02123_subscribe_original.up.sql b/initialize/migrate/database/02123_subscribe_original.up.sql new file mode 100644 index 0000000..af04a8b --- /dev/null +++ b/initialize/migrate/database/02123_subscribe_original.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE `subscribe` + ADD COLUMN `show_original_price` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'display the original price: 0 not display, 1 display' AFTER `created_at`; diff --git a/initialize/migrate/database/02124_server_group_delete.down.sql b/initialize/migrate/database/02124_server_group_delete.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/initialize/migrate/database/02124_server_group_delete.up.sql b/initialize/migrate/database/02124_server_group_delete.up.sql new file mode 100644 index 0000000..57c0b5a --- /dev/null +++ b/initialize/migrate/database/02124_server_group_delete.up.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS `server_group`; \ No newline at end of file diff --git a/initialize/migrate/database/02125_subscribe_stock.down.sql b/initialize/migrate/database/02125_subscribe_stock.down.sql new file mode 100644 index 0000000..c8482cc --- /dev/null +++ b/initialize/migrate/database/02125_subscribe_stock.down.sql @@ -0,0 +1,5 @@ + +-- This migration script reverts the inventory values in the 'subscribe' table +UPDATE `subscribe` +SET `inventory` = 0 +WHERE `inventory` = -1; \ No newline at end of file diff --git a/initialize/migrate/database/02125_subscribe_stock.up.sql b/initialize/migrate/database/02125_subscribe_stock.up.sql new file mode 100644 index 0000000..88fead1 --- /dev/null +++ b/initialize/migrate/database/02125_subscribe_stock.up.sql @@ -0,0 +1,4 @@ +-- Update the `subscribe` table to set `inventory` to -1 where it is currently 0 +UPDATE `subscribe` +SET `inventory` = -1 +WHERE `inventory` = 0; \ No newline at end of file diff --git a/internal/config/config.go b/internal/config/config.go index 59ece74..fc93da5 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -29,6 +29,7 @@ type Config struct { Invite InviteConfig `yaml:"Invite"` Telegram Telegram `yaml:"Telegram"` Log Log `yaml:"Log"` + Currency Currency `yaml:"Currency"` Administrator struct { Email string `yaml:"Email" default:"admin@ppanel.dev"` Password string `yaml:"Password" default:"password"` @@ -241,3 +242,9 @@ type NodeDBConfig struct { Block string Outbound string } + +type Currency struct { + Unit string `yaml:"Unit" default:"CNY"` + Symbol string `yaml:"Symbol" default:"USD"` + AccessKey string `yaml:"AccessKey" default:""` +} diff --git a/internal/handler/admin/server/hasMigrateSeverNodeHandler.go b/internal/handler/admin/server/hasMigrateSeverNodeHandler.go deleted file mode 100644 index 6088577..0000000 --- a/internal/handler/admin/server/hasMigrateSeverNodeHandler.go +++ /dev/null @@ -1,18 +0,0 @@ -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) - } -} diff --git a/internal/handler/admin/server/migrateServerNodeHandler.go b/internal/handler/admin/server/migrateServerNodeHandler.go deleted file mode 100644 index 8f8c842..0000000 --- a/internal/handler/admin/server/migrateServerNodeHandler.go +++ /dev/null @@ -1,18 +0,0 @@ -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) - } -} diff --git a/internal/handler/admin/user/resetUserSubscribeTokenHandler.go b/internal/handler/admin/user/resetUserSubscribeTokenHandler.go new file mode 100644 index 0000000..0ccff0c --- /dev/null +++ b/internal/handler/admin/user/resetUserSubscribeTokenHandler.go @@ -0,0 +1,26 @@ +package user + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Reset user subscribe token +func ResetUserSubscribeTokenHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.ResetUserSubscribeTokenRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := user.NewResetUserSubscribeTokenLogic(c.Request.Context(), svcCtx) + err := l.ResetUserSubscribeToken(&req) + result.HttpResult(c, nil, err) + } +} diff --git a/internal/handler/admin/user/resetUserSubscribeTrafficHandler.go b/internal/handler/admin/user/resetUserSubscribeTrafficHandler.go new file mode 100644 index 0000000..80dccfc --- /dev/null +++ b/internal/handler/admin/user/resetUserSubscribeTrafficHandler.go @@ -0,0 +1,26 @@ +package user + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Reset user subscribe traffic +func ResetUserSubscribeTrafficHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.ResetUserSubscribeTrafficRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := user.NewResetUserSubscribeTrafficLogic(c.Request.Context(), svcCtx) + err := l.ResetUserSubscribeTraffic(&req) + result.HttpResult(c, nil, err) + } +} diff --git a/internal/handler/admin/user/toggleUserSubscribeStatusHandler.go b/internal/handler/admin/user/toggleUserSubscribeStatusHandler.go new file mode 100644 index 0000000..5883e99 --- /dev/null +++ b/internal/handler/admin/user/toggleUserSubscribeStatusHandler.go @@ -0,0 +1,26 @@ +package user + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Stop user subscribe +func ToggleUserSubscribeStatusHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.ToggleUserSubscribeStatusRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := user.NewToggleUserSubscribeStatusLogic(c.Request.Context(), svcCtx) + err := l.ToggleUserSubscribeStatus(&req) + result.HttpResult(c, nil, err) + } +} diff --git a/internal/handler/routes.go b/internal/handler/routes.go index f235295..fcd86fa 100644 --- a/internal/handler/routes.go +++ b/internal/handler/routes.go @@ -33,7 +33,6 @@ import ( publicSubscribe "github.com/perfect-panel/server/internal/handler/public/subscribe" publicTicket "github.com/perfect-panel/server/internal/handler/public/ticket" publicUser "github.com/perfect-panel/server/internal/handler/public/user" - publicUserWs "github.com/perfect-panel/server/internal/handler/public/user/ws" server "github.com/perfect-panel/server/internal/handler/server" "github.com/perfect-panel/server/internal/middleware" "github.com/perfect-panel/server/internal/svc" @@ -312,12 +311,6 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { // Filter Server List 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 adminServerGroupRouter.POST("/node/create", adminServer.CreateNodeHandler(serverCtx)) @@ -583,6 +576,15 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { // Get user subcribe reset traffic logs adminUserGroupRouter.GET("/subscribe/reset/logs", adminUser.GetUserSubscribeResetTrafficLogsHandler(serverCtx)) + // Reset user subscribe token + adminUserGroupRouter.POST("/subscribe/reset/token", adminUser.ResetUserSubscribeTokenHandler(serverCtx)) + + // Reset user subscribe traffic + adminUserGroupRouter.POST("/subscribe/reset/traffic", adminUser.ResetUserSubscribeTrafficHandler(serverCtx)) + + // Stop user subscribe + adminUserGroupRouter.POST("/subscribe/toggle", adminUser.ToggleUserSubscribeStatusHandler(serverCtx)) + // Get user subcribe traffic logs adminUserGroupRouter.GET("/subscribe/traffic_logs", adminUser.GetUserSubscribeTrafficLogsHandler(serverCtx)) } @@ -872,14 +874,6 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { publicUserGroupRouter.GET("/withdrawal_log", publicUser.QueryWithdrawalLogHandler(serverCtx)) } - publicUserWsGroupRouter := router.Group("/v1/public/user") - publicUserWsGroupRouter.Use(middleware.AuthMiddleware(serverCtx)) - - { - // Webosocket Device Connect - publicUserWsGroupRouter.GET("/device_ws_connect", publicUserWs.DeviceWsConnectHandler(serverCtx)) - } - serverGroupRouter := router.Group("/v1/server") serverGroupRouter.Use(middleware.ServerMiddleware(serverCtx)) diff --git a/internal/handler/subscribe.go b/internal/handler/subscribe.go index 6c228ed..2db3297 100644 --- a/internal/handler/subscribe.go +++ b/internal/handler/subscribe.go @@ -23,6 +23,10 @@ func SubscribeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { ua := c.GetHeader("User-Agent") req.UA = c.Request.Header.Get("User-Agent") req.Flag = c.Query("flag") + req.Type = c.Query("type") + // 获取所有查询参数 + req.Params = getQueryMap(c.Request) + if svcCtx.Config.Subscribe.PanDomain { domain := c.Request.Host domainArr := strings.Split(domain, ".") @@ -33,7 +37,8 @@ func SubscribeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { c.Abort() return } - if short != domainArr[0] { + if strings.ToLower(short) != strings.ToLower(domainArr[0]) { + logger.Debugf("[SubscribeHandler] Generate short token failed, short: %s, domain: %s", short, domainArr[0]) c.String(http.StatusForbidden, "Access denied") c.Abort() return @@ -94,3 +99,14 @@ func RegisterSubscribeHandlers(router *gin.Engine, serverCtx *svc.ServiceContext } router.GET(path, SubscribeHandler(serverCtx)) } + +// GetQueryMap 将 http.Request 的查询参数转换为 map[string]string +func getQueryMap(r *http.Request) map[string]string { + result := make(map[string]string) + for k, v := range r.URL.Query() { + if len(v) > 0 { + result[k] = v[0] + } + } + return result +} diff --git a/internal/logic/admin/server/createNodeLogic.go b/internal/logic/admin/server/createNodeLogic.go index f635f85..78ce987 100644 --- a/internal/logic/admin/server/createNodeLogic.go +++ b/internal/logic/admin/server/createNodeLogic.go @@ -31,6 +31,7 @@ func (l *CreateNodeLogic) CreateNode(req *types.CreateNodeRequest) error { data := node.Node{ Name: req.Name, Tags: tool.StringSliceToString(req.Tags), + Enabled: req.Enabled, Port: req.Port, Address: req.Address, ServerId: req.ServerId, diff --git a/internal/logic/admin/server/hasMigrateSeverNodeLogic.go b/internal/logic/admin/server/hasMigrateSeverNodeLogic.go deleted file mode 100644 index 128b7f6..0000000 --- a/internal/logic/admin/server/hasMigrateSeverNodeLogic.go +++ /dev/null @@ -1,52 +0,0 @@ -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 -} diff --git a/internal/logic/admin/server/migrateServerNodeLogic.go b/internal/logic/admin/server/migrateServerNodeLogic.go deleted file mode 100644 index 4f0a497..0000000 --- a/internal/logic/admin/server/migrateServerNodeLogic.go +++ /dev/null @@ -1,338 +0,0 @@ -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{ - Id: info.Id, - 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, - Ratio: float64(info.TrafficRatio), - }) - 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, - Ratio: float64(info.TrafficRatio), - } - 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, - Ratio: float64(info.TrafficRatio), - } - 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, - Ratio: float64(info.TrafficRatio), - } - 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: "hysteria", - 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, - Ratio: float64(info.TrafficRatio), - } - 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, - Ratio: float64(info.TrafficRatio), - } - 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, - Ratio: float64(info.TrafficRatio), - } - 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 -} diff --git a/internal/logic/admin/subscribe/createSubscribeLogic.go b/internal/logic/admin/subscribe/createSubscribeLogic.go index bf50d6a..6309e2b 100644 --- a/internal/logic/admin/subscribe/createSubscribeLogic.go +++ b/internal/logic/admin/subscribe/createSubscribeLogic.go @@ -35,28 +35,29 @@ func (l *CreateSubscribeLogic) CreateSubscribe(req *types.CreateSubscribeRequest discount = string(val) } sub := &subscribe.Subscribe{ - Id: 0, - Name: req.Name, - Language: req.Language, - Description: req.Description, - UnitPrice: req.UnitPrice, - UnitTime: req.UnitTime, - Discount: discount, - Replacement: req.Replacement, - Inventory: req.Inventory, - Traffic: req.Traffic, - SpeedLimit: req.SpeedLimit, - DeviceLimit: req.DeviceLimit, - Quota: req.Quota, - Nodes: tool.Int64SliceToString(req.Nodes), - NodeTags: tool.StringSliceToString(req.NodeTags), - Show: req.Show, - Sell: req.Sell, - Sort: 0, - DeductionRatio: req.DeductionRatio, - AllowDeduction: req.AllowDeduction, - ResetCycle: req.ResetCycle, - RenewalReset: req.RenewalReset, + Id: 0, + Name: req.Name, + Language: req.Language, + Description: req.Description, + UnitPrice: req.UnitPrice, + UnitTime: req.UnitTime, + Discount: discount, + Replacement: req.Replacement, + Inventory: req.Inventory, + Traffic: req.Traffic, + SpeedLimit: req.SpeedLimit, + DeviceLimit: req.DeviceLimit, + Quota: req.Quota, + Nodes: tool.Int64SliceToString(req.Nodes), + NodeTags: tool.StringSliceToString(req.NodeTags), + Show: req.Show, + Sell: req.Sell, + Sort: 0, + DeductionRatio: req.DeductionRatio, + AllowDeduction: req.AllowDeduction, + ResetCycle: req.ResetCycle, + RenewalReset: req.RenewalReset, + ShowOriginalPrice: req.ShowOriginalPrice, } err := l.svcCtx.SubscribeModel.Insert(l.ctx, sub) if err != nil { diff --git a/internal/logic/admin/subscribe/updateSubscribeLogic.go b/internal/logic/admin/subscribe/updateSubscribeLogic.go index 060af5a..b79fdfe 100644 --- a/internal/logic/admin/subscribe/updateSubscribeLogic.go +++ b/internal/logic/admin/subscribe/updateSubscribeLogic.go @@ -43,28 +43,29 @@ func (l *UpdateSubscribeLogic) UpdateSubscribe(req *types.UpdateSubscribeRequest discount = string(val) } sub := &subscribe.Subscribe{ - Id: req.Id, - Name: req.Name, - Language: req.Language, - Description: req.Description, - UnitPrice: req.UnitPrice, - UnitTime: req.UnitTime, - Discount: discount, - Replacement: req.Replacement, - Inventory: req.Inventory, - Traffic: req.Traffic, - SpeedLimit: req.SpeedLimit, - DeviceLimit: req.DeviceLimit, - Quota: req.Quota, - Nodes: tool.Int64SliceToString(req.Nodes), - NodeTags: tool.StringSliceToString(req.NodeTags), - Show: req.Show, - Sell: req.Sell, - Sort: req.Sort, - DeductionRatio: req.DeductionRatio, - AllowDeduction: req.AllowDeduction, - ResetCycle: req.ResetCycle, - RenewalReset: req.RenewalReset, + Id: req.Id, + Name: req.Name, + Language: req.Language, + Description: req.Description, + UnitPrice: req.UnitPrice, + UnitTime: req.UnitTime, + Discount: discount, + Replacement: req.Replacement, + Inventory: req.Inventory, + Traffic: req.Traffic, + SpeedLimit: req.SpeedLimit, + DeviceLimit: req.DeviceLimit, + Quota: req.Quota, + Nodes: tool.Int64SliceToString(req.Nodes), + NodeTags: tool.StringSliceToString(req.NodeTags), + Show: req.Show, + Sell: req.Sell, + Sort: req.Sort, + DeductionRatio: req.DeductionRatio, + AllowDeduction: req.AllowDeduction, + ResetCycle: req.ResetCycle, + RenewalReset: req.RenewalReset, + ShowOriginalPrice: req.ShowOriginalPrice, } err = l.svcCtx.SubscribeModel.Update(l.ctx, sub) if err != nil { diff --git a/internal/logic/admin/system/getNodeConfigLogic.go b/internal/logic/admin/system/getNodeConfigLogic.go index 1212b1a..6037aa9 100644 --- a/internal/logic/admin/system/getNodeConfigLogic.go +++ b/internal/logic/admin/system/getNodeConfigLogic.go @@ -3,6 +3,7 @@ package system import ( "context" "encoding/json" + "github.com/perfect-panel/server/internal/config" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/types" diff --git a/internal/logic/admin/system/setNodeMultiplierLogic.go b/internal/logic/admin/system/setNodeMultiplierLogic.go index 78edf50..98033fe 100644 --- a/internal/logic/admin/system/setNodeMultiplierLogic.go +++ b/internal/logic/admin/system/setNodeMultiplierLogic.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" + "github.com/perfect-panel/server/initialize" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/types" "github.com/perfect-panel/server/pkg/logger" @@ -32,9 +33,12 @@ func (l *SetNodeMultiplierLogic) SetNodeMultiplier(req *types.SetNodeMultiplierR l.Logger.Error("Marshal Node Multiplier Config Error: ", logger.Field("error", err.Error())) return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Marshal Node Multiplier Config Error: %s", err.Error()) } - if err := l.svcCtx.SystemModel.UpdateNodeMultiplierConfig(l.ctx, string(data)); err != nil { + if err = l.svcCtx.SystemModel.UpdateNodeMultiplierConfig(l.ctx, string(data)); err != nil { l.Logger.Error("Update Node Multiplier Config Error: ", logger.Field("error", err.Error())) return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Update Node Multiplier Config Error: %s", err.Error()) } + // update Node Multiplier + initialize.Node(l.svcCtx) + return nil } diff --git a/internal/logic/admin/user/getUserListLogic.go b/internal/logic/admin/user/getUserListLogic.go index 3859f76..8935af1 100644 --- a/internal/logic/admin/user/getUserListLogic.go +++ b/internal/logic/admin/user/getUserListLogic.go @@ -30,6 +30,7 @@ func (l *GetUserListLogic) GetUserList(req *types.GetUserListRequest) (*types.Ge list, total, err := l.svcCtx.UserModel.QueryPageList(l.ctx, req.Page, req.Size, &user.UserFilterParams{ UserId: req.UserId, Search: req.Search, + Unscoped: req.Unscoped, SubscribeId: req.SubscribeId, UserSubscribeId: req.UserSubscribeId, Order: "DESC", diff --git a/internal/logic/admin/user/getUserSubscribeLogic.go b/internal/logic/admin/user/getUserSubscribeLogic.go index 2deb3ac..cd1e733 100644 --- a/internal/logic/admin/user/getUserSubscribeLogic.go +++ b/internal/logic/admin/user/getUserSubscribeLogic.go @@ -41,6 +41,7 @@ func (l *GetUserSubscribeLogic) GetUserSubscribe(req *types.GetUserSubscribeList for _, item := range data { var sub types.UserSubscribe tool.DeepCopy(&sub, item) + sub.Short, _ = tool.FixedUniqueString(item.Token, 8, "") resp.List = append(resp.List, sub) } return diff --git a/internal/logic/admin/user/resetUserSubscribeTokenLogic.go b/internal/logic/admin/user/resetUserSubscribeTokenLogic.go new file mode 100644 index 0000000..02f46ca --- /dev/null +++ b/internal/logic/admin/user/resetUserSubscribeTokenLogic.go @@ -0,0 +1,55 @@ +package user + +import ( + "context" + "fmt" + "time" + + "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/uuidx" + "github.com/perfect-panel/server/pkg/xerr" + "github.com/pkg/errors" +) + +type ResetUserSubscribeTokenLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewResetUserSubscribeTokenLogic Reset user subscribe token +func NewResetUserSubscribeTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ResetUserSubscribeTokenLogic { + return &ResetUserSubscribeTokenLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *ResetUserSubscribeTokenLogic) ResetUserSubscribeToken(req *types.ResetUserSubscribeTokenRequest) error { + userSub, err := l.svcCtx.UserModel.FindOneSubscribe(l.ctx, req.UserSubscribeId) + if err != nil { + logger.Errorf("[ResetUserSubscribeToken] FindOneSubscribe error: %v", err.Error()) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindOneSubscribe error: %v", err.Error()) + } + userSub.Token = uuidx.SubscribeToken(fmt.Sprintf("AdminUpdate:%d", time.Now().UnixMilli())) + + err = l.svcCtx.UserModel.UpdateSubscribe(l.ctx, userSub) + if err != nil { + logger.Errorf("[ResetUserSubscribeToken] UpdateSubscribe error: %v", err.Error()) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "UpdateSubscribe error: %v", err.Error()) + } + // Clear user subscribe cache + if err = l.svcCtx.UserModel.ClearSubscribeCache(l.ctx, userSub); err != nil { + l.Errorw("ClearSubscribeCache failed:", logger.Field("error", err.Error()), logger.Field("userSubscribeId", userSub.Id)) + return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "ClearSubscribeCache failed: %v", err.Error()) + } + // Clear subscribe cache + if err = l.svcCtx.SubscribeModel.ClearCache(l.ctx, userSub.SubscribeId); err != nil { + l.Errorw("failed to clear subscribe cache", logger.Field("error", err.Error()), logger.Field("subscribeId", userSub.SubscribeId)) + return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "failed to clear subscribe cache: %v", err.Error()) + } + return nil +} diff --git a/internal/logic/admin/user/resetUserSubscribeTrafficLogic.go b/internal/logic/admin/user/resetUserSubscribeTrafficLogic.go new file mode 100644 index 0000000..5b90147 --- /dev/null +++ b/internal/logic/admin/user/resetUserSubscribeTrafficLogic.go @@ -0,0 +1,53 @@ +package user + +import ( + "context" + + "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 ResetUserSubscribeTrafficLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewResetUserSubscribeTrafficLogic Reset user subscribe traffic +func NewResetUserSubscribeTrafficLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ResetUserSubscribeTrafficLogic { + return &ResetUserSubscribeTrafficLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *ResetUserSubscribeTrafficLogic) ResetUserSubscribeTraffic(req *types.ResetUserSubscribeTrafficRequest) error { + userSub, err := l.svcCtx.UserModel.FindOneSubscribe(l.ctx, req.UserSubscribeId) + if err != nil { + l.Errorw("FindOneSubscribe error", logger.Field("error", err.Error()), logger.Field("userSubscribeId", req.UserSubscribeId)) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), " FindOneSubscribe error: %v", err.Error()) + } + userSub.Download = 0 + userSub.Upload = 0 + + err = l.svcCtx.UserModel.UpdateSubscribe(l.ctx, userSub) + if err != nil { + l.Errorw("UpdateSubscribe error", logger.Field("error", err.Error()), logger.Field("userSubscribeId", req.UserSubscribeId)) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), " UpdateSubscribe error: %v", err.Error()) + } + // Clear user subscribe cache + if err = l.svcCtx.UserModel.ClearSubscribeCache(l.ctx, userSub); err != nil { + l.Errorw("ClearSubscribeCache failed:", logger.Field("error", err.Error()), logger.Field("userSubscribeId", userSub.Id)) + return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "ClearSubscribeCache failed: %v", err.Error()) + } + // Clear subscribe cache + if err = l.svcCtx.SubscribeModel.ClearCache(l.ctx, userSub.SubscribeId); err != nil { + l.Errorw("failed to clear subscribe cache", logger.Field("error", err.Error()), logger.Field("subscribeId", userSub.SubscribeId)) + return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "failed to clear subscribe cache: %v", err.Error()) + } + return nil +} diff --git a/internal/logic/admin/user/toggleUserSubscribeStatusLogic.go b/internal/logic/admin/user/toggleUserSubscribeStatusLogic.go new file mode 100644 index 0000000..5f06e98 --- /dev/null +++ b/internal/logic/admin/user/toggleUserSubscribeStatusLogic.go @@ -0,0 +1,63 @@ +package user + +import ( + "context" + + "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 ToggleUserSubscribeStatusLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewToggleUserSubscribeStatusLogic Stop user subscribe +func NewToggleUserSubscribeStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ToggleUserSubscribeStatusLogic { + return &ToggleUserSubscribeStatusLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *ToggleUserSubscribeStatusLogic) ToggleUserSubscribeStatus(req *types.ToggleUserSubscribeStatusRequest) error { + userSub, err := l.svcCtx.UserModel.FindOneSubscribe(l.ctx, req.UserSubscribeId) + if err != nil { + l.Errorw("FindOneSubscribe error", logger.Field("error", err.Error()), logger.Field("userSubscribeId", req.UserSubscribeId)) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), " FindOneSubscribe error: %v", err.Error()) + } + + switch userSub.Status { + case 2: // active + userSub.Status = 5 // set status to stopped + case 5: // stopped + userSub.Status = 2 // set status to active + default: + l.Errorw("invalid user subscribe status", logger.Field("userSubscribeId", req.UserSubscribeId), logger.Field("status", userSub.Status)) + return errors.Wrapf(xerr.NewErrCodeMsg(xerr.ERROR, "invalid subscribe status"), "invalid user subscribe status: %d", userSub.Status) + } + + err = l.svcCtx.UserModel.UpdateSubscribe(l.ctx, userSub) + if err != nil { + l.Errorw("UpdateSubscribe error", logger.Field("error", err.Error()), logger.Field("userSubscribeId", req.UserSubscribeId)) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), " UpdateSubscribe error: %v", err.Error()) + } + + // Clear user subscribe cache + if err = l.svcCtx.UserModel.ClearSubscribeCache(l.ctx, userSub); err != nil { + l.Errorw("ClearSubscribeCache failed:", logger.Field("error", err.Error()), logger.Field("userSubscribeId", userSub.Id)) + return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "ClearSubscribeCache failed: %v", err.Error()) + } + // Clear subscribe cache + if err = l.svcCtx.SubscribeModel.ClearCache(l.ctx, userSub.SubscribeId); err != nil { + l.Errorw("failed to clear subscribe cache", logger.Field("error", err.Error()), logger.Field("subscribeId", userSub.SubscribeId)) + return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "failed to clear subscribe cache: %v", err.Error()) + } + + return nil +} diff --git a/internal/logic/auth/bindDeviceLogic.go b/internal/logic/auth/bindDeviceLogic.go index 391c080..34b35d3 100644 --- a/internal/logic/auth/bindDeviceLogic.go +++ b/internal/logic/auth/bindDeviceLogic.go @@ -2,6 +2,7 @@ package auth import ( "context" + "time" "github.com/perfect-panel/server/internal/model/user" "github.com/perfect-panel/server/internal/svc" @@ -206,6 +207,86 @@ func (l *BindDeviceLogic) rebindDeviceToNewUser(deviceInfo *user.Device, ip, use //如果没有其他认证方式,禁用旧用户账号 if count < 1 { + //检查设备下是否有套餐,有套餐。就检查即将绑定过去的所有账户是否有套餐,如果有,那么检查两个套餐是否一致。如果一致就将即将删除的用户套餐,时间叠加到我绑定过去的用户套餐上面(如果套餐已过期就忽略)。新绑定设备的账户上套餐不一致或者不存在直接将套餐换绑即可 + var oldUserSubscribes []user.Subscribe + err = tx.Where("user_id = ? AND status IN ?", oldUserId, []int64{0, 1}).Find(&oldUserSubscribes).Error + if err != nil { + l.Errorw("failed to query old user subscribes", + logger.Field("old_user_id", oldUserId), + logger.Field("error", err.Error()), + ) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query old user subscribes failed: %v", err) + } + + if len(oldUserSubscribes) > 0 { + l.Infow("processing old user subscribes", + logger.Field("old_user_id", oldUserId), + logger.Field("new_user_id", newUserId), + logger.Field("subscribe_count", len(oldUserSubscribes)), + ) + + for _, oldSub := range oldUserSubscribes { + // 检查新用户是否有相同套餐ID的订阅 + var newUserSub user.Subscribe + err = tx.Where("user_id = ? AND subscribe_id = ? AND status IN ?", newUserId, oldSub.SubscribeId, []int64{0, 1}).First(&newUserSub).Error + + if err != nil { + // 新用户没有该套餐,直接换绑 + oldSub.UserId = newUserId + if err := tx.Save(&oldSub).Error; err != nil { + l.Errorw("failed to rebind subscribe to new user", + logger.Field("subscribe_id", oldSub.Id), + logger.Field("old_user_id", oldUserId), + logger.Field("new_user_id", newUserId), + logger.Field("error", err.Error()), + ) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "rebind subscribe failed: %v", err) + } + l.Infow("rebind subscribe to new user", + logger.Field("subscribe_id", oldSub.Id), + logger.Field("new_user_id", newUserId), + ) + } else { + // 新用户已有该套餐,检查旧套餐是否过期 + now := time.Now() + if oldSub.ExpireTime.After(now) { + // 旧套餐未过期,叠加剩余时间 + remainingDuration := oldSub.ExpireTime.Sub(now) + if newUserSub.ExpireTime.After(now) { + // 新套餐未过期,叠加时间 + newUserSub.ExpireTime = newUserSub.ExpireTime.Add(remainingDuration) + } else { + newUserSub.ExpireTime = time.Now().Add(remainingDuration) + } + if err := tx.Save(&newUserSub).Error; err != nil { + l.Errorw("failed to update subscribe expire time", + logger.Field("subscribe_id", newUserSub.Id), + logger.Field("error", err.Error()), + ) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update subscribe expire time failed: %v", err) + } + l.Infow("merged subscribe time", + logger.Field("subscribe_id", newUserSub.Id), + logger.Field("new_expire_time", newUserSub.ExpireTime), + ) + } else { + l.Infow("old subscribe expired, skip merge", + logger.Field("subscribe_id", oldSub.Id), + logger.Field("expire_time", oldSub.ExpireTime), + ) + } + // 删除旧用户的套餐 + if err := tx.Delete(&oldSub).Error; err != nil { + l.Errorw("failed to delete old subscribe", + logger.Field("subscribe_id", oldSub.Id), + logger.Field("error", err.Error()), + ) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete old subscribe failed: %v", err) + } + } + } + } + if err := tx.Model(&user.User{}).Where("id = ?", oldUserId).Delete(&user.User{}).Error; err != nil { l.Errorw("failed to disable old user", logger.Field("old_user_id", oldUserId), diff --git a/internal/logic/auth/userLoginLogic.go b/internal/logic/auth/userLoginLogic.go index bf2ed2d..4e6fac2 100644 --- a/internal/logic/auth/userLoginLogic.go +++ b/internal/logic/auth/userLoginLogic.go @@ -68,6 +68,10 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log userInfo, err = l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email) + if userInfo.DeletedAt.Valid { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserNotExist), "user email deleted: %v", req.Email) + } + if err != nil { if errors.As(err, &gorm.ErrRecordNotFound) { return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserNotExist), "user email not exist: %v", req.Email) diff --git a/internal/logic/auth/userRegisterLogic.go b/internal/logic/auth/userRegisterLogic.go index 0b5a4b0..e128a69 100644 --- a/internal/logic/auth/userRegisterLogic.go +++ b/internal/logic/auth/userRegisterLogic.go @@ -79,12 +79,14 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp * } } // Check if the user exists - _, err = l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email) + u, err := l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { l.Errorw("FindOneByEmail Error", logger.Field("error", err)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query user info failed: %v", err.Error()) - } else if err == nil { + } else if err == nil && !u.DeletedAt.Valid { return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserExist), "user email exist: %v", req.Email) + } else if err == nil && u.DeletedAt.Valid { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserDisabled), "user email deleted: %v", req.Email) } if !registerIpLimit(l.svcCtx, l.ctx, req.IP, "email", req.Email) { @@ -154,9 +156,8 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp * time.Now().Unix(), l.svcCtx.Config.JwtAuth.AccessExpire, jwt.WithOption("UserId", userInfo.Id), - jwt.WithOption("identifier", req.Identifier), jwt.WithOption("SessionId", sessionId), - jwt.WithOption("CtxLoginType", req.LoginType), + jwt.WithOption("LoginType", req.LoginType), ) if err != nil { l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error())) @@ -237,12 +238,5 @@ func (l *UserRegisterLogic) activeTrial(uid int64) error { UUID: uuidx.NewUUID().String(), Status: 1, } - err = l.svcCtx.UserModel.InsertSubscribe(l.ctx, userSub) - if err != nil { - return err - } - if clearErr := l.svcCtx.NodeModel.ClearServerAllCache(l.ctx); clearErr != nil { - l.Errorf("ClearServerAllCache error: %v", clearErr.Error()) - } - return err + return l.svcCtx.UserModel.InsertSubscribe(l.ctx, userSub) } diff --git a/internal/logic/common/getGlobalConfigLogic.go b/internal/logic/common/getGlobalConfigLogic.go index 61b2c1e..1b55898 100644 --- a/internal/logic/common/getGlobalConfigLogic.go +++ b/internal/logic/common/getGlobalConfigLogic.go @@ -51,6 +51,8 @@ func (l *GetGlobalConfigLogic) GetGlobalConfig() (resp *types.GetGlobalConfigRes tool.SystemConfigSliceReflectToStruct(currencyCfg, &resp.Currency) tool.SystemConfigSliceReflectToStruct(verifyCodeCfg, &resp.VerifyCode) + resp.Subscribe.SubscribePath = "/sub" + l.svcCtx.Config.Subscribe.SubscribePath + resp.Verify = types.VeifyConfig{ TurnstileSiteKey: l.svcCtx.Config.Verify.TurnstileSiteKey, EnableLoginVerify: l.svcCtx.Config.Verify.LoginVerify, diff --git a/internal/logic/common/getStatLogic.go b/internal/logic/common/getStatLogic.go index df14af0..f97223d 100644 --- a/internal/logic/common/getStatLogic.go +++ b/internal/logic/common/getStatLogic.go @@ -11,7 +11,7 @@ import ( "time" "github.com/perfect-panel/server/internal/config" - "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/svc" "github.com/perfect-panel/server/internal/types" @@ -57,13 +57,13 @@ func (l *GetStatLogic) GetStat() (resp *types.GetStatResponse, err error) { u = 1 } var n int64 - err = l.svcCtx.DB.Model(&server.Server{}).Where("enable = 1").Count(&n).Error + err = l.svcCtx.DB.Model(&node.Node{}).Where("enabled = 1").Count(&n).Error if err != nil { l.Logger.Error("[GetStatLogic] get server count failed: ", logger.Field("error", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get server count failed: %v", err.Error()) } var nodeaddr []string - err = l.svcCtx.DB.Model(&server.Server{}).Where("enable = 1").Pluck("server_addr", &nodeaddr).Error + err = l.svcCtx.DB.Model(&node.Server{}).Pluck("address", &nodeaddr).Error if err != nil { l.Logger.Error("[GetStatLogic] get server_addr failed: ", logger.Field("error", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get server_addr failed: %v", err.Error()) @@ -111,9 +111,23 @@ func (l *GetStatLogic) GetStat() (resp *types.GetStatResponse, err error) { } protocolDict := make(map[string]void) var protocol []string - l.svcCtx.DB.Model(&server.Server{}).Where("enable = true").Pluck("protocol", &protocol) + err = l.svcCtx.DB.Model(&node.Node{}).Where("enabled = true").Pluck("protocol", &protocol).Error + if err != nil { + l.Logger.Error("[GetStatLogic] get protocol failed: ", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get protocol failed: %v", err.Error()) + } + for _, p := range protocol { - protocolDict[p] = v + var protocols []node.Protocol + err = json.Unmarshal([]byte(p), &protocols) + if err != nil { + continue + } + for _, proto := range protocols { + if _, exists := protocolDict[proto.Type]; !exists { + protocolDict[proto.Type] = v + } + } } protocol = nil for p := range protocolDict { diff --git a/internal/logic/public/order/closeOrderLogic.go b/internal/logic/public/order/closeOrderLogic.go index ced53b8..dd7ea13 100644 --- a/internal/logic/public/order/closeOrderLogic.go +++ b/internal/logic/public/order/closeOrderLogic.go @@ -51,6 +51,16 @@ func (l *CloseOrderLogic) CloseOrder(req *types.CloseOrderRequest) error { ) return nil } + + sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, orderInfo.SubscribeId) + if err != nil { + l.Errorw("[CloseOrder] Find subscribe info failed", + logger.Field("error", err.Error()), + logger.Field("subscribeId", orderInfo.SubscribeId), + ) + return nil + } + err = l.svcCtx.DB.Transaction(func(tx *gorm.DB) error { // update order status err := tx.Model(&order.Order{}).Where("order_no = ?", req.OrderNo).Update("status", 3).Error @@ -124,9 +134,21 @@ func (l *CloseOrderLogic) CloseOrder(req *types.CloseOrderRequest) error { // update user cache return l.svcCtx.UserModel.UpdateUserCache(l.ctx, userInfo) } + if sub.Inventory != -1 { + sub.Inventory++ + if e := l.svcCtx.SubscribeModel.Update(l.ctx, sub, tx); e != nil { + l.Errorw("[CloseOrder] Restore subscribe inventory failed", + logger.Field("error", e.Error()), + logger.Field("subscribeId", sub.Id), + ) + return e + } + } + return nil }) if err != nil { + logger.Errorf("[CloseOrder] Transaction failed: %v", err.Error()) return err } return nil diff --git a/internal/logic/public/order/purchaseLogic.go b/internal/logic/public/order/purchaseLogic.go index 519a80a..cbc960f 100644 --- a/internal/logic/public/order/purchaseLogic.go +++ b/internal/logic/public/order/purchaseLogic.go @@ -81,6 +81,12 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P if !*sub.Sell { return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "subscribe not sell") } + + // check subscribe plan inventory + if sub.Inventory == 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SubscribeOutOfStock), "subscribe out of stock") + } + // check subscribe plan limit if sub.Quota > 0 { var count int64 @@ -221,10 +227,23 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P return e } } + + if sub.Inventory != -1 { + // decrease subscribe plan stock + sub.Inventory -= 1 + // update subscribe plan stock + if err = l.svcCtx.SubscribeModel.Update(l.ctx, sub, db); err != nil { + l.Errorw("[Purchase] Database update error", logger.Field("error", err.Error()), logger.Field("subscribe", sub)) + return err + } + } + // insert order return db.WithContext(l.ctx).Model(&order.Order{}).Create(&orderInfo).Error }) if err != nil { + l.Errorw("[Purchase] Database insert error", logger.Field("error", err.Error()), logger.Field("orderInfo", orderInfo)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert order error: %v", err.Error()) } // Deferred task diff --git a/internal/logic/public/portal/purchaseCheckoutLogic.go b/internal/logic/public/portal/purchaseCheckoutLogic.go index ff7faf2..62c2c11 100644 --- a/internal/logic/public/portal/purchaseCheckoutLogic.go +++ b/internal/logic/public/portal/purchaseCheckoutLogic.go @@ -9,6 +9,7 @@ import ( "github.com/perfect-panel/server/internal/model/log" "github.com/perfect-panel/server/internal/report" "github.com/perfect-panel/server/pkg/constant" + "github.com/perfect-panel/server/pkg/exchangeRate" paymentPlatform "github.com/perfect-panel/server/pkg/payment" @@ -21,12 +22,10 @@ import ( "github.com/perfect-panel/server/internal/model/payment" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/types" - "github.com/perfect-panel/server/pkg/exchangeRate" "github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/payment/alipay" "github.com/perfect-panel/server/pkg/payment/epay" "github.com/perfect-panel/server/pkg/payment/stripe" - "github.com/perfect-panel/server/pkg/tool" "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" ) @@ -261,6 +260,7 @@ func (l *PurchaseCheckoutLogic) stripePayment(config string, info *order.Order, // epayPayment processes EPay payment by generating a payment URL for redirect // It handles currency conversion and creates a payment URL for external payment processing func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order.Order, returnUrl string) (string, error) { + var err error // Parse EPay configuration from payment settings epayConfig := &payment.EPayConfig{} if err := epayConfig.Unmarshal([]byte(config.Config)); err != nil { @@ -269,15 +269,18 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order } // Initialize EPay client with merchant credentials client := epay.NewClient(epayConfig.Pid, epayConfig.Url, epayConfig.Key, epayConfig.Type) - - // Convert order amount to CNY using current exchange rate - amount, err := l.queryExchangeRate("CNY", info.Amount) - if err != nil { - return "", err + var amount float64 + if l.svcCtx.Config.Currency.Unit != "CNY" { + // Convert order amount to CNY using current exchange rate + amount, err = l.queryExchangeRate("CNY", info.Amount) + if err != nil { + return "", err + } + } else { + amount = float64(info.Amount) / float64(100) } // gateway mod - isGatewayMod := report.IsGatewayMode() // Build notification URL for payment status callbacks @@ -293,7 +296,6 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order if !ok { host = l.svcCtx.Config.Host } - notifyUrl = "https://" + host if isGatewayMod { notifyUrl += "/api" @@ -316,6 +318,7 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order // CryptoSaaSPayment processes CryptoSaaSPayment payment by generating a payment URL for redirect // It handles currency conversion and creates a payment URL for external payment processing func (l *PurchaseCheckoutLogic) CryptoSaaSPayment(config *payment.Payment, info *order.Order, returnUrl string) (string, error) { + var err error // Parse EPay configuration from payment settings epayConfig := &payment.CryptoSaaSConfig{} if err := epayConfig.Unmarshal([]byte(config.Config)); err != nil { @@ -325,10 +328,16 @@ func (l *PurchaseCheckoutLogic) CryptoSaaSPayment(config *payment.Payment, info // Initialize EPay client with merchant credentials client := epay.NewClient(epayConfig.AccountID, epayConfig.Endpoint, epayConfig.SecretKey, epayConfig.Type) - // Convert order amount to CNY using current exchange rate - amount, err := l.queryExchangeRate("CNY", info.Amount) - if err != nil { - return "", err + var amount float64 + + if l.svcCtx.Config.Currency.Unit != "CNY" { + // Convert order amount to CNY using current exchange rate + amount, err = l.queryExchangeRate("CNY", info.Amount) + if err != nil { + return "", err + } + } else { + amount = float64(info.Amount) / float64(100) } // gateway mod @@ -354,6 +363,7 @@ func (l *PurchaseCheckoutLogic) CryptoSaaSPayment(config *payment.Payment, info } notifyUrl = notifyUrl + "/v1/notify/" + config.Platform + "/" + config.Token } + // Create payment URL for user redirection url := client.CreatePayUrl(epay.Order{ Name: l.svcCtx.Config.Site.SiteName, @@ -377,35 +387,18 @@ func (l *PurchaseCheckoutLogic) queryExchangeRate(to string, src int64) (amount return amount, nil } - // Retrieve system currency configuration - currency, err := l.svcCtx.SystemModel.GetCurrencyConfig(l.ctx) - if err != nil { - l.Errorw("[PurchaseCheckout] GetCurrencyConfig error", logger.Field("error", err.Error())) - return 0, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetCurrencyConfig error: %s", err.Error()) - } - - // Parse currency configuration - configs := struct { - CurrencyUnit string - CurrencySymbol string - AccessKey string - }{} - tool.SystemConfigSliceReflectToStruct(currency, &configs) - // Skip conversion if no exchange rate API key configured - if configs.AccessKey == "" { + if l.svcCtx.Config.Currency.AccessKey == "" { return amount, nil } // Convert currency if system currency differs from target currency - if configs.CurrencyUnit != to { - result, err := exchangeRate.GetExchangeRete(configs.CurrencyUnit, to, configs.AccessKey, 1) - if err != nil { - return 0, err - } - amount = result * amount + result, err := exchangeRate.GetExchangeRete(l.svcCtx.Config.Currency.Unit, to, l.svcCtx.Config.Currency.AccessKey, 1) + if err != nil { + return 0, err } - return amount, nil + l.svcCtx.ExchangeRate = result + return result * amount, nil } // balancePayment processes balance payment with gift amount priority logic diff --git a/internal/logic/public/portal/purchaseLogic.go b/internal/logic/public/portal/purchaseLogic.go index 322f94c..c21cb69 100644 --- a/internal/logic/public/portal/purchaseLogic.go +++ b/internal/logic/public/portal/purchaseLogic.go @@ -55,6 +55,12 @@ func (l *PurchaseLogic) Purchase(req *types.PortalPurchaseRequest) (resp *types. l.Errorw("[Purchase] Database query error", logger.Field("error", err.Error()), logger.Field("subscribe_id", req.SubscribeId)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe error: %v", err.Error()) } + + // check subscribe plan stock + if sub.Inventory == 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SubscribeOutOfStock), "subscribe out of stock") + } + // check subscribe plan status if !*sub.Sell { return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "subscribe not sell") @@ -149,6 +155,16 @@ func (l *PurchaseLogic) Purchase(req *types.PortalPurchaseRequest) (resp *types. return err } l.Infow("[Purchase] Guest order", logger.Field("order_no", orderInfo.OrderNo), logger.Field("identifier", req.Identifier)) + + // Decrease subscribe plan stock + if sub.Inventory != -1 { + sub.Inventory-- + if e := l.svcCtx.SubscribeModel.Update(l.ctx, sub, tx); e != nil { + l.Errorw("[Purchase] Database update error", logger.Field("error", e.Error()), logger.Field("subscribe_id", sub.Id)) + return e + } + } + // save guest order if err = l.svcCtx.OrderModel.Insert(l.ctx, orderInfo, tx); err != nil { return err diff --git a/internal/logic/server/serverPushUserTrafficLogic.go b/internal/logic/server/serverPushUserTrafficLogic.go index c6ab4e6..53bc9be 100644 --- a/internal/logic/server/serverPushUserTrafficLogic.go +++ b/internal/logic/server/serverPushUserTrafficLogic.go @@ -51,7 +51,7 @@ func (l *ServerPushUserTrafficLogic) ServerPushUserTraffic(req *types.ServerPush if err != nil { l.Errorw("[ServerPushUserTraffic] Push traffic task error", logger.Field("error", err.Error()), logger.Field("task", t)) } else { - l.Infow("[ServerPushUserTraffic] Push traffic task success", logger.Field("task", t), logger.Field("info", info)) + l.Infow("[ServerPushUserTraffic] Push traffic task success", logger.Field("task", t.Type()), logger.Field("info", string(info.Payload))) } // Update server last reported time diff --git a/internal/logic/subscribe/subscribeLogic.go b/internal/logic/subscribe/subscribeLogic.go index 6a4a6e5..28f9ecb 100644 --- a/internal/logic/subscribe/subscribeLogic.go +++ b/internal/logic/subscribe/subscribeLogic.go @@ -10,6 +10,7 @@ import ( "github.com/perfect-panel/server/internal/model/client" "github.com/perfect-panel/server/internal/model/log" "github.com/perfect-panel/server/internal/model/node" + "github.com/perfect-panel/server/internal/report" "github.com/perfect-panel/server/internal/model/user" @@ -106,10 +107,13 @@ func (l *SubscribeLogic) Handler(req *types.SubscribeRequest) (resp *types.Subsc Download: userSubscribe.Download, Upload: userSubscribe.Upload, Traffic: userSubscribe.Traffic, - SubscribeURL: l.getSubscribeV2URL(req.Token), + SubscribeURL: l.getSubscribeV2URL(), }), + adapter.WithParams(req.Params), ) + logger.Debugf("[SubscribeLogic] Building client config for user %d with URI %s", userSubscribe.UserId, l.getSubscribeV2URL()) + // Get client config adapterClient, err := a.Client() if err != nil { @@ -143,17 +147,20 @@ func (l *SubscribeLogic) Handler(req *types.SubscribeRequest) (resp *types.Subsc return } -func (l *SubscribeLogic) getSubscribeV2URL(token string) string { - if l.svc.Config.Subscribe.PanDomain { - return fmt.Sprintf("https://%s", l.ctx.Request.Host) - } +func (l *SubscribeLogic) getSubscribeV2URL() string { + uri := l.ctx.Request.RequestURI + // is gateway mode, add /sub prefix + if report.IsGatewayMode() { + uri = "/sub" + uri + } + // use custom domain if configured if l.svc.Config.Subscribe.SubscribeDomain != "" { domains := strings.Split(l.svc.Config.Subscribe.SubscribeDomain, "\n") - return fmt.Sprintf("https://%s%s?token=%s", domains[0], l.svc.Config.Subscribe.SubscribePath, token) + return fmt.Sprintf("https://%s%s", domains[0], uri) } - - return fmt.Sprintf("https://%s%s?token=%s&", l.ctx.Request.Host, l.svc.Config.Subscribe.SubscribePath, token) + // use current request host + return fmt.Sprintf("https://%s%s", l.ctx.Request.Host, uri) } func (l *SubscribeLogic) getUserSubscribe(token string) (*user.Subscribe, error) { @@ -209,13 +216,16 @@ func (l *SubscribeLogic) getServers(userSub *user.Subscribe) ([]*node.Node, erro } nodeIds := tool.StringToInt64Slice(subDetails.Nodes) - tags := strings.Split(subDetails.NodeTags, ",") - - l.Debugf("[Generate Subscribe]nodes: %v, NodeTags: %v", nodeIds, tags) + tags := tool.RemoveStringElement(strings.Split(subDetails.NodeTags, ","), "") + l.Debugf("[Generate Subscribe]nodes: %v, NodeTags: %v", len(nodeIds), len(tags)) + if len(nodeIds) == 0 && len(tags) == 0 { + logger.Infow("[Generate Subscribe]no subscribe nodes") + return []*node.Node{}, nil + } enable := true - - _, nodes, err := l.svc.NodeModel.FilterNodeList(l.ctx.Request.Context(), &node.FilterNodeParams{ + var nodes []*node.Node + _, nodes, err = l.svc.NodeModel.FilterNodeList(l.ctx.Request.Context(), &node.FilterNodeParams{ Page: 1, Size: 1000, NodeId: nodeIds, diff --git a/internal/model/announcement/model.go b/internal/model/announcement/model.go index 973fc97..6d83262 100644 --- a/internal/model/announcement/model.go +++ b/internal/model/announcement/model.go @@ -27,6 +27,9 @@ type Filter struct { // GetAnnouncementListByPage get announcement list by page func (m *customAnnouncementModel) GetAnnouncementListByPage(ctx context.Context, page, size int, filter Filter) (int64, []*Announcement, error) { + if size == 0 { + size = 10 + } var list []*Announcement var total int64 err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error { diff --git a/internal/model/server/default.go b/internal/model/server/default.go deleted file mode 100644 index b013f1a..0000000 --- a/internal/model/server/default.go +++ /dev/null @@ -1,141 +0,0 @@ -package server - -import ( - "context" - "errors" - "fmt" - - "github.com/perfect-panel/server/pkg/cache" - "github.com/redis/go-redis/v9" - "gorm.io/gorm" -) - -var _ Model = (*customServerModel)(nil) -var ( - cacheServerIdPrefix = "cache:server:id:" -) - -type ( - Model interface { - serverModel - customServerLogicModel - } - serverModel interface { - Insert(ctx context.Context, data *Server, tx ...*gorm.DB) error - FindOne(ctx context.Context, id int64) (*Server, error) - Update(ctx context.Context, data *Server, tx ...*gorm.DB) error - Delete(ctx context.Context, id int64, tx ...*gorm.DB) error - Transaction(ctx context.Context, fn func(db *gorm.DB) error) error - } - - customServerModel struct { - *defaultServerModel - } - defaultServerModel struct { - cache.CachedConn - table string - } -) - -func newServerModel(db *gorm.DB, c *redis.Client) *defaultServerModel { - return &defaultServerModel{ - CachedConn: cache.NewConn(db, c), - table: "`Server`", - } -} - -// NewModel returns a model for the database table. -func NewModel(conn *gorm.DB, c *redis.Client) Model { - return &customServerModel{ - defaultServerModel: newServerModel(conn, c), - } -} - -//nolint:unused -func (m *defaultServerModel) batchGetCacheKeys(Servers ...*Server) []string { - var keys []string - for _, server := range Servers { - keys = append(keys, m.getCacheKeys(server)...) - } - return keys - -} - -func (m *defaultServerModel) getCacheKeys(data *Server) []string { - if data == nil { - return []string{} - } - detailsKey := fmt.Sprintf("%s%v", CacheServerDetailPrefix, data.Id) - ServerIdKey := fmt.Sprintf("%s%v", cacheServerIdPrefix, data.Id) - //configIdKey := fmt.Sprintf("%s%v", config.ServerConfigCacheKey, data.Id) - //userIDKey := fmt.Sprintf("%s%d", config.ServerUserListCacheKey, data.Id) - - // query protocols to get config keys - - cacheKeys := []string{ - ServerIdKey, - detailsKey, - //configIdKey, - //userIDKey, - } - return cacheKeys -} - -func (m *defaultServerModel) Insert(ctx context.Context, data *Server, tx ...*gorm.DB) error { - err := m.ExecCtx(ctx, func(conn *gorm.DB) error { - if len(tx) > 0 { - conn = tx[0] - } - return conn.Create(&data).Error - }, m.getCacheKeys(data)...) - return err -} - -func (m *defaultServerModel) FindOne(ctx context.Context, id int64) (*Server, error) { - ServerIdKey := fmt.Sprintf("%s%v", cacheServerIdPrefix, id) - var resp Server - err := m.QueryCtx(ctx, &resp, ServerIdKey, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&Server{}).Where("`id` = ?", id).First(&resp).Error - }) - switch { - case err == nil: - return &resp, nil - default: - return nil, err - } -} - -func (m *defaultServerModel) Update(ctx context.Context, data *Server, tx ...*gorm.DB) error { - old, err := m.FindOne(ctx, data.Id) - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - return err - } - err = m.ExecCtx(ctx, func(conn *gorm.DB) error { - if len(tx) > 0 { - conn = tx[0] - } - return conn.Save(data).Error - }, m.getCacheKeys(old)...) - return err -} - -func (m *defaultServerModel) Delete(ctx context.Context, id int64, tx ...*gorm.DB) error { - data, err := m.FindOne(ctx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil - } - return err - } - err = m.ExecCtx(ctx, func(conn *gorm.DB) error { - if len(tx) > 0 { - conn = tx[0] - } - return conn.Delete(&Server{}, id).Error - }, m.getCacheKeys(data)...) - return err -} - -func (m *defaultServerModel) Transaction(ctx context.Context, fn func(db *gorm.DB) error) error { - return m.TransactCtx(ctx, fn) -} diff --git a/internal/model/server/model.go b/internal/model/server/model.go deleted file mode 100644 index 58ae7b7..0000000 --- a/internal/model/server/model.go +++ /dev/null @@ -1,292 +0,0 @@ -package server - -import ( - "context" - "fmt" - "strings" - - "gorm.io/gorm" -) - -type customServerLogicModel interface { - FindServerListByFilter(ctx context.Context, filter *ServerFilter) (total int64, list []*Server, err error) - ClearCache(ctx context.Context, id int64) error - QueryServerCountByServerGroups(ctx context.Context, groupIds []int64) (int64, error) - QueryAllGroup(ctx context.Context) ([]*Group, error) - BatchDeleteNodeGroup(ctx context.Context, ids []int64) error - InsertGroup(ctx context.Context, data *Group) error - FindOneGroup(ctx context.Context, id int64) (*Group, error) - UpdateGroup(ctx context.Context, data *Group) error - DeleteGroup(ctx context.Context, id int64) error - FindServerDetailByGroupIdsAndIds(ctx context.Context, groupId, ids []int64) ([]*Server, error) - FindServerListByGroupIds(ctx context.Context, groupId []int64) ([]*Server, error) - FindAllServer(ctx context.Context) ([]*Server, error) - FindNodeByServerAddrAndProtocol(ctx context.Context, serverAddr string, protocol string) ([]*Server, error) - FindServerMinSortByIds(ctx context.Context, ids []int64) (int64, error) - FindServerListByIds(ctx context.Context, ids []int64) ([]*Server, error) - InsertRuleGroup(ctx context.Context, data *RuleGroup) error - FindOneRuleGroup(ctx context.Context, id int64) (*RuleGroup, error) - UpdateRuleGroup(ctx context.Context, data *RuleGroup) error - DeleteRuleGroup(ctx context.Context, id int64) error - QueryAllRuleGroup(ctx context.Context) ([]*RuleGroup, error) - FindServersByTag(ctx context.Context, tag string) ([]*Server, error) - FindServerTags(ctx context.Context) ([]string, error) - - SetDefaultRuleGroup(ctx context.Context, id int64) error -} - -var ( - CacheServerDetailPrefix = "cache:server:detail:" - cacheServerGroupAllKeys = "cache:serverGroup:all" - cacheServerRuleGroupAllKeys = "cache:serverRuleGroup:all" -) - -// ClearCache Clear Cache -func (m *customServerModel) ClearCache(ctx context.Context, id int64) error { - serverIdKey := fmt.Sprintf("%s%v", cacheServerIdPrefix, id) - //configKey := fmt.Sprintf("%s%d", config.ServerConfigCacheKey, id) - //userListKey := fmt.Sprintf("%s%v", config.ServerUserListCacheKey, id) - - return m.DelCacheCtx(ctx, serverIdKey) -} - -// QueryServerCountByServerGroups Query Server Count By Server Groups -func (m *customServerModel) QueryServerCountByServerGroups(ctx context.Context, groupIds []int64) (int64, error) { - var count int64 - err := m.QueryNoCacheCtx(ctx, &count, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&Server{}).Where("group_id IN ?", groupIds).Count(&count).Error - }) - return count, err -} - -// QueryAllGroup returns all groups. -func (m *customServerModel) QueryAllGroup(ctx context.Context) ([]*Group, error) { - var groups []*Group - err := m.QueryCtx(ctx, &groups, cacheServerGroupAllKeys, func(conn *gorm.DB, v interface{}) error { - return conn.Find(&groups).Error - }) - return groups, err -} - -// BatchDeleteNodeGroup deletes multiple groups. -func (m *customServerModel) BatchDeleteNodeGroup(ctx context.Context, ids []int64) error { - return m.Transaction(ctx, func(tx *gorm.DB) error { - for _, id := range ids { - if err := m.Delete(ctx, id); err != nil { - return err - } - } - return nil - }) -} - -// InsertGroup inserts a group. -func (m *customServerModel) InsertGroup(ctx context.Context, data *Group) error { - return m.ExecCtx(ctx, func(conn *gorm.DB) error { - return conn.Create(data).Error - }, cacheServerGroupAllKeys) -} - -// FindOneGroup finds a group. -func (m *customServerModel) FindOneGroup(ctx context.Context, id int64) (*Group, error) { - var group Group - err := m.QueryCtx(ctx, &group, fmt.Sprintf("cache:serverGroup:%v", id), func(conn *gorm.DB, v interface{}) error { - return conn.Model(&Group{}).Where("id = ?", id).First(&group).Error - }) - return &group, err -} - -// UpdateGroup updates a group. -func (m *customServerModel) UpdateGroup(ctx context.Context, data *Group) error { - return m.ExecCtx(ctx, func(conn *gorm.DB) error { - return conn.Model(&Group{}).Where("id = ?", data.Id).Updates(data).Error - }, cacheServerGroupAllKeys, fmt.Sprintf("cache:serverGroup:%v", data.Id)) -} - -// DeleteGroup deletes a group. -func (m *customServerModel) DeleteGroup(ctx context.Context, id int64) error { - return m.ExecCtx(ctx, func(conn *gorm.DB) error { - return conn.Where("id = ?", id).Delete(&Group{}).Error - }, cacheServerGroupAllKeys, fmt.Sprintf("cache:serverGroup:%v", id)) -} - -// FindServerDetailByGroupIdsAndIds finds server details by group IDs and IDs. -func (m *customServerModel) FindServerDetailByGroupIdsAndIds(ctx context.Context, groupId, ids []int64) ([]*Server, error) { - if len(groupId) == 0 && len(ids) == 0 { - return []*Server{}, nil - } - var list []*Server - err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error { - conn = conn. - Model(&Server{}). - Where("`enable` = ?", true) - if len(groupId) > 0 && len(ids) > 0 { - // OR is used to connect group_id and id conditions - conn = conn.Where("(`group_id` IN ? OR `id` IN ?)", groupId, ids) - } else if len(groupId) > 0 { - conn = conn.Where("`group_id` IN ?", groupId) - } else if len(ids) > 0 { - conn = conn.Where("`id` IN ?", ids) - } - - return conn.Order("sort ASC").Find(v).Error - }) - return list, err -} - -func (m *customServerModel) FindServerListByGroupIds(ctx context.Context, groupId []int64) ([]*Server, error) { - var data []*Server - err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&Server{}).Where("group_id IN ?", groupId).Find(v).Error - }) - return data, err -} - -func (m *customServerModel) FindAllServer(ctx context.Context) ([]*Server, error) { - var data []*Server - err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&Server{}).Order("sort ASC").Find(v).Error - }) - return data, err -} - -func (m *customServerModel) FindNodeByServerAddrAndProtocol(ctx context.Context, serverAddr string, protocol string) ([]*Server, error) { - var data []*Server - err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&Server{}).Where("server_addr = ? and protocol = ?", serverAddr, protocol).Order("sort ASC").Find(v).Error - }) - return data, err -} - -func (m *customServerModel) FindServerMinSortByIds(ctx context.Context, ids []int64) (int64, error) { - var minSort int64 - err := m.QueryNoCacheCtx(ctx, &minSort, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&Server{}).Where("id IN ?", ids).Select("COALESCE(MIN(sort), 0)").Scan(v).Error - }) - return minSort, err -} - -func (m *customServerModel) FindServerListByIds(ctx context.Context, ids []int64) ([]*Server, error) { - var list []*Server - err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&Server{}).Where("id IN ?", ids).Find(v).Error - }) - return list, err -} - -// InsertRuleGroup inserts a group. -func (m *customServerModel) InsertRuleGroup(ctx context.Context, data *RuleGroup) error { - return m.ExecCtx(ctx, func(conn *gorm.DB) error { - return conn.Where(&RuleGroup{}).Create(data).Error - }, cacheServerRuleGroupAllKeys, fmt.Sprintf("cache:serverRuleGroup:%v", data.Id)) -} - -// FindOneRuleGroup finds a group. -func (m *customServerModel) FindOneRuleGroup(ctx context.Context, id int64) (*RuleGroup, error) { - var group RuleGroup - err := m.QueryCtx(ctx, &group, fmt.Sprintf("cache:serverRuleGroup:%v", id), func(conn *gorm.DB, v interface{}) error { - return conn.Where(&RuleGroup{}).Model(&RuleGroup{}).Where("id = ?", id).First(&group).Error - }) - return &group, err -} - -// UpdateRuleGroup updates a group. -func (m *customServerModel) UpdateRuleGroup(ctx context.Context, data *RuleGroup) error { - return m.ExecCtx(ctx, func(conn *gorm.DB) error { - return conn.Where(&RuleGroup{}).Model(&RuleGroup{}).Where("id = ?", data.Id).Save(data).Error - }, cacheServerRuleGroupAllKeys, fmt.Sprintf("cache:serverRuleGroup:%v", data.Id)) -} - -// DeleteRuleGroup deletes a group. -func (m *customServerModel) DeleteRuleGroup(ctx context.Context, id int64) error { - return m.ExecCtx(ctx, func(conn *gorm.DB) error { - return conn.Where(&RuleGroup{}).Where("id = ?", id).Delete(&RuleGroup{}).Error - }, cacheServerRuleGroupAllKeys, fmt.Sprintf("cache:serverRuleGroup:%v", id)) -} - -// QueryAllRuleGroup returns all rule groups. -func (m *customServerModel) QueryAllRuleGroup(ctx context.Context) ([]*RuleGroup, error) { - var groups []*RuleGroup - err := m.QueryCtx(ctx, &groups, cacheServerRuleGroupAllKeys, func(conn *gorm.DB, v interface{}) error { - return conn.Where(&RuleGroup{}).Find(&groups).Error - }) - return groups, err -} - -func (m *customServerModel) FindServerListByFilter(ctx context.Context, filter *ServerFilter) (total int64, list []*Server, err error) { - var data []*Server - if filter == nil { - filter = &ServerFilter{ - Page: 1, - Size: 10, - } - } - - if filter.Page <= 0 { - filter.Page = 1 - } - if filter.Size <= 0 { - filter.Size = 10 - } - - err = m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error { - query := conn.Model(&Server{}).Order("sort ASC") - if filter.Group > 0 { - query = conn.Where("group_id = ?", filter.Group) - } - if filter.Search != "" { - query = query.Where("name LIKE ? OR server_addr LIKE ? OR tags LIKE ?", "%"+filter.Search+"%", "%"+filter.Search+"%", "%"+filter.Search+"%") - } - if len(filter.Tags) > 0 { - for i, tag := range filter.Tags { - if i == 0 { - query = query.Where("tags LIKE ?", "%"+tag+"%") - } else { - query = query.Or("tags LIKE ?", "%"+tag+"%") - } - } - } - return query.Count(&total).Limit(filter.Size).Offset((filter.Page - 1) * filter.Size).Find(v).Error - }) - if err != nil { - return 0, nil, err - } - return total, data, nil -} - -func (m *customServerModel) FindServerTags(ctx context.Context) ([]string, error) { - var data []string - err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&Server{}).Distinct("tags").Pluck("tags", v).Error - }) - var tags []string - for _, tag := range data { - if strings.Contains(tag, ",") { - tags = append(tags, strings.Split(tag, ",")...) - } else { - tags = append(tags, tag) - } - } - return tags, err -} - -func (m *customServerModel) FindServersByTag(ctx context.Context, tag string) ([]*Server, error) { - var data []*Server - err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&Server{}).Where("FIND_IN_SET(?, tags)", tag).Order("sort ASC").Find(v).Error - }) - return data, err -} - -// SetDefaultRuleGroup sets the default rule group. - -func (m *customServerModel) SetDefaultRuleGroup(ctx context.Context, id int64) error { - return m.ExecCtx(ctx, func(conn *gorm.DB) error { - // Reset all groups to not default - if err := conn.Model(&RuleGroup{}).Where("`id` != ?", id).Update("default", false).Error; err != nil { - return err - } - // Set the specified group as default - return conn.Model(&RuleGroup{}).Where("`id` = ?", id).Update("default", true).Error - }, cacheServerRuleGroupAllKeys, fmt.Sprintf("cache:serverRuleGroup:%v", id)) -} diff --git a/internal/model/server/server.go b/internal/model/server/server.go deleted file mode 100644 index 2da10da..0000000 --- a/internal/model/server/server.go +++ /dev/null @@ -1,219 +0,0 @@ -package server - -import ( - "time" - - "github.com/perfect-panel/server/pkg/logger" - - "gorm.io/gorm" -) - -const ( - RelayModeNone = "none" - RelayModeAll = "all" - RelayModeRandom = "random" - RuleGroupTypeReject = "reject" - RuleGroupTypeDefault = "default" - RuleGroupTypeDirect = "direct" -) - -type ServerFilter struct { - Id int64 - Tags []string - Group int64 - Search string - Page int - Size int -} - -// Deprecated: use internal/model/node/server.go -type Server struct { - Id int64 `gorm:"primary_key"` - Name string `gorm:"type:varchar(100);not null;default:'';comment:Node Name"` - Tags string `gorm:"type:varchar(128);not null;default:'';comment:Tags"` - Country string `gorm:"type:varchar(128);not null;default:'';comment:Country"` - City string `gorm:"type:varchar(128);not null;default:'';comment:City"` - Latitude string `gorm:"type:varchar(128);not null;default:'';comment:Latitude"` - Longitude string `gorm:"type:varchar(128);not null;default:'';comment:Longitude"` - ServerAddr string `gorm:"type:varchar(100);not null;default:'';comment:Server Address"` - RelayMode string `gorm:"type:varchar(20);not null;default:'none';comment:Relay Mode"` - RelayNode string `gorm:"type:text;comment:Relay Node"` - SpeedLimit int `gorm:"type:int;not null;default:0;comment:Speed Limit"` - TrafficRatio float32 `gorm:"type:DECIMAL(4,2);not null;default:0;comment:Traffic Ratio"` - GroupId int64 `gorm:"index:idx_group_id;type:int;default:null;comment:Group ID"` - Protocol string `gorm:"type:varchar(20);not null;default:'';comment:Protocol"` - Config string `gorm:"type:text;comment:Config"` - Enable *bool `gorm:"type:tinyint(1);not null;default:1;comment:Enabled"` - Sort int64 `gorm:"type:int;not null;default:0;comment:Sort"` - LastReportedAt time.Time `gorm:"comment:Last Reported Time"` - CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` - UpdatedAt time.Time `gorm:"comment:Update Time"` -} - -func (*Server) TableName() string { - return "server" -} - -func (s *Server) BeforeDelete(tx *gorm.DB) error { - logger.Debugf("[Server] BeforeDelete") - if err := tx.Exec("UPDATE `server` SET sort = sort - 1 WHERE sort > ?", s.Sort).Error; err != nil { - return err - } - return nil -} - -func (s *Server) BeforeUpdate(tx *gorm.DB) error { - logger.Debugf("[Server] BeforeUpdate") - var count int64 - if err := tx.Set("gorm:query_option", "FOR UPDATE").Model(&Server{}). - Where("sort = ? AND id != ?", s.Sort, s.Id).Count(&count).Error; err != nil { - return err - } - if count > 1 { - // reorder sort - if err := reorderSort(tx); err != nil { - logger.Errorf("[Server] BeforeUpdate reorderSort error: %v", err.Error()) - return err - } - // get max sort - var maxSort int64 - if err := tx.Model(&Server{}).Select("MAX(sort)").Scan(&maxSort).Error; err != nil { - return err - } - s.Sort = maxSort + 1 - } - return nil -} - -func (s *Server) BeforeCreate(tx *gorm.DB) error { - logger.Debugf("[Server] BeforeCreate") - if s.Sort == 0 { - var maxSort int64 - if err := tx.Model(&Server{}).Select("COALESCE(MAX(sort), 0)").Scan(&maxSort).Error; err != nil { - return err - } - s.Sort = maxSort + 1 - } - return nil -} - -type Vless struct { - Port int `json:"port"` - Flow string `json:"flow"` - Transport string `json:"transport"` - TransportConfig TransportConfig `json:"transport_config"` - Security string `json:"security"` - SecurityConfig SecurityConfig `json:"security_config"` -} - -type Vmess struct { - Port int `json:"port"` - Flow string `json:"flow"` - Transport string `json:"transport"` - TransportConfig TransportConfig `json:"transport_config"` - Security string `json:"security"` - SecurityConfig SecurityConfig `json:"security_config"` -} - -type Trojan struct { - Port int `json:"port"` - Flow string `json:"flow"` - Transport string `json:"transport"` - TransportConfig TransportConfig `json:"transport_config"` - Security string `json:"security"` - SecurityConfig SecurityConfig `json:"security_config"` -} - -type Shadowsocks struct { - Method string `json:"method"` - Port int `json:"port"` - ServerKey string `json:"server_key"` -} - -type Hysteria2 struct { - Port int `json:"port"` - HopPorts string `json:"hop_ports"` - HopInterval int `json:"hop_interval"` - ObfsPassword string `json:"obfs_password"` - SecurityConfig SecurityConfig `json:"security_config"` -} - -type Tuic struct { - Port int `json:"port"` - DisableSNI bool `json:"disable_sni"` - ReduceRtt bool `json:"reduce_rtt"` - UDPRelayMode string `json:"udp_relay_mode"` - CongestionController string `json:"congestion_controller"` - SecurityConfig SecurityConfig `json:"security_config"` -} - -type AnyTLS struct { - Port int `json:"port"` - SecurityConfig SecurityConfig `json:"security_config"` -} - -type TransportConfig struct { - Path string `json:"path,omitempty"` // ws/httpupgrade - Host string `json:"host,omitempty"` - ServiceName string `json:"service_name"` // grpc -} - -type SecurityConfig struct { - SNI string `json:"sni"` - AllowInsecure bool `json:"allow_insecure"` - Fingerprint string `json:"fingerprint"` - RealityServerAddr 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"` -} - -type NodeRelay struct { - Host string `json:"host"` - Port int `json:"port"` - Prefix string `json:"prefix"` -} - -type Group struct { - Id int64 `gorm:"primary_key"` - Name string `gorm:"type:varchar(100);not null;default:'';comment:Group Name"` - Description string `gorm:"type:varchar(255);default:'';comment:Group Description"` - CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` - UpdatedAt time.Time `gorm:"comment:Update Time"` -} - -func (Group) TableName() string { - return "server_group" -} - -type RuleGroup struct { - Id int64 `gorm:"primary_key"` - Icon string `gorm:"type:MEDIUMTEXT;comment:Rule Group Icon"` - Name string `gorm:"type:varchar(100);not null;default:'';comment:Rule Group Name"` - Type string `gorm:"type:varchar(100);not null;default:'';comment:Rule Group Type"` - Tags string `gorm:"type:text;comment:Selected Node Tags"` - Rules string `gorm:"type:MEDIUMTEXT;comment:Rules"` - Enable bool `gorm:"type:tinyint(1);not null;default:1;comment:Rule Group Enable"` - Default bool `gorm:"type:tinyint(1);not null;default:0;comment:Rule Group is Default"` - CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` - UpdatedAt time.Time `gorm:"comment:Update Time"` -} - -func (RuleGroup) TableName() string { - return "server_rule_group" -} -func reorderSort(tx *gorm.DB) error { - var servers []Server - if err := tx.Order("sort, id").Find(&servers).Error; err != nil { - return err - } - for i, server := range servers { - if server.Sort != int64(i)+1 { - if err := tx.Exec("UPDATE `server` SET sort = ? WHERE id = ?", i+1, server.Id).Error; err != nil { - return err - } - } - } - return nil -} diff --git a/internal/model/subscribe/subscribe.go b/internal/model/subscribe/subscribe.go index a80ea63..c9c1046 100644 --- a/internal/model/subscribe/subscribe.go +++ b/internal/model/subscribe/subscribe.go @@ -7,30 +7,31 @@ import ( ) type Subscribe struct { - Id int64 `gorm:"primaryKey"` - Name string `gorm:"type:varchar(255);not null;default:'';comment:Subscribe Name"` - Language string `gorm:"type:varchar(255);not null;default:'';comment:Language"` - 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"` - Quota int64 `gorm:"type:int;not null;default:0;comment:Quota"` - Nodes string `gorm:"type:varchar(255);comment:Node Ids"` - NodeTags string `gorm:"type:varchar(255);comment:Node Tags"` - 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"` - Sort int64 `gorm:"type:int;not null;default:0;comment:Sort"` - DeductionRatio int64 `gorm:"type:int;default:0;comment:Deduction Ratio"` - AllowDeduction *bool `gorm:"type:tinyint(1);default:1;comment:Allow deduction"` - ResetCycle int64 `gorm:"type:int;default:0;comment:Reset Cycle: 0: No Reset, 1: 1st, 2: Monthly, 3: Yearly"` - RenewalReset *bool `gorm:"type:tinyint(1);default:0;comment:Renew Reset"` - CreatedAt time.Time `gorm:"<-:create;comment:Create Time"` - UpdatedAt time.Time `gorm:"comment:Update Time"` + Id int64 `gorm:"primaryKey"` + Name string `gorm:"type:varchar(255);not null;default:'';comment:Subscribe Name"` + Language string `gorm:"type:varchar(255);not null;default:'';comment:Language"` + 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:-1;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"` + Quota int64 `gorm:"type:int;not null;default:0;comment:Quota"` + Nodes string `gorm:"type:varchar(255);comment:Node Ids"` + NodeTags string `gorm:"type:varchar(255);comment:Node Tags"` + 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"` + Sort int64 `gorm:"type:int;not null;default:0;comment:Sort"` + DeductionRatio int64 `gorm:"type:int;default:0;comment:Deduction Ratio"` + AllowDeduction *bool `gorm:"type:tinyint(1);default:1;comment:Allow deduction"` + ResetCycle int64 `gorm:"type:int;default:0;comment:Reset Cycle: 0: No Reset, 1: 1st, 2: Monthly, 3: Yearly"` + RenewalReset *bool `gorm:"type:tinyint(1);default:0;comment:Renew Reset"` + ShowOriginalPrice bool `gorm:"type:tinyint(1);not null;default:1;comment:Show Original Price"` + CreatedAt time.Time `gorm:"<-:create;comment:Create Time"` + UpdatedAt time.Time `gorm:"comment:Update Time"` } func (*Subscribe) TableName() string { diff --git a/internal/model/user/default.go b/internal/model/user/default.go index e2a326a..8eeb399 100644 --- a/internal/model/user/default.go +++ b/internal/model/user/default.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/perfect-panel/server/pkg/cache" + "github.com/perfect-panel/server/pkg/logger" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) @@ -72,7 +73,7 @@ func (m *defaultUserModel) FindOneByEmail(ctx context.Context, email string) (*U if err := conn.Model(&AuthMethods{}).Where("`auth_type` = 'email' AND `auth_identifier` = ?", email).First(&data).Error; err != nil { return err } - return conn.Model(&User{}).Where("`id` = ?", data.UserId).Preload("UserDevices").Preload("AuthMethods").First(v).Error + return conn.Model(&User{}).Unscoped().Where("`id` = ?", data.UserId).Preload("UserDevices").Preload("AuthMethods").First(v).Error }) return &user, err } @@ -91,7 +92,7 @@ func (m *defaultUserModel) FindOne(ctx context.Context, id int64) (*User, error) userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id) var resp User err := m.QueryCtx(ctx, &resp, userIdKey, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&User{}).Where("`id` = ?", id).Preload("UserDevices").Preload("AuthMethods").First(&resp).Error + return conn.Model(&User{}).Unscoped().Where("`id` = ?", id).Preload("UserDevices").Preload("AuthMethods").First(&resp).Error }) return &resp, err } @@ -119,10 +120,11 @@ func (m *defaultUserModel) Delete(ctx context.Context, id int64, tx ...*gorm.DB) return err } - // 使用批量相关缓存清理,包含所有相关数据的缓存 + // Use batch related cache cleaning, including a cache of all relevant data defer func() { if clearErr := m.BatchClearRelatedCache(ctx, data); clearErr != nil { - // 记录清理缓存错误,但不阻断删除操作 + // Record cache cleaning errors, but do not block deletion operations + logger.Errorf("failed to clear related cache for user %d: %v", id, clearErr.Error()) } }() @@ -130,24 +132,11 @@ func (m *defaultUserModel) Delete(ctx context.Context, id int64, tx ...*gorm.DB) if len(tx) > 0 { db = tx[0] } - - // 删除用户相关的所有数据 + // Soft deletion of user information without any processing of other information (Determine whether to allow login/subscription based on the user's deletion status) if err := db.Model(&User{}).Where("`id` = ?", id).Delete(&User{}).Error; err != nil { return err } - if err := db.Model(&AuthMethods{}).Where("`user_id` = ?", id).Delete(&AuthMethods{}).Error; err != nil { - return err - } - - if err := db.Model(&Subscribe{}).Where("`user_id` = ?", id).Delete(&Subscribe{}).Error; err != nil { - return err - } - - if err := db.Model(&Device{}).Where("`user_id` = ?", id).Delete(&Device{}).Error; err != nil { - return err - } - return nil }) } diff --git a/internal/model/user/model.go b/internal/model/user/model.go index b9ffe04..e0c2a29 100644 --- a/internal/model/user/model.go +++ b/internal/model/user/model.go @@ -62,6 +62,7 @@ type UserFilterParams struct { SubscribeId *int64 UserSubscribeId *int64 Order string // Order by id, e.g., "desc" + Unscoped bool // Whether to include soft-deleted records } type customUserLogicModel interface { @@ -148,6 +149,9 @@ func (m *customUserModel) QueryPageList(ctx context.Context, page, size int, fil if filter.Order != "" { conn = conn.Order(fmt.Sprintf("user.id %s", filter.Order)) } + if filter.Unscoped { + conn = conn.Unscoped() + } } return conn.Model(&User{}).Group("user.id").Count(&total).Limit(size).Offset((page-1)*size).Preload("UserDevices").Preload("AuthMethods", func(db *gorm.DB) *gorm.DB { return db.Order("user_auth_methods.auth_type desc") }).Find(&list).Error }) diff --git a/internal/model/user/user.go b/internal/model/user/user.go index 923594e..697f4e8 100644 --- a/internal/model/user/user.go +++ b/internal/model/user/user.go @@ -2,32 +2,35 @@ package user import ( "time" + + "gorm.io/gorm" ) type User struct { - Id int64 `gorm:"primaryKey"` - Password string `gorm:"type:varchar(100);not null;comment:User Password"` - Algo string `gorm:"type:varchar(20);default:'default';comment:Encryption Algorithm"` - Salt string `gorm:"type:varchar(20);default:null;comment:Password Salt"` - Avatar string `gorm:"type:MEDIUMTEXT;comment:User Avatar"` - Balance int64 `gorm:"default:0;comment:User Balance"` // User Balance Amount - ReferCode string `gorm:"type:varchar(20);default:'';comment:Referral Code"` - RefererId int64 `gorm:"index:idx_referer;comment:Referrer ID"` - Commission int64 `gorm:"default:0;comment:Commission"` // Commission Amount - ReferralPercentage uint8 `gorm:"default:0;comment:Referral"` // Referral Percentage - OnlyFirstPurchase *bool `gorm:"default:true;not null;comment:Only First Purchase"` // Only First Purchase Referral - GiftAmount int64 `gorm:"default:0;comment:User Gift Amount"` - Enable *bool `gorm:"default:true;not null;comment:Is Account Enabled"` - IsAdmin *bool `gorm:"default:false;not null;comment:Is Admin"` - EnableBalanceNotify *bool `gorm:"default:false;not null;comment:Enable Balance Change Notifications"` - EnableLoginNotify *bool `gorm:"default:false;not null;comment:Enable Login Notifications"` - EnableSubscribeNotify *bool `gorm:"default:false;not null;comment:Enable Subscription Notifications"` - EnableTradeNotify *bool `gorm:"default:false;not null;comment:Enable Trade Notifications"` - AuthMethods []AuthMethods `gorm:"foreignKey:UserId;references:Id"` - UserDevices []Device `gorm:"foreignKey:UserId;references:Id"` - Rules string `gorm:"type:TEXT;comment:User Rules"` - CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` - UpdatedAt time.Time `gorm:"comment:Update Time"` + Id int64 `gorm:"primaryKey"` + Password string `gorm:"type:varchar(100);not null;comment:User Password"` + Algo string `gorm:"type:varchar(20);default:'default';comment:Encryption Algorithm"` + Salt string `gorm:"type:varchar(20);default:null;comment:Password Salt"` + Avatar string `gorm:"type:MEDIUMTEXT;comment:User Avatar"` + Balance int64 `gorm:"default:0;comment:User Balance"` // User Balance Amount + ReferCode string `gorm:"type:varchar(20);default:'';comment:Referral Code"` + RefererId int64 `gorm:"index:idx_referer;comment:Referrer ID"` + Commission int64 `gorm:"default:0;comment:Commission"` // Commission Amount + ReferralPercentage uint8 `gorm:"default:0;comment:Referral"` // Referral Percentage + OnlyFirstPurchase *bool `gorm:"default:true;not null;comment:Only First Purchase"` // Only First Purchase Referral + GiftAmount int64 `gorm:"default:0;comment:User Gift Amount"` + Enable *bool `gorm:"default:true;not null;comment:Is Account Enabled"` + IsAdmin *bool `gorm:"default:false;not null;comment:Is Admin"` + EnableBalanceNotify *bool `gorm:"default:false;not null;comment:Enable Balance Change Notifications"` + EnableLoginNotify *bool `gorm:"default:false;not null;comment:Enable Login Notifications"` + EnableSubscribeNotify *bool `gorm:"default:false;not null;comment:Enable Subscription Notifications"` + EnableTradeNotify *bool `gorm:"default:false;not null;comment:Enable Trade Notifications"` + AuthMethods []AuthMethods `gorm:"foreignKey:UserId;references:Id"` + UserDevices []Device `gorm:"foreignKey:UserId;references:Id"` + Rules string `gorm:"type:TEXT;comment:User Rules"` + CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` + UpdatedAt time.Time `gorm:"comment:Update Time"` + DeletedAt gorm.DeletedAt `gorm:"index;comment:Deletion Time"` } func (*User) TableName() string { @@ -49,7 +52,6 @@ type Subscribe struct { Token string `gorm:"index:idx_token;unique;type:varchar(255);default:'';comment:Token"` UUID string `gorm:"type:varchar(255);unique;index:idx_uuid;default:'';comment:UUID"` Status uint8 `gorm:"type:tinyint(1);default:0;comment:Subscription Status: 0: Pending 1: Active 2: Finished 3: Expired 4: Deducted"` - Note string `gorm:"type:varchar(500);default:'';comment:User note for subscription"` CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` UpdatedAt time.Time `gorm:"comment:Update Time"` } diff --git a/internal/svc/serviceContext.go b/internal/svc/serviceContext.go index 4f6bc3a..aa79ccc 100644 --- a/internal/svc/serviceContext.go +++ b/internal/svc/serviceContext.go @@ -97,7 +97,7 @@ func NewServiceContext(c config.Config) *ServiceContext { Redis: rds, Config: c, Queue: NewAsynqClient(c), - ExchangeRate: 1.0, + ExchangeRate: 0, GeoIP: geoIP, //NodeCache: cache.NewNodeCacheClient(rds), AuthLimiter: authLimiter, diff --git a/internal/types/subscribe.go b/internal/types/subscribe.go index 0e8ab26..ec1bfd9 100644 --- a/internal/types/subscribe.go +++ b/internal/types/subscribe.go @@ -2,9 +2,11 @@ package types type ( SubscribeRequest struct { - Flag string - Token string - UA string + Flag string + Token string + Type string + UA string + Params map[string]string } SubscribeResponse struct { Config []byte diff --git a/internal/types/types.go b/internal/types/types.go index 3054d16..078a2fa 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -394,26 +394,27 @@ type CreateSubscribeGroupRequest struct { } type CreateSubscribeRequest struct { - Name string `json:"name" validate:"required"` - Language string `json:"language"` - Description string `json:"description"` - UnitPrice int64 `json:"unit_price"` - UnitTime string `json:"unit_time"` - Discount []SubscribeDiscount `json:"discount"` - Replacement int64 `json:"replacement"` - Inventory int64 `json:"inventory"` - Traffic int64 `json:"traffic"` - SpeedLimit int64 `json:"speed_limit"` - DeviceLimit int64 `json:"device_limit"` - Quota int64 `json:"quota"` - Nodes []int64 `json:"nodes"` - NodeTags []string `json:"node_tags"` - Show *bool `json:"show"` - Sell *bool `json:"sell"` - DeductionRatio int64 `json:"deduction_ratio"` - AllowDeduction *bool `json:"allow_deduction"` - ResetCycle int64 `json:"reset_cycle"` - RenewalReset *bool `json:"renewal_reset"` + Name string `json:"name" validate:"required"` + Language string `json:"language"` + Description string `json:"description"` + UnitPrice int64 `json:"unit_price"` + UnitTime string `json:"unit_time"` + Discount []SubscribeDiscount `json:"discount"` + Replacement int64 `json:"replacement"` + Inventory int64 `json:"inventory"` + Traffic int64 `json:"traffic"` + SpeedLimit int64 `json:"speed_limit"` + DeviceLimit int64 `json:"device_limit"` + Quota int64 `json:"quota"` + Nodes []int64 `json:"nodes"` + NodeTags []string `json:"node_tags"` + Show *bool `json:"show"` + Sell *bool `json:"sell"` + DeductionRatio int64 `json:"deduction_ratio"` + AllowDeduction *bool `json:"allow_deduction"` + ResetCycle int64 `json:"reset_cycle"` + RenewalReset *bool `json:"renewal_reset"` + ShowOriginalPrice bool `json:"show_original_price"` } type CreateTicketFollowRequest struct { @@ -1054,6 +1055,7 @@ type GetUserListRequest struct { Size int `form:"size"` Search string `form:"search,omitempty"` UserId *int64 `form:"user_id,omitempty"` + Unscoped bool `form:"unscoped,omitempty"` SubscribeId *int64 `form:"subscribe_id,omitempty"` UserSubscribeId *int64 `form:"user_subscribe_id,omitempty"` } @@ -1568,7 +1570,7 @@ type PurchaseOrderResponse struct { type QueryAnnouncementRequest struct { Page int `form:"page"` - Size int `form:"size"` + Size int `form:"size,default=15"` Pinned *bool `form:"pinned"` Popup *bool `form:"popup"` } @@ -1856,6 +1858,10 @@ type ResetUserSubscribeTokenRequest struct { UserSubscribeId int64 `json:"user_subscribe_id"` } +type ResetUserSubscribeTrafficRequest struct { + UserSubscribeId int64 `json:"user_subscribe_id"` +} + type RevenueStatisticsResponse struct { Today OrdersStatistics `json:"today"` Monthly OrdersStatistics `json:"monthly"` @@ -2056,30 +2062,31 @@ type StripePayment struct { } type Subscribe struct { - Id int64 `json:"id"` - Name string `json:"name"` - Language string `json:"language"` - Description string `json:"description"` - UnitPrice int64 `json:"unit_price"` - UnitTime string `json:"unit_time"` - Discount []SubscribeDiscount `json:"discount"` - Replacement int64 `json:"replacement"` - Inventory int64 `json:"inventory"` - Traffic int64 `json:"traffic"` - SpeedLimit int64 `json:"speed_limit"` - DeviceLimit int64 `json:"device_limit"` - Quota int64 `json:"quota"` - Nodes []int64 `json:"nodes"` - NodeTags []string `json:"node_tags"` - Show bool `json:"show"` - Sell bool `json:"sell"` - Sort int64 `json:"sort"` - DeductionRatio int64 `json:"deduction_ratio"` - AllowDeduction bool `json:"allow_deduction"` - ResetCycle int64 `json:"reset_cycle"` - RenewalReset bool `json:"renewal_reset"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` + Id int64 `json:"id"` + Name string `json:"name"` + Language string `json:"language"` + Description string `json:"description"` + UnitPrice int64 `json:"unit_price"` + UnitTime string `json:"unit_time"` + Discount []SubscribeDiscount `json:"discount"` + Replacement int64 `json:"replacement"` + Inventory int64 `json:"inventory"` + Traffic int64 `json:"traffic"` + SpeedLimit int64 `json:"speed_limit"` + DeviceLimit int64 `json:"device_limit"` + Quota int64 `json:"quota"` + Nodes []int64 `json:"nodes"` + NodeTags []string `json:"node_tags"` + Show bool `json:"show"` + Sell bool `json:"sell"` + Sort int64 `json:"sort"` + DeductionRatio int64 `json:"deduction_ratio"` + AllowDeduction bool `json:"allow_deduction"` + ResetCycle int64 `json:"reset_cycle"` + RenewalReset bool `json:"renewal_reset"` + ShowOriginalPrice bool `json:"show_original_price"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` } type SubscribeApplication struct { @@ -2239,6 +2246,10 @@ type ToggleNodeStatusRequest struct { Enable *bool `json:"enable"` } +type ToggleUserSubscribeStatusRequest struct { + UserSubscribeId int64 `json:"user_subscribe_id"` +} + type TosConfig struct { TosContent string `json:"tos_content"` } @@ -2435,28 +2446,29 @@ type UpdateSubscribeGroupRequest struct { } type UpdateSubscribeRequest struct { - Id int64 `json:"id" validate:"required"` - Name string `json:"name" validate:"required"` - Language string `json:"language"` - Description string `json:"description"` - UnitPrice int64 `json:"unit_price"` - UnitTime string `json:"unit_time"` - Discount []SubscribeDiscount `json:"discount"` - Replacement int64 `json:"replacement"` - Inventory int64 `json:"inventory"` - Traffic int64 `json:"traffic"` - SpeedLimit int64 `json:"speed_limit"` - DeviceLimit int64 `json:"device_limit"` - Quota int64 `json:"quota"` - Nodes []int64 `json:"nodes"` - NodeTags []string `json:"node_tags"` - Show *bool `json:"show"` - Sell *bool `json:"sell"` - Sort int64 `json:"sort"` - DeductionRatio int64 `json:"deduction_ratio"` - AllowDeduction *bool `json:"allow_deduction"` - ResetCycle int64 `json:"reset_cycle"` - RenewalReset *bool `json:"renewal_reset"` + Id int64 `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + Language string `json:"language"` + Description string `json:"description"` + UnitPrice int64 `json:"unit_price"` + UnitTime string `json:"unit_time"` + Discount []SubscribeDiscount `json:"discount"` + Replacement int64 `json:"replacement"` + Inventory int64 `json:"inventory"` + Traffic int64 `json:"traffic"` + SpeedLimit int64 `json:"speed_limit"` + DeviceLimit int64 `json:"device_limit"` + Quota int64 `json:"quota"` + Nodes []int64 `json:"nodes"` + NodeTags []string `json:"node_tags"` + Show *bool `json:"show"` + Sell *bool `json:"sell"` + Sort int64 `json:"sort"` + DeductionRatio int64 `json:"deduction_ratio"` + AllowDeduction *bool `json:"allow_deduction"` + ResetCycle int64 `json:"reset_cycle"` + RenewalReset *bool `json:"renewal_reset"` + ShowOriginalPrice bool `json:"show_original_price"` } type UpdateTicketStatusRequest struct { @@ -2551,7 +2563,6 @@ type User struct { CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` DeletedAt int64 `json:"deleted_at,omitempty"` - IsDel bool `json:"is_del,omitempty"` } type UserAffiliate struct { diff --git a/pkg/logger/gorm.go b/pkg/logger/gorm.go index 7dbdd4a..c7a295d 100644 --- a/pkg/logger/gorm.go +++ b/pkg/logger/gorm.go @@ -32,15 +32,15 @@ func (l *GormLogger) LogMode(logger.LogLevel) logger.Interface { } func (l *GormLogger) Info(ctx context.Context, str string, args ...interface{}) { - WithContext(ctx).WithCallerSkip(6).Infof("%s Info: %s", TAG, str, args) + WithContext(ctx).WithCallerSkip(2).Infof("%s Info: %s", TAG, str, args) } func (l *GormLogger) Warn(ctx context.Context, str string, args ...interface{}) { - WithContext(ctx).WithCallerSkip(6).Infof("%s Warn: %s", TAG, str, args) + WithContext(ctx).WithCallerSkip(2).Infof("%s Warn: %s", TAG, str, args) } func (l *GormLogger) Error(ctx context.Context, str string, args ...interface{}) { - WithContext(ctx).WithCallerSkip(6).Errorf("%s Error: %s", TAG, str, args) + WithContext(ctx).WithCallerSkip(2).Errorf("%s Error: %s", TAG, str, args) } func (l *GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) { diff --git a/pkg/xerr/errCode.go b/pkg/xerr/errCode.go index 24756ce..c37e9b9 100644 --- a/pkg/xerr/errCode.go +++ b/pkg/xerr/errCode.go @@ -71,6 +71,7 @@ const ( SubscribeIsUsedError uint32 = 60004 SingleSubscribeModeExceedsLimit uint32 = 60005 SubscribeQuotaLimit uint32 = 60006 + SubscribeOutOfStock uint32 = 60007 ) // Auth error diff --git a/pkg/xerr/errMsg.go b/pkg/xerr/errMsg.go index 7e46e76..ed054ae 100644 --- a/pkg/xerr/errMsg.go +++ b/pkg/xerr/errMsg.go @@ -56,6 +56,7 @@ func init() { SubscribeIsUsedError: "Subscribe is used", SingleSubscribeModeExceedsLimit: "Single subscribe mode exceeds limit", SubscribeQuotaLimit: "Subscribe quota limit", + SubscribeOutOfStock: "Subscribe out of stock", // auth error VerifyCodeError: "Verify code error", diff --git a/queue/logic/traffic/trafficStatisticsLogic.go b/queue/logic/traffic/trafficStatisticsLogic.go index ed98cd1..37614cb 100644 --- a/queue/logic/traffic/trafficStatisticsLogic.go +++ b/queue/logic/traffic/trafficStatisticsLogic.go @@ -79,6 +79,7 @@ func (l *TrafficStatisticsLogic) ProcessTask(ctx context.Context, task *asynq.Ta now := time.Now() realTimeMultiplier := l.svc.NodeMultiplierManager.GetMultiplier(now) + logger.Debugf("[TrafficStatisticsLogic] Current time traffic multiplier: %.2f", realTimeMultiplier) for _, log := range payload.Logs { // query user Subscribe Info sub, err := l.svc.UserModel.FindOneSubscribe(ctx, log.SID)