From af9be21b32f172584a98a3f162602bee81e39868 Mon Sep 17 00:00:00 2001 From: ddrwode Date: Sun, 1 Mar 2026 23:39:47 +0800 Subject: [PATCH] ha'ha --- API文档.md | 14 ++-- server/api/tasks.py | 177 +++++++++++++++++++++++++++++++++----------- server/urls.py | 2 +- 3 files changed, 141 insertions(+), 52 deletions(-) diff --git a/API文档.md b/API文档.md index 8d9a4dc..da1b629 100644 --- a/API文档.md +++ b/API文档.md @@ -483,30 +483,30 @@ Set-Cookie: auth_token=a1b2c3d4e5f6...; HttpOnly; Max-Age=31536000; SameSite=Lax --- -### GET /api/tasks/{task_id} +### GET /api/tasks/{account_id} -**说明**: 查询指定任务的状态和结果。 +**说明**: 按账号 ID 查询任务列表(不支持按 task_id 查询)。 **状态码**: | 状态码 | 说明 | |--------|------| -| 200 | 成功,返回单个任务对象 | +| 200 | 成功,返回任务数组 | | 401 | 未登录或 token 失效 | -| 404 | 任务不存在 | +| 404 | 账号不存在 | **路径参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| -| task_id | string | 是 | 任务 ID | +| account_id | int | 是 | 账号 ID(即 `/api/accounts` 返回的 `id`) | -**成功响应** (200): 同上单个任务对象 +**成功响应** (200): 同上任务对象数组 **失败响应** (404): ```json { - "detail": "任务 xxx 不存在" + "detail": "账号 xxx 不存在" } ``` diff --git a/server/api/tasks.py b/server/api/tasks.py index 5f4def9..0de241e 100644 --- a/server/api/tasks.py +++ b/server/api/tasks.py @@ -6,6 +6,7 @@ import json import logging from datetime import datetime +from typing import Optional from asgiref.sync import async_to_sync from rest_framework import status as http_status @@ -13,8 +14,8 @@ from rest_framework.decorators import api_view from common.protocol import TaskStatus, TaskType from server.core.response import api_success, api_error -from server.models import BossAccount, TaskCreate -from server.serializers import TaskCreateSerializer, TaskOutSerializer +from server.models import BossAccount, TaskCreate, TaskLog +from server.serializers import TaskCreateSerializer from server.core.worker_manager import worker_manager from server.core.task_dispatcher import task_dispatcher @@ -44,6 +45,115 @@ def _task_to_dict(t) -> dict: } +def _parse_limit(raw_value, default: int = 50, max_value: int = 200) -> int: + """解析分页上限,避免非法参数导致 500。""" + try: + value = int(raw_value) + except (TypeError, ValueError): + return default + if value <= 0: + return default + return min(value, max_value) + + +def _parse_task_status(raw_status: Optional[str]) -> Optional[TaskStatus]: + """解析任务状态。为空时返回 None。""" + if not raw_status: + return None + try: + return TaskStatus(raw_status) + except ValueError: + return None + + +def _task_log_account_name(task_log: TaskLog) -> Optional[str]: + """从任务日志里提取可识别的环境名。""" + result = task_log.result if isinstance(task_log.result, dict) else {} + params = task_log.params if isinstance(task_log.params, dict) else {} + account_name = ( + result.get("browser_name") + or result.get("account_name") + or params.get("account_name") + or params.get("browser_name") + ) + if not account_name: + return None + return str(account_name).strip() or None + + +def _task_log_to_dict(task_log: TaskLog, account_name: Optional[str] = None) -> dict: + """将 TaskLog 转为统一响应结构。""" + return { + "task_id": task_log.task_id, + "task_type": task_log.task_type, + "status": task_log.status, + "worker_id": task_log.worker_id, + "account_name": account_name or _task_log_account_name(task_log), + "params": task_log.params if isinstance(task_log.params, dict) else {}, + "progress": None, + "result": task_log.result, + "error": task_log.error, + "created_at": task_log.created_at.strftime("%Y-%m-%dT%H:%M:%S"), + "updated_at": task_log.created_at.strftime("%Y-%m-%dT%H:%M:%S"), + } + + +def _is_task_log_for_account(task_log: TaskLog, account: BossAccount) -> bool: + """判断任务日志是否属于某个账号(用于账号任务列表兼容)。""" + if task_log.worker_id and task_log.worker_id != account.worker_id: + return False + + if account.current_task_id and task_log.task_id == account.current_task_id: + return True + + matched_name = _task_log_account_name(task_log) + if matched_name and matched_name == account.browser_name: + return True + + params = task_log.params if isinstance(task_log.params, dict) else {} + account_pk = str(account.pk) + for key in ("id", "account_id", "boss_id"): + value = params.get(key) + if value is None: + continue + if str(value).strip() == account_pk: + return True + + return False + + +def _list_tasks_by_account(account: BossAccount, task_status: Optional[TaskStatus], limit: int) -> list: + """ + 聚合某账号的任务列表: + 1) 内存任务(实时) + 2) TaskLog 历史任务(重启后可查) + """ + items_by_task_id = {} + + memory_limit = max(limit * 3, 100) + memory_tasks = task_dispatcher.list_tasks(worker_id=account.worker_id, status=task_status, limit=memory_limit) + for t in memory_tasks: + if t.account_name != account.browser_name: + continue + items_by_task_id[t.task_id] = _task_to_dict(t) + + db_qs = TaskLog.objects.filter(worker_id=account.worker_id).order_by("-created_at") + if task_status: + db_qs = db_qs.filter(status=task_status.value) + + # 多取一些做过滤,避免因为条件匹配损耗导致结果太少 + for task_log in db_qs[: max(limit * 8, 200)]: + if not _is_task_log_for_account(task_log, account): + continue + if task_log.task_id in items_by_task_id: + continue + items_by_task_id[task_log.task_id] = _task_log_to_dict(task_log, account_name=account.browser_name) + + merged = list(items_by_task_id.values()) + merged.sort(key=lambda item: item.get("created_at") or "", reverse=True) + return merged[:limit] + + @api_view(["GET", "POST"]) def task_list(request): """ @@ -58,30 +168,24 @@ def task_list(request): wid = request.query_params.get("worker_id") account_id = request.query_params.get("account_id") st = request.query_params.get("status") - limit = int(request.query_params.get("limit", 50)) - task_status = TaskStatus(st) if st else None - - # 如果指定了 account_id,先查询账号信息,转换为 account_name 过滤 - account_name = None + limit = _parse_limit(request.query_params.get("limit", 50)) + task_status = _parse_task_status(st) + if st and task_status is None: + return api_error(http_status.HTTP_400_BAD_REQUEST, f"不支持的任务状态: {st}") + + # 如果指定了 account_id,直接返回该账号的任务列表(兼容前端“查看任务”) if account_id: try: account_id = int(account_id) account = BossAccount.objects.get(pk=account_id) - account_name = account.browser_name - if not wid: - wid = account.worker_id except (ValueError, BossAccount.DoesNotExist): return api_error( http_status.HTTP_404_NOT_FOUND, f"未找到 id={account_id} 的账号", ) - + return api_success(_list_tasks_by_account(account, task_status=task_status, limit=limit)) + tasks = task_dispatcher.list_tasks(worker_id=wid, status=task_status, limit=limit) - - # 按 account_name 进一步过滤(如果指定了 account_id) - if account_name: - tasks = [t for t in tasks if t.account_name == account_name] - return api_success([_task_to_dict(t) for t in tasks]) # POST: 提交新任务 @@ -179,31 +283,16 @@ def task_list(request): @api_view(["GET"]) -def task_detail(request, task_id): - """查询指定任务的状态和结果。""" - # 先查内存 - task = task_dispatcher.get_task(task_id) - if task: - return api_success(_task_to_dict(task)) - - # 再查数据库(任务已完成的情况) - try: - from server.models import TaskLog - task_log = TaskLog.objects.get(task_id=task_id) - # 将数据库记录转换为相同格式 - task_dict = { - "task_id": task_log.task_id, - "task_type": task_log.task_type, - "status": task_log.status, - "worker_id": task_log.worker_id, - "account_name": None, - "params": task_log.params, - "progress": None, - "result": task_log.result, - "error": task_log.error, - "created_at": task_log.created_at.strftime("%Y-%m-%dT%H:%M:%S"), - "updated_at": task_log.created_at.strftime("%Y-%m-%dT%H:%M:%S"), - } - return api_success(task_dict) - except Exception: - return api_error(http_status.HTTP_404_NOT_FOUND, f"任务 {task_id} 不存在") +def task_list_by_account(request, account_id: int): + """按账号 ID 查询任务列表(不支持按 task_id 查询)。""" + account = BossAccount.objects.filter(pk=account_id).first() + if not account: + return api_error(http_status.HTTP_404_NOT_FOUND, f"账号 {account_id} 不存在") + + st = request.query_params.get("status") + task_status = _parse_task_status(st) + if st and task_status is None: + return api_error(http_status.HTTP_400_BAD_REQUEST, f"不支持的任务状态: {st}") + + limit = _parse_limit(request.query_params.get("limit", 50)) + return api_success(_list_tasks_by_account(account, task_status=task_status, limit=limit)) diff --git a/server/urls.py b/server/urls.py index 5368477..493efd6 100644 --- a/server/urls.py +++ b/server/urls.py @@ -22,7 +22,7 @@ urlpatterns = [ # ─── 任务 ─── path("api/tasks", tasks.task_list), - path("api/tasks/", tasks.task_detail), + path("api/tasks/", tasks.task_list_by_account), # ─── 账号 ─── path("api/accounts", accounts.account_list),