| package handler |
|
|
| import ( |
| "fmt" |
| "log" |
| "net/http" |
| "strconv" |
| "strings" |
| "time" |
|
|
| "github.com/gin-gonic/gin" |
| "zencoder-2api/internal/database" |
| "zencoder-2api/internal/model" |
| "zencoder-2api/internal/service" |
| ) |
|
|
| type AccountHandler struct{} |
|
|
| func NewAccountHandler() *AccountHandler { |
| return &AccountHandler{} |
| } |
|
|
| func (h *AccountHandler) List(c *gin.Context) { |
| page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) |
| size, _ := strconv.Atoi(c.DefaultQuery("size", "10")) |
| |
| |
| status := c.DefaultQuery("status", "") |
| if status == "" { |
| category := c.DefaultQuery("category", "normal") |
| if category == "abnormal" { |
| status = "cooling" |
| } else { |
| status = category |
| } |
| } |
|
|
| if page < 1 { |
| page = 1 |
| } |
| if size < 1 { |
| size = 10 |
| } |
|
|
| var accounts []model.Account |
| var total int64 |
|
|
| query := database.GetDB().Model(&model.Account{}) |
| if status != "all" { |
| query = query.Where("status = ?", status) |
| } |
|
|
| if err := query.Count(&total).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) |
| return |
| } |
|
|
| offset := (page - 1) * size |
| if err := query.Offset(offset).Limit(size).Order("id desc").Find(&accounts).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) |
| return |
| } |
| |
| |
| if status == "cooling" { |
| for _, acc := range accounts { |
| if !acc.CoolingUntil.IsZero() { |
| log.Printf("[DEBUG] 冷却账号 %s (ID:%d) - CoolingUntil: %s (UTC), 现在: %s (UTC)", |
| acc.Email, acc.ID, |
| acc.CoolingUntil.Format("2006-01-02 15:04:05"), |
| time.Now().UTC().Format("2006-01-02 15:04:05")) |
| } |
| } |
| } |
|
|
| |
| var stats struct { |
| TotalAccounts int64 `json:"total_accounts"` |
| NormalAccounts int64 `json:"normal_accounts"` |
| BannedAccounts int64 `json:"banned_accounts"` |
| ErrorAccounts int64 `json:"error_accounts"` |
| CoolingAccounts int64 `json:"cooling_accounts"` |
| DisabledAccounts int64 `json:"disabled_accounts"` |
| TodayUsage float64 `json:"today_usage"` |
| TotalUsage float64 `json:"total_usage"` |
| } |
|
|
| db := database.GetDB() |
|
|
| db.Model(&model.Account{}).Count(&stats.TotalAccounts) |
| db.Model(&model.Account{}).Where("status = ?", "normal").Count(&stats.NormalAccounts) |
| db.Model(&model.Account{}).Where("status = ?", "banned").Count(&stats.BannedAccounts) |
| db.Model(&model.Account{}).Where("status = ?", "error").Count(&stats.ErrorAccounts) |
| db.Model(&model.Account{}).Where("status = ?", "cooling").Count(&stats.CoolingAccounts) |
| db.Model(&model.Account{}).Where("status = ?", "disabled").Count(&stats.DisabledAccounts) |
|
|
| db.Model(&model.Account{}).Select("COALESCE(SUM(daily_used), 0)").Scan(&stats.TodayUsage) |
| db.Model(&model.Account{}).Select("COALESCE(SUM(total_used), 0)").Scan(&stats.TotalUsage) |
|
|
| |
| statsMap := map[string]interface{}{ |
| "total_accounts": stats.TotalAccounts, |
| "active_accounts": stats.NormalAccounts, |
| "banned_accounts": stats.BannedAccounts, |
| "error_accounts": stats.ErrorAccounts, |
| "cooling_accounts": stats.CoolingAccounts, |
| "disabled_accounts": stats.DisabledAccounts, |
| "today_usage": stats.TodayUsage, |
| "total_usage": stats.TotalUsage, |
| } |
|
|
| c.JSON(http.StatusOK, gin.H{ |
| "items": accounts, |
| "total": total, |
| "page": page, |
| "size": size, |
| "stats": statsMap, |
| }) |
| } |
|
|
| type BatchCategoryRequest struct { |
| IDs []uint `json:"ids"` |
| Category string `json:"category"` |
| Status string `json:"status"` |
| } |
|
|
| func (h *AccountHandler) BatchUpdateCategory(c *gin.Context) { |
| var req BatchCategoryRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) |
| return |
| } |
|
|
| if len(req.IDs) == 0 { |
| c.JSON(http.StatusBadRequest, gin.H{"error": "no ids provided"}) |
| return |
| } |
|
|
| status := req.Status |
| if status == "" { |
| status = req.Category |
| } |
|
|
| updates := map[string]interface{}{ |
| "status": status, |
| |
| "category": status, |
| } |
|
|
| switch status { |
| case "normal": |
| updates["is_active"] = true |
| updates["is_cooling"] = false |
| case "cooling": |
| updates["is_active"] = true |
| updates["is_cooling"] = true |
| case "disabled": |
| updates["is_active"] = false |
| updates["is_cooling"] = false |
| default: |
| updates["is_active"] = false |
| updates["is_cooling"] = false |
| } |
|
|
| if err := database.GetDB().Model(&model.Account{}).Where("id IN ?", req.IDs).Updates(updates).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) |
| return |
| } |
|
|
| |
| c.JSON(http.StatusOK, gin.H{"message": "updated", "count": len(req.IDs)}) |
| } |
|
|
| type MoveAllRequest struct { |
| FromStatus string `json:"from_status"` |
| ToStatus string `json:"to_status"` |
| } |
|
|
| |
| func (h *AccountHandler) BatchMoveAll(c *gin.Context) { |
| var req MoveAllRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) |
| return |
| } |
|
|
| if req.FromStatus == "" || req.ToStatus == "" { |
| c.JSON(http.StatusBadRequest, gin.H{"error": "from_status and to_status are required"}) |
| return |
| } |
|
|
| if req.FromStatus == req.ToStatus { |
| c.JSON(http.StatusBadRequest, gin.H{"error": "from_status and to_status cannot be the same"}) |
| return |
| } |
|
|
| updates := map[string]interface{}{ |
| "status": req.ToStatus, |
| |
| "category": req.ToStatus, |
| } |
|
|
| |
| switch req.ToStatus { |
| case "normal": |
| updates["is_active"] = true |
| updates["is_cooling"] = false |
| updates["error_count"] = 0 |
| updates["ban_reason"] = "" |
| case "cooling": |
| updates["is_active"] = false |
| updates["is_cooling"] = true |
| updates["ban_reason"] = "" |
| case "disabled": |
| updates["is_active"] = false |
| updates["is_cooling"] = false |
| updates["ban_reason"] = "" |
| case "banned": |
| updates["is_active"] = false |
| updates["is_cooling"] = false |
| case "error": |
| updates["is_active"] = false |
| updates["is_cooling"] = false |
| default: |
| c.JSON(http.StatusBadRequest, gin.H{"error": "invalid to_status"}) |
| return |
| } |
|
|
| |
| result := database.GetDB().Model(&model.Account{}).Where("status = ?", req.FromStatus).Updates(updates) |
| if result.Error != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": result.Error.Error()}) |
| return |
| } |
|
|
| log.Printf("[批量移动] 从 %s 移动到 %s,影响 %d 个账号", req.FromStatus, req.ToStatus, result.RowsAffected) |
|
|
| c.JSON(http.StatusOK, gin.H{ |
| "message": "moved successfully", |
| "moved_count": result.RowsAffected, |
| "from_status": req.FromStatus, |
| "to_status": req.ToStatus, |
| }) |
| } |
|
|
| type BatchRefreshTokenRequest struct { |
| IDs []uint `json:"ids"` |
| All bool `json:"all"` |
| } |
|
|
| type BatchDeleteRequest struct { |
| IDs []uint `json:"ids"` |
| DeleteAll bool `json:"delete_all"` |
| Status string `json:"status"` |
| } |
|
|
| |
| func (h *AccountHandler) BatchRefreshToken(c *gin.Context) { |
| var req BatchRefreshTokenRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) |
| return |
| } |
|
|
| |
| c.Header("Content-Type", "text/event-stream") |
| c.Header("Cache-Control", "no-cache") |
| c.Header("Connection", "keep-alive") |
| c.Header("X-Accel-Buffering", "no") |
|
|
| flusher, ok := c.Writer.(http.Flusher) |
| if !ok { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": "流式传输不支持"}) |
| return |
| } |
|
|
| var accounts []model.Account |
| var err error |
|
|
| |
| if req.All { |
| |
| err = database.GetDB().Where("status = ? AND (client_id != '' AND client_secret != '')", "normal").Find(&accounts).Error |
| if err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": "获取账号列表失败: " + err.Error()}) |
| return |
| } |
| log.Printf("[批量刷新Token] 准备刷新所有正常账号,共 %d 个", len(accounts)) |
| } else if len(req.IDs) > 0 { |
| |
| err = database.GetDB().Where("id IN ? AND (client_id != '' AND client_secret != '')", req.IDs).Find(&accounts).Error |
| if err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": "获取选中账号失败: " + err.Error()}) |
| return |
| } |
| log.Printf("[批量刷新Token] 准备刷新选中账号,共 %d 个", len(accounts)) |
| } else { |
| c.JSON(http.StatusBadRequest, gin.H{"error": "请选择要刷新的账号或选择刷新所有账号"}) |
| return |
| } |
|
|
| if len(accounts) == 0 { |
| c.JSON(http.StatusBadRequest, gin.H{"error": "没有找到可刷新的账号"}) |
| return |
| } |
|
|
| |
| fmt.Fprintf(c.Writer, "data: {\"type\":\"start\",\"total\":%d}\n\n", len(accounts)) |
| flusher.Flush() |
|
|
| successCount := 0 |
| failCount := 0 |
|
|
| |
| for i, account := range accounts { |
| log.Printf("[批量刷新Token] 开始刷新第 %d/%d 个账号: %s (ID:%d)", i+1, len(accounts), account.ClientID, account.ID) |
| |
| |
| if err := service.RefreshAccountToken(&account); err != nil { |
| failCount++ |
| errMsg := fmt.Sprintf("刷新失败: %v", err) |
| |
| |
| if lockoutErr, ok := err.(*service.AccountLockoutError); ok { |
| errMsg = fmt.Sprintf("账号被锁定已自动标记为封禁: %s", lockoutErr.Body) |
| log.Printf("[批量刷新Token] 第 %d/%d 个账号被锁定: %s - %s", i+1, len(accounts), account.ClientID, lockoutErr.Body) |
| } else { |
| log.Printf("[批量刷新Token] 第 %d/%d 个账号刷新失败: %s - %v", i+1, len(accounts), account.ClientID, err) |
| } |
| |
| fmt.Fprintf(c.Writer, "data: {\"type\":\"error\",\"index\":%d,\"account_id\":\"%s\",\"message\":\"%s\"}\n\n", i+1, account.ClientID, errMsg) |
| flusher.Flush() |
| } else { |
| successCount++ |
| log.Printf("[批量刷新Token] 第 %d/%d 个账号刷新成功: %s (ID:%d)", i+1, len(accounts), account.ClientID, account.ID) |
| fmt.Fprintf(c.Writer, "data: {\"type\":\"success\",\"index\":%d,\"account_id\":\"%s\",\"email\":\"%s\"}\n\n", i+1, account.ClientID, account.Email) |
| flusher.Flush() |
| } |
|
|
| |
| if i < len(accounts)-1 { |
| time.Sleep(200 * time.Millisecond) |
| } |
| } |
|
|
| |
| log.Printf("[批量刷新Token] 完成: 成功 %d 个, 失败 %d 个", successCount, failCount) |
| fmt.Fprintf(c.Writer, "data: {\"type\":\"complete\",\"success\":%d,\"fail\":%d}\n\n", successCount, failCount) |
| flusher.Flush() |
| } |
|
|
| func (h *AccountHandler) Create(c *gin.Context) { |
| var req model.AccountRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) |
| return |
| } |
|
|
| |
| if req.GenerateMode { |
| |
| if req.RefreshToken == "" && req.Token == "" { |
| c.JSON(http.StatusBadRequest, gin.H{"error": "生成模式需要提供 access_token 或 RefreshToken"}) |
| return |
| } |
|
|
| |
| var masterToken string |
| |
| if req.Token != "" { |
| |
| masterToken = req.Token |
| log.Printf("[生成凭证] 使用提供的 access_token") |
| } else { |
| |
| tokenResp, err := service.RefreshAccessToken(req.RefreshToken, req.Proxy) |
| if err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"error": "RefreshToken 无效: " + err.Error()}) |
| return |
| } |
| masterToken = tokenResp.AccessToken |
| log.Printf("[生成凭证] 通过 RefreshToken 获取了 access_token") |
| } |
|
|
| log.Printf("[生成凭证] 开始生成账号凭证") |
| |
| |
| cred, err := service.GenerateCredential(masterToken) |
| if err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("生成失败: %v", err)}) |
| return |
| } |
|
|
| log.Printf("[生成凭证] 凭证生成成功: ClientID=%s", cred.ClientID) |
|
|
| |
| account := model.Account{ |
| ClientID: cred.ClientID, |
| ClientSecret: cred.Secret, |
| Proxy: req.Proxy, |
| IsActive: true, |
| Status: "normal", |
| } |
|
|
| |
| |
| if _, err := service.RefreshToken(&account); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("认证失败: %v", err)}) |
| return |
| } |
|
|
| |
| if payload, err := service.ParseJWT(account.AccessToken); err == nil { |
| account.Email = payload.Email |
| account.SubscriptionStartDate = service.GetSubscriptionDate(payload) |
| |
| if payload.Expiration > 0 { |
| account.TokenExpiry = time.Unix(payload.Expiration, 0) |
| } |
|
|
| plan := payload.CustomClaims.Plan |
| if plan != "" { |
| plan = strings.ToUpper(plan[:1]) + plan[1:] |
| } |
| if plan != "" { |
| account.PlanType = model.PlanType(plan) |
| } |
| } |
| if account.PlanType == "" { |
| account.PlanType = model.PlanFree |
| } |
|
|
| |
| var existing model.Account |
| var count int64 |
| database.GetDB().Model(&model.Account{}).Where("client_id = ?", account.ClientID).Count(&count) |
| if count > 0 { |
| |
| database.GetDB().Where("client_id = ?", account.ClientID).First(&existing) |
| |
| existing.AccessToken = account.AccessToken |
| existing.TokenExpiry = account.TokenExpiry |
| existing.PlanType = account.PlanType |
| existing.Email = account.Email |
| existing.SubscriptionStartDate = account.SubscriptionStartDate |
| existing.IsActive = true |
| existing.Status = "normal" |
| existing.ClientSecret = account.ClientSecret |
| if account.Proxy != "" { |
| existing.Proxy = account.Proxy |
| } |
|
|
| if err := database.GetDB().Save(&existing).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("更新失败: %v", err)}) |
| return |
| } |
| |
| log.Printf("[添加账号] 账号更新成功: ClientID=%s, Email=%s, Plan=%s", existing.ClientID, existing.Email, existing.PlanType) |
| c.JSON(http.StatusOK, existing) |
| } else { |
| |
| if err := database.GetDB().Create(&account).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("创建失败: %v", err)}) |
| return |
| } |
| |
| log.Printf("[添加账号] 新账号创建成功: ClientID=%s, Email=%s, Plan=%s", account.ClientID, account.Email, account.PlanType) |
| c.JSON(http.StatusCreated, account) |
| } |
| return |
| } |
|
|
| |
| account := model.Account{ |
| Proxy: req.Proxy, |
| IsActive: true, |
| Status: "normal", |
| } |
|
|
| |
| if req.Token != "" && req.RefreshToken != "" { |
| log.Printf("[凭证模式] 同时提供了 access_token 和 RefreshToken,优先使用 access_token") |
| } |
| |
| if req.Token != "" { |
| |
| payload, err := service.ParseJWT(req.Token) |
| if err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"error": "无效的Token: " + err.Error()}) |
| return |
| } |
|
|
| account.AccessToken = req.Token |
| |
| if payload.ClientID != "" { |
| account.ClientID = payload.ClientID |
| } else { |
| account.ClientID = payload.Subject |
| } |
|
|
| account.Email = payload.Email |
| account.SubscriptionStartDate = service.GetSubscriptionDate(payload) |
|
|
| if payload.Expiration > 0 { |
| account.TokenExpiry = time.Unix(payload.Expiration, 0) |
| } else { |
| account.TokenExpiry = time.Now().Add(24 * time.Hour) |
| } |
|
|
| |
| plan := payload.CustomClaims.Plan |
| |
| |
| if plan != "" { |
| plan = strings.ToUpper(plan[:1]) + plan[1:] |
| } |
| account.PlanType = model.PlanType(plan) |
| if account.PlanType == "" { |
| account.PlanType = model.PlanFree |
| } |
|
|
| |
| account.ClientSecret = "jwt-login" |
| } else if req.RefreshToken != "" { |
| |
| tokenResp, err := service.RefreshAccessToken(req.RefreshToken, req.Proxy) |
| if err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"error": "RefreshToken 无效: " + err.Error()}) |
| return |
| } |
|
|
| account.AccessToken = tokenResp.AccessToken |
| account.RefreshToken = tokenResp.RefreshToken |
| account.TokenExpiry = time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second) |
|
|
| |
| if payload, err := service.ParseJWT(tokenResp.AccessToken); err == nil { |
| |
| if payload.Email != "" { |
| account.Email = payload.Email |
| } else if tokenResp.Email != "" { |
| account.Email = tokenResp.Email |
| } |
| |
| |
| if payload.Email != "" { |
| account.ClientID = payload.Email |
| } else if payload.Subject != "" { |
| account.ClientID = payload.Subject |
| } else if payload.ClientID != "" { |
| account.ClientID = payload.ClientID |
| } |
| |
| account.SubscriptionStartDate = service.GetSubscriptionDate(payload) |
|
|
| |
| plan := payload.CustomClaims.Plan |
| if plan != "" { |
| plan = strings.ToUpper(plan[:1]) + plan[1:] |
| } |
| account.PlanType = model.PlanType(plan) |
| if account.PlanType == "" { |
| account.PlanType = model.PlanFree |
| } |
| |
| log.Printf("[凭证模式-RefreshToken] 解析JWT成功: ClientID=%s, Email=%s, Plan=%s", |
| account.ClientID, account.Email, account.PlanType) |
| } else { |
| log.Printf("[凭证模式-RefreshToken] 解析JWT失败: %v", err) |
| |
| if tokenResp.UserID != "" { |
| account.ClientID = tokenResp.UserID |
| account.Email = tokenResp.UserID |
| } |
| } |
|
|
| |
| account.ClientSecret = "refresh-token-login" |
| |
| |
| if account.ClientID == "" { |
| c.JSON(http.StatusBadRequest, gin.H{"error": "无法获取用户信息,请检查RefreshToken是否有效"}) |
| return |
| } |
| |
| } else { |
| |
| if req.PlanType == "" { |
| req.PlanType = model.PlanFree |
| } |
| account.ClientID = req.ClientID |
| account.ClientSecret = req.ClientSecret |
| account.Email = req.Email |
| account.PlanType = req.PlanType |
|
|
| |
| if _, err := service.RefreshToken(&account); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"error": "认证失败: " + err.Error()}) |
| return |
| } |
|
|
| |
| if payload, err := service.ParseJWT(account.AccessToken); err == nil { |
| if account.Email == "" { |
| account.Email = payload.Email |
| } |
| account.SubscriptionStartDate = service.GetSubscriptionDate(payload) |
| |
| if payload.Expiration > 0 { |
| account.TokenExpiry = time.Unix(payload.Expiration, 0) |
| } |
|
|
| plan := payload.CustomClaims.Plan |
| if plan != "" { |
| plan = strings.ToUpper(plan[:1]) + plan[1:] |
| } |
| if plan != "" { |
| account.PlanType = model.PlanType(plan) |
| } |
| } |
| if account.PlanType == "" { |
| account.PlanType = model.PlanFree |
| } |
| } |
|
|
| |
| var existing model.Account |
| var count int64 |
| database.GetDB().Model(&model.Account{}).Where("client_id = ?", account.ClientID).Count(&count) |
| if count > 0 { |
| |
| database.GetDB().Where("client_id = ?", account.ClientID).First(&existing) |
| |
| existing.AccessToken = account.AccessToken |
| existing.RefreshToken = account.RefreshToken |
| existing.TokenExpiry = account.TokenExpiry |
| existing.PlanType = account.PlanType |
| existing.Email = account.Email |
| existing.SubscriptionStartDate = account.SubscriptionStartDate |
| existing.IsActive = true |
| existing.Status = "normal" |
| if account.Proxy != "" { |
| existing.Proxy = account.Proxy |
| } |
| |
| if account.ClientSecret != "jwt-login" && account.ClientSecret != "refresh-token-login" { |
| existing.ClientSecret = account.ClientSecret |
| } |
|
|
| if err := database.GetDB().Save(&existing).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) |
| return |
| } |
| c.JSON(http.StatusOK, existing) |
| return |
| } |
|
|
| if err := database.GetDB().Create(&account).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) |
| return |
| } |
|
|
| c.JSON(http.StatusCreated, account) |
| } |
|
|
| func (h *AccountHandler) Update(c *gin.Context) { |
| id := c.Param("id") |
| var account model.Account |
| if err := database.GetDB().First(&account, id).Error; err != nil { |
| c.JSON(http.StatusNotFound, gin.H{"error": "account not found"}) |
| return |
| } |
|
|
| var req model.AccountRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) |
| return |
| } |
|
|
| account.Email = req.Email |
| account.PlanType = req.PlanType |
| account.Proxy = req.Proxy |
|
|
| if err := database.GetDB().Save(&account).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) |
| return |
| } |
|
|
| c.JSON(http.StatusOK, account) |
| } |
|
|
| |
| func (h *AccountHandler) BatchDelete(c *gin.Context) { |
| var req BatchDeleteRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) |
| return |
| } |
|
|
| var deletedCount int64 |
|
|
| if req.DeleteAll { |
| |
| if req.Status == "" { |
| c.JSON(http.StatusBadRequest, gin.H{"error": "delete_all模式需要指定status"}) |
| return |
| } |
|
|
| |
| result := database.GetDB().Where("status = ?", req.Status).Delete(&model.Account{}) |
| if result.Error != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": result.Error.Error()}) |
| return |
| } |
|
|
| deletedCount = result.RowsAffected |
| log.Printf("[批量删除] 删除分类 %s 的所有账号,共删除 %d 个", req.Status, deletedCount) |
|
|
| } else { |
| |
| if len(req.IDs) == 0 { |
| c.JSON(http.StatusBadRequest, gin.H{"error": "未选择要删除的账号"}) |
| return |
| } |
|
|
| |
| result := database.GetDB().Where("id IN ?", req.IDs).Delete(&model.Account{}) |
| if result.Error != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": result.Error.Error()}) |
| return |
| } |
|
|
| deletedCount = result.RowsAffected |
| log.Printf("[批量删除] 删除选中的 %d 个账号,实际删除 %d 个", len(req.IDs), deletedCount) |
| } |
|
|
| c.JSON(http.StatusOK, gin.H{ |
| "message": "批量删除成功", |
| "deleted_count": deletedCount, |
| }) |
| } |
|
|
| func (h *AccountHandler) Delete(c *gin.Context) { |
| id := c.Param("id") |
| if err := database.GetDB().Delete(&model.Account{}, id).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) |
| return |
| } |
| c.JSON(http.StatusOK, gin.H{"message": "deleted"}) |
| } |
|
|
| func (h *AccountHandler) Toggle(c *gin.Context) { |
| id := c.Param("id") |
| var account model.Account |
| if err := database.GetDB().First(&account, id).Error; err != nil { |
| c.JSON(http.StatusNotFound, gin.H{"error": "account not found"}) |
| return |
| } |
|
|
| |
| if account.Status == "disabled" || !account.IsActive { |
| account.Status = "normal" |
| account.IsActive = true |
| account.IsCooling = false |
| account.ErrorCount = 0 |
| } else { |
| account.Status = "disabled" |
| account.IsActive = false |
| } |
| |
| database.GetDB().Save(&account) |
|
|
| c.JSON(http.StatusOK, account) |
| } |
|
|