mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-03 06:52:13 +08:00
fix: 并发/排队面板支持 platform/group 过滤
- 添加 platformFilter/groupIdFilter props 变化监听器,过滤条件变化时 立即重新加载数据(修复选择平台后显示"暂无数据"的问题) - 全栈为 getUserConcurrencyStats 添加 platform/group_id 过滤支持: 前端 API → Handler 解析 query params → Service 层过滤逻辑 - Service 层通过账号的 group 关联反查用户的 AllowedGroups, 与 GetConcurrencyStats 的过滤模式保持一致
This commit is contained in:
@@ -65,6 +65,10 @@ func (h *OpsHandler) GetConcurrencyStats(c *gin.Context) {
|
||||
|
||||
// GetUserConcurrencyStats returns real-time concurrency usage for all active users.
|
||||
// GET /api/v1/admin/ops/user-concurrency
|
||||
//
|
||||
// Query params:
|
||||
// - platform: optional, filter users by allowed platform
|
||||
// - group_id: optional, filter users by allowed group
|
||||
func (h *OpsHandler) GetUserConcurrencyStats(c *gin.Context) {
|
||||
if h.opsService == nil {
|
||||
response.Error(c, http.StatusServiceUnavailable, "Ops service not available")
|
||||
@@ -84,7 +88,18 @@ func (h *OpsHandler) GetUserConcurrencyStats(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
users, collectedAt, err := h.opsService.GetUserConcurrencyStats(c.Request.Context())
|
||||
platformFilter := strings.TrimSpace(c.Query("platform"))
|
||||
var groupID *int64
|
||||
if v := strings.TrimSpace(c.Query("group_id")); v != "" {
|
||||
id, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
response.BadRequest(c, "Invalid group_id")
|
||||
return
|
||||
}
|
||||
groupID = &id
|
||||
}
|
||||
|
||||
users, collectedAt, err := h.opsService.GetUserConcurrencyStats(c.Request.Context(), platformFilter, groupID)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
|
||||
@@ -344,8 +344,16 @@ func (s *OpsService) getUsersLoadMapBestEffort(ctx context.Context, users []User
|
||||
return out
|
||||
}
|
||||
|
||||
// GetUserConcurrencyStats returns real-time concurrency usage for all active users.
|
||||
func (s *OpsService) GetUserConcurrencyStats(ctx context.Context) (map[int64]*UserConcurrencyInfo, *time.Time, error) {
|
||||
// GetUserConcurrencyStats returns real-time concurrency usage for active users.
|
||||
//
|
||||
// Optional filters:
|
||||
// - platformFilter: only include users who have access to groups belonging to that platform
|
||||
// - groupIDFilter: only include users who have access to that specific group
|
||||
func (s *OpsService) GetUserConcurrencyStats(
|
||||
ctx context.Context,
|
||||
platformFilter string,
|
||||
groupIDFilter *int64,
|
||||
) (map[int64]*UserConcurrencyInfo, *time.Time, error) {
|
||||
if err := s.RequireMonitoringEnabled(ctx); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -355,6 +363,15 @@ func (s *OpsService) GetUserConcurrencyStats(ctx context.Context) (map[int64]*Us
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Build a set of allowed group IDs when filtering is requested.
|
||||
var allowedGroupIDs map[int64]struct{}
|
||||
if platformFilter != "" || (groupIDFilter != nil && *groupIDFilter > 0) {
|
||||
allowedGroupIDs, err = s.buildAllowedGroupIDsForFilter(ctx, platformFilter, groupIDFilter)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
collectedAt := time.Now()
|
||||
loadMap := s.getUsersLoadMapBestEffort(ctx, users)
|
||||
|
||||
@@ -365,6 +382,12 @@ func (s *OpsService) GetUserConcurrencyStats(ctx context.Context) (map[int64]*Us
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply group/platform filter: skip users whose AllowedGroups
|
||||
// have no intersection with the matching group IDs.
|
||||
if allowedGroupIDs != nil && !userMatchesGroupFilter(u.AllowedGroups, allowedGroupIDs) {
|
||||
continue
|
||||
}
|
||||
|
||||
load := loadMap[u.ID]
|
||||
currentInUse := int64(0)
|
||||
waiting := int64(0)
|
||||
@@ -394,3 +417,46 @@ func (s *OpsService) GetUserConcurrencyStats(ctx context.Context) (map[int64]*Us
|
||||
|
||||
return result, &collectedAt, nil
|
||||
}
|
||||
|
||||
// buildAllowedGroupIDsForFilter returns the set of group IDs that match the given
|
||||
// platform and/or group ID filter. It reuses listAllAccountsForOps (which already
|
||||
// supports platform filtering at the DB level) to collect group IDs from accounts.
|
||||
func (s *OpsService) buildAllowedGroupIDsForFilter(ctx context.Context, platformFilter string, groupIDFilter *int64) (map[int64]struct{}, error) {
|
||||
// Fast path: only group ID filter, no platform filter needed.
|
||||
if platformFilter == "" && groupIDFilter != nil && *groupIDFilter > 0 {
|
||||
return map[int64]struct{}{*groupIDFilter: {}}, nil
|
||||
}
|
||||
|
||||
// Use the same account-based approach as GetConcurrencyStats to collect group IDs.
|
||||
accounts, err := s.listAllAccountsForOps(ctx, platformFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groupIDs := make(map[int64]struct{})
|
||||
for _, acc := range accounts {
|
||||
for _, grp := range acc.Groups {
|
||||
if grp == nil || grp.ID <= 0 {
|
||||
continue
|
||||
}
|
||||
// If groupIDFilter is set, only include that specific group.
|
||||
if groupIDFilter != nil && *groupIDFilter > 0 && grp.ID != *groupIDFilter {
|
||||
continue
|
||||
}
|
||||
groupIDs[grp.ID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return groupIDs, nil
|
||||
}
|
||||
|
||||
// userMatchesGroupFilter returns true if the user's AllowedGroups contains
|
||||
// at least one group ID in the allowed set.
|
||||
func userMatchesGroupFilter(userGroups []int64, allowedGroupIDs map[int64]struct{}) bool {
|
||||
for _, gid := range userGroups {
|
||||
if _, ok := allowedGroupIDs[gid]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -366,8 +366,16 @@ export async function getConcurrencyStats(platform?: string, groupId?: number |
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getUserConcurrencyStats(): Promise<OpsUserConcurrencyStatsResponse> {
|
||||
const { data } = await apiClient.get<OpsUserConcurrencyStatsResponse>('/admin/ops/user-concurrency')
|
||||
export async function getUserConcurrencyStats(platform?: string, groupId?: number | null): Promise<OpsUserConcurrencyStatsResponse> {
|
||||
const params: Record<string, any> = {}
|
||||
if (platform) {
|
||||
params.platform = platform
|
||||
}
|
||||
if (typeof groupId === 'number' && groupId > 0) {
|
||||
params.group_id = groupId
|
||||
}
|
||||
|
||||
const { data } = await apiClient.get<OpsUserConcurrencyStatsResponse>('/admin/ops/user-concurrency', { params })
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
@@ -265,7 +265,7 @@ async function loadData() {
|
||||
try {
|
||||
if (showByUser.value) {
|
||||
// 用户视图模式只加载用户并发数据
|
||||
const userData = await opsAPI.getUserConcurrencyStats()
|
||||
const userData = await opsAPI.getUserConcurrencyStats(props.platformFilter, props.groupIdFilter)
|
||||
userConcurrency.value = userData
|
||||
} else {
|
||||
// 常规模式加载账号/平台/分组数据
|
||||
@@ -301,6 +301,14 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
// 过滤条件变化时重新加载数据
|
||||
watch(
|
||||
[() => props.platformFilter, () => props.groupIdFilter],
|
||||
() => {
|
||||
loadData()
|
||||
}
|
||||
)
|
||||
|
||||
function getLoadBarClass(loadPct: number): string {
|
||||
if (loadPct >= 90) return 'bg-red-500 dark:bg-red-600'
|
||||
if (loadPct >= 70) return 'bg-orange-500 dark:bg-orange-600'
|
||||
|
||||
Reference in New Issue
Block a user