Files
GPT_Management/backend/internal/handler/chatgpt_account_handler.go
sar 474f592dcd feat(teams): fix checkbox multi-select and improve batch operations UI
- Fix checkbox binding using :model-value instead of :checked
- Change selectedIds from Set to reactive array for proper Vue reactivity
- Move batch refresh/delete buttons to top bar (matching CardKeysPage layout)
- Buttons show selection count like 'Refresh (2)' when items selected
- Swap position of 'Add Team' and 'Random Invite' buttons
- Remove unused isIndeterminate computed property
2026-01-16 11:53:04 +08:00

481 lines
12 KiB
Go

package handler
import (
"database/sql"
"encoding/json"
"net/http"
"strconv"
"time"
"gpt-manager-go/internal/models"
"gpt-manager-go/internal/repository"
"gpt-manager-go/internal/service"
)
// ChatGPTAccountHandler ChatGPT 账号处理器
type ChatGPTAccountHandler struct {
repo *repository.ChatGPTAccountRepository
invitationRepo *repository.InvitationRepository
chatgptService *service.ChatGPTService
}
// NewChatGPTAccountHandler 创建处理器
func NewChatGPTAccountHandler(repo *repository.ChatGPTAccountRepository, invitationRepo *repository.InvitationRepository, chatgptService *service.ChatGPTService) *ChatGPTAccountHandler {
return &ChatGPTAccountHandler{
repo: repo,
invitationRepo: invitationRepo,
chatgptService: chatgptService,
}
}
// CreateAccountRequest 创建账号请求
type CreateAccountRequest struct {
Name string `json:"name"`
TeamAccountID string `json:"team_account_id"`
AuthToken string `json:"auth_token"`
}
// AccountResponse 账号响应
type AccountResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
Data *models.ChatGPTAccount `json:"data,omitempty"`
}
// ListResponse 列表响应
type ListResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
Data []*models.ChatGPTAccount `json:"data,omitempty"`
Total int `json:"total,omitempty"`
Page int `json:"page,omitempty"`
PageSize int `json:"page_size,omitempty"`
}
// Create 创建账号
func (h *ChatGPTAccountHandler) Create(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
respondJSON(w, http.StatusMethodNotAllowed, AccountResponse{
Success: false,
Message: "Method not allowed",
})
return
}
var req CreateAccountRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondJSON(w, http.StatusBadRequest, AccountResponse{
Success: false,
Message: "Invalid request body",
})
return
}
// 验证必填字段
if req.TeamAccountID == "" || req.AuthToken == "" {
respondJSON(w, http.StatusBadRequest, AccountResponse{
Success: false,
Message: "team_account_id and auth_token are required",
})
return
}
// 检查是否已存在
existing, err := h.repo.FindByTeamAccountID(req.TeamAccountID)
if err != nil {
respondJSON(w, http.StatusInternalServerError, AccountResponse{
Success: false,
Message: "Database error: " + err.Error(),
})
return
}
if existing != nil {
respondJSON(w, http.StatusConflict, AccountResponse{
Success: false,
Message: "Team account already exists",
})
return
}
// 调用 ChatGPT API 获取订阅信息
subInfo, err := h.chatgptService.GetSubscription(req.TeamAccountID, req.AuthToken)
if err != nil {
respondJSON(w, http.StatusInternalServerError, AccountResponse{
Success: false,
Message: "Failed to verify team account: " + err.Error(),
})
return
}
// 设置账号名称
name := req.Name
if name == "" {
name = "Team-" + req.TeamAccountID[:8]
}
// 创建账号
account := &models.ChatGPTAccount{
Name: name,
AuthToken: req.AuthToken,
TeamAccountID: req.TeamAccountID,
SeatsInUse: subInfo.SeatsInUse,
SeatsEntitled: subInfo.SeatsEntitled,
IsActive: subInfo.IsValid,
}
// 设置时间
if subInfo.IsValid {
account.ActiveStart = sql.NullTime{Time: subInfo.ActiveStart, Valid: !subInfo.ActiveStart.IsZero()}
account.ActiveUntil = sql.NullTime{Time: subInfo.ActiveUntil, Valid: !subInfo.ActiveUntil.IsZero()}
account.LastCheck = sql.NullTime{Time: time.Now(), Valid: true}
}
if err := h.repo.Create(account); err != nil {
respondJSON(w, http.StatusInternalServerError, AccountResponse{
Success: false,
Message: "Failed to create account: " + err.Error(),
})
return
}
// 隐藏敏感信息
account.AuthToken = ""
respondJSON(w, http.StatusCreated, AccountResponse{
Success: true,
Message: "Account created successfully",
Data: account,
})
}
// List 获取账号列表
func (h *ChatGPTAccountHandler) List(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
respondJSON(w, http.StatusMethodNotAllowed, ListResponse{
Success: false,
Message: "Method not allowed",
})
return
}
// 获取分页参数
pageStr := r.URL.Query().Get("page")
pageSizeStr := r.URL.Query().Get("page_size")
page, _ := strconv.Atoi(pageStr)
pageSize, _ := strconv.Atoi(pageSizeStr)
// 默认值
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
if pageSize > 100 {
pageSize = 100
}
// 获取总数
total, err := h.repo.Count()
if err != nil {
respondJSON(w, http.StatusInternalServerError, ListResponse{
Success: false,
Message: "Failed to count accounts",
})
return
}
// 获取分页数据
accounts, err := h.repo.FindAllPaginated(page, pageSize)
if err != nil {
respondJSON(w, http.StatusInternalServerError, ListResponse{
Success: false,
Message: "Failed to fetch accounts",
})
return
}
// 隐藏敏感信息
for _, a := range accounts {
a.AuthToken = ""
}
respondJSON(w, http.StatusOK, ListResponse{
Success: true,
Message: "OK",
Data: accounts,
Total: total,
Page: page,
PageSize: pageSize,
})
}
// Refresh 刷新账号信息
func (h *ChatGPTAccountHandler) Refresh(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
respondJSON(w, http.StatusMethodNotAllowed, AccountResponse{
Success: false,
Message: "Method not allowed",
})
return
}
// 获取账号 ID
idStr := r.URL.Query().Get("id")
id, err := strconv.Atoi(idStr)
if err != nil {
respondJSON(w, http.StatusBadRequest, AccountResponse{
Success: false,
Message: "Invalid account ID",
})
return
}
account, err := h.repo.FindByID(id)
if err != nil || account == nil {
respondJSON(w, http.StatusNotFound, AccountResponse{
Success: false,
Message: "Account not found",
})
return
}
// 调用 ChatGPT API 获取订阅信息
subInfo, err := h.chatgptService.GetSubscription(account.TeamAccountID, account.AuthToken)
if err != nil {
// API 调用失败,增加失败计数并设为不活跃
account.ConsecutiveFailures++
account.IsActive = false
account.LastCheck = sql.NullTime{Time: time.Now(), Valid: true}
h.repo.Update(account)
respondJSON(w, http.StatusInternalServerError, AccountResponse{
Success: false,
Message: "Failed to refresh: " + err.Error(),
})
return
}
// 更新账号信息
if subInfo.IsValid {
account.SeatsInUse = subInfo.SeatsInUse
account.SeatsEntitled = subInfo.SeatsEntitled
account.ActiveStart = sql.NullTime{Time: subInfo.ActiveStart, Valid: !subInfo.ActiveStart.IsZero()}
account.ActiveUntil = sql.NullTime{Time: subInfo.ActiveUntil, Valid: !subInfo.ActiveUntil.IsZero()}
account.IsActive = true
account.ConsecutiveFailures = 0
} else {
account.IsActive = false
account.ConsecutiveFailures++
}
account.LastCheck = sql.NullTime{Time: time.Now(), Valid: true}
if err := h.repo.Update(account); err != nil {
respondJSON(w, http.StatusInternalServerError, AccountResponse{
Success: false,
Message: "Failed to update account",
})
return
}
// 隐藏敏感信息
account.AuthToken = ""
respondJSON(w, http.StatusOK, AccountResponse{
Success: true,
Message: "Account refreshed successfully",
Data: account,
})
}
// Delete 删除账号
func (h *ChatGPTAccountHandler) Delete(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete {
respondJSON(w, http.StatusMethodNotAllowed, AccountResponse{
Success: false,
Message: "Method not allowed",
})
return
}
idStr := r.URL.Query().Get("id")
id, err := strconv.Atoi(idStr)
if err != nil {
respondJSON(w, http.StatusBadRequest, AccountResponse{
Success: false,
Message: "Invalid account ID",
})
return
}
// 先删除相关的邀请记录,避免外键约束失败
if err := h.invitationRepo.DeleteByAccountID(id); err != nil {
respondJSON(w, http.StatusInternalServerError, AccountResponse{
Success: false,
Message: "Failed to delete related invitations: " + err.Error(),
})
return
}
if err := h.repo.Delete(id); err != nil {
respondJSON(w, http.StatusInternalServerError, AccountResponse{
Success: false,
Message: "Failed to delete account: " + err.Error(),
})
return
}
respondJSON(w, http.StatusOK, AccountResponse{
Success: true,
Message: "Account deleted successfully",
})
}
// BatchDeleteRequest 批量删除请求
type BatchDeleteAccountRequest struct {
IDs []int `json:"ids"`
}
// BatchDeleteResponse 批量操作响应
type BatchOperationResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
SuccessCount int `json:"success_count"`
FailedCount int `json:"failed_count"`
}
// BatchDelete 批量删除账号
func (h *ChatGPTAccountHandler) BatchDelete(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete {
respondJSON(w, http.StatusMethodNotAllowed, BatchOperationResponse{
Success: false,
Message: "Method not allowed",
})
return
}
var req BatchDeleteAccountRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondJSON(w, http.StatusBadRequest, BatchOperationResponse{
Success: false,
Message: "Invalid request body",
})
return
}
if len(req.IDs) == 0 {
respondJSON(w, http.StatusBadRequest, BatchOperationResponse{
Success: false,
Message: "No accounts selected",
})
return
}
successCount := 0
failedCount := 0
for _, id := range req.IDs {
// 先删除相关的邀请记录
if err := h.invitationRepo.DeleteByAccountID(id); err != nil {
failedCount++
continue
}
// 再删除账号
if err := h.repo.Delete(id); err != nil {
failedCount++
continue
}
successCount++
}
respondJSON(w, http.StatusOK, BatchOperationResponse{
Success: failedCount == 0,
Message: "Batch delete completed",
SuccessCount: successCount,
FailedCount: failedCount,
})
}
// BatchRefreshRequest 批量刷新请求
type BatchRefreshRequest struct {
IDs []int `json:"ids"`
}
// BatchRefresh 批量刷新账号
func (h *ChatGPTAccountHandler) BatchRefresh(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
respondJSON(w, http.StatusMethodNotAllowed, BatchOperationResponse{
Success: false,
Message: "Method not allowed",
})
return
}
var req BatchRefreshRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondJSON(w, http.StatusBadRequest, BatchOperationResponse{
Success: false,
Message: "Invalid request body",
})
return
}
if len(req.IDs) == 0 {
respondJSON(w, http.StatusBadRequest, BatchOperationResponse{
Success: false,
Message: "No accounts selected",
})
return
}
successCount := 0
failedCount := 0
for _, id := range req.IDs {
account, err := h.repo.FindByID(id)
if err != nil || account == nil {
failedCount++
continue
}
// 调用 ChatGPT API 获取订阅信息
subInfo, err := h.chatgptService.GetSubscription(account.TeamAccountID, account.AuthToken)
if err != nil {
account.ConsecutiveFailures++
account.IsActive = false
account.LastCheck = sql.NullTime{Time: time.Now(), Valid: true}
h.repo.Update(account)
failedCount++
continue
}
// 更新账号信息
if subInfo.IsValid {
account.SeatsInUse = subInfo.SeatsInUse
account.SeatsEntitled = subInfo.SeatsEntitled
account.ActiveStart = sql.NullTime{Time: subInfo.ActiveStart, Valid: !subInfo.ActiveStart.IsZero()}
account.ActiveUntil = sql.NullTime{Time: subInfo.ActiveUntil, Valid: !subInfo.ActiveUntil.IsZero()}
account.IsActive = true
account.ConsecutiveFailures = 0
} else {
account.IsActive = false
account.ConsecutiveFailures++
}
account.LastCheck = sql.NullTime{Time: time.Now(), Valid: true}
if err := h.repo.Update(account); err != nil {
failedCount++
continue
}
successCount++
}
respondJSON(w, http.StatusOK, BatchOperationResponse{
Success: failedCount == 0,
Message: "Batch refresh completed",
SuccessCount: successCount,
FailedCount: failedCount,
})
}