This commit is contained in:
Your Name
2026-02-12 18:22:02 +08:00
parent a12bc92351
commit c030902c0a
6 changed files with 72 additions and 97 deletions

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
BOSS 账号 API
BOSS 账号 API(需要登录)
- POST /api/accounts -> 前台添加账号时绑定到指定电脑
- POST /api/accounts/check -> 提交环境名称,自动派发 check_login 任务
- GET /api/accounts -> 查询所有账号登录状态
@@ -17,52 +17,25 @@ from server.models import AccountBindRequest, CheckLoginRequest, TaskCreate
from server.core.worker_manager import worker_manager
from server.core.task_dispatcher import task_dispatcher
from server import db
from server.auth_dep import require_auth
from server.api.deps import require_auth, parse_body
router = APIRouter(prefix="/api/accounts", tags=["accounts"], dependencies=[Depends(require_auth)])
async def _parse_body(request: Request) -> dict:
"""
兼容两种请求体:
1) application/json
2) multipart/form-data 或 x-www-form-urlencoded
"""
ctype = (request.headers.get("content-type") or "").lower()
if "application/json" in ctype:
data = await request.json()
if not isinstance(data, dict):
raise HTTPException(status_code=422, detail="JSON body 必须是对象")
return data
form = await request.form()
return dict(form)
@router.post("", status_code=201)
async def bind_account(request: Request):
"""前台添加账号:保存账号环境与电脑绑定关系。"""
req = AccountBindRequest(**(await _parse_body(request)))
req = AccountBindRequest(**(await parse_body(request)))
db.bind_account_to_worker(worker_id=req.worker_id, browser_name=req.browser_name)
return {"message": f"账号绑定已保存: {req.browser_name} -> {req.worker_id}"}
# ────────────────────────── 一键检测登录 ──────────────────────────
@router.post("/check", status_code=201)
async def check_login(request: Request):
"""
前端提交
{
"browser_name": "环境名称"
// 可选 "worker_id": "pc-1"
}
系统自动:
1. 如果请求没传 worker_id则按已绑定关系查 worker_id
2. 派发 check_login 任务Worker 会在比特浏览器中按名称查找该环境并打开)
3. 返回任务 ID前端可轮询 GET /api/tasks/{task_id} 获取结果
前端提交 browser_name可选 worker_id→ 自动派发 check_login 任务。
"""
req = CheckLoginRequest(**(await _parse_body(request)))
req = CheckLoginRequest(**(await parse_body(request)))
worker_id = req.worker_id
if not worker_id:
bind = db.get_account_by_name(req.browser_name)
@@ -73,11 +46,9 @@ async def check_login(request: Request):
)
worker_id = bind.get("worker_id")
# 检查 Worker 是否在线
if not worker_manager.is_online(worker_id):
raise HTTPException(status_code=503, detail=f"Worker {worker_id} 不在线")
# 创建 check_login 任务
task_req = TaskCreate(
task_type=TaskType.CHECK_LOGIN,
worker_id=worker_id,
@@ -86,7 +57,6 @@ async def check_login(request: Request):
)
task = task_dispatcher.create_task(task_req)
# 通过 WebSocket 派发
ws = worker_manager.get_ws(worker_id)
if not ws:
task.status = TaskStatus.FAILED
@@ -106,8 +76,6 @@ async def check_login(request: Request):
}
# ────────────────────────── 查询账号状态 ──────────────────────────
@router.get("")
async def list_accounts(worker_id: Optional[str] = None):
"""查询 BOSS 账号登录状态列表。"""

View File

@@ -1,3 +1,7 @@
# -*- coding: utf-8 -*-
"""
认证 API登录无需 token
"""
from __future__ import annotations
import uuid
@@ -6,23 +10,11 @@ from fastapi import APIRouter, HTTPException, Request, Response, status
from server import config, db
from server.models import LoginRequest, LoginResponse
from server.api.deps import parse_body
router = APIRouter(prefix="/api/auth", tags=["auth"])
async def _parse_body(request: Request) -> dict:
"""兼容 JSON 和 form-data。"""
ctype = (request.headers.get("content-type") or "").lower()
if "application/json" in ctype:
data = await request.json()
if not isinstance(data, dict):
raise HTTPException(status_code=422, detail="JSON body 必须是对象")
return data
form = await request.form()
return dict(form)
@router.post("/login", response_model=LoginResponse)
async def login(request: Request, response: Response):
"""
@@ -32,7 +24,7 @@ async def login(request: Request, response: Response):
- 通过 Set-Cookie 返回 auth_token前端后续请求自动携带
- 下一次登录会生成新 token旧 token 自动失效
"""
req = LoginRequest(**(await _parse_body(request)))
req = LoginRequest(**(await parse_body(request)))
if req.username != config.ADMIN_USERNAME or req.password != config.ADMIN_PASSWORD:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="用户名或密码错误")

49
server/api/deps.py Normal file
View File

@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
"""
API 公共依赖:认证校验、请求体解析。
所有路由文件统一从这里导入,避免重复代码。
"""
from __future__ import annotations
from fastapi import Cookie, HTTPException, Request, status
from server import db
# ────────────────────────── 认证依赖 ──────────────────────────
async def require_auth(auth_token: str | None = Cookie(default=None)):
"""
从 cookie 中读取 auth_token 并校验。
用法:在 Router 或单个接口上加 dependencies=[Depends(require_auth)]
"""
if auth_token is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="未登录,请先调用 POST /api/auth/login",
)
user = db.get_user_by_token(auth_token)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="登录已失效,请重新登录",
)
return user
# ────────────────────────── 请求体解析 ──────────────────────────
async def parse_body(request: Request) -> dict:
"""
兼容两种请求体格式:
1) application/json
2) multipart/form-data 或 x-www-form-urlencoded
"""
ctype = (request.headers.get("content-type") or "").lower()
if "application/json" in ctype:
data = await request.json()
if not isinstance(data, dict):
raise HTTPException(status_code=422, detail="JSON body 必须是对象")
return data
form = await request.form()
return dict(form)

View File

@@ -1,32 +1,28 @@
# -*- coding: utf-8 -*-
"""
任务提交与查询 API。
任务提交与查询 API(需要登录)
"""
from __future__ import annotations
from typing import List, Optional
from fastapi import APIRouter, HTTPException
from fastapi import APIRouter, Depends, HTTPException
from common.protocol import TaskStatus
from server.models import TaskCreate, TaskOut
from server.core.worker_manager import worker_manager
from server.core.task_dispatcher import task_dispatcher
from server.api.deps import require_auth
router = APIRouter(prefix="/api/tasks", tags=["tasks"])
router = APIRouter(prefix="/api/tasks", tags=["tasks"], dependencies=[Depends(require_auth)])
@router.post("", response_model=TaskOut, status_code=201)
async def create_task(req: TaskCreate):
"""
提交一个新任务。
路由规则(优先级从高到低):
1. 如果指定了 worker_id → 直接发到该 Worker
2. 如果指定了 account_name → 查找拥有该浏览器的 Worker
3. 两者都没有 → 400 错误
路由规则worker_id > account_name。
"""
# 确定目标 worker_id
target_worker_id = req.worker_id
if not target_worker_id and req.account_name:
@@ -38,23 +34,14 @@ async def create_task(req: TaskCreate):
)
if not target_worker_id:
raise HTTPException(
status_code=400,
detail="请指定 worker_id 或 account_name",
)
raise HTTPException(status_code=400, detail="请指定 worker_id 或 account_name")
# 检查 Worker 是否在线
if not worker_manager.is_online(target_worker_id):
raise HTTPException(
status_code=503,
detail=f"Worker {target_worker_id} 不在线",
)
raise HTTPException(status_code=503, detail=f"Worker {target_worker_id} 不在线")
# 创建任务
req.worker_id = target_worker_id
task = task_dispatcher.create_task(req)
# 通过 WebSocket 派发
ws = worker_manager.get_ws(target_worker_id)
if not ws:
task.status = TaskStatus.FAILED
@@ -65,9 +52,7 @@ async def create_task(req: TaskCreate):
if not success:
raise HTTPException(status_code=503, detail=f"任务派发失败: {task.error}")
# 更新 Worker 当前任务
worker_manager.set_current_task(target_worker_id, task.task_id)
return _to_out(task)

View File

@@ -1,14 +1,16 @@
# -*- coding: utf-8 -*-
"""
Worker 查询 API。
Worker 查询 API(需要登录)
"""
from fastapi import APIRouter, HTTPException
from typing import List
from fastapi import APIRouter, Depends, HTTPException
from server.models import WorkerOut
from server.core.worker_manager import worker_manager
from server.api.deps import require_auth
router = APIRouter(prefix="/api/workers", tags=["workers"])
router = APIRouter(prefix="/api/workers", tags=["workers"], dependencies=[Depends(require_auth)])
@router.get("", response_model=List[WorkerOut])

View File

@@ -1,21 +0,0 @@
from __future__ import annotations
from fastapi import Cookie, HTTPException, status
from server import db
async def require_auth(auth_token: str | None = Cookie(default=None)):
"""
认证依赖:
- 从 cookie 中读取名为 auth_token 的 token
- 校验 token 是否存在且有效
"""
if auth_token is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="未登录")
user = db.get_user_by_token(auth_token)
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="登录已失效,请重新登录")
return user