Files
verify/backend/internal/router/developer/devices.go
T
2026-04-27 17:22:56 +08:00

507 lines
13 KiB
Go

package developer
import (
"fmt"
"log"
"time"
"verification-platform-backend/internal/database"
"verification-platform-backend/internal/model"
"verification-platform-backend/internal/service"
"verification-platform-backend/pkg/response"
"github.com/gin-gonic/gin"
)
type DeviceWithDetails struct {
model.UserDevice
OnlineSessions int `json:"online_sessions"`
}
func SetupDeviceRoutes(r *gin.RouterGroup) {
devices := r.Group("/devices")
{
devices.GET("", handleGetDevices)
devices.PUT("/:id/status", handleUpdateDeviceStatus)
devices.DELETE("/:id", handleUnbindDevice)
devices.DELETE("/batch", handleBatchUnbindDevices)
devices.POST("/batch/status", handleBatchUpdateDeviceStatus)
devices.POST("/:id/force-offline", handleForceOfflineDevice)
}
sessions := r.Group("/sessions")
{
sessions.GET("", handleGetSessions)
sessions.DELETE("/:id", handleDeleteSession)
}
}
func handleGetDevices(c *gin.Context) {
userID := c.GetUint("user_id")
log.Printf("[DEBUG] handleGetDevices called, userID: %d", userID)
userIDFilter := c.Query("user_id")
deviceIDFilter := c.Query("device_id")
var devices []model.UserDevice
var appHeartbeatTimeoutMap map[uint]int
var ownApps []model.Application
if err := database.DB.Where("user_id = ?", userID).Find(&ownApps).Error; err != nil {
response.Error(c, 500, "获取应用列表失败")
return
}
var agentApps []model.AgentApplication
if err := database.DB.Where("agent_id = ? AND is_received = ?", userID, true).Find(&agentApps).Error; err != nil {
log.Printf("[DEBUG] Failed to get agent apps: %v", err)
}
appHeartbeatTimeoutMap = make(map[uint]int)
var appIDs []uint
for _, app := range ownApps {
timeout := app.HeartbeatTimeout
if timeout == 0 {
timeout = 300
}
appHeartbeatTimeoutMap[app.ID] = timeout
appIDs = append(appIDs, app.ID)
}
for _, agentApp := range agentApps {
var app model.Application
if err := database.DB.First(&app, agentApp.ApplicationID).Error; err != nil {
continue
}
timeout := app.HeartbeatTimeout
if timeout == 0 {
timeout = 300
}
appHeartbeatTimeoutMap[app.ID] = timeout
appIDs = append(appIDs, app.ID)
}
if len(appIDs) == 0 {
response.Success(c, gin.H{"devices": []DeviceWithDetails{}})
return
}
query := database.DB.Preload("User").Preload("Application").Where("application_id IN ?", appIDs)
if userIDFilter != "" {
query = query.Where("user_id = ?", userIDFilter)
}
if deviceIDFilter != "" {
query = query.Where("device_id = ?", deviceIDFilter)
}
if err := query.Find(&devices).Error; err != nil {
response.Error(c, 500, "获取设备列表失败")
return
}
devicesWithDetails := make([]DeviceWithDetails, 0, len(devices))
for _, device := range devices {
heartbeatTimeout := appHeartbeatTimeoutMap[device.ApplicationID]
timeoutThreshold := time.Now().Add(-time.Duration(heartbeatTimeout) * time.Second)
var onlineSessionCount int64
database.DB.Model(&model.DeviceSession{}).
Where("device_id = ? AND last_heartbeat > ?", device.ID, timeoutThreshold).
Count(&onlineSessionCount)
devicesWithDetails = append(devicesWithDetails, DeviceWithDetails{
UserDevice: device,
OnlineSessions: int(onlineSessionCount),
})
}
response.Success(c, gin.H{"devices": devicesWithDetails})
}
func handleUpdateDeviceStatus(c *gin.Context) {
userID := c.GetUint("user_id")
deviceID := c.Param("id")
var req struct {
Status string `json:"status"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var device model.UserDevice
if err := database.DB.First(&device, deviceID).Error; err != nil {
response.Error(c, 404, "设备不存在")
return
}
var app model.Application
if err := database.DB.First(&app, device.ApplicationID).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if app.UserID != userID {
var agentApp model.AgentApplication
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
response.Error(c, 403, "无权限修改该设备")
return
}
}
device.Status = req.Status
if err := database.DB.Save(&device).Error; err != nil {
response.Error(c, 500, "更新设备状态失败")
return
}
response.Success(c, device)
}
func handleUnbindDevice(c *gin.Context) {
userID := c.GetUint("user_id")
deviceID := c.Param("id")
var device model.UserDevice
if err := database.DB.First(&device, deviceID).Error; err != nil {
response.Error(c, 404, "设备不存在")
return
}
var app model.Application
if err := database.DB.First(&app, device.ApplicationID).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if app.UserID != userID {
var agentApp model.AgentApplication
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
response.Error(c, 403, "无权限解绑该设备")
return
}
}
database.DB.Where("device_id = ?", device.ID).Delete(&model.DeviceSession{})
if err := database.DB.Delete(&device).Error; err != nil {
response.Error(c, 500, "解绑设备失败")
return
}
service.LogOperation(c, "unbind", "device", &device.ID, fmt.Sprintf("解绑设备: %s (应用: %s)", device.DeviceID, app.Name), nil)
response.Success(c, nil)
}
func handleBatchUnbindDevices(c *gin.Context) {
userID := c.GetUint("user_id")
var req struct {
DeviceIDs []uint `json:"device_ids"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var devices []model.UserDevice
if err := database.DB.Where("id IN ?", req.DeviceIDs).Find(&devices).Error; err != nil {
response.Error(c, 500, "获取设备列表失败")
return
}
var validDeviceIDs []uint
for _, device := range devices {
var app model.Application
if err := database.DB.First(&app, device.ApplicationID).Error; err != nil {
continue
}
if app.UserID == userID {
validDeviceIDs = append(validDeviceIDs, device.ID)
} else {
var agentApp model.AgentApplication
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err == nil {
validDeviceIDs = append(validDeviceIDs, device.ID)
}
}
}
if len(validDeviceIDs) > 0 {
database.DB.Where("device_id IN ?", validDeviceIDs).Delete(&model.DeviceSession{})
if err := database.DB.Where("id IN ?", validDeviceIDs).Delete(&model.UserDevice{}).Error; err != nil {
response.Error(c, 500, "批量解绑失败")
return
}
}
service.LogOperation(c, "batch_unbind", "device", nil, fmt.Sprintf("批量解绑设备: %d个", len(validDeviceIDs)), nil)
response.Success(c, nil)
}
func handleBatchUpdateDeviceStatus(c *gin.Context) {
userID := c.GetUint("user_id")
var req struct {
DeviceIDs []uint `json:"device_ids"`
Status string `json:"status"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var devices []model.UserDevice
if err := database.DB.Where("id IN ?", req.DeviceIDs).Find(&devices).Error; err != nil {
response.Error(c, 500, "获取设备列表失败")
return
}
var validDeviceIDs []uint
for _, device := range devices {
var app model.Application
if err := database.DB.First(&app, device.ApplicationID).Error; err != nil {
continue
}
if app.UserID == userID {
validDeviceIDs = append(validDeviceIDs, device.ID)
} else {
var agentApp model.AgentApplication
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err == nil {
validDeviceIDs = append(validDeviceIDs, device.ID)
}
}
}
if len(validDeviceIDs) > 0 {
if err := database.DB.Model(&model.UserDevice{}).Where("id IN ?", validDeviceIDs).Update("status", req.Status).Error; err != nil {
response.Error(c, 500, "批量更新状态失败")
return
}
}
response.Success(c, nil)
}
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"`
}
func handleGetSessions(c *gin.Context) {
userID := c.GetUint("user_id")
deviceIDFilter := c.Query("device_id")
appIDFilter := c.Query("app_id")
var ownApps []model.Application
if err := database.DB.Where("user_id = ?", userID).Find(&ownApps).Error; err != nil {
response.Error(c, 500, "获取应用列表失败")
return
}
var agentApps []model.AgentApplication
if err := database.DB.Where("agent_id = ? AND is_received = ?", userID, true).Find(&agentApps).Error; err != nil {
log.Printf("[DEBUG] Failed to get agent apps: %v", err)
}
appHeartbeatTimeoutMap := make(map[uint]int)
var appIDs []uint
for _, app := range ownApps {
timeout := app.HeartbeatTimeout
if timeout == 0 {
timeout = 300
}
appHeartbeatTimeoutMap[app.ID] = timeout
appIDs = append(appIDs, app.ID)
}
for _, agentApp := range agentApps {
var app model.Application
if err := database.DB.First(&app, agentApp.ApplicationID).Error; err != nil {
continue
}
timeout := app.HeartbeatTimeout
if timeout == 0 {
timeout = 300
}
appHeartbeatTimeoutMap[app.ID] = timeout
appIDs = append(appIDs, app.ID)
}
if len(appIDs) == 0 {
response.Success(c, gin.H{"sessions": []SessionWithDetails{}})
return
}
query := database.DB.Model(&model.DeviceSession{}).Where("application_id IN ?", appIDs)
if deviceIDFilter != "" {
query = query.Where("device_id = ?", deviceIDFilter)
}
if appIDFilter != "" {
query = query.Where("application_id = ?", appIDFilter)
}
var sessions []model.DeviceSession
if err := query.Find(&sessions).Error; err != nil {
response.Error(c, 500, "获取会话列表失败")
return
}
sessionsWithDetails := 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 := appHeartbeatTimeoutMap[session.ApplicationID]
timeoutThreshold := time.Now().Add(-time.Duration(heartbeatTimeout) * time.Second)
isOnline := session.LastHeartbeat != nil && session.LastHeartbeat.After(timeoutThreshold)
sessionsWithDetails = append(sessionsWithDetails, SessionWithDetails{
DeviceSession: session,
DeviceID: device.DeviceID,
DeviceName: device.DeviceName,
Username: user.Username,
AppName: app.Name,
IsOnline: isOnline,
})
}
response.Success(c, gin.H{"sessions": sessionsWithDetails})
}
func handleDeleteSession(c *gin.Context) {
userID := c.GetUint("user_id")
sessionID := c.Param("id")
if sessionID == "" {
response.Error(c, 400, "会话ID不能为空")
return
}
var session model.DeviceSession
if err := database.DB.First(&session, sessionID).Error; err != nil {
response.Error(c, 404, "会话不存在")
return
}
var ownApps []model.Application
database.DB.Where("user_id = ?", userID).Find(&ownApps)
ownAppIDs := make([]uint, len(ownApps))
for i, app := range ownApps {
ownAppIDs[i] = app.ID
}
var agentApps []model.AgentApplication
database.DB.Where("agent_id = ? AND is_received = ?", userID, true).Find(&agentApps)
agentAppIDs := make([]uint, 0)
for _, agentApp := range agentApps {
agentAppIDs = append(agentAppIDs, agentApp.ApplicationID)
}
validAppIDs := append(ownAppIDs, agentAppIDs...)
isValid := false
for _, appID := range validAppIDs {
if session.ApplicationID == appID {
isValid = true
break
}
}
if !isValid {
response.Error(c, 403, "无权操作此会话")
return
}
if err := database.DB.Delete(&session).Error; err != nil {
response.Error(c, 500, "删除会话失败")
return
}
response.Success(c, nil)
}
func handleForceOfflineDevice(c *gin.Context) {
userID := c.GetUint("user_id")
deviceID := c.Param("id")
if deviceID == "" {
response.Error(c, 400, "设备ID不能为空")
return
}
var device model.UserDevice
if err := database.DB.First(&device, deviceID).Error; err != nil {
response.Error(c, 404, "设备不存在")
return
}
var ownApps []model.Application
database.DB.Where("user_id = ?", userID).Find(&ownApps)
ownAppIDs := make([]uint, len(ownApps))
for i, app := range ownApps {
ownAppIDs[i] = app.ID
}
var agentApps []model.AgentApplication
database.DB.Where("agent_id = ? AND is_received = ?", userID, true).Find(&agentApps)
agentAppIDs := make([]uint, 0)
for _, agentApp := range agentApps {
agentAppIDs = append(agentAppIDs, agentApp.ApplicationID)
}
validAppIDs := append(ownAppIDs, agentAppIDs...)
isValid := false
for _, appID := range validAppIDs {
if device.ApplicationID == appID {
isValid = true
break
}
}
if !isValid {
response.Error(c, 403, "无权操作此设备")
return
}
var sessions []model.DeviceSession
if err := database.DB.Where("device_id = ?", device.ID).Find(&sessions).Error; err != nil {
response.Error(c, 500, "获取会话列表失败")
return
}
if len(sessions) == 0 {
response.Success(c, gin.H{"count": 0})
return
}
if err := database.DB.Where("device_id = ?", device.ID).Delete(&model.DeviceSession{}).Error; err != nil {
response.Error(c, 500, "强制离线失败")
return
}
response.Success(c, gin.H{"count": len(sessions)})
}