rtc_backend/utils/exceptions.py
repair-agent 88b8f023f4
Some checks failed
Build and Deploy Backend / build-and-deploy (push) Failing after 1m36s
Fix app api
2026-02-09 15:35:33 +08:00

180 lines
5.1 KiB
Python

"""
自定义异常处理
"""
import os
import traceback
import threading
import requests
from rest_framework.views import exception_handler
from rest_framework import status
from utils.response import APIResponse
# Log Center 配置
LOG_CENTER_URL = os.environ.get('LOG_CENTER_URL', 'https://qiyuan-log-center-api.airlabs.art')
LOG_CENTER_ENABLED = os.environ.get('LOG_CENTER_ENABLED', 'true').lower() == 'true'
def report_to_log_center(exc, context):
"""异步上报错误到 Log Center"""
if not LOG_CENTER_ENABLED:
return
try:
# 提取堆栈信息
tb = traceback.extract_tb(exc.__traceback__) if exc.__traceback__ else []
last_frame = tb[-1] if tb else None
# 获取请求信息
request = context.get('request')
request_path = request.path if request else 'unknown'
request_method = request.method if request else 'unknown'
payload = {
"project_id": "rtc_backend",
"environment": os.environ.get('ENVIRONMENT', 'production'),
"level": "ERROR",
"error": {
"type": type(exc).__name__,
"message": str(exc),
"file_path": last_frame.filename if last_frame else "unknown",
"line_number": last_frame.lineno if last_frame else 0,
"stack_trace": traceback.format_exception(exc) if exc.__traceback__ else [str(exc)]
},
"context": {
"url": request_path,
"method": request_method,
"view": str(context.get('view', '')),
}
}
# 异步发送,不阻塞响应
def send_async():
try:
requests.post(
f"{LOG_CENTER_URL}/api/v1/logs/report",
json=payload,
timeout=3
)
except Exception:
pass # 静默失败
thread = threading.Thread(target=send_async)
thread.daemon = True
thread.start()
except Exception:
pass # 上报失败不影响主业务
class BusinessException(Exception):
"""业务异常"""
def __init__(self, code=1, message='业务错误', status_code=status.HTTP_400_BAD_REQUEST):
self.code = code
self.message = message
self.status_code = status_code
super().__init__(message)
class ErrorCode:
"""错误码定义"""
# 通用错误 1-99
SUCCESS = 0
UNKNOWN_ERROR = 1
PARAM_ERROR = 2
NOT_FOUND = 3
PERMISSION_DENIED = 4
# 用户模块 100-199
USER_NOT_FOUND = 100
USER_DISABLED = 101
PHONE_INVALID = 102
TOKEN_EXPIRED = 103
SMS_CODE_INVALID = 110
SMS_CODE_EXPIRED = 111
SMS_CODE_SEND_LIMIT = 112
# 设备模块 200-299
DEVICE_NOT_FOUND = 200
DEVICE_ALREADY_BOUND = 201
DEVICE_MAC_EXISTS = 202
DEVICE_SN_INVALID = 203
# 智能体模块 300-399
SPIRIT_NOT_FOUND = 300
SPIRIT_LIMIT_EXCEEDED = 301
# 批次模块 400-499
BATCH_NOT_FOUND = 400
BATCH_SN_EXISTS = 401
DEVICE_TYPE_NOT_FOUND = 402
# 管理员模块 500-599
ADMIN_NOT_FOUND = 500
ADMIN_PASSWORD_ERROR = 501
# 故事模块 600-699
STORY_NOT_FOUND = 600
SHELF_NOT_FOUND = 601
SHELF_LOCKED = 602
POINTS_NOT_ENOUGH = 603
# 音乐模块 700-799
TRACK_NOT_FOUND = 700
MUSIC_GENERATE_FAILED = 701
# 通知模块 800-899
NOTIFICATION_NOT_FOUND = 800
# 系统模块 900-999
FEEDBACK_SUBMIT_FAILED = 900
def custom_exception_handler(exc, context):
"""自定义异常处理器"""
# 上报到 Log Center (仅上报非业务异常)
if not isinstance(exc, BusinessException):
report_to_log_center(exc, context)
# 处理业务异常
if isinstance(exc, BusinessException):
return APIResponse(
code=exc.code,
message=exc.message,
status_code=exc.status_code
)
# 调用DRF默认异常处理
response = exception_handler(exc, context)
if response is not None:
# 统一格式化DRF异常
error_message = ''
if isinstance(response.data, dict):
if 'detail' in response.data:
error_message = str(response.data['detail'])
else:
# 处理字段验证错误
errors = []
for field, messages in response.data.items():
if isinstance(messages, list):
errors.append(f"{field}: {', '.join(str(m) for m in messages)}")
else:
errors.append(f"{field}: {messages}")
error_message = '; '.join(errors)
else:
error_message = str(response.data)
return APIResponse(
code=ErrorCode.UNKNOWN_ERROR,
message=error_message,
status_code=response.status_code
)
# 处理未知异常
return APIResponse(
code=ErrorCode.UNKNOWN_ERROR,
message=str(exc),
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
)