feat: agent devices/sessions filter by agent-associated users only
- Add created_by field to AppUser model to track who created the user - Set created_by when agent creates users - Extract getAgentUserIDs() helper: finds users who recharged with agent/sub-agent cards OR were created by agent/sub-agent - Extract getAgentAppIDs() helper: finds agent's authorized applications - Refactor handleGetUsers and checkAgentUserPermission to use getAgentUserIDs - Create agent-specific handleGetDevices: filter by agent user IDs + app IDs - Create agent-specific handleGetSessions: filter by agent user IDs + app IDs - Replace admin handler references with agent-specific handlers for GET routes
This commit is contained in:
@@ -61,6 +61,7 @@ type AppUser struct {
|
||||
IsTrialUser bool `gorm:"default:false" json:"is_trial_user"`
|
||||
TrialStartAt *time.Time `json:"trial_start_at"`
|
||||
TrialEndAt *time.Time `json:"trial_end_at"`
|
||||
CreatedBy *uint `gorm:"index" json:"created_by"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
@@ -37,12 +37,12 @@ func SetupAgentRoutes(r *gin.RouterGroup) {
|
||||
r.GET("/cloud-variables", handleGetCloudVariables)
|
||||
r.GET("/cloud-variables/:id/records", handleGetCloudVariableRecords)
|
||||
|
||||
r.GET("/devices", admin.HandleGetDevices)
|
||||
r.GET("/devices", handleGetDevices)
|
||||
r.PUT("/devices/:id/status", admin.HandleUpdateDeviceStatus)
|
||||
r.DELETE("/devices/:id", admin.HandleUnbindDevice)
|
||||
r.POST("/devices/:id/force-offline", admin.HandleForceOfflineDevice)
|
||||
|
||||
r.GET("/sessions", admin.HandleGetSessions)
|
||||
r.GET("/sessions", handleGetSessions)
|
||||
r.DELETE("/sessions/:id", admin.HandleDeleteSession)
|
||||
}
|
||||
|
||||
@@ -435,17 +435,9 @@ func generateCardCode() string {
|
||||
func handleGetUsers(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
agentIDs := []uint{userID}
|
||||
var childAgents []model.User
|
||||
database.DB.Where("parent_agent_id = ? AND role = ?", userID, "agent").Find(&childAgents)
|
||||
for _, child := range childAgents {
|
||||
agentIDs = append(agentIDs, child.ID)
|
||||
}
|
||||
appUserIDs := getAgentUserIDs(userID)
|
||||
|
||||
var cardIDs []uint
|
||||
database.DB.Model(&model.Card{}).Where("agent_id IN ?", agentIDs).Pluck("id", &cardIDs)
|
||||
|
||||
if len(cardIDs) == 0 {
|
||||
if len(appUserIDs) == 0 {
|
||||
response.Success(c, gin.H{
|
||||
"users": []interface{}{},
|
||||
"total": 0,
|
||||
@@ -456,12 +448,6 @@ func handleGetUsers(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var appUserIDs []uint
|
||||
database.DB.Model(&model.RechargeRecord{}).
|
||||
Where("card_id IN ? AND status = ?", cardIDs, "success").
|
||||
Distinct("user_id").
|
||||
Pluck("user_id", &appUserIDs)
|
||||
|
||||
if len(appUserIDs) == 0 {
|
||||
response.Success(c, gin.H{
|
||||
"users": []interface{}{},
|
||||
@@ -1099,6 +1085,7 @@ func handleCreateUser(c *gin.Context) {
|
||||
Avatar: "",
|
||||
Status: "active",
|
||||
ApplicationID: app.ID,
|
||||
CreatedBy: &userID,
|
||||
}
|
||||
|
||||
if err := tx.Create(&user).Error; err != nil {
|
||||
@@ -1285,7 +1272,7 @@ func handleUpdateUserStatus(c *gin.Context) {
|
||||
response.Success(c, user)
|
||||
}
|
||||
|
||||
func checkAgentUserPermission(userID uint, appUserID uint) bool {
|
||||
func getAgentUserIDs(userID uint) []uint {
|
||||
agentIDs := []uint{userID}
|
||||
var childAgents []model.User
|
||||
database.DB.Where("parent_agent_id = ? AND role = ?", userID, "agent").Find(&childAgents)
|
||||
@@ -1296,16 +1283,53 @@ func checkAgentUserPermission(userID uint, appUserID uint) bool {
|
||||
var cardIDs []uint
|
||||
database.DB.Model(&model.Card{}).Where("agent_id IN ?", agentIDs).Pluck("id", &cardIDs)
|
||||
|
||||
if len(cardIDs) == 0 {
|
||||
return false
|
||||
userIDSet := make(map[uint]bool)
|
||||
|
||||
if len(cardIDs) > 0 {
|
||||
var rechargedUserIDs []uint
|
||||
database.DB.Model(&model.RechargeRecord{}).
|
||||
Where("card_id IN ? AND status = ?", cardIDs, "success").
|
||||
Distinct("user_id").
|
||||
Pluck("user_id", &rechargedUserIDs)
|
||||
for _, id := range rechargedUserIDs {
|
||||
userIDSet[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
var count int64
|
||||
database.DB.Model(&model.RechargeRecord{}).
|
||||
Where("user_id = ? AND card_id IN ? AND status = ?", appUserID, cardIDs, "success").
|
||||
Count(&count)
|
||||
var createdUserIDs []uint
|
||||
database.DB.Model(&model.AppUser{}).
|
||||
Where("created_by IN ?", agentIDs).
|
||||
Pluck("id", &createdUserIDs)
|
||||
for _, id := range createdUserIDs {
|
||||
userIDSet[id] = true
|
||||
}
|
||||
|
||||
return count > 0
|
||||
result := make([]uint, 0, len(userIDSet))
|
||||
for id := range userIDSet {
|
||||
result = append(result, id)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func getAgentAppIDs(userID uint) []uint {
|
||||
var agentApps []model.AgentApplication
|
||||
database.DB.Where("agent_id = ? AND is_received = ?", userID, true).Find(&agentApps)
|
||||
|
||||
appIDs := make([]uint, 0, len(agentApps))
|
||||
for _, agentApp := range agentApps {
|
||||
appIDs = append(appIDs, agentApp.ApplicationID)
|
||||
}
|
||||
return appIDs
|
||||
}
|
||||
|
||||
func checkAgentUserPermission(userID uint, appUserID uint) bool {
|
||||
agentUserIDs := getAgentUserIDs(userID)
|
||||
for _, id := range agentUserIDs {
|
||||
if id == appUserID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func handleGetUser(c *gin.Context) {
|
||||
@@ -1373,3 +1397,237 @@ func handleUpdateUser(c *gin.Context) {
|
||||
|
||||
response.Success(c, user)
|
||||
}
|
||||
|
||||
func handleGetDevices(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
appUserIDs := getAgentUserIDs(userID)
|
||||
if len(appUserIDs) == 0 {
|
||||
response.Success(c, gin.H{
|
||||
"devices": []interface{}{},
|
||||
"total": 0,
|
||||
"banned_count": 0,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
appIDs := getAgentAppIDs(userID)
|
||||
if len(appIDs) == 0 {
|
||||
response.Success(c, gin.H{
|
||||
"devices": []interface{}{},
|
||||
"total": 0,
|
||||
"banned_count": 0,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
statusFilter := c.Query("status")
|
||||
applicationIDFilter := c.Query("application_id")
|
||||
|
||||
query := database.DB.Model(&model.UserDevice{}).
|
||||
Where("user_id IN ? AND application_id IN ?", appUserIDs, appIDs)
|
||||
|
||||
if statusFilter != "" {
|
||||
query = query.Where("status = ?", statusFilter)
|
||||
}
|
||||
if applicationIDFilter != "" {
|
||||
appID, _ := strconv.Atoi(applicationIDFilter)
|
||||
if appID > 0 {
|
||||
query = query.Where("application_id = ?", appID)
|
||||
}
|
||||
}
|
||||
|
||||
var total int64
|
||||
query.Count(&total)
|
||||
|
||||
var bannedCount int64
|
||||
database.DB.Model(&model.UserDevice{}).
|
||||
Where("user_id IN ? AND application_id IN ? AND status = ?", appUserIDs, appIDs, "banned").
|
||||
Count(&bannedCount)
|
||||
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var devices []model.UserDevice
|
||||
findQuery := database.DB.Preload("User").Preload("Application").
|
||||
Where("user_id IN ? AND application_id IN ?", appUserIDs, appIDs)
|
||||
|
||||
if statusFilter != "" {
|
||||
findQuery = findQuery.Where("status = ?", statusFilter)
|
||||
}
|
||||
if applicationIDFilter != "" {
|
||||
appID, _ := strconv.Atoi(applicationIDFilter)
|
||||
if appID > 0 {
|
||||
findQuery = findQuery.Where("application_id = ?", appID)
|
||||
}
|
||||
}
|
||||
|
||||
if err := findQuery.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&devices).Error; err != nil {
|
||||
response.Error(c, 500, "获取设备列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
deviceIDs := make([]uint, len(devices))
|
||||
for i, d := range devices {
|
||||
deviceIDs[i] = d.ID
|
||||
}
|
||||
|
||||
onlineSessionMap := make(map[uint]int)
|
||||
if len(deviceIDs) > 0 {
|
||||
type SessionCountResult struct {
|
||||
DeviceID uint
|
||||
Count int
|
||||
}
|
||||
var sessionCounts []SessionCountResult
|
||||
database.DB.Model(&model.DeviceSession{}).
|
||||
Select("device_id, COUNT(*) as count").
|
||||
Where("device_id IN ? AND last_heartbeat > ?", deviceIDs, time.Now().Add(-5*time.Minute)).
|
||||
Group("device_id").
|
||||
Find(&sessionCounts)
|
||||
for _, sc := range sessionCounts {
|
||||
onlineSessionMap[sc.DeviceID] = sc.Count
|
||||
}
|
||||
}
|
||||
|
||||
type DeviceWithDetails struct {
|
||||
model.UserDevice
|
||||
OnlineSessions int `json:"online_sessions"`
|
||||
}
|
||||
|
||||
result := make([]DeviceWithDetails, 0, len(devices))
|
||||
for _, device := range devices {
|
||||
result = append(result, DeviceWithDetails{
|
||||
UserDevice: device,
|
||||
OnlineSessions: onlineSessionMap[device.ID],
|
||||
})
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"devices": result,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total_page": (total + int64(pageSize) - 1) / int64(pageSize),
|
||||
"banned_count": bannedCount,
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetSessions(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
appUserIDs := getAgentUserIDs(userID)
|
||||
if len(appUserIDs) == 0 {
|
||||
response.Success(c, gin.H{
|
||||
"sessions": []interface{}{},
|
||||
"total": 0,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
appIDs := getAgentAppIDs(userID)
|
||||
if len(appIDs) == 0 {
|
||||
response.Success(c, gin.H{
|
||||
"sessions": []interface{}{},
|
||||
"total": 0,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
appIDFilter := c.Query("app_id")
|
||||
searchFilter := c.Query("search")
|
||||
|
||||
query := database.DB.Model(&model.DeviceSession{}).
|
||||
Where("user_id IN ? AND application_id IN ?", appUserIDs, appIDs)
|
||||
|
||||
if appIDFilter != "" {
|
||||
appID, _ := strconv.Atoi(appIDFilter)
|
||||
if appID > 0 {
|
||||
query = query.Where("application_id = ?", appID)
|
||||
}
|
||||
}
|
||||
|
||||
var total int64
|
||||
query.Count(&total)
|
||||
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var sessions []model.DeviceSession
|
||||
findQuery := database.DB.Where("user_id IN ? AND application_id IN ?", appUserIDs, appIDs)
|
||||
|
||||
if appIDFilter != "" {
|
||||
appID, _ := strconv.Atoi(appIDFilter)
|
||||
if appID > 0 {
|
||||
findQuery = findQuery.Where("application_id = ?", appID)
|
||||
}
|
||||
}
|
||||
|
||||
if err := findQuery.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&sessions).Error; err != nil {
|
||||
response.Error(c, 500, "获取会话列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
type SessionWithDetails struct {
|
||||
model.DeviceSession
|
||||
DeviceID string `json:"device_identifier"`
|
||||
DeviceName string `json:"device_name"`
|
||||
Username string `json:"username"`
|
||||
AppName string `json:"app_name"`
|
||||
IsOnline bool `json:"is_online"`
|
||||
}
|
||||
|
||||
result := make([]SessionWithDetails, 0, len(sessions))
|
||||
for _, session := range sessions {
|
||||
var device model.UserDevice
|
||||
if err := database.DB.First(&device, session.DeviceID).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var user model.AppUser
|
||||
if err := database.DB.First(&user, session.UserID).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, session.ApplicationID).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
heartbeatTimeout := app.HeartbeatTimeout
|
||||
if heartbeatTimeout == 0 {
|
||||
heartbeatTimeout = 300
|
||||
}
|
||||
timeoutThreshold := time.Now().Add(-time.Duration(heartbeatTimeout) * time.Second)
|
||||
isOnline := session.LastHeartbeat != nil && session.LastHeartbeat.After(timeoutThreshold)
|
||||
|
||||
if searchFilter != "" {
|
||||
searchLower := strings.ToLower(searchFilter)
|
||||
instanceIDMatch := strings.Contains(strings.ToLower(session.InstanceID), searchLower)
|
||||
deviceIDMatch := strings.Contains(strings.ToLower(device.DeviceID), searchLower)
|
||||
usernameMatch := strings.Contains(strings.ToLower(user.Username), searchLower)
|
||||
deviceNameMatch := strings.Contains(strings.ToLower(device.DeviceName), searchLower)
|
||||
|
||||
if !instanceIDMatch && !deviceIDMatch && !usernameMatch && !deviceNameMatch {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, SessionWithDetails{
|
||||
DeviceSession: session,
|
||||
DeviceID: device.DeviceID,
|
||||
DeviceName: device.DeviceName,
|
||||
Username: user.Username,
|
||||
AppName: app.Name,
|
||||
IsOnline: isOnline,
|
||||
})
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"sessions": result,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user