分组概念
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 7m58s

This commit is contained in:
shanshanzhong 2026-03-19 03:21:47 -07:00
parent e5e9f93f68
commit f703b5089a
15 changed files with 101 additions and 17 deletions

View File

@ -15,10 +15,10 @@ Logger: # 日志配置
Level: debug # 日志级别: debug, info, warn, error, panic, fatal Level: debug # 日志级别: debug, info, warn, error, panic, fatal
MySQL: MySQL:
Addr: 103.150.215.44:3306 # host 网络模式; bridge 模式改为 mysql:3306 Addr: 154.12.35.103:3306 # host 网络模式; bridge 模式改为 mysql:3306
Username: root # MySQL用户名 Username: root # MySQL用户名
Password: jpcV41ppanel # MySQL密码与 .env MYSQL_ROOT_PASSWORD 一致 Password: jpcV41ppanel # MySQL密码与 .env MYSQL_ROOT_PASSWORD 一致
Dbname: hifast # MySQL数据库名 Dbname: ppanel # MySQL数据库名
Config: charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai Config: charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
MaxIdleConns: 10 MaxIdleConns: 10
MaxOpenConns: 100 MaxOpenConns: 100

View File

@ -1,11 +1,16 @@
package auth package auth
import ( import (
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/auth" "github.com/perfect-panel/server/internal/logic/auth"
"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/result" "github.com/perfect-panel/server/pkg/result"
"github.com/perfect-panel/server/pkg/turnstile"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
) )
// User Telephone login // User Telephone login

View File

@ -6,6 +6,7 @@ import (
"time" "time"
commonLogic "github.com/perfect-panel/server/internal/logic/common" commonLogic "github.com/perfect-panel/server/internal/logic/common"
"github.com/perfect-panel/server/internal/model/group"
"github.com/perfect-panel/server/internal/model/node" "github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/model/user" "github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/svc"
@ -117,20 +118,24 @@ func (l *QueryUserSubscribeNodeListLogic) getServers(userSub *user.Subscribe) (u
l.Debugw("[GetServers] Failed to check group enabled", logger.Field("error", err.Error())) l.Debugw("[GetServers] Failed to check group enabled", logger.Field("error", err.Error()))
// Continue with tag-based filtering // Continue with tag-based filtering
} }
nodeIds := tool.StringToInt64Slice(subDetails.Nodes)
tags := normalizeSubscribeNodeTags(subDetails.NodeTags)
isGroupEnabled := (groupEnabled == "true" || groupEnabled == "1") isGroupEnabled := (groupEnabled == "true" || groupEnabled == "1")
enable := true var nodes []*node.Node
if isGroupEnabled {
_, nodes, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{ // Group mode: use group_ids to filter nodes
Page: 1, nodes, err = l.getNodesByGroup(userSub)
Size: 1000, if err != nil {
NodeId: nodeIds, l.Errorw("[GetServers] Failed to get nodes by group", logger.Field("error", err.Error()))
Tag: tags, return nil, err
Enabled: &enable, // Only get enabled nodes }
}) } else {
// Tag mode: use node_ids and tags to filter nodes
nodes, err = l.getNodesByTag(userSub)
if err != nil {
l.Errorw("[GetServers] Failed to get nodes by tag", logger.Field("error", err.Error()))
return nil, err
}
}
// Process nodes and create response // Process nodes and create response
if len(nodes) > 0 { if len(nodes) > 0 {

View File

@ -70,6 +70,9 @@ type Subscribe struct {
NewUserOnly *bool `gorm:"type:tinyint(1);default:0;comment:New user only: allow purchase within 24h of registration, once per user"` NewUserOnly *bool `gorm:"type:tinyint(1);default:0;comment:New user only: allow purchase within 24h of registration, once per user"`
Nodes string `gorm:"type:varchar(255);comment:Node Ids"` Nodes string `gorm:"type:varchar(255);comment:Node Ids"`
NodeTags string `gorm:"type:varchar(255);comment:Node Tags"` NodeTags string `gorm:"type:varchar(255);comment:Node Tags"`
NodeGroupIds JSONInt64Slice `gorm:"type:json;comment:Node Group IDs (JSON array, multiple groups)"`
NodeGroupId int64 `gorm:"default:0;index:idx_node_group_id;comment:Default Node Group ID (single ID)"`
TrafficLimit string `gorm:"type:text;comment:Traffic Limit Rules"`
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"`

View File

@ -561,6 +561,13 @@ type DeleteAccountResponse struct {
Code int64 `json:"code"` Code int64 `json:"code"`
} }
type DailyTrafficStats struct {
Date string `json:"date"`
Upload int64 `json:"upload"`
Download int64 `json:"download"`
Total int64 `json:"total"`
}
type DeleteAdsRequest struct { type DeleteAdsRequest struct {
Id int64 `json:"id"` Id int64 `json:"id"`
} }
@ -681,6 +688,10 @@ type EmailAuthticateConfig struct {
DomainSuffixList string `json:"domain_suffix_list"` DomainSuffixList string `json:"domain_suffix_list"`
} }
type ExportGroupResultRequest struct {
HistoryId *int64 `form:"history_id,omitempty"`
}
type ErrorLogMessage struct { type ErrorLogMessage struct {
Id int64 `json:"id"` Id int64 `json:"id"`
Platform string `json:"platform"` Platform string `json:"platform"`
@ -1114,6 +1125,37 @@ type GetInviteSalesResponse struct {
List []InvitedUserSale `json:"list"` List []InvitedUserSale `json:"list"`
} }
type GetGroupConfigRequest struct {
Keys []string `form:"keys,omitempty"`
}
type GetGroupConfigResponse struct {
Enabled bool `json:"enabled"`
Mode string `json:"mode"`
Config map[string]interface{} `json:"config"`
State RecalculationState `json:"state"`
}
type GetGroupHistoryDetailRequest struct {
Id int64 `form:"id" validate:"required"`
}
type GetGroupHistoryDetailResponse struct {
GroupHistoryDetail
}
type GetGroupHistoryRequest struct {
Page int `form:"page"`
Size int `form:"size"`
GroupMode string `form:"group_mode,omitempty"`
TriggerType string `form:"trigger_type,omitempty"`
}
type GetGroupHistoryResponse struct {
Total int64 `json:"total"`
List []GroupHistory `json:"list"`
}
type GetLoginLogRequest struct { type GetLoginLogRequest struct {
Page int `form:"page"` Page int `form:"page"`
Size int `form:"size"` Size int `form:"size"`
@ -2881,6 +2923,26 @@ type UpdateFamilyMaxMembersRequest struct {
MaxMembers int64 `json:"max_members" validate:"required,gt=0"` MaxMembers int64 `json:"max_members" validate:"required,gt=0"`
} }
type UpdateGroupConfigRequest struct {
Enabled bool `json:"enabled"`
Mode string `json:"mode"`
Config map[string]interface{} `json:"config"`
}
type UpdateNodeGroupRequest struct {
Id int64 `json:"id" validate:"required"`
Name string `json:"name"`
Description string `json:"description"`
Sort int `json:"sort"`
ForCalculation *bool `json:"for_calculation"`
IsExpiredGroup *bool `json:"is_expired_group"`
ExpiredDaysLimit *int `json:"expired_days_limit"`
MaxTrafficGBExpired *int64 `json:"max_traffic_gb_expired,omitempty"`
SpeedLimit *int `json:"speed_limit"`
MinTrafficGB *int64 `json:"min_traffic_gb,omitempty"`
MaxTrafficGB *int64 `json:"max_traffic_gb,omitempty"`
}
type UpdateNodeRequest struct { type UpdateNodeRequest struct {
Id int64 `json:"id"` Id int64 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
@ -3171,6 +3233,7 @@ type UserStatisticsResponse struct {
type UserSubscribe struct { type UserSubscribe struct {
Id int64 `json:"id"` Id int64 `json:"id"`
IdStr string `json:"id_str"`
UserId int64 `json:"user_id"` UserId int64 `json:"user_id"`
OrderId int64 `json:"order_id"` OrderId int64 `json:"order_id"`
SubscribeId int64 `json:"subscribe_id"` SubscribeId int64 `json:"subscribe_id"`

View File

@ -134,6 +134,11 @@ const (
DeviceBindLimitExceeded uint32 = 90019 DeviceBindLimitExceeded uint32 = 90019
) )
// Permission error
const (
PermissionDenied uint32 = 40300
)
const ( const (
OrderNotExist uint32 = 61001 OrderNotExist uint32 = 61001
PaymentMethodNotFound uint32 = 61002 PaymentMethodNotFound uint32 = 61002

View File

@ -102,6 +102,9 @@ func init() {
PaymentMethodNotFound: "Payment method not found", PaymentMethodNotFound: "Payment method not found",
OrderStatusError: "Order status error", OrderStatusError: "Order status error",
InsufficientOfPeriod: "Insufficient number of period", InsufficientOfPeriod: "Insufficient number of period",
// Permission error
PermissionDenied: "Permission denied",
} }
} }