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
This commit is contained in:
parent
47c41d1d14
commit
80ee9a6acf
@ -8,16 +8,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Adapter struct {
|
type Adapter struct {
|
||||||
SiteName string // 站点名称
|
Type string // 协议类型
|
||||||
Servers []*node.Node // 服务器列表
|
SiteName string // 站点名称
|
||||||
UserInfo User // 用户信息
|
Servers []*node.Node // 服务器列表
|
||||||
ClientTemplate string // 客户端配置模板
|
UserInfo User // 用户信息
|
||||||
OutputFormat string // 输出格式,默认是 base64
|
ClientTemplate string // 客户端配置模板
|
||||||
SubscribeName string // 订阅名称
|
OutputFormat string // 输出格式,默认是 base64
|
||||||
|
SubscribeName string // 订阅名称
|
||||||
|
Params map[string]string // 其他参数
|
||||||
}
|
}
|
||||||
|
|
||||||
type Option func(*Adapter)
|
type Option func(*Adapter)
|
||||||
|
|
||||||
|
func WithParams(params map[string]string) Option {
|
||||||
|
return func(opts *Adapter) {
|
||||||
|
opts.Params = params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithServers 设置服务器列表
|
// WithServers 设置服务器列表
|
||||||
func WithServers(servers []*node.Node) Option {
|
func WithServers(servers []*node.Node) Option {
|
||||||
return func(opts *Adapter) {
|
return func(opts *Adapter) {
|
||||||
@ -101,55 +109,58 @@ func (adapter *Adapter) Proxies(servers []*node.Node) ([]Proxy, error) {
|
|||||||
}
|
}
|
||||||
for _, protocol := range protocols {
|
for _, protocol := range protocols {
|
||||||
if protocol.Type == item.Protocol {
|
if protocol.Type == item.Protocol {
|
||||||
proxies = append(proxies, Proxy{
|
proxies = append(
|
||||||
Sort: item.Sort,
|
proxies,
|
||||||
Name: item.Name,
|
Proxy{
|
||||||
Server: item.Address,
|
Sort: item.Sort,
|
||||||
Port: item.Port,
|
Name: item.Name,
|
||||||
Type: item.Protocol,
|
Server: item.Address,
|
||||||
Tags: strings.Split(item.Tags, ","),
|
Port: item.Port,
|
||||||
Security: protocol.Security,
|
Type: item.Protocol,
|
||||||
SNI: protocol.SNI,
|
Tags: strings.Split(item.Tags, ","),
|
||||||
AllowInsecure: protocol.AllowInsecure,
|
Security: protocol.Security,
|
||||||
Fingerprint: protocol.Fingerprint,
|
SNI: protocol.SNI,
|
||||||
RealityServerAddr: protocol.RealityServerAddr,
|
AllowInsecure: protocol.AllowInsecure,
|
||||||
RealityServerPort: protocol.RealityServerPort,
|
Fingerprint: protocol.Fingerprint,
|
||||||
RealityPrivateKey: protocol.RealityPrivateKey,
|
RealityServerAddr: protocol.RealityServerAddr,
|
||||||
RealityPublicKey: protocol.RealityPublicKey,
|
RealityServerPort: protocol.RealityServerPort,
|
||||||
RealityShortId: protocol.RealityShortId,
|
RealityPrivateKey: protocol.RealityPrivateKey,
|
||||||
Transport: protocol.Transport,
|
RealityPublicKey: protocol.RealityPublicKey,
|
||||||
Host: protocol.Host,
|
RealityShortId: protocol.RealityShortId,
|
||||||
Path: protocol.Path,
|
Transport: protocol.Transport,
|
||||||
ServiceName: protocol.ServiceName,
|
Host: protocol.Host,
|
||||||
Method: protocol.Cipher,
|
Path: protocol.Path,
|
||||||
ServerKey: protocol.ServerKey,
|
ServiceName: protocol.ServiceName,
|
||||||
Flow: protocol.Flow,
|
Method: protocol.Cipher,
|
||||||
HopPorts: protocol.HopPorts,
|
ServerKey: protocol.ServerKey,
|
||||||
HopInterval: protocol.HopInterval,
|
Flow: protocol.Flow,
|
||||||
ObfsPassword: protocol.ObfsPassword,
|
HopPorts: protocol.HopPorts,
|
||||||
UpMbps: protocol.UpMbps,
|
HopInterval: protocol.HopInterval,
|
||||||
DownMbps: protocol.DownMbps,
|
ObfsPassword: protocol.ObfsPassword,
|
||||||
DisableSNI: protocol.DisableSNI,
|
UpMbps: protocol.UpMbps,
|
||||||
ReduceRtt: protocol.ReduceRtt,
|
DownMbps: protocol.DownMbps,
|
||||||
UDPRelayMode: protocol.UDPRelayMode,
|
DisableSNI: protocol.DisableSNI,
|
||||||
CongestionController: protocol.CongestionController,
|
ReduceRtt: protocol.ReduceRtt,
|
||||||
PaddingScheme: protocol.PaddingScheme,
|
UDPRelayMode: protocol.UDPRelayMode,
|
||||||
Multiplex: protocol.Multiplex,
|
CongestionController: protocol.CongestionController,
|
||||||
XhttpMode: protocol.XhttpMode,
|
PaddingScheme: protocol.PaddingScheme,
|
||||||
XhttpExtra: protocol.XhttpExtra,
|
Multiplex: protocol.Multiplex,
|
||||||
Encryption: protocol.Encryption,
|
XhttpMode: protocol.XhttpMode,
|
||||||
EncryptionMode: protocol.EncryptionMode,
|
XhttpExtra: protocol.XhttpExtra,
|
||||||
EncryptionRtt: protocol.EncryptionRtt,
|
Encryption: protocol.Encryption,
|
||||||
EncryptionTicket: protocol.EncryptionTicket,
|
EncryptionMode: protocol.EncryptionMode,
|
||||||
EncryptionServerPadding: protocol.EncryptionServerPadding,
|
EncryptionRtt: protocol.EncryptionRtt,
|
||||||
EncryptionPrivateKey: protocol.EncryptionPrivateKey,
|
EncryptionTicket: protocol.EncryptionTicket,
|
||||||
EncryptionClientPadding: protocol.EncryptionClientPadding,
|
EncryptionServerPadding: protocol.EncryptionServerPadding,
|
||||||
EncryptionPassword: protocol.EncryptionPassword,
|
EncryptionPrivateKey: protocol.EncryptionPrivateKey,
|
||||||
Ratio: protocol.Ratio,
|
EncryptionClientPadding: protocol.EncryptionClientPadding,
|
||||||
CertMode: protocol.CertMode,
|
EncryptionPassword: protocol.EncryptionPassword,
|
||||||
CertDNSProvider: protocol.CertDNSProvider,
|
Ratio: protocol.Ratio,
|
||||||
CertDNSEnv: protocol.CertDNSEnv,
|
CertMode: protocol.CertMode,
|
||||||
})
|
CertDNSProvider: protocol.CertDNSProvider,
|
||||||
|
CertDNSEnv: protocol.CertDNSEnv,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -93,12 +93,13 @@ type User struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
SiteName string // Name of the site
|
SiteName string // Name of the site
|
||||||
SubscribeName string // Name of the subscription
|
SubscribeName string // Name of the subscription
|
||||||
ClientTemplate string // Template for the entire client configuration
|
ClientTemplate string // Template for the entire client configuration
|
||||||
OutputFormat string // json, yaml, etc.
|
OutputFormat string // json, yaml, etc.
|
||||||
Proxies []Proxy // List of proxy configurations
|
Proxies []Proxy // List of proxy configurations
|
||||||
UserInfo User // User information
|
UserInfo User // User information
|
||||||
|
Params map[string]string // Additional parameters
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Build() ([]byte, error) {
|
func (c *Client) Build() ([]byte, error) {
|
||||||
@ -119,6 +120,7 @@ func (c *Client) Build() ([]byte, error) {
|
|||||||
"OutputFormat": c.OutputFormat,
|
"OutputFormat": c.OutputFormat,
|
||||||
"Proxies": proxies,
|
"Proxies": proxies,
|
||||||
"UserInfo": c.UserInfo,
|
"UserInfo": c.UserInfo,
|
||||||
|
"Params": c.Params,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@ -189,14 +189,6 @@ service ppanel {
|
|||||||
@handler ToggleNodeStatus
|
@handler ToggleNodeStatus
|
||||||
post /node/status/toggle (ToggleNodeStatusRequest)
|
post /node/status/toggle (ToggleNodeStatusRequest)
|
||||||
|
|
||||||
@doc "Check if there is any server or node to migrate"
|
|
||||||
@handler HasMigrateSeverNode
|
|
||||||
get /migrate/has returns (HasMigrateSeverNodeResponse)
|
|
||||||
|
|
||||||
@doc "Migrate server and node data to new database"
|
|
||||||
@handler MigrateServerNode
|
|
||||||
post /migrate/run returns (MigrateServerNodeResponse)
|
|
||||||
|
|
||||||
@doc "Reset server sort"
|
@doc "Reset server sort"
|
||||||
@handler ResetSortWithServer
|
@handler ResetSortWithServer
|
||||||
post /server/sort (ResetSortRequest)
|
post /server/sort (ResetSortRequest)
|
||||||
|
|||||||
@ -34,50 +34,52 @@ type (
|
|||||||
Ids []int64 `json:"ids" validate:"required"`
|
Ids []int64 `json:"ids" validate:"required"`
|
||||||
}
|
}
|
||||||
CreateSubscribeRequest {
|
CreateSubscribeRequest {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
UnitPrice int64 `json:"unit_price"`
|
UnitPrice int64 `json:"unit_price"`
|
||||||
UnitTime string `json:"unit_time"`
|
UnitTime string `json:"unit_time"`
|
||||||
Discount []SubscribeDiscount `json:"discount"`
|
Discount []SubscribeDiscount `json:"discount"`
|
||||||
Replacement int64 `json:"replacement"`
|
Replacement int64 `json:"replacement"`
|
||||||
Inventory int64 `json:"inventory"`
|
Inventory int64 `json:"inventory"`
|
||||||
Traffic int64 `json:"traffic"`
|
Traffic int64 `json:"traffic"`
|
||||||
SpeedLimit int64 `json:"speed_limit"`
|
SpeedLimit int64 `json:"speed_limit"`
|
||||||
DeviceLimit int64 `json:"device_limit"`
|
DeviceLimit int64 `json:"device_limit"`
|
||||||
Quota int64 `json:"quota"`
|
Quota int64 `json:"quota"`
|
||||||
Nodes []int64 `json:"nodes"`
|
Nodes []int64 `json:"nodes"`
|
||||||
NodeTags []string `json:"node_tags"`
|
NodeTags []string `json:"node_tags"`
|
||||||
Show *bool `json:"show"`
|
Show *bool `json:"show"`
|
||||||
Sell *bool `json:"sell"`
|
Sell *bool `json:"sell"`
|
||||||
DeductionRatio int64 `json:"deduction_ratio"`
|
DeductionRatio int64 `json:"deduction_ratio"`
|
||||||
AllowDeduction *bool `json:"allow_deduction"`
|
AllowDeduction *bool `json:"allow_deduction"`
|
||||||
ResetCycle int64 `json:"reset_cycle"`
|
ResetCycle int64 `json:"reset_cycle"`
|
||||||
RenewalReset *bool `json:"renewal_reset"`
|
RenewalReset *bool `json:"renewal_reset"`
|
||||||
|
ShowOriginalPrice bool `json:"show_original_price"`
|
||||||
}
|
}
|
||||||
UpdateSubscribeRequest {
|
UpdateSubscribeRequest {
|
||||||
Id int64 `json:"id" validate:"required"`
|
Id int64 `json:"id" validate:"required"`
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
UnitPrice int64 `json:"unit_price"`
|
UnitPrice int64 `json:"unit_price"`
|
||||||
UnitTime string `json:"unit_time"`
|
UnitTime string `json:"unit_time"`
|
||||||
Discount []SubscribeDiscount `json:"discount"`
|
Discount []SubscribeDiscount `json:"discount"`
|
||||||
Replacement int64 `json:"replacement"`
|
Replacement int64 `json:"replacement"`
|
||||||
Inventory int64 `json:"inventory"`
|
Inventory int64 `json:"inventory"`
|
||||||
Traffic int64 `json:"traffic"`
|
Traffic int64 `json:"traffic"`
|
||||||
SpeedLimit int64 `json:"speed_limit"`
|
SpeedLimit int64 `json:"speed_limit"`
|
||||||
DeviceLimit int64 `json:"device_limit"`
|
DeviceLimit int64 `json:"device_limit"`
|
||||||
Quota int64 `json:"quota"`
|
Quota int64 `json:"quota"`
|
||||||
Nodes []int64 `json:"nodes"`
|
Nodes []int64 `json:"nodes"`
|
||||||
NodeTags []string `json:"node_tags"`
|
NodeTags []string `json:"node_tags"`
|
||||||
Show *bool `json:"show"`
|
Show *bool `json:"show"`
|
||||||
Sell *bool `json:"sell"`
|
Sell *bool `json:"sell"`
|
||||||
Sort int64 `json:"sort"`
|
Sort int64 `json:"sort"`
|
||||||
DeductionRatio int64 `json:"deduction_ratio"`
|
DeductionRatio int64 `json:"deduction_ratio"`
|
||||||
AllowDeduction *bool `json:"allow_deduction"`
|
AllowDeduction *bool `json:"allow_deduction"`
|
||||||
ResetCycle int64 `json:"reset_cycle"`
|
ResetCycle int64 `json:"reset_cycle"`
|
||||||
RenewalReset *bool `json:"renewal_reset"`
|
RenewalReset *bool `json:"renewal_reset"`
|
||||||
|
ShowOriginalPrice bool `json:"show_original_price"`
|
||||||
}
|
}
|
||||||
SubscribeSortRequest {
|
SubscribeSortRequest {
|
||||||
Sort []SortItem `json:"sort"`
|
Sort []SortItem `json:"sort"`
|
||||||
|
|||||||
@ -19,6 +19,7 @@ type (
|
|||||||
Size int `form:"size"`
|
Size int `form:"size"`
|
||||||
Search string `form:"search,omitempty"`
|
Search string `form:"search,omitempty"`
|
||||||
UserId *int64 `form:"user_id,omitempty"`
|
UserId *int64 `form:"user_id,omitempty"`
|
||||||
|
Unscoped bool `form:"unscoped,omitempty"`
|
||||||
SubscribeId *int64 `form:"subscribe_id,omitempty"`
|
SubscribeId *int64 `form:"subscribe_id,omitempty"`
|
||||||
UserSubscribeId *int64 `form:"user_subscribe_id,omitempty"`
|
UserSubscribeId *int64 `form:"user_subscribe_id,omitempty"`
|
||||||
}
|
}
|
||||||
@ -183,6 +184,12 @@ type (
|
|||||||
GetUserSubscribeByIdRequest {
|
GetUserSubscribeByIdRequest {
|
||||||
Id int64 `form:"id" validate:"required"`
|
Id int64 `form:"id" validate:"required"`
|
||||||
}
|
}
|
||||||
|
ToggleUserSubscribeStatusRequest {
|
||||||
|
UserSubscribeId int64 `json:"user_subscribe_id"`
|
||||||
|
}
|
||||||
|
ResetUserSubscribeTrafficRequest {
|
||||||
|
UserSubscribeId int64 `json:"user_subscribe_id"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@server (
|
@server (
|
||||||
@ -291,5 +298,17 @@ service ppanel {
|
|||||||
@doc "Get user login logs"
|
@doc "Get user login logs"
|
||||||
@handler GetUserLoginLogs
|
@handler GetUserLoginLogs
|
||||||
get /login/logs (GetUserLoginLogsRequest) returns (GetUserLoginLogsResponse)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,9 +14,11 @@ type (
|
|||||||
QuerySubscribeListRequest {
|
QuerySubscribeListRequest {
|
||||||
Language string `form:"language"`
|
Language string `form:"language"`
|
||||||
}
|
}
|
||||||
QueryUserSubscribeNodeListResponse {
|
|
||||||
List []UserSubscribeInfo `json:"list"`
|
QueryUserSubscribeNodeListResponse {
|
||||||
}
|
List []UserSubscribeInfo `json:"list"`
|
||||||
|
}
|
||||||
|
|
||||||
UserSubscribeInfo {
|
UserSubscribeInfo {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
UserId int64 `json:"user_id"`
|
UserId int64 `json:"user_id"`
|
||||||
|
|||||||
@ -66,9 +66,7 @@ type (
|
|||||||
UnbindOAuthRequest {
|
UnbindOAuthRequest {
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
}
|
}
|
||||||
ResetUserSubscribeTokenRequest {
|
|
||||||
UserSubscribeId int64 `json:"user_subscribe_id"`
|
|
||||||
}
|
|
||||||
GetLoginLogRequest {
|
GetLoginLogRequest {
|
||||||
Page int `form:"page"`
|
Page int `form:"page"`
|
||||||
Size int `form:"size"`
|
Size int `form:"size"`
|
||||||
@ -282,15 +280,3 @@ service ppanel {
|
|||||||
get /withdrawal_log (QueryWithdrawalLogListRequest) returns (QueryWithdrawalLogListResponse)
|
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
|
|
||||||
}
|
|
||||||
@ -32,7 +32,6 @@ type (
|
|||||||
CreatedAt int64 `json:"created_at"`
|
CreatedAt int64 `json:"created_at"`
|
||||||
UpdatedAt int64 `json:"updated_at"`
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
DeletedAt int64 `json:"deleted_at,omitempty"`
|
DeletedAt int64 `json:"deleted_at,omitempty"`
|
||||||
IsDel bool `json:"is_del,omitempty"`
|
|
||||||
}
|
}
|
||||||
Follow {
|
Follow {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
@ -136,12 +135,14 @@ type (
|
|||||||
EnableDomainSuffix bool `json:"enable_domain_suffix"`
|
EnableDomainSuffix bool `json:"enable_domain_suffix"`
|
||||||
DomainSuffixList string `json:"domain_suffix_list"`
|
DomainSuffixList string `json:"domain_suffix_list"`
|
||||||
}
|
}
|
||||||
DeviceAuthticateConfig {
|
|
||||||
Enable bool `json:"enable"`
|
DeviceAuthticateConfig {
|
||||||
ShowAds bool `json:"show_ads"`
|
Enable bool `json:"enable"`
|
||||||
EnableSecurity bool `json:"enable_security"`
|
ShowAds bool `json:"show_ads"`
|
||||||
OnlyRealDevice bool `json:"only_real_device"`
|
EnableSecurity bool `json:"enable_security"`
|
||||||
}
|
OnlyRealDevice bool `json:"only_real_device"`
|
||||||
|
}
|
||||||
|
|
||||||
RegisterConfig {
|
RegisterConfig {
|
||||||
StopRegister bool `json:"stop_register"`
|
StopRegister bool `json:"stop_register"`
|
||||||
EnableTrial bool `json:"enable_trial"`
|
EnableTrial bool `json:"enable_trial"`
|
||||||
@ -209,30 +210,31 @@ type (
|
|||||||
Discount float64 `json:"discount"`
|
Discount float64 `json:"discount"`
|
||||||
}
|
}
|
||||||
Subscribe {
|
Subscribe {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
UnitPrice int64 `json:"unit_price"`
|
UnitPrice int64 `json:"unit_price"`
|
||||||
UnitTime string `json:"unit_time"`
|
UnitTime string `json:"unit_time"`
|
||||||
Discount []SubscribeDiscount `json:"discount"`
|
Discount []SubscribeDiscount `json:"discount"`
|
||||||
Replacement int64 `json:"replacement"`
|
Replacement int64 `json:"replacement"`
|
||||||
Inventory int64 `json:"inventory"`
|
Inventory int64 `json:"inventory"`
|
||||||
Traffic int64 `json:"traffic"`
|
Traffic int64 `json:"traffic"`
|
||||||
SpeedLimit int64 `json:"speed_limit"`
|
SpeedLimit int64 `json:"speed_limit"`
|
||||||
DeviceLimit int64 `json:"device_limit"`
|
DeviceLimit int64 `json:"device_limit"`
|
||||||
Quota int64 `json:"quota"`
|
Quota int64 `json:"quota"`
|
||||||
Nodes []int64 `json:"nodes"`
|
Nodes []int64 `json:"nodes"`
|
||||||
NodeTags []string `json:"node_tags"`
|
NodeTags []string `json:"node_tags"`
|
||||||
Show bool `json:"show"`
|
Show bool `json:"show"`
|
||||||
Sell bool `json:"sell"`
|
Sell bool `json:"sell"`
|
||||||
Sort int64 `json:"sort"`
|
Sort int64 `json:"sort"`
|
||||||
DeductionRatio int64 `json:"deduction_ratio"`
|
DeductionRatio int64 `json:"deduction_ratio"`
|
||||||
AllowDeduction bool `json:"allow_deduction"`
|
AllowDeduction bool `json:"allow_deduction"`
|
||||||
ResetCycle int64 `json:"reset_cycle"`
|
ResetCycle int64 `json:"reset_cycle"`
|
||||||
RenewalReset bool `json:"renewal_reset"`
|
RenewalReset bool `json:"renewal_reset"`
|
||||||
CreatedAt int64 `json:"created_at"`
|
ShowOriginalPrice bool `json:"show_original_price"`
|
||||||
UpdatedAt int64 `json:"updated_at"`
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
}
|
}
|
||||||
SubscribeGroup {
|
SubscribeGroup {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
@ -656,7 +658,7 @@ type (
|
|||||||
// public announcement
|
// public announcement
|
||||||
QueryAnnouncementRequest {
|
QueryAnnouncementRequest {
|
||||||
Page int `form:"page"`
|
Page int `form:"page"`
|
||||||
Size int `form:"size"`
|
Size int `form:"size,default=15"`
|
||||||
Pinned *bool `form:"pinned"`
|
Pinned *bool `form:"pinned"`
|
||||||
Popup *bool `form:"popup"`
|
Popup *bool `form:"popup"`
|
||||||
}
|
}
|
||||||
@ -673,6 +675,7 @@ type (
|
|||||||
List []SubscribeGroup `json:"list"`
|
List []SubscribeGroup `json:"list"`
|
||||||
Total int64 `json:"total"`
|
Total int64 `json:"total"`
|
||||||
}
|
}
|
||||||
|
|
||||||
GetUserSubscribeTrafficLogsRequest {
|
GetUserSubscribeTrafficLogsRequest {
|
||||||
Page int `form:"page"`
|
Page int `form:"page"`
|
||||||
Size int `form:"size"`
|
Size int `form:"size"`
|
||||||
@ -845,5 +848,9 @@ type (
|
|||||||
CertDNSProvider string `json:"cert_dns_provider,omitempty"` // DNS provider for certificate
|
CertDNSProvider string `json:"cert_dns_provider,omitempty"` // DNS provider for certificate
|
||||||
CertDNSEnv string `json:"cert_dns_env,omitempty"` // Environment for DNS provider
|
CertDNSEnv string `json:"cert_dns_env,omitempty"` // Environment for DNS provider
|
||||||
}
|
}
|
||||||
|
// reset user subscribe token
|
||||||
|
ResetUserSubscribeTokenRequest {
|
||||||
|
UserSubscribeId int64 `json:"user_subscribe_id"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
2
go.mod
2
go.mod
@ -27,7 +27,7 @@ require (
|
|||||||
github.com/jinzhu/copier v0.4.0
|
github.com/jinzhu/copier v0.4.0
|
||||||
github.com/klauspost/compress v1.17.7
|
github.com/klauspost/compress v1.17.7
|
||||||
github.com/nyaruka/phonenumbers v1.5.0
|
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/redis/go-redis/v9 v9.7.2
|
||||||
github.com/smartwalle/alipay/v3 v3.2.23
|
github.com/smartwalle/alipay/v3 v3.2.23
|
||||||
github.com/spf13/cast v1.7.0 // indirect
|
github.com/spf13/cast v1.7.0 // indirect
|
||||||
|
|||||||
34
initialize/currency.go
Normal file
34
initialize/currency.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
@ -15,6 +15,7 @@ func StartInitSystemConfig(svc *svc.ServiceContext) {
|
|||||||
Subscribe(svc)
|
Subscribe(svc)
|
||||||
Register(svc)
|
Register(svc)
|
||||||
Mobile(svc)
|
Mobile(svc)
|
||||||
|
Currency(svc)
|
||||||
if !svc.Config.Debug {
|
if !svc.Config.Debug {
|
||||||
Telegram(svc)
|
Telegram(svc)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `subscribe`
|
||||||
|
DROP COLUMN `show_original_price`;
|
||||||
@ -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`;
|
||||||
@ -0,0 +1 @@
|
|||||||
|
DROP TABLE IF EXISTS `server_group`;
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
-- This migration script reverts the inventory values in the 'subscribe' table
|
||||||
|
UPDATE `subscribe`
|
||||||
|
SET `inventory` = 0
|
||||||
|
WHERE `inventory` = -1;
|
||||||
4
initialize/migrate/database/02125_subscribe_stock.up.sql
Normal file
4
initialize/migrate/database/02125_subscribe_stock.up.sql
Normal file
@ -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;
|
||||||
@ -29,6 +29,7 @@ type Config struct {
|
|||||||
Invite InviteConfig `yaml:"Invite"`
|
Invite InviteConfig `yaml:"Invite"`
|
||||||
Telegram Telegram `yaml:"Telegram"`
|
Telegram Telegram `yaml:"Telegram"`
|
||||||
Log Log `yaml:"Log"`
|
Log Log `yaml:"Log"`
|
||||||
|
Currency Currency `yaml:"Currency"`
|
||||||
Administrator struct {
|
Administrator struct {
|
||||||
Email string `yaml:"Email" default:"admin@ppanel.dev"`
|
Email string `yaml:"Email" default:"admin@ppanel.dev"`
|
||||||
Password string `yaml:"Password" default:"password"`
|
Password string `yaml:"Password" default:"password"`
|
||||||
@ -241,3 +242,9 @@ type NodeDBConfig struct {
|
|||||||
Block string
|
Block string
|
||||||
Outbound string
|
Outbound string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Currency struct {
|
||||||
|
Unit string `yaml:"Unit" default:"CNY"`
|
||||||
|
Symbol string `yaml:"Symbol" default:"USD"`
|
||||||
|
AccessKey string `yaml:"AccessKey" default:""`
|
||||||
|
}
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -33,7 +33,6 @@ import (
|
|||||||
publicSubscribe "github.com/perfect-panel/server/internal/handler/public/subscribe"
|
publicSubscribe "github.com/perfect-panel/server/internal/handler/public/subscribe"
|
||||||
publicTicket "github.com/perfect-panel/server/internal/handler/public/ticket"
|
publicTicket "github.com/perfect-panel/server/internal/handler/public/ticket"
|
||||||
publicUser "github.com/perfect-panel/server/internal/handler/public/user"
|
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"
|
server "github.com/perfect-panel/server/internal/handler/server"
|
||||||
"github.com/perfect-panel/server/internal/middleware"
|
"github.com/perfect-panel/server/internal/middleware"
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
@ -312,12 +311,6 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
// Filter Server List
|
// Filter Server List
|
||||||
adminServerGroupRouter.GET("/list", adminServer.FilterServerListHandler(serverCtx))
|
adminServerGroupRouter.GET("/list", adminServer.FilterServerListHandler(serverCtx))
|
||||||
|
|
||||||
// Check if there is any server or node to migrate
|
|
||||||
adminServerGroupRouter.GET("/migrate/has", adminServer.HasMigrateSeverNodeHandler(serverCtx))
|
|
||||||
|
|
||||||
// Migrate server and node data to new database
|
|
||||||
adminServerGroupRouter.POST("/migrate/run", adminServer.MigrateServerNodeHandler(serverCtx))
|
|
||||||
|
|
||||||
// Create Node
|
// Create Node
|
||||||
adminServerGroupRouter.POST("/node/create", adminServer.CreateNodeHandler(serverCtx))
|
adminServerGroupRouter.POST("/node/create", adminServer.CreateNodeHandler(serverCtx))
|
||||||
|
|
||||||
@ -583,6 +576,15 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
// Get user subcribe reset traffic logs
|
// Get user subcribe reset traffic logs
|
||||||
adminUserGroupRouter.GET("/subscribe/reset/logs", adminUser.GetUserSubscribeResetTrafficLogsHandler(serverCtx))
|
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
|
// Get user subcribe traffic logs
|
||||||
adminUserGroupRouter.GET("/subscribe/traffic_logs", adminUser.GetUserSubscribeTrafficLogsHandler(serverCtx))
|
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))
|
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 := router.Group("/v1/server")
|
||||||
serverGroupRouter.Use(middleware.ServerMiddleware(serverCtx))
|
serverGroupRouter.Use(middleware.ServerMiddleware(serverCtx))
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,10 @@ func SubscribeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
|||||||
ua := c.GetHeader("User-Agent")
|
ua := c.GetHeader("User-Agent")
|
||||||
req.UA = c.Request.Header.Get("User-Agent")
|
req.UA = c.Request.Header.Get("User-Agent")
|
||||||
req.Flag = c.Query("flag")
|
req.Flag = c.Query("flag")
|
||||||
|
req.Type = c.Query("type")
|
||||||
|
// 获取所有查询参数
|
||||||
|
req.Params = getQueryMap(c.Request)
|
||||||
|
|
||||||
if svcCtx.Config.Subscribe.PanDomain {
|
if svcCtx.Config.Subscribe.PanDomain {
|
||||||
domain := c.Request.Host
|
domain := c.Request.Host
|
||||||
domainArr := strings.Split(domain, ".")
|
domainArr := strings.Split(domain, ".")
|
||||||
@ -33,7 +37,8 @@ func SubscribeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
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.String(http.StatusForbidden, "Access denied")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@ -94,3 +99,14 @@ func RegisterSubscribeHandlers(router *gin.Engine, serverCtx *svc.ServiceContext
|
|||||||
}
|
}
|
||||||
router.GET(path, SubscribeHandler(serverCtx))
|
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
|
||||||
|
}
|
||||||
|
|||||||
@ -31,6 +31,7 @@ func (l *CreateNodeLogic) CreateNode(req *types.CreateNodeRequest) error {
|
|||||||
data := node.Node{
|
data := node.Node{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Tags: tool.StringSliceToString(req.Tags),
|
Tags: tool.StringSliceToString(req.Tags),
|
||||||
|
Enabled: req.Enabled,
|
||||||
Port: req.Port,
|
Port: req.Port,
|
||||||
Address: req.Address,
|
Address: req.Address,
|
||||||
ServerId: req.ServerId,
|
ServerId: req.ServerId,
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
@ -35,28 +35,29 @@ func (l *CreateSubscribeLogic) CreateSubscribe(req *types.CreateSubscribeRequest
|
|||||||
discount = string(val)
|
discount = string(val)
|
||||||
}
|
}
|
||||||
sub := &subscribe.Subscribe{
|
sub := &subscribe.Subscribe{
|
||||||
Id: 0,
|
Id: 0,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Language: req.Language,
|
Language: req.Language,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
UnitPrice: req.UnitPrice,
|
UnitPrice: req.UnitPrice,
|
||||||
UnitTime: req.UnitTime,
|
UnitTime: req.UnitTime,
|
||||||
Discount: discount,
|
Discount: discount,
|
||||||
Replacement: req.Replacement,
|
Replacement: req.Replacement,
|
||||||
Inventory: req.Inventory,
|
Inventory: req.Inventory,
|
||||||
Traffic: req.Traffic,
|
Traffic: req.Traffic,
|
||||||
SpeedLimit: req.SpeedLimit,
|
SpeedLimit: req.SpeedLimit,
|
||||||
DeviceLimit: req.DeviceLimit,
|
DeviceLimit: req.DeviceLimit,
|
||||||
Quota: req.Quota,
|
Quota: req.Quota,
|
||||||
Nodes: tool.Int64SliceToString(req.Nodes),
|
Nodes: tool.Int64SliceToString(req.Nodes),
|
||||||
NodeTags: tool.StringSliceToString(req.NodeTags),
|
NodeTags: tool.StringSliceToString(req.NodeTags),
|
||||||
Show: req.Show,
|
Show: req.Show,
|
||||||
Sell: req.Sell,
|
Sell: req.Sell,
|
||||||
Sort: 0,
|
Sort: 0,
|
||||||
DeductionRatio: req.DeductionRatio,
|
DeductionRatio: req.DeductionRatio,
|
||||||
AllowDeduction: req.AllowDeduction,
|
AllowDeduction: req.AllowDeduction,
|
||||||
ResetCycle: req.ResetCycle,
|
ResetCycle: req.ResetCycle,
|
||||||
RenewalReset: req.RenewalReset,
|
RenewalReset: req.RenewalReset,
|
||||||
|
ShowOriginalPrice: req.ShowOriginalPrice,
|
||||||
}
|
}
|
||||||
err := l.svcCtx.SubscribeModel.Insert(l.ctx, sub)
|
err := l.svcCtx.SubscribeModel.Insert(l.ctx, sub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -43,28 +43,29 @@ func (l *UpdateSubscribeLogic) UpdateSubscribe(req *types.UpdateSubscribeRequest
|
|||||||
discount = string(val)
|
discount = string(val)
|
||||||
}
|
}
|
||||||
sub := &subscribe.Subscribe{
|
sub := &subscribe.Subscribe{
|
||||||
Id: req.Id,
|
Id: req.Id,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Language: req.Language,
|
Language: req.Language,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
UnitPrice: req.UnitPrice,
|
UnitPrice: req.UnitPrice,
|
||||||
UnitTime: req.UnitTime,
|
UnitTime: req.UnitTime,
|
||||||
Discount: discount,
|
Discount: discount,
|
||||||
Replacement: req.Replacement,
|
Replacement: req.Replacement,
|
||||||
Inventory: req.Inventory,
|
Inventory: req.Inventory,
|
||||||
Traffic: req.Traffic,
|
Traffic: req.Traffic,
|
||||||
SpeedLimit: req.SpeedLimit,
|
SpeedLimit: req.SpeedLimit,
|
||||||
DeviceLimit: req.DeviceLimit,
|
DeviceLimit: req.DeviceLimit,
|
||||||
Quota: req.Quota,
|
Quota: req.Quota,
|
||||||
Nodes: tool.Int64SliceToString(req.Nodes),
|
Nodes: tool.Int64SliceToString(req.Nodes),
|
||||||
NodeTags: tool.StringSliceToString(req.NodeTags),
|
NodeTags: tool.StringSliceToString(req.NodeTags),
|
||||||
Show: req.Show,
|
Show: req.Show,
|
||||||
Sell: req.Sell,
|
Sell: req.Sell,
|
||||||
Sort: req.Sort,
|
Sort: req.Sort,
|
||||||
DeductionRatio: req.DeductionRatio,
|
DeductionRatio: req.DeductionRatio,
|
||||||
AllowDeduction: req.AllowDeduction,
|
AllowDeduction: req.AllowDeduction,
|
||||||
ResetCycle: req.ResetCycle,
|
ResetCycle: req.ResetCycle,
|
||||||
RenewalReset: req.RenewalReset,
|
RenewalReset: req.RenewalReset,
|
||||||
|
ShowOriginalPrice: req.ShowOriginalPrice,
|
||||||
}
|
}
|
||||||
err = l.svcCtx.SubscribeModel.Update(l.ctx, sub)
|
err = l.svcCtx.SubscribeModel.Update(l.ctx, sub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package system
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/config"
|
"github.com/perfect-panel/server/internal/config"
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
"github.com/perfect-panel/server/internal/types"
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/initialize"
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
"github.com/perfect-panel/server/internal/types"
|
"github.com/perfect-panel/server/internal/types"
|
||||||
"github.com/perfect-panel/server/pkg/logger"
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
@ -32,9 +33,12 @@ func (l *SetNodeMultiplierLogic) SetNodeMultiplier(req *types.SetNodeMultiplierR
|
|||||||
l.Logger.Error("Marshal Node Multiplier Config Error: ", logger.Field("error", err.Error()))
|
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())
|
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()))
|
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())
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Update Node Multiplier Config Error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
// update Node Multiplier
|
||||||
|
initialize.Node(l.svcCtx)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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{
|
list, total, err := l.svcCtx.UserModel.QueryPageList(l.ctx, req.Page, req.Size, &user.UserFilterParams{
|
||||||
UserId: req.UserId,
|
UserId: req.UserId,
|
||||||
Search: req.Search,
|
Search: req.Search,
|
||||||
|
Unscoped: req.Unscoped,
|
||||||
SubscribeId: req.SubscribeId,
|
SubscribeId: req.SubscribeId,
|
||||||
UserSubscribeId: req.UserSubscribeId,
|
UserSubscribeId: req.UserSubscribeId,
|
||||||
Order: "DESC",
|
Order: "DESC",
|
||||||
|
|||||||
@ -41,6 +41,7 @@ func (l *GetUserSubscribeLogic) GetUserSubscribe(req *types.GetUserSubscribeList
|
|||||||
for _, item := range data {
|
for _, item := range data {
|
||||||
var sub types.UserSubscribe
|
var sub types.UserSubscribe
|
||||||
tool.DeepCopy(&sub, item)
|
tool.DeepCopy(&sub, item)
|
||||||
|
sub.Short, _ = tool.FixedUniqueString(item.Token, 8, "")
|
||||||
resp.List = append(resp.List, sub)
|
resp.List = append(resp.List, sub)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
55
internal/logic/admin/user/resetUserSubscribeTokenLogic.go
Normal file
55
internal/logic/admin/user/resetUserSubscribeTokenLogic.go
Normal file
@ -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
|
||||||
|
}
|
||||||
53
internal/logic/admin/user/resetUserSubscribeTrafficLogic.go
Normal file
53
internal/logic/admin/user/resetUserSubscribeTrafficLogic.go
Normal file
@ -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
|
||||||
|
}
|
||||||
63
internal/logic/admin/user/toggleUserSubscribeStatusLogic.go
Normal file
63
internal/logic/admin/user/toggleUserSubscribeStatusLogic.go
Normal file
@ -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
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/model/user"
|
"github.com/perfect-panel/server/internal/model/user"
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
@ -206,6 +207,86 @@ func (l *BindDeviceLogic) rebindDeviceToNewUser(deviceInfo *user.Device, ip, use
|
|||||||
|
|
||||||
//如果没有其他认证方式,禁用旧用户账号
|
//如果没有其他认证方式,禁用旧用户账号
|
||||||
if count < 1 {
|
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 {
|
if err := tx.Model(&user.User{}).Where("id = ?", oldUserId).Delete(&user.User{}).Error; err != nil {
|
||||||
l.Errorw("failed to disable old user",
|
l.Errorw("failed to disable old user",
|
||||||
logger.Field("old_user_id", oldUserId),
|
logger.Field("old_user_id", oldUserId),
|
||||||
|
|||||||
@ -68,6 +68,10 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log
|
|||||||
|
|
||||||
userInfo, err = l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email)
|
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 err != nil {
|
||||||
if errors.As(err, &gorm.ErrRecordNotFound) {
|
if errors.As(err, &gorm.ErrRecordNotFound) {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserNotExist), "user email not exist: %v", req.Email)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserNotExist), "user email not exist: %v", req.Email)
|
||||||
|
|||||||
@ -79,12 +79,14 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check if the user exists
|
// 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) {
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
l.Errorw("FindOneByEmail Error", logger.Field("error", err))
|
l.Errorw("FindOneByEmail Error", logger.Field("error", err))
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query user info failed: %v", err.Error())
|
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)
|
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) {
|
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(),
|
time.Now().Unix(),
|
||||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
jwt.WithOption("UserId", userInfo.Id),
|
jwt.WithOption("UserId", userInfo.Id),
|
||||||
jwt.WithOption("identifier", req.Identifier),
|
|
||||||
jwt.WithOption("SessionId", sessionId),
|
jwt.WithOption("SessionId", sessionId),
|
||||||
jwt.WithOption("CtxLoginType", req.LoginType),
|
jwt.WithOption("LoginType", req.LoginType),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
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(),
|
UUID: uuidx.NewUUID().String(),
|
||||||
Status: 1,
|
Status: 1,
|
||||||
}
|
}
|
||||||
err = l.svcCtx.UserModel.InsertSubscribe(l.ctx, userSub)
|
return 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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,6 +51,8 @@ func (l *GetGlobalConfigLogic) GetGlobalConfig() (resp *types.GetGlobalConfigRes
|
|||||||
tool.SystemConfigSliceReflectToStruct(currencyCfg, &resp.Currency)
|
tool.SystemConfigSliceReflectToStruct(currencyCfg, &resp.Currency)
|
||||||
tool.SystemConfigSliceReflectToStruct(verifyCodeCfg, &resp.VerifyCode)
|
tool.SystemConfigSliceReflectToStruct(verifyCodeCfg, &resp.VerifyCode)
|
||||||
|
|
||||||
|
resp.Subscribe.SubscribePath = "/sub" + l.svcCtx.Config.Subscribe.SubscribePath
|
||||||
|
|
||||||
resp.Verify = types.VeifyConfig{
|
resp.Verify = types.VeifyConfig{
|
||||||
TurnstileSiteKey: l.svcCtx.Config.Verify.TurnstileSiteKey,
|
TurnstileSiteKey: l.svcCtx.Config.Verify.TurnstileSiteKey,
|
||||||
EnableLoginVerify: l.svcCtx.Config.Verify.LoginVerify,
|
EnableLoginVerify: l.svcCtx.Config.Verify.LoginVerify,
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/config"
|
"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/model/user"
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
"github.com/perfect-panel/server/internal/types"
|
"github.com/perfect-panel/server/internal/types"
|
||||||
@ -57,13 +57,13 @@ func (l *GetStatLogic) GetStat() (resp *types.GetStatResponse, err error) {
|
|||||||
u = 1
|
u = 1
|
||||||
}
|
}
|
||||||
var n int64
|
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 {
|
if err != nil {
|
||||||
l.Logger.Error("[GetStatLogic] get server count failed: ", logger.Field("error", err.Error()))
|
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())
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get server count failed: %v", err.Error())
|
||||||
}
|
}
|
||||||
var nodeaddr []string
|
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 {
|
if err != nil {
|
||||||
l.Logger.Error("[GetStatLogic] get server_addr failed: ", logger.Field("error", err.Error()))
|
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())
|
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)
|
protocolDict := make(map[string]void)
|
||||||
var protocol []string
|
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 {
|
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
|
protocol = nil
|
||||||
for p := range protocolDict {
|
for p := range protocolDict {
|
||||||
|
|||||||
@ -51,6 +51,16 @@ func (l *CloseOrderLogic) CloseOrder(req *types.CloseOrderRequest) error {
|
|||||||
)
|
)
|
||||||
return nil
|
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 {
|
err = l.svcCtx.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
// update order status
|
// update order status
|
||||||
err := tx.Model(&order.Order{}).Where("order_no = ?", req.OrderNo).Update("status", 3).Error
|
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
|
// update user cache
|
||||||
return l.svcCtx.UserModel.UpdateUserCache(l.ctx, userInfo)
|
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
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logger.Errorf("[CloseOrder] Transaction failed: %v", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -81,6 +81,12 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P
|
|||||||
if !*sub.Sell {
|
if !*sub.Sell {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "subscribe not 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
|
// check subscribe plan limit
|
||||||
if sub.Quota > 0 {
|
if sub.Quota > 0 {
|
||||||
var count int64
|
var count int64
|
||||||
@ -221,10 +227,23 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P
|
|||||||
return e
|
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
|
// insert order
|
||||||
return db.WithContext(l.ctx).Model(&order.Order{}).Create(&orderInfo).Error
|
return db.WithContext(l.ctx).Model(&order.Order{}).Create(&orderInfo).Error
|
||||||
})
|
})
|
||||||
if err != nil {
|
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())
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert order error: %v", err.Error())
|
||||||
}
|
}
|
||||||
// Deferred task
|
// Deferred task
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/perfect-panel/server/internal/model/log"
|
"github.com/perfect-panel/server/internal/model/log"
|
||||||
"github.com/perfect-panel/server/internal/report"
|
"github.com/perfect-panel/server/internal/report"
|
||||||
"github.com/perfect-panel/server/pkg/constant"
|
"github.com/perfect-panel/server/pkg/constant"
|
||||||
|
"github.com/perfect-panel/server/pkg/exchangeRate"
|
||||||
|
|
||||||
paymentPlatform "github.com/perfect-panel/server/pkg/payment"
|
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/model/payment"
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
"github.com/perfect-panel/server/internal/types"
|
"github.com/perfect-panel/server/internal/types"
|
||||||
"github.com/perfect-panel/server/pkg/exchangeRate"
|
|
||||||
"github.com/perfect-panel/server/pkg/logger"
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
"github.com/perfect-panel/server/pkg/payment/alipay"
|
"github.com/perfect-panel/server/pkg/payment/alipay"
|
||||||
"github.com/perfect-panel/server/pkg/payment/epay"
|
"github.com/perfect-panel/server/pkg/payment/epay"
|
||||||
"github.com/perfect-panel/server/pkg/payment/stripe"
|
"github.com/perfect-panel/server/pkg/payment/stripe"
|
||||||
"github.com/perfect-panel/server/pkg/tool"
|
|
||||||
"github.com/perfect-panel/server/pkg/xerr"
|
"github.com/perfect-panel/server/pkg/xerr"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
@ -261,6 +260,7 @@ func (l *PurchaseCheckoutLogic) stripePayment(config string, info *order.Order,
|
|||||||
// epayPayment processes EPay payment by generating a payment URL for redirect
|
// epayPayment processes EPay payment by generating a payment URL for redirect
|
||||||
// It handles currency conversion and creates a payment URL for external payment processing
|
// 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) {
|
func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order.Order, returnUrl string) (string, error) {
|
||||||
|
var err error
|
||||||
// Parse EPay configuration from payment settings
|
// Parse EPay configuration from payment settings
|
||||||
epayConfig := &payment.EPayConfig{}
|
epayConfig := &payment.EPayConfig{}
|
||||||
if err := epayConfig.Unmarshal([]byte(config.Config)); err != nil {
|
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
|
// Initialize EPay client with merchant credentials
|
||||||
client := epay.NewClient(epayConfig.Pid, epayConfig.Url, epayConfig.Key, epayConfig.Type)
|
client := epay.NewClient(epayConfig.Pid, epayConfig.Url, epayConfig.Key, epayConfig.Type)
|
||||||
|
var amount float64
|
||||||
// Convert order amount to CNY using current exchange rate
|
if l.svcCtx.Config.Currency.Unit != "CNY" {
|
||||||
amount, err := l.queryExchangeRate("CNY", info.Amount)
|
// Convert order amount to CNY using current exchange rate
|
||||||
if err != nil {
|
amount, err = l.queryExchangeRate("CNY", info.Amount)
|
||||||
return "", err
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
amount = float64(info.Amount) / float64(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
// gateway mod
|
// gateway mod
|
||||||
|
|
||||||
isGatewayMod := report.IsGatewayMode()
|
isGatewayMod := report.IsGatewayMode()
|
||||||
|
|
||||||
// Build notification URL for payment status callbacks
|
// Build notification URL for payment status callbacks
|
||||||
@ -293,7 +296,6 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order
|
|||||||
if !ok {
|
if !ok {
|
||||||
host = l.svcCtx.Config.Host
|
host = l.svcCtx.Config.Host
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyUrl = "https://" + host
|
notifyUrl = "https://" + host
|
||||||
if isGatewayMod {
|
if isGatewayMod {
|
||||||
notifyUrl += "/api"
|
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
|
// CryptoSaaSPayment processes CryptoSaaSPayment payment by generating a payment URL for redirect
|
||||||
// It handles currency conversion and creates a payment URL for external payment processing
|
// 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) {
|
func (l *PurchaseCheckoutLogic) CryptoSaaSPayment(config *payment.Payment, info *order.Order, returnUrl string) (string, error) {
|
||||||
|
var err error
|
||||||
// Parse EPay configuration from payment settings
|
// Parse EPay configuration from payment settings
|
||||||
epayConfig := &payment.CryptoSaaSConfig{}
|
epayConfig := &payment.CryptoSaaSConfig{}
|
||||||
if err := epayConfig.Unmarshal([]byte(config.Config)); err != nil {
|
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
|
// Initialize EPay client with merchant credentials
|
||||||
client := epay.NewClient(epayConfig.AccountID, epayConfig.Endpoint, epayConfig.SecretKey, epayConfig.Type)
|
client := epay.NewClient(epayConfig.AccountID, epayConfig.Endpoint, epayConfig.SecretKey, epayConfig.Type)
|
||||||
|
|
||||||
// Convert order amount to CNY using current exchange rate
|
var amount float64
|
||||||
amount, err := l.queryExchangeRate("CNY", info.Amount)
|
|
||||||
if err != nil {
|
if l.svcCtx.Config.Currency.Unit != "CNY" {
|
||||||
return "", err
|
// 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
|
// gateway mod
|
||||||
@ -354,6 +363,7 @@ func (l *PurchaseCheckoutLogic) CryptoSaaSPayment(config *payment.Payment, info
|
|||||||
}
|
}
|
||||||
notifyUrl = notifyUrl + "/v1/notify/" + config.Platform + "/" + config.Token
|
notifyUrl = notifyUrl + "/v1/notify/" + config.Platform + "/" + config.Token
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create payment URL for user redirection
|
// Create payment URL for user redirection
|
||||||
url := client.CreatePayUrl(epay.Order{
|
url := client.CreatePayUrl(epay.Order{
|
||||||
Name: l.svcCtx.Config.Site.SiteName,
|
Name: l.svcCtx.Config.Site.SiteName,
|
||||||
@ -377,35 +387,18 @@ func (l *PurchaseCheckoutLogic) queryExchangeRate(to string, src int64) (amount
|
|||||||
return amount, nil
|
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
|
// Skip conversion if no exchange rate API key configured
|
||||||
if configs.AccessKey == "" {
|
if l.svcCtx.Config.Currency.AccessKey == "" {
|
||||||
return amount, nil
|
return amount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert currency if system currency differs from target currency
|
// Convert currency if system currency differs from target currency
|
||||||
if configs.CurrencyUnit != to {
|
result, err := exchangeRate.GetExchangeRete(l.svcCtx.Config.Currency.Unit, to, l.svcCtx.Config.Currency.AccessKey, 1)
|
||||||
result, err := exchangeRate.GetExchangeRete(configs.CurrencyUnit, to, configs.AccessKey, 1)
|
if err != nil {
|
||||||
if err != nil {
|
return 0, err
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
amount = result * amount
|
|
||||||
}
|
}
|
||||||
return amount, nil
|
l.svcCtx.ExchangeRate = result
|
||||||
|
return result * amount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// balancePayment processes balance payment with gift amount priority logic
|
// balancePayment processes balance payment with gift amount priority logic
|
||||||
|
|||||||
@ -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))
|
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())
|
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
|
// check subscribe plan status
|
||||||
if !*sub.Sell {
|
if !*sub.Sell {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "subscribe not 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
|
return err
|
||||||
}
|
}
|
||||||
l.Infow("[Purchase] Guest order", logger.Field("order_no", orderInfo.OrderNo), logger.Field("identifier", req.Identifier))
|
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
|
// save guest order
|
||||||
if err = l.svcCtx.OrderModel.Insert(l.ctx, orderInfo, tx); err != nil {
|
if err = l.svcCtx.OrderModel.Insert(l.ctx, orderInfo, tx); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -51,7 +51,7 @@ func (l *ServerPushUserTrafficLogic) ServerPushUserTraffic(req *types.ServerPush
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorw("[ServerPushUserTraffic] Push traffic task error", logger.Field("error", err.Error()), logger.Field("task", t))
|
l.Errorw("[ServerPushUserTraffic] Push traffic task error", logger.Field("error", err.Error()), logger.Field("task", t))
|
||||||
} else {
|
} 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
|
// Update server last reported time
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/perfect-panel/server/internal/model/client"
|
"github.com/perfect-panel/server/internal/model/client"
|
||||||
"github.com/perfect-panel/server/internal/model/log"
|
"github.com/perfect-panel/server/internal/model/log"
|
||||||
"github.com/perfect-panel/server/internal/model/node"
|
"github.com/perfect-panel/server/internal/model/node"
|
||||||
|
"github.com/perfect-panel/server/internal/report"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/model/user"
|
"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,
|
Download: userSubscribe.Download,
|
||||||
Upload: userSubscribe.Upload,
|
Upload: userSubscribe.Upload,
|
||||||
Traffic: userSubscribe.Traffic,
|
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
|
// Get client config
|
||||||
adapterClient, err := a.Client()
|
adapterClient, err := a.Client()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -143,17 +147,20 @@ func (l *SubscribeLogic) Handler(req *types.SubscribeRequest) (resp *types.Subsc
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *SubscribeLogic) getSubscribeV2URL(token string) string {
|
func (l *SubscribeLogic) getSubscribeV2URL() string {
|
||||||
if l.svc.Config.Subscribe.PanDomain {
|
|
||||||
return fmt.Sprintf("https://%s", l.ctx.Request.Host)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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 != "" {
|
if l.svc.Config.Subscribe.SubscribeDomain != "" {
|
||||||
domains := strings.Split(l.svc.Config.Subscribe.SubscribeDomain, "\n")
|
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)
|
||||||
}
|
}
|
||||||
|
// use current request host
|
||||||
return fmt.Sprintf("https://%s%s?token=%s&", l.ctx.Request.Host, l.svc.Config.Subscribe.SubscribePath, token)
|
return fmt.Sprintf("https://%s%s", l.ctx.Request.Host, uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *SubscribeLogic) getUserSubscribe(token string) (*user.Subscribe, error) {
|
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)
|
nodeIds := tool.StringToInt64Slice(subDetails.Nodes)
|
||||||
tags := strings.Split(subDetails.NodeTags, ",")
|
tags := tool.RemoveStringElement(strings.Split(subDetails.NodeTags, ","), "")
|
||||||
|
|
||||||
l.Debugf("[Generate Subscribe]nodes: %v, NodeTags: %v", nodeIds, tags)
|
|
||||||
|
|
||||||
|
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
|
enable := true
|
||||||
|
var nodes []*node.Node
|
||||||
_, nodes, err := l.svc.NodeModel.FilterNodeList(l.ctx.Request.Context(), &node.FilterNodeParams{
|
_, nodes, err = l.svc.NodeModel.FilterNodeList(l.ctx.Request.Context(), &node.FilterNodeParams{
|
||||||
Page: 1,
|
Page: 1,
|
||||||
Size: 1000,
|
Size: 1000,
|
||||||
NodeId: nodeIds,
|
NodeId: nodeIds,
|
||||||
|
|||||||
@ -27,6 +27,9 @@ type Filter struct {
|
|||||||
|
|
||||||
// GetAnnouncementListByPage get announcement list by page
|
// GetAnnouncementListByPage get announcement list by page
|
||||||
func (m *customAnnouncementModel) GetAnnouncementListByPage(ctx context.Context, page, size int, filter Filter) (int64, []*Announcement, error) {
|
func (m *customAnnouncementModel) GetAnnouncementListByPage(ctx context.Context, page, size int, filter Filter) (int64, []*Announcement, error) {
|
||||||
|
if size == 0 {
|
||||||
|
size = 10
|
||||||
|
}
|
||||||
var list []*Announcement
|
var list []*Announcement
|
||||||
var total int64
|
var total int64
|
||||||
err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error {
|
err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error {
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
@ -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))
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
@ -7,30 +7,31 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Subscribe struct {
|
type Subscribe struct {
|
||||||
Id int64 `gorm:"primaryKey"`
|
Id int64 `gorm:"primaryKey"`
|
||||||
Name string `gorm:"type:varchar(255);not null;default:'';comment:Subscribe Name"`
|
Name string `gorm:"type:varchar(255);not null;default:'';comment:Subscribe Name"`
|
||||||
Language string `gorm:"type:varchar(255);not null;default:'';comment:Language"`
|
Language string `gorm:"type:varchar(255);not null;default:'';comment:Language"`
|
||||||
Description string `gorm:"type:text;comment:Subscribe Description"`
|
Description string `gorm:"type:text;comment:Subscribe Description"`
|
||||||
UnitPrice int64 `gorm:"type:int;not null;default:0;comment:Unit Price"`
|
UnitPrice int64 `gorm:"type:int;not null;default:0;comment:Unit Price"`
|
||||||
UnitTime string `gorm:"type:varchar(255);not null;default:'';comment:Unit Time"`
|
UnitTime string `gorm:"type:varchar(255);not null;default:'';comment:Unit Time"`
|
||||||
Discount string `gorm:"type:text;comment:Discount"`
|
Discount string `gorm:"type:text;comment:Discount"`
|
||||||
Replacement int64 `gorm:"type:int;not null;default:0;comment:Replacement"`
|
Replacement int64 `gorm:"type:int;not null;default:0;comment:Replacement"`
|
||||||
Inventory int64 `gorm:"type:int;not null;default:0;comment:Inventory"`
|
Inventory int64 `gorm:"type:int;not null;default:-1;comment:Inventory"`
|
||||||
Traffic int64 `gorm:"type:int;not null;default:0;comment:Traffic"`
|
Traffic int64 `gorm:"type:int;not null;default:0;comment:Traffic"`
|
||||||
SpeedLimit int64 `gorm:"type:int;not null;default:0;comment:Speed Limit"`
|
SpeedLimit int64 `gorm:"type:int;not null;default:0;comment:Speed Limit"`
|
||||||
DeviceLimit int64 `gorm:"type:int;not null;default:0;comment:Device Limit"`
|
DeviceLimit int64 `gorm:"type:int;not null;default:0;comment:Device Limit"`
|
||||||
Quota int64 `gorm:"type:int;not null;default:0;comment:Quota"`
|
Quota int64 `gorm:"type:int;not null;default:0;comment:Quota"`
|
||||||
Nodes string `gorm:"type:varchar(255);comment:Node Ids"`
|
Nodes string `gorm:"type:varchar(255);comment:Node Ids"`
|
||||||
NodeTags string `gorm:"type:varchar(255);comment:Node Tags"`
|
NodeTags string `gorm:"type:varchar(255);comment:Node Tags"`
|
||||||
Show *bool `gorm:"type:tinyint(1);not null;default:0;comment:Show portal page"`
|
Show *bool `gorm:"type:tinyint(1);not null;default:0;comment:Show portal page"`
|
||||||
Sell *bool `gorm:"type:tinyint(1);not null;default:0;comment:Sell"`
|
Sell *bool `gorm:"type:tinyint(1);not null;default:0;comment:Sell"`
|
||||||
Sort int64 `gorm:"type:int;not null;default:0;comment:Sort"`
|
Sort int64 `gorm:"type:int;not null;default:0;comment:Sort"`
|
||||||
DeductionRatio int64 `gorm:"type:int;default:0;comment:Deduction Ratio"`
|
DeductionRatio int64 `gorm:"type:int;default:0;comment:Deduction Ratio"`
|
||||||
AllowDeduction *bool `gorm:"type:tinyint(1);default:1;comment:Allow deduction"`
|
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"`
|
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"`
|
RenewalReset *bool `gorm:"type:tinyint(1);default:0;comment:Renew Reset"`
|
||||||
CreatedAt time.Time `gorm:"<-:create;comment:Create Time"`
|
ShowOriginalPrice bool `gorm:"type:tinyint(1);not null;default:1;comment:Show Original Price"`
|
||||||
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
CreatedAt time.Time `gorm:"<-:create;comment:Create Time"`
|
||||||
|
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Subscribe) TableName() string {
|
func (*Subscribe) TableName() string {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/pkg/cache"
|
"github.com/perfect-panel/server/pkg/cache"
|
||||||
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
"gorm.io/gorm"
|
"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 {
|
if err := conn.Model(&AuthMethods{}).Where("`auth_type` = 'email' AND `auth_identifier` = ?", email).First(&data).Error; err != nil {
|
||||||
return err
|
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
|
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)
|
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
|
||||||
var resp User
|
var resp User
|
||||||
err := m.QueryCtx(ctx, &resp, userIdKey, func(conn *gorm.DB, v interface{}) error {
|
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
|
return &resp, err
|
||||||
}
|
}
|
||||||
@ -119,10 +120,11 @@ func (m *defaultUserModel) Delete(ctx context.Context, id int64, tx ...*gorm.DB)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用批量相关缓存清理,包含所有相关数据的缓存
|
// Use batch related cache cleaning, including a cache of all relevant data
|
||||||
defer func() {
|
defer func() {
|
||||||
if clearErr := m.BatchClearRelatedCache(ctx, data); clearErr != nil {
|
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 {
|
if len(tx) > 0 {
|
||||||
db = 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 {
|
if err := db.Model(&User{}).Where("`id` = ?", id).Delete(&User{}).Error; err != nil {
|
||||||
return err
|
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
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,6 +62,7 @@ type UserFilterParams struct {
|
|||||||
SubscribeId *int64
|
SubscribeId *int64
|
||||||
UserSubscribeId *int64
|
UserSubscribeId *int64
|
||||||
Order string // Order by id, e.g., "desc"
|
Order string // Order by id, e.g., "desc"
|
||||||
|
Unscoped bool // Whether to include soft-deleted records
|
||||||
}
|
}
|
||||||
|
|
||||||
type customUserLogicModel interface {
|
type customUserLogicModel interface {
|
||||||
@ -148,6 +149,9 @@ func (m *customUserModel) QueryPageList(ctx context.Context, page, size int, fil
|
|||||||
if filter.Order != "" {
|
if filter.Order != "" {
|
||||||
conn = conn.Order(fmt.Sprintf("user.id %s", 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
|
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
|
||||||
})
|
})
|
||||||
|
|||||||
@ -2,32 +2,35 @@ package user
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Id int64 `gorm:"primaryKey"`
|
Id int64 `gorm:"primaryKey"`
|
||||||
Password string `gorm:"type:varchar(100);not null;comment:User Password"`
|
Password string `gorm:"type:varchar(100);not null;comment:User Password"`
|
||||||
Algo string `gorm:"type:varchar(20);default:'default';comment:Encryption Algorithm"`
|
Algo string `gorm:"type:varchar(20);default:'default';comment:Encryption Algorithm"`
|
||||||
Salt string `gorm:"type:varchar(20);default:null;comment:Password Salt"`
|
Salt string `gorm:"type:varchar(20);default:null;comment:Password Salt"`
|
||||||
Avatar string `gorm:"type:MEDIUMTEXT;comment:User Avatar"`
|
Avatar string `gorm:"type:MEDIUMTEXT;comment:User Avatar"`
|
||||||
Balance int64 `gorm:"default:0;comment:User Balance"` // User Balance Amount
|
Balance int64 `gorm:"default:0;comment:User Balance"` // User Balance Amount
|
||||||
ReferCode string `gorm:"type:varchar(20);default:'';comment:Referral Code"`
|
ReferCode string `gorm:"type:varchar(20);default:'';comment:Referral Code"`
|
||||||
RefererId int64 `gorm:"index:idx_referer;comment:Referrer ID"`
|
RefererId int64 `gorm:"index:idx_referer;comment:Referrer ID"`
|
||||||
Commission int64 `gorm:"default:0;comment:Commission"` // Commission Amount
|
Commission int64 `gorm:"default:0;comment:Commission"` // Commission Amount
|
||||||
ReferralPercentage uint8 `gorm:"default:0;comment:Referral"` // Referral Percentage
|
ReferralPercentage uint8 `gorm:"default:0;comment:Referral"` // Referral Percentage
|
||||||
OnlyFirstPurchase *bool `gorm:"default:true;not null;comment:Only First Purchase"` // Only First Purchase Referral
|
OnlyFirstPurchase *bool `gorm:"default:true;not null;comment:Only First Purchase"` // Only First Purchase Referral
|
||||||
GiftAmount int64 `gorm:"default:0;comment:User Gift Amount"`
|
GiftAmount int64 `gorm:"default:0;comment:User Gift Amount"`
|
||||||
Enable *bool `gorm:"default:true;not null;comment:Is Account Enabled"`
|
Enable *bool `gorm:"default:true;not null;comment:Is Account Enabled"`
|
||||||
IsAdmin *bool `gorm:"default:false;not null;comment:Is Admin"`
|
IsAdmin *bool `gorm:"default:false;not null;comment:Is Admin"`
|
||||||
EnableBalanceNotify *bool `gorm:"default:false;not null;comment:Enable Balance Change Notifications"`
|
EnableBalanceNotify *bool `gorm:"default:false;not null;comment:Enable Balance Change Notifications"`
|
||||||
EnableLoginNotify *bool `gorm:"default:false;not null;comment:Enable Login Notifications"`
|
EnableLoginNotify *bool `gorm:"default:false;not null;comment:Enable Login Notifications"`
|
||||||
EnableSubscribeNotify *bool `gorm:"default:false;not null;comment:Enable Subscription Notifications"`
|
EnableSubscribeNotify *bool `gorm:"default:false;not null;comment:Enable Subscription Notifications"`
|
||||||
EnableTradeNotify *bool `gorm:"default:false;not null;comment:Enable Trade Notifications"`
|
EnableTradeNotify *bool `gorm:"default:false;not null;comment:Enable Trade Notifications"`
|
||||||
AuthMethods []AuthMethods `gorm:"foreignKey:UserId;references:Id"`
|
AuthMethods []AuthMethods `gorm:"foreignKey:UserId;references:Id"`
|
||||||
UserDevices []Device `gorm:"foreignKey:UserId;references:Id"`
|
UserDevices []Device `gorm:"foreignKey:UserId;references:Id"`
|
||||||
Rules string `gorm:"type:TEXT;comment:User Rules"`
|
Rules string `gorm:"type:TEXT;comment:User Rules"`
|
||||||
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
|
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
|
||||||
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index;comment:Deletion Time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*User) TableName() string {
|
func (*User) TableName() string {
|
||||||
@ -49,7 +52,6 @@ type Subscribe struct {
|
|||||||
Token string `gorm:"index:idx_token;unique;type:varchar(255);default:'';comment:Token"`
|
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"`
|
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"`
|
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"`
|
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
|
||||||
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,7 +97,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||||||
Redis: rds,
|
Redis: rds,
|
||||||
Config: c,
|
Config: c,
|
||||||
Queue: NewAsynqClient(c),
|
Queue: NewAsynqClient(c),
|
||||||
ExchangeRate: 1.0,
|
ExchangeRate: 0,
|
||||||
GeoIP: geoIP,
|
GeoIP: geoIP,
|
||||||
//NodeCache: cache.NewNodeCacheClient(rds),
|
//NodeCache: cache.NewNodeCacheClient(rds),
|
||||||
AuthLimiter: authLimiter,
|
AuthLimiter: authLimiter,
|
||||||
|
|||||||
@ -2,9 +2,11 @@ package types
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
SubscribeRequest struct {
|
SubscribeRequest struct {
|
||||||
Flag string
|
Flag string
|
||||||
Token string
|
Token string
|
||||||
UA string
|
Type string
|
||||||
|
UA string
|
||||||
|
Params map[string]string
|
||||||
}
|
}
|
||||||
SubscribeResponse struct {
|
SubscribeResponse struct {
|
||||||
Config []byte
|
Config []byte
|
||||||
|
|||||||
@ -394,26 +394,27 @@ type CreateSubscribeGroupRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CreateSubscribeRequest struct {
|
type CreateSubscribeRequest struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
UnitPrice int64 `json:"unit_price"`
|
UnitPrice int64 `json:"unit_price"`
|
||||||
UnitTime string `json:"unit_time"`
|
UnitTime string `json:"unit_time"`
|
||||||
Discount []SubscribeDiscount `json:"discount"`
|
Discount []SubscribeDiscount `json:"discount"`
|
||||||
Replacement int64 `json:"replacement"`
|
Replacement int64 `json:"replacement"`
|
||||||
Inventory int64 `json:"inventory"`
|
Inventory int64 `json:"inventory"`
|
||||||
Traffic int64 `json:"traffic"`
|
Traffic int64 `json:"traffic"`
|
||||||
SpeedLimit int64 `json:"speed_limit"`
|
SpeedLimit int64 `json:"speed_limit"`
|
||||||
DeviceLimit int64 `json:"device_limit"`
|
DeviceLimit int64 `json:"device_limit"`
|
||||||
Quota int64 `json:"quota"`
|
Quota int64 `json:"quota"`
|
||||||
Nodes []int64 `json:"nodes"`
|
Nodes []int64 `json:"nodes"`
|
||||||
NodeTags []string `json:"node_tags"`
|
NodeTags []string `json:"node_tags"`
|
||||||
Show *bool `json:"show"`
|
Show *bool `json:"show"`
|
||||||
Sell *bool `json:"sell"`
|
Sell *bool `json:"sell"`
|
||||||
DeductionRatio int64 `json:"deduction_ratio"`
|
DeductionRatio int64 `json:"deduction_ratio"`
|
||||||
AllowDeduction *bool `json:"allow_deduction"`
|
AllowDeduction *bool `json:"allow_deduction"`
|
||||||
ResetCycle int64 `json:"reset_cycle"`
|
ResetCycle int64 `json:"reset_cycle"`
|
||||||
RenewalReset *bool `json:"renewal_reset"`
|
RenewalReset *bool `json:"renewal_reset"`
|
||||||
|
ShowOriginalPrice bool `json:"show_original_price"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateTicketFollowRequest struct {
|
type CreateTicketFollowRequest struct {
|
||||||
@ -1054,6 +1055,7 @@ type GetUserListRequest struct {
|
|||||||
Size int `form:"size"`
|
Size int `form:"size"`
|
||||||
Search string `form:"search,omitempty"`
|
Search string `form:"search,omitempty"`
|
||||||
UserId *int64 `form:"user_id,omitempty"`
|
UserId *int64 `form:"user_id,omitempty"`
|
||||||
|
Unscoped bool `form:"unscoped,omitempty"`
|
||||||
SubscribeId *int64 `form:"subscribe_id,omitempty"`
|
SubscribeId *int64 `form:"subscribe_id,omitempty"`
|
||||||
UserSubscribeId *int64 `form:"user_subscribe_id,omitempty"`
|
UserSubscribeId *int64 `form:"user_subscribe_id,omitempty"`
|
||||||
}
|
}
|
||||||
@ -1568,7 +1570,7 @@ type PurchaseOrderResponse struct {
|
|||||||
|
|
||||||
type QueryAnnouncementRequest struct {
|
type QueryAnnouncementRequest struct {
|
||||||
Page int `form:"page"`
|
Page int `form:"page"`
|
||||||
Size int `form:"size"`
|
Size int `form:"size,default=15"`
|
||||||
Pinned *bool `form:"pinned"`
|
Pinned *bool `form:"pinned"`
|
||||||
Popup *bool `form:"popup"`
|
Popup *bool `form:"popup"`
|
||||||
}
|
}
|
||||||
@ -1856,6 +1858,10 @@ type ResetUserSubscribeTokenRequest struct {
|
|||||||
UserSubscribeId int64 `json:"user_subscribe_id"`
|
UserSubscribeId int64 `json:"user_subscribe_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResetUserSubscribeTrafficRequest struct {
|
||||||
|
UserSubscribeId int64 `json:"user_subscribe_id"`
|
||||||
|
}
|
||||||
|
|
||||||
type RevenueStatisticsResponse struct {
|
type RevenueStatisticsResponse struct {
|
||||||
Today OrdersStatistics `json:"today"`
|
Today OrdersStatistics `json:"today"`
|
||||||
Monthly OrdersStatistics `json:"monthly"`
|
Monthly OrdersStatistics `json:"monthly"`
|
||||||
@ -2056,30 +2062,31 @@ type StripePayment struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Subscribe struct {
|
type Subscribe struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
UnitPrice int64 `json:"unit_price"`
|
UnitPrice int64 `json:"unit_price"`
|
||||||
UnitTime string `json:"unit_time"`
|
UnitTime string `json:"unit_time"`
|
||||||
Discount []SubscribeDiscount `json:"discount"`
|
Discount []SubscribeDiscount `json:"discount"`
|
||||||
Replacement int64 `json:"replacement"`
|
Replacement int64 `json:"replacement"`
|
||||||
Inventory int64 `json:"inventory"`
|
Inventory int64 `json:"inventory"`
|
||||||
Traffic int64 `json:"traffic"`
|
Traffic int64 `json:"traffic"`
|
||||||
SpeedLimit int64 `json:"speed_limit"`
|
SpeedLimit int64 `json:"speed_limit"`
|
||||||
DeviceLimit int64 `json:"device_limit"`
|
DeviceLimit int64 `json:"device_limit"`
|
||||||
Quota int64 `json:"quota"`
|
Quota int64 `json:"quota"`
|
||||||
Nodes []int64 `json:"nodes"`
|
Nodes []int64 `json:"nodes"`
|
||||||
NodeTags []string `json:"node_tags"`
|
NodeTags []string `json:"node_tags"`
|
||||||
Show bool `json:"show"`
|
Show bool `json:"show"`
|
||||||
Sell bool `json:"sell"`
|
Sell bool `json:"sell"`
|
||||||
Sort int64 `json:"sort"`
|
Sort int64 `json:"sort"`
|
||||||
DeductionRatio int64 `json:"deduction_ratio"`
|
DeductionRatio int64 `json:"deduction_ratio"`
|
||||||
AllowDeduction bool `json:"allow_deduction"`
|
AllowDeduction bool `json:"allow_deduction"`
|
||||||
ResetCycle int64 `json:"reset_cycle"`
|
ResetCycle int64 `json:"reset_cycle"`
|
||||||
RenewalReset bool `json:"renewal_reset"`
|
RenewalReset bool `json:"renewal_reset"`
|
||||||
CreatedAt int64 `json:"created_at"`
|
ShowOriginalPrice bool `json:"show_original_price"`
|
||||||
UpdatedAt int64 `json:"updated_at"`
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubscribeApplication struct {
|
type SubscribeApplication struct {
|
||||||
@ -2239,6 +2246,10 @@ type ToggleNodeStatusRequest struct {
|
|||||||
Enable *bool `json:"enable"`
|
Enable *bool `json:"enable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ToggleUserSubscribeStatusRequest struct {
|
||||||
|
UserSubscribeId int64 `json:"user_subscribe_id"`
|
||||||
|
}
|
||||||
|
|
||||||
type TosConfig struct {
|
type TosConfig struct {
|
||||||
TosContent string `json:"tos_content"`
|
TosContent string `json:"tos_content"`
|
||||||
}
|
}
|
||||||
@ -2435,28 +2446,29 @@ type UpdateSubscribeGroupRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UpdateSubscribeRequest struct {
|
type UpdateSubscribeRequest struct {
|
||||||
Id int64 `json:"id" validate:"required"`
|
Id int64 `json:"id" validate:"required"`
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
UnitPrice int64 `json:"unit_price"`
|
UnitPrice int64 `json:"unit_price"`
|
||||||
UnitTime string `json:"unit_time"`
|
UnitTime string `json:"unit_time"`
|
||||||
Discount []SubscribeDiscount `json:"discount"`
|
Discount []SubscribeDiscount `json:"discount"`
|
||||||
Replacement int64 `json:"replacement"`
|
Replacement int64 `json:"replacement"`
|
||||||
Inventory int64 `json:"inventory"`
|
Inventory int64 `json:"inventory"`
|
||||||
Traffic int64 `json:"traffic"`
|
Traffic int64 `json:"traffic"`
|
||||||
SpeedLimit int64 `json:"speed_limit"`
|
SpeedLimit int64 `json:"speed_limit"`
|
||||||
DeviceLimit int64 `json:"device_limit"`
|
DeviceLimit int64 `json:"device_limit"`
|
||||||
Quota int64 `json:"quota"`
|
Quota int64 `json:"quota"`
|
||||||
Nodes []int64 `json:"nodes"`
|
Nodes []int64 `json:"nodes"`
|
||||||
NodeTags []string `json:"node_tags"`
|
NodeTags []string `json:"node_tags"`
|
||||||
Show *bool `json:"show"`
|
Show *bool `json:"show"`
|
||||||
Sell *bool `json:"sell"`
|
Sell *bool `json:"sell"`
|
||||||
Sort int64 `json:"sort"`
|
Sort int64 `json:"sort"`
|
||||||
DeductionRatio int64 `json:"deduction_ratio"`
|
DeductionRatio int64 `json:"deduction_ratio"`
|
||||||
AllowDeduction *bool `json:"allow_deduction"`
|
AllowDeduction *bool `json:"allow_deduction"`
|
||||||
ResetCycle int64 `json:"reset_cycle"`
|
ResetCycle int64 `json:"reset_cycle"`
|
||||||
RenewalReset *bool `json:"renewal_reset"`
|
RenewalReset *bool `json:"renewal_reset"`
|
||||||
|
ShowOriginalPrice bool `json:"show_original_price"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateTicketStatusRequest struct {
|
type UpdateTicketStatusRequest struct {
|
||||||
@ -2551,7 +2563,6 @@ type User struct {
|
|||||||
CreatedAt int64 `json:"created_at"`
|
CreatedAt int64 `json:"created_at"`
|
||||||
UpdatedAt int64 `json:"updated_at"`
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
DeletedAt int64 `json:"deleted_at,omitempty"`
|
DeletedAt int64 `json:"deleted_at,omitempty"`
|
||||||
IsDel bool `json:"is_del,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserAffiliate struct {
|
type UserAffiliate struct {
|
||||||
|
|||||||
@ -32,15 +32,15 @@ func (l *GormLogger) LogMode(logger.LogLevel) logger.Interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *GormLogger) Info(ctx context.Context, str string, args ...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{}) {
|
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{}) {
|
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) {
|
func (l *GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
|
||||||
|
|||||||
@ -71,6 +71,7 @@ const (
|
|||||||
SubscribeIsUsedError uint32 = 60004
|
SubscribeIsUsedError uint32 = 60004
|
||||||
SingleSubscribeModeExceedsLimit uint32 = 60005
|
SingleSubscribeModeExceedsLimit uint32 = 60005
|
||||||
SubscribeQuotaLimit uint32 = 60006
|
SubscribeQuotaLimit uint32 = 60006
|
||||||
|
SubscribeOutOfStock uint32 = 60007
|
||||||
)
|
)
|
||||||
|
|
||||||
// Auth error
|
// Auth error
|
||||||
|
|||||||
@ -56,6 +56,7 @@ func init() {
|
|||||||
SubscribeIsUsedError: "Subscribe is used",
|
SubscribeIsUsedError: "Subscribe is used",
|
||||||
SingleSubscribeModeExceedsLimit: "Single subscribe mode exceeds limit",
|
SingleSubscribeModeExceedsLimit: "Single subscribe mode exceeds limit",
|
||||||
SubscribeQuotaLimit: "Subscribe quota limit",
|
SubscribeQuotaLimit: "Subscribe quota limit",
|
||||||
|
SubscribeOutOfStock: "Subscribe out of stock",
|
||||||
|
|
||||||
// auth error
|
// auth error
|
||||||
VerifyCodeError: "Verify code error",
|
VerifyCodeError: "Verify code error",
|
||||||
|
|||||||
@ -79,6 +79,7 @@ func (l *TrafficStatisticsLogic) ProcessTask(ctx context.Context, task *asynq.Ta
|
|||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
realTimeMultiplier := l.svc.NodeMultiplierManager.GetMultiplier(now)
|
realTimeMultiplier := l.svc.NodeMultiplierManager.GetMultiplier(now)
|
||||||
|
logger.Debugf("[TrafficStatisticsLogic] Current time traffic multiplier: %.2f", realTimeMultiplier)
|
||||||
for _, log := range payload.Logs {
|
for _, log := range payload.Logs {
|
||||||
// query user Subscribe Info
|
// query user Subscribe Info
|
||||||
sub, err := l.svc.UserModel.FindOneSubscribe(ctx, log.SID)
|
sub, err := l.svc.UserModel.FindOneSubscribe(ctx, log.SID)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user