优化商家订单详情接口500错误
This commit is contained in:
@@ -73,12 +73,12 @@ app.conf.beat_schedule = {
|
||||
# 🔥 核心任务:订单调度器(每2分钟运行)
|
||||
|
||||
|
||||
# 🔥 补偿检查任务(每5分钟运行)
|
||||
'check_order_expire_task': {
|
||||
'task': 'dingdan.tasks.check_order_expire_task',
|
||||
'schedule': crontab(minute='*/5'),
|
||||
'options': {'queue': 'order_tasks', 'priority': 3},
|
||||
},
|
||||
# 🔥 补偿检查任务(每5分钟运行)— 已禁用:见 settings.ORDER_AUTO_SETTLE_ENABLED
|
||||
# 'check_order_expire_task': {
|
||||
# 'task': 'dingdan.tasks.check_order_expire_task',
|
||||
# 'schedule': crontab(minute='*/5'),
|
||||
# 'options': {'queue': 'order_tasks', 'priority': 3},
|
||||
# },
|
||||
|
||||
|
||||
# 1. 每日凌晨0点执行 - 清零任务
|
||||
|
||||
@@ -459,7 +459,10 @@ CELERY_TASK_QUEUES = {
|
||||
# 所有时间配置都在这里,方便修改
|
||||
|
||||
# 订单超时时间(秒) - 48小时
|
||||
ORDER_EXPIRE_SECONDS = 48000 * 60 * 60 # 172800秒
|
||||
ORDER_EXPIRE_SECONDS = 48 * 60 * 60 # 172800秒
|
||||
|
||||
# 订单自动结算(Celery 延时任务 + 定时补偿)是否启用
|
||||
ORDER_AUTO_SETTLE_ENABLED = False
|
||||
|
||||
# 每日任务执行时间(24小时制)
|
||||
DAILY_TASK_HOUR = 0 # 0点
|
||||
@@ -532,6 +535,61 @@ BATCH_LINK_DEFAULT_COUNT = 1
|
||||
BATCH_LINK_MAX_COUNT = 100
|
||||
|
||||
|
||||
# ==================== 日志配置(终端输出,便于生产排查) ====================
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': '[{asctime}] {levelname} {name} {pathname}:{lineno} — {message}',
|
||||
'style': '{',
|
||||
},
|
||||
'simple': {
|
||||
'format': '[{asctime}] {levelname} {name} — {message}',
|
||||
'style': '{',
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'verbose',
|
||||
'level': 'INFO',
|
||||
},
|
||||
},
|
||||
'root': {
|
||||
'handlers': ['console'],
|
||||
'level': 'INFO',
|
||||
},
|
||||
'loggers': {
|
||||
'django': {
|
||||
'handlers': ['console'],
|
||||
'level': 'INFO',
|
||||
'propagate': False,
|
||||
},
|
||||
'django.request': {
|
||||
'handlers': ['console'],
|
||||
'level': 'ERROR',
|
||||
'propagate': False,
|
||||
},
|
||||
'yonghu': {
|
||||
'handlers': ['console'],
|
||||
'level': 'INFO',
|
||||
'propagate': False,
|
||||
},
|
||||
'dingdan': {
|
||||
'handlers': ['console'],
|
||||
'level': 'INFO',
|
||||
'propagate': False,
|
||||
},
|
||||
'utils': {
|
||||
'handlers': ['console'],
|
||||
'level': 'INFO',
|
||||
'propagate': False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,10 @@ def handle_order_status_8(sender, instance, **kwargs):
|
||||
|
||||
# 状态从 非8 变为 8
|
||||
if old != 8 and instance.zhuangtai == 8:
|
||||
if not getattr(settings, 'ORDER_AUTO_SETTLE_ENABLED', False):
|
||||
logger.info(f"订单 {instance.dingdan_id} 状态变为8,自动结算已禁用,不提交任务")
|
||||
return
|
||||
|
||||
logger.info(f"📅 订单 {instance.dingdan_id} 状态变为8,提交定时任务")
|
||||
|
||||
# 设置时间标记
|
||||
|
||||
@@ -33,8 +33,10 @@ def process_expired_order(self, dingdan_id):
|
||||
"""
|
||||
处理单个超时订单(订单状态=8,超过48小时未处理)
|
||||
"""
|
||||
|
||||
|
||||
if not getattr(settings, 'ORDER_AUTO_SETTLE_ENABLED', False):
|
||||
logger.info('订单自动结算已禁用,跳过 process_expired_order(%s)', dingdan_id)
|
||||
return f'自动结算已禁用,跳过订单{dingdan_id}'
|
||||
|
||||
try:
|
||||
# 使用select_for_update锁定记录,防止并发修改
|
||||
with transaction.atomic():
|
||||
@@ -149,6 +151,10 @@ def check_order_expire_task():
|
||||
每5分钟执行一次,作为信号处理的备用方案
|
||||
查找状态为8且创建时间超过72小时的订单(宽松时间,防止误处理)
|
||||
"""
|
||||
if not getattr(settings, 'ORDER_AUTO_SETTLE_ENABLED', False):
|
||||
logger.info('订单自动结算已禁用,跳过 check_order_expire_task')
|
||||
return {"success": True, "message": "自动结算已禁用", "count": 0}
|
||||
|
||||
try:
|
||||
# 计算更宽松的时间点:当前时间减去72小时(48+24小时)
|
||||
# 这样避免误处理刚变为状态8的订单
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
# /opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.443/django/utils/exception_handler.py
|
||||
|
||||
from rest_framework.views import exception_handler
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
import logging
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
logger = logging.getLogger('django.request')
|
||||
|
||||
logger = logging.getLogger('django')
|
||||
|
||||
def custom_exception_handler(exc, context):
|
||||
"""
|
||||
生产环境安全异常处理器 - 不泄露任何敏感信息
|
||||
基于你之前项目的代码,做了小优化
|
||||
统一异常处理:所有错误写入终端日志(logger),生产环境响应不泄露细节。
|
||||
"""
|
||||
# 先调用默认异常处理器
|
||||
response = exception_handler(exc, context)
|
||||
|
||||
request = context.get('request')
|
||||
path = getattr(request, 'path', 'unknown')
|
||||
method = getattr(request, 'method', 'unknown')
|
||||
user = request.user if request else None
|
||||
user_info = f"用户: {user.id if user and user.is_authenticated else '未登录'}"
|
||||
|
||||
if response is not None:
|
||||
# 🔥 记录原始错误信息到日志(便于排查)
|
||||
request = context['request']
|
||||
user_info = f"用户: {request.user.id if request.user.is_authenticated else '未登录'}"
|
||||
logger.error(
|
||||
f"API异常 - 路径: {request.path}, "
|
||||
f"方法: {request.method}, {user_info}, "
|
||||
f"状态码: {response.status_code}, 错误: {str(exc)}"
|
||||
'API异常 path=%s method=%s %s status=%s error=%s',
|
||||
path, method, user_info, response.status_code, exc,
|
||||
exc_info=exc,
|
||||
)
|
||||
|
||||
# 🔥 生产环境返回标准化错误,不暴露任何细节
|
||||
if settings.DEBUG:
|
||||
return response
|
||||
error_messages = {
|
||||
status.HTTP_400_BAD_REQUEST: {'code': 400, 'message': '请求参数错误'},
|
||||
status.HTTP_401_UNAUTHORIZED: {'code': 401, 'message': '请先登录'},
|
||||
@@ -40,19 +41,27 @@ def custom_exception_handler(exc, context):
|
||||
status.HTTP_503_SERVICE_UNAVAILABLE: {'code': 503, 'message': '服务暂不可用'},
|
||||
status.HTTP_504_GATEWAY_TIMEOUT: {'code': 504, 'message': '网关超时'},
|
||||
}
|
||||
|
||||
# 🔥 如果是调试模式,返回详细错误(便于开发)
|
||||
from django.conf import settings
|
||||
if settings.DEBUG:
|
||||
return response
|
||||
|
||||
# 🔥 生产环境返回标准错误
|
||||
standard_response = error_messages.get(
|
||||
response.status_code,
|
||||
{'code': response.status_code, 'message': '请求错误'}
|
||||
response.status_code,
|
||||
{'code': response.status_code, 'message': '请求错误'},
|
||||
)
|
||||
|
||||
return Response(standard_response, status=response.status_code)
|
||||
|
||||
# 对于非DRF异常,这里可以根据需要处理
|
||||
return response
|
||||
|
||||
# DRF 未处理的异常(数据库字段缺失、AttributeError 等)→ 500 且无日志是常见问题
|
||||
logger.error(
|
||||
'未处理API异常 path=%s method=%s %s error=%s traceback=%s',
|
||||
path,
|
||||
method,
|
||||
user_info,
|
||||
exc,
|
||||
traceback.format_exc(),
|
||||
)
|
||||
if settings.DEBUG:
|
||||
return Response(
|
||||
{'code': 500, 'message': str(exc), 'detail': traceback.format_exc()},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
return Response(
|
||||
{'code': 500, 'message': '服务器内部错误'},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
@@ -8672,6 +8672,21 @@ class KefuGetOrderDetailView(APIView):
|
||||
def post(self, request):
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
return self._build_order_detail_response(request, start_time)
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
'客服订单详情异常 dingdan_id=%s phone=%s error=%s',
|
||||
request.data.get('dingdan_id'),
|
||||
request.data.get('phone'),
|
||||
e,
|
||||
)
|
||||
return Response(
|
||||
{'code': 500, 'msg': '获取订单详情失败', 'data': None},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
def _build_order_detail_response(self, request, start_time):
|
||||
# 1. 获取参数
|
||||
phone = request.data.get('phone', '').strip()
|
||||
dingdan_id = request.data.get('dingdan_id', '').strip()
|
||||
@@ -8716,16 +8731,9 @@ class KefuGetOrderDetailView(APIView):
|
||||
|
||||
# 3. 查询订单
|
||||
try:
|
||||
# 使用 select_related 预加载扩展表,避免后续 N+1 查询
|
||||
dingdan_obj = Dingdan.objects.select_related(
|
||||
'pingtai_kuozhan',
|
||||
'shangjia_kuozhan'
|
||||
).only(
|
||||
'dingdan_id', 'zhuangtai', 'fadan_pingtai', 'jine', 'dashou_fencheng',
|
||||
'guanshi_fencheng', 'guanshi_shangjia_fencheng',
|
||||
'jiedan_dashou_id', 'dashou_liuyan', 'zhiding_id', 'shangpin_id',
|
||||
'leixing_id', 'tupian', 'jieshao', 'beizhu', 'tkly', 'nicheng',
|
||||
'clkf', 'create_time', 'update_time'
|
||||
).get(dingdan_id=dingdan_id)
|
||||
|
||||
except Dingdan.DoesNotExist:
|
||||
@@ -8735,15 +8743,21 @@ class KefuGetOrderDetailView(APIView):
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
def _to_float(val, default=0.0):
|
||||
try:
|
||||
return float(val) if val is not None else default
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
# 4. 构建基础响应数据(主表字段)
|
||||
response_data = {
|
||||
'dingdan_id': dingdan_obj.dingdan_id or '',
|
||||
'zhuangtai': dingdan_obj.zhuangtai or 0,
|
||||
'fadanpingtai': dingdan_obj.fadan_pingtai or 0,
|
||||
'jiage': float(dingdan_obj.jine) if dingdan_obj.jine else 0.00,
|
||||
'dashou_fencheng': float(dingdan_obj.dashou_fencheng) if dingdan_obj.dashou_fencheng else 0.00,
|
||||
'guanshi_fencheng': float(dingdan_obj.guanshi_fencheng) if dingdan_obj.guanshi_fencheng else 0.00,
|
||||
'guanshi_shangjia_fencheng': float(dingdan_obj.guanshi_shangjia_fencheng) if dingdan_obj.guanshi_shangjia_fencheng else 0.00,
|
||||
'jiage': _to_float(dingdan_obj.jine),
|
||||
'dashou_fencheng': _to_float(dingdan_obj.dashou_fencheng),
|
||||
'guanshi_fencheng': _to_float(getattr(dingdan_obj, 'guanshi_fencheng', 0)),
|
||||
'guanshi_shangjia_fencheng': _to_float(getattr(dingdan_obj, 'guanshi_shangjia_fencheng', 0)),
|
||||
'jiedan_dashou_id': dingdan_obj.jiedan_dashou_id or '',
|
||||
'dashou_liuyan': dingdan_obj.dashou_liuyan or '',
|
||||
'zhiding_id': dingdan_obj.zhiding_id or '',
|
||||
|
||||
Reference in New Issue
Block a user