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