feat(cookies): Cookie 失效后可账号重新登录替换, 并修复 Cookie 列空白过多
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
"""Cookie 管理路由"""
|
"""Cookie 管理路由"""
|
||||||
|
|
||||||
|
import threading
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
import requests
|
import requests
|
||||||
@@ -10,10 +11,12 @@ from sqlalchemy.orm import Session, defer, joinedload
|
|||||||
import io
|
import io
|
||||||
import csv
|
import csv
|
||||||
|
|
||||||
from ..database import get_db
|
from ..database import get_db, SessionLocal
|
||||||
from ..models import User, LoginTask, Account
|
from ..models import User, LoginTask, Account, ProxyConfig as ProxyConfigModel
|
||||||
from ..deps import get_current_user, require_permission
|
from ..deps import get_current_user, require_permission
|
||||||
from ..permissions import user_has_permission
|
from ..permissions import user_has_permission, get_user_permissions
|
||||||
|
from ..schemas import CookieReloginRequest
|
||||||
|
from ..services.login_service import LoginBatchRunner
|
||||||
|
|
||||||
|
|
||||||
def _fmt_dt(dt) -> str | None:
|
def _fmt_dt(dt) -> str | None:
|
||||||
@@ -327,6 +330,68 @@ def check_cookies(
|
|||||||
return {"results": results, "success": True}
|
return {"results": results, "success": True}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/relogin")
|
||||||
|
def relogin_cookies(
|
||||||
|
req: CookieReloginRequest,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current: User = Depends(require_permission("login:batch")),
|
||||||
|
):
|
||||||
|
"""重新登录失效的 Cookie:用账号信息重新登录,成功后替换旧 Cookie。
|
||||||
|
|
||||||
|
复用原 LoginTask 记录,行不消失;重新登录失败保留旧 Cookie 并记录原因。
|
||||||
|
"""
|
||||||
|
if not req.ids:
|
||||||
|
raise HTTPException(status_code=400, detail="请选择要重新登录的 Cookie")
|
||||||
|
|
||||||
|
tasks = _visible_cookie_tasks_query(db, current).filter(LoginTask.id.in_(req.ids)).all()
|
||||||
|
if not tasks:
|
||||||
|
raise HTTPException(status_code=404, detail="记录不存在")
|
||||||
|
|
||||||
|
# 过滤缺少登录凭据的账号(密码/邮箱),避免启动后全部失败
|
||||||
|
task_ids: list[int] = []
|
||||||
|
skipped: list[str] = []
|
||||||
|
accounts_map = {t.account_id: t for t in tasks}
|
||||||
|
for acc_id, task in accounts_map.items():
|
||||||
|
acc = db.query(Account).filter(Account.id == acc_id).first()
|
||||||
|
if not acc:
|
||||||
|
continue
|
||||||
|
if not acc.password or not acc.email:
|
||||||
|
skipped.append(task.id)
|
||||||
|
continue
|
||||||
|
task_ids.append(task.id)
|
||||||
|
|
||||||
|
if not task_ids:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="所选 Cookie 对应账号缺少密码或邮箱信息,无法重新登录,请先在账号管理中补充",
|
||||||
|
)
|
||||||
|
|
||||||
|
proxy = db.query(ProxyConfigModel).first()
|
||||||
|
thread_db = SessionLocal()
|
||||||
|
runner = LoginBatchRunner(
|
||||||
|
db=thread_db,
|
||||||
|
account_ids=[],
|
||||||
|
created_by=current.id,
|
||||||
|
creator_permissions=get_user_permissions(current),
|
||||||
|
proxy_config=proxy,
|
||||||
|
log_queue=None,
|
||||||
|
loop=None,
|
||||||
|
concurrency=3,
|
||||||
|
mode="relogin",
|
||||||
|
relogin_task_ids=task_ids,
|
||||||
|
)
|
||||||
|
batch_id = runner.batch_id
|
||||||
|
thread = threading.Thread(target=runner.run, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"batch_id": batch_id,
|
||||||
|
"count": len(task_ids),
|
||||||
|
"skipped": len(skipped),
|
||||||
|
"success": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{task_id}")
|
@router.get("/{task_id}")
|
||||||
def get_cookie(
|
def get_cookie(
|
||||||
task_id: int,
|
task_id: int,
|
||||||
|
|||||||
@@ -158,6 +158,10 @@ class LoginBatchRequest(BaseModel):
|
|||||||
mode: str = Field("login", pattern="^(login|check)$") # login=登录取CK,check=账号状态检测
|
mode: str = Field("login", pattern="^(login|check)$") # login=登录取CK,check=账号状态检测
|
||||||
|
|
||||||
|
|
||||||
|
class CookieReloginRequest(BaseModel):
|
||||||
|
ids: list[int] = [] # 需要重新登录的 Cookie 记录 ID
|
||||||
|
|
||||||
|
|
||||||
class LoginTaskOut(BaseModel):
|
class LoginTaskOut(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
batch_id: str
|
batch_id: str
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ class LoginBatchRunner:
|
|||||||
concurrency: int = 3,
|
concurrency: int = 3,
|
||||||
api_strategy: str = "wgapi",
|
api_strategy: str = "wgapi",
|
||||||
mode: str = "login",
|
mode: str = "login",
|
||||||
|
relogin_task_ids: Optional[list[int]] = None,
|
||||||
):
|
):
|
||||||
self.db = db
|
self.db = db
|
||||||
self.account_ids = account_ids
|
self.account_ids = account_ids
|
||||||
@@ -72,7 +73,8 @@ class LoginBatchRunner:
|
|||||||
self.batch_id = uuid.uuid4().hex[:12]
|
self.batch_id = uuid.uuid4().hex[:12]
|
||||||
self.concurrency = max(1, min(concurrency, 10)) # 限制 1-10
|
self.concurrency = max(1, min(concurrency, 10)) # 限制 1-10
|
||||||
self.api_strategy = _create_api_strategy(api_strategy)
|
self.api_strategy = _create_api_strategy(api_strategy)
|
||||||
self.mode = "check" if mode == "check" else "login"
|
self.mode = mode if mode in ("login", "check", "relogin") else "login"
|
||||||
|
self.relogin_task_ids = list(relogin_task_ids) if relogin_task_ids else []
|
||||||
self._stop = threading.Event()
|
self._stop = threading.Event()
|
||||||
self._counter_lock = threading.Lock()
|
self._counter_lock = threading.Lock()
|
||||||
self._completed = 0
|
self._completed = 0
|
||||||
@@ -133,6 +135,17 @@ class LoginBatchRunner:
|
|||||||
"""在独立线程中执行单个账号登录,使用独立的 DB 会话。"""
|
"""在独立线程中执行单个账号登录,使用独立的 DB 会话。"""
|
||||||
if self._stop.is_set():
|
if self._stop.is_set():
|
||||||
self._push_log("warning", f"任务已停止,跳过: {acc_info['username']}")
|
self._push_log("warning", f"任务已停止,跳过: {acc_info['username']}")
|
||||||
|
if self.mode == "relogin":
|
||||||
|
# 重新登录被停止时恢复原 Cookie 记录,避免列表行消失
|
||||||
|
worker_db = SessionLocal()
|
||||||
|
try:
|
||||||
|
task = worker_db.query(LoginTask).filter(LoginTask.id == task_id).first()
|
||||||
|
if task and task.status in ("pending", "running"):
|
||||||
|
task.status = "success"
|
||||||
|
task.finished_at = datetime.now(timezone.utc)
|
||||||
|
worker_db.commit()
|
||||||
|
finally:
|
||||||
|
worker_db.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
worker_db = SessionLocal()
|
worker_db = SessionLocal()
|
||||||
@@ -148,7 +161,7 @@ class LoginBatchRunner:
|
|||||||
self._completed += 1
|
self._completed += 1
|
||||||
current = self._completed
|
current = self._completed
|
||||||
|
|
||||||
action_name = "检测" if self.mode == "check" else "登录"
|
action_name = "检测" if self.mode == "check" else "重新登录" if self.mode == "relogin" else "登录"
|
||||||
self._push_log("info", f"[{current}/{total}] 开始{action_name}: {acc_info['username']}")
|
self._push_log("info", f"[{current}/{total}] 开始{action_name}: {acc_info['username']}")
|
||||||
|
|
||||||
# 解析代理配置
|
# 解析代理配置
|
||||||
@@ -198,16 +211,34 @@ class LoginBatchRunner:
|
|||||||
task.status = "success"
|
task.status = "success"
|
||||||
task.cookie = result.cookie
|
task.cookie = result.cookie
|
||||||
task.message = result.message or "登录成功"
|
task.message = result.message or "登录成功"
|
||||||
self._push_log("success", f"[{current}] {acc_info['username']} {task.message}")
|
if self.mode == "relogin":
|
||||||
|
task.ck_check_status = ""
|
||||||
|
task.ck_check_result = None
|
||||||
|
task.ck_checked_at = None
|
||||||
|
task.message = "重新登录成功"
|
||||||
|
self._push_log("success", f"[{current}] {acc_info['username']} 重新登录成功,Cookie 已替换")
|
||||||
|
else:
|
||||||
|
self._push_log("success", f"[{current}] {acc_info['username']} {task.message}")
|
||||||
else:
|
else:
|
||||||
task.status = "failed"
|
if self.mode == "relogin":
|
||||||
task.message = result.message
|
# 重新登录失败时保留旧 Cookie 与成功状态,仅记录失败原因,行不消失
|
||||||
self._push_log("error", f"[{current}] {acc_info['username']} {action_name}失败: {result.message}")
|
task.status = "success"
|
||||||
|
task.message = f"重新登录失败: {result.message}"
|
||||||
|
self._push_log("error", f"[{current}] {acc_info['username']} 重新登录失败: {result.message}")
|
||||||
|
else:
|
||||||
|
task.status = "failed"
|
||||||
|
task.message = result.message
|
||||||
|
self._push_log("error", f"[{current}] {acc_info['username']} {action_name}失败: {result.message}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
task.status = "error"
|
if self.mode == "relogin":
|
||||||
task.message = str(e)
|
task.status = "success"
|
||||||
self._push_log("error", f"[{current}] {acc_info['username']} {action_name}异常: {e}")
|
task.message = f"重新登录异常: {e}"
|
||||||
|
self._push_log("error", f"[{current}] {acc_info['username']} 重新登录异常: {e}")
|
||||||
|
else:
|
||||||
|
task.status = "error"
|
||||||
|
task.message = str(e)
|
||||||
|
self._push_log("error", f"[{current}] {acc_info['username']} {action_name}异常: {e}")
|
||||||
|
|
||||||
task.finished_at = datetime.now(timezone.utc)
|
task.finished_at = datetime.now(timezone.utc)
|
||||||
worker_db.commit()
|
worker_db.commit()
|
||||||
@@ -219,8 +250,8 @@ class LoginBatchRunner:
|
|||||||
"""在线程中执行批量登录。"""
|
"""在线程中执行批量登录。"""
|
||||||
batch_id = self.batch_id
|
batch_id = self.batch_id
|
||||||
concurrency = self.concurrency
|
concurrency = self.concurrency
|
||||||
action_name = "账号检测" if self.mode == "check" else "登录"
|
action_name = "账号检测" if self.mode == "check" else "重新登录" if self.mode == "relogin" else "登录"
|
||||||
self._push_log("info", f"批量{action_name}任务 {batch_id} 开始,共 {len(self.account_ids)} 个账号,并发数: {concurrency}")
|
self._push_log("info", f"批量{action_name}任务 {batch_id} 开始,共 {len(self.account_ids or self.relogin_task_ids)} 个账号,并发数: {concurrency}")
|
||||||
|
|
||||||
# 批次开始前同步一次出口 IP 到白名单,后续 fetch_new_proxy 不再主动同步
|
# 批次开始前同步一次出口 IP 到白名单,后续 fetch_new_proxy 不再主动同步
|
||||||
if self._shared_proxy_fetcher:
|
if self._shared_proxy_fetcher:
|
||||||
@@ -228,56 +259,77 @@ class LoginBatchRunner:
|
|||||||
if msg != "无白名单凭据,跳过":
|
if msg != "无白名单凭据,跳过":
|
||||||
self._push_log("info" if ok else "warning", f"白名单预热: {msg}")
|
self._push_log("info" if ok else "warning", f"白名单预热: {msg}")
|
||||||
|
|
||||||
|
def _append_task_info(task: LoginTask, acc: AccountModel):
|
||||||
|
task.batch_id = batch_id
|
||||||
|
task.status = "pending"
|
||||||
|
task.message = ""
|
||||||
|
task.finished_at = None
|
||||||
|
self.db.flush()
|
||||||
|
task_infos.append({
|
||||||
|
"task_id": task.id,
|
||||||
|
"acc_info": {
|
||||||
|
"username": acc.username,
|
||||||
|
"password": acc.password,
|
||||||
|
"email": acc.email,
|
||||||
|
"email_password": acc.email_password,
|
||||||
|
"email_imap_server": acc.email_imap_server or "",
|
||||||
|
"email_imap_port": acc.email_imap_port or 993,
|
||||||
|
"email_imap_ssl": acc.email_imap_ssl if acc.email_imap_ssl is not None else True,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 创建或复用任务记录(顺序执行,线程安全)
|
# 创建或复用任务记录(顺序执行,线程安全)
|
||||||
task_infos: list[dict] = [] # {task_id, acc_info}
|
task_infos: list[dict] = [] # {task_id, acc_info}
|
||||||
for aid in self.account_ids:
|
if self.relogin_task_ids:
|
||||||
acc = self.db.query(AccountModel).filter(AccountModel.id == aid).first()
|
# 重新登录模式:复用指定 Cookie 记录,登录成功后原地替换 Cookie
|
||||||
if not acc:
|
for task_id in self.relogin_task_ids:
|
||||||
continue
|
task = self.db.query(LoginTask).filter(LoginTask.id == task_id).first()
|
||||||
# 权限检查:客服只能跑分配给自己的
|
if not task:
|
||||||
if "login:view_all" not in self.creator_permissions:
|
|
||||||
if acc.assigned_to != self.created_by:
|
|
||||||
self._push_log("warning", f"跳过无权账号: {acc.username}")
|
|
||||||
continue
|
continue
|
||||||
|
acc = self.db.query(AccountModel).filter(AccountModel.id == task.account_id).first()
|
||||||
|
if not acc:
|
||||||
|
self._push_log("warning", f"跳过无账号的任务 #{task_id}")
|
||||||
|
continue
|
||||||
|
if "login:view_all" not in self.creator_permissions:
|
||||||
|
if acc.assigned_to != self.created_by:
|
||||||
|
self._push_log("warning", f"跳过无权账号: {acc.username}")
|
||||||
|
continue
|
||||||
|
task.ck_check_status = ""
|
||||||
|
task.ck_check_result = None
|
||||||
|
task.ck_checked_at = None
|
||||||
|
_append_task_info(task, acc)
|
||||||
|
else:
|
||||||
|
for aid in self.account_ids:
|
||||||
|
acc = self.db.query(AccountModel).filter(AccountModel.id == aid).first()
|
||||||
|
if not acc:
|
||||||
|
continue
|
||||||
|
# 权限检查:客服只能跑分配给自己的
|
||||||
|
if "login:view_all" not in self.creator_permissions:
|
||||||
|
if acc.assigned_to != self.created_by:
|
||||||
|
self._push_log("warning", f"跳过无权账号: {acc.username}")
|
||||||
|
continue
|
||||||
|
|
||||||
# 复用该账号最近一条失败任务记录,避免重复产生多条
|
# 复用该账号最近一条失败任务记录,避免重复产生多条
|
||||||
existing_task = (
|
existing_task = (
|
||||||
self.db.query(LoginTask)
|
self.db.query(LoginTask)
|
||||||
.filter(LoginTask.account_id == aid, LoginTask.status.in_(["failed", "error"]))
|
.filter(LoginTask.account_id == aid, LoginTask.status.in_(["failed", "error"]))
|
||||||
.order_by(LoginTask.id.desc())
|
.order_by(LoginTask.id.desc())
|
||||||
.first()
|
.first()
|
||||||
)
|
|
||||||
if existing_task:
|
|
||||||
existing_task.batch_id = batch_id
|
|
||||||
existing_task.status = "pending"
|
|
||||||
existing_task.cookie = ""
|
|
||||||
existing_task.message = ""
|
|
||||||
existing_task.finished_at = None
|
|
||||||
task = existing_task
|
|
||||||
else:
|
|
||||||
task = LoginTask(
|
|
||||||
batch_id=batch_id,
|
|
||||||
account_id=aid,
|
|
||||||
status="pending",
|
|
||||||
created_by=self.created_by,
|
|
||||||
)
|
)
|
||||||
self.db.add(task)
|
if existing_task:
|
||||||
|
existing_task.cookie = ""
|
||||||
|
task = existing_task
|
||||||
|
else:
|
||||||
|
task = LoginTask(
|
||||||
|
batch_id=batch_id,
|
||||||
|
account_id=aid,
|
||||||
|
status="pending",
|
||||||
|
created_by=self.created_by,
|
||||||
|
)
|
||||||
|
self.db.add(task)
|
||||||
|
|
||||||
self.db.flush() # 获取 task.id
|
_append_task_info(task, acc)
|
||||||
|
|
||||||
task_infos.append({
|
|
||||||
"task_id": task.id,
|
|
||||||
"acc_info": {
|
|
||||||
"username": acc.username,
|
|
||||||
"password": acc.password,
|
|
||||||
"email": acc.email,
|
|
||||||
"email_password": acc.email_password,
|
|
||||||
"email_imap_server": acc.email_imap_server or "",
|
|
||||||
"email_imap_port": acc.email_imap_port or 993,
|
|
||||||
"email_imap_ssl": acc.email_imap_ssl if acc.email_imap_ssl is not None else True,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
total = len(task_infos)
|
total = len(task_infos)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import api from './client';
|
import api from './client';
|
||||||
import type { BasicSummary, CookieCheckResult, CookieItem, PageParams, PaginatedResponse, MessageDeletedResponse, MessageResponse } from './types';
|
import type { BasicSummary, CookieCheckResult, CookieItem, LoginTaskItem, PageParams, PaginatedResponse, MessageDeletedResponse, MessageResponse } from './types';
|
||||||
|
|
||||||
export const cookieApi = {
|
export const cookieApi = {
|
||||||
list: () => api.get<CookieItem[], CookieItem[]>('/cookies'),
|
list: () => api.get<CookieItem[], CookieItem[]>('/cookies'),
|
||||||
@@ -10,6 +10,10 @@ export const cookieApi = {
|
|||||||
exportCsv: (format?: string) => api.get<Blob, Blob>('/cookies/export', { responseType: 'blob', params: format ? { format } : {} }),
|
exportCsv: (format?: string) => api.get<Blob, Blob>('/cookies/export', { responseType: 'blob', params: format ? { format } : {} }),
|
||||||
check: (ids: number[]) =>
|
check: (ids: number[]) =>
|
||||||
api.post<{ results: CookieCheckResult[] }, { results: CookieCheckResult[] }>('/cookies/check', null, { params: { ids: ids.join(',') } }),
|
api.post<{ results: CookieCheckResult[] }, { results: CookieCheckResult[] }>('/cookies/check', null, { params: { ids: ids.join(',') } }),
|
||||||
|
relogin: (ids: number[]) =>
|
||||||
|
api.post<{ batch_id: string; count: number; skipped: number; success: boolean }, { batch_id: string; count: number; skipped: number; success: boolean }>('/cookies/relogin', { ids }),
|
||||||
|
loginTasks: (batchId: string) =>
|
||||||
|
api.get<LoginTaskItem[], LoginTaskItem[]>('/login/tasks', { params: { batch_id: batchId } }),
|
||||||
delete: (id: number) => api.delete<MessageResponse, MessageResponse>(`/cookies/${id}`),
|
delete: (id: number) => api.delete<MessageResponse, MessageResponse>(`/cookies/${id}`),
|
||||||
deleteBatch: (ids: number[]) => api.delete<MessageDeletedResponse, MessageDeletedResponse>('/cookies/batch', { params: { task_ids: ids.join(',') } }),
|
deleteBatch: (ids: number[]) => api.delete<MessageDeletedResponse, MessageDeletedResponse>('/cookies/batch', { params: { task_ids: ids.join(',') } }),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState, useCallback } from 'react';
|
import { useEffect, useState, useCallback } from 'react';
|
||||||
import { Table, Button, Card, Row, Col, Statistic, Tag, Popconfirm, Space, Typography, Input, theme, Dropdown, Tooltip } from 'antd';
|
import { Table, Button, Card, Row, Col, Statistic, Tag, Popconfirm, Space, Typography, Input, theme, Dropdown, Tooltip } from 'antd';
|
||||||
import { message } from '../utils/antdMessage';
|
import { message } from '../utils/antdMessage';
|
||||||
import { DownloadOutlined, DeleteOutlined, CopyOutlined, SearchOutlined, LoadingOutlined } from '@ant-design/icons';
|
import { DownloadOutlined, DeleteOutlined, CopyOutlined, SearchOutlined, LoadingOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||||
import { cookieApi, type BasicSummary, type CookieCheckResult, type CookieItem } from '../api/modules';
|
import { cookieApi, type BasicSummary, type CookieCheckResult, type CookieItem } from '../api/modules';
|
||||||
import { usePermissions } from '../hooks/usePermissions';
|
import { usePermissions } from '../hooks/usePermissions';
|
||||||
import { formatTime } from '../utils/time';
|
import { formatTime } from '../utils/time';
|
||||||
@@ -52,10 +52,64 @@ export default function CookiePage() {
|
|||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [checkingIds, setCheckingIds] = useState<Set<number>>(new Set());
|
const [checkingIds, setCheckingIds] = useState<Set<number>>(new Set());
|
||||||
const [checkResults, setCheckResults] = useState<Map<number, CookieCheckResult>>(new Map());
|
const [checkResults, setCheckResults] = useState<Map<number, CookieCheckResult>>(new Map());
|
||||||
|
const [reloginIds, setReloginIds] = useState<Set<number>>(new Set());
|
||||||
const { can } = usePermissions();
|
const { can } = usePermissions();
|
||||||
|
|
||||||
const canView = can('cookie:view');
|
const canView = can('cookie:view');
|
||||||
const canExport = can('cookie:export');
|
const canExport = can('cookie:export');
|
||||||
|
const canRelogin = can('login:batch');
|
||||||
|
|
||||||
|
const pollRelogin = (batchId: string, count: number) => {
|
||||||
|
let attempts = 0;
|
||||||
|
const timer = window.setInterval(async () => {
|
||||||
|
attempts += 1;
|
||||||
|
try {
|
||||||
|
const tasks = await cookieApi.loginTasks(batchId);
|
||||||
|
const done = tasks.every((t) => !['pending', 'running'].includes(t.status));
|
||||||
|
if (done) {
|
||||||
|
window.clearInterval(timer);
|
||||||
|
message.success(`重新登录完成: ${tasks.filter((t) => t.status === 'success').length}/${tasks.length} 个成功`);
|
||||||
|
loadCookies();
|
||||||
|
loadSummary();
|
||||||
|
} else if (attempts > 120) {
|
||||||
|
window.clearInterval(timer);
|
||||||
|
message.warning('重新登录超时,请到登录任务页查看进度');
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
window.clearInterval(timer);
|
||||||
|
message.warning('查询重登进度失败,请稍后手动刷新');
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
void count;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRelogin = async (ids: number[]) => {
|
||||||
|
if (ids.length === 0) return;
|
||||||
|
setReloginIds((prev) => new Set([...prev, ...ids]));
|
||||||
|
try {
|
||||||
|
const res = await cookieApi.relogin(ids);
|
||||||
|
const tip = res.skipped > 0 ? `,${res.skipped} 条因账号缺少登录凭据被跳过` : '';
|
||||||
|
message.success(`已开始重新登录 ${res.count} 个账号${tip}`);
|
||||||
|
pollRelogin(res.batch_id, res.count);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
message.error(getErrorMessage(e));
|
||||||
|
} finally {
|
||||||
|
setReloginIds((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
for (const id of ids) next.delete(id);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReloginSelected = () => {
|
||||||
|
const ids = selectedRowKeys.map((k) => Number(k));
|
||||||
|
if (ids.length === 0) {
|
||||||
|
message.warning('请先选择要重新登录的 Cookie');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void handleRelogin(ids);
|
||||||
|
};
|
||||||
|
|
||||||
const handleCheck = async (ids: number[]) => {
|
const handleCheck = async (ids: number[]) => {
|
||||||
if (ids.length === 0) return;
|
if (ids.length === 0) return;
|
||||||
@@ -234,6 +288,7 @@ export default function CookiePage() {
|
|||||||
{
|
{
|
||||||
title: 'Cookie',
|
title: 'Cookie',
|
||||||
dataIndex: 'cookie_preview',
|
dataIndex: 'cookie_preview',
|
||||||
|
width: 300,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (val: string) => {
|
render: (val: string) => {
|
||||||
if (!canView) return <Tag>***</Tag>;
|
if (!canView) return <Tag>***</Tag>;
|
||||||
@@ -306,6 +361,21 @@ export default function CookiePage() {
|
|||||||
检测
|
检测
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{canRelogin && (
|
||||||
|
<Popconfirm
|
||||||
|
title="用账号信息重新登录并替换失效 Cookie?"
|
||||||
|
onConfirm={() => handleRelogin([record.id])}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<ReloadOutlined />}
|
||||||
|
loading={reloginIds.has(record.id)}
|
||||||
|
disabled={reloginIds.has(record.id)}
|
||||||
|
>
|
||||||
|
重登
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
)}
|
||||||
{canExport && (
|
{canExport && (
|
||||||
<Popconfirm title="确认删除?" onConfirm={() => handleDelete(record.id)}>
|
<Popconfirm title="确认删除?" onConfirm={() => handleDelete(record.id)}>
|
||||||
<Button danger size="small" icon={<DeleteOutlined />} />
|
<Button danger size="small" icon={<DeleteOutlined />} />
|
||||||
@@ -337,6 +407,20 @@ export default function CookiePage() {
|
|||||||
检测选中 {selectedRowKeys.length > 0 && `(${selectedRowKeys.length})`}
|
检测选中 {selectedRowKeys.length > 0 && `(${selectedRowKeys.length})`}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{canRelogin && (
|
||||||
|
<Popconfirm
|
||||||
|
title={`确认重新登录选中的 ${selectedRowKeys.length} 条 Cookie?将用账号信息重新登录并替换`}
|
||||||
|
onConfirm={handleReloginSelected}
|
||||||
|
disabled={selectedRowKeys.length === 0}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
icon={<ReloadOutlined />}
|
||||||
|
disabled={selectedRowKeys.length === 0}
|
||||||
|
>
|
||||||
|
重登选中 {selectedRowKeys.length > 0 && `(${selectedRowKeys.length})`}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
)}
|
||||||
{canExport && (
|
{canExport && (
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title={`确认删除选中的 ${selectedRowKeys.length} 条 Cookie?`}
|
title={`确认删除选中的 ${selectedRowKeys.length} 条 Cookie?`}
|
||||||
@@ -431,7 +515,7 @@ export default function CookiePage() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
scroll={{ x: 900 }}
|
scroll={{ x: 1210 }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user