diff --git a/.github/workflows/build-webui.yml b/.github/workflows/build-webui.yml deleted file mode 100644 index e9fdf45..0000000 --- a/.github/workflows/build-webui.yml +++ /dev/null @@ -1,76 +0,0 @@ -# 自动构建 WebUI 并提交构建产物 -# 触发条件:webui 目录下的文件变更 - -name: Build WebUI - -on: - push: - branches: - - main - paths: - - 'webui/**' - - '.github/workflows/build-webui.yml' - pull_request: - branches: - - main - paths: - - 'webui/**' - # 允许手动触发 - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - - # 只在主仓库运行,避免 fork 仓库运行 - if: github.repository == 'CJackHwang/ds2api' - - permissions: - contents: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - cache-dependency-path: webui/package-lock.json - - - name: Install dependencies - working-directory: webui - run: npm ci - - - name: Build WebUI - working-directory: webui - run: npm run build - - - name: Check for changes - id: check_changes - run: | - git add static/admin - if git diff --staged --quiet; then - echo "changed=false" >> $GITHUB_OUTPUT - else - echo "changed=true" >> $GITHUB_OUTPUT - fi - - - name: Commit and push changes - if: steps.check_changes.outputs.changed == 'true' && github.event_name == 'push' - run: | - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git commit -m "chore: auto-build WebUI [skip ci]" - git push - - - name: Upload build artifacts (for PR review) - if: github.event_name == 'pull_request' - uses: actions/upload-artifact@v4 - with: - name: webui-build - path: static/admin - retention-days: 7 diff --git a/DEPLOY.md b/DEPLOY.md index b1c47d9..1da68d5 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -131,8 +131,8 @@ WebUI 开发服务器会启动在 `http://localhost:5173`,并自动代理 API WebUI 构建产物位于 `static/admin/` 目录。 **自动构建(推荐)**: -- 当 `webui/` 目录下的文件变更并推送到 `main` 分支时,GitHub Actions 会自动构建并提交产物 -- PR 合并时会自动触发构建 +- 当前由 Vercel 在部署时执行 WebUI 构建(见 `vercel.json` 的 `buildCommand`) +- GitHub Actions 的 WebUI 自动构建流程已关闭 **手动构建**: ```bash @@ -145,7 +145,7 @@ npm install npm run build ``` -> **贡献者注意**:修改 WebUI 后无需手动构建,CI 会自动处理。 +> **贡献者注意**:修改 WebUI 后无需手动构建,Vercel 部署会自动构建。 --- diff --git a/routes/admin/accounts.py b/routes/admin/accounts.py index f53bb55..6603acd 100644 --- a/routes/admin/accounts.py +++ b/routes/admin/accounts.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Admin 账号管理模块 - 账号验证和测试""" +"""Admin 账号管理模块 - 账号测试与导入""" import asyncio import json import base64 @@ -24,145 +24,6 @@ router = APIRouter() -# ---------------------------------------------------------------------- -# 账号验证 -# ---------------------------------------------------------------------- -async def validate_single_account(account: dict) -> dict: - """验证单个账号的有效性""" - acc_id = get_account_identifier(account) - result = { - "account": acc_id, - "valid": False, - "has_token": bool(account.get("token", "").strip()), - "message": "", - } - - def _is_token_invalid(status_code: int, data: dict) -> bool: - msg = (data.get("msg") or data.get("message") or "").lower() - code = data.get("code") - return status_code in {401, 403} or code in {40001, 40002, 40003} or "token" in msg or "unauthorized" in msg - - def _create_session(token: str) -> dict: - headers = {**BASE_HEADERS, "authorization": f"Bearer {token}"} - try: - session_resp = cffi_requests.post( - DEEPSEEK_CREATE_SESSION_URL, - headers=headers, - json={"agent": "chat"}, - impersonate="safari15_3", - timeout=15, - ) - except Exception as e: - return {"success": False, "message": f"请求异常: {e}", "status_code": 0, "data": {}} - - try: - data = session_resp.json() - except Exception: - data = {} - finally: - session_resp.close() - if session_resp.status_code == 200 and data.get("code") == 0: - return { - "success": True, - "session_id": data.get("data", {}).get("biz_data", {}).get("id"), - "status_code": session_resp.status_code, - "data": data, - } - return { - "success": False, - "message": data.get("msg") or f"HTTP {session_resp.status_code}", - "status_code": session_resp.status_code, - "data": data, - } - - try: - token = account.get("token", "").strip() - if token: - session_result = _create_session(token) - if session_result["success"]: - result["valid"] = True - result["message"] = "Token 有效" - return result - - if _is_token_invalid(session_result["status_code"], session_result["data"]): - token = "" - account["token"] = "" - - if not token: - try: - login_deepseek_via_account(account) - token = account.get("token", "").strip() - session_result = _create_session(token) - if session_result["success"]: - result["valid"] = True - result["has_token"] = True - result["message"] = "登录成功并验证通过" - else: - result["message"] = f"登录成功但验证失败: {session_result['message']}" - except Exception as e: - result["valid"] = False - result["message"] = f"登录失败: {str(e)}" - except Exception as e: - result["message"] = f"验证出错: {str(e)}" - - return result - - -@router.post("/accounts/validate") -async def validate_account(request: Request, _: bool = Depends(verify_admin)): - """验证单个账号""" - data = await request.json() - identifier = data.get("identifier", "").strip() - - if not identifier: - raise HTTPException(status_code=400, detail="需要账号标识(email 或 mobile)") - - account = None - for acc in CONFIG.get("accounts", []): - if acc.get("email") == identifier or acc.get("mobile") == identifier: - account = acc - break - - if not account: - raise HTTPException(status_code=404, detail="账号不存在") - - result = await validate_single_account(account) - - if result["valid"] and result["has_token"]: - save_config(CONFIG) - - return JSONResponse(content=result) - - -@router.post("/accounts/validate-all") -async def validate_all_accounts(_: bool = Depends(verify_admin)): - """批量验证所有账号""" - accounts = CONFIG.get("accounts", []) - if not accounts: - return JSONResponse(content={ - "total": 0, "valid": 0, "invalid": 0, "results": [], - }) - - results = [] - valid_count = 0 - - for acc in accounts: - result = await validate_single_account(acc) - results.append(result) - if result["valid"]: - valid_count += 1 - await asyncio.sleep(0.5) - - save_config(CONFIG) - - return JSONResponse(content={ - "total": len(accounts), - "valid": valid_count, - "invalid": len(accounts) - valid_count, - "results": results, - }) - - # ---------------------------------------------------------------------- # 账号 API 测试 # ---------------------------------------------------------------------- diff --git a/routes/home.py b/routes/home.py index dad0fb8..7199f33 100644 --- a/routes/home.py +++ b/routes/home.py @@ -290,14 +290,19 @@ async def webui(request: Request, path: str = ""): if path and "." in path: file_path = os.path.join(STATIC_ADMIN_DIR, path) if os.path.isfile(file_path): - return FileResponse(file_path) + cache_control = "public, max-age=31536000, immutable" + if path.startswith("assets/"): + headers = {"Cache-Control": cache_control} + else: + headers = {"Cache-Control": "no-store, must-revalidate"} + return FileResponse(file_path, headers=headers) return HTMLResponse(content="Not Found", status_code=404) # 否则返回 index.html(SPA 路由) index_path = os.path.join(STATIC_ADMIN_DIR, "index.html") if os.path.isfile(index_path): - return FileResponse(index_path) + headers = {"Cache-Control": "no-store, must-revalidate"} + return FileResponse(index_path, headers=headers) return HTMLResponse(content="index.html not found", status_code=404) - diff --git a/vercel.json b/vercel.json index 98e2637..a34b297 100644 --- a/vercel.json +++ b/vercel.json @@ -6,10 +6,31 @@ "use": "@vercel/python" } ], - "routes": [ + "buildCommand": "bash scripts/build-webui.sh", + "rewrites": [ { - "src": "/(.*)", - "dest": "app.py" + "source": "/(.*)", + "destination": "/app.py" + } + ], + "headers": [ + { + "source": "/admin/assets/(.*)", + "headers": [ + { + "key": "Cache-Control", + "value": "public, max-age=31536000, immutable" + } + ] + }, + { + "source": "/admin/(.*)", + "headers": [ + { + "key": "Cache-Control", + "value": "no-store, must-revalidate" + } + ] } ] -} \ No newline at end of file +} diff --git a/webui/src/components/AccountManager.jsx b/webui/src/components/AccountManager.jsx index 66658a0..dd0d48d 100644 --- a/webui/src/components/AccountManager.jsx +++ b/webui/src/components/AccountManager.jsx @@ -2,12 +2,8 @@ import { useState, useEffect } from 'react' import { Plus, Trash2, - RefreshCw, CheckCircle2, - AlertCircle, - Search, Play, - MoreHorizontal, X, Server, ShieldCheck, @@ -23,8 +19,6 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch const [copiedKey, setCopiedKey] = useState(null) const [newAccount, setNewAccount] = useState({ email: '', mobile: '', password: '' }) const [loading, setLoading] = useState(false) - const [validating, setValidating] = useState({}) - const [validatingAll, setValidatingAll] = useState(false) const [testing, setTesting] = useState({}) const [testingAll, setTestingAll] = useState(false) const [batchProgress, setBatchProgress] = useState({ current: 0, total: 0, results: [] }) @@ -133,60 +127,6 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch } } - const validateAccount = async (identifier) => { - setValidating(prev => ({ ...prev, [identifier]: true })) - try { - const res = await apiFetch('/admin/accounts/validate', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ identifier }), - }) - const data = await res.json() - onMessage(data.valid ? 'success' : 'error', `${identifier}: ${data.message}`) - onRefresh() - } catch (e) { - onMessage('error', 'Validation failed: ' + e.message) - } finally { - setValidating(prev => ({ ...prev, [identifier]: false })) - } - } - - const validateAllAccounts = async () => { - if (!confirm('校验所有账号?这可能需要一些时间。')) return - const accounts = config.accounts || [] - if (accounts.length === 0) return - - setValidatingAll(true) - 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 apiFetch('/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', `Completed: ${validCount}/${accounts.length} valid`) - onRefresh() - setValidatingAll(false) - } - const testAccount = async (identifier) => { setTesting(prev => ({ ...prev, [identifier]: true })) try { @@ -347,20 +287,12 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch