package app import ( "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" "gorm.io/gorm" ) func SetupAccountRoutes(r *gin.RouterGroup) { r.GET("/account", handleAppGetAccount) r.POST("/heartbeat", handleAppHeartbeat) } func handleAppGetAccount(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 user model.AppUser if err := database.DB.First(&user, req.UserID).Error; err != nil { response.Error(c, 404, "用户不存在") return } response.Success(c, gin.H{ "user_id": user.ID, "username": user.Username, "balance": user.Balance, "status": user.Status, }) } func handleAppHeartbeat(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 } // 从 JWT token 中获取用户信息 userIDInterface, exists := c.Get("user_id") if !exists { response.Error(c, 401, "未授权") return } userID := userIDInterface.(uint) usernameInterface, _ := c.Get("username") username := usernameInterface.(string) // 从 JWT token 中获取设备信息 deviceIDInterface, exists := c.Get("device_id") deviceID := "" if exists { deviceID = deviceIDInterface.(string) } instanceIDInterface, exists := c.Get("instance_id") instanceID := "" if exists { instanceID = instanceIDInterface.(string) } if instanceID == "" { instanceID = deviceID } var user model.AppUser if err := database.DB.First(&user, userID).Error; err != nil { response.Error(c, 404, "用户不存在") return } if blocked, reason := checkRiskControl(c, app.ID, deviceID, username); blocked { log.Printf("[DEBUG] Heartbeat blocked by risk control: %s", reason) response.Error(c, 403, reason) return } now := time.Now() user.LastHeartbeatAt = &now if app.BillingType != "free" && app.DeductionMode == "auto" && app.DeductionAmount > 0 { shouldDeduct := false switch app.DeductionType { case "timer": if user.LastHeartbeatAt != nil { elapsed := now.Sub(*user.LastHeartbeatAt) var interval time.Duration switch app.DeductionUnit { case "minute": interval = time.Duration(app.DeductionInterval) * time.Minute case "hour": interval = time.Duration(app.DeductionInterval) * time.Hour case "day": interval = time.Duration(app.DeductionInterval) * 24 * time.Hour default: interval = time.Duration(app.DeductionInterval) * time.Minute } if elapsed >= interval { shouldDeduct = true } } case "login": shouldDeduct = true } if shouldDeduct { if user.Balance == -1 { log.Printf("[DEBUG] User %d is permanent member, skip deduction", user.ID) } else if app.BillingType == "subscription" { if user.ExpiryAt == nil || user.ExpiryAt.Before(now) { log.Printf("[DEBUG] User %d subscription expired, ExpiryAt=%v", user.ID, user.ExpiryAt) response.Error(c, 403, "订阅已过期") return } log.Printf("[DEBUG] User %d subscription valid, skip balance deduction", user.ID) } else { result := database.DB.Model(&model.AppUser{}).Where("id = ? AND balance >= ?", user.ID, app.DeductionAmount). Update("balance", gorm.Expr("balance - ?", app.DeductionAmount)) if result.Error != nil || result.RowsAffected == 0 { log.Printf("[DEBUG] User %d has insufficient balance: %.2f < %.2f", user.ID, user.Balance, app.DeductionAmount) response.Error(c, 403, "余额不足") return } database.DB.Where("id = ?", user.ID).First(&user) log.Printf("[DEBUG] Deducted %.2f from user %d, new balance: %.2f", app.DeductionAmount, user.ID, user.Balance) } } } if err := database.DB.Save(&user).Error; err != nil { response.Error(c, 500, "更新心跳失败") return } if deviceID != "" { var device model.UserDevice err := database.DB.Where("user_id = ? AND application_id = ? AND device_id = ?", user.ID, app.ID, deviceID).First(&device).Error if err != nil { device = model.UserDevice{ UserID: user.ID, ApplicationID: app.ID, DeviceID: deviceID, DeviceName: deviceID, Status: "active", } if err := database.DB.Create(&device).Error; err != nil { log.Printf("[DEBUG] Failed to create device: %v", err) } } if device.ID > 0 { var session model.DeviceSession sessionErr := database.DB.Where("device_id = ? AND instance_id = ?", device.ID, instanceID).First(&session).Error if sessionErr != nil { session = model.DeviceSession{ DeviceID: device.ID, UserID: user.ID, ApplicationID: app.ID, InstanceID: instanceID, LastHeartbeat: &now, } if err := database.DB.Create(&session).Error; err != nil { log.Printf("[DEBUG] Failed to create session: %v", err) } } else { session.LastHeartbeat = &now if err := database.DB.Save(&session).Error; err != nil { log.Printf("[DEBUG] Failed to update session: %v", err) } } } } response.SuccessWithMessage(c, "心跳成功", gin.H{ "message": "心跳成功", "balance": user.Balance, }) }