From 83c8f72cc78ba5d2907310cea6211fcb8bfe0742 Mon Sep 17 00:00:00 2001 From: 27942 Date: Thu, 26 Feb 2026 01:27:35 +0800 Subject: [PATCH] haha --- server/api/accounts.py | 12 ++++++------ server/api/auth.py | 22 ++++++++++------------ server/api/contacts.py | 21 +++++++++------------ server/api/filters.py | 14 +++++++------- server/api/scripts.py | 14 +++++++------- server/api/settings.py | 12 ++++++------ server/api/stats.py | 6 +++--- server/api/tasks.py | 24 ++++++++++++------------ server/api/workers.py | 11 +++++------ server/core/exception_handler.py | 16 ++++++++-------- server/core/response.py | 29 +++++++++++++++++++++++++++++ 11 files changed, 102 insertions(+), 79 deletions(-) create mode 100644 server/core/response.py diff --git a/server/api/accounts.py b/server/api/accounts.py index 87eee0b..20c9a7e 100644 --- a/server/api/accounts.py +++ b/server/api/accounts.py @@ -10,8 +10,8 @@ BOSS 账号 API(需要登录): """ from rest_framework import status from rest_framework.decorators import api_view -from rest_framework.response import Response +from server.core.response import api_success, api_error from server.models import BossAccount from server.serializers import BossAccountSerializer, AccountBindSerializer from server.core.worker_manager import worker_manager @@ -41,7 +41,7 @@ def account_list(request): qs = BossAccount.objects.all().order_by("-updated_at") if worker_id: qs = qs.filter(worker_id=worker_id) - return Response([_enrich(a) for a in qs]) + return api_success([_enrich(a) for a in qs]) # POST: 添加账号 ser = AccountBindSerializer(data=request.data) @@ -58,7 +58,7 @@ def account_list(request): "is_logged_in": False, }, ) - return Response(_enrich(account), status=status.HTTP_201_CREATED) + return api_success(_enrich(account), http_status=status.HTTP_201_CREATED) @api_view(["GET", "DELETE"]) @@ -70,11 +70,11 @@ def account_detail(request, account_id): try: account = BossAccount.objects.get(pk=account_id) except BossAccount.DoesNotExist: - return Response({"detail": "账号不存在"}, status=status.HTTP_404_NOT_FOUND) + return api_error(status.HTTP_404_NOT_FOUND, "账号不存在") if request.method == "GET": - return Response(_enrich(account)) + return api_success(_enrich(account)) # DELETE account.delete() - return Response({"message": "账号已删除"}) + return api_success(msg="账号已删除") diff --git a/server/api/auth.py b/server/api/auth.py index f66f0d2..86e7cfe 100644 --- a/server/api/auth.py +++ b/server/api/auth.py @@ -7,9 +7,9 @@ import uuid from rest_framework import status from rest_framework.decorators import api_view, authentication_classes, permission_classes from rest_framework.permissions import AllowAny -from rest_framework.response import Response from server import config +from server.core.response import api_success, api_error from server.models import AuthToken from server.serializers import LoginSerializer @@ -32,7 +32,7 @@ def login(request): password = ser.validated_data["password"] if username != config.ADMIN_USERNAME or password != config.ADMIN_PASSWORD: - return Response({"detail": "用户名或密码错误"}, status=status.HTTP_401_UNAUTHORIZED) + return api_error(status.HTTP_401_UNAUTHORIZED, "用户名或密码错误") token = uuid.uuid4().hex AuthToken.objects.update_or_create( @@ -40,7 +40,7 @@ def login(request): defaults={"token": token}, ) - return Response({"token": token}) + return api_success({"token": token}) @api_view(["GET"]) @@ -55,18 +55,16 @@ def get_info(request): token = auth_header.replace("Bearer ", "").strip() if auth_header else "" if not token: - return Response({"detail": "未提供认证令牌"}, status=status.HTTP_401_UNAUTHORIZED) + return api_error(status.HTTP_401_UNAUTHORIZED, "未提供认证令牌") try: record = AuthToken.objects.get(token=token) except AuthToken.DoesNotExist: - return Response({"detail": "令牌无效或已过期"}, status=status.HTTP_401_UNAUTHORIZED) + return api_error(status.HTTP_401_UNAUTHORIZED, "令牌无效或已过期") - return Response({ - "data": { - "account_id": record.id, - "username": record.username, - "token": record.token, - "created_at": record.created_at, - }, + return api_success({ + "account_id": record.id, + "username": record.username, + "token": record.token, + "created_at": record.created_at, }) diff --git a/server/api/contacts.py b/server/api/contacts.py index 707388a..fba4d2d 100644 --- a/server/api/contacts.py +++ b/server/api/contacts.py @@ -12,8 +12,8 @@ from django.db.models import Q from rest_framework import status from rest_framework.decorators import api_view -from rest_framework.response import Response +from server.core.response import api_success, api_error from server.models import ContactRecord from server.serializers import ContactRecordSerializer @@ -44,7 +44,7 @@ def contact_list(request): end = start + page_size records = qs[start:end] - return Response({ + return api_success({ "total": total, "page": page, "page_size": page_size, @@ -54,7 +54,7 @@ def contact_list(request): ser = ContactRecordSerializer(data=request.data) ser.is_valid(raise_exception=True) ser.save() - return Response(ser.data, status=status.HTTP_201_CREATED) + return api_success(ser.data, http_status=status.HTTP_201_CREATED) @api_view(["GET"]) @@ -65,10 +65,7 @@ def contact_query(request): """ contact = (request.query_params.get("contact") or "").strip() if not contact: - return Response( - {"detail": "请提供 contact 参数(电话号码或微信号)"}, - status=status.HTTP_400_BAD_REQUEST, - ) + return api_error(status.HTTP_400_BAD_REQUEST, "请提供 contact 参数(电话号码或微信号)") page = int(request.query_params.get("page", 1)) page_size = int(request.query_params.get("page_size", 20)) @@ -78,7 +75,7 @@ def contact_query(request): end = start + page_size records = qs[start:end] - return Response({ + return api_success({ "total": total, "page": page, "page_size": page_size, @@ -92,16 +89,16 @@ def contact_detail(request, pk): try: obj = ContactRecord.objects.get(pk=pk) except ContactRecord.DoesNotExist: - return Response({"detail": "联系记录不存在"}, status=status.HTTP_404_NOT_FOUND) + return api_error(status.HTTP_404_NOT_FOUND, "联系记录不存在") if request.method == "GET": - return Response(ContactRecordSerializer(obj).data) + return api_success(ContactRecordSerializer(obj).data) if request.method == "PUT": ser = ContactRecordSerializer(obj, data=request.data, partial=True) ser.is_valid(raise_exception=True) ser.save() - return Response(ser.data) + return api_success(ser.data) obj.delete() - return Response({"message": "联系记录已删除"}) + return api_success(msg="联系记录已删除") diff --git a/server/api/filters.py b/server/api/filters.py index ef4e131..5dfc651 100644 --- a/server/api/filters.py +++ b/server/api/filters.py @@ -9,8 +9,8 @@ """ from rest_framework import status from rest_framework.decorators import api_view -from rest_framework.response import Response +from server.core.response import api_success, api_error from server.models import FilterConfig from server.serializers import FilterConfigSerializer @@ -19,12 +19,12 @@ from server.serializers import FilterConfigSerializer def filter_list(request): if request.method == "GET": qs = FilterConfig.objects.all().order_by("-updated_at") - return Response(FilterConfigSerializer(qs, many=True).data) + return api_success(FilterConfigSerializer(qs, many=True).data) ser = FilterConfigSerializer(data=request.data) ser.is_valid(raise_exception=True) ser.save() - return Response(ser.data, status=status.HTTP_201_CREATED) + return api_success(ser.data, http_status=status.HTTP_201_CREATED) @api_view(["GET", "PUT", "DELETE"]) @@ -32,16 +32,16 @@ def filter_detail(request, pk): try: obj = FilterConfig.objects.get(pk=pk) except FilterConfig.DoesNotExist: - return Response({"detail": "筛选配置不存在"}, status=status.HTTP_404_NOT_FOUND) + return api_error(status.HTTP_404_NOT_FOUND, "筛选配置不存在") if request.method == "GET": - return Response(FilterConfigSerializer(obj).data) + return api_success(FilterConfigSerializer(obj).data) if request.method == "PUT": ser = FilterConfigSerializer(obj, data=request.data, partial=True) ser.is_valid(raise_exception=True) ser.save() - return Response(ser.data) + return api_success(ser.data) obj.delete() - return Response({"message": "筛选配置已删除"}) + return api_success(msg="筛选配置已删除") diff --git a/server/api/scripts.py b/server/api/scripts.py index 1c38b07..0ed4ee8 100644 --- a/server/api/scripts.py +++ b/server/api/scripts.py @@ -9,8 +9,8 @@ """ from rest_framework import status from rest_framework.decorators import api_view -from rest_framework.response import Response +from server.core.response import api_success, api_error from server.models import ChatScript from server.serializers import ChatScriptSerializer @@ -25,12 +25,12 @@ def script_list(request): qs = qs.filter(position=position) if script_type: qs = qs.filter(script_type=script_type) - return Response(ChatScriptSerializer(qs, many=True).data) + return api_success(ChatScriptSerializer(qs, many=True).data) ser = ChatScriptSerializer(data=request.data) ser.is_valid(raise_exception=True) ser.save() - return Response(ser.data, status=status.HTTP_201_CREATED) + return api_success(ser.data, http_status=status.HTTP_201_CREATED) @api_view(["GET", "PUT", "DELETE"]) @@ -38,16 +38,16 @@ def script_detail(request, pk): try: obj = ChatScript.objects.get(pk=pk) except ChatScript.DoesNotExist: - return Response({"detail": "话术不存在"}, status=status.HTTP_404_NOT_FOUND) + return api_error(status.HTTP_404_NOT_FOUND, "话术不存在") if request.method == "GET": - return Response(ChatScriptSerializer(obj).data) + return api_success(ChatScriptSerializer(obj).data) if request.method == "PUT": ser = ChatScriptSerializer(obj, data=request.data, partial=True) ser.is_valid(raise_exception=True) ser.save() - return Response(ser.data) + return api_success(ser.data) obj.delete() - return Response({"message": "话术已删除"}) + return api_success(msg="话术已删除") diff --git a/server/api/settings.py b/server/api/settings.py index 38ae97a..8459dc1 100644 --- a/server/api/settings.py +++ b/server/api/settings.py @@ -7,8 +7,8 @@ """ from rest_framework import status from rest_framework.decorators import api_view -from rest_framework.response import Response +from server.core.response import api_success, api_error from server.models import SystemConfig from server.serializers import SystemConfigSerializer @@ -17,11 +17,11 @@ from server.serializers import SystemConfigSerializer def settings_list(request): if request.method == "GET": qs = SystemConfig.objects.all().order_by("key") - return Response(SystemConfigSerializer(qs, many=True).data) + return api_success(SystemConfigSerializer(qs, many=True).data) # POST: 批量更新,请求体格式 {"key1": "value1", "key2": "value2", ...} if not isinstance(request.data, dict): - return Response({"detail": "请传入 key-value 对象"}, status=status.HTTP_400_BAD_REQUEST) + return api_error(status.HTTP_400_BAD_REQUEST, "请传入 key-value 对象") results = [] for key, value in request.data.items(): @@ -30,7 +30,7 @@ def settings_list(request): defaults={"value": str(value)}, ) results.append(SystemConfigSerializer(obj).data) - return Response(results) + return api_success(results) @api_view(["GET"]) @@ -38,5 +38,5 @@ def settings_detail(request, key): try: obj = SystemConfig.objects.get(key=key) except SystemConfig.DoesNotExist: - return Response({"detail": f"配置项 '{key}' 不存在"}, status=status.HTTP_404_NOT_FOUND) - return Response(SystemConfigSerializer(obj).data) + return api_error(status.HTTP_404_NOT_FOUND, f"配置项 '{key}' 不存在") + return api_success(SystemConfigSerializer(obj).data) diff --git a/server/api/stats.py b/server/api/stats.py index 3b65159..db7b3c4 100644 --- a/server/api/stats.py +++ b/server/api/stats.py @@ -9,8 +9,8 @@ from datetime import timedelta from django.db.models import Count, Q from django.utils import timezone from rest_framework.decorators import api_view -from rest_framework.response import Response +from server.core.response import api_success from server.models import ContactRecord, BossAccount, TaskLog from server.core.worker_manager import worker_manager @@ -55,7 +55,7 @@ def stats_overview(request): reply_rate = round(total_replied / max(total_contacts, 1) * 100, 1) wechat_rate = round(total_wechat / max(total_replied, 1) * 100, 1) - return Response({ + return api_success({ "period": period, "contacts": { "total": total_contacts, @@ -105,4 +105,4 @@ def stats_daily(request): "reply_rate": round(replied / max(total, 1) * 100, 1), }) - return Response(daily_data) + return api_success(daily_data) diff --git a/server/api/tasks.py b/server/api/tasks.py index 785fd4f..61548e5 100644 --- a/server/api/tasks.py +++ b/server/api/tasks.py @@ -9,9 +9,9 @@ import logging from asgiref.sync import async_to_sync from rest_framework import status as http_status from rest_framework.decorators import api_view -from rest_framework.response import Response 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.core.worker_manager import worker_manager @@ -49,7 +49,7 @@ def task_list(request): limit = int(request.query_params.get("limit", 50)) task_status = TaskStatus(st) if st else None tasks = task_dispatcher.list_tasks(worker_id=wid, status=task_status, limit=limit) - return Response([_task_to_dict(t) for t in tasks]) + return api_success([_task_to_dict(t) for t in tasks]) # POST: 提交新任务 data = request.data.copy() @@ -70,16 +70,16 @@ def task_list(request): if not target_worker_id and req.account_name: target_worker_id = worker_manager.find_worker_by_account(req.account_name) if not target_worker_id: - return Response( - {"detail": f"未找到拥有浏览器 '{req.account_name}' 的在线 Worker"}, - status=http_status.HTTP_404_NOT_FOUND, + return api_error( + http_status.HTTP_404_NOT_FOUND, + f"未找到拥有浏览器 '{req.account_name}' 的在线 Worker", ) if not target_worker_id: - return Response({"detail": "请指定 worker_id 或 account_name"}, status=http_status.HTTP_400_BAD_REQUEST) + return api_error(http_status.HTTP_400_BAD_REQUEST, "请指定 worker_id 或 account_name") if not worker_manager.is_online(target_worker_id): - return Response({"detail": f"Worker {target_worker_id} 不在线"}, status=http_status.HTTP_503_SERVICE_UNAVAILABLE) + return api_error(http_status.HTTP_503_SERVICE_UNAVAILABLE, f"Worker {target_worker_id} 不在线") req.worker_id = target_worker_id task = task_dispatcher.create_task(req) @@ -88,11 +88,11 @@ def task_list(request): if not send_fn: task.status = TaskStatus.FAILED task.error = "Worker WebSocket 连接不存在" - return Response({"detail": "Worker WebSocket 连接不存在"}, status=http_status.HTTP_503_SERVICE_UNAVAILABLE) + return api_error(http_status.HTTP_503_SERVICE_UNAVAILABLE, "Worker WebSocket 连接不存在") success = async_to_sync(task_dispatcher.dispatch)(task, send_fn) if not success: - return Response({"detail": f"任务派发失败: {task.error}"}, status=http_status.HTTP_503_SERVICE_UNAVAILABLE) + return api_error(http_status.HTTP_503_SERVICE_UNAVAILABLE, f"任务派发失败: {task.error}") worker_manager.set_current_task(target_worker_id, task.task_id) @@ -109,7 +109,7 @@ def task_list(request): except Exception as e: logger.warning("关联账号任务状态失败: %s", e) - return Response(_task_to_dict(task), status=http_status.HTTP_201_CREATED) + return api_success(_task_to_dict(task), http_status=http_status.HTTP_201_CREATED) @api_view(["GET"]) @@ -117,5 +117,5 @@ def task_detail(request, task_id): """查询指定任务的状态和结果。""" task = task_dispatcher.get_task(task_id) if not task: - return Response({"detail": f"任务 {task_id} 不存在"}, status=http_status.HTTP_404_NOT_FOUND) - return Response(_task_to_dict(task)) + return api_error(http_status.HTTP_404_NOT_FOUND, f"任务 {task_id} 不存在") + return api_success(_task_to_dict(task)) diff --git a/server/api/workers.py b/server/api/workers.py index 483b914..bd22f98 100644 --- a/server/api/workers.py +++ b/server/api/workers.py @@ -5,9 +5,8 @@ Worker 查询 API(需要登录)。 from rest_framework import status from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import AllowAny -from rest_framework.response import Response -from server.serializers import WorkerOutSerializer +from server.core.response import api_success, api_error from server.core.worker_manager import worker_manager @@ -16,7 +15,7 @@ from server.core.worker_manager import worker_manager def health_check(request): """健康检查。""" online = len([w for w in worker_manager.get_all_workers() if w.online]) - return Response({"status": "ok", "workers_online": online}) + return api_success({"status": "ok", "workers_online": online}) def _worker_to_dict(w) -> dict: @@ -33,7 +32,7 @@ def _worker_to_dict(w) -> dict: def worker_list(request): """获取所有已注册的 Worker(含在线状态与浏览器列表)。""" workers = worker_manager.get_all_workers() - return Response([_worker_to_dict(w) for w in workers]) + return api_success([_worker_to_dict(w) for w in workers]) @api_view(["GET"]) @@ -41,5 +40,5 @@ def worker_detail(request, worker_id): """获取指定 Worker 的详情。""" w = worker_manager.get_worker(worker_id) if not w: - return Response({"detail": f"Worker {worker_id} 不存在"}, status=status.HTTP_404_NOT_FOUND) - return Response(_worker_to_dict(w)) + return api_error(status.HTTP_404_NOT_FOUND, f"Worker {worker_id} 不存在") + return api_success(_worker_to_dict(w)) diff --git a/server/core/exception_handler.py b/server/core/exception_handler.py index 0be8280..05790b0 100644 --- a/server/core/exception_handler.py +++ b/server/core/exception_handler.py @@ -1,22 +1,22 @@ # -*- coding: utf-8 -*- """ -DRF 自定义异常处理器:统一错误响应格式,并确保返回正确 HTTP 状态码。 +DRF 自定义异常处理器:统一错误响应格式为 code、data、msg。 """ from rest_framework.views import exception_handler -from rest_framework.response import Response from rest_framework.exceptions import APIException def custom_exception_handler(exc, context): response = exception_handler(exc, context) if response is not None: - # 确保使用异常自带的 HTTP 状态码(如 401 登录失效、403 无权限) if isinstance(exc, APIException) and getattr(exc, "status_code", None): response.status_code = exc.status_code - # 统一格式:{"detail": "...", "code": 状态码} + code = response.status_code if isinstance(response.data, list): - response.data = {"detail": response.data[0] if response.data else str(exc)} - elif isinstance(response.data, dict) and "detail" not in response.data: - response.data = {"detail": str(response.data)} - response.data["code"] = response.status_code + msg = response.data[0] if response.data else str(exc) + elif isinstance(response.data, dict) and "detail" in response.data: + msg = response.data["detail"] + else: + msg = str(response.data) if response.data else str(exc) + response.data = {"code": code, "data": None, "msg": msg} return response diff --git a/server/core/response.py b/server/core/response.py new file mode 100644 index 0000000..c4c228c --- /dev/null +++ b/server/core/response.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +""" +统一 API 响应格式:code、data、msg。 +""" +from rest_framework.response import Response +from rest_framework import status + + +def api_response(code: int = 0, data=None, msg: str = "success", http_status: int = None): + """ + 统一接口响应格式。 + - code: 0 表示成功,非 0 表示错误(通常与 HTTP 状态码对应) + - data: 业务数据 + - msg: 提示信息 + """ + resp = {"code": code, "data": data, "msg": msg} + if http_status is None: + http_status = status.HTTP_200_OK if code == 0 else min(code, 599) + return Response(resp, status=http_status) + + +def api_success(data=None, msg: str = "success", http_status: int = status.HTTP_200_OK): + """成功响应。""" + return api_response(code=0, data=data, msg=msg, http_status=http_status) + + +def api_error(code: int, msg: str, data=None): + """错误响应。""" + return api_response(code=code, data=data, msg=msg, http_status=code)