Features:
- Node group CRUD operations with traffic-based filtering - Three grouping modes: average distribution, subscription-based, and traffic-based - Automatic and manual group recalculation with history tracking - Group assignment preview before applying changes - User subscription group locking to prevent automatic reassignment - Subscribe-to-group mapping configuration - Group calculation history and detailed reports - System configuration for group management (enabled/mode/auto_create) Database: - Add node_group table for group definitions - Add group_history and group_history_detail tables for tracking - Add node_group_ids (JSON) to nodes and subscribe tables - Add node_group_id and group_locked fields to user_subscribe table - Add migration files for schema changes
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/perfect-panel/server/internal/logic/admin/group"
|
||||
"github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
@@ -64,6 +65,7 @@ func (l *CreateUserSubscribeLogic) CreateUserSubscribe(req *types.CreateUserSubs
|
||||
Upload: 0,
|
||||
Token: uuidx.SubscribeToken(fmt.Sprintf("adminCreate:%d", time.Now().UnixMilli())),
|
||||
UUID: uuid.New().String(),
|
||||
NodeGroupId: sub.NodeGroupId,
|
||||
Status: 1,
|
||||
}
|
||||
if err = l.svcCtx.UserModel.InsertSubscribe(l.ctx, &userSub); err != nil {
|
||||
@@ -71,6 +73,60 @@ func (l *CreateUserSubscribeLogic) CreateUserSubscribe(req *types.CreateUserSubs
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "InsertSubscribe error: %v", err.Error())
|
||||
}
|
||||
|
||||
// Trigger user group recalculation (runs in background)
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Check if group management is enabled
|
||||
var groupEnabled string
|
||||
err := l.svcCtx.DB.Table("system").
|
||||
Where("`category` = ? AND `key` = ?", "group", "enabled").
|
||||
Select("value").
|
||||
Scan(&groupEnabled).Error
|
||||
if err != nil || groupEnabled != "true" && groupEnabled != "1" {
|
||||
l.Debugf("Group management not enabled, skipping recalculation")
|
||||
return
|
||||
}
|
||||
|
||||
// Get the configured grouping mode
|
||||
var groupMode string
|
||||
err = l.svcCtx.DB.Table("system").
|
||||
Where("`category` = ? AND `key` = ?", "group", "mode").
|
||||
Select("value").
|
||||
Scan(&groupMode).Error
|
||||
if err != nil {
|
||||
l.Errorw("Failed to get group mode", logger.Field("error", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Validate group mode
|
||||
if groupMode != "average" && groupMode != "subscribe" && groupMode != "traffic" {
|
||||
l.Debugf("Invalid group mode (current: %s), skipping", groupMode)
|
||||
return
|
||||
}
|
||||
|
||||
// Trigger group recalculation with the configured mode
|
||||
logic := group.NewRecalculateGroupLogic(ctx, l.svcCtx)
|
||||
req := &types.RecalculateGroupRequest{
|
||||
Mode: groupMode,
|
||||
}
|
||||
|
||||
if err := logic.RecalculateGroup(req); err != nil {
|
||||
l.Errorw("Failed to recalculate user group",
|
||||
logger.Field("user_id", userInfo.Id),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
l.Infow("Successfully recalculated user group after admin created subscription",
|
||||
logger.Field("user_id", userInfo.Id),
|
||||
logger.Field("subscribe_id", userSub.Id),
|
||||
logger.Field("mode", groupMode),
|
||||
)
|
||||
}()
|
||||
|
||||
err = l.svcCtx.UserModel.UpdateUserCache(l.ctx, userInfo)
|
||||
if err != nil {
|
||||
l.Errorw("UpdateUserCache error", logger.Field("error", err.Error()))
|
||||
@@ -81,5 +137,6 @@ func (l *CreateUserSubscribeLogic) CreateUserSubscribe(req *types.CreateUserSubs
|
||||
if err != nil {
|
||||
logger.Errorw("ClearSubscribe error", logger.Field("error", err.Error()))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -120,7 +120,31 @@ func (l *UpdateUserBasicInfoLogic) UpdateUserBasicInfo(req *types.UpdateUserBasi
|
||||
}
|
||||
userInfo.Commission = req.Commission
|
||||
}
|
||||
tool.DeepCopy(userInfo, req)
|
||||
|
||||
// 只更新指定的字段,不使用 DeepCopy 避免零值覆盖
|
||||
|
||||
// 处理头像(只在提供时更新)
|
||||
if req.Avatar != "" {
|
||||
userInfo.Avatar = req.Avatar
|
||||
}
|
||||
|
||||
// 处理推荐码(只在提供时更新)
|
||||
if req.ReferCode != "" {
|
||||
userInfo.ReferCode = req.ReferCode
|
||||
}
|
||||
|
||||
// 处理推荐人ID(只在非零时更新)
|
||||
if req.RefererId != 0 {
|
||||
userInfo.RefererId = req.RefererId
|
||||
}
|
||||
|
||||
// 处理启用状态(始终更新)
|
||||
userInfo.Enable = &req.Enable
|
||||
|
||||
// 处理管理员状态(始终更新)
|
||||
userInfo.IsAdmin = &req.IsAdmin
|
||||
|
||||
// 更新其他字段(只有在明确提供时才更新)
|
||||
userInfo.OnlyFirstPurchase = &req.OnlyFirstPurchase
|
||||
userInfo.ReferralPercentage = req.ReferralPercentage
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ func (l *UpdateUserSubscribeLogic) UpdateUserSubscribe(req *types.UpdateUserSubs
|
||||
Token: userSub.Token,
|
||||
UUID: userSub.UUID,
|
||||
Status: userSub.Status,
|
||||
NodeGroupId: userSub.NodeGroupId,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -74,5 +75,6 @@ func (l *UpdateUserSubscribeLogic) UpdateUserSubscribe(req *types.UpdateUserSubs
|
||||
l.Errorf("ClearServerAllCache error: %v", err.Error())
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "failed to clear server cache: %v", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user