507 lines
13 KiB
Go
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)})
|
|
}
|