feat(api): migrate server and node data handling, update related structures and logic
This commit is contained in:
parent
9b3cdbbb4f
commit
c7884d94aa
@ -1,26 +1,25 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/server"
|
||||
"github.com/perfect-panel/server/internal/model/node"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
"github.com/perfect-panel/server/pkg/random"
|
||||
)
|
||||
|
||||
type Adapter struct {
|
||||
SiteName string // 站点名称
|
||||
Servers []*server.Server // 服务器列表
|
||||
UserInfo User // 用户信息
|
||||
ClientTemplate string // 客户端配置模板
|
||||
OutputFormat string // 输出格式,默认是 base64
|
||||
SubscribeName string // 订阅名称
|
||||
SiteName string // 站点名称
|
||||
Servers []*node.Node // 服务器列表
|
||||
UserInfo User // 用户信息
|
||||
ClientTemplate string // 客户端配置模板
|
||||
OutputFormat string // 输出格式,默认是 base64
|
||||
SubscribeName string // 订阅名称
|
||||
}
|
||||
|
||||
type Option func(*Adapter)
|
||||
|
||||
// WithServers 设置服务器列表
|
||||
func WithServers(servers []*server.Server) Option {
|
||||
func WithServers(servers []*node.Node) Option {
|
||||
return func(opts *Adapter) {
|
||||
opts.Servers = servers
|
||||
}
|
||||
@ -56,7 +55,7 @@ func WithSubscribeName(name string) Option {
|
||||
|
||||
func NewAdapter(tpl string, opts ...Option) *Adapter {
|
||||
adapter := &Adapter{
|
||||
Servers: []*server.Server{},
|
||||
Servers: []*node.Node{},
|
||||
UserInfo: User{},
|
||||
ClientTemplate: tpl,
|
||||
OutputFormat: "base64", // 默认输出格式
|
||||
@ -87,51 +86,54 @@ func (adapter *Adapter) Client() (*Client, error) {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (adapter *Adapter) Proxies(servers []*server.Server) ([]Proxy, error) {
|
||||
func (adapter *Adapter) Proxies(servers []*node.Node) ([]Proxy, error) {
|
||||
var proxies []Proxy
|
||||
for _, srv := range servers {
|
||||
switch srv.RelayMode {
|
||||
case server.RelayModeAll:
|
||||
var relays []server.NodeRelay
|
||||
if err := json.Unmarshal([]byte(srv.RelayNode), &relays); err != nil {
|
||||
logger.Errorw("Unmarshal RelayNode", logger.Field("error", err.Error()), logger.Field("node", srv.Name), logger.Field("relayNode", srv.RelayNode))
|
||||
continue
|
||||
}
|
||||
for _, relay := range relays {
|
||||
proxy, err := adapterProxy(*srv, relay.Host, uint64(relay.Port))
|
||||
if err != nil {
|
||||
logger.Errorw("Adapter Proxy", logger.Field("error", err.Error()), logger.Field("node", srv.Name), logger.Field("relayNode", relay))
|
||||
continue
|
||||
}
|
||||
proxies = append(proxies, proxy)
|
||||
}
|
||||
|
||||
case server.RelayModeRandom:
|
||||
var relays []server.NodeRelay
|
||||
if err := json.Unmarshal([]byte(srv.RelayNode), &relays); err != nil {
|
||||
logger.Errorw("Unmarshal RelayNode", logger.Field("error", err.Error()), logger.Field("node", srv.Name), logger.Field("relayNode", srv.RelayNode))
|
||||
continue
|
||||
}
|
||||
randNum := random.RandomInRange(0, len(relays)-1)
|
||||
relay := relays[randNum]
|
||||
proxy, err := adapterProxy(*srv, relay.Host, uint64(relay.Port))
|
||||
if err != nil {
|
||||
logger.Errorw("Adapter Proxy", logger.Field("error", err.Error()), logger.Field("node", srv.Name), logger.Field("relayNode", relay))
|
||||
continue
|
||||
}
|
||||
proxies = append(proxies, proxy)
|
||||
|
||||
case server.RelayModeNone:
|
||||
proxy, err := adapterProxy(*srv, srv.ServerAddr, 0)
|
||||
if err != nil {
|
||||
logger.Errorw("Adapter Proxy", logger.Field("error", err.Error()), logger.Field("node", srv.Name), logger.Field("serverAddr", srv.ServerAddr))
|
||||
continue
|
||||
}
|
||||
proxies = append(proxies, proxy)
|
||||
default:
|
||||
logger.Errorw("Unknown RelayMode", logger.Field("node", srv.Name), logger.Field("relayMode", srv.RelayMode))
|
||||
for _, item := range servers {
|
||||
if item.Server == nil {
|
||||
logger.Errorf("[Adapter] Server is nil for node ID: %d", item.Id)
|
||||
continue
|
||||
}
|
||||
protocols, err := item.Server.UnmarshalProtocols()
|
||||
if err != nil {
|
||||
logger.Errorf("[Adapter] Unmarshal Protocols error: %s; server id : %d", err.Error(), item.ServerId)
|
||||
continue
|
||||
}
|
||||
for _, protocol := range protocols {
|
||||
if protocol.Type == item.Protocol {
|
||||
proxies = append(proxies, Proxy{
|
||||
Name: item.Name,
|
||||
Server: item.Address,
|
||||
Port: item.Port,
|
||||
Type: item.Protocol,
|
||||
Tags: strings.Split(item.Tags, ","),
|
||||
Security: protocol.Security,
|
||||
SNI: protocol.SNI,
|
||||
AllowInsecure: protocol.AllowInsecure,
|
||||
Fingerprint: protocol.Fingerprint,
|
||||
RealityServerAddr: protocol.RealityServerAddr,
|
||||
RealityServerPort: protocol.RealityServerPort,
|
||||
RealityPrivateKey: protocol.RealityPrivateKey,
|
||||
RealityPublicKey: protocol.RealityPublicKey,
|
||||
RealityShortId: protocol.RealityShortId,
|
||||
Transport: protocol.Transport,
|
||||
Host: protocol.Host,
|
||||
Path: protocol.Path,
|
||||
ServiceName: protocol.ServiceName,
|
||||
Method: protocol.Cipher,
|
||||
ServerKey: protocol.ServerKey,
|
||||
Flow: protocol.Flow,
|
||||
HopPorts: protocol.HopPorts,
|
||||
HopInterval: protocol.HopInterval,
|
||||
ObfsPassword: protocol.ObfsPassword,
|
||||
DisableSNI: protocol.DisableSNI,
|
||||
ReduceRtt: protocol.ReduceRtt,
|
||||
UDPRelayMode: protocol.UDPRelayMode,
|
||||
CongestionController: protocol.CongestionController,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return proxies, nil
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
type Proxy struct {
|
||||
Name string
|
||||
Server string
|
||||
Port uint64
|
||||
Port uint16
|
||||
Type string
|
||||
Tags []string
|
||||
|
||||
|
||||
112
adapter/utils.go
112
adapter/utils.go
@ -1,113 +1 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/server"
|
||||
"github.com/perfect-panel/server/pkg/tool"
|
||||
)
|
||||
|
||||
func adapterProxy(svr server.Server, host string, port uint64) (Proxy, error) {
|
||||
tags := strings.Split(svr.Tags, ",")
|
||||
if len(tags) > 0 {
|
||||
tags = tool.RemoveDuplicateElements(tags...)
|
||||
}
|
||||
node := Proxy{
|
||||
Name: svr.Name,
|
||||
Host: host,
|
||||
Port: port,
|
||||
Type: svr.Protocol,
|
||||
Tags: tags,
|
||||
}
|
||||
switch svr.Protocol {
|
||||
case "shadowsocks":
|
||||
var ss server.Shadowsocks
|
||||
if err := json.Unmarshal([]byte(svr.Config), &ss); err != nil {
|
||||
return node, fmt.Errorf("unmarshal shadowsocks config: %v", err.Error())
|
||||
}
|
||||
if port == 0 {
|
||||
node.Port = uint64(ss.Port)
|
||||
}
|
||||
node.Method = ss.Method
|
||||
node.ServerKey = ss.ServerKey
|
||||
case "vless":
|
||||
var vless server.Vless
|
||||
if err := json.Unmarshal([]byte(svr.Config), &vless); err != nil {
|
||||
return node, fmt.Errorf("unmarshal vless config: %v", err.Error())
|
||||
}
|
||||
if port == 0 {
|
||||
node.Port = uint64(vless.Port)
|
||||
}
|
||||
node.Flow = vless.Flow
|
||||
node.Transport = vless.Transport
|
||||
tool.DeepCopy(&node, vless.TransportConfig)
|
||||
node.Security = vless.Security
|
||||
tool.DeepCopy(&node, vless.SecurityConfig)
|
||||
case "vmess":
|
||||
var vmess server.Vmess
|
||||
if err := json.Unmarshal([]byte(svr.Config), &vmess); err != nil {
|
||||
return node, fmt.Errorf("unmarshal vmess config: %v", err.Error())
|
||||
}
|
||||
if port == 0 {
|
||||
node.Port = uint64(vmess.Port)
|
||||
}
|
||||
node.Flow = vmess.Flow
|
||||
node.Transport = vmess.Transport
|
||||
tool.DeepCopy(&node, vmess.TransportConfig)
|
||||
node.Security = vmess.Security
|
||||
tool.DeepCopy(&node, vmess.SecurityConfig)
|
||||
case "trojan":
|
||||
var trojan server.Trojan
|
||||
if err := json.Unmarshal([]byte(svr.Config), &trojan); err != nil {
|
||||
return node, fmt.Errorf("unmarshal trojan config: %v", err.Error())
|
||||
}
|
||||
if port == 0 {
|
||||
node.Port = uint64(trojan.Port)
|
||||
|
||||
}
|
||||
|
||||
node.Flow = trojan.Flow
|
||||
node.Transport = trojan.Transport
|
||||
tool.DeepCopy(&node, trojan.TransportConfig)
|
||||
node.Security = trojan.Security
|
||||
tool.DeepCopy(&node, trojan.SecurityConfig)
|
||||
case "hysteria2":
|
||||
var hysteria2 server.Hysteria2
|
||||
if err := json.Unmarshal([]byte(svr.Config), &hysteria2); err != nil {
|
||||
return node, fmt.Errorf("unmarshal hysteria2 config: %v", err.Error())
|
||||
}
|
||||
if port == 0 {
|
||||
node.Port = uint64(hysteria2.Port)
|
||||
}
|
||||
node.HopPorts = hysteria2.HopPorts
|
||||
node.HopInterval = hysteria2.HopInterval
|
||||
node.ObfsPassword = hysteria2.ObfsPassword
|
||||
tool.DeepCopy(&node, hysteria2.SecurityConfig)
|
||||
case "tuic":
|
||||
var tuic server.Tuic
|
||||
if err := json.Unmarshal([]byte(svr.Config), &tuic); err != nil {
|
||||
return node, fmt.Errorf("unmarshal tuic config: %v", err.Error())
|
||||
}
|
||||
if port == 0 {
|
||||
node.Port = uint64(tuic.Port)
|
||||
}
|
||||
node.DisableSNI = tuic.DisableSNI
|
||||
node.ReduceRtt = tuic.ReduceRtt
|
||||
node.UDPRelayMode = tuic.UDPRelayMode
|
||||
node.CongestionController = tuic.CongestionController
|
||||
case "anytls":
|
||||
var anytls server.AnyTLS
|
||||
if err := json.Unmarshal([]byte(svr.Config), &anytls); err != nil {
|
||||
return node, fmt.Errorf("unmarshal anytls config: %v", err.Error())
|
||||
}
|
||||
if port == 0 {
|
||||
node.Port = uint64(anytls.Port)
|
||||
}
|
||||
tool.DeepCopy(&node, anytls.SecurityConfig)
|
||||
default:
|
||||
return node, fmt.Errorf("unsupported protocol: %s", svr.Protocol)
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
@ -150,6 +150,14 @@ type (
|
||||
Total int64 `json:"total"`
|
||||
List []Node `json:"list"`
|
||||
}
|
||||
HasMigrateSeverNodeResponse {
|
||||
HasMigrate bool `json:"has_migrate"`
|
||||
}
|
||||
MigrateServerNodeResponse {
|
||||
Succee uint64 `json:"succee"`
|
||||
Fail uint64 `json:"fail"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
@server (
|
||||
@ -197,5 +205,13 @@ service ppanel {
|
||||
@doc "Toggle Node Status"
|
||||
@handler ToggleNodeStatus
|
||||
post /node/status/toggle (ToggleNodeStatusRequest)
|
||||
|
||||
@doc "Check if there is any server or node to migrate"
|
||||
@handler HasMigrateSeverNode
|
||||
get /migrate/has returns (HasMigrateSeverNodeResponse)
|
||||
|
||||
@doc "Migrate server and node data to new database"
|
||||
@handler MigrateServerNode
|
||||
post /migrate/run returns (MigrateServerNodeResponse)
|
||||
}
|
||||
|
||||
|
||||
@ -46,8 +46,8 @@ type (
|
||||
DeviceLimit int64 `json:"device_limit"`
|
||||
Quota int64 `json:"quota"`
|
||||
GroupId int64 `json:"group_id"`
|
||||
ServerGroup []int64 `json:"server_group"`
|
||||
Server []int64 `json:"server"`
|
||||
Nodes []int64 `json:"nodes"`
|
||||
NodeTags []string `json:"node_tags"`
|
||||
Show *bool `json:"show"`
|
||||
Sell *bool `json:"sell"`
|
||||
DeductionRatio int64 `json:"deduction_ratio"`
|
||||
@ -69,8 +69,8 @@ type (
|
||||
DeviceLimit int64 `json:"device_limit"`
|
||||
Quota int64 `json:"quota"`
|
||||
GroupId int64 `json:"group_id"`
|
||||
ServerGroup []int64 `json:"server_group"`
|
||||
Server []int64 `json:"server"`
|
||||
Nodes []int64 `json:"nodes"`
|
||||
NodeTags []string `json:"node_tags"`
|
||||
Show *bool `json:"show"`
|
||||
Sell *bool `json:"sell"`
|
||||
Sort int64 `json:"sort"`
|
||||
|
||||
@ -194,8 +194,8 @@ type (
|
||||
DeviceLimit int64 `json:"device_limit"`
|
||||
Quota int64 `json:"quota"`
|
||||
GroupId int64 `json:"group_id"`
|
||||
ServerGroup []int64 `json:"server_group"`
|
||||
Server []int64 `json:"server"`
|
||||
Nodes []int64 `json:"nodes"`
|
||||
NodeTags []string `json:"node_tags"`
|
||||
Show bool `json:"show"`
|
||||
Sell bool `json:"sell"`
|
||||
Sort int64 `json:"sort"`
|
||||
|
||||
@ -1 +1,2 @@
|
||||
DROP TABLE IF EXISTS `servers`;
|
||||
DROP TABLE IF EXISTS `nodes`;
|
||||
DROP TABLE IF EXISTS `servers`;
|
||||
|
||||
@ -11,4 +11,18 @@ CREATE TABLE IF NOT EXISTS `servers` (
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `nodes` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Node Name',
|
||||
`tags` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Tags',
|
||||
`port` smallint unsigned NOT NULL DEFAULT '0' COMMENT 'Connect Port',
|
||||
`address` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Connect Address',
|
||||
`server_id` bigint NOT NULL DEFAULT '0' COMMENT 'Server ID',
|
||||
`protocol` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Protocol',
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Enabled',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
5
initialize/migrate/database/02106_subscribe.down.sql
Normal file
5
initialize/migrate/database/02106_subscribe.down.sql
Normal file
@ -0,0 +1,5 @@
|
||||
ALTER TABLE `subscribe`
|
||||
DROP COLUMN `nodes`,
|
||||
DROP COLUMN `node_tags`,
|
||||
ADD COLUMN `server` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Server',
|
||||
ADD COLUMN `server_group` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Server Group';
|
||||
7
initialize/migrate/database/02106_subscribe.up.sql
Normal file
7
initialize/migrate/database/02106_subscribe.up.sql
Normal file
@ -0,0 +1,7 @@
|
||||
ALTER TABLE `subscribe`
|
||||
ADD COLUMN `nodes` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Node IDs',
|
||||
ADD COLUMN `node_tags` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Node Tags',
|
||||
DROP COLUMN `server`,
|
||||
DROP COLUMN `server_group`;
|
||||
|
||||
DROP TABLE IF EXISTS `server_rule_group`;
|
||||
@ -3,7 +3,10 @@ package migrate
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/node"
|
||||
"github.com/perfect-panel/server/pkg/orm"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func getDSN() string {
|
||||
@ -30,3 +33,17 @@ func TestMigrate(t *testing.T) {
|
||||
t.Log("migrate success")
|
||||
}
|
||||
}
|
||||
func TestMysql(t *testing.T) {
|
||||
db, err := gorm.Open(mysql.New(mysql.Config{
|
||||
DSN: "root:mylove520@tcp(localhost:3306)/vpnboard",
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to connect to MySQL: %v", err)
|
||||
}
|
||||
err = db.Migrator().AutoMigrate(&node.Node{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to auto migrate: %v", err)
|
||||
return
|
||||
}
|
||||
t.Log("MySQL connection and migration successful")
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ func TrafficDataToRedis(svcCtx *svc.ServiceContext) {
|
||||
}
|
||||
var nodeCacheData []cache.NodeTodayTrafficRank
|
||||
for _, node := range nodeData {
|
||||
serverInfo, err := svcCtx.ServerModel.FindOne(ctx, node.ServerId)
|
||||
serverInfo, err := svcCtx.NodeModel.FindOneServer(ctx, node.ServerId)
|
||||
if err != nil {
|
||||
logger.Errorw("查询节点信息失败", logger.Field("error", err.Error()))
|
||||
continue
|
||||
|
||||
18
internal/handler/admin/server/hasMigrateSeverNodeHandler.go
Normal file
18
internal/handler/admin/server/hasMigrateSeverNodeHandler.go
Normal file
@ -0,0 +1,18 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/perfect-panel/server/internal/logic/admin/server"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/pkg/result"
|
||||
)
|
||||
|
||||
// Check if there is any server or node to migrate
|
||||
func HasMigrateSeverNodeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
|
||||
l := server.NewHasMigrateSeverNodeLogic(c.Request.Context(), svcCtx)
|
||||
resp, err := l.HasMigrateSeverNode()
|
||||
result.HttpResult(c, resp, err)
|
||||
}
|
||||
}
|
||||
18
internal/handler/admin/server/migrateServerNodeHandler.go
Normal file
18
internal/handler/admin/server/migrateServerNodeHandler.go
Normal file
@ -0,0 +1,18 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/perfect-panel/server/internal/logic/admin/server"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/pkg/result"
|
||||
)
|
||||
|
||||
// Migrate server and node data to new database
|
||||
func MigrateServerNodeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
|
||||
l := server.NewMigrateServerNodeLogic(c.Request.Context(), svcCtx)
|
||||
resp, err := l.MigrateServerNode()
|
||||
result.HttpResult(c, resp, err)
|
||||
}
|
||||
}
|
||||
@ -296,6 +296,12 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
||||
// Filter Server List
|
||||
adminServerGroupRouter.GET("/list", adminServer.FilterServerListHandler(serverCtx))
|
||||
|
||||
// Check if there is any server or node to migrate
|
||||
adminServerGroupRouter.GET("/migrate/has", adminServer.HasMigrateSeverNodeHandler(serverCtx))
|
||||
|
||||
// Migrate server and node data to new database
|
||||
adminServerGroupRouter.POST("/migrate/run", adminServer.MigrateServerNodeHandler(serverCtx))
|
||||
|
||||
// Create Node
|
||||
adminServerGroupRouter.POST("/node/create", adminServer.CreateNodeHandler(serverCtx))
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/adapter"
|
||||
"github.com/perfect-panel/server/internal/model/node"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
@ -28,7 +29,10 @@ func NewPreviewSubscribeTemplateLogic(ctx context.Context, svcCtx *svc.ServiceCo
|
||||
}
|
||||
|
||||
func (l *PreviewSubscribeTemplateLogic) PreviewSubscribeTemplate(req *types.PreviewSubscribeTemplateRequest) (resp *types.PreviewSubscribeTemplateResponse, err error) {
|
||||
servers, err := l.svcCtx.ServerModel.FindAllServer(l.ctx)
|
||||
_, servers, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{
|
||||
Page: 1,
|
||||
Size: 1000,
|
||||
})
|
||||
if err != nil {
|
||||
l.Errorf("[PreviewSubscribeTemplateLogic] FindAllServer error: %v", err.Error())
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindAllServer error: %v", err.Error())
|
||||
|
||||
11
internal/logic/admin/server/constant.go
Normal file
11
internal/logic/admin/server/constant.go
Normal file
@ -0,0 +1,11 @@
|
||||
package server
|
||||
|
||||
const (
|
||||
ShadowSocks = "shadowsocks"
|
||||
Vmess = "vmess"
|
||||
Vless = "vless"
|
||||
Trojan = "trojan"
|
||||
AnyTLS = "anytls"
|
||||
Tuic = "tuic"
|
||||
Hysteria2 = "hysteria2"
|
||||
)
|
||||
@ -28,7 +28,7 @@ func NewFilterNodeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Fi
|
||||
}
|
||||
|
||||
func (l *FilterNodeListLogic) FilterNodeList(req *types.FilterNodeListRequest) (resp *types.FilterNodeListResponse, err error) {
|
||||
total, data, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterParams{
|
||||
total, data, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{
|
||||
Page: req.Page,
|
||||
Size: req.Size,
|
||||
Search: req.Search,
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"github.com/perfect-panel/server/pkg/tool"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type FilterServerListLogic struct {
|
||||
@ -68,7 +69,9 @@ func (l *FilterServerListLogic) handlerServerStatus(id int64) types.ServerStatus
|
||||
var result types.ServerStatus
|
||||
nodeStatus, err := l.svcCtx.NodeCache.GetNodeStatus(l.ctx, id)
|
||||
if err != nil {
|
||||
l.Errorw("[handlerServerStatus] GetNodeStatus Error: ", logger.Field("error", err.Error()), logger.Field("node_id", id))
|
||||
if !errors.Is(err, redis.Nil) {
|
||||
l.Errorw("[handlerServerStatus] GetNodeStatus Error: ", logger.Field("error", err.Error()), logger.Field("node_id", id))
|
||||
}
|
||||
return result
|
||||
}
|
||||
result = types.ServerStatus{
|
||||
|
||||
52
internal/logic/admin/server/hasMigrateSeverNodeLogic.go
Normal file
52
internal/logic/admin/server/hasMigrateSeverNodeLogic.go
Normal file
@ -0,0 +1,52 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/node"
|
||||
"github.com/perfect-panel/server/internal/model/server"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type HasMigrateSeverNodeLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// NewHasMigrateSeverNodeLogic Check if there is any server or node to migrate
|
||||
func NewHasMigrateSeverNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *HasMigrateSeverNodeLogic {
|
||||
return &HasMigrateSeverNodeLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *HasMigrateSeverNodeLogic) HasMigrateSeverNode() (resp *types.HasMigrateSeverNodeResponse, err error) {
|
||||
var oldCount, newCount int64
|
||||
query := l.svcCtx.DB.WithContext(l.ctx)
|
||||
|
||||
err = query.Model(&server.Server{}).Count(&oldCount).Error
|
||||
if err != nil {
|
||||
l.Errorw("[HasMigrateSeverNode] Query Old Server Count Error: ", logger.Field("error", err.Error()))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[HasMigrateSeverNode] Query Old Server Count Error")
|
||||
}
|
||||
err = query.Model(&node.Server{}).Count(&newCount).Error
|
||||
if err != nil {
|
||||
l.Errorw("[HasMigrateSeverNode] Query New Server Count Error: ", logger.Field("error", err.Error()))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[HasMigrateSeverNode] Query New Server Count Error")
|
||||
}
|
||||
var shouldMigrate bool
|
||||
if oldCount != 0 && newCount == 0 {
|
||||
shouldMigrate = true
|
||||
}
|
||||
|
||||
return &types.HasMigrateSeverNodeResponse{
|
||||
HasMigrate: shouldMigrate,
|
||||
}, nil
|
||||
}
|
||||
330
internal/logic/admin/server/migrateServerNodeLogic.go
Normal file
330
internal/logic/admin/server/migrateServerNodeLogic.go
Normal file
@ -0,0 +1,330 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/node"
|
||||
"github.com/perfect-panel/server/internal/model/server"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
)
|
||||
|
||||
type MigrateServerNodeLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// NewMigrateServerNodeLogic Migrate server and node data to new database
|
||||
func NewMigrateServerNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *MigrateServerNodeLogic {
|
||||
return &MigrateServerNodeLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *MigrateServerNodeLogic) MigrateServerNode() (resp *types.MigrateServerNodeResponse, err error) {
|
||||
tx := l.svcCtx.DB.WithContext(l.ctx).Begin()
|
||||
var oldServers []*server.Server
|
||||
var newServers []*node.Server
|
||||
var newNodes []*node.Node
|
||||
|
||||
err = tx.Model(&server.Server{}).Find(&oldServers).Error
|
||||
if err != nil {
|
||||
l.Errorw("[MigrateServerNode] Query Old Server List Error: ", logger.Field("error", err.Error()))
|
||||
return &types.MigrateServerNodeResponse{
|
||||
Succee: 0,
|
||||
Fail: 0,
|
||||
Message: fmt.Sprintf("Query Old Server List Error: %s", err.Error()),
|
||||
}, nil
|
||||
}
|
||||
for _, oldServer := range oldServers {
|
||||
data, err := l.adapterServer(oldServer)
|
||||
if err != nil {
|
||||
l.Errorw("[MigrateServerNode] Adapter Server Error: ", logger.Field("error", err.Error()))
|
||||
if resp == nil {
|
||||
resp = &types.MigrateServerNodeResponse{}
|
||||
}
|
||||
resp.Fail++
|
||||
if resp.Message == "" {
|
||||
resp.Message = fmt.Sprintf("Adapter Server Error: %s", err.Error())
|
||||
} else {
|
||||
resp.Message = fmt.Sprintf("%s; Adapter Server Error: %s", resp.Message, err.Error())
|
||||
}
|
||||
continue
|
||||
}
|
||||
newServers = append(newServers, data)
|
||||
|
||||
newNode, err := l.adapterNode(oldServer)
|
||||
if err != nil {
|
||||
l.Errorw("[MigrateServerNode] Adapter Node Error: ", logger.Field("error", err.Error()))
|
||||
if resp == nil {
|
||||
resp = &types.MigrateServerNodeResponse{}
|
||||
}
|
||||
resp.Fail++
|
||||
if resp.Message == "" {
|
||||
resp.Message = fmt.Sprintf("Adapter Node Error: %s", err.Error())
|
||||
} else {
|
||||
resp.Message = fmt.Sprintf("%s; Adapter Node Error: %s", resp.Message, err.Error())
|
||||
}
|
||||
continue
|
||||
}
|
||||
for _, item := range newNode {
|
||||
if item.Port == 0 {
|
||||
protocols, _ := data.UnmarshalProtocols()
|
||||
if len(protocols) > 0 {
|
||||
item.Port = protocols[0].Port
|
||||
}
|
||||
}
|
||||
newNodes = append(newNodes, item)
|
||||
}
|
||||
}
|
||||
|
||||
if len(newServers) > 0 {
|
||||
err = tx.Model(&node.Server{}).CreateInBatches(newServers, 20).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
l.Errorw("[MigrateServerNode] Insert New Server List Error: ", logger.Field("error", err.Error()))
|
||||
return &types.MigrateServerNodeResponse{
|
||||
Succee: 0,
|
||||
Fail: uint64(len(newServers)),
|
||||
Message: fmt.Sprintf("Insert New Server List Error: %s", err.Error()),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
if len(newNodes) > 0 {
|
||||
err = tx.Model(&node.Node{}).CreateInBatches(newNodes, 20).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
l.Errorw("[MigrateServerNode] Insert New Node List Error: ", logger.Field("error", err.Error()))
|
||||
return &types.MigrateServerNodeResponse{
|
||||
Succee: uint64(len(newServers)),
|
||||
Fail: uint64(len(newNodes)),
|
||||
Message: fmt.Sprintf("Insert New Node List Error: %s", err.Error()),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
tx.Commit()
|
||||
|
||||
return &types.MigrateServerNodeResponse{
|
||||
Succee: uint64(len(newServers)),
|
||||
Fail: 0,
|
||||
Message: fmt.Sprintf("Migrate Success: %d servers and %d nodes", len(newServers), len(newNodes)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *MigrateServerNodeLogic) adapterServer(info *server.Server) (*node.Server, error) {
|
||||
result := &node.Server{
|
||||
Name: info.Name,
|
||||
Country: info.Country,
|
||||
City: info.City,
|
||||
Ratio: info.TrafficRatio,
|
||||
Address: info.ServerAddr,
|
||||
Sort: int(info.Sort),
|
||||
Protocols: "",
|
||||
}
|
||||
var protocols []node.Protocol
|
||||
|
||||
switch info.Protocol {
|
||||
case ShadowSocks:
|
||||
var src server.Shadowsocks
|
||||
err := json.Unmarshal([]byte(info.Config), &src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protocols = append(protocols, node.Protocol{
|
||||
Type: "shadowsocks",
|
||||
Cipher: src.Method,
|
||||
Port: uint16(src.Port),
|
||||
ServerKey: src.ServerKey,
|
||||
})
|
||||
case Vmess:
|
||||
var src server.Vmess
|
||||
err := json.Unmarshal([]byte(info.Config), &src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protocol := node.Protocol{
|
||||
Type: "vmess",
|
||||
Port: uint16(src.Port),
|
||||
Security: src.Security,
|
||||
SNI: src.SecurityConfig.SNI,
|
||||
AllowInsecure: src.SecurityConfig.AllowInsecure,
|
||||
Fingerprint: src.SecurityConfig.Fingerprint,
|
||||
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
|
||||
RealityServerPort: src.SecurityConfig.RealityServerPort,
|
||||
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
|
||||
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
|
||||
RealityShortId: src.SecurityConfig.RealityShortId,
|
||||
Transport: src.Transport,
|
||||
Host: src.TransportConfig.Host,
|
||||
Path: src.TransportConfig.Path,
|
||||
ServiceName: src.TransportConfig.ServiceName,
|
||||
Flow: src.Flow,
|
||||
}
|
||||
protocols = append(protocols, protocol)
|
||||
protocols = append(protocols, protocol)
|
||||
case Vless:
|
||||
var src server.Vless
|
||||
err := json.Unmarshal([]byte(info.Config), &src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protocol := node.Protocol{
|
||||
Type: "vless",
|
||||
Port: uint16(src.Port),
|
||||
Security: src.Security,
|
||||
SNI: src.SecurityConfig.SNI,
|
||||
AllowInsecure: src.SecurityConfig.AllowInsecure,
|
||||
Fingerprint: src.SecurityConfig.Fingerprint,
|
||||
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
|
||||
RealityServerPort: src.SecurityConfig.RealityServerPort,
|
||||
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
|
||||
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
|
||||
RealityShortId: src.SecurityConfig.RealityShortId,
|
||||
Transport: src.Transport,
|
||||
Host: src.TransportConfig.Host,
|
||||
Path: src.TransportConfig.Path,
|
||||
ServiceName: src.TransportConfig.ServiceName,
|
||||
Flow: src.Flow,
|
||||
}
|
||||
protocols = append(protocols, protocol)
|
||||
case Trojan:
|
||||
var src server.Trojan
|
||||
err := json.Unmarshal([]byte(info.Config), &src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protocol := node.Protocol{
|
||||
Type: "trojan",
|
||||
Port: uint16(src.Port),
|
||||
Security: src.Security,
|
||||
SNI: src.SecurityConfig.SNI,
|
||||
AllowInsecure: src.SecurityConfig.AllowInsecure,
|
||||
Fingerprint: src.SecurityConfig.Fingerprint,
|
||||
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
|
||||
RealityServerPort: src.SecurityConfig.RealityServerPort,
|
||||
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
|
||||
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
|
||||
RealityShortId: src.SecurityConfig.RealityShortId,
|
||||
Transport: src.Transport,
|
||||
Host: src.TransportConfig.Host,
|
||||
Path: src.TransportConfig.Path,
|
||||
ServiceName: src.TransportConfig.ServiceName,
|
||||
Flow: src.Flow,
|
||||
}
|
||||
protocols = append(protocols, protocol)
|
||||
case Hysteria2:
|
||||
var src server.Hysteria2
|
||||
err := json.Unmarshal([]byte(info.Config), &src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protocol := node.Protocol{
|
||||
Type: "hysteria2",
|
||||
Port: uint16(src.Port),
|
||||
HopPorts: src.HopPorts,
|
||||
HopInterval: src.HopInterval,
|
||||
ObfsPassword: src.ObfsPassword,
|
||||
SNI: src.SecurityConfig.SNI,
|
||||
AllowInsecure: src.SecurityConfig.AllowInsecure,
|
||||
Fingerprint: src.SecurityConfig.Fingerprint,
|
||||
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
|
||||
RealityServerPort: src.SecurityConfig.RealityServerPort,
|
||||
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
|
||||
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
|
||||
RealityShortId: src.SecurityConfig.RealityShortId,
|
||||
}
|
||||
protocols = append(protocols, protocol)
|
||||
case Tuic:
|
||||
var src server.Tuic
|
||||
err := json.Unmarshal([]byte(info.Config), &src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protocol := node.Protocol{
|
||||
Type: "tuic",
|
||||
Port: uint16(src.Port),
|
||||
DisableSNI: src.DisableSNI,
|
||||
ReduceRtt: src.ReduceRtt,
|
||||
UDPRelayMode: src.UDPRelayMode,
|
||||
CongestionController: src.CongestionController,
|
||||
SNI: src.SecurityConfig.SNI,
|
||||
AllowInsecure: src.SecurityConfig.AllowInsecure,
|
||||
Fingerprint: src.SecurityConfig.Fingerprint,
|
||||
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
|
||||
RealityServerPort: src.SecurityConfig.RealityServerPort,
|
||||
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
|
||||
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
|
||||
RealityShortId: src.SecurityConfig.RealityShortId,
|
||||
}
|
||||
protocols = append(protocols, protocol)
|
||||
case AnyTLS:
|
||||
var src server.AnyTLS
|
||||
err := json.Unmarshal([]byte(info.Config), &src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protocol := node.Protocol{
|
||||
Type: "anytls",
|
||||
Port: uint16(src.Port),
|
||||
SNI: src.SecurityConfig.SNI,
|
||||
AllowInsecure: src.SecurityConfig.AllowInsecure,
|
||||
Fingerprint: src.SecurityConfig.Fingerprint,
|
||||
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
|
||||
RealityServerPort: src.SecurityConfig.RealityServerPort,
|
||||
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
|
||||
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
|
||||
RealityShortId: src.SecurityConfig.RealityShortId,
|
||||
}
|
||||
protocols = append(protocols, protocol)
|
||||
}
|
||||
if len(protocols) > 0 {
|
||||
err := result.MarshalProtocols(protocols)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (l *MigrateServerNodeLogic) adapterNode(info *server.Server) ([]*node.Node, error) {
|
||||
var nodes []*node.Node
|
||||
enable := true
|
||||
switch info.RelayMode {
|
||||
case server.RelayModeNone:
|
||||
nodes = append(nodes, &node.Node{
|
||||
Name: info.Name,
|
||||
Tags: "",
|
||||
Port: 0,
|
||||
Address: info.ServerAddr,
|
||||
ServerId: info.Id,
|
||||
Protocol: info.Protocol,
|
||||
Enabled: &enable,
|
||||
})
|
||||
default:
|
||||
var relays []server.NodeRelay
|
||||
err := json.Unmarshal([]byte(info.RelayNode), &relays)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, relay := range relays {
|
||||
nodes = append(nodes, &node.Node{
|
||||
Name: relay.Prefix + info.Name,
|
||||
Tags: "",
|
||||
Port: uint16(relay.Port),
|
||||
Address: relay.Host,
|
||||
ServerId: info.Id,
|
||||
Protocol: info.Protocol,
|
||||
Enabled: &enable,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
@ -48,8 +48,8 @@ func (l *CreateSubscribeLogic) CreateSubscribe(req *types.CreateSubscribeRequest
|
||||
DeviceLimit: req.DeviceLimit,
|
||||
Quota: req.Quota,
|
||||
GroupId: req.GroupId,
|
||||
ServerGroup: tool.Int64SliceToString(req.ServerGroup),
|
||||
Server: tool.Int64SliceToString(req.Server),
|
||||
Nodes: tool.Int64SliceToString(req.Nodes),
|
||||
NodeTags: tool.StringSliceToString(req.NodeTags),
|
||||
Show: req.Show,
|
||||
Sell: req.Sell,
|
||||
Sort: 0,
|
||||
|
||||
@ -3,6 +3,7 @@ package subscribe
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
@ -41,7 +42,7 @@ func (l *GetSubscribeDetailsLogic) GetSubscribeDetails(req *types.GetSubscribeDe
|
||||
l.Logger.Error("[GetSubscribeDetailsLogic] JSON unmarshal failed: ", logger.Field("error", err.Error()), logger.Field("discount", sub.Discount))
|
||||
}
|
||||
}
|
||||
resp.Server = tool.StringToInt64Slice(sub.Server)
|
||||
resp.ServerGroup = tool.StringToInt64Slice(sub.ServerGroup)
|
||||
resp.Nodes = tool.StringToInt64Slice(sub.Nodes)
|
||||
resp.NodeTags = strings.Split(sub.NodeTags, ",")
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package subscribe
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
@ -47,8 +48,8 @@ func (l *GetSubscribeListLogic) GetSubscribeList(req *types.GetSubscribeListRequ
|
||||
l.Logger.Error("[GetSubscribeListLogic] JSON unmarshal failed: ", logger.Field("error", err.Error()), logger.Field("discount", item.Discount))
|
||||
}
|
||||
}
|
||||
sub.Server = tool.StringToInt64Slice(item.Server)
|
||||
sub.ServerGroup = tool.StringToInt64Slice(item.ServerGroup)
|
||||
sub.Nodes = tool.StringToInt64Slice(item.Nodes)
|
||||
sub.NodeTags = strings.Split(item.NodeTags, ",")
|
||||
resultList = append(resultList, sub)
|
||||
}
|
||||
|
||||
|
||||
@ -56,8 +56,8 @@ func (l *UpdateSubscribeLogic) UpdateSubscribe(req *types.UpdateSubscribeRequest
|
||||
DeviceLimit: req.DeviceLimit,
|
||||
Quota: req.Quota,
|
||||
GroupId: req.GroupId,
|
||||
ServerGroup: tool.Int64SliceToString(req.ServerGroup),
|
||||
Server: tool.Int64SliceToString(req.Server),
|
||||
Nodes: tool.Int64SliceToString(req.Nodes),
|
||||
NodeTags: tool.StringSliceToString(req.NodeTags),
|
||||
Show: req.Show,
|
||||
Sell: req.Sell,
|
||||
Sort: req.Sort,
|
||||
|
||||
@ -51,7 +51,7 @@ func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (res
|
||||
Success: loginStatus,
|
||||
}
|
||||
content, _ := loginLog.Marshal()
|
||||
if err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||
if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||
Id: 0,
|
||||
Type: log.TypeLogin.Uint8(),
|
||||
Date: time.Now().Format("2006-01-02"),
|
||||
|
||||
@ -59,7 +59,7 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r
|
||||
Success: loginStatus,
|
||||
}
|
||||
content, _ := loginLog.Marshal()
|
||||
if err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||
if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||
Id: 0,
|
||||
Type: log.TypeLogin.Uint8(),
|
||||
Date: time.Now().Format("2006-01-02"),
|
||||
|
||||
@ -110,7 +110,7 @@ func (l *TelephoneResetPasswordLogic) TelephoneResetPassword(req *types.Telephon
|
||||
Success: token != "",
|
||||
}
|
||||
content, _ := loginLog.Marshal()
|
||||
if err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||
if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||
Id: 0,
|
||||
Type: log.TypeLogin.Uint8(),
|
||||
Date: time.Now().Format("2006-01-02"),
|
||||
|
||||
@ -165,7 +165,7 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR
|
||||
Success: token != "",
|
||||
}
|
||||
content, _ := loginLog.Marshal()
|
||||
if err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||
if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||
Id: 0,
|
||||
Type: log.TypeLogin.Uint8(),
|
||||
Date: time.Now().Format("2006-01-02"),
|
||||
@ -188,7 +188,7 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR
|
||||
RegisterTime: time.Now().UnixMilli(),
|
||||
}
|
||||
content, _ = registerLog.Marshal()
|
||||
if err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||
if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||
Type: log.TypeRegister.Uint8(),
|
||||
ObjectID: userInfo.Id,
|
||||
Date: time.Now().Format("2006-01-02"),
|
||||
|
||||
@ -49,7 +49,7 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log
|
||||
Success: loginStatus,
|
||||
}
|
||||
content, _ := loginLog.Marshal()
|
||||
if err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||
if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||
Type: log.TypeLogin.Uint8(),
|
||||
Date: time.Now().Format("2006-01-02"),
|
||||
ObjectID: userInfo.Id,
|
||||
|
||||
@ -153,7 +153,7 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *
|
||||
Success: loginStatus,
|
||||
}
|
||||
content, _ := loginLog.Marshal()
|
||||
if err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||
if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||
Id: 0,
|
||||
Type: log.TypeLogin.Uint8(),
|
||||
Date: time.Now().Format("2006-01-02"),
|
||||
|
||||
@ -1,3 +1,83 @@
|
||||
package server
|
||||
|
||||
const Unchanged = "Unchanged"
|
||||
const (
|
||||
Unchanged = "Unchanged"
|
||||
ShadowSocks = "shadowsocks"
|
||||
Vmess = "vmess"
|
||||
Vless = "vless"
|
||||
Trojan = "trojan"
|
||||
AnyTLS = "anytls"
|
||||
Tuic = "tuic"
|
||||
Hysteria2 = "hysteria2"
|
||||
)
|
||||
|
||||
type SecurityConfig struct {
|
||||
SNI string `json:"sni"`
|
||||
AllowInsecure *bool `json:"allow_insecure"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
RealityServerAddress string `json:"reality_server_addr"`
|
||||
RealityServerPort int `json:"reality_server_port"`
|
||||
RealityPrivateKey string `json:"reality_private_key"`
|
||||
RealityPublicKey string `json:"reality_public_key"`
|
||||
RealityShortId string `json:"reality_short_id"`
|
||||
RealityMldsa65seed string `json:"reality_mldsa65seed"`
|
||||
}
|
||||
|
||||
type TransportConfig struct {
|
||||
Path string `json:"path"`
|
||||
Host string `json:"host"`
|
||||
ServiceName string `json:"service_name"`
|
||||
DisableSNI bool `json:"disable_sni"`
|
||||
ReduceRtt bool `json:"reduce_rtt"`
|
||||
UDPRelayMode string `json:"udp_relay_mode"`
|
||||
CongestionController string `json:"congestion_controller"`
|
||||
}
|
||||
|
||||
type VlessNode struct {
|
||||
Port uint16 `json:"port"`
|
||||
Flow string `json:"flow"`
|
||||
Network string `json:"transport"`
|
||||
TransportConfig *TransportConfig `json:"transport_config"`
|
||||
Security string `json:"security"`
|
||||
SecurityConfig *SecurityConfig `json:"security_config"`
|
||||
}
|
||||
|
||||
type VmessNode struct {
|
||||
Port uint16 `json:"port"`
|
||||
Network string `json:"transport"`
|
||||
TransportConfig *TransportConfig `json:"transport_config"`
|
||||
Security string `json:"security"`
|
||||
SecurityConfig *SecurityConfig `json:"security_config"`
|
||||
}
|
||||
|
||||
type ShadowsocksNode struct {
|
||||
Port uint16 `json:"port"`
|
||||
Cipher string `json:"method"`
|
||||
ServerKey string `json:"server_key"`
|
||||
}
|
||||
|
||||
type TrojanNode struct {
|
||||
Port uint16 `json:"port"`
|
||||
Network string `json:"transport"`
|
||||
TransportConfig *TransportConfig `json:"transport_config"`
|
||||
Security string `json:"security"`
|
||||
SecurityConfig *SecurityConfig `json:"security_config"`
|
||||
}
|
||||
|
||||
type AnyTLSNode struct {
|
||||
Port uint16 `json:"port"`
|
||||
SecurityConfig *SecurityConfig `json:"security_config"`
|
||||
}
|
||||
|
||||
type TuicNode struct {
|
||||
Port uint16 `json:"port"`
|
||||
SecurityConfig *SecurityConfig `json:"security_config"`
|
||||
}
|
||||
|
||||
type Hysteria2Node struct {
|
||||
Port uint16 `json:"port"`
|
||||
HopPorts string `json:"hop_ports"`
|
||||
HopInterval int `json:"hop_interval"`
|
||||
ObfsPassword string `json:"obfs_password"`
|
||||
SecurityConfig *SecurityConfig `json:"security_config"`
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/perfect-panel/server/internal/model/node"
|
||||
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
@ -51,21 +51,21 @@ func (l *GetServerConfigLogic) GetServerConfig(req *types.GetServerConfigRequest
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
nodeInfo, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.ServerId)
|
||||
data, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerId)
|
||||
if err != nil {
|
||||
l.Errorw("[GetServerConfig] FindOne error", logger.Field("error", err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
cfg := make(map[string]interface{})
|
||||
err = json.Unmarshal([]byte(nodeInfo.Config), &cfg)
|
||||
|
||||
protocols, err := data.UnmarshalProtocols()
|
||||
if err != nil {
|
||||
l.Errorw("[GetServerConfig] json unmarshal error", logger.Field("error", err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if nodeInfo.Protocol == "shadowsocks" {
|
||||
if value, ok := cfg["server_key"]; ok && value != "" {
|
||||
cfg["server_key"] = base64.StdEncoding.EncodeToString([]byte(value.(string)))
|
||||
var cfg map[string]interface{}
|
||||
for _, protocol := range protocols {
|
||||
if protocol.Type == req.Protocol {
|
||||
cfg = l.compatible(protocol)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,18 +74,162 @@ func (l *GetServerConfigLogic) GetServerConfig(req *types.GetServerConfigRequest
|
||||
PullInterval: l.svcCtx.Config.Node.NodePullInterval,
|
||||
PushInterval: l.svcCtx.Config.Node.NodePushInterval,
|
||||
},
|
||||
Protocol: nodeInfo.Protocol,
|
||||
Protocol: req.Protocol,
|
||||
Config: cfg,
|
||||
}
|
||||
data, err := json.Marshal(resp)
|
||||
c, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
l.Errorw("[GetServerConfig] json marshal error", logger.Field("error", err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
etag := tool.GenerateETag(data)
|
||||
etag := tool.GenerateETag(c)
|
||||
l.ctx.Header("ETag", etag)
|
||||
if err = l.svcCtx.Redis.Set(l.ctx, cacheKey, data, -1).Err(); err != nil {
|
||||
if err = l.svcCtx.Redis.Set(l.ctx, cacheKey, c, -1).Err(); err != nil {
|
||||
l.Errorw("[GetServerConfig] redis set error", logger.Field("error", err.Error()))
|
||||
}
|
||||
// Check If-None-Match header
|
||||
match := l.ctx.GetHeader("If-None-Match")
|
||||
if match == etag {
|
||||
return nil, xerr.StatusNotModified
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (l *GetServerConfigLogic) compatible(config node.Protocol) map[string]interface{} {
|
||||
var result interface{}
|
||||
switch config.Type {
|
||||
case ShadowSocks:
|
||||
result = ShadowsocksNode{
|
||||
Port: config.Port,
|
||||
Cipher: config.Cipher,
|
||||
ServerKey: config.ServerKey,
|
||||
}
|
||||
case Vless:
|
||||
result = VlessNode{
|
||||
Port: config.Port,
|
||||
Flow: config.Flow,
|
||||
Network: config.Transport,
|
||||
TransportConfig: &TransportConfig{
|
||||
Path: config.Path,
|
||||
Host: config.Host,
|
||||
ServiceName: config.ServiceName,
|
||||
DisableSNI: config.DisableSNI,
|
||||
ReduceRtt: config.ReduceRtt,
|
||||
UDPRelayMode: config.UDPRelayMode,
|
||||
CongestionController: config.CongestionController,
|
||||
},
|
||||
Security: config.Security,
|
||||
SecurityConfig: &SecurityConfig{
|
||||
SNI: config.SNI,
|
||||
AllowInsecure: &config.AllowInsecure,
|
||||
Fingerprint: config.Fingerprint,
|
||||
RealityServerAddress: config.RealityServerAddr,
|
||||
RealityServerPort: config.RealityServerPort,
|
||||
RealityPrivateKey: config.RealityPrivateKey,
|
||||
RealityPublicKey: config.RealityPublicKey,
|
||||
RealityShortId: config.RealityShortId,
|
||||
},
|
||||
}
|
||||
case Vmess:
|
||||
result = VmessNode{
|
||||
Port: config.Port,
|
||||
Network: config.Transport,
|
||||
TransportConfig: &TransportConfig{
|
||||
Path: config.Path,
|
||||
Host: config.Host,
|
||||
ServiceName: config.ServiceName,
|
||||
DisableSNI: config.DisableSNI,
|
||||
ReduceRtt: config.ReduceRtt,
|
||||
UDPRelayMode: config.UDPRelayMode,
|
||||
CongestionController: config.CongestionController,
|
||||
},
|
||||
Security: config.Security,
|
||||
SecurityConfig: &SecurityConfig{
|
||||
SNI: config.SNI,
|
||||
AllowInsecure: &config.AllowInsecure,
|
||||
Fingerprint: config.Fingerprint,
|
||||
RealityServerAddress: config.RealityServerAddr,
|
||||
RealityServerPort: config.RealityServerPort,
|
||||
RealityPrivateKey: config.RealityPrivateKey,
|
||||
RealityPublicKey: config.RealityPublicKey,
|
||||
RealityShortId: config.RealityShortId,
|
||||
},
|
||||
}
|
||||
case Trojan:
|
||||
result = TrojanNode{
|
||||
Port: config.Port,
|
||||
Network: config.Transport,
|
||||
TransportConfig: &TransportConfig{
|
||||
Path: config.Path,
|
||||
Host: config.Host,
|
||||
ServiceName: config.ServiceName,
|
||||
DisableSNI: config.DisableSNI,
|
||||
ReduceRtt: config.ReduceRtt,
|
||||
UDPRelayMode: config.UDPRelayMode,
|
||||
CongestionController: config.CongestionController,
|
||||
},
|
||||
Security: config.Security,
|
||||
SecurityConfig: &SecurityConfig{
|
||||
SNI: config.SNI,
|
||||
AllowInsecure: &config.AllowInsecure,
|
||||
Fingerprint: config.Fingerprint,
|
||||
RealityServerAddress: config.RealityServerAddr,
|
||||
RealityServerPort: config.RealityServerPort,
|
||||
RealityPrivateKey: config.RealityPrivateKey,
|
||||
RealityPublicKey: config.RealityPublicKey,
|
||||
RealityShortId: config.RealityShortId,
|
||||
},
|
||||
}
|
||||
case AnyTLS:
|
||||
result = AnyTLSNode{
|
||||
Port: config.Port,
|
||||
SecurityConfig: &SecurityConfig{
|
||||
SNI: config.SNI,
|
||||
AllowInsecure: &config.AllowInsecure,
|
||||
Fingerprint: config.Fingerprint,
|
||||
RealityServerAddress: config.RealityServerAddr,
|
||||
RealityServerPort: config.RealityServerPort,
|
||||
RealityPrivateKey: config.RealityPrivateKey,
|
||||
RealityPublicKey: config.RealityPublicKey,
|
||||
RealityShortId: config.RealityShortId,
|
||||
},
|
||||
}
|
||||
case Tuic:
|
||||
result = TuicNode{
|
||||
Port: config.Port,
|
||||
SecurityConfig: &SecurityConfig{
|
||||
SNI: config.SNI,
|
||||
AllowInsecure: &config.AllowInsecure,
|
||||
Fingerprint: config.Fingerprint,
|
||||
RealityServerAddress: config.RealityServerAddr,
|
||||
RealityServerPort: config.RealityServerPort,
|
||||
RealityPrivateKey: config.RealityPrivateKey,
|
||||
RealityPublicKey: config.RealityPublicKey,
|
||||
RealityShortId: config.RealityShortId,
|
||||
},
|
||||
}
|
||||
case Hysteria2:
|
||||
result = Hysteria2Node{
|
||||
Port: config.Port,
|
||||
HopPorts: config.HopPorts,
|
||||
HopInterval: config.HopInterval,
|
||||
ObfsPassword: config.ObfsPassword,
|
||||
SecurityConfig: &SecurityConfig{
|
||||
SNI: config.SNI,
|
||||
AllowInsecure: &config.AllowInsecure,
|
||||
Fingerprint: config.Fingerprint,
|
||||
RealityServerAddress: config.RealityServerAddr,
|
||||
RealityServerPort: config.RealityServerPort,
|
||||
RealityPrivateKey: config.RealityPrivateKey,
|
||||
RealityPublicKey: config.RealityPublicKey,
|
||||
RealityShortId: config.RealityShortId,
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
var resp map[string]interface{}
|
||||
s, _ := json.Marshal(result)
|
||||
_ = json.Unmarshal(s, &resp)
|
||||
return resp
|
||||
}
|
||||
|
||||
@ -3,8 +3,10 @@ package server
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/perfect-panel/server/internal/model/node"
|
||||
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
@ -33,28 +35,46 @@ func NewGetServerUserListLogic(ctx *gin.Context, svcCtx *svc.ServiceContext) *Ge
|
||||
func (l *GetServerUserListLogic) GetServerUserList(req *types.GetServerUserListRequest) (resp *types.GetServerUserListResponse, err error) {
|
||||
cacheKey := fmt.Sprintf("%s%d", config.ServerUserListCacheKey, req.ServerId)
|
||||
cache, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result()
|
||||
if err == nil {
|
||||
if cache != "" {
|
||||
etag := tool.GenerateETag([]byte(cache))
|
||||
resp := &types.GetServerUserListResponse{}
|
||||
// Check If-None-Match header
|
||||
if match := l.ctx.GetHeader("If-None-Match"); match == etag {
|
||||
return nil, xerr.StatusNotModified
|
||||
}
|
||||
l.ctx.Header("ETag", etag)
|
||||
err = json.Unmarshal([]byte(cache), resp)
|
||||
if err != nil {
|
||||
l.Errorw("[ServerUserListCacheKey] json unmarshal error", logger.Field("error", err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
if cache != "" {
|
||||
etag := tool.GenerateETag([]byte(cache))
|
||||
resp = &types.GetServerUserListResponse{}
|
||||
// Check If-None-Match header
|
||||
if match := l.ctx.GetHeader("If-None-Match"); match == etag {
|
||||
return nil, xerr.StatusNotModified
|
||||
}
|
||||
l.ctx.Header("ETag", etag)
|
||||
err = json.Unmarshal([]byte(cache), resp)
|
||||
if err != nil {
|
||||
l.Errorw("[ServerUserListCacheKey] json unmarshal error", logger.Field("error", err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
server, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.ServerId)
|
||||
server, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subs, err := l.svcCtx.SubscribeModel.QuerySubscribeIdsByServerIdAndServerGroupId(l.ctx, server.Id, server.GroupId)
|
||||
|
||||
_, nodes, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{
|
||||
Page: 1,
|
||||
Size: 1000,
|
||||
ServerId: []int64{server.Id},
|
||||
Protocol: req.Protocol,
|
||||
})
|
||||
if err != nil {
|
||||
l.Errorw("FilterNodeList error", logger.Field("error", err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
var nodeTag []string
|
||||
var nodeIds []int64
|
||||
for _, n := range nodes {
|
||||
nodeIds = append(nodeIds, n.Id)
|
||||
if n.Tags != "" {
|
||||
nodeTag = append(nodeTag, strings.Split(n.Tags, ",")...)
|
||||
}
|
||||
}
|
||||
|
||||
subs, err := l.svcCtx.SubscribeModel.QuerySubscribeIdsByNodeIdAndNodeTag(l.ctx, nodeIds, nodeTag)
|
||||
if err != nil {
|
||||
l.Errorw("QuerySubscribeIdsByServerIdAndServerGroupId error", logger.Field("error", err.Error()))
|
||||
return nil, err
|
||||
@ -76,16 +96,10 @@ func (l *GetServerUserListLogic) GetServerUserList(req *types.GetServerUserListR
|
||||
return nil, err
|
||||
}
|
||||
for _, datum := range data {
|
||||
speedLimit := server.SpeedLimit
|
||||
if (int(sub.SpeedLimit) < server.SpeedLimit && sub.SpeedLimit != 0) ||
|
||||
(int(sub.SpeedLimit) > server.SpeedLimit && sub.SpeedLimit == 0) {
|
||||
speedLimit = int(sub.SpeedLimit)
|
||||
}
|
||||
|
||||
users = append(users, types.ServerUser{
|
||||
Id: datum.Id,
|
||||
UUID: datum.UUID,
|
||||
SpeedLimit: int64(speedLimit),
|
||||
SpeedLimit: sub.SpeedLimit,
|
||||
DeviceLimit: sub.DeviceLimit,
|
||||
})
|
||||
}
|
||||
@ -106,5 +120,9 @@ func (l *GetServerUserListLogic) GetServerUserList(req *types.GetServerUserListR
|
||||
if err != nil {
|
||||
l.Errorw("[ServerUserListCacheKey] redis set error", logger.Field("error", err.Error()))
|
||||
}
|
||||
// Check If-None-Match header
|
||||
if match := l.ctx.GetHeader("If-None-Match"); match == etag {
|
||||
return nil, xerr.StatusNotModified
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ func (l *PushOnlineUsersLogic) PushOnlineUsers(req *types.OnlineUsersRequest) er
|
||||
}
|
||||
|
||||
// Find server info
|
||||
_, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.ServerId)
|
||||
_, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerId)
|
||||
if err != nil {
|
||||
l.Errorw("[PushOnlineUsers] FindOne error", logger.Field("error", err))
|
||||
return fmt.Errorf("server not found: %w", err)
|
||||
|
||||
@ -27,7 +27,7 @@ func NewServerPushStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *
|
||||
|
||||
func (l *ServerPushStatusLogic) ServerPushStatus(req *types.ServerPushStatusRequest) error {
|
||||
// Find server info
|
||||
serverInfo, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.ServerId)
|
||||
serverInfo, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerId)
|
||||
if err != nil || serverInfo.Id <= 0 {
|
||||
l.Errorw("[PushOnlineUsers] FindOne error", logger.Field("error", err))
|
||||
return errors.New("server not found")
|
||||
|
||||
@ -32,7 +32,7 @@ func NewServerPushUserTrafficLogic(ctx context.Context, svcCtx *svc.ServiceConte
|
||||
|
||||
func (l *ServerPushUserTrafficLogic) ServerPushUserTraffic(req *types.ServerPushUserTrafficRequest) error {
|
||||
// Find server info
|
||||
serverInfo, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.ServerId)
|
||||
serverInfo, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerId)
|
||||
if err != nil {
|
||||
l.Errorw("[PushOnlineUsers] FindOne error", logger.Field("error", err))
|
||||
return errors.New("server not found")
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
"github.com/perfect-panel/server/adapter"
|
||||
"github.com/perfect-panel/server/internal/model/client"
|
||||
"github.com/perfect-panel/server/internal/model/log"
|
||||
"github.com/perfect-panel/server/internal/model/server"
|
||||
"github.com/perfect-panel/server/internal/model/node"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/user"
|
||||
|
||||
@ -196,7 +196,7 @@ func (l *SubscribeLogic) logSubscribeActivity(subscribeStatus bool, userSub *use
|
||||
}
|
||||
}
|
||||
|
||||
func (l *SubscribeLogic) getServers(userSub *user.Subscribe) ([]*server.Server, error) {
|
||||
func (l *SubscribeLogic) getServers(userSub *user.Subscribe) ([]*node.Node, error) {
|
||||
if l.isSubscriptionExpired(userSub) {
|
||||
return l.createExpiredServers(), nil
|
||||
}
|
||||
@ -207,49 +207,61 @@ func (l *SubscribeLogic) getServers(userSub *user.Subscribe) ([]*server.Server,
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe details error: %v", err.Error())
|
||||
}
|
||||
|
||||
serverIds := tool.StringToInt64Slice(subDetails.Server)
|
||||
groupIds := tool.StringToInt64Slice(subDetails.ServerGroup)
|
||||
nodeIds := tool.StringToInt64Slice(subDetails.Nodes)
|
||||
tags := strings.Split(subDetails.NodeTags, ",")
|
||||
|
||||
l.Debugf("[Generate Subscribe]serverIds: %v, groupIds: %v", serverIds, groupIds)
|
||||
l.Debugf("[Generate Subscribe]nodes: %v, NodeTags: %v", nodeIds, tags)
|
||||
|
||||
servers, err := l.svc.ServerModel.FindServerDetailByGroupIdsAndIds(l.ctx.Request.Context(), groupIds, serverIds)
|
||||
_, nodes, err := l.svc.NodeModel.FilterNodeList(l.ctx.Request.Context(), &node.FilterNodeParams{
|
||||
Page: 1,
|
||||
Size: 1000,
|
||||
ServerId: nodeIds,
|
||||
Tag: tags,
|
||||
Preload: true,
|
||||
})
|
||||
|
||||
l.Debugf("[Query Subscribe]found servers: %v", len(servers))
|
||||
l.Debugf("[Query Subscribe]found servers: %v", len(nodes))
|
||||
|
||||
if err != nil {
|
||||
l.Errorw("[Generate Subscribe]find server details error: %v", logger.Field("error", err.Error()))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find server details error: %v", err.Error())
|
||||
}
|
||||
logger.Debugf("[Generate Subscribe]found servers: %v", len(servers))
|
||||
return servers, nil
|
||||
logger.Debugf("[Generate Subscribe]found servers: %v", len(nodes))
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (l *SubscribeLogic) isSubscriptionExpired(userSub *user.Subscribe) bool {
|
||||
return userSub.ExpireTime.Unix() < time.Now().Unix() && userSub.ExpireTime.Unix() != 0
|
||||
}
|
||||
|
||||
func (l *SubscribeLogic) createExpiredServers() []*server.Server {
|
||||
func (l *SubscribeLogic) createExpiredServers() []*node.Node {
|
||||
enable := true
|
||||
host := l.getFirstHostLine()
|
||||
|
||||
return []*server.Server{
|
||||
return []*node.Node{
|
||||
{
|
||||
Name: "Subscribe Expired",
|
||||
ServerAddr: "127.0.0.1",
|
||||
RelayMode: "none",
|
||||
Protocol: "shadowsocks",
|
||||
Config: "{\"method\":\"aes-256-gcm\",\"port\":1}",
|
||||
Enable: &enable,
|
||||
Sort: 0,
|
||||
Name: "Subscribe Expired",
|
||||
Tags: "",
|
||||
Port: 18080,
|
||||
Address: "127.0.0.1",
|
||||
Server: &node.Server{
|
||||
Name: "Subscribe Expired",
|
||||
Protocols: "[{\"type:\"\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]",
|
||||
},
|
||||
Protocol: "shadowsocks",
|
||||
Enabled: &enable,
|
||||
},
|
||||
{
|
||||
Name: host,
|
||||
ServerAddr: "127.0.0.1",
|
||||
RelayMode: "none",
|
||||
Protocol: "shadowsocks",
|
||||
Config: "{\"method\":\"aes-256-gcm\",\"port\":1}",
|
||||
Enable: &enable,
|
||||
Sort: 0,
|
||||
Name: host,
|
||||
Tags: "",
|
||||
Port: 18080,
|
||||
Address: "127.0.0.1",
|
||||
Server: &node.Server{
|
||||
Name: "Subscribe Expired",
|
||||
Protocols: "[{\"type:\"\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]",
|
||||
},
|
||||
Protocol: "shadowsocks",
|
||||
Enabled: &enable,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package node
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@ -35,19 +36,21 @@ type (
|
||||
}
|
||||
defaultServerModel struct {
|
||||
*gorm.DB
|
||||
Cache *redis.Client
|
||||
}
|
||||
)
|
||||
|
||||
func newServerModel(db *gorm.DB) *defaultServerModel {
|
||||
func newServerModel(db *gorm.DB, cache *redis.Client) *defaultServerModel {
|
||||
return &defaultServerModel{
|
||||
DB: db,
|
||||
DB: db,
|
||||
Cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
// NewModel returns a model for the database table.
|
||||
func NewModel(conn *gorm.DB) Model {
|
||||
func NewModel(conn *gorm.DB, cache *redis.Client) Model {
|
||||
return &customServerModel{
|
||||
defaultServerModel: newServerModel(conn),
|
||||
defaultServerModel: newServerModel(conn, cache),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,26 @@
|
||||
package node
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/perfect-panel/server/pkg/tool"
|
||||
)
|
||||
|
||||
type customServerLogicModel interface {
|
||||
FilterServerList(ctx context.Context, params *FilterParams) (int64, []*Server, error)
|
||||
FilterNodeList(ctx context.Context, params *FilterParams) (int64, []*Node, error)
|
||||
FilterNodeList(ctx context.Context, params *FilterNodeParams) (int64, []*Node, error)
|
||||
ClearNodeCache(ctx context.Context, params *FilterNodeParams) error
|
||||
}
|
||||
|
||||
const (
|
||||
// ServerUserListCacheKey Server User List Cache Key
|
||||
ServerUserListCacheKey = "server:user_list:id:"
|
||||
|
||||
// ServerConfigCacheKey Server Config Cache Key
|
||||
ServerConfigCacheKey = "server:config:id:"
|
||||
)
|
||||
|
||||
// FilterParams Filter Server Params
|
||||
type FilterParams struct {
|
||||
Page int
|
||||
@ -14,6 +28,16 @@ type FilterParams struct {
|
||||
Search string
|
||||
}
|
||||
|
||||
type FilterNodeParams struct {
|
||||
Page int // Page Number
|
||||
Size int // Page Size
|
||||
ServerId []int64 // Server IDs
|
||||
Tag []string // Tags
|
||||
Search string // Search Address or Name
|
||||
Protocol string // Protocol
|
||||
Preload bool // Preload Server
|
||||
}
|
||||
|
||||
// FilterServerList Filter Server List
|
||||
func (m *customServerModel) FilterServerList(ctx context.Context, params *FilterParams) (int64, []*Server, error) {
|
||||
var servers []*Server
|
||||
@ -29,17 +53,18 @@ func (m *customServerModel) FilterServerList(ctx context.Context, params *Filter
|
||||
s := "%" + params.Search + "%"
|
||||
query = query.Where("`name` LIKE ? OR `address` LIKE ?", s, s)
|
||||
}
|
||||
|
||||
err := query.Count(&total).Limit(params.Size).Offset((params.Page - 1) * params.Size).Find(&servers).Error
|
||||
return total, servers, err
|
||||
}
|
||||
|
||||
// FilterNodeList Filter Node List
|
||||
func (m *customServerModel) FilterNodeList(ctx context.Context, params *FilterParams) (int64, []*Node, error) {
|
||||
func (m *customServerModel) FilterNodeList(ctx context.Context, params *FilterNodeParams) (int64, []*Node, error) {
|
||||
var nodes []*Node
|
||||
var total int64
|
||||
query := m.WithContext(ctx).Model(&Node{})
|
||||
if params == nil {
|
||||
params = &FilterParams{
|
||||
params = &FilterNodeParams{
|
||||
Page: 1,
|
||||
Size: 10,
|
||||
}
|
||||
@ -48,6 +73,40 @@ func (m *customServerModel) FilterNodeList(ctx context.Context, params *FilterPa
|
||||
s := "%" + params.Search + "%"
|
||||
query = query.Where("`name` LIKE ? OR `address` LIKE ? OR `tags` LIKE ? OR `port` LIKE ? ", s, s, s, s)
|
||||
}
|
||||
if len(params.ServerId) > 0 {
|
||||
query = query.Where("server_id IN ?", params.ServerId)
|
||||
}
|
||||
if len(params.Tag) > 0 {
|
||||
for _, tag := range params.Tag {
|
||||
query = query.Or("FIND_IN_SET(?,tags)", tag)
|
||||
}
|
||||
}
|
||||
if params.Protocol != "" {
|
||||
query = query.Where("protocol = ?", params.Protocol)
|
||||
}
|
||||
|
||||
if params.Preload {
|
||||
query = query.Preload("Server")
|
||||
}
|
||||
|
||||
err := query.Count(&total).Limit(params.Size).Offset((params.Page - 1) * params.Size).Find(&nodes).Error
|
||||
return total, nodes, err
|
||||
}
|
||||
|
||||
// ClearNodeCache Clear Node Cache
|
||||
func (m *customServerModel) ClearNodeCache(ctx context.Context, params *FilterNodeParams) error {
|
||||
_, nodes, err := m.FilterNodeList(ctx, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var cacheKeys []string
|
||||
for _, node := range nodes {
|
||||
cacheKeys = append(cacheKeys, fmt.Sprintf("%s%d", ServerUserListCacheKey, node.ServerId), fmt.Sprintf("%s%d", ServerConfigCacheKey, node.ServerId))
|
||||
}
|
||||
|
||||
if len(cacheKeys) > 0 {
|
||||
cacheKeys = tool.RemoveDuplicateElements(cacheKeys...)
|
||||
return m.Cache.Del(ctx, cacheKeys...).Err()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ type Node struct {
|
||||
Port uint16 `gorm:"not null;default:0;comment:Connect Port"`
|
||||
Address string `gorm:"type:varchar(255);not null;default:'';comment:Connect Address"`
|
||||
ServerId int64 `gorm:"not null;default:0;comment:Server ID"`
|
||||
Server *Server `gorm:"foreignKey:ServerId;references:Id"`
|
||||
Protocol string `gorm:"type:varchar(100);not null;default:'';comment:Protocol"`
|
||||
Enabled *bool `gorm:"type:boolean;not null;default:true;comment:Enabled"`
|
||||
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
|
||||
|
||||
@ -8,17 +8,17 @@ import (
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
Id int64 `gorm:"primary_key"`
|
||||
Name string `gorm:"type:varchar(100);not null;default:'';comment:Server Name"`
|
||||
Country string `gorm:"type:varchar(128);not null;default:'';comment:Country"`
|
||||
City string `gorm:"type:varchar(128);not null;default:'';comment:City"`
|
||||
Ratio float32 `gorm:"type:DECIMAL(4,2);not null;default:0;comment:Traffic Ratio"`
|
||||
Address string `gorm:"type:varchar(100);not null;default:'';comment:Server Address"`
|
||||
Sort int `gorm:"type:int;not null;default:0;comment:Sort"`
|
||||
Protocols string `gorm:"type:text;default:null;comment:Protocol"`
|
||||
LastReportedAt time.Time `gorm:"comment:Last Reported Time"`
|
||||
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
|
||||
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
||||
Id int64 `gorm:"primary_key"`
|
||||
Name string `gorm:"type:varchar(100);not null;default:'';comment:Server Name"`
|
||||
Country string `gorm:"type:varchar(128);not null;default:'';comment:Country"`
|
||||
City string `gorm:"type:varchar(128);not null;default:'';comment:City"`
|
||||
Ratio float32 `gorm:"type:DECIMAL(4,2);not null;default:0;comment:Traffic Ratio"`
|
||||
Address string `gorm:"type:varchar(100);not null;default:'';comment:Server Address"`
|
||||
Sort int `gorm:"type:int;not null;default:0;comment:Sort"`
|
||||
Protocols string `gorm:"type:text;default:null;comment:Protocol"`
|
||||
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 {
|
||||
|
||||
@ -4,11 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
"github.com/perfect-panel/server/internal/model/server"
|
||||
"github.com/perfect-panel/server/pkg/cache"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
@ -61,42 +57,7 @@ func (m *defaultSubscribeModel) getCacheKeys(data *Subscribe) []string {
|
||||
if data == nil {
|
||||
return []string{}
|
||||
}
|
||||
SubscribeIdKey := fmt.Sprintf("%s%v", cacheSubscribeIdPrefix, data.Id)
|
||||
serverKey := make([]string, 0)
|
||||
if data.Server != "" {
|
||||
cacheKey := strings.Split(data.Server, ",")
|
||||
for _, v := range cacheKey {
|
||||
if v != "" {
|
||||
serverKey = append(serverKey, fmt.Sprintf("%s%v", config.ServerUserListCacheKey, v))
|
||||
}
|
||||
}
|
||||
}
|
||||
// Temporary solution waiting for refactoring
|
||||
if data.ServerGroup != "" {
|
||||
cacheKey := strings.Split(data.ServerGroup, ",")
|
||||
groupIds := make([]int64, 0)
|
||||
for _, v := range cacheKey {
|
||||
if v != "" {
|
||||
id, _ := strconv.ParseInt(v, 10, 64)
|
||||
if id > 0 {
|
||||
groupIds = append(groupIds, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
var ids []int64
|
||||
_ = m.Transaction(context.Background(), func(tx *gorm.DB) error {
|
||||
return tx.Model(&server.Server{}).Where("group_id IN ?", groupIds).Pluck("id", &ids).Error
|
||||
})
|
||||
for _, id := range ids {
|
||||
serverKey = append(serverKey, fmt.Sprintf("%s%v", config.ServerUserListCacheKey, id))
|
||||
}
|
||||
}
|
||||
|
||||
cacheKeys := []string{SubscribeIdKey}
|
||||
if len(serverKey) > 0 {
|
||||
cacheKeys = append(cacheKeys, serverKey...)
|
||||
}
|
||||
return cacheKeys
|
||||
return []string{fmt.Sprintf("%s%v", cacheSubscribeIdPrefix, data.Id)}
|
||||
}
|
||||
|
||||
func (m *defaultSubscribeModel) Insert(ctx context.Context, data *Subscribe, tx ...*gorm.DB) error {
|
||||
|
||||
@ -7,32 +7,11 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// type Details struct {
|
||||
// Id int64 `gorm:"primaryKey"`
|
||||
// Name string `gorm:"type:varchar(255);not null;default:'';comment:Subscribe Name"`
|
||||
// Description string `gorm:"type:text;comment:Subscribe Description"`
|
||||
// UnitPrice int64 `gorm:"type:int;not null;default:0;comment:Unit Price"`
|
||||
// UnitTime string `gorm:"type:varchar(255);not null;default:'';comment:Unit Time"`
|
||||
// Discount string `gorm:"type:text;comment:Discount"`
|
||||
// Replacement int64 `gorm:"type:int;not null;default:0;comment:Replacement"`
|
||||
// Inventory int64 `gorm:"type:int;not null;default:0;comment:Inventory"`
|
||||
// Traffic int64 `gorm:"type:int;not null;default:0;comment:Traffic"`
|
||||
// SpeedLimit int64 `gorm:"type:int;not null;default:0;comment:Speed Limit"`
|
||||
// DeviceLimit int64 `gorm:"type:int;not null;default:0;comment:Device Limit"`
|
||||
// GroupId int64 `gorm:"type:bigint;comment:Group Id"`
|
||||
// Quota int64 `gorm:"type:int;not null;default:0;comment:Quota"`
|
||||
// Show *bool `gorm:"type:tinyint(1);not null;default:0;comment:Show"`
|
||||
// Sell *bool `gorm:"type:tinyint(1);not null;default:0;comment:Sell"`
|
||||
// DeductionRatio int64 `gorm:"type:int;default:0;comment:Deduction Ratio"`
|
||||
// PurchaseWithDiscount bool `gorm:"type:tinyint(1);default:0;comment:PurchaseWithDiscount"`
|
||||
// ResetCycle int64 `gorm:"type:int;default:0;comment:Reset Cycle"`
|
||||
// RenewalReset bool `gorm:"type:tinyint(1);default:0;comment:Renew Reset"`
|
||||
// }
|
||||
type customSubscribeLogicModel interface {
|
||||
QuerySubscribeListByPage(ctx context.Context, page, size int, group int64, search string) (total int64, list []*Subscribe, err error)
|
||||
QuerySubscribeList(ctx context.Context) ([]*Subscribe, error)
|
||||
QuerySubscribeListByShow(ctx context.Context) ([]*Subscribe, error)
|
||||
QuerySubscribeIdsByServerIdAndServerGroupId(ctx context.Context, serverId, serverGroupId int64) ([]*Subscribe, error)
|
||||
QuerySubscribeIdsByNodeIdAndNodeTag(ctx context.Context, node []int64, tags []string) ([]*Subscribe, error)
|
||||
QuerySubscribeMinSortByIds(ctx context.Context, ids []int64) (int64, error)
|
||||
QuerySubscribeListByIds(ctx context.Context, ids []int64) ([]*Subscribe, error)
|
||||
ClearCache(ctx context.Context, id ...int64) error
|
||||
@ -75,10 +54,24 @@ func (m *customSubscribeModel) QuerySubscribeList(ctx context.Context) ([]*Subsc
|
||||
return list, err
|
||||
}
|
||||
|
||||
func (m *customSubscribeModel) QuerySubscribeIdsByServerIdAndServerGroupId(ctx context.Context, serverId, serverGroupId int64) ([]*Subscribe, error) {
|
||||
func (m *customSubscribeModel) QuerySubscribeIdsByNodeIdAndNodeTag(ctx context.Context, node []int64, tags []string) ([]*Subscribe, error) {
|
||||
var data []*Subscribe
|
||||
err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error {
|
||||
return conn.Model(&Subscribe{}).Where("FIND_IN_SET(?, server)", serverId).Or("FIND_IN_SET(?, server_group)", serverGroupId).Find(v).Error
|
||||
db := conn.Model(&Subscribe{})
|
||||
if len(node) > 0 {
|
||||
for _, id := range node {
|
||||
db = db.Or("FIND_IN_SET(?, nodes)", id)
|
||||
}
|
||||
}
|
||||
|
||||
if len(tags) > 0 {
|
||||
// 拼接多个 tag 条件
|
||||
for _, t := range tags {
|
||||
db = db.Or("FIND_IN_SET(?, node_tags)", t)
|
||||
}
|
||||
}
|
||||
|
||||
return db.Find(v).Error
|
||||
})
|
||||
return data, err
|
||||
}
|
||||
|
||||
@ -20,8 +20,8 @@ type Subscribe struct {
|
||||
DeviceLimit int64 `gorm:"type:int;not null;default:0;comment:Device Limit"`
|
||||
Quota int64 `gorm:"type:int;not null;default:0;comment:Quota"`
|
||||
GroupId int64 `gorm:"type:bigint;comment:Group Id"`
|
||||
ServerGroup string `gorm:"type:varchar(255);comment:Server Group"`
|
||||
Server string `gorm:"type:varchar(255);comment:Server"`
|
||||
Nodes string `gorm:"type:varchar(255);comment:Node Ids"`
|
||||
NodeTags string `gorm:"type:varchar(255);comment:Node Tags"`
|
||||
Show *bool `gorm:"type:tinyint(1);not null;default:0;comment:Show portal page"`
|
||||
Sell *bool `gorm:"type:tinyint(1);not null;default:0;comment:Sell"`
|
||||
Sort int64 `gorm:"type:int;not null;default:0;comment:Sort"`
|
||||
|
||||
@ -18,7 +18,6 @@ import (
|
||||
"github.com/perfect-panel/server/internal/model/log"
|
||||
"github.com/perfect-panel/server/internal/model/order"
|
||||
"github.com/perfect-panel/server/internal/model/payment"
|
||||
"github.com/perfect-panel/server/internal/model/server"
|
||||
"github.com/perfect-panel/server/internal/model/subscribe"
|
||||
"github.com/perfect-panel/server/internal/model/subscribeType"
|
||||
"github.com/perfect-panel/server/internal/model/system"
|
||||
@ -36,20 +35,20 @@ import (
|
||||
)
|
||||
|
||||
type ServiceContext struct {
|
||||
DB *gorm.DB
|
||||
Redis *redis.Client
|
||||
Config config.Config
|
||||
Queue *asynq.Client
|
||||
NodeCache *cache.NodeCacheClient
|
||||
AuthModel auth.Model
|
||||
AdsModel ads.Model
|
||||
LogModel log.Model
|
||||
NodeModel node.Model
|
||||
UserModel user.Model
|
||||
OrderModel order.Model
|
||||
ClientModel client.Model
|
||||
TicketModel ticket.Model
|
||||
ServerModel server.Model
|
||||
DB *gorm.DB
|
||||
Redis *redis.Client
|
||||
Config config.Config
|
||||
Queue *asynq.Client
|
||||
NodeCache *cache.NodeCacheClient
|
||||
AuthModel auth.Model
|
||||
AdsModel ads.Model
|
||||
LogModel log.Model
|
||||
NodeModel node.Model
|
||||
UserModel user.Model
|
||||
OrderModel order.Model
|
||||
ClientModel client.Model
|
||||
TicketModel ticket.Model
|
||||
//ServerModel server.Model
|
||||
SystemModel system.Model
|
||||
CouponModel coupon.Model
|
||||
PaymentModel payment.Model
|
||||
@ -87,21 +86,21 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
||||
}
|
||||
authLimiter := limit.NewPeriodLimit(86400, 15, rds, config.SendCountLimitKeyPrefix, limit.Align())
|
||||
srv := &ServiceContext{
|
||||
DB: db,
|
||||
Redis: rds,
|
||||
Config: c,
|
||||
Queue: NewAsynqClient(c),
|
||||
NodeCache: cache.NewNodeCacheClient(rds),
|
||||
AuthLimiter: authLimiter,
|
||||
AdsModel: ads.NewModel(db, rds),
|
||||
LogModel: log.NewModel(db),
|
||||
NodeModel: node.NewModel(db),
|
||||
AuthModel: auth.NewModel(db, rds),
|
||||
UserModel: user.NewModel(db, rds),
|
||||
OrderModel: order.NewModel(db, rds),
|
||||
ClientModel: client.NewSubscribeApplicationModel(db),
|
||||
TicketModel: ticket.NewModel(db, rds),
|
||||
ServerModel: server.NewModel(db, rds),
|
||||
DB: db,
|
||||
Redis: rds,
|
||||
Config: c,
|
||||
Queue: NewAsynqClient(c),
|
||||
NodeCache: cache.NewNodeCacheClient(rds),
|
||||
AuthLimiter: authLimiter,
|
||||
AdsModel: ads.NewModel(db, rds),
|
||||
LogModel: log.NewModel(db),
|
||||
NodeModel: node.NewModel(db, rds),
|
||||
AuthModel: auth.NewModel(db, rds),
|
||||
UserModel: user.NewModel(db, rds),
|
||||
OrderModel: order.NewModel(db, rds),
|
||||
ClientModel: client.NewSubscribeApplicationModel(db),
|
||||
TicketModel: ticket.NewModel(db, rds),
|
||||
//ServerModel: server.NewModel(db, rds),
|
||||
SystemModel: system.NewModel(db, rds),
|
||||
CouponModel: coupon.NewModel(db, rds),
|
||||
PaymentModel: payment.NewModel(db, rds),
|
||||
|
||||
@ -384,8 +384,8 @@ type CreateSubscribeRequest struct {
|
||||
DeviceLimit int64 `json:"device_limit"`
|
||||
Quota int64 `json:"quota"`
|
||||
GroupId int64 `json:"group_id"`
|
||||
ServerGroup []int64 `json:"server_group"`
|
||||
Server []int64 `json:"server"`
|
||||
Nodes []int64 `json:"nodes"`
|
||||
NodeTags []string `json:"node_tags"`
|
||||
Show *bool `json:"show"`
|
||||
Sell *bool `json:"sell"`
|
||||
DeductionRatio int64 `json:"deduction_ratio"`
|
||||
@ -1117,6 +1117,10 @@ type GoogleLoginCallbackRequest struct {
|
||||
State string `form:"state"`
|
||||
}
|
||||
|
||||
type HasMigrateSeverNodeResponse struct {
|
||||
HasMigrate bool `json:"has_migrate"`
|
||||
}
|
||||
|
||||
type Hysteria2 struct {
|
||||
Port int `json:"port" validate:"required"`
|
||||
HopPorts string `json:"hop_ports" validate:"required"`
|
||||
@ -1163,6 +1167,12 @@ type MessageLog struct {
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
}
|
||||
|
||||
type MigrateServerNodeResponse struct {
|
||||
Succee uint64 `json:"succee"`
|
||||
Fail uint64 `json:"fail"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
type MobileAuthenticateConfig struct {
|
||||
Enable bool `json:"enable"`
|
||||
EnableWhitelist bool `json:"enable_whitelist"`
|
||||
@ -1825,8 +1835,8 @@ type Subscribe struct {
|
||||
DeviceLimit int64 `json:"device_limit"`
|
||||
Quota int64 `json:"quota"`
|
||||
GroupId int64 `json:"group_id"`
|
||||
ServerGroup []int64 `json:"server_group"`
|
||||
Server []int64 `json:"server"`
|
||||
Nodes []int64 `json:"nodes"`
|
||||
NodeTags []string `json:"node_tags"`
|
||||
Show bool `json:"show"`
|
||||
Sell bool `json:"sell"`
|
||||
Sort int64 `json:"sort"`
|
||||
@ -2195,8 +2205,8 @@ type UpdateSubscribeRequest struct {
|
||||
DeviceLimit int64 `json:"device_limit"`
|
||||
Quota int64 `json:"quota"`
|
||||
GroupId int64 `json:"group_id"`
|
||||
ServerGroup []int64 `json:"server_group"`
|
||||
Server []int64 `json:"server"`
|
||||
Nodes []int64 `json:"nodes"`
|
||||
NodeTags []string `json:"node_tags"`
|
||||
Show *bool `json:"show"`
|
||||
Sell *bool `json:"sell"`
|
||||
Sort int64 `json:"sort"`
|
||||
|
||||
@ -59,6 +59,7 @@ func Int64SliceToString(intSlice []int64) string {
|
||||
|
||||
// string slice to string
|
||||
func StringSliceToString(stringSlice []string) string {
|
||||
stringSlice = RemoveDuplicateElements(stringSlice...)
|
||||
return strings.Join(stringSlice, ",")
|
||||
}
|
||||
|
||||
|
||||
@ -2,14 +2,9 @@ package countrylogic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/pkg/ip"
|
||||
"github.com/perfect-panel/server/queue/types"
|
||||
)
|
||||
|
||||
type GetNodeCountryLogic struct {
|
||||
@ -22,39 +17,6 @@ func NewGetNodeCountryLogic(svcCtx *svc.ServiceContext) *GetNodeCountryLogic {
|
||||
}
|
||||
}
|
||||
func (l *GetNodeCountryLogic) ProcessTask(ctx context.Context, task *asynq.Task) error {
|
||||
var payload types.GetNodeCountry
|
||||
if err := json.Unmarshal(task.Payload(), &payload); err != nil {
|
||||
logger.WithContext(ctx).Error("[GetNodeCountryLogic] Unmarshal payload failed",
|
||||
logger.Field("error", err.Error()),
|
||||
logger.Field("payload", task.Payload()),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
serverAddr := payload.ServerAddr
|
||||
resp, err := ip.GetRegionByIp(serverAddr)
|
||||
if err != nil {
|
||||
logger.WithContext(ctx).Error("[GetNodeCountryLogic] ", logger.Field("error", err.Error()), logger.Field("serverAddr", serverAddr))
|
||||
return nil
|
||||
}
|
||||
|
||||
servers, err := l.svcCtx.ServerModel.FindNodeByServerAddrAndProtocol(ctx, payload.ServerAddr, payload.Protocol)
|
||||
if err != nil {
|
||||
logger.WithContext(ctx).Error("[GetNodeCountryLogic] FindNodeByServerAddrAnd", logger.Field("error", err.Error()), logger.Field("serverAddr", serverAddr))
|
||||
return err
|
||||
}
|
||||
if len(servers) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, ser := range servers {
|
||||
ser.Country = resp.Country
|
||||
ser.City = resp.City
|
||||
ser.Latitude = resp.Latitude
|
||||
ser.Longitude = resp.Longitude
|
||||
err := l.svcCtx.ServerModel.Update(ctx, ser)
|
||||
if err != nil {
|
||||
logger.WithContext(ctx).Error("[GetNodeCountryLogic] ", logger.Field("error", err.Error()), logger.Field("id", ser.Id))
|
||||
}
|
||||
}
|
||||
logger.WithContext(ctx).Info("[GetNodeCountryLogic] ", logger.Field("country", resp.Country), logger.Field("city", resp.Country))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -7,16 +7,17 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/log"
|
||||
"github.com/perfect-panel/server/internal/model/node"
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
"github.com/perfect-panel/server/internal/logic/telegram"
|
||||
"github.com/perfect-panel/server/internal/model/order"
|
||||
"github.com/perfect-panel/server/internal/model/subscribe"
|
||||
@ -429,34 +430,18 @@ func (l *ActivateOrderLogic) calculateCommission(price int64) int64 {
|
||||
|
||||
// clearServerCache clears user list cache for all servers associated with the subscription
|
||||
func (l *ActivateOrderLogic) clearServerCache(ctx context.Context, sub *subscribe.Subscribe) {
|
||||
serverIds := tool.StringToInt64Slice(sub.Server)
|
||||
groupServerIds := l.getServerIdsByGroups(ctx, sub.ServerGroup)
|
||||
allServerIds := append(serverIds, groupServerIds...)
|
||||
nodeIds := tool.StringToInt64Slice(sub.Nodes)
|
||||
tags := strings.Split(sub.NodeTags, ",")
|
||||
|
||||
for _, id := range allServerIds {
|
||||
cacheKey := fmt.Sprintf("%s%d", config.ServerUserListCacheKey, id)
|
||||
if err := l.svc.Redis.Del(ctx, cacheKey).Err(); err != nil {
|
||||
logger.WithContext(ctx).Error("Del server user list cache failed",
|
||||
logger.Field("error", err.Error()),
|
||||
logger.Field("cache_key", cacheKey),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getServerIdsByGroups retrieves server IDs from server groups
|
||||
func (l *ActivateOrderLogic) getServerIdsByGroups(ctx context.Context, serverGroup string) []int64 {
|
||||
data, err := l.svc.ServerModel.FindServerListByGroupIds(ctx, tool.StringToInt64Slice(serverGroup))
|
||||
err := l.svc.NodeModel.ClearNodeCache(ctx, &node.FilterNodeParams{
|
||||
Page: 1,
|
||||
Size: 1000,
|
||||
ServerId: nodeIds,
|
||||
Tag: tags,
|
||||
})
|
||||
if err != nil {
|
||||
logger.WithContext(ctx).Error("Find server list failed", logger.Field("error", err.Error()))
|
||||
return nil
|
||||
logger.WithContext(ctx).Error("[Order Queue] Clear node cache failed", logger.Field("error", err.Error()))
|
||||
}
|
||||
|
||||
serverIds := make([]int64, len(data))
|
||||
for i, item := range data {
|
||||
serverIds[i] = item.Id
|
||||
}
|
||||
return serverIds
|
||||
}
|
||||
|
||||
// Renewal handles subscription renewal including subscription extension,
|
||||
|
||||
@ -73,7 +73,7 @@ func (l *ServerDataLogic) getRanking(ctx context.Context) (top10ServerToday, top
|
||||
if s.ServerId == 0 {
|
||||
continue
|
||||
}
|
||||
serverInfo, err := l.svc.ServerModel.FindOne(ctx, s.ServerId)
|
||||
serverInfo, err := l.svc.NodeModel.FindOneServer(ctx, s.ServerId)
|
||||
if err != nil {
|
||||
logger.Error("[ServerDataLogic] Find server failed", logger.Field("error", err.Error()))
|
||||
continue
|
||||
@ -92,7 +92,7 @@ func (l *ServerDataLogic) getRanking(ctx context.Context) (top10ServerToday, top
|
||||
logger.Error("[ServerDataLogic] Get top servers traffic by day failed", logger.Field("error", err.Error()))
|
||||
} else {
|
||||
for _, s := range serverYesterday {
|
||||
serverInfo, err := l.svc.ServerModel.FindOne(ctx, s.ServerId)
|
||||
serverInfo, err := l.svc.NodeModel.FindOneServer(ctx, s.ServerId)
|
||||
if err != nil {
|
||||
logger.Error("[ServerDataLogic] Find server failed", logger.Field("error", err.Error()))
|
||||
continue
|
||||
|
||||
@ -38,7 +38,7 @@ func (l *TrafficStatisticsLogic) ProcessTask(ctx context.Context, task *asynq.Ta
|
||||
return nil
|
||||
}
|
||||
// query server info
|
||||
serverInfo, err := l.svc.ServerModel.FindOne(ctx, payload.ServerId)
|
||||
serverInfo, err := l.svc.NodeModel.FindOneServer(ctx, payload.ServerId)
|
||||
if err != nil {
|
||||
logger.WithContext(ctx).Error("[TrafficStatistics] Find server info failed",
|
||||
logger.Field("serverId", payload.ServerId),
|
||||
@ -46,23 +46,22 @@ func (l *TrafficStatisticsLogic) ProcessTask(ctx context.Context, task *asynq.Ta
|
||||
)
|
||||
return nil
|
||||
}
|
||||
if serverInfo.TrafficRatio == 0 {
|
||||
logger.WithContext(ctx).Error("[TrafficStatistics] Server log ratio is 0",
|
||||
logger.Field("serverId", payload.ServerId),
|
||||
)
|
||||
return nil
|
||||
var serverRatio float32 = 1.0
|
||||
if serverInfo.Ratio > 0 {
|
||||
serverRatio = serverInfo.Ratio
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
realTimeMultiplier := l.svc.NodeMultiplierManager.GetMultiplier(now)
|
||||
for _, log := range payload.Logs {
|
||||
// update user subscribe with log
|
||||
d := int64(float32(log.Download) * serverInfo.TrafficRatio * realTimeMultiplier)
|
||||
u := int64(float32(log.Upload) * serverInfo.TrafficRatio * realTimeMultiplier)
|
||||
d := int64(float32(log.Download) * serverRatio * realTimeMultiplier)
|
||||
u := int64(float32(log.Upload) * serverRatio * realTimeMultiplier)
|
||||
if err := l.svc.UserModel.UpdateUserSubscribeWithTraffic(ctx, log.SID, d, u); err != nil {
|
||||
logger.WithContext(ctx).Error("[TrafficStatistics] Update user subscribe with log failed",
|
||||
logger.Field("sid", log.SID),
|
||||
logger.Field("download", float32(log.Download)*serverInfo.TrafficRatio),
|
||||
logger.Field("upload", float32(log.Upload)*serverInfo.TrafficRatio),
|
||||
logger.Field("download", float32(log.Download)*serverRatio),
|
||||
logger.Field("upload", float32(log.Upload)*serverRatio),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
continue
|
||||
@ -88,8 +87,8 @@ func (l *TrafficStatisticsLogic) ProcessTask(ctx context.Context, task *asynq.Ta
|
||||
}); err != nil {
|
||||
logger.WithContext(ctx).Error("[TrafficStatistics] Create log log failed",
|
||||
logger.Field("uid", log.SID),
|
||||
logger.Field("download", float32(log.Download)*serverInfo.TrafficRatio),
|
||||
logger.Field("upload", float32(log.Upload)*serverInfo.TrafficRatio),
|
||||
logger.Field("download", float32(log.Download)*serverRatio),
|
||||
logger.Field("upload", float32(log.Upload)*serverRatio),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user