mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-22 07:34:45 +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.
|
// GetUserConcurrencyStats returns real-time concurrency usage for all active users.
|
||||||
// GET /api/v1/admin/ops/user-concurrency
|
// 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) {
|
func (h *OpsHandler) GetUserConcurrencyStats(c *gin.Context) {
|
||||||
if h.opsService == nil {
|
if h.opsService == nil {
|
||||||
response.Error(c, http.StatusServiceUnavailable, "Ops service not available")
|
response.Error(c, http.StatusServiceUnavailable, "Ops service not available")
|
||||||
@@ -84,7 +88,18 @@ func (h *OpsHandler) GetUserConcurrencyStats(c *gin.Context) {
|
|||||||
return
|
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 {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -344,8 +344,16 @@ func (s *OpsService) getUsersLoadMapBestEffort(ctx context.Context, users []User
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserConcurrencyStats returns real-time concurrency usage for all active users.
|
// GetUserConcurrencyStats returns real-time concurrency usage for active users.
|
||||||
func (s *OpsService) GetUserConcurrencyStats(ctx context.Context) (map[int64]*UserConcurrencyInfo, *time.Time, error) {
|
//
|
||||||
|
// 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 {
|
if err := s.RequireMonitoringEnabled(ctx); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -355,6 +363,15 @@ func (s *OpsService) GetUserConcurrencyStats(ctx context.Context) (map[int64]*Us
|
|||||||
return nil, nil, err
|
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()
|
collectedAt := time.Now()
|
||||||
loadMap := s.getUsersLoadMapBestEffort(ctx, users)
|
loadMap := s.getUsersLoadMapBestEffort(ctx, users)
|
||||||
|
|
||||||
@@ -365,6 +382,12 @@ func (s *OpsService) GetUserConcurrencyStats(ctx context.Context) (map[int64]*Us
|
|||||||
continue
|
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]
|
load := loadMap[u.ID]
|
||||||
currentInUse := int64(0)
|
currentInUse := int64(0)
|
||||||
waiting := int64(0)
|
waiting := int64(0)
|
||||||
@@ -394,3 +417,46 @@ func (s *OpsService) GetUserConcurrencyStats(ctx context.Context) (map[int64]*Us
|
|||||||
|
|
||||||
return result, &collectedAt, nil
|
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
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserConcurrencyStats(): Promise<OpsUserConcurrencyStatsResponse> {
|
export async function getUserConcurrencyStats(platform?: string, groupId?: number | null): Promise<OpsUserConcurrencyStatsResponse> {
|
||||||
const { data } = await apiClient.get<OpsUserConcurrencyStatsResponse>('/admin/ops/user-concurrency')
|
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
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ async function loadData() {
|
|||||||
try {
|
try {
|
||||||
if (showByUser.value) {
|
if (showByUser.value) {
|
||||||
// 用户视图模式只加载用户并发数据
|
// 用户视图模式只加载用户并发数据
|
||||||
const userData = await opsAPI.getUserConcurrencyStats()
|
const userData = await opsAPI.getUserConcurrencyStats(props.platformFilter, props.groupIdFilter)
|
||||||
userConcurrency.value = userData
|
userConcurrency.value = userData
|
||||||
} else {
|
} else {
|
||||||
// 常规模式加载账号/平台/分组数据
|
// 常规模式加载账号/平台/分组数据
|
||||||
@@ -301,6 +301,14 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 过滤条件变化时重新加载数据
|
||||||
|
watch(
|
||||||
|
[() => props.platformFilter, () => props.groupIdFilter],
|
||||||
|
() => {
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
function getLoadBarClass(loadPct: number): string {
|
function getLoadBarClass(loadPct: number): string {
|
||||||
if (loadPct >= 90) return 'bg-red-500 dark:bg-red-600'
|
if (loadPct >= 90) return 'bg-red-500 dark:bg-red-600'
|
||||||
if (loadPct >= 70) return 'bg-orange-500 dark:bg-orange-600'
|
if (loadPct >= 70) return 'bg-orange-500 dark:bg-orange-600'
|
||||||
|
|||||||
Reference in New Issue
Block a user