diff --git a/routes/admin.py b/routes/admin.py index 2cc81ae..296d4ee 100644 --- a/routes/admin.py +++ b/routes/admin.py @@ -62,11 +62,13 @@ async def get_config(_: bool = Depends(verify_admin)): "claude_model_mapping": CONFIG.get("claude_model_mapping", {}), } for acc in CONFIG.get("accounts", []): + token = acc.get("token", "") safe_acc = { "email": acc.get("email", ""), "mobile": acc.get("mobile", ""), "has_password": bool(acc.get("password")), - "has_token": bool(acc.get("token")), + "has_token": bool(token), + "token_preview": token[:12] + "..." if len(token) > 12 else "" if token else "", } safe_config["accounts"].append(safe_acc) return JSONResponse(content=safe_config) @@ -288,14 +290,9 @@ async def validate_all_accounts(_: bool = Depends(verify_admin)): # 账号 API 测试(实际发送请求) # ---------------------------------------------------------------------- async def test_account_api(account: dict, model: str = "deepseek-chat", message: str = "") -> dict: - """测试单个账号的 API 调用能力 - - 如果提供了 message,会发送实际请求并返回 AI 回复; - 否则只测试创建会话(快速测试)。 - """ + """测试单个账号的 API 调用能力(通过创建会话验证)""" from curl_cffi import requests as cffi_requests - from core.deepseek import DEEPSEEK_CREATE_SESSION_URL, DEEPSEEK_CHAT_COMPLETION_URL, BASE_HEADERS - from core.pow import get_pow_response_direct + from core.deepseek import DEEPSEEK_CREATE_SESSION_URL, BASE_HEADERS acc_id = get_account_identifier(account) result = { @@ -304,7 +301,6 @@ async def test_account_api(account: dict, model: str = "deepseek-chat", message: "response_time": 0, "message": "", "model": model, - "reply": "", # AI 回复内容 } import time @@ -323,7 +319,7 @@ async def test_account_api(account: dict, model: str = "deepseek-chat", message: headers = {**BASE_HEADERS, "authorization": f"Bearer {token}"} - # 1. 创建会话 + # 创建会话来测试 API 可用性 session_resp = cffi_requests.post( DEEPSEEK_CREATE_SESSION_URL, headers=headers, @@ -343,70 +339,8 @@ async def test_account_api(account: dict, model: str = "deepseek-chat", message: account["token"] = "" return result - session_id = session_data["data"]["biz_data"]["id"] - - # 如果没有消息,只测试会话创建 - if not message: - result["success"] = True - result["message"] = "API 测试成功" - result["response_time"] = round((time.time() - start_time) * 1000) - return result - - # 2. 发送实际请求 - from core.pow import get_pow_response_direct - pow_resp = get_pow_response_direct(headers) - if not pow_resp: - result["message"] = "获取 PoW 失败" - return result - - chat_headers = {**headers, "x-ds-pow-response": pow_resp} - thinking_enabled = "reasoner" in model.lower() - search_enabled = "search" in model.lower() - - payload = { - "chat_session_id": session_id, - "parent_message_id": None, - "prompt": message, - "ref_file_ids": [], - "thinking_enabled": thinking_enabled, - "search_enabled": search_enabled, - } - - chat_resp = cffi_requests.post( - DEEPSEEK_CHAT_COMPLETION_URL, - headers=chat_headers, - json=payload, - impersonate="safari15_3", - timeout=60, - stream=True, - ) - - if chat_resp.status_code != 200: - result["message"] = f"对话请求失败: HTTP {chat_resp.status_code}" - return result - - # 解析流式响应获取回复 - reply_text = "" - for line in chat_resp.iter_lines(): - try: - line_str = line.decode("utf-8") - if line_str.startswith("data:"): - data_str = line_str[5:].strip() - if data_str == "[DONE]": - break - import json - chunk = json.loads(data_str) - choices = chunk.get("choices", []) - if choices: - delta = choices[0].get("delta", {}) - if delta.get("type") == "text": - reply_text += delta.get("content", "") - except: - pass - result["success"] = True result["message"] = "API 测试成功" - result["reply"] = reply_text[:500] # 限制长度 result["response_time"] = round((time.time() - start_time) * 1000) except Exception as e: diff --git a/tests/test_all.py b/tests/test_all.py index 4424f55..1ef14e4 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -558,6 +558,80 @@ class TestRunner: "details": {"input_length": len(long_text)} } + # ===================================================================== + # 管理 API 测试 + # ===================================================================== + + def test_admin_config(self) -> dict: + """测试管理配置 API""" + resp = requests.get( + f"{self.endpoint}/admin/config", + timeout=10 + ) + + if resp.status_code != 200: + return {"success": False, "message": f"状态码: {resp.status_code}"} + + data = resp.json() + + # 验证返回结构 + if "accounts" not in data: + return {"success": False, "message": "响应缺少 accounts 字段"} + + # 验证 token_preview 字段存在 + accounts = data.get("accounts", []) + if accounts: + first_acc = accounts[0] + if "token_preview" not in first_acc: + return {"success": False, "message": "响应缺少 token_preview 字段"} + + return { + "success": True, + "message": f"获取配置成功,{len(accounts)} 个账号", + "details": {"account_count": len(accounts)} + } + + def test_admin_account_test(self) -> dict: + """测试单账号 API 测试端点""" + # 先获取配置以获取账号 + config_resp = requests.get(f"{self.endpoint}/admin/config", timeout=10) + if config_resp.status_code != 200: + return {"success": False, "message": "获取配置失败"} + + accounts = config_resp.json().get("accounts", []) + if not accounts: + return {"success": False, "message": "没有可测试的账号"} + + # 测试第一个账号 + first_acc = accounts[0] + identifier = first_acc.get("email") or first_acc.get("mobile") + + resp = requests.post( + f"{self.endpoint}/admin/accounts/test", + json={"identifier": identifier}, + timeout=30 + ) + + if resp.status_code != 200: + return {"success": False, "message": f"状态码: {resp.status_code}"} + + data = resp.json() + + # 验证返回结构 + required_fields = ["account", "success", "response_time", "message"] + for field in required_fields: + if field not in data: + return {"success": False, "message": f"响应缺少 {field} 字段"} + + if not data["success"]: + return {"success": False, "message": f"账号测试失败: {data['message']}"} + + return { + "success": True, + "message": f"账号 {identifier} 测试成功 ({data['response_time']}ms)", + "details": {"response_time": data["response_time"]} + } + # ===================================================================== # 运行测试 # ===================================================================== @@ -599,6 +673,10 @@ class TestRunner: self.run_test("多轮对话", self.test_multi_turn_conversation) self.run_test("长输入处理", self.test_long_input) + # 管理 API 测试 + self.run_test("管理配置 API", self.test_admin_config) + self.run_test("账号测试 API", self.test_admin_account_test) + # 输出测试报告 self.print_report() diff --git a/version.txt b/version.txt index d0149fe..42a0d46 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.3.4 +1.6.11 \ No newline at end of file diff --git a/webui/src/components/AccountManager.jsx b/webui/src/components/AccountManager.jsx index 595d195..838418f 100644 --- a/webui/src/components/AccountManager.jsx +++ b/webui/src/components/AccountManager.jsx @@ -10,6 +10,7 @@ export default function AccountManager({ config, onRefresh, onMessage }) { const [validatingAll, setValidatingAll] = useState(false) const [testing, setTesting] = useState({}) // 单个账号测试状态 const [testingAll, setTestingAll] = useState(false) + const [batchProgress, setBatchProgress] = useState({ current: 0, total: 0, results: [] }) const [queueStatus, setQueueStatus] = useState(null) // 获取队列状态 @@ -137,20 +138,41 @@ export default function AccountManager({ config, onRefresh, onMessage }) { } } - // 批量验证所有账号 + // 批量验证所有账号(带进度) const validateAllAccounts = async () => { - if (!confirm('确定要验证所有账号?这可能需要一些时间。')) return + if (!confirm('确定要验证所有账号?')) return + const accounts = config.accounts || [] + if (accounts.length === 0) return + setValidatingAll(true) - try { - const res = await fetch('/admin/accounts/validate-all', { method: 'POST' }) - const data = await res.json() - onMessage('success', `验证完成: ${data.valid}/${data.total} 个账号有效`) - onRefresh() - } catch (e) { - onMessage('error', '批量验证失败: ' + e.message) - } finally { - setValidatingAll(false) + setBatchProgress({ current: 0, total: accounts.length, results: [] }) + + let validCount = 0 + const results = [] + + for (let i = 0; i < accounts.length; i++) { + const acc = accounts[i] + const id = acc.email || acc.mobile + + try { + const res = await fetch('/admin/accounts/validate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ identifier: id }), + }) + const data = await res.json() + results.push({ id, success: data.valid, message: data.message }) + if (data.valid) validCount++ + } catch (e) { + results.push({ id, success: false, message: e.message }) + } + + setBatchProgress({ current: i + 1, total: accounts.length, results: [...results] }) } + + onMessage('success', `验证完成: ${validCount}/${accounts.length} 个账号有效`) + onRefresh() + setValidatingAll(false) } // 测试单个账号 API @@ -176,24 +198,41 @@ export default function AccountManager({ config, onRefresh, onMessage }) { } } - // 批量测试所有账号 API + // 批量测试所有账号 API(带进度) const testAllAccounts = async () => { - if (!confirm('确定要测试所有账号的 API?这可能需要较长时间。')) return + if (!confirm('确定要测试所有账号的 API?')) return + const accounts = config.accounts || [] + if (accounts.length === 0) return + setTestingAll(true) - try { - const res = await fetch('/admin/accounts/test-all', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({}), - }) - const data = await res.json() - onMessage('success', `API 测试完成: ${data.success}/${data.total} 个账号可用`) - onRefresh() - } catch (e) { - onMessage('error', '批量 API 测试失败: ' + e.message) - } finally { - setTestingAll(false) + setBatchProgress({ current: 0, total: accounts.length, results: [] }) + + let successCount = 0 + const results = [] + + for (let i = 0; i < accounts.length; i++) { + const acc = accounts[i] + const id = acc.email || acc.mobile + + try { + const res = await fetch('/admin/accounts/test', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ identifier: id }), + }) + const data = await res.json() + results.push({ id, success: data.success, message: data.message, time: data.response_time }) + if (data.success) successCount++ + } catch (e) { + results.push({ id, success: false, message: e.message }) + } + + setBatchProgress({ current: i + 1, total: accounts.length, results: [...results] }) } + + onMessage('success', `API 测试完成: ${successCount}/${accounts.length} 个账号可用`) + onRefresh() + setTestingAll(false) } return ( @@ -267,6 +306,31 @@ export default function AccountManager({ config, onRefresh, onMessage }) { + {/* 批量操作进度条 */} + {(testingAll || validatingAll) && batchProgress.total > 0 && ( +
+
+ {testingAll ? '🧪 批量测试中...' : '✅ 批量验证中...'} + {batchProgress.current}/{batchProgress.total} +
+
+
+
+ {batchProgress.results.length > 0 && ( +
+ {batchProgress.results.map((r, i) => ( +
+ {r.success ? '✓' : '✗'} {r.id} {r.time ? `(${r.time}ms)` : ''} +
+ ))} +
+ )} +
+ )} + {config.accounts?.length > 0 ? (
{config.accounts.map((acc, i) => { @@ -278,6 +342,11 @@ export default function AccountManager({ config, onRefresh, onMessage }) { {acc.has_token ? '已登录' : '未登录'} + {acc.token_preview && ( + + 🔑 {acc.token_preview} + + )}