feat(server): implement server management handlers and database schema

This commit is contained in:
Chang lue Tsen
2025-08-25 14:06:37 -04:00
parent 1ccbdc18b1
commit ad4f3df74e
108 changed files with 3152 additions and 718 deletions
-8
View File
@@ -39,14 +39,6 @@ const (
AllNodeUploadTrafficCacheKey = "node:all_node_upload_traffic"
// YesterdayTotalTrafficRank 昨日节点总流量排行榜
YesterdayNodeTotalTrafficRank = "node:yesterday_total_traffic_rank"
// YesterdayUploadTrafficRank 昨日节点上传流量排行榜
YesterdayNodeUploadTrafficRank = "node:yesterday_upload_traffic_rank"
// YesterdayDownloadTrafficRank 昨日节点下载流量排行榜
YesterdayNodeDownloadTrafficRank = "node:yesterday_download_traffic_rank"
// YesterdayUserTotalTrafficRank 昨日用户总流量排行榜
YesterdayUserTotalTrafficRank = "node:yesterday_user_total_traffic_rank"
// YesterdayUserUploadTrafficRank 昨日用户上传流量排行榜
YesterdayUserUploadTrafficRank = "node:yesterday_user_upload_traffic_rank"
// YesterdayUserDownloadTrafficRank 昨日用户下载流量排行榜
YesterdayUserDownloadTrafficRank = "node:yesterday_user_download_traffic_rank"
)
+51 -51
View File
@@ -7,20 +7,44 @@ import (
type Type uint8
/*
Log Types:
1X Message Logs
2X Subscription Logs
3X User Logs
4X Traffic Ranking Logs
*/
const (
TypeEmailMessage Type = iota + 1 // Message log
TypeMobileMessage // Mobile message log
TypeSubscribe // Subscription log
TypeSubscribeTraffic // Subscription traffic log
TypeServerTraffic // Server traffic log
TypeLogin // Login log
TypeRegister // Registration log
TypeBalance // Balance log
TypeCommission // Commission log
TypeResetSubscribe // Reset subscription log
TypeGift // Gift log
TypeUserTrafficRank // Top 10 User traffic rank log
TypeServerTrafficRank // Top 10 Server traffic rank log
TypeEmailMessage Type = 10 // Message log
TypeMobileMessage Type = 11 // Mobile message log
TypeSubscribe Type = 20 // Subscription log
TypeSubscribeTraffic Type = 21 // Subscription traffic log
TypeServerTraffic Type = 22 // Server traffic log
TypeResetSubscribe Type = 23 // Reset subscription log
TypeLogin Type = 30 // Login log
TypeRegister Type = 31 // Registration log
TypeBalance Type = 32 // Balance log
TypeCommission Type = 33 // Commission log
TypeGift Type = 34 // Gift log
TypeUserTrafficRank Type = 40 // Top 10 User traffic rank log
TypeServerTrafficRank Type = 41 // Top 10 Server traffic rank log
)
const (
ResetSubscribeTypeAuto uint16 = 231 // Auto reset
ResetSubscribeTypeAdvance uint16 = 232 // Advance reset
ResetSubscribeTypePaid uint16 = 233 // Paid reset
BalanceTypeRecharge uint16 = 321 // Recharge
BalanceTypeWithdraw uint16 = 322 // Withdraw
BalanceTypePayment uint16 = 323 // Payment
BalanceTypeRefund uint16 = 324 // Refund
BalanceTypeReward uint16 = 325 // Reward
CommissionTypePurchase uint16 = 331 // Purchase
CommissionTypeRenewal uint16 = 332 // Renewal
CommissionTypeRefund uint16 = 333 // Gift
GiftTypeIncrease uint16 = 341 // Increase
GiftTypeReduce uint16 = 342 // Reduce
)
// Uint8 converts Type to uint8.
@@ -95,6 +119,7 @@ func (s *Traffic) Unmarshal(data []byte) error {
// Login represents a login log entry.
type Login struct {
Method string `json:"method"`
LoginIP string `json:"login_ip"`
UserAgent string `json:"user_agent"`
Success bool `json:"success"`
@@ -147,10 +172,10 @@ func (r *Register) Unmarshal(data []byte) error {
// Subscribe represents a subscription log entry.
type Subscribe struct {
Token string `json:"token"`
UserAgent string `json:"user_agent"`
ClientIP string `json:"client_ip"`
SubscribeId int64 `json:"subscribe_id"`
Token string `json:"token"`
UserAgent string `json:"user_agent"`
ClientIP string `json:"client_ip"`
UserSubscribeId int64 `json:"user_subscribe_id"`
}
// Marshal implements the json.Marshaler interface for Subscribe.
@@ -170,15 +195,10 @@ func (s *Subscribe) Unmarshal(data []byte) error {
return json.Unmarshal(data, aux)
}
const (
ResetSubscribeTypeAuto uint8 = 1
ResetSubscribeTypeAdvance uint8 = 2
ResetSubscribeTypePaid uint8 = 3
)
// ResetSubscribe represents a reset subscription log entry.
type ResetSubscribe struct {
Type uint8 `json:"type"`
Type uint16 `json:"type"`
UserId int64 `json:"user_id"`
OrderNo string `json:"order_no,omitempty"`
ResetAt int64 `json:"reset_at"`
}
@@ -200,22 +220,13 @@ func (r *ResetSubscribe) Unmarshal(data []byte) error {
return json.Unmarshal(data, aux)
}
const (
BalanceTypeRecharge uint8 = 1 // Recharge
BalanceTypeWithdraw uint8 = 2 // Withdraw
BalanceTypePayment uint8 = 3 // Payment
BalanceTypeRefund uint8 = 4 // Refund
BalanceTypeReward uint8 = 5 // Reward
)
// Balance represents a balance log entry.
type Balance struct {
Id int64 `json:"id"`
Type uint8 `json:"type"`
Amount int64 `json:"amount"`
OrderId int64 `json:"order_id,omitempty"`
Balance int64 `json:"balance"`
Timestamp int64 `json:"timestamp"`
Type uint16 `json:"type"`
Amount int64 `json:"amount"`
OrderId int64 `json:"order_id,omitempty"`
Balance int64 `json:"balance"`
Timestamp int64 `json:"timestamp"`
}
// Marshal implements the json.Marshaler interface for Balance.
@@ -235,15 +246,9 @@ func (b *Balance) Unmarshal(data []byte) error {
return json.Unmarshal(data, aux)
}
const (
CommissionTypePurchase uint8 = 1 // Purchase
CommissionTypeRenewal uint8 = 2 // Renewal
CommissionTypeRefund uint8 = 3 // Gift
)
// Commission represents a commission log entry.
type Commission struct {
Type uint8 `json:"type"`
Type uint16 `json:"type"`
Amount int64 `json:"amount"`
OrderNo string `json:"order_no"`
CreatedAt int64 `json:"created_at"`
@@ -266,14 +271,9 @@ func (c *Commission) Unmarshal(data []byte) error {
return json.Unmarshal(data, aux)
}
const (
GiftTypeIncrease uint8 = 1 // Increase
GiftTypeReduce uint8 = 2 // Reduce
)
// Gift represents a gift log entry.
type Gift struct {
Type uint8 `json:"type"`
Type uint16 `json:"type"`
OrderNo string `json:"order_no"`
SubscribeId int64 `json:"subscribe_id"`
Amount int64 `json:"amount"`
+6
View File
@@ -16,6 +16,7 @@ type FilterParams struct {
Page int
Size int
Type uint8
Data string
Search string
ObjectID int64
}
@@ -43,6 +44,11 @@ func (m *customSystemLogModel) FilterSystemLog(ctx context.Context, filter *Filt
if filter.Type != 0 {
tx = tx.Where("`type` = ?", filter.Type)
}
if filter.Data != "" {
tx = tx.Where("`data` = ?", filter.Data)
}
if filter.ObjectID != 0 {
tx = tx.Where("`object_id` = ?", filter.ObjectID)
}
+127
View File
@@ -0,0 +1,127 @@
package node
import (
"context"
"gorm.io/gorm"
)
var _ Model = (*customServerModel)(nil)
//goland:noinspection GoNameStartsWithPackageName
type (
Model interface {
serverModel
NodeModel
customServerLogicModel
}
serverModel interface {
InsertServer(ctx context.Context, data *Server, tx ...*gorm.DB) error
FindOneServer(ctx context.Context, id int64) (*Server, error)
UpdateServer(ctx context.Context, data *Server, tx ...*gorm.DB) error
DeleteServer(ctx context.Context, id int64, tx ...*gorm.DB) error
Transaction(ctx context.Context, fn func(db *gorm.DB) error) error
}
NodeModel interface {
InsertNode(ctx context.Context, data *Node, tx ...*gorm.DB) error
FindOneNode(ctx context.Context, id int64) (*Node, error)
UpdateNode(ctx context.Context, data *Node, tx ...*gorm.DB) error
DeleteNode(ctx context.Context, id int64, tx ...*gorm.DB) error
}
customServerModel struct {
*defaultServerModel
}
defaultServerModel struct {
*gorm.DB
}
)
func newServerModel(db *gorm.DB) *defaultServerModel {
return &defaultServerModel{
DB: db,
}
}
// NewModel returns a model for the database table.
func NewModel(conn *gorm.DB) Model {
return &customServerModel{
defaultServerModel: newServerModel(conn),
}
}
func (m *defaultServerModel) InsertServer(ctx context.Context, data *Server, tx ...*gorm.DB) error {
db := m.DB
if len(tx) > 0 {
db = tx[0]
}
return db.WithContext(ctx).Create(data).Error
}
func (m *defaultServerModel) FindOneServer(ctx context.Context, id int64) (*Server, error) {
var server Server
err := m.WithContext(ctx).Model(&Server{}).Where("id = ?", id).First(&server).Error
return &server, err
}
func (m *defaultServerModel) UpdateServer(ctx context.Context, data *Server, tx ...*gorm.DB) error {
_, err := m.FindOneServer(ctx, data.Id)
if err != nil {
return err
}
db := m.DB
if len(tx) > 0 {
db = tx[0]
}
return db.WithContext(ctx).Save(data).Error
}
func (m *defaultServerModel) DeleteServer(ctx context.Context, id int64, tx ...*gorm.DB) error {
db := m.DB
if len(tx) > 0 {
db = tx[0]
}
return db.WithContext(ctx).Where("`id` = ?", id).Delete(&Server{}).Error
}
func (m *defaultServerModel) InsertNode(ctx context.Context, data *Node, tx ...*gorm.DB) error {
db := m.DB
if len(tx) > 0 {
db = tx[0]
}
return db.WithContext(ctx).Create(data).Error
}
func (m *defaultServerModel) FindOneNode(ctx context.Context, id int64) (*Node, error) {
var node Node
err := m.WithContext(ctx).Model(&Node{}).Where("id = ?", id).First(&node).Error
return &node, err
}
func (m *defaultServerModel) UpdateNode(ctx context.Context, data *Node, tx ...*gorm.DB) error {
_, err := m.FindOneNode(ctx, data.Id)
if err != nil {
return err
}
db := m.DB
if len(tx) > 0 {
db = tx[0]
}
return db.WithContext(ctx).Save(data).Error
}
func (m *defaultServerModel) DeleteNode(ctx context.Context, id int64, tx ...*gorm.DB) error {
db := m.DB
if len(tx) > 0 {
db = tx[0]
}
return db.WithContext(ctx).Where("`id` = ?", id).Delete(&Node{}).Error
}
func (m *defaultServerModel) Transaction(ctx context.Context, fn func(db *gorm.DB) error) error {
return m.WithContext(ctx).Transaction(fn)
}
+53
View File
@@ -0,0 +1,53 @@
package node
import "context"
type customServerLogicModel interface {
FilterServerList(ctx context.Context, params *FilterParams) (int64, []*Server, error)
FilterNodeList(ctx context.Context, params *FilterParams) (int64, []*Node, error)
}
// FilterParams Filter Server Params
type FilterParams struct {
Page int
Size int
Search string
}
// FilterServerList Filter Server List
func (m *customServerModel) FilterServerList(ctx context.Context, params *FilterParams) (int64, []*Server, error) {
var servers []*Server
var total int64
query := m.WithContext(ctx).Model(&Server{})
if params == nil {
params = &FilterParams{
Page: 1,
Size: 10,
}
}
if params.Search != "" {
s := "%" + params.Search + "%"
query = query.Where("`name` LIKE ? OR `address` LIKE ?", s, s)
}
err := query.Count(&total).Limit(params.Size).Offset((params.Page - 1) * params.Size).Find(&servers).Error
return total, servers, err
}
// FilterNodeList Filter Node List
func (m *customServerModel) FilterNodeList(ctx context.Context, params *FilterParams) (int64, []*Node, error) {
var nodes []*Node
var total int64
query := m.WithContext(ctx).Model(&Node{})
if params == nil {
params = &FilterParams{
Page: 1,
Size: 10,
}
}
if params.Search != "" {
s := "%" + params.Search + "%"
query = query.Where("`name` LIKE ? OR `address` LIKE ? OR `tags` LIKE ? OR `port` LIKE ? ", s, s, s, s)
}
err := query.Count(&total).Limit(params.Size).Offset((params.Page - 1) * params.Size).Find(&nodes).Error
return total, nodes, err
}
+20
View File
@@ -0,0 +1,20 @@
package node
import "time"
type Node struct {
Id int64 `gorm:"primary_key"`
Name string `gorm:"type:varchar(100);not null;default:'';comment:Node Name"`
Tags string `gorm:"type:varchar(255);not null;default:'';comment:Tags"`
Port uint16 `gorm:"not null;default:0;comment:Connect Port"`
Address string `gorm:"type:varchar(255);not null;default:'';comment:Connect Address"`
ServerId int64 `gorm:"not null;default:0;comment:Server ID"`
Protocol string `gorm:"type:varchar(100);not null;default:'';comment:Protocol"`
Enabled *bool `gorm:"type:boolean;not null;default:true;comment:Enabled"`
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
UpdatedAt time.Time `gorm:"comment:Update Time"`
}
func (Node) TableName() string {
return "nodes"
}
+108
View File
@@ -0,0 +1,108 @@
package node
import (
"encoding/json"
"time"
"github.com/pkg/errors"
)
type Server struct {
Id int64 `gorm:"primary_key"`
Name string `gorm:"type:varchar(100);not null;default:'';comment:Server Name"`
Country string `gorm:"type:varchar(128);not null;default:'';comment:Country"`
City string `gorm:"type:varchar(128);not null;default:'';comment:City"`
Ratio float32 `gorm:"type:DECIMAL(4,2);not null;default:0;comment:Traffic Ratio"`
Address string `gorm:"type:varchar(100);not null;default:'';comment:Server Address"`
Sort int `gorm:"type:int;not null;default:0;comment:Sort"`
Protocols string `gorm:"type:text;default:null;comment:Protocol"`
LastReportedAt time.Time `gorm:"comment:Last Reported Time"`
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
UpdatedAt time.Time `gorm:"comment:Update Time"`
}
func (*Server) TableName() string {
return "servers"
}
// MarshalProtocols Marshal server protocols to json
func (m *Server) MarshalProtocols(list []Protocol) error {
var validate = make(map[string]bool)
for _, protocol := range list {
if protocol.Type == "" {
return errors.New("protocol type is required")
}
if _, exists := validate[protocol.Type]; exists {
return errors.New("duplicate protocol type: " + protocol.Type)
}
validate[protocol.Type] = true
}
data, err := json.Marshal(list)
if err != nil {
return err
}
m.Protocols = string(data)
return nil
}
// UnmarshalProtocols Unmarshal server protocols from json
func (m *Server) UnmarshalProtocols() ([]Protocol, error) {
var list []Protocol
if m.Protocols == "" {
return list, nil
}
err := json.Unmarshal([]byte(m.Protocols), &list)
if err != nil {
return nil, err
}
return list, nil
}
type Protocol struct {
Type string `json:"type"`
Port uint16 `json:"port"`
Security string `json:"security,omitempty"`
SNI string `json:"sni,omitempty"`
AllowInsecure bool `json:"allow_insecure,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
RealityServerAddr string `json:"reality_server_addr,omitempty"`
RealityServerPort int `json:"reality_server_port,omitempty"`
RealityPrivateKey string `json:"reality_private_key,omitempty"`
RealityPublicKey string `json:"reality_public_key,omitempty"`
RealityShortId string `json:"reality_short_id,omitempty"`
Transport string `json:"transport,omitempty"`
Host string `json:"host,omitempty"`
Path string `json:"path,omitempty"`
ServiceName string `json:"service_name,omitempty"`
Cipher string `json:"cipher,omitempty"`
ServerKey string `json:"server_key,omitempty"`
Flow string `json:"flow,omitempty"`
HopPorts string `json:"hop_ports,omitempty"`
HopInterval int `json:"hop_interval,omitempty"`
ObfsPassword string `json:"obfs_password,omitempty"`
DisableSNI bool `json:"disable_sni,omitempty"`
ReduceRtt bool `json:"reduce_rtt,omitempty"`
UDPRelayMode string `json:"udp_relay_mode,omitempty"`
CongestionController string `json:"congestion_controller,omitempty"`
}
// Marshal protocol to json
func (m *Protocol) Marshal() ([]byte, error) {
type Alias Protocol
return json.Marshal(&struct {
*Alias
}{
Alias: (*Alias)(m),
})
}
// Unmarshal json to protocol
func (m *Protocol) Unmarshal(data []byte) error {
type Alias Protocol
aux := &struct {
*Alias
}{
Alias: (*Alias)(m),
}
return json.Unmarshal(data, &aux)
}