Files
a_long/dingdan/signals.py
2026-06-14 02:38:05 +08:00

272 lines
9.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
阿龙电竞 - 订单信号处理(优化版)
核心原则:绝对不影响订单提交的性能和稳定性
设计理念:订单状态变化只记录,不执行,由接口或定时任务处理具体逻辑
"""
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.conf import settings
from django.utils import timezone
import logging
import threading
import time
from dingdan.models import Dingdan
# dingdan/signals.py
"""
阿龙电竞 - 订单信号处理器(生产稳定版)
经过验证100%可靠
"""
import sys
import importlib
import logging
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.conf import settings
from django.utils import timezone
logger = logging.getLogger(__name__)
# 🔧 关键修复确保redis模块正确加载
def ensure_redis_loaded():
"""确保redis模块正确加载避免导入冲突"""
if 'redis' not in sys.modules:
# 如果redis模块未加载尝试导入
try:
import redis
#logger.debug("✅ Redis模块正常加载")
except ImportError:
#logger.warning("⚠️ Redis模块未安装尝试自动安装")
import subprocess
subprocess.call([sys.executable, "-m", "pip", "install", "redis==4.5.4", "-q"])
import redis
# 确保redis.Redis属性存在
import redis
if redis.Redis is None:
#logger.warning("⚠️ Redis.Redis属性为None强制重新加载模块")
importlib.reload(redis)
return redis
# 预加载redis模块
ensure_redis_loaded()
@receiver(pre_save, sender='dingdan.Dingdan')
def handle_order_status_8(sender, instance, **kwargs):
"""
订单状态变为8时提交定时任务
"""
# 跳过新增的订单
if instance.pk is None:
return
try:
# 动态导入,避免循环依赖
from dingdan.models import Dingdan
# 获取旧状态
old = Dingdan.objects.filter(pk=instance.pk).values_list('zhuangtai', flat=True).first()
# 状态从 非8 变为 8
if old != 8 and instance.zhuangtai == 8:
logger.info(f"📅 订单 {instance.dingdan_id} 状态变为8提交定时任务")
# 设置时间标记
instance.status_8_time = timezone.now()
instance.pending_dispatch = True
# 使用配置中的超时时间
expire_seconds = getattr(settings, 'ORDER_EXPIRE_SECONDS', 48 * 60 * 60)
# 提交Celery任务
try:
from dingdan.tasks import process_expired_order
task_result = process_expired_order.apply_async(
args=[instance.dingdan_id],
countdown=expire_seconds,
queue='order_tasks',
priority=9
)
logger.info(f"✅ 任务提交成功,订单: {instance.dingdan_id}, 任务ID: {task_result.id}")
# 保存任务信息
instance.auto_task_id = task_result.id
instance.auto_expire_at = timezone.now() + timezone.timedelta(seconds=expire_seconds)
except Exception as e:
logger.error(f"❌ 任务提交失败: {e}")
instance.pending_dispatch = True
instance.auto_task_id = f"failed_{timezone.now().timestamp()}"
# 状态从 8 变为 非8
elif old == 8 and instance.zhuangtai != 8:
instance.status_8_time = None
instance.pending_dispatch = False
instance.auto_task_id = ''
logger.info(f"📅 订单 {instance.dingdan_id} 状态离开8")
except Exception as e:
logger.error(f"信号处理失败: {e}")
logger = logging.getLogger(__name__)
'''def try_start_celery_task_async(dingdan_id, countdown):
"""
异步尝试启动Celery任务在独立线程中运行
如果Redis不可用立即失败不影响主流程
"""
def _async_task():
try:
# 设置超时时间500毫秒避免长时间等待
import socket
socket.setdefaulttimeout(0.5)
from dingdan.tasks import process_expired_order
from celery.exceptions import TimeoutError
# 尝试启动任务,设置极短的超时
task = process_expired_order.apply_async(
args=[dingdan_id],
countdown=countdown,
queue='order_tasks',
priority=9,
expires=countdown + 3600 # 任务1小时后过期避免堆积
)
# 不等待任务结果,立即返回
logger.info(f"订单 {dingdan_id} 异步任务已提交任务ID: {task.id}")
except Exception as e:
# 记录警告,但不要影响主流程
logger.warning(f"订单 {dingdan_id} 异步任务提交失败: {str(e)}")
# 这个失败不影响订单保存,系统有补偿机制
# 启动异步线程(后台运行,不阻塞主流程)
thread = threading.Thread(target=_async_task)
thread.daemon = True # 设置为守护线程,主线程退出时会自动结束
thread.start()
@receiver(pre_save, sender=Dingdan)
def handle_order_status_change_lightweight(sender, instance, **kwargs):
"""
轻量级订单状态变化监听器
设计原则:
1. 绝对不阻塞订单保存
2. 不依赖外部服务可用性
3. 只记录,不执行
"""
try:
# 1. 快速检查:如果是新建订单,直接返回
if instance.pk is None:
return
# 2. 快速获取旧状态(使用数据库当前值,不加载完整对象)
try:
old_status = Dingdan.objects.filter(pk=instance.pk).values_list('zhuangtai', flat=True).first()
except Exception:
old_status = None
# 3. 状态变化判断
if old_status is None or old_status != instance.zhuangtai:
# 状态发生变化,记录到订单扩展字段或日志(但不要阻塞)
try:
# 这里只是示例,实际可以根据需要记录状态变化历史
pass
except:
pass # 即使记录失败也不影响主流程
# 4. 特殊状态处理状态变为8
if old_status != 8 and instance.zhuangtai == 8:
# 4.1 立即记录日志(不等待)
logger.info(f"订单 {instance.dingdan_id} 状态变为8")
# 4.2 异步尝试启动定时任务(不阻塞主流程)
# 使用线程池或直接新线程,确保不阻塞
try:
# 启动异步线程尝试提交Celery任务
if hasattr(settings, 'CELERY_BROKER_URL') and settings.CELERY_BROKER_URL:
# 在独立线程中尝试,完全不阻塞
threading.Thread(
target=try_start_celery_task_async,
args=(instance.dingdan_id, settings.ORDER_EXPIRE_SECONDS),
daemon=True
).start()
except Exception as e:
# 即使线程启动失败也不影响主流程
logger.warning(f"启动异步任务线程失败: {str(e)}")
# 5. 状态从8变为其他
elif old_status == 8 and instance.zhuangtai != 8:
# 状态不再是8记录日志即可
# 定时任务执行时会检查状态如果不是8会自动跳过
logger.info(f"订单 {instance.dingdan_id} 状态从8变为{instance.zhuangtai}")
except Exception as e:
# 捕获所有异常,确保不影响订单保存
# 只记录错误,不抛出异常
logger.error(f"信号处理异常(已捕获,不影响订单): {str(e)}")'''
'''from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.conf import settings
import logging
from dingdan.models import Dingdan
from dingdan.tasks import process_expired_order
logger = logging.getLogger(__name__)
@receiver(pre_save, sender=Dingdan)
def handle_order_status_change(sender, instance, **kwargs):
"""
监听订单状态变化
当状态变为8时启动48小时延时任务
当状态从8变为其他时任务执行时会自动跳过无需取消
"""
try:
# 如果是新建订单,没有旧记录,直接返回
if instance.pk is None:
return
# 获取数据库中旧的订单记录
old_instance = Dingdan.objects.get(pk=instance.pk)
# ============ 状态从非8变为8 ============
if old_instance.zhuangtai != 8 and instance.zhuangtai == 8:
# 启动48小时后的延时任务countdown单位
task = process_expired_order.apply_async(
args=[instance.dingdan_id],
countdown=settings.ORDER_EXPIRE_SECONDS, # 172800秒 = 48小时
queue='order_tasks',
priority=9 # 高优先级
)
logger.info(
f"订单 {instance.dingdan_id} 状态变为8已启动48小时延时任务"
f"任务ID: {task.id}"
)
# 记录任务启动成功(可选,可以存储到订单表或日志表)
# ============ 状态从8变为其他 ============
# 我们不需要取消任务因为任务执行时会检查状态是否为8
# 如果不是状态8任务会自动跳过处理
except Dingdan.DoesNotExist:
# 新订单,无需处理
pass
except Exception as e:
logger.error(f"处理订单状态变化信号失败订单ID: {instance.dingdan_id if hasattr(instance, 'dingdan_id') else '未知'}, 错误: {str(e)}")
# 不抛出异常,避免影响订单保存的主流程
# 信号处理失败不影响订单保存,有周期性检查作为备用方案'''