将文件夹命名从拼音改为英文,整理了一些代码,将导入语句迁移到了头部
This commit is contained in:
@@ -1,12 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
阿龙电竞 - Celery定时任务主配置
|
阿龙电竞 - Celery定时任务主配置
|
||||||
生产环境就绪版本,支持精确延时任务和周期性任务
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from celery import Celery
|
from celery import Celery
|
||||||
from celery.schedules import crontab
|
from celery.schedules import crontab
|
||||||
@@ -26,14 +20,9 @@ app.config_from_object('django.conf:settings', namespace='CELERY')
|
|||||||
#app.autodiscover_tasks()
|
#app.autodiscover_tasks()
|
||||||
app.autodiscover_tasks(['yonghu.tasks', 'yonghu.ranking_tasks', 'dingdan.tasks', 'peizhi.tasks'])
|
app.autodiscover_tasks(['yonghu.tasks', 'yonghu.ranking_tasks', 'dingdan.tasks', 'peizhi.tasks'])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 配置周期性任务(Celery Beat Schedule)
|
# 配置周期性任务(Celery Beat Schedule)
|
||||||
app.conf.beat_schedule = {
|
app.conf.beat_schedule = {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 月榜数据转移(每月最后一天23:50执行)
|
# 月榜数据转移(每月最后一天23:50执行)
|
||||||
'yuebang_zhuanyi': {
|
'yuebang_zhuanyi': {
|
||||||
'task': 'yonghu.ranking_tasks.zhuanyi_yuebang',
|
'task': 'yonghu.ranking_tasks.zhuanyi_yuebang',
|
||||||
@@ -41,8 +30,6 @@ app.conf.beat_schedule = {
|
|||||||
'options': {'queue': 'periodic_tasks', 'priority': 7},
|
'options': {'queue': 'periodic_tasks', 'priority': 7},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 日榜数据转移(每天23:55执行,在清零前)
|
# 日榜数据转移(每天23:55执行,在清零前)
|
||||||
'ribang_zhuanyi': {
|
'ribang_zhuanyi': {
|
||||||
'task': 'yonghu.ranking_tasks.zhuanyi_ribang',
|
'task': 'yonghu.ranking_tasks.zhuanyi_ribang',
|
||||||
@@ -51,8 +38,6 @@ app.conf.beat_schedule = {
|
|||||||
'options': {'queue': 'periodic_tasks', 'priority': 7}, # 优先级高于清零任务
|
'options': {'queue': 'periodic_tasks', 'priority': 7}, # 优先级高于清零任务
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 清理旧历史数据(每月1号凌晨1点执行)
|
# 清理旧历史数据(每月1号凌晨1点执行)
|
||||||
'qingli_jiulishuju': {
|
'qingli_jiulishuju': {
|
||||||
'task': 'yonghu.ranking_tasks.qingli_jiulishuju',
|
'task': 'yonghu.ranking_tasks.qingli_jiulishuju',
|
||||||
@@ -60,21 +45,13 @@ app.conf.beat_schedule = {
|
|||||||
'options': {'queue': 'periodic_tasks', 'priority': 4},
|
'options': {'queue': 'periodic_tasks', 'priority': 4},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
# 补偿检查任务(每5分钟运行)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 🔥 补偿检查任务(每5分钟运行)
|
|
||||||
'check_order_expire_task': {
|
'check_order_expire_task': {
|
||||||
'task': 'dingdan.tasks.check_order_expire_task',
|
'task': 'dingdan.tasks.check_order_expire_task',
|
||||||
'schedule': crontab(minute='*/5000'),
|
'schedule': crontab(minute='*/5000'),
|
||||||
'options': {'queue': 'order_tasks', 'priority': 3},
|
'options': {'queue': 'order_tasks', 'priority': 3},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
# 1. 每日凌晨0点执行 - 清零任务
|
# 1. 每日凌晨0点执行 - 清零任务
|
||||||
'daily_reset_task': {
|
'daily_reset_task': {
|
||||||
'task': 'yonghu.tasks.daily_reset_task',
|
'task': 'yonghu.tasks.daily_reset_task',
|
||||||
@@ -99,10 +76,6 @@ app.conf.beat_schedule = {
|
|||||||
'kwargs': {}
|
'kwargs': {}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 4. 每天凌晨0点5分清理收支记录
|
# 4. 每天凌晨0点5分清理收支记录
|
||||||
'daily_sz_reset_task': {
|
'daily_sz_reset_task': {
|
||||||
'task': 'peizhi.tasks.daily_sz_reset_task',
|
'task': 'peizhi.tasks.daily_sz_reset_task',
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'a_long_dianjing.settings')
|
|||||||
app = Celery('a_long_dianjing')
|
app = Celery('a_long_dianjing')
|
||||||
app.config_from_object('django.conf:settings', namespace='CELERY')
|
app.config_from_object('django.conf:settings', namespace='CELERY')
|
||||||
# 手动导入任务模块,避免与已有 tasks.py 冲突
|
# 手动导入任务模块,避免与已有 tasks.py 冲突
|
||||||
app.autodiscover_tasks(['dingdan.tongzhi_tasks'])
|
app.autodiscover_tasks(['dingdan.notice_tasks'])
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ CELERY_TASK_SERIALIZER = 'json'
|
|||||||
CELERY_RESULT_SERIALIZER = 'json'
|
CELERY_RESULT_SERIALIZER = 'json'
|
||||||
CELERY_TIMEZONE = 'Asia/Shanghai'
|
CELERY_TIMEZONE = 'Asia/Shanghai'
|
||||||
CELERY_ENABLE_UTC = True
|
CELERY_ENABLE_UTC = True
|
||||||
CELERY_IMPORTS = ('dingdan.tongzhi_tasks',)
|
CELERY_IMPORTS = ('dingdan.notice_tasks',)
|
||||||
CELERY_TASK_TIME_LIMIT = 300
|
CELERY_TASK_TIME_LIMIT = 300
|
||||||
CELERY_TASK_SOFT_TIME_LIMIT = 240
|
CELERY_TASK_SOFT_TIME_LIMIT = 240
|
||||||
CELERY_WORKER_CONCURRENCY = 4
|
CELERY_WORKER_CONCURRENCY = 4
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#小程序弹窗序列化器
|
#小程序弹窗序列化器
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from peizhi.models import PopupPage, PopupConfig, PopupImage
|
from config.models import PopupPage, PopupConfig, PopupImage
|
||||||
|
|
||||||
class PopupImageSerializer(serializers.ModelSerializer):
|
class PopupImageSerializer(serializers.ModelSerializer):
|
||||||
"""图片序列化器(输出用)"""
|
"""图片序列化器(输出用)"""
|
||||||
@@ -11,6 +11,8 @@ from django.db.models import F
|
|||||||
from datetime import date
|
from datetime import date
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -217,10 +219,6 @@ def update_guanshi_daily_by_action(yonghuid, action, amount=Decimal('0.00')):
|
|||||||
根据操作行为更新管事每日统计(邀请打手、充值会员、订单/押金分红)
|
根据操作行为更新管事每日统计(邀请打手、充值会员、订单/押金分红)
|
||||||
action: 1 = 邀请打手, 2 = 充值会员, 3 = 商家订单分红, 4 = 押金分红
|
action: 1 = 邀请打手, 2 = 充值会员, 3 = 商家订单分红, 4 = 押金分红
|
||||||
"""
|
"""
|
||||||
from django.db import transaction
|
|
||||||
from django.db.models import F
|
|
||||||
from datetime import date
|
|
||||||
|
|
||||||
today = date.today()
|
today = date.today()
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
@@ -1,74 +1,94 @@
|
|||||||
import hmac
|
import hmac
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
from calendar import monthrange
|
|
||||||
|
|
||||||
from django.contrib.auth.hashers import make_password
|
|
||||||
from django.db.models import Q, Count
|
|
||||||
from django.core.paginator import Paginator
|
|
||||||
|
|
||||||
|
|
||||||
from houtai.utils import update_dashou_daily_by_action
|
|
||||||
from dingdan.models import Lilubiao
|
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
|
|
||||||
|
|
||||||
from utils.oss_utils import upload_to_oss, validate_image, delete_from_oss
|
|
||||||
from shangpin.models import Shangpin, ShangpinLeixing, ShangpinZhuanqu,Huiyuan
|
|
||||||
|
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
from django.db.models import Q, Sum, F
|
|
||||||
from rest_framework import status
|
|
||||||
|
|
||||||
from houtai.models import Role, Permission, RolePermission, UserRole, TixianRiTongji
|
|
||||||
from houtai.utils import verify_kefu_permission
|
|
||||||
import xmltodict
|
import xmltodict
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from dingdan.models import Dashoutupian, Chufajilu, PreSettlement, OrderDashouHistory
|
|
||||||
# apps/houtai/views.py 继续添加
|
|
||||||
|
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
|
|
||||||
from houtai.models import Role
|
|
||||||
|
|
||||||
from yonghu.models import UserMain, UserGuanshi, UserBoss, UserZuzhang
|
|
||||||
|
|
||||||
from shangpin.models import DuociFenhong
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
|
import os
|
||||||
|
import secrets
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
from calendar import monthrange
|
||||||
|
from decimal import Decimal
|
||||||
from datetime import date, datetime, timedelta
|
from datetime import date, datetime, timedelta
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.conf import settings
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
from django.db import models, transaction, IntegrityError
|
||||||
|
from django.db.models import Q, Count, Sum, F, Exists, OuterRef
|
||||||
|
from django.db.models.functions import TruncDate, TruncMonth, TruncYear
|
||||||
|
from django.contrib.auth.hashers import make_password
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
|
||||||
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.parsers import JSONParser
|
from rest_framework import status
|
||||||
|
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||||
|
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
|
||||||
|
|
||||||
from dingdan.utils import update_daily_dispatch_stat
|
# 工具类导入
|
||||||
from shangpin.models import ShangpinLeixing, Huiyuangoumai, Huiyuan
|
from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss
|
||||||
from yonghu.models import UserMain, UserKefu, UserDashou, Xiugaijilu, UserShangjia
|
from backend.utils import (
|
||||||
from peizhi.models import AccountPermission, ClubConfig, Club, DailyDispatchStat, OrderTypeMapping, TixianQuotaDefault
|
update_dashou_daily_by_action,
|
||||||
from dingdan.models import Dingdan, Tuikuanjilu, CrossPlatformOrderData
|
update_shangjia_daily
|
||||||
|
)
|
||||||
|
from orders.utils import update_daily_dispatch_stat, update_daily_payout, sync_order_to_partners
|
||||||
|
from shop.utils import update_dianpu_daily_stat
|
||||||
|
from products.utils import update_shangpin_daily_stat
|
||||||
|
from orders.notice_tasks import dingdan_guangbo
|
||||||
|
from .utils import verify_kefu_permission
|
||||||
|
|
||||||
# views.py
|
# models 集中导入
|
||||||
import os
|
## backend
|
||||||
from peizhi.models import PopupPage, PopupConfig, PopupImage
|
from backend.models import Role, Permission, RolePermission, UserRole, TixianRiTongji
|
||||||
|
|
||||||
|
## users
|
||||||
|
from users.models import (
|
||||||
|
UserMain, UserGuanshi, UserBoss, UserZuzhang,
|
||||||
|
UserKefu, UserDashou, Xiugaijilu, UserShangjia, UserShenheguan
|
||||||
|
)
|
||||||
|
|
||||||
|
## orders
|
||||||
|
from orders.models import (
|
||||||
|
Lilubiao, Dashoutupian, Chufajilu, PreSettlement,
|
||||||
|
OrderDashouHistory, Dingdan, Tuikuanjilu, CrossPlatformOrderData,
|
||||||
|
DingdanShangjia, Fadan, FadanShensuTupian, FadanFenhong
|
||||||
|
)
|
||||||
|
|
||||||
|
## shop
|
||||||
|
from shop.models import Dianpu, DianpuMorenPeizhi, YonghuPingzheng, ShangpinLeixingDianpu, DianpuShangpinShenheShezhi
|
||||||
|
|
||||||
|
## products
|
||||||
|
from products.models import (
|
||||||
|
Shangpin, ShangpinLeixing, ShangpinZhuanqu, Huiyuan,
|
||||||
|
DuociFenhong, Czjilu, Huiyuangoumai, Gsfenhong
|
||||||
|
)
|
||||||
|
|
||||||
|
## config
|
||||||
|
from config.models import (
|
||||||
|
WithdrawConfig, ShangjiaLianjie, AccountPermission, ClubConfig,
|
||||||
|
Club, DailyDispatchStat, OrderTypeMapping, TixianQuotaDefault,
|
||||||
|
DailyIncomeStat, DailyPayoutStat, PopupPage, PopupConfig, PopupImage
|
||||||
|
)
|
||||||
|
|
||||||
|
## rank
|
||||||
|
from rank.models import (
|
||||||
|
KaoheguanBankuai, Bankuai, Chenghao, KaoheCishuFeiyong,
|
||||||
|
YonghuChenghao, ShenheJilu
|
||||||
|
)
|
||||||
|
|
||||||
|
# 序列化器
|
||||||
from .serializers import PopupPageSerializer, PopupConfigSerializer, PopupImageSerializer
|
from .serializers import PopupPageSerializer, PopupConfigSerializer, PopupImageSerializer
|
||||||
|
|
||||||
|
# 全局常量、日志对象
|
||||||
|
logger = logging.getLogger('houtai')
|
||||||
|
SHOP_PRODUCT_PERMS = ['1199ab', '1199abc', '1199abd']
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class GetClubConfigView(APIView):
|
class GetClubConfigView(APIView):
|
||||||
@@ -611,7 +631,6 @@ class GetCrossOrderDetailView(APIView):
|
|||||||
|
|
||||||
# ========== 新增:查询更换打手生成的新订单信息 ==========
|
# ========== 新增:查询更换打手生成的新订单信息 ==========
|
||||||
try:
|
try:
|
||||||
from peizhi.models import ShangjiaLianjie
|
|
||||||
new_lianjie = ShangjiaLianjie.objects.filter(
|
new_lianjie = ShangjiaLianjie.objects.filter(
|
||||||
previous_dingdan_id=dingdan_id
|
previous_dingdan_id=dingdan_id
|
||||||
).order_by('-create_time').first()
|
).order_by('-create_time').first()
|
||||||
@@ -763,7 +782,6 @@ class CrossPlatformRefundView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
# ✅ 新方法(行为驱动,更安全)
|
# ✅ 新方法(行为驱动,更安全)
|
||||||
from houtai.utils import update_shangjia_daily
|
|
||||||
shangjia_ext = order.shangjia_kuozhan
|
shangjia_ext = order.shangjia_kuozhan
|
||||||
shangjia_id = shangjia_ext.shangjia_id
|
shangjia_id = shangjia_ext.shangjia_id
|
||||||
update_shangjia_daily(
|
update_shangjia_daily(
|
||||||
@@ -1034,7 +1052,6 @@ class CrossPlatformRefundView(APIView):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# ✅ 新方法(行为驱动,更安全)
|
# ✅ 新方法(行为驱动,更安全)
|
||||||
from houtai.utils import update_shangjia_daily
|
|
||||||
update_shangjia_daily(
|
update_shangjia_daily(
|
||||||
yonghuid=shangjia_id,
|
yonghuid=shangjia_id,
|
||||||
amount=Decimal(str(order.jine)),
|
amount=Decimal(str(order.jine)),
|
||||||
@@ -1133,13 +1150,10 @@ class CrossPlatformRefundView(APIView):
|
|||||||
try:
|
try:
|
||||||
#更新支出记录
|
#更新支出记录
|
||||||
if order.fadan_pingtai == 1:
|
if order.fadan_pingtai == 1:
|
||||||
from dingdan.utils import update_daily_payout
|
|
||||||
update_daily_payout(order.jine)
|
update_daily_payout(order.jine)
|
||||||
|
|
||||||
# ========== 新增:商品和店铺每日统计 ==========
|
# ========== 新增:商品和店铺每日统计 ==========
|
||||||
try:
|
try:
|
||||||
from shangdian.utils import update_dianpu_daily_stat
|
|
||||||
from shangpin.utils import update_shangpin_daily_stat
|
|
||||||
# 商品每日统计(只要有商品ID就执行)
|
# 商品每日统计(只要有商品ID就执行)
|
||||||
if order.shangpin_id:
|
if order.shangpin_id:
|
||||||
# 从扩展表获取已计算好的收益值(下单时已存储)
|
# 从扩展表获取已计算好的收益值(下单时已存储)
|
||||||
@@ -1500,9 +1514,6 @@ class PartnerRefundNotifyView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from houtai.utils import update_dashou_daily_by_action
|
|
||||||
|
|
||||||
class CrossPlatformForceCompleteView(APIView):
|
class CrossPlatformForceCompleteView(APIView):
|
||||||
"""
|
"""
|
||||||
跨平台订单强制结单接口
|
跨平台订单强制结单接口
|
||||||
@@ -1646,8 +1657,6 @@ class CrossPlatformForceCompleteView(APIView):
|
|||||||
# ========== 新增:商品和店铺每日统计 ==========
|
# ========== 新增:商品和店铺每日统计 ==========
|
||||||
if order.fadan_pingtai == 1:
|
if order.fadan_pingtai == 1:
|
||||||
try:
|
try:
|
||||||
from shangdian import update_dianpu_daily_stat
|
|
||||||
from shangpin import update_shangpin_daily_stat
|
|
||||||
# 商品每日统计(只要有商品ID就执行)
|
# 商品每日统计(只要有商品ID就执行)
|
||||||
if order.shangpin_id:
|
if order.shangpin_id:
|
||||||
# 从扩展表获取已计算好的收益值(下单时已存储)
|
# 从扩展表获取已计算好的收益值(下单时已存储)
|
||||||
@@ -1705,8 +1714,6 @@ class CrossPlatformForceCompleteView(APIView):
|
|||||||
# ========== 新增:商品和店铺每日统计 ==========
|
# ========== 新增:商品和店铺每日统计 ==========
|
||||||
if order.fadan_pingtai == 1:
|
if order.fadan_pingtai == 1:
|
||||||
try:
|
try:
|
||||||
from shangdian.utils import update_dianpu_daily_stat
|
|
||||||
from shangpin.utils import update_shangpin_daily_stat
|
|
||||||
# 商品每日统计(只要有商品ID就执行)
|
# 商品每日统计(只要有商品ID就执行)
|
||||||
if order.shangpin_id:
|
if order.shangpin_id:
|
||||||
# 从扩展表获取已计算好的收益值(下单时已存储)
|
# 从扩展表获取已计算好的收益值(下单时已存储)
|
||||||
@@ -1803,7 +1810,6 @@ class CrossPlatformForceCompleteView(APIView):
|
|||||||
if order.fadan_pingtai == 2:
|
if order.fadan_pingtai == 2:
|
||||||
shangjia_ext = order.shangjia_kuozhan
|
shangjia_ext = order.shangjia_kuozhan
|
||||||
# ✅ 新方法(行为驱动,更安全)
|
# ✅ 新方法(行为驱动,更安全)
|
||||||
from houtai.utils import update_shangjia_daily
|
|
||||||
update_shangjia_daily(
|
update_shangjia_daily(
|
||||||
yonghuid=shangjia_ext.shangjia_id,
|
yonghuid=shangjia_ext.shangjia_id,
|
||||||
amount=Decimal(str(order.jine)),
|
amount=Decimal(str(order.jine)),
|
||||||
@@ -3531,10 +3537,6 @@ class PreSettlementActionView(APIView):
|
|||||||
# ---------- 打手每日统计(成交) ----------
|
# ---------- 打手每日统计(成交) ----------
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from houtai.utils import update_dashou_daily_by_action
|
|
||||||
update_dashou_daily_by_action(
|
update_dashou_daily_by_action(
|
||||||
yonghuid=dashou_id,
|
yonghuid=dashou_id,
|
||||||
amount=amount,
|
amount=amount,
|
||||||
@@ -3562,7 +3564,6 @@ class PreSettlementActionView(APIView):
|
|||||||
try:
|
try:
|
||||||
dashou_id = pre_settlement.dashou_id
|
dashou_id = pre_settlement.dashou_id
|
||||||
amount = pre_settlement.amount
|
amount = pre_settlement.amount
|
||||||
from houtai.utils import update_dashou_daily_by_action
|
|
||||||
update_dashou_daily_by_action(
|
update_dashou_daily_by_action(
|
||||||
yonghuid=dashou_id,
|
yonghuid=dashou_id,
|
||||||
amount=amount,
|
amount=amount,
|
||||||
@@ -3727,10 +3728,6 @@ class ModifyRolePermissionView(APIView):
|
|||||||
return Response({'code': 400, 'msg': '无效的 action'})
|
return Response({'code': 400, 'msg': '无效的 action'})
|
||||||
|
|
||||||
|
|
||||||
import random
|
|
||||||
import string
|
|
||||||
from django.db import transaction
|
|
||||||
|
|
||||||
class AddRoleView(APIView):
|
class AddRoleView(APIView):
|
||||||
"""
|
"""
|
||||||
添加角色:自动生成角色编码,前端提供角色名称、描述、选择的权限列表
|
添加角色:自动生成角色编码,前端提供角色名称、描述、选择的权限列表
|
||||||
@@ -3788,18 +3785,6 @@ class AddRoleView(APIView):
|
|||||||
return Response({'code': 0, 'msg': '角色添加成功', 'data': {'role_code': role_code}})
|
return Response({'code': 0, 'msg': '角色添加成功', 'data': {'role_code': role_code}})
|
||||||
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import traceback
|
|
||||||
from django.core.paginator import Paginator
|
|
||||||
from rest_framework.views import APIView
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.permissions import IsAuthenticated
|
|
||||||
from django.db.models import Exists, OuterRef # 若不再使用可移除,此处保留以备后用
|
|
||||||
|
|
||||||
# 请根据项目实际路径导入 verify_kefu_permission
|
|
||||||
from .utils import verify_kefu_permission
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class GetAdminUserListView(APIView):
|
class GetAdminUserListView(APIView):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
@@ -5840,7 +5825,6 @@ class DeleteProductView(APIView):
|
|||||||
tupian_url = product.tupian_url
|
tupian_url = product.tupian_url
|
||||||
if tupian_url:
|
if tupian_url:
|
||||||
# 调用已有的 delete_from_oss 函数
|
# 调用已有的 delete_from_oss 函数
|
||||||
from utils.oss_utils import delete_from_oss
|
|
||||||
delete_success = delete_from_oss(tupian_url)
|
delete_success = delete_from_oss(tupian_url)
|
||||||
if not delete_success:
|
if not delete_success:
|
||||||
logger.warning(f"删除商品图片失败: {tupian_url}, 商品ID: {product_id}")
|
logger.warning(f"删除商品图片失败: {tupian_url}, 商品ID: {product_id}")
|
||||||
@@ -6230,10 +6214,6 @@ class UpdateMemberView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import random
|
|
||||||
import string
|
|
||||||
|
|
||||||
class AddMemberView(APIView):
|
class AddMemberView(APIView):
|
||||||
"""
|
"""
|
||||||
添加会员
|
添加会员
|
||||||
@@ -6836,7 +6816,6 @@ class GetWithdrawSettingsView(APIView):
|
|||||||
today_totals[rec.leixing] = float(rec.total_amount)
|
today_totals[rec.leixing] = float(rec.total_amount)
|
||||||
|
|
||||||
# 🆕 获取提现模式(自动1 / 手动2),默认2
|
# 🆕 获取提现模式(自动1 / 手动2),默认2
|
||||||
from peizhi.models import WithdrawConfig # 确保导入模型
|
|
||||||
config = WithdrawConfig.objects.filter(id=1).first()
|
config = WithdrawConfig.objects.filter(id=1).first()
|
||||||
withdraw_mode = config.mode if config else 2
|
withdraw_mode = config.mode if config else 2
|
||||||
|
|
||||||
@@ -6983,7 +6962,6 @@ class UpdateWithdrawSettingsView(APIView):
|
|||||||
obj.save()
|
obj.save()
|
||||||
|
|
||||||
# 🆕 修改提现模式(有5500a/b/c任一即可)
|
# 🆕 修改提现模式(有5500a/b/c任一即可)
|
||||||
from peizhi.models import WithdrawConfig
|
|
||||||
withdraw_mode = request.data.get('withdraw_mode')
|
withdraw_mode = request.data.get('withdraw_mode')
|
||||||
if withdraw_mode is not None:
|
if withdraw_mode is not None:
|
||||||
if not any(p in permissions for p in ('5500a', '5500b', '5500c')):
|
if not any(p in permissions for p in ('5500a', '5500b', '5500c')):
|
||||||
@@ -7545,343 +7523,6 @@ class GetProductTypeZoneView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
'''class ModifyProductTypeZoneView(APIView):
|
|
||||||
"""
|
|
||||||
统一修改商品类型/专区(增、删、改)
|
|
||||||
权限:需要拥有 7007a
|
|
||||||
请求:POST /houtai/htxgsplx
|
|
||||||
支持 multipart/form-data 和 application/json
|
|
||||||
参数(通用):
|
|
||||||
username: 客服账号
|
|
||||||
type: 'leixing' 或 'zhuanqu'
|
|
||||||
action: 'add' / 'update' / 'delete'
|
|
||||||
类型专用参数:
|
|
||||||
- add/update: id (update时必填), jieshao, tupian_url(可不上传), file(图片文件), yaoqiuleixing, huiyuan_id, yongjin, shenhezhuangtai, is_cross_enabled, sync_to_products (可选)
|
|
||||||
- delete: id, delete_products (布尔)
|
|
||||||
专区专用参数:
|
|
||||||
- add/update: id (update时必填), mingzi, leixing_id, shenhezhuangtai
|
|
||||||
- delete: id, delete_products (布尔)
|
|
||||||
"""
|
|
||||||
permission_classes = [IsAuthenticated]
|
|
||||||
parser_classes = [JSONParser, MultiPartParser, FormParser]
|
|
||||||
|
|
||||||
def post(self, request):
|
|
||||||
username = request.data.get('username', '').strip()
|
|
||||||
obj_type = request.data.get('type') # 'leixing' 或 'zhuanqu'
|
|
||||||
action = request.data.get('action') # 'add', 'update', 'delete'
|
|
||||||
|
|
||||||
if not username or not obj_type or not action:
|
|
||||||
return Response({'code': 400, 'msg': '缺少必要参数'})
|
|
||||||
|
|
||||||
# 权限校验
|
|
||||||
kefu, permissions = verify_kefu_permission(request, username)
|
|
||||||
if kefu is None:
|
|
||||||
return permissions
|
|
||||||
if '7007a' not in permissions:
|
|
||||||
return Response({'code': 403, 'msg': '您无权进行此操作'})
|
|
||||||
|
|
||||||
if obj_type == 'leixing':
|
|
||||||
return self.handle_leixing(request, action)
|
|
||||||
elif obj_type == 'zhuanqu':
|
|
||||||
return self.handle_zhuanqu(request, action)
|
|
||||||
else:
|
|
||||||
return Response({'code': 400, 'msg': '无效的type参数'})
|
|
||||||
|
|
||||||
# ---------- 商品类型处理 ----------
|
|
||||||
def handle_leixing(self, request, action):
|
|
||||||
if action == 'add':
|
|
||||||
return self.add_leixing(request)
|
|
||||||
elif action == 'update':
|
|
||||||
return self.update_leixing(request)
|
|
||||||
elif action == 'delete':
|
|
||||||
return self.delete_leixing(request)
|
|
||||||
else:
|
|
||||||
return Response({'code': 400, 'msg': '无效的action'})
|
|
||||||
|
|
||||||
def add_leixing(self, request):
|
|
||||||
# 获取表单字段
|
|
||||||
jieshao = request.data.get('jieshao', '').strip()
|
|
||||||
yaoqiuleixing = request.data.get('yaoqiuleixing')
|
|
||||||
huiyuan_id = request.data.get('huiyuan_id', '').strip() or None
|
|
||||||
yongjin = request.data.get('yongjin')
|
|
||||||
shenhezhuangtai = request.data.get('shenhezhuangtai', 1)
|
|
||||||
is_cross_enabled = request.data.get('is_cross_enabled', False)
|
|
||||||
image_file = request.FILES.get('file') # 上传的图片文件
|
|
||||||
|
|
||||||
if not jieshao:
|
|
||||||
return Response({'code': 400, 'msg': '类型名称不能为空'})
|
|
||||||
if yaoqiuleixing not in [1, 2]:
|
|
||||||
return Response({'code': 400, 'msg': '抢单要求类型无效'})
|
|
||||||
if not image_file:
|
|
||||||
return Response({'code': 400, 'msg': '请上传类型图片'})
|
|
||||||
|
|
||||||
# 校验图片
|
|
||||||
try:
|
|
||||||
from utils.oss_utils import validate_image
|
|
||||||
valid, msg = validate_image(image_file)
|
|
||||||
if not valid:
|
|
||||||
return Response({'code': 400, 'msg': msg})
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 上传图片到OSS
|
|
||||||
ext = image_file.name.split('.')[-1].lower()
|
|
||||||
new_filename = f"{uuid.uuid4().hex}.{ext}"
|
|
||||||
oss_path = f"a_long/shangpin/shangpinleixing/{new_filename}"
|
|
||||||
url = upload_to_oss(image_file, oss_path)
|
|
||||||
if not url:
|
|
||||||
return Response({'code': 500, 'msg': '图片上传失败'})
|
|
||||||
|
|
||||||
# 创建类型
|
|
||||||
try:
|
|
||||||
with transaction.atomic():
|
|
||||||
leixing = ShangpinLeixing.objects.create(
|
|
||||||
jieshao=jieshao,
|
|
||||||
tupian_url=oss_path, # 存储相对路径
|
|
||||||
yaoqiuleixing=yaoqiuleixing,
|
|
||||||
huiyuan_id=huiyuan_id,
|
|
||||||
yongjin=Decimal(yongjin) if yongjin else None,
|
|
||||||
shenhezhuangtai=int(shenhezhuangtai),
|
|
||||||
is_cross_enabled=is_cross_enabled in [True, 'true', '1'],
|
|
||||||
paixu=0 # 新添加的类型排序默认0,前端可再调整
|
|
||||||
)
|
|
||||||
return Response({'code': 0, 'msg': '添加成功', 'data': {'id': leixing.id}})
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"添加类型失败: {e}", exc_info=True)
|
|
||||||
# 删除已上传的图片
|
|
||||||
delete_from_oss(oss_path)
|
|
||||||
return Response({'code': 500, 'msg': '数据库错误'})
|
|
||||||
|
|
||||||
def update_leixing(self, request):
|
|
||||||
type_id = request.data.get('id')
|
|
||||||
if not type_id:
|
|
||||||
return Response({'code': 400, 'msg': '缺少类型ID'})
|
|
||||||
try:
|
|
||||||
leixing = ShangpinLeixing.objects.get(id=type_id)
|
|
||||||
except ShangpinLeixing.DoesNotExist:
|
|
||||||
return Response({'code': 404, 'msg': '类型不存在'})
|
|
||||||
|
|
||||||
# 收集修改字段
|
|
||||||
changes = {}
|
|
||||||
jieshao = request.data.get('jieshao')
|
|
||||||
if jieshao is not None and jieshao != leixing.jieshao:
|
|
||||||
changes['jieshao'] = jieshao.strip()
|
|
||||||
|
|
||||||
yaoqiuleixing = request.data.get('yaoqiuleixing')
|
|
||||||
if yaoqiuleixing is not None and int(yaoqiuleixing) != leixing.yaoqiuleixing:
|
|
||||||
changes['yaoqiuleixing'] = int(yaoqiuleixing)
|
|
||||||
|
|
||||||
huiyuan_id = request.data.get('huiyuan_id', '').strip() or None
|
|
||||||
if huiyuan_id != leixing.huiyuan_id:
|
|
||||||
changes['huiyuan_id'] = huiyuan_id
|
|
||||||
|
|
||||||
yongjin = request.data.get('yongjin')
|
|
||||||
if yongjin is not None:
|
|
||||||
new_yongjin = Decimal(yongjin) if yongjin else None
|
|
||||||
if new_yongjin != leixing.yongjin:
|
|
||||||
changes['yongjin'] = new_yongjin
|
|
||||||
|
|
||||||
shenhezhuangtai = request.data.get('shenhezhuangtai')
|
|
||||||
if shenhezhuangtai is not None and int(shenhezhuangtai) != leixing.shenhezhuangtai:
|
|
||||||
changes['shenhezhuangtai'] = int(shenhezhuangtai)
|
|
||||||
|
|
||||||
is_cross = request.data.get('is_cross_enabled')
|
|
||||||
if is_cross is not None:
|
|
||||||
new_cross = is_cross in [True, 'true', '1']
|
|
||||||
if new_cross != leixing.is_cross_enabled:
|
|
||||||
changes['is_cross_enabled'] = new_cross
|
|
||||||
|
|
||||||
# 图片处理
|
|
||||||
image_file = request.FILES.get('file')
|
|
||||||
old_image_path = leixing.tupian_url
|
|
||||||
if image_file:
|
|
||||||
# 验证并上传新图片
|
|
||||||
try:
|
|
||||||
from utils.oss_utils import validate_image
|
|
||||||
valid, msg = validate_image(image_file)
|
|
||||||
if not valid:
|
|
||||||
return Response({'code': 400, 'msg': msg})
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
ext = image_file.name.split('.')[-1].lower()
|
|
||||||
new_filename = f"{uuid.uuid4().hex}.{ext}"
|
|
||||||
oss_path = f"a_long/shangpin/shangpinleixing/{new_filename}"
|
|
||||||
url = upload_to_oss(image_file, oss_path)
|
|
||||||
if not url:
|
|
||||||
return Response({'code': 500, 'msg': '图片上传失败'})
|
|
||||||
changes['tupian_url'] = oss_path
|
|
||||||
|
|
||||||
if not changes and not image_file:
|
|
||||||
return Response({'code': 400, 'msg': '未做任何修改'})
|
|
||||||
|
|
||||||
# 执行更新
|
|
||||||
sync_to_products = request.data.get('sync_to_products') in [True, 'true', '1']
|
|
||||||
try:
|
|
||||||
with transaction.atomic():
|
|
||||||
# 更新类型
|
|
||||||
for field, value in changes.items():
|
|
||||||
setattr(leixing, field, value)
|
|
||||||
leixing.save()
|
|
||||||
|
|
||||||
# 如果修改了抢单要求相关字段且需要同步商品
|
|
||||||
if sync_to_products and ('yaoqiuleixing' in changes or 'huiyuan_id' in changes or 'yongjin' in changes):
|
|
||||||
# 更新所有关联此类型的商品
|
|
||||||
product_updates = {}
|
|
||||||
if 'yaoqiuleixing' in changes:
|
|
||||||
product_updates['yaoqiuleixing'] = changes['yaoqiuleixing']
|
|
||||||
if 'huiyuan_id' in changes:
|
|
||||||
product_updates['huiyuan_id'] = changes['huiyuan_id']
|
|
||||||
if 'yongjin' in changes:
|
|
||||||
product_updates['yongjin'] = changes['yongjin']
|
|
||||||
Shangpin.objects.filter(leixing_id=type_id).update(**product_updates)
|
|
||||||
|
|
||||||
# 删除旧图片(新图片上传成功后才删除)
|
|
||||||
if image_file and old_image_path:
|
|
||||||
delete_from_oss(old_image_path)
|
|
||||||
|
|
||||||
return Response({'code': 0, 'msg': '修改成功'})
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"更新类型失败: {e}", exc_info=True)
|
|
||||||
# 如果上传了新图片,尝试删除
|
|
||||||
if image_file and 'oss_path' in locals():
|
|
||||||
delete_from_oss(oss_path)
|
|
||||||
return Response({'code': 500, 'msg': '修改失败'})
|
|
||||||
|
|
||||||
def delete_leixing(self, request):
|
|
||||||
type_id = request.data.get('id')
|
|
||||||
if not type_id:
|
|
||||||
return Response({'code': 400, 'msg': '缺少类型ID'})
|
|
||||||
delete_products = request.data.get('delete_products') in [True, 'true', '1']
|
|
||||||
|
|
||||||
try:
|
|
||||||
leixing = ShangpinLeixing.objects.get(id=type_id)
|
|
||||||
except ShangpinLeixing.DoesNotExist:
|
|
||||||
return Response({'code': 404, 'msg': '类型不存在'})
|
|
||||||
|
|
||||||
with transaction.atomic():
|
|
||||||
# 删除该类型下的所有专区
|
|
||||||
zones = ShangpinZhuanqu.objects.filter(leixing_id=type_id)
|
|
||||||
if delete_products:
|
|
||||||
# 删除专区下的所有商品
|
|
||||||
for zone in zones:
|
|
||||||
Shangpin.objects.filter(zhuanqu_id=zone.id).delete()
|
|
||||||
# 删除所有专区
|
|
||||||
zones.delete()
|
|
||||||
# 删除该类型下的所有商品(可能还有直接关联类型但无专区的商品)
|
|
||||||
Shangpin.objects.filter(leixing_id=type_id).delete()
|
|
||||||
else:
|
|
||||||
# 只删除专区,不删除商品
|
|
||||||
zones.delete()
|
|
||||||
# 商品保持原样(leixing_id 保留,但类型已删除,可能导致数据不一致,按需求不处理)
|
|
||||||
# 也可以将商品的 leixing_id 置空
|
|
||||||
Shangpin.objects.filter(leixing_id=type_id).update(leixing_id=None)
|
|
||||||
|
|
||||||
# 删除类型本身
|
|
||||||
leixing.delete()
|
|
||||||
|
|
||||||
# 删除类型图片
|
|
||||||
if leixing.tupian_url:
|
|
||||||
delete_from_oss(leixing.tupian_url)
|
|
||||||
|
|
||||||
return Response({'code': 0, 'msg': '删除成功'})
|
|
||||||
|
|
||||||
# ---------- 商品专区处理 ----------
|
|
||||||
def handle_zhuanqu(self, request, action):
|
|
||||||
if action == 'add':
|
|
||||||
return self.add_zhuanqu(request)
|
|
||||||
elif action == 'update':
|
|
||||||
return self.update_zhuanqu(request)
|
|
||||||
elif action == 'delete':
|
|
||||||
return self.delete_zhuanqu(request)
|
|
||||||
else:
|
|
||||||
return Response({'code': 400, 'msg': '无效的action'})
|
|
||||||
|
|
||||||
def add_zhuanqu(self, request):
|
|
||||||
mingzi = request.data.get('mingzi', '').strip()
|
|
||||||
leixing_id = request.data.get('leixing_id')
|
|
||||||
shenhezhuangtai = request.data.get('shenhezhuangtai', 1)
|
|
||||||
|
|
||||||
if not mingzi:
|
|
||||||
return Response({'code': 400, 'msg': '专区名称不能为空'})
|
|
||||||
if not leixing_id:
|
|
||||||
return Response({'code': 400, 'msg': '请选择所属类型'})
|
|
||||||
# 校验类型是否存在
|
|
||||||
if not ShangpinLeixing.objects.filter(id=leixing_id).exists():
|
|
||||||
return Response({'code': 404, 'msg': '所属类型不存在'})
|
|
||||||
|
|
||||||
try:
|
|
||||||
with transaction.atomic():
|
|
||||||
zhuanqu = ShangpinZhuanqu.objects.create(
|
|
||||||
mingzi=mingzi,
|
|
||||||
leixing_id=int(leixing_id),
|
|
||||||
shenhezhuangtai=int(shenhezhuangtai),
|
|
||||||
paixu=0
|
|
||||||
)
|
|
||||||
return Response({'code': 0, 'msg': '添加成功', 'data': {'id': zhuanqu.id}})
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"添加专区失败: {e}", exc_info=True)
|
|
||||||
return Response({'code': 500, 'msg': '数据库错误'})
|
|
||||||
|
|
||||||
def update_zhuanqu(self, request):
|
|
||||||
zone_id = request.data.get('id')
|
|
||||||
if not zone_id:
|
|
||||||
return Response({'code': 400, 'msg': '缺少专区ID'})
|
|
||||||
try:
|
|
||||||
zhuanqu = ShangpinZhuanqu.objects.get(id=zone_id)
|
|
||||||
except ShangpinZhuanqu.DoesNotExist:
|
|
||||||
return Response({'code': 404, 'msg': '专区不存在'})
|
|
||||||
|
|
||||||
changes = {}
|
|
||||||
mingzi = request.data.get('mingzi')
|
|
||||||
if mingzi is not None and mingzi != zhuanqu.mingzi:
|
|
||||||
changes['mingzi'] = mingzi.strip()
|
|
||||||
leixing_id = request.data.get('leixing_id')
|
|
||||||
if leixing_id is not None and int(leixing_id) != zhuanqu.leixing_id:
|
|
||||||
# 校验新类型存在
|
|
||||||
if not ShangpinLeixing.objects.filter(id=leixing_id).exists():
|
|
||||||
return Response({'code': 404, 'msg': '新所属类型不存在'})
|
|
||||||
changes['leixing_id'] = int(leixing_id)
|
|
||||||
shenhezhuangtai = request.data.get('shenhezhuangtai')
|
|
||||||
if shenhezhuangtai is not None and int(shenhezhuangtai) != zhuanqu.shenhezhuangtai:
|
|
||||||
changes['shenhezhuangtai'] = int(shenhezhuangtai)
|
|
||||||
|
|
||||||
if not changes:
|
|
||||||
return Response({'code': 400, 'msg': '未做任何修改'})
|
|
||||||
|
|
||||||
try:
|
|
||||||
with transaction.atomic():
|
|
||||||
for field, value in changes.items():
|
|
||||||
setattr(zhuanqu, field, value)
|
|
||||||
zhuanqu.save()
|
|
||||||
return Response({'code': 0, 'msg': '修改成功'})
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"更新专区失败: {e}", exc_info=True)
|
|
||||||
return Response({'code': 500, 'msg': '修改失败'})
|
|
||||||
|
|
||||||
def delete_zhuanqu(self, request):
|
|
||||||
zone_id = request.data.get('id')
|
|
||||||
if not zone_id:
|
|
||||||
return Response({'code': 400, 'msg': '缺少专区ID'})
|
|
||||||
delete_products = request.data.get('delete_products') in [True, 'true', '1']
|
|
||||||
|
|
||||||
try:
|
|
||||||
zhuanqu = ShangpinZhuanqu.objects.get(id=zone_id)
|
|
||||||
except ShangpinZhuanqu.DoesNotExist:
|
|
||||||
return Response({'code': 404, 'msg': '专区不存在'})
|
|
||||||
|
|
||||||
with transaction.atomic():
|
|
||||||
if delete_products:
|
|
||||||
Shangpin.objects.filter(zhuanqu_id=zone_id).delete()
|
|
||||||
else:
|
|
||||||
Shangpin.objects.filter(zhuanqu_id=zone_id).update(zhuanqu_id=None)
|
|
||||||
zhuanqu.delete()
|
|
||||||
|
|
||||||
return Response({'code': 0, 'msg': '删除成功'})'''
|
|
||||||
|
|
||||||
class ModifyProductTypeZoneView(APIView):
|
class ModifyProductTypeZoneView(APIView):
|
||||||
"""
|
"""
|
||||||
统一修改商品类型/专区(增、删、改)
|
统一修改商品类型/专区(增、删、改)
|
||||||
@@ -7964,7 +7605,6 @@ class ModifyProductTypeZoneView(APIView):
|
|||||||
|
|
||||||
# 验证图片
|
# 验证图片
|
||||||
try:
|
try:
|
||||||
from utils.oss_utils import validate_image
|
|
||||||
valid, msg = validate_image(image_file)
|
valid, msg = validate_image(image_file)
|
||||||
if not valid:
|
if not valid:
|
||||||
return Response({'code': 400, 'msg': msg})
|
return Response({'code': 400, 'msg': msg})
|
||||||
@@ -8067,7 +7707,6 @@ class ModifyProductTypeZoneView(APIView):
|
|||||||
old_image_path = leixing.tupian_url
|
old_image_path = leixing.tupian_url
|
||||||
if image_file:
|
if image_file:
|
||||||
try:
|
try:
|
||||||
from utils.oss_utils import validate_image
|
|
||||||
valid, msg = validate_image(image_file)
|
valid, msg = validate_image(image_file)
|
||||||
if not valid:
|
if not valid:
|
||||||
return Response({'code': 400, 'msg': msg})
|
return Response({'code': 400, 'msg': msg})
|
||||||
@@ -8286,7 +7925,6 @@ class GetRateView(APIView):
|
|||||||
if '7007b' not in permissions:
|
if '7007b' not in permissions:
|
||||||
return Response({'code': 403, 'msg': '您无权查看费率'})
|
return Response({'code': 403, 'msg': '您无权查看费率'})
|
||||||
|
|
||||||
from dingdan.models import Lilubiao
|
|
||||||
try:
|
try:
|
||||||
platform_rate_obj = Lilubiao.objects.get(fadanpingtai='1')
|
platform_rate_obj = Lilubiao.objects.get(fadanpingtai='1')
|
||||||
merchant_rate_obj = Lilubiao.objects.get(fadanpingtai='3')
|
merchant_rate_obj = Lilubiao.objects.get(fadanpingtai='3')
|
||||||
@@ -8438,7 +8076,6 @@ class PopupNoticeModifyAPIView(APIView):
|
|||||||
return Response({'code': 400, 'msg': f'未知的action: {action}'})
|
return Response({'code': 400, 'msg': f'未知的action: {action}'})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# 捕获未预期的异常,返回服务器错误
|
# 捕获未预期的异常,返回服务器错误
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return Response({'code': 500, 'msg': f'服务器错误: {str(e)}'})
|
return Response({'code': 500, 'msg': f'服务器错误: {str(e)}'})
|
||||||
|
|
||||||
@@ -8695,8 +8332,6 @@ class PopupNoticeModifyAPIView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from shangdian.models import Dianpu, DianpuMorenPeizhi, YonghuPingzheng
|
|
||||||
class ShopListView(APIView):
|
class ShopListView(APIView):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
@@ -8792,7 +8427,7 @@ class ShopListView(APIView):
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
from django.db import transaction, IntegrityError
|
|
||||||
class ShopModifyView(APIView):
|
class ShopModifyView(APIView):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
@@ -8958,25 +8593,7 @@ class ShopModifyView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
# houtai/views.py (完整新增接口,可直接追加到文件末尾)
|
|
||||||
import logging
|
|
||||||
from django.db import models # Q 查询必须导入
|
|
||||||
from django.core.paginator import Paginator
|
|
||||||
from django.db import transaction
|
|
||||||
from rest_framework.views import APIView
|
|
||||||
from rest_framework.permissions import IsAuthenticated
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from shangpin.models import ShangpinLeixing # 公共商品类型
|
|
||||||
from shangdian.models import (
|
|
||||||
Dianpu, ShangpinLeixingDianpu,
|
|
||||||
DianpuShangpinShenheShezhi, YonghuPingzheng
|
|
||||||
)
|
|
||||||
from .utils import verify_kefu_permission # 已提供的公共身份校验方法
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# 店铺商品管理所需权限组(拥有任意一个即可访问列表类接口)
|
|
||||||
SHOP_PRODUCT_PERMS = ['1199ab', '1199abc', '1199abd']
|
|
||||||
|
|
||||||
|
|
||||||
# ==================== 1. 公共商品类型 + 审核模式 ====================
|
# ==================== 1. 公共商品类型 + 审核模式 ====================
|
||||||
@@ -9574,11 +9191,7 @@ class KefuChatPermissionsView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
from rest_framework.permissions import AllowAny
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from dingdan.models import Dingdan, Fadan, CrossPlatformOrderData # 按实际路径
|
|
||||||
class FineApplyView(APIView):
|
class FineApplyView(APIView):
|
||||||
"""
|
"""
|
||||||
客服申请罚款接口(金额罚款,非积分)
|
客服申请罚款接口(金额罚款,非积分)
|
||||||
@@ -9799,7 +9412,7 @@ class PartnerFineNotifyView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
from dingdan.models import Fadan, FadanShensuTupian
|
|
||||||
# 权限码到身份的映射
|
# 权限码到身份的映射
|
||||||
PERMISSION_TO_SHENFEN = {
|
PERMISSION_TO_SHENFEN = {
|
||||||
'66693a': 1, # 打手
|
'66693a': 1, # 打手
|
||||||
@@ -10156,7 +9769,7 @@ class FaKuanChuangJianView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
from dengji.models import Bankuai
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -10366,19 +9979,7 @@ class BkxgView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
from rest_framework.permissions import IsAuthenticated
|
|
||||||
from rest_framework.views import APIView
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from django.db.models import Sum
|
|
||||||
from django.utils import timezone
|
|
||||||
from datetime import date, timedelta
|
|
||||||
from peizhi.models import (
|
|
||||||
DailyIncomeStat, DailyPayoutStat
|
|
||||||
)
|
|
||||||
from shangpin.models import (
|
|
||||||
Czjilu,Huiyuangoumai
|
|
||||||
)
|
|
||||||
from .utils import verify_kefu_permission
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -10558,8 +10159,7 @@ class CwhybkhqView(APIView):
|
|||||||
logger.exception("CwhybkhqView 接口错误")
|
logger.exception("CwhybkhqView 接口错误")
|
||||||
return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)
|
return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)
|
||||||
|
|
||||||
from django.db.models.functions import TruncDate, TruncMonth, TruncYear
|
|
||||||
from shangpin.models import Czjilu, Gsfenhong # 请根据实际路径调整
|
|
||||||
class HybkjtsjView(APIView):
|
class HybkjtsjView(APIView):
|
||||||
"""
|
"""
|
||||||
会员详细统计数据
|
会员详细统计数据
|
||||||
@@ -10599,7 +10199,6 @@ class HybkjtsjView(APIView):
|
|||||||
return Response({'code': 1, 'msg': '缺少会员ID'}, status=400)
|
return Response({'code': 1, 'msg': '缺少会员ID'}, status=400)
|
||||||
|
|
||||||
# 🔧 直接使用 date.today() 避免模块名冲突
|
# 🔧 直接使用 date.today() 避免模块名冲突
|
||||||
from datetime import date, datetime, timedelta
|
|
||||||
now = date.today()
|
now = date.today()
|
||||||
|
|
||||||
# ---------- 构造时间范围 ----------
|
# ---------- 构造时间范围 ----------
|
||||||
@@ -10789,8 +10388,6 @@ class SzxxView(APIView):
|
|||||||
def post(self, request):
|
def post(self, request):
|
||||||
try:
|
try:
|
||||||
# 避免模块名冲突,局部导入
|
# 避免模块名冲突,局部导入
|
||||||
from datetime import date, datetime
|
|
||||||
|
|
||||||
username_from_frontend = request.data.get('phone', None)
|
username_from_frontend = request.data.get('phone', None)
|
||||||
kefu_obj, permissions = verify_kefu_permission(request, username_from_frontend)
|
kefu_obj, permissions = verify_kefu_permission(request, username_from_frontend)
|
||||||
if kefu_obj is None:
|
if kefu_obj is None:
|
||||||
@@ -11148,16 +10745,6 @@ class CwhqjtddsjView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
from dingdan.models import Fadan, FadanFenhong # 罚单和罚款分红表,根据实际路径调整
|
|
||||||
import logging
|
|
||||||
from datetime import date, datetime
|
|
||||||
from django.db.models import Count, Sum, Q
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from django.db.models.functions import TruncDate, TruncMonth, TruncYear
|
|
||||||
|
|
||||||
|
|
||||||
class CwqtczhqView(APIView):
|
class CwqtczhqView(APIView):
|
||||||
"""
|
"""
|
||||||
其他充值/罚款数据统计(非会员充值)
|
其他充值/罚款数据统计(非会员充值)
|
||||||
@@ -11660,11 +11247,6 @@ class CwqtczhqView(APIView):
|
|||||||
# ==================== 后端接口 ====================
|
# ==================== 后端接口 ====================
|
||||||
# 文件:views.py (添加以下两个视图类)
|
# 文件:views.py (添加以下两个视图类)
|
||||||
|
|
||||||
from dengji.models import (
|
|
||||||
Chenghao, KaoheCishuFeiyong, Bankuai,
|
|
||||||
YonghuChenghao, ShenheJilu, # 可能不需要用到但导入无妨
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class KhpzhqView(APIView):
|
class KhpzhqView(APIView):
|
||||||
@@ -11895,13 +11477,7 @@ class ChzsgcView(APIView):
|
|||||||
# ==================== 后端 views.py ====================
|
# ==================== 后端 views.py ====================
|
||||||
# 请确保已有以下导入,如果没有请在文件头部添加
|
# 请确保已有以下导入,如果没有请在文件头部添加
|
||||||
|
|
||||||
from yonghu.models import (
|
|
||||||
UserMain, UserShenheguan
|
|
||||||
)
|
|
||||||
|
|
||||||
from dengji.models import (
|
|
||||||
KaoheguanBankuai, Bankuai
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class KhgglView(APIView):
|
class KhgglView(APIView):
|
||||||
@@ -12128,13 +11704,6 @@ class ShgxgsjView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import secrets
|
|
||||||
from dingdan.models import Dingdan, DingdanShangjia
|
|
||||||
from dingdan.tongzhi_tasks import dingdan_guangbo
|
|
||||||
|
|
||||||
from peizhi.models import ShangjiaLianjie
|
|
||||||
|
|
||||||
class ZxkfghdsView(APIView):
|
class ZxkfghdsView(APIView):
|
||||||
"""
|
"""
|
||||||
客服更换打手接口
|
客服更换打手接口
|
||||||
@@ -12294,7 +11863,6 @@ class ZxkfghdsView(APIView):
|
|||||||
return Response({'code': 500, 'msg': '通知对方平台失败'})
|
return Response({'code': 500, 'msg': '通知对方平台失败'})
|
||||||
|
|
||||||
# 更新跨平台扩展表(如果存在)
|
# 更新跨平台扩展表(如果存在)
|
||||||
from dingdan.models import CrossPlatformOrderData
|
|
||||||
CrossPlatformOrderData.objects.update_or_create(
|
CrossPlatformOrderData.objects.update_or_create(
|
||||||
dingdan_id=dingdan_id,
|
dingdan_id=dingdan_id,
|
||||||
defaults={
|
defaults={
|
||||||
@@ -12391,7 +11959,6 @@ class ZxkfghdsView(APIView):
|
|||||||
|
|
||||||
# 跨平台同步(与客户填写接口保持一致)
|
# 跨平台同步(与客户填写接口保持一致)
|
||||||
try:
|
try:
|
||||||
from dingdan.utils import sync_order_to_partners
|
|
||||||
sync_order_to_partners(new_dingdan_id)
|
sync_order_to_partners(new_dingdan_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"跨平台同步失败: {e}")
|
logger.error(f"跨平台同步失败: {e}")
|
||||||
@@ -12416,7 +11983,6 @@ class ZxkfghdsView(APIView):
|
|||||||
def _get_game_type_name(self, leixing_id):
|
def _get_game_type_name(self, leixing_id):
|
||||||
"""根据类型ID获取游戏类型名称"""
|
"""根据类型ID获取游戏类型名称"""
|
||||||
try:
|
try:
|
||||||
from shangpin.models import ShangpinLeixing
|
|
||||||
gt = ShangpinLeixing.objects.filter(id=leixing_id).first()
|
gt = ShangpinLeixing.objects.filter(id=leixing_id).first()
|
||||||
return gt.jieshao if gt else '游戏订单'
|
return gt.jieshao if gt else '游戏订单'
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -12427,18 +11993,7 @@ class ZxkfghdsView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db.models import ObjectDoesNotExist
|
|
||||||
from rest_framework import status
|
|
||||||
from rest_framework.parsers import JSONParser
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.views import APIView
|
|
||||||
|
|
||||||
from dingdan.models import Dingdan
|
|
||||||
|
|
||||||
logger = logging.getLogger('houtai')
|
|
||||||
|
|
||||||
|
|
||||||
class KptxwztjbView(APIView):
|
class KptxwztjbView(APIView):
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Lunbo(models.Model):
|
class Lunbo(models.Model):
|
||||||
tupian_url = models.CharField(max_length=500, null=True, blank=True, verbose_name='轮播图片URL')
|
tupian_url = models.CharField(max_length=500, null=True, blank=True, verbose_name='轮播图片URL')
|
||||||
leixing = models.IntegerField(null=True, blank=True, verbose_name='图片类型:1:轮播2:个人中心背景')
|
leixing = models.IntegerField(null=True, blank=True, verbose_name='图片类型:1:轮播2:个人中心背景')
|
||||||
@@ -66,9 +68,6 @@ class Szjilu(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
# models/shangjia_moban.py
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class ShangjiaMoban(models.Model):
|
class ShangjiaMoban(models.Model):
|
||||||
"""商家订单模板表 - 存储商家创建的订单模板"""
|
"""商家订单模板表 - 存储商家创建的订单模板"""
|
||||||
@@ -119,8 +118,6 @@ class ShangjiaMoban(models.Model):
|
|||||||
return f"模板{self.id}-{self.yonghu_id}"
|
return f"模板{self.id}-{self.yonghu_id}"
|
||||||
|
|
||||||
|
|
||||||
# models/shangjia_lianjie.py
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class ShangjiaLianjie(models.Model):
|
class ShangjiaLianjie(models.Model):
|
||||||
@@ -274,10 +271,6 @@ class WithdrawConfig(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# apps/club/models.py
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
class Club(models.Model):
|
class Club(models.Model):
|
||||||
"""合作俱乐部/平台信息表"""
|
"""合作俱乐部/平台信息表"""
|
||||||
# 自己的俱乐部ID(6位字符)
|
# 自己的俱乐部ID(6位字符)
|
||||||
@@ -384,8 +377,6 @@ class ClubConfig(models.Model):
|
|||||||
return f"{self.club_nickname or self.club_id} ({'我方' if self.is_self else '对方'})"
|
return f"{self.club_nickname or self.club_id} ({'我方' if self.is_self else '对方'})"
|
||||||
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
class AccountPermission(models.Model):
|
class AccountPermission(models.Model):
|
||||||
"""账号权限表:记录每个账号拥有的权限及开关状态"""
|
"""账号权限表:记录每个账号拥有的权限及开关状态"""
|
||||||
account_id = models.CharField(max_length=11, db_index=True, verbose_name='账号ID')
|
account_id = models.CharField(max_length=11, db_index=True, verbose_name='账号ID')
|
||||||
@@ -521,10 +512,6 @@ class DailyPayoutStat(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
class PopupPage(models.Model):
|
class PopupPage(models.Model):
|
||||||
page_key = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='页面标识')
|
page_key = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='页面标识')
|
||||||
name = models.CharField(max_length=100, verbose_name='页面名称')
|
name = models.CharField(max_length=100, verbose_name='页面名称')
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
处理收支记录表的每日清零
|
处理收支记录表的每日清零
|
||||||
固定使用ID=1的记录
|
固定使用ID=1的记录
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
@@ -12,7 +11,7 @@ from decimal import Decimal
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
# 导入模型
|
# 导入模型
|
||||||
from peizhi.models import Szjilu
|
from config.models import Szjilu
|
||||||
from utils.celery_utils import log_task_execution, rollback_on_failure
|
from utils.celery_utils import log_task_execution, rollback_on_failure
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,614 +0,0 @@
|
|||||||
"""
|
|
||||||
阿龙电竞 - 信号处理器(修复Redis导入问题)
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 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}")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 🔥 关键修复:在最开始强制正确导入redis模块
|
|
||||||
'''import sys
|
|
||||||
|
|
||||||
# 1. 如果redis模块已加载但有问题,先移除
|
|
||||||
if 'redis' in sys.modules:
|
|
||||||
print(f"🔧 移除已加载的redis模块: {sys.modules['redis']}")
|
|
||||||
del sys.modules['redis']
|
|
||||||
# 同时移除相关模块
|
|
||||||
for key in list(sys.modules.keys()):
|
|
||||||
if key.startswith('redis.') or key == 'redis':
|
|
||||||
del sys.modules[key]
|
|
||||||
|
|
||||||
# 2. 重新导入redis模块
|
|
||||||
try:
|
|
||||||
import importlib
|
|
||||||
redis = importlib.import_module('redis')
|
|
||||||
print(f"✅ 重新导入redis成功: {redis}")
|
|
||||||
print(f" Redis类: {redis.Redis}")
|
|
||||||
print(f" Redis类是否为None: {redis.Redis is None}")
|
|
||||||
|
|
||||||
# 验证Redis类是否可用
|
|
||||||
if redis.Redis is None:
|
|
||||||
print("❌ Redis类为None,尝试从源码级别修复")
|
|
||||||
# 强制重新加载
|
|
||||||
import importlib.util
|
|
||||||
import os
|
|
||||||
|
|
||||||
# 找到redis包路径
|
|
||||||
import pkgutil
|
|
||||||
redis_spec = pkgutil.find_loader('redis')
|
|
||||||
if redis_spec:
|
|
||||||
print(f" Redis包路径: {redis_spec.origin}")
|
|
||||||
# 强制重新加载
|
|
||||||
importlib.invalidate_caches()
|
|
||||||
redis = importlib.import_module('redis')
|
|
||||||
importlib.reload(redis)
|
|
||||||
print(f" Redis类(重载后): {redis.Redis}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ 导入redis失败: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
# 尝试直接安装
|
|
||||||
print("尝试直接安装redis包...")
|
|
||||||
import subprocess
|
|
||||||
subprocess.call([sys.executable, "-m", "pip", "install", "redis==4.5.4", "-q"])
|
|
||||||
|
|
||||||
# 3. 确保kombu.transport.redis也能正确导入
|
|
||||||
try:
|
|
||||||
import kombu.transport.redis
|
|
||||||
print(f"✅ kombu.transport.redis导入成功")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ kombu.transport.redis导入失败: {e}")
|
|
||||||
# 修复:先导入redis,再导入kombu.transport.redis
|
|
||||||
if 'redis' not in sys.modules:
|
|
||||||
import redis
|
|
||||||
# 重新导入
|
|
||||||
import importlib
|
|
||||||
if 'kombu.transport.redis' in sys.modules:
|
|
||||||
del sys.modules['kombu.transport.redis']
|
|
||||||
kombu_transport_redis = importlib.import_module('kombu.transport.redis')
|
|
||||||
print(f"✅ 重新导入kombu.transport.redis成功")
|
|
||||||
|
|
||||||
# 4. 继续原来的信号处理器代码
|
|
||||||
import logging
|
|
||||||
from django.db.models.signals import pre_save
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
@receiver(pre_save, sender='dingdan.Dingdan')
|
|
||||||
def order_status_8_handler_fixed(sender, instance, **kwargs):
|
|
||||||
"""
|
|
||||||
修复Redis导入问题后的信号处理器
|
|
||||||
"""
|
|
||||||
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 = 60 # 测试用60秒
|
|
||||||
|
|
||||||
# 🔥 再次验证redis模块
|
|
||||||
try:
|
|
||||||
import redis
|
|
||||||
logger.info(f"🔍 信号处理器内redis模块状态: {redis.Redis}")
|
|
||||||
if redis.Redis is None:
|
|
||||||
logger.error("❌ 信号处理器内redis.Redis为None!")
|
|
||||||
# 使用备用方案
|
|
||||||
instance.auto_task_id = f"redis_none_{timezone.now().timestamp()}"
|
|
||||||
return
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ 信号处理器内验证redis失败: {e}")
|
|
||||||
instance.auto_task_id = f"redis_check_failed_{timezone.now().timestamp()}"
|
|
||||||
return
|
|
||||||
|
|
||||||
# 提交Celery任务
|
|
||||||
try:
|
|
||||||
from dingdan.tasks import process_expired_order
|
|
||||||
|
|
||||||
logger.info(f"🔧 开始提交任务,订单: {instance.dingdan_id}")
|
|
||||||
|
|
||||||
# 使用更安全的方式提交任务
|
|
||||||
from celery import current_app
|
|
||||||
|
|
||||||
# 验证current_app的backend
|
|
||||||
if hasattr(current_app, 'backend'):
|
|
||||||
logger.info(f"✅ current_app.backend存在: {current_app.backend}")
|
|
||||||
else:
|
|
||||||
logger.error("❌ current_app没有backend属性")
|
|
||||||
|
|
||||||
task_result = process_expired_order.apply_async(
|
|
||||||
args=[instance.dingdan_id],
|
|
||||||
countdown=expire_seconds,
|
|
||||||
queue='order_tasks',
|
|
||||||
priority=9
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"✅ Celery任务提交成功,订单: {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"❌ Celery任务提交失败: {type(e).__name__}: {e}")
|
|
||||||
|
|
||||||
# 特别检查Redis相关错误
|
|
||||||
if "'NoneType' object has no attribute 'Redis'" in str(e):
|
|
||||||
logger.error("💥 检测到Redis类为None错误")
|
|
||||||
logger.error("💥 尝试从当前环境重新导入redis模块")
|
|
||||||
|
|
||||||
# 尝试修复
|
|
||||||
try:
|
|
||||||
import importlib
|
|
||||||
import redis as redis_module
|
|
||||||
importlib.reload(redis_module)
|
|
||||||
logger.info(f"💥 重新加载redis模块后: {redis_module.Redis}")
|
|
||||||
except Exception as reload_e:
|
|
||||||
logger.error(f"💥 重新加载失败: {reload_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"订单状态变更处理失败,订单ID: {getattr(instance, 'dingdan_id', '未知')}, 错误: {e}")'''
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
'''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
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
# 在你的 dingdan/tasks.py 文件末尾添加以下代码
|
|
||||||
|
|
||||||
from django.db.models.signals import pre_save
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.utils import timezone
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from dingdan.models import Dingdan
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
@receiver(pre_save, sender=Dingdan)
|
|
||||||
def handle_order_status_change(sender, instance, **kwargs):
|
|
||||||
"""
|
|
||||||
订单状态变化监听器(纯净标记版)
|
|
||||||
严格遵循原始逻辑,仅更新数据库标记。
|
|
||||||
"""
|
|
||||||
# 1. 跳过新建的订单
|
|
||||||
if instance.pk is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
# 2. 获取数据库中此订单的旧状态
|
|
||||||
try:
|
|
||||||
old_status = Dingdan.objects.filter(pk=instance.pk).values_list('zhuangtai', flat=True).first()
|
|
||||||
except Dingdan.DoesNotExist:
|
|
||||||
return # 旧订单不存在,不处理
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"获取订单 {instance.pk} 旧状态失败: {e},跳过信号处理")
|
|
||||||
return # 出错时静默退出,绝不阻塞订单保存
|
|
||||||
|
|
||||||
old_status = old_status or 0 # 防None
|
|
||||||
|
|
||||||
# 3. 【核心逻辑】状态从 非8 变为 8
|
|
||||||
if old_status != 8 and instance.zhuangtai == 8:
|
|
||||||
instance.pending_dispatch = True # 标记为待处理
|
|
||||||
instance.status_8_time = timezone.now() # 记录变8时间
|
|
||||||
# 清空可能存在的旧任务信息(安全起见)
|
|
||||||
instance.auto_task_id = ''
|
|
||||||
instance.auto_expire_at = None
|
|
||||||
logger.info(f"📝 订单 {instance.dingdan_id} 状态变8,已标记为待调度。")
|
|
||||||
|
|
||||||
# 4. 【核心逻辑】状态从 8 变为 非8
|
|
||||||
elif old_status == 8 and instance.zhuangtai != 8:
|
|
||||||
# 清除所有调度标记和任务信息
|
|
||||||
instance.pending_dispatch = False
|
|
||||||
instance.status_8_time = None
|
|
||||||
instance.auto_task_id = ''
|
|
||||||
instance.auto_expire_at = None
|
|
||||||
logger.info(f"📝 订单 {instance.dingdan_id} 状态离开8,已清除所有调度标记。")'''
|
|
||||||
|
|
||||||
|
|
||||||
from django.db.models.signals import pre_save
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.utils import timezone
|
|
||||||
from datetime import timedelta
|
|
||||||
from celery.result import AsyncResult
|
|
||||||
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):
|
|
||||||
"""
|
|
||||||
监听订单状态变化(稳定生产版)- 修复配置问题
|
|
||||||
"""
|
|
||||||
# 1. 跳过新建的订单(无旧状态可比)
|
|
||||||
if instance.pk is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
old = sender.objects.get(pk=instance.pk)
|
|
||||||
except sender.DoesNotExist:
|
|
||||||
return
|
|
||||||
# dingdan/signals.py - 修改状态变8的逻辑部分
|
|
||||||
|
|
||||||
|
|
||||||
# 2. 状态变成 8:提交任务
|
|
||||||
if old.zhuangtai != 8 and instance.zhuangtai == 8:
|
|
||||||
from django.conf import settings
|
|
||||||
from celery import current_app
|
|
||||||
|
|
||||||
# 🔥【核心修复】确保信号上下文中的Celery应用有正确配置
|
|
||||||
# 直接从你的Django settings中获取Redis连接字符串,并设置给current_app
|
|
||||||
if hasattr(settings, 'CELERY_BROKER_URL'):
|
|
||||||
current_app.conf.update(broker_url=settings.CELERY_BROKER_URL)
|
|
||||||
else:
|
|
||||||
# 如果settings里没有,就按你的配置拼接(和celery.py里一致)
|
|
||||||
redis_password = getattr(settings, 'REDIS_PASSWORD', 'Dujieduze5.3')
|
|
||||||
redis_host = getattr(settings, 'REDIS_HOST', '172.19.0.3')
|
|
||||||
redis_port = getattr(settings, 'REDIS_PORT', 6379)
|
|
||||||
redis_db_celery = getattr(settings, 'REDIS_DB_CELERY', 0)
|
|
||||||
broker_url = f'redis://:{redis_password}@{redis_host}:{redis_port}/{redis_db_celery}'
|
|
||||||
current_app.conf.update(broker_url=broker_url)
|
|
||||||
|
|
||||||
countdown_seconds = getattr(settings, 'ORDER_EXPIRE_SECONDS', 172800) # 默认48小时
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 现在再提交任务
|
|
||||||
job = process_expired_order.apply_async(
|
|
||||||
args=[instance.dingdan_id],
|
|
||||||
countdown=countdown_seconds,
|
|
||||||
queue='order_tasks',
|
|
||||||
priority=9
|
|
||||||
)
|
|
||||||
# ✅ 核心:将任务ID和计划时间记录到当前实例
|
|
||||||
instance.auto_task_id = job.id
|
|
||||||
instance.auto_expire_at = timezone.now() + timedelta(seconds=countdown_seconds)
|
|
||||||
logger.info(f"✅ 订单 {instance.dingdan_id} 状态变8,已提交延时任务,ID: {job.id}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ 提交订单 {instance.dingdan_id} 的延时任务失败: {str(e)}")
|
|
||||||
return # 处理完毕,直接返回
|
|
||||||
|
|
||||||
# 3. 状态从 8 被改成别的 -> 尝试撤销任务 (这部分代码不变)
|
|
||||||
if old.zhuangtai == 8 and instance.zhuangtai != 8:
|
|
||||||
if old.auto_task_id:
|
|
||||||
try:
|
|
||||||
AsyncResult(old.auto_task_id).revoke()
|
|
||||||
logger.info(f"订单 {instance.dingdan_id} 状态离开8,已撤销原任务: {old.auto_task_id}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"撤销订单 {instance.dingdan_id} 的任务失败: {str(e)}")
|
|
||||||
instance.auto_task_id = ''
|
|
||||||
instance.auto_expire_at = None'''
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
'''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):
|
|
||||||
print(f"🚨【信号触发】订单ID: {getattr(instance, 'dingdan_id', 'N/A')}, 新状态: {instance.zhuangtai}")
|
|
||||||
"""
|
|
||||||
轻量级订单状态变化监听器
|
|
||||||
设计原则:
|
|
||||||
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)}")
|
|
||||||
# 不抛出异常,避免影响订单保存的主流程
|
|
||||||
# 信号处理失败不影响订单保存,有周期性检查作为备用方案'''
|
|
||||||
@@ -1,28 +1,28 @@
|
|||||||
# /opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.443/django/gunicorn.conf.py
|
# /opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.443/django/gunicorn.conf.py
|
||||||
|
|
||||||
# 🔥 绑定的IP和端口(根据你的Nginx配置,后端在8001端口)
|
# 绑定的IP和端口(根据你的Nginx配置,后端在8001端口)
|
||||||
bind = "127.0.0.1:8001"
|
bind = "127.0.0.1:8001"
|
||||||
|
|
||||||
# 🔥 工作进程数(根据CPU核心数调整,建议:CPU核心数*2+1)
|
# 工作进程数(根据CPU核心数调整,建议:CPU核心数*2+1)
|
||||||
# 查看CPU核心数:cat /proc/cpuinfo | grep "processor" | wc -l
|
# 查看CPU核心数:cat /proc/cpuinfo | grep "processor" | wc -l
|
||||||
workers = 4
|
workers = 4
|
||||||
|
|
||||||
# 🔥 工作模式(使用gevent支持异步,更高效)
|
# 工作模式(使用gevent支持异步,更高效)
|
||||||
#worker_class = "gevent"
|
#worker_class = "gevent"
|
||||||
worker_class = "sync" # 改成这个
|
worker_class = "sync" # 改成这个
|
||||||
|
|
||||||
# 🔥 每个worker的最大请求数(防止内存泄漏)
|
# 每个worker的最大请求数(防止内存泄漏)
|
||||||
max_requests = 1000
|
max_requests = 1000
|
||||||
max_requests_jitter = 50 # 随机抖动,避免所有worker同时重启
|
max_requests_jitter = 50 # 随机抖动,避免所有worker同时重启
|
||||||
|
|
||||||
# 🔥 超时时间(秒)
|
# 超时时间(秒)
|
||||||
timeout = 30
|
timeout = 30
|
||||||
keepalive = 2
|
keepalive = 2
|
||||||
|
|
||||||
# 🔥 预加载应用(减少内存占用,加快请求处理)
|
# 预加载应用(减少内存占用,加快请求处理)
|
||||||
preload_app = True
|
preload_app = True
|
||||||
|
|
||||||
# 🔥 日志配置
|
# 日志配置
|
||||||
# 访问日志:输出到标准输出,便于OnePanel查看
|
# 访问日志:输出到标准输出,便于OnePanel查看
|
||||||
accesslog = "-"
|
accesslog = "-"
|
||||||
# 错误日志:输出到标准错误
|
# 错误日志:输出到标准错误
|
||||||
@@ -30,23 +30,23 @@ errorlog = "-"
|
|||||||
# 日志级别
|
# 日志级别
|
||||||
loglevel = "info"
|
loglevel = "info"
|
||||||
|
|
||||||
# 🔥 进程名称(便于识别)
|
# 进程名称(便于识别)
|
||||||
proc_name = "a_long_dianjing_gunicorn"
|
proc_name = "a_long_dianjing_gunicorn"
|
||||||
|
|
||||||
# 🔥 工作进程的用户/组(安全考虑,用非root用户运行)
|
# 工作进程的用户/组(安全考虑,用非root用户运行)
|
||||||
# user = "www-data"
|
# user = "www-data"
|
||||||
# group = "www-data"
|
# group = "www-data"
|
||||||
|
|
||||||
# 🔥 工作进程临时目录(避免权限问题)
|
# 工作进程临时目录(避免权限问题)
|
||||||
worker_tmp_dir = "/dev/shm"
|
worker_tmp_dir = "/dev/shm"
|
||||||
|
|
||||||
# 🔥 限制请求数据大小(防止大文件攻击)
|
# 限制请求数据大小(防止大文件攻击)
|
||||||
limit_request_line = 4094
|
limit_request_line = 4094
|
||||||
limit_request_fields = 100
|
limit_request_fields = 100
|
||||||
limit_request_field_size = 8190
|
limit_request_field_size = 8190
|
||||||
|
|
||||||
# 🔥 优雅退出时间
|
# 优雅退出时间
|
||||||
graceful_timeout = 30
|
graceful_timeout = 30
|
||||||
|
|
||||||
# 🔥 进程守护(如果用systemd管理,这里设为False)
|
# 进程守护(如果用systemd管理,这里设为False)
|
||||||
daemon = False
|
daemon = False
|
||||||
@@ -7,4 +7,4 @@ class DingdanConfig(AppConfig):
|
|||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
# 导入信号处理,确保信号被注册
|
# 导入信号处理,确保信号被注册
|
||||||
import dingdan.signals
|
import orders.signals
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
class Dingdan(models.Model):
|
class Dingdan(models.Model):
|
||||||
"""订单主表"""
|
"""订单主表"""
|
||||||
# 核心编号:唯一且不可为空[citation:3][citation:9]
|
# 核心编号:唯一且不可为空[citation:3][citation:9]
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
# dingdan/tongzhi_tasks.py
|
# dingdan/notice_tasks.py
|
||||||
import logging
|
import logging
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
from a_long_dianjing.celery_app import app
|
from a_long_dianjing.celery_app import app
|
||||||
from yonghu.models import OfficialAccountUser
|
from users.models import OfficialAccountUser
|
||||||
from utils.weixin_broadcast import WeixinBroadcastSender
|
from utils.weixin_broadcast import WeixinBroadcastSender
|
||||||
|
|
||||||
logger = logging.getLogger('weixin_broadcast')
|
logger = logging.getLogger('weixin_broadcast')
|
||||||
102
orders/signals.py
Normal file
102
orders/signals.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
"""
|
||||||
|
阿龙电竞 - 订单信号处理器(生产稳定版)
|
||||||
|
经过验证,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 orders.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 orders.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}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -14,8 +14,8 @@ from decimal import Decimal
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
# 导入模型 - 确保路径正确
|
# 导入模型 - 确保路径正确
|
||||||
from dingdan.models import Dingdan, DingdanShangjia
|
from orders.models import Dingdan
|
||||||
from yonghu.models import UserMain, UserDashou, UserShangjia
|
from users.models import UserMain, UserDashou, UserShangjia
|
||||||
from utils.celery_utils import safe_decimal_operation, log_task_execution, rollback_on_failure
|
from utils.celery_utils import safe_decimal_operation, log_task_execution, rollback_on_failure
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -25,9 +25,6 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@shared_task(bind=True, max_retries=3, default_retry_delay=60)
|
@shared_task(bind=True, max_retries=3, default_retry_delay=60)
|
||||||
def process_expired_order(self, dingdan_id):
|
def process_expired_order(self, dingdan_id):
|
||||||
"""
|
"""
|
||||||
@@ -44,13 +41,6 @@ def process_expired_order(self, dingdan_id):
|
|||||||
dingdan_id=dingdan_id
|
dingdan_id=dingdan_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 其余代码不变...
|
|
||||||
# ... 保持原有代码
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 再次检查订单状态是否为8
|
# 再次检查订单状态是否为8
|
||||||
if order.zhuangtai != 8:
|
if order.zhuangtai != 8:
|
||||||
logger.info(f"订单{dingdan_id}状态已不是8(当前:{order.zhuangtai}),跳过处理")
|
logger.info(f"订单{dingdan_id}状态已不是8(当前:{order.zhuangtai}),跳过处理")
|
||||||
@@ -124,7 +114,7 @@ def process_expired_order(self, dingdan_id):
|
|||||||
|
|
||||||
if fadan_pingtai == 1:
|
if fadan_pingtai == 1:
|
||||||
try:
|
try:
|
||||||
from dingdan.utils import update_platform_profit # 导入函数
|
from orders.utils import update_platform_profit # 导入函数
|
||||||
update_platform_profit(dingdan_id)
|
update_platform_profit(dingdan_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# 平台收益更新失败不影响主流程,但记录日志
|
# 平台收益更新失败不影响主流程,但记录日志
|
||||||
@@ -65,8 +65,6 @@ urlpatterns = [
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
path('partner_sync_order', csrf_exempt(PartnerSyncOrderView.as_view()), name='东道主派单接口'),
|
path('partner_sync_order', csrf_exempt(PartnerSyncOrderView.as_view()), name='东道主派单接口'),
|
||||||
path('partner_check_claim', csrf_exempt(PartnerCheckClaimView.as_view()), name='对方抢单询问接口'),
|
path('partner_check_claim', csrf_exempt(PartnerCheckClaimView.as_view()), name='对方抢单询问接口'),
|
||||||
|
|
||||||
@@ -86,6 +84,4 @@ urlpatterns = [
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
]
|
]
|
||||||
@@ -1,21 +1,25 @@
|
|||||||
|
|
||||||
import threading
|
import threading
|
||||||
import requests
|
import requests
|
||||||
|
import time
|
||||||
from django.conf import settings
|
|
||||||
from peizhi.models import Club
|
|
||||||
from shangpin.models import ShangpinLeixing
|
|
||||||
from dingdan.models import Dingdan # 根据实际路径导入
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from peizhi.models import DailyDispatchStat # 假设模型在 peizhi 应用中
|
|
||||||
|
from config.models import Club, DailyDispatchStat, DailyIncomeStat, DailyPayoutStat
|
||||||
|
from orders.models import Dingdan, DingdanPingtai, DingdanShangjia, Lilubiao
|
||||||
|
from products.models import ShangpinLeixing, Gsfenhong
|
||||||
|
from users.models import UserDashou, UserGuanshi
|
||||||
|
|
||||||
|
from backend.utils import update_guanshi_daily_by_action
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 可选:线程池限制(避免大量线程同时创建,建议使用 Celery,这里简单用线程)
|
# 可选:线程池限制(避免大量线程同时创建,建议使用 Celery,这里简单用线程)
|
||||||
THREAD_POOL_SIZE = 10
|
THREAD_POOL_SIZE = 10
|
||||||
_semaphore = threading.Semaphore(THREAD_POOL_SIZE)
|
_semaphore = threading.Semaphore(THREAD_POOL_SIZE)
|
||||||
@@ -149,7 +153,6 @@ def _send_to_single_partner(dingdan, club):
|
|||||||
logger.error(f"同步订单异常(尝试 {attempt}/3): {e}")
|
logger.error(f"同步订单异常(尝试 {attempt}/3): {e}")
|
||||||
|
|
||||||
if attempt < 3:
|
if attempt < 3:
|
||||||
import time
|
|
||||||
time.sleep(2 ** attempt) # 指数退避
|
time.sleep(2 ** attempt) # 指数退避
|
||||||
else:
|
else:
|
||||||
logger.error(f"同步订单 {dingdan.dingdan_id} 到俱乐部 {club.club_id} 最终失败")
|
logger.error(f"同步订单 {dingdan.dingdan_id} 到俱乐部 {club.club_id} 最终失败")
|
||||||
@@ -158,7 +161,6 @@ def _send_to_single_partner(dingdan, club):
|
|||||||
|
|
||||||
# 获取下单方ID(根据订单类型)
|
# 获取下单方ID(根据订单类型)
|
||||||
def get_order_user_id(order):
|
def get_order_user_id(order):
|
||||||
from dingdan.models import DingdanPingtai, DingdanShangjia
|
|
||||||
if order.fadan_pingtai == 1:
|
if order.fadan_pingtai == 1:
|
||||||
try:
|
try:
|
||||||
ext = DingdanPingtai.objects.get(dingdan=order)
|
ext = DingdanPingtai.objects.get(dingdan=order)
|
||||||
@@ -176,18 +178,6 @@ def get_order_user_id(order):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
# dingdan/utils/dispatch_stat.py
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from datetime import date
|
|
||||||
from decimal import Decimal
|
|
||||||
from django.db import transaction
|
|
||||||
from django.db.models import F
|
|
||||||
from peizhi.models import DailyDispatchStat # 请根据实际模型位置修改导入路径
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def update_daily_dispatch_stat(direction, partner_club_id,
|
def update_daily_dispatch_stat(direction, partner_club_id,
|
||||||
dispatch_amount=None, dispatch_count=1,
|
dispatch_amount=None, dispatch_count=1,
|
||||||
@@ -255,9 +245,6 @@ def update_daily_dispatch_stat(direction, partner_club_id,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
from peizhi.models import DailyIncomeStat
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def update_daily_income(amount):
|
def update_daily_income(amount):
|
||||||
@@ -295,9 +282,6 @@ def update_daily_income(amount):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
from peizhi.models import DailyPayoutStat
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
def update_daily_payout(amount):
|
def update_daily_payout(amount):
|
||||||
"""
|
"""
|
||||||
@@ -335,10 +319,6 @@ def update_daily_payout(amount):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
from dingdan.models import Lilubiao
|
|
||||||
|
|
||||||
|
|
||||||
def calc_shangjia_order_fencheng(jine):
|
def calc_shangjia_order_fencheng(jine):
|
||||||
"""
|
"""
|
||||||
@@ -374,8 +354,6 @@ def settle_shangjia_order_guanshi_fenhong(order, dashou_id):
|
|||||||
"""
|
"""
|
||||||
商家订单结单时结算管事分红(幂等,失败不抛异常阻断主流程)。
|
商家订单结单时结算管事分红(幂等,失败不抛异常阻断主流程)。
|
||||||
"""
|
"""
|
||||||
from shangpin.models import Gsfenhong
|
|
||||||
from yonghu.models import UserDashou, UserGuanshi
|
|
||||||
|
|
||||||
if getattr(order, 'fadan_pingtai', None) != 2:
|
if getattr(order, 'fadan_pingtai', None) != 2:
|
||||||
return
|
return
|
||||||
@@ -433,7 +411,6 @@ def settle_shangjia_order_guanshi_fenhong(order, dashou_id):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from houtai.utils import update_guanshi_daily_by_action
|
|
||||||
update_guanshi_daily_by_action(
|
update_guanshi_daily_by_action(
|
||||||
yonghuid=guanshi_id,
|
yonghuid=guanshi_id,
|
||||||
action=3,
|
action=3,
|
||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user