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:
@@ -31,6 +31,7 @@ func (l *CreateNodeLogic) CreateNode(req *types.CreateNodeRequest) error {
|
||||
data := node.Node{
|
||||
Name: req.Name,
|
||||
Tags: tool.StringSliceToString(req.Tags),
|
||||
Enabled: req.Enabled,
|
||||
Port: req.Port,
|
||||
Address: req.Address,
|
||||
ServerId: req.ServerId,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
sub := &subscribe.Subscribe{
|
||||
Id: 0,
|
||||
Name: req.Name,
|
||||
Language: req.Language,
|
||||
Description: req.Description,
|
||||
UnitPrice: req.UnitPrice,
|
||||
UnitTime: req.UnitTime,
|
||||
Discount: discount,
|
||||
Replacement: req.Replacement,
|
||||
Inventory: req.Inventory,
|
||||
Traffic: req.Traffic,
|
||||
SpeedLimit: req.SpeedLimit,
|
||||
DeviceLimit: req.DeviceLimit,
|
||||
Quota: req.Quota,
|
||||
Nodes: tool.Int64SliceToString(req.Nodes),
|
||||
NodeTags: tool.StringSliceToString(req.NodeTags),
|
||||
Show: req.Show,
|
||||
Sell: req.Sell,
|
||||
Sort: 0,
|
||||
DeductionRatio: req.DeductionRatio,
|
||||
AllowDeduction: req.AllowDeduction,
|
||||
ResetCycle: req.ResetCycle,
|
||||
RenewalReset: req.RenewalReset,
|
||||
Id: 0,
|
||||
Name: req.Name,
|
||||
Language: req.Language,
|
||||
Description: req.Description,
|
||||
UnitPrice: req.UnitPrice,
|
||||
UnitTime: req.UnitTime,
|
||||
Discount: discount,
|
||||
Replacement: req.Replacement,
|
||||
Inventory: req.Inventory,
|
||||
Traffic: req.Traffic,
|
||||
SpeedLimit: req.SpeedLimit,
|
||||
DeviceLimit: req.DeviceLimit,
|
||||
Quota: req.Quota,
|
||||
Nodes: tool.Int64SliceToString(req.Nodes),
|
||||
NodeTags: tool.StringSliceToString(req.NodeTags),
|
||||
Show: req.Show,
|
||||
Sell: req.Sell,
|
||||
Sort: 0,
|
||||
DeductionRatio: req.DeductionRatio,
|
||||
AllowDeduction: req.AllowDeduction,
|
||||
ResetCycle: req.ResetCycle,
|
||||
RenewalReset: req.RenewalReset,
|
||||
ShowOriginalPrice: req.ShowOriginalPrice,
|
||||
}
|
||||
err := l.svcCtx.SubscribeModel.Insert(l.ctx, sub)
|
||||
if err != nil {
|
||||
|
||||
@@ -43,28 +43,29 @@ func (l *UpdateSubscribeLogic) UpdateSubscribe(req *types.UpdateSubscribeRequest
|
||||
discount = string(val)
|
||||
}
|
||||
sub := &subscribe.Subscribe{
|
||||
Id: req.Id,
|
||||
Name: req.Name,
|
||||
Language: req.Language,
|
||||
Description: req.Description,
|
||||
UnitPrice: req.UnitPrice,
|
||||
UnitTime: req.UnitTime,
|
||||
Discount: discount,
|
||||
Replacement: req.Replacement,
|
||||
Inventory: req.Inventory,
|
||||
Traffic: req.Traffic,
|
||||
SpeedLimit: req.SpeedLimit,
|
||||
DeviceLimit: req.DeviceLimit,
|
||||
Quota: req.Quota,
|
||||
Nodes: tool.Int64SliceToString(req.Nodes),
|
||||
NodeTags: tool.StringSliceToString(req.NodeTags),
|
||||
Show: req.Show,
|
||||
Sell: req.Sell,
|
||||
Sort: req.Sort,
|
||||
DeductionRatio: req.DeductionRatio,
|
||||
AllowDeduction: req.AllowDeduction,
|
||||
ResetCycle: req.ResetCycle,
|
||||
RenewalReset: req.RenewalReset,
|
||||
Id: req.Id,
|
||||
Name: req.Name,
|
||||
Language: req.Language,
|
||||
Description: req.Description,
|
||||
UnitPrice: req.UnitPrice,
|
||||
UnitTime: req.UnitTime,
|
||||
Discount: discount,
|
||||
Replacement: req.Replacement,
|
||||
Inventory: req.Inventory,
|
||||
Traffic: req.Traffic,
|
||||
SpeedLimit: req.SpeedLimit,
|
||||
DeviceLimit: req.DeviceLimit,
|
||||
Quota: req.Quota,
|
||||
Nodes: tool.Int64SliceToString(req.Nodes),
|
||||
NodeTags: tool.StringSliceToString(req.NodeTags),
|
||||
Show: req.Show,
|
||||
Sell: req.Sell,
|
||||
Sort: req.Sort,
|
||||
DeductionRatio: req.DeductionRatio,
|
||||
AllowDeduction: req.AllowDeduction,
|
||||
ResetCycle: req.ResetCycle,
|
||||
RenewalReset: req.RenewalReset,
|
||||
ShowOriginalPrice: req.ShowOriginalPrice,
|
||||
}
|
||||
err = l.svcCtx.SubscribeModel.Update(l.ctx, sub)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,6 +3,7 @@ package system
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/perfect-panel/server/initialize"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
@@ -32,9 +33,12 @@ func (l *SetNodeMultiplierLogic) SetNodeMultiplier(req *types.SetNodeMultiplierR
|
||||
l.Logger.Error("Marshal Node Multiplier Config Error: ", logger.Field("error", err.Error()))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Marshal Node Multiplier Config Error: %s", err.Error())
|
||||
}
|
||||
if err := l.svcCtx.SystemModel.UpdateNodeMultiplierConfig(l.ctx, string(data)); err != nil {
|
||||
if err = l.svcCtx.SystemModel.UpdateNodeMultiplierConfig(l.ctx, string(data)); err != nil {
|
||||
l.Logger.Error("Update Node Multiplier Config Error: ", logger.Field("error", err.Error()))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Update Node Multiplier Config Error: %s", err.Error())
|
||||
}
|
||||
// update Node Multiplier
|
||||
initialize.Node(l.svcCtx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ func (l *GetUserListLogic) GetUserList(req *types.GetUserListRequest) (*types.Ge
|
||||
list, total, err := l.svcCtx.UserModel.QueryPageList(l.ctx, req.Page, req.Size, &user.UserFilterParams{
|
||||
UserId: req.UserId,
|
||||
Search: req.Search,
|
||||
Unscoped: req.Unscoped,
|
||||
SubscribeId: req.SubscribeId,
|
||||
UserSubscribeId: req.UserSubscribeId,
|
||||
Order: "DESC",
|
||||
|
||||
@@ -41,6 +41,7 @@ func (l *GetUserSubscribeLogic) GetUserSubscribe(req *types.GetUserSubscribeList
|
||||
for _, item := range data {
|
||||
var sub types.UserSubscribe
|
||||
tool.DeepCopy(&sub, item)
|
||||
sub.Short, _ = tool.FixedUniqueString(item.Token, 8, "")
|
||||
resp.List = append(resp.List, sub)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
@@ -206,6 +207,86 @@ func (l *BindDeviceLogic) rebindDeviceToNewUser(deviceInfo *user.Device, ip, use
|
||||
|
||||
//如果没有其他认证方式,禁用旧用户账号
|
||||
if count < 1 {
|
||||
//检查设备下是否有套餐,有套餐。就检查即将绑定过去的所有账户是否有套餐,如果有,那么检查两个套餐是否一致。如果一致就将即将删除的用户套餐,时间叠加到我绑定过去的用户套餐上面(如果套餐已过期就忽略)。新绑定设备的账户上套餐不一致或者不存在直接将套餐换绑即可
|
||||
var oldUserSubscribes []user.Subscribe
|
||||
err = tx.Where("user_id = ? AND status IN ?", oldUserId, []int64{0, 1}).Find(&oldUserSubscribes).Error
|
||||
if err != nil {
|
||||
l.Errorw("failed to query old user subscribes",
|
||||
logger.Field("old_user_id", oldUserId),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query old user subscribes failed: %v", err)
|
||||
}
|
||||
|
||||
if len(oldUserSubscribes) > 0 {
|
||||
l.Infow("processing old user subscribes",
|
||||
logger.Field("old_user_id", oldUserId),
|
||||
logger.Field("new_user_id", newUserId),
|
||||
logger.Field("subscribe_count", len(oldUserSubscribes)),
|
||||
)
|
||||
|
||||
for _, oldSub := range oldUserSubscribes {
|
||||
// 检查新用户是否有相同套餐ID的订阅
|
||||
var newUserSub user.Subscribe
|
||||
err = tx.Where("user_id = ? AND subscribe_id = ? AND status IN ?", newUserId, oldSub.SubscribeId, []int64{0, 1}).First(&newUserSub).Error
|
||||
|
||||
if err != nil {
|
||||
// 新用户没有该套餐,直接换绑
|
||||
oldSub.UserId = newUserId
|
||||
if err := tx.Save(&oldSub).Error; err != nil {
|
||||
l.Errorw("failed to rebind subscribe to new user",
|
||||
logger.Field("subscribe_id", oldSub.Id),
|
||||
logger.Field("old_user_id", oldUserId),
|
||||
logger.Field("new_user_id", newUserId),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "rebind subscribe failed: %v", err)
|
||||
}
|
||||
l.Infow("rebind subscribe to new user",
|
||||
logger.Field("subscribe_id", oldSub.Id),
|
||||
logger.Field("new_user_id", newUserId),
|
||||
)
|
||||
} else {
|
||||
// 新用户已有该套餐,检查旧套餐是否过期
|
||||
now := time.Now()
|
||||
if oldSub.ExpireTime.After(now) {
|
||||
// 旧套餐未过期,叠加剩余时间
|
||||
remainingDuration := oldSub.ExpireTime.Sub(now)
|
||||
if newUserSub.ExpireTime.After(now) {
|
||||
// 新套餐未过期,叠加时间
|
||||
newUserSub.ExpireTime = newUserSub.ExpireTime.Add(remainingDuration)
|
||||
} else {
|
||||
newUserSub.ExpireTime = time.Now().Add(remainingDuration)
|
||||
}
|
||||
if err := tx.Save(&newUserSub).Error; err != nil {
|
||||
l.Errorw("failed to update subscribe expire time",
|
||||
logger.Field("subscribe_id", newUserSub.Id),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update subscribe expire time failed: %v", err)
|
||||
}
|
||||
l.Infow("merged subscribe time",
|
||||
logger.Field("subscribe_id", newUserSub.Id),
|
||||
logger.Field("new_expire_time", newUserSub.ExpireTime),
|
||||
)
|
||||
} else {
|
||||
l.Infow("old subscribe expired, skip merge",
|
||||
logger.Field("subscribe_id", oldSub.Id),
|
||||
logger.Field("expire_time", oldSub.ExpireTime),
|
||||
)
|
||||
}
|
||||
// 删除旧用户的套餐
|
||||
if err := tx.Delete(&oldSub).Error; err != nil {
|
||||
l.Errorw("failed to delete old subscribe",
|
||||
logger.Field("subscribe_id", oldSub.Id),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete old subscribe failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Model(&user.User{}).Where("id = ?", oldUserId).Delete(&user.User{}).Error; err != nil {
|
||||
l.Errorw("failed to disable old user",
|
||||
logger.Field("old_user_id", oldUserId),
|
||||
|
||||
@@ -68,6 +68,10 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log
|
||||
|
||||
userInfo, err = l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email)
|
||||
|
||||
if userInfo.DeletedAt.Valid {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserNotExist), "user email deleted: %v", req.Email)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if errors.As(err, &gorm.ErrRecordNotFound) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserNotExist), "user email not exist: %v", req.Email)
|
||||
|
||||
@@ -79,12 +79,14 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *
|
||||
}
|
||||
}
|
||||
// Check if the user exists
|
||||
_, err = l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email)
|
||||
u, err := l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
l.Errorw("FindOneByEmail Error", logger.Field("error", err))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query user info failed: %v", err.Error())
|
||||
} else if err == nil {
|
||||
} else if err == nil && !u.DeletedAt.Valid {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserExist), "user email exist: %v", req.Email)
|
||||
} else if err == nil && u.DeletedAt.Valid {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserDisabled), "user email deleted: %v", req.Email)
|
||||
}
|
||||
|
||||
if !registerIpLimit(l.svcCtx, l.ctx, req.IP, "email", req.Email) {
|
||||
@@ -154,9 +156,8 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *
|
||||
time.Now().Unix(),
|
||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||
jwt.WithOption("UserId", userInfo.Id),
|
||||
jwt.WithOption("identifier", req.Identifier),
|
||||
jwt.WithOption("SessionId", sessionId),
|
||||
jwt.WithOption("CtxLoginType", req.LoginType),
|
||||
jwt.WithOption("LoginType", req.LoginType),
|
||||
)
|
||||
if err != nil {
|
||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
||||
@@ -237,12 +238,5 @@ func (l *UserRegisterLogic) activeTrial(uid int64) error {
|
||||
UUID: uuidx.NewUUID().String(),
|
||||
Status: 1,
|
||||
}
|
||||
err = l.svcCtx.UserModel.InsertSubscribe(l.ctx, userSub)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if clearErr := l.svcCtx.NodeModel.ClearServerAllCache(l.ctx); clearErr != nil {
|
||||
l.Errorf("ClearServerAllCache error: %v", clearErr.Error())
|
||||
}
|
||||
return err
|
||||
return l.svcCtx.UserModel.InsertSubscribe(l.ctx, userSub)
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ func (l *GetGlobalConfigLogic) GetGlobalConfig() (resp *types.GetGlobalConfigRes
|
||||
tool.SystemConfigSliceReflectToStruct(currencyCfg, &resp.Currency)
|
||||
tool.SystemConfigSliceReflectToStruct(verifyCodeCfg, &resp.VerifyCode)
|
||||
|
||||
resp.Subscribe.SubscribePath = "/sub" + l.svcCtx.Config.Subscribe.SubscribePath
|
||||
|
||||
resp.Verify = types.VeifyConfig{
|
||||
TurnstileSiteKey: l.svcCtx.Config.Verify.TurnstileSiteKey,
|
||||
EnableLoginVerify: l.svcCtx.Config.Verify.LoginVerify,
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
"github.com/perfect-panel/server/internal/model/server"
|
||||
"github.com/perfect-panel/server/internal/model/node"
|
||||
"github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
@@ -57,13 +57,13 @@ func (l *GetStatLogic) GetStat() (resp *types.GetStatResponse, err error) {
|
||||
u = 1
|
||||
}
|
||||
var n int64
|
||||
err = l.svcCtx.DB.Model(&server.Server{}).Where("enable = 1").Count(&n).Error
|
||||
err = l.svcCtx.DB.Model(&node.Node{}).Where("enabled = 1").Count(&n).Error
|
||||
if err != nil {
|
||||
l.Logger.Error("[GetStatLogic] get server count failed: ", logger.Field("error", err.Error()))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get server count failed: %v", err.Error())
|
||||
}
|
||||
var nodeaddr []string
|
||||
err = l.svcCtx.DB.Model(&server.Server{}).Where("enable = 1").Pluck("server_addr", &nodeaddr).Error
|
||||
err = l.svcCtx.DB.Model(&node.Server{}).Pluck("address", &nodeaddr).Error
|
||||
if err != nil {
|
||||
l.Logger.Error("[GetStatLogic] get server_addr failed: ", logger.Field("error", err.Error()))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get server_addr failed: %v", err.Error())
|
||||
@@ -111,9 +111,23 @@ func (l *GetStatLogic) GetStat() (resp *types.GetStatResponse, err error) {
|
||||
}
|
||||
protocolDict := make(map[string]void)
|
||||
var protocol []string
|
||||
l.svcCtx.DB.Model(&server.Server{}).Where("enable = true").Pluck("protocol", &protocol)
|
||||
err = l.svcCtx.DB.Model(&node.Node{}).Where("enabled = true").Pluck("protocol", &protocol).Error
|
||||
if err != nil {
|
||||
l.Logger.Error("[GetStatLogic] get protocol failed: ", logger.Field("error", err.Error()))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get protocol failed: %v", err.Error())
|
||||
}
|
||||
|
||||
for _, p := range protocol {
|
||||
protocolDict[p] = v
|
||||
var protocols []node.Protocol
|
||||
err = json.Unmarshal([]byte(p), &protocols)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, proto := range protocols {
|
||||
if _, exists := protocolDict[proto.Type]; !exists {
|
||||
protocolDict[proto.Type] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
protocol = nil
|
||||
for p := range protocolDict {
|
||||
|
||||
@@ -51,6 +51,16 @@ func (l *CloseOrderLogic) CloseOrder(req *types.CloseOrderRequest) error {
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, orderInfo.SubscribeId)
|
||||
if err != nil {
|
||||
l.Errorw("[CloseOrder] Find subscribe info failed",
|
||||
logger.Field("error", err.Error()),
|
||||
logger.Field("subscribeId", orderInfo.SubscribeId),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
err = l.svcCtx.DB.Transaction(func(tx *gorm.DB) error {
|
||||
// update order status
|
||||
err := tx.Model(&order.Order{}).Where("order_no = ?", req.OrderNo).Update("status", 3).Error
|
||||
@@ -124,9 +134,21 @@ func (l *CloseOrderLogic) CloseOrder(req *types.CloseOrderRequest) error {
|
||||
// update user cache
|
||||
return l.svcCtx.UserModel.UpdateUserCache(l.ctx, userInfo)
|
||||
}
|
||||
if sub.Inventory != -1 {
|
||||
sub.Inventory++
|
||||
if e := l.svcCtx.SubscribeModel.Update(l.ctx, sub, tx); e != nil {
|
||||
l.Errorw("[CloseOrder] Restore subscribe inventory failed",
|
||||
logger.Field("error", e.Error()),
|
||||
logger.Field("subscribeId", sub.Id),
|
||||
)
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorf("[CloseOrder] Transaction failed: %v", err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -81,6 +81,12 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P
|
||||
if !*sub.Sell {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "subscribe not sell")
|
||||
}
|
||||
|
||||
// check subscribe plan inventory
|
||||
if sub.Inventory == 0 {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SubscribeOutOfStock), "subscribe out of stock")
|
||||
}
|
||||
|
||||
// check subscribe plan limit
|
||||
if sub.Quota > 0 {
|
||||
var count int64
|
||||
@@ -221,10 +227,23 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
if sub.Inventory != -1 {
|
||||
// decrease subscribe plan stock
|
||||
sub.Inventory -= 1
|
||||
// update subscribe plan stock
|
||||
if err = l.svcCtx.SubscribeModel.Update(l.ctx, sub, db); err != nil {
|
||||
l.Errorw("[Purchase] Database update error", logger.Field("error", err.Error()), logger.Field("subscribe", sub))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// insert order
|
||||
return db.WithContext(l.ctx).Model(&order.Order{}).Create(&orderInfo).Error
|
||||
})
|
||||
if err != nil {
|
||||
l.Errorw("[Purchase] Database insert error", logger.Field("error", err.Error()), logger.Field("orderInfo", orderInfo))
|
||||
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert order error: %v", err.Error())
|
||||
}
|
||||
// Deferred task
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/perfect-panel/server/internal/model/log"
|
||||
"github.com/perfect-panel/server/internal/report"
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
"github.com/perfect-panel/server/pkg/exchangeRate"
|
||||
|
||||
paymentPlatform "github.com/perfect-panel/server/pkg/payment"
|
||||
|
||||
@@ -21,12 +22,10 @@ import (
|
||||
"github.com/perfect-panel/server/internal/model/payment"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/exchangeRate"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
"github.com/perfect-panel/server/pkg/payment/alipay"
|
||||
"github.com/perfect-panel/server/pkg/payment/epay"
|
||||
"github.com/perfect-panel/server/pkg/payment/stripe"
|
||||
"github.com/perfect-panel/server/pkg/tool"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -261,6 +260,7 @@ func (l *PurchaseCheckoutLogic) stripePayment(config string, info *order.Order,
|
||||
// epayPayment processes EPay payment by generating a payment URL for redirect
|
||||
// It handles currency conversion and creates a payment URL for external payment processing
|
||||
func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order.Order, returnUrl string) (string, error) {
|
||||
var err error
|
||||
// Parse EPay configuration from payment settings
|
||||
epayConfig := &payment.EPayConfig{}
|
||||
if err := epayConfig.Unmarshal([]byte(config.Config)); err != nil {
|
||||
@@ -269,15 +269,18 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order
|
||||
}
|
||||
// Initialize EPay client with merchant credentials
|
||||
client := epay.NewClient(epayConfig.Pid, epayConfig.Url, epayConfig.Key, epayConfig.Type)
|
||||
|
||||
// Convert order amount to CNY using current exchange rate
|
||||
amount, err := l.queryExchangeRate("CNY", info.Amount)
|
||||
if err != nil {
|
||||
return "", err
|
||||
var amount float64
|
||||
if l.svcCtx.Config.Currency.Unit != "CNY" {
|
||||
// Convert order amount to CNY using current exchange rate
|
||||
amount, err = l.queryExchangeRate("CNY", info.Amount)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
amount = float64(info.Amount) / float64(100)
|
||||
}
|
||||
|
||||
// gateway mod
|
||||
|
||||
isGatewayMod := report.IsGatewayMode()
|
||||
|
||||
// Build notification URL for payment status callbacks
|
||||
@@ -293,7 +296,6 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order
|
||||
if !ok {
|
||||
host = l.svcCtx.Config.Host
|
||||
}
|
||||
|
||||
notifyUrl = "https://" + host
|
||||
if isGatewayMod {
|
||||
notifyUrl += "/api"
|
||||
@@ -316,6 +318,7 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order
|
||||
// CryptoSaaSPayment processes CryptoSaaSPayment payment by generating a payment URL for redirect
|
||||
// It handles currency conversion and creates a payment URL for external payment processing
|
||||
func (l *PurchaseCheckoutLogic) CryptoSaaSPayment(config *payment.Payment, info *order.Order, returnUrl string) (string, error) {
|
||||
var err error
|
||||
// Parse EPay configuration from payment settings
|
||||
epayConfig := &payment.CryptoSaaSConfig{}
|
||||
if err := epayConfig.Unmarshal([]byte(config.Config)); err != nil {
|
||||
@@ -325,10 +328,16 @@ func (l *PurchaseCheckoutLogic) CryptoSaaSPayment(config *payment.Payment, info
|
||||
// Initialize EPay client with merchant credentials
|
||||
client := epay.NewClient(epayConfig.AccountID, epayConfig.Endpoint, epayConfig.SecretKey, epayConfig.Type)
|
||||
|
||||
// Convert order amount to CNY using current exchange rate
|
||||
amount, err := l.queryExchangeRate("CNY", info.Amount)
|
||||
if err != nil {
|
||||
return "", err
|
||||
var amount float64
|
||||
|
||||
if l.svcCtx.Config.Currency.Unit != "CNY" {
|
||||
// Convert order amount to CNY using current exchange rate
|
||||
amount, err = l.queryExchangeRate("CNY", info.Amount)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
amount = float64(info.Amount) / float64(100)
|
||||
}
|
||||
|
||||
// gateway mod
|
||||
@@ -354,6 +363,7 @@ func (l *PurchaseCheckoutLogic) CryptoSaaSPayment(config *payment.Payment, info
|
||||
}
|
||||
notifyUrl = notifyUrl + "/v1/notify/" + config.Platform + "/" + config.Token
|
||||
}
|
||||
|
||||
// Create payment URL for user redirection
|
||||
url := client.CreatePayUrl(epay.Order{
|
||||
Name: l.svcCtx.Config.Site.SiteName,
|
||||
@@ -377,35 +387,18 @@ func (l *PurchaseCheckoutLogic) queryExchangeRate(to string, src int64) (amount
|
||||
return amount, nil
|
||||
}
|
||||
|
||||
// Retrieve system currency configuration
|
||||
currency, err := l.svcCtx.SystemModel.GetCurrencyConfig(l.ctx)
|
||||
if err != nil {
|
||||
l.Errorw("[PurchaseCheckout] GetCurrencyConfig error", logger.Field("error", err.Error()))
|
||||
return 0, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetCurrencyConfig error: %s", err.Error())
|
||||
}
|
||||
|
||||
// Parse currency configuration
|
||||
configs := struct {
|
||||
CurrencyUnit string
|
||||
CurrencySymbol string
|
||||
AccessKey string
|
||||
}{}
|
||||
tool.SystemConfigSliceReflectToStruct(currency, &configs)
|
||||
|
||||
// Skip conversion if no exchange rate API key configured
|
||||
if configs.AccessKey == "" {
|
||||
if l.svcCtx.Config.Currency.AccessKey == "" {
|
||||
return amount, nil
|
||||
}
|
||||
|
||||
// Convert currency if system currency differs from target currency
|
||||
if configs.CurrencyUnit != to {
|
||||
result, err := exchangeRate.GetExchangeRete(configs.CurrencyUnit, to, configs.AccessKey, 1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
amount = result * amount
|
||||
result, err := exchangeRate.GetExchangeRete(l.svcCtx.Config.Currency.Unit, to, l.svcCtx.Config.Currency.AccessKey, 1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return amount, nil
|
||||
l.svcCtx.ExchangeRate = result
|
||||
return result * amount, nil
|
||||
}
|
||||
|
||||
// balancePayment processes balance payment with gift amount priority logic
|
||||
|
||||
@@ -55,6 +55,12 @@ func (l *PurchaseLogic) Purchase(req *types.PortalPurchaseRequest) (resp *types.
|
||||
l.Errorw("[Purchase] Database query error", logger.Field("error", err.Error()), logger.Field("subscribe_id", req.SubscribeId))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe error: %v", err.Error())
|
||||
}
|
||||
|
||||
// check subscribe plan stock
|
||||
if sub.Inventory == 0 {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SubscribeOutOfStock), "subscribe out of stock")
|
||||
}
|
||||
|
||||
// check subscribe plan status
|
||||
if !*sub.Sell {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "subscribe not sell")
|
||||
@@ -149,6 +155,16 @@ func (l *PurchaseLogic) Purchase(req *types.PortalPurchaseRequest) (resp *types.
|
||||
return err
|
||||
}
|
||||
l.Infow("[Purchase] Guest order", logger.Field("order_no", orderInfo.OrderNo), logger.Field("identifier", req.Identifier))
|
||||
|
||||
// Decrease subscribe plan stock
|
||||
if sub.Inventory != -1 {
|
||||
sub.Inventory--
|
||||
if e := l.svcCtx.SubscribeModel.Update(l.ctx, sub, tx); e != nil {
|
||||
l.Errorw("[Purchase] Database update error", logger.Field("error", e.Error()), logger.Field("subscribe_id", sub.Id))
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
// save guest order
|
||||
if err = l.svcCtx.OrderModel.Insert(l.ctx, orderInfo, tx); err != nil {
|
||||
return err
|
||||
|
||||
@@ -51,7 +51,7 @@ func (l *ServerPushUserTrafficLogic) ServerPushUserTraffic(req *types.ServerPush
|
||||
if err != nil {
|
||||
l.Errorw("[ServerPushUserTraffic] Push traffic task error", logger.Field("error", err.Error()), logger.Field("task", t))
|
||||
} else {
|
||||
l.Infow("[ServerPushUserTraffic] Push traffic task success", logger.Field("task", t), logger.Field("info", info))
|
||||
l.Infow("[ServerPushUserTraffic] Push traffic task success", logger.Field("task", t.Type()), logger.Field("info", string(info.Payload)))
|
||||
}
|
||||
|
||||
// Update server last reported time
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/perfect-panel/server/internal/model/client"
|
||||
"github.com/perfect-panel/server/internal/model/log"
|
||||
"github.com/perfect-panel/server/internal/model/node"
|
||||
"github.com/perfect-panel/server/internal/report"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/user"
|
||||
|
||||
@@ -106,10 +107,13 @@ func (l *SubscribeLogic) Handler(req *types.SubscribeRequest) (resp *types.Subsc
|
||||
Download: userSubscribe.Download,
|
||||
Upload: userSubscribe.Upload,
|
||||
Traffic: userSubscribe.Traffic,
|
||||
SubscribeURL: l.getSubscribeV2URL(req.Token),
|
||||
SubscribeURL: l.getSubscribeV2URL(),
|
||||
}),
|
||||
adapter.WithParams(req.Params),
|
||||
)
|
||||
|
||||
logger.Debugf("[SubscribeLogic] Building client config for user %d with URI %s", userSubscribe.UserId, l.getSubscribeV2URL())
|
||||
|
||||
// Get client config
|
||||
adapterClient, err := a.Client()
|
||||
if err != nil {
|
||||
@@ -143,17 +147,20 @@ func (l *SubscribeLogic) Handler(req *types.SubscribeRequest) (resp *types.Subsc
|
||||
return
|
||||
}
|
||||
|
||||
func (l *SubscribeLogic) getSubscribeV2URL(token string) string {
|
||||
if l.svc.Config.Subscribe.PanDomain {
|
||||
return fmt.Sprintf("https://%s", l.ctx.Request.Host)
|
||||
}
|
||||
func (l *SubscribeLogic) getSubscribeV2URL() string {
|
||||
|
||||
uri := l.ctx.Request.RequestURI
|
||||
// is gateway mode, add /sub prefix
|
||||
if report.IsGatewayMode() {
|
||||
uri = "/sub" + uri
|
||||
}
|
||||
// use custom domain if configured
|
||||
if l.svc.Config.Subscribe.SubscribeDomain != "" {
|
||||
domains := strings.Split(l.svc.Config.Subscribe.SubscribeDomain, "\n")
|
||||
return fmt.Sprintf("https://%s%s?token=%s", domains[0], l.svc.Config.Subscribe.SubscribePath, token)
|
||||
return fmt.Sprintf("https://%s%s", domains[0], uri)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("https://%s%s?token=%s&", l.ctx.Request.Host, l.svc.Config.Subscribe.SubscribePath, token)
|
||||
// use current request host
|
||||
return fmt.Sprintf("https://%s%s", l.ctx.Request.Host, uri)
|
||||
}
|
||||
|
||||
func (l *SubscribeLogic) getUserSubscribe(token string) (*user.Subscribe, error) {
|
||||
@@ -209,13 +216,16 @@ func (l *SubscribeLogic) getServers(userSub *user.Subscribe) ([]*node.Node, erro
|
||||
}
|
||||
|
||||
nodeIds := tool.StringToInt64Slice(subDetails.Nodes)
|
||||
tags := strings.Split(subDetails.NodeTags, ",")
|
||||
|
||||
l.Debugf("[Generate Subscribe]nodes: %v, NodeTags: %v", nodeIds, tags)
|
||||
tags := tool.RemoveStringElement(strings.Split(subDetails.NodeTags, ","), "")
|
||||
|
||||
l.Debugf("[Generate Subscribe]nodes: %v, NodeTags: %v", len(nodeIds), len(tags))
|
||||
if len(nodeIds) == 0 && len(tags) == 0 {
|
||||
logger.Infow("[Generate Subscribe]no subscribe nodes")
|
||||
return []*node.Node{}, nil
|
||||
}
|
||||
enable := true
|
||||
|
||||
_, nodes, err := l.svc.NodeModel.FilterNodeList(l.ctx.Request.Context(), &node.FilterNodeParams{
|
||||
var nodes []*node.Node
|
||||
_, nodes, err = l.svc.NodeModel.FilterNodeList(l.ctx.Request.Context(), &node.FilterNodeParams{
|
||||
Page: 1,
|
||||
Size: 1000,
|
||||
NodeId: nodeIds,
|
||||
|
||||
Reference in New Issue
Block a user