哈哈
This commit is contained in:
@@ -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 账号登录状态列表。"""
|
||||
|
||||
@@ -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
49
server/api/deps.py
Normal 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)
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user