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

391 lines
10 KiB
Go

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": "密码修改成功",
})
}