package app import ( "fmt" "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" ) func SetupDeviceRoutes(r *gin.RouterGroup) { r.GET("/devices", handleAppGetDevices) r.POST("/unbind-device", handleAppUnbindDevice) r.GET("/device-count", handleAppGetDeviceCount) r.GET("/instances", handleAppGetInstances) r.POST("/instances/:instance_id/offline", handleAppForceOfflineInstance) } func SetupDevicePublicRoutes(r *gin.RouterGroup) { r.POST("/unbind-device-with-auth", handleAppUnbindDeviceWithAuth) r.POST("/change-password", handleAppChangePassword) } func handleAppUnbindDeviceWithAuth(c *gin.Context) { appKey := c.Param("appKey") var app model.Application if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil { response.Error(c, 404, "应用不存在") return } if service.GetApplicationDisabledStatus(app.ID) { response.Error(c, 403, "该应用已被禁用") return } var req struct { Username string `json:"username"` Password string `json:"password"` DeviceID string `json:"device_id"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } if req.Username == "" || req.Password == "" || req.DeviceID == "" { response.Error(c, 400, "用户名、密码和设备ID不能为空") return } var user model.AppUser if err := database.DB.Where("username = ? AND application_id = ?", req.Username, app.ID).First(&user).Error; err != nil { response.Error(c, 404, "用户不存在") return } if user.Password != req.Password { response.Error(c, 401, "密码错误") return } var device model.UserDevice if err := database.DB.Where("user_id = ? AND application_id = ? AND device_id = ?", user.ID, app.ID, req.DeviceID).First(&device).Error; err != nil { response.Error(c, 404, "设备不存在") 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 } if user.DeviceID == req.DeviceID { now := time.Now() database.DB.Model(&user).Updates(map[string]interface{}{ "device_id": "", "last_heartbeat_at": &now, }) } service.LogVerification(c, &app.ID, &user.ID, "unbind_device", fmt.Sprintf("用户解绑设备(认证): %s", req.DeviceID), req.DeviceID, nil) response.Success(c, gin.H{ "message": "解绑成功", }) } func handleAppGetDevices(c *gin.Context) { appKey := c.Param("appKey") var app model.Application if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil { response.Error(c, 404, "应用不存在") return } if service.GetApplicationDisabledStatus(app.ID) { response.Error(c, 403, "该应用已被禁用") return } userID, exists := c.Get("user_id") if !exists { response.Error(c, 401, "未授权") return } var devices []model.UserDevice if err := database.DB.Where("user_id = ? AND application_id = ?", userID, app.ID).Find(&devices).Error; err != nil { response.Error(c, 500, "获取设备列表失败") return } result := make([]gin.H, 0) for _, device := range devices { var onlineSessionCount int64 tenMinutesAgo := time.Now().Add(-10 * time.Minute) database.DB.Model(&model.DeviceSession{}). Where("device_id = ? AND last_heartbeat > ?", device.ID, tenMinutesAgo). Count(&onlineSessionCount) result = append(result, gin.H{ "id": device.ID, "device_id": device.DeviceID, "device_name": device.DeviceName, "device_type": device.DeviceType, "status": device.Status, "online_sessions": onlineSessionCount, "created_at": device.CreatedAt, }) } response.Success(c, result) } func handleAppGetDeviceCount(c *gin.Context) { appKey := c.Param("appKey") var app model.Application if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil { response.Error(c, 404, "应用不存在") return } if service.GetApplicationDisabledStatus(app.ID) { response.Error(c, 403, "该应用已被禁用") return } var req struct { UserID uint `json:"user_id"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } var count int64 if err := database.DB.Model(&model.UserDevice{}).Where("user_id = ? AND application_id = ?", req.UserID, app.ID).Count(&count).Error; err != nil { response.Error(c, 500, "获取设备数量失败") return } response.Success(c, gin.H{ "count": count, "max_devices": app.MaxDevices, "remaining": app.MaxDevices - int(count), }) } func handleAppUnbindDevice(c *gin.Context) { appKey := c.Param("appKey") var app model.Application if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil { response.Error(c, 404, "应用不存在") return } if service.GetApplicationDisabledStatus(app.ID) { response.Error(c, 403, "该应用已被禁用") return } var req struct { UserID uint `json:"user_id"` DeviceID string `json:"device_id"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } var device model.UserDevice if err := database.DB.Where("user_id = ? AND application_id = ? AND device_id = ?", req.UserID, app.ID, req.DeviceID).First(&device).Error; err != nil { response.Error(c, 404, "设备不存在") 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 } var user model.AppUser if err := database.DB.First(&user, req.UserID).Error; err == nil { if user.DeviceID == req.DeviceID { now := time.Now() database.DB.Model(&user).Updates(map[string]interface{}{ "device_id": "", "last_heartbeat_at": &now, }) } } service.LogVerification(c, &app.ID, &user.ID, "unbind_device", fmt.Sprintf("用户解绑设备: %s", req.DeviceID), req.DeviceID, nil) response.Success(c, gin.H{ "message": "解绑成功", }) } func handleAppGetInstances(c *gin.Context) { appKey := c.Param("appKey") var app model.Application if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil { response.Error(c, 404, "应用不存在") return } if service.GetApplicationDisabledStatus(app.ID) { response.Error(c, 403, "该应用已被禁用") return } var req struct { UserID uint `json:"user_id"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } var devices []model.UserDevice if err := database.DB.Where("user_id = ? AND application_id = ?", req.UserID, app.ID).Find(&devices).Error; err != nil { response.Error(c, 500, "获取设备列表失败") return } deviceIDs := make([]uint, len(devices)) for i, d := range devices { deviceIDs[i] = d.ID } heartbeatTimeout := app.HeartbeatTimeout if heartbeatTimeout <= 0 { heartbeatTimeout = 10 } timeoutThreshold := time.Now().Add(-time.Duration(heartbeatTimeout) * time.Minute) var sessions []model.DeviceSession if len(deviceIDs) > 0 { if err := database.DB.Where("device_id IN ?", deviceIDs).Order("last_heartbeat DESC").Find(&sessions).Error; err != nil { response.Error(c, 500, "获取实例列表失败") return } } result := make([]gin.H, 0) for _, session := range sessions { isOnline := session.LastHeartbeat != nil && session.LastHeartbeat.After(timeoutThreshold) var device model.UserDevice for _, d := range devices { if d.ID == session.DeviceID { device = d break } } result = append(result, gin.H{ "id": session.ID, "instance_id": session.InstanceID, "device_id": device.DeviceID, "device_name": device.DeviceName, "is_online": isOnline, "last_heartbeat": session.LastHeartbeat, "created_at": session.CreatedAt, }) } response.Success(c, result) } func handleAppForceOfflineInstance(c *gin.Context) { appKey := c.Param("appKey") instanceID := c.Param("instance_id") var app model.Application if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil { response.Error(c, 404, "应用不存在") return } if service.GetApplicationDisabledStatus(app.ID) { response.Error(c, 403, "该应用已被禁用") return } var req struct { UserID uint `json:"user_id"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } var devices []model.UserDevice if err := database.DB.Where("user_id = ? AND application_id = ?", req.UserID, app.ID).Find(&devices).Error; err != nil { response.Error(c, 500, "获取设备列表失败") return } deviceIDs := make([]uint, len(devices)) for i, d := range devices { deviceIDs[i] = d.ID } if len(deviceIDs) == 0 { response.Error(c, 404, "实例不存在") return } result := database.DB.Where("instance_id = ? AND device_id IN ?", instanceID, deviceIDs).Delete(&model.DeviceSession{}) if result.RowsAffected == 0 { response.Error(c, 404, "实例不存在") return } service.LogVerification(c, &app.ID, &req.UserID, "force_offline", fmt.Sprintf("强制离线实例: %s", instanceID), instanceID, nil) response.Success(c, gin.H{ "message": "已强制离线", }) } func handleAppChangePassword(c *gin.Context) { appKey := c.Param("appKey") var app model.Application if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil { response.Error(c, 404, "应用不存在") return } var req struct { Username string `json:"username" binding:"required"` OldPassword string `json:"old_password" binding:"required"` NewPassword string `json:"new_password" binding:"required,min=6"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } var user model.AppUser if err := database.DB.Where("username = ? AND application_id = ?", req.Username, app.ID).First(&user).Error; err != nil { response.Error(c, 404, "用户不存在") return } if user.Password != req.OldPassword { response.Error(c, 400, "原密码错误") return } user.Password = req.NewPassword if err := database.DB.Save(&user).Error; err != nil { response.Error(c, 500, "密码修改失败") return } response.Success(c, gin.H{ "message": "密码修改成功", }) }