diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 7532f5e..55334b8 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -104,6 +104,7 @@ func startServer(cfg *config.Config) { mux.HandleFunc("/api/db/owners", api.CORS(handleGetOwners)) mux.HandleFunc("/api/db/owners/stats", api.CORS(handleGetOwnerStats)) mux.HandleFunc("/api/db/owners/clear", api.CORS(handleClearOwners)) + mux.HandleFunc("/api/db/owners/refetch-account-ids", api.CORS(api.HandleRefetchAccountIDs)) mux.HandleFunc("/api/upload/validate", api.CORS(api.HandleUploadValidate)) // 注册测试 API diff --git a/backend/internal/api/upload.go b/backend/internal/api/upload.go index 1f79bba..68fae63 100644 --- a/backend/internal/api/upload.go +++ b/backend/internal/api/upload.go @@ -128,6 +128,84 @@ func HandleUploadValidate(w http.ResponseWriter, r *http.Request) { }) } +// HandleRefetchAccountIDs 重新获取缺少 account_id 的母号 +func HandleRefetchAccountIDs(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + Error(w, http.StatusMethodNotAllowed, "仅支持 POST") + return + } + if database.Instance == nil { + Error(w, http.StatusInternalServerError, "数据库未初始化") + return + } + + // 获取所有缺少 account_id 的 owners + owners, err := database.Instance.GetOwnersWithoutAccountID() + if err != nil { + Error(w, http.StatusInternalServerError, fmt.Sprintf("查询数据库失败: %v", err)) + return + } + + if len(owners) == 0 { + Success(w, map[string]interface{}{ + "message": "所有母号都已有 account_id", + "total": 0, + "success": 0, + "fail": 0, + }) + return + } + + logger.Info(fmt.Sprintf("开始重新获取 account_id: 共 %d 个", len(owners)), "", "upload") + + // 并发获取 account_id + var wg sync.WaitGroup + sem := make(chan struct{}, 20) // 20 并发 + var mu sync.Mutex + successCount := 0 + failCount := 0 + + for _, owner := range owners { + wg.Add(1) + sem <- struct{}{} + + go func(o database.TeamOwner) { + defer wg.Done() + defer func() { <-sem }() + + accountID, err := fetchAccountID(o.Token) + + mu.Lock() + defer mu.Unlock() + + if err != nil { + failCount++ + logger.Warning(fmt.Sprintf("获取 account_id 失败 (%s): %v", o.Email, err), "", "upload") + } else { + // 更新数据库 + if updateErr := database.Instance.UpdateOwnerAccountID(o.ID, accountID); updateErr != nil { + failCount++ + logger.Error(fmt.Sprintf("更新 account_id 失败 (%s): %v", o.Email, updateErr), "", "upload") + } else { + successCount++ + logger.Info(fmt.Sprintf("获取 account_id 成功: %s -> %s", o.Email, accountID), "", "upload") + } + } + }(owner) + } + + wg.Wait() + + logger.Info(fmt.Sprintf("重新获取 account_id 完成: 成功=%d, 失败=%d", successCount, failCount), "", "upload") + + Success(w, map[string]interface{}{ + "message": "重新获取 account_id 完成", + "total": len(owners), + "success": successCount, + "fail": failCount, + }) +} + // fetchAccountIDsConcurrent 并发获取 account_id func fetchAccountIDsConcurrent(owners []database.TeamOwner, concurrency int) { var wg sync.WaitGroup diff --git a/backend/internal/database/sqlite.go b/backend/internal/database/sqlite.go index c375fa4..76a60c3 100644 --- a/backend/internal/database/sqlite.go +++ b/backend/internal/database/sqlite.go @@ -237,6 +237,36 @@ func (d *DB) ClearTeamOwners() error { return err } +// GetOwnersWithoutAccountID 获取缺少 account_id 的 owners +func (d *DB) GetOwnersWithoutAccountID() ([]TeamOwner, error) { + rows, err := d.db.Query(` + SELECT id, email, password, token, account_id, status, created_at + FROM team_owners WHERE account_id = '' OR account_id IS NULL + ORDER BY created_at DESC + `) + if err != nil { + return nil, err + } + defer rows.Close() + + var owners []TeamOwner + for rows.Next() { + var owner TeamOwner + err := rows.Scan(&owner.ID, &owner.Email, &owner.Password, &owner.Token, &owner.AccountID, &owner.Status, &owner.CreatedAt) + if err != nil { + continue + } + owners = append(owners, owner) + } + return owners, nil +} + +// UpdateOwnerAccountID 更新 owner 的 account_id +func (d *DB) UpdateOwnerAccountID(id int64, accountID string) error { + _, err := d.db.Exec("UPDATE team_owners SET account_id = ? WHERE id = ?", accountID, id) + return err +} + // GetOwnerStats 获取统计 func (d *DB) GetOwnerStats() map[string]int { stats := map[string]int{ diff --git a/frontend/src/components/upload/OwnerList.tsx b/frontend/src/components/upload/OwnerList.tsx index 2b54c21..8ff8dac 100644 --- a/frontend/src/components/upload/OwnerList.tsx +++ b/frontend/src/components/upload/OwnerList.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react' -import { Users, Trash2, RefreshCw, ChevronLeft, ChevronRight } from 'lucide-react' +import { Users, Trash2, RefreshCw, ChevronLeft, ChevronRight, Key } from 'lucide-react' import { Card, CardHeader, CardTitle, CardContent, Button } from '../common' interface TeamOwner { @@ -84,6 +84,30 @@ export default function OwnerList({ onStatsChange }: OwnerListProps) { } } + const [refetching, setRefetching] = useState(false) + + const handleRefetchAccountIds = async () => { + if (refetching) return + setRefetching(true) + try { + const res = await fetch('/api/db/owners/refetch-account-ids', { method: 'POST' }) + const data = await res.json() + if (data.code === 0) { + const result = data.data + alert(`重新获取完成\n总数: ${result.total}\n成功: ${result.success}\n失败: ${result.fail}`) + loadOwners() + onStatsChange?.() + } else { + alert(`操作失败: ${data.message}`) + } + } catch (e) { + console.error('Failed to refetch account ids:', e) + alert('操作失败,请查看控制台') + } finally { + setRefetching(false) + } + } + const totalPages = Math.ceil(total / limit) const formatTime = (ts: string) => { @@ -124,6 +148,16 @@ export default function OwnerList({ onStatsChange }: OwnerListProps) { +