haha
This commit is contained in:
@@ -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="账号已删除")
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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="联系记录已删除")
|
||||
|
||||
@@ -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="筛选配置已删除")
|
||||
|
||||
@@ -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="话术已删除")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
29
server/core/response.py
Normal file
29
server/core/response.py
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user