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) {
}>
刷新
+ }
+ className="text-blue-500 hover:text-blue-600"
+ >
+ {refetching ? '获取中...' : '重新获取ID'}
+