最新版生产环境后端

This commit is contained in:
XingQue
2026-06-12 03:43:54 +08:00
commit 25d6ec70f3
197 changed files with 74825 additions and 0 deletions

11
.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
__pycache__/
*.pyc
db.sqlite3
/venv/
/env/
.env
staticfiles/
media/
*.log
.DS_Store
migrations/00*.py

2
_init_.py Normal file
View File

@@ -0,0 +1,2 @@
# 3. 在项目的 `__init__.py` 文件与manage.py同级添加两行代码
# 文件路径a_long_dianjing/__init__.py

View File

@@ -0,0 +1,8 @@
"""
阿龙电竞 - Django项目初始化
确保Celery在Django启动时自动加载
"""
from .celery import app as celery_app
__all__ = ('celery_app',)

16
a_long_dianjing/asgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
ASGI config for a_long_dianjing project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/6.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'a_long_dianjing.settings')
application = get_asgi_application()

200
a_long_dianjing/celery.py Normal file
View File

@@ -0,0 +1,200 @@
"""
阿龙电竞 - Celery定时任务主配置
生产环境就绪版本,支持精确延时任务和周期性任务
"""
import os
from celery import Celery
from celery.schedules import crontab
# 在 Django Shell 中执行
# 设置Django默认设置模块
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'a_long_dianjing.settings')
# 创建Celery应用实例
app = Celery('a_long_dianjing')
# 从Django settings中加载Celery配置CELERY_前缀
app.config_from_object('django.conf:settings', namespace='CELERY')
# 自动发现所有已注册app中的tasks.py文件
#app.autodiscover_tasks()
app.autodiscover_tasks(['yonghu.tasks', 'yonghu.ranking_tasks', 'dingdan.tasks', 'peizhi.tasks'])
# 配置周期性任务Celery Beat Schedule
app.conf.beat_schedule = {
# 月榜数据转移每月最后一天23:50执行
'yuebang_zhuanyi': {
'task': 'yonghu.ranking_tasks.zhuanyi_yuebang',
'schedule': crontab(hour=23, minute=50, day_of_month='28-31'), # ✅ 修改这里
'options': {'queue': 'periodic_tasks', 'priority': 7},
},
# 日榜数据转移每天23:55执行在清零前
'ribang_zhuanyi': {
'task': 'yonghu.ranking_tasks.zhuanyi_ribang',
#'schedule': crontab(minute='*/1'), # 每分钟执行一次,测试用
'schedule': crontab(hour=23, minute=55), # 每天23:55
'options': {'queue': 'periodic_tasks', 'priority': 7}, # 优先级高于清零任务
},
# 清理旧历史数据每月1号凌晨1点执行
'qingli_jiulishuju': {
'task': 'yonghu.ranking_tasks.qingli_jiulishuju',
'schedule': crontab(hour=1, minute=0, day_of_month=1), # 每月1号凌晨1点
'options': {'queue': 'periodic_tasks', 'priority': 4},
},
# 🔥 补偿检查任务每5分钟运行
'check_order_expire_task': {
'task': 'dingdan.tasks.check_order_expire_task',
'schedule': crontab(minute='*/5000'),
'options': {'queue': 'order_tasks', 'priority': 3},
},
# 1. 每日凌晨0点执行 - 清零任务
'daily_reset_task': {
'task': 'yonghu.tasks.daily_reset_task',
'schedule': crontab(hour=0, minute=0), # 每天0点
'options': {
'queue': 'periodic_tasks',
'priority': 5
},
'args': (),
'kwargs': {}
},
# 2. 每月1日凌晨0点执行 - 月度清零任务
'monthly_reset_task': {
'task': 'yonghu.tasks.monthly_reset_task',
'schedule': crontab(hour=0, minute=0, day_of_month=1), # 每月1日0点
'options': {
'queue': 'periodic_tasks',
'priority': 5
},
'args': (),
'kwargs': {}
},
# 4. 每天凌晨0点5分清理收支记录
'daily_sz_reset_task': {
'task': 'peizhi.tasks.daily_sz_reset_task',
'schedule': crontab(hour=0, minute=5), # 每天0点5分
'options': {
'queue': 'periodic_tasks',
'priority': 4
},
'args': (),
'kwargs': {}
}
}
# 配置周期性任务Celery Beat Schedule
'''app.conf.beat_schedule = {
# 1. 每日凌晨0点执行 - 清零任务
'daily_reset_task': {
'task': 'yonghu.tasks.daily_reset_task',
'schedule': crontab(hour=0, minute=0), # 每天0点
'options': {
'queue': 'periodic_tasks',
'priority': 5
},
'args': (),
'kwargs': {}
},
# 2. 每月1日凌晨0点执行 - 月度清零任务
'monthly_reset_task': {
'task': 'yonghu.tasks.monthly_reset_task',
'schedule': crontab(hour=0, minute=0, day_of_month=1), # 每月1日0点
'options': {
'queue': 'periodic_tasks',
'priority': 5
},
'args': (),
'kwargs': {}
},
# 3. 每5分钟检查一次待处理订单 - 作为补偿机制
'check_order_expire_task': {
'task': 'dingdan.tasks.check_order_expire_task',
'schedule': crontab(minute='*/5'), # 每5分钟
'options': {
'queue': 'order_tasks',
'priority': 3
},
'args': (),
'kwargs': {}
},
# 4. 每天凌晨0点5分清理收支记录
'daily_sz_reset_task': {
'task': 'peizhi.tasks.daily_sz_reset_task',
'schedule': crontab(hour=0, minute=5), # 每天0点5分
'options': {
'queue': 'periodic_tasks',
'priority': 4
},
'args': (),
'kwargs': {}
}
}'''
# 时区设置
app.conf.timezone = 'Asia/Shanghai'
app.conf.enable_utc = True
# 队列路由配置
app.conf.task_routes = {
'dingdan.tasks.*': {'queue': 'order_tasks'},
'yonghu.tasks.*': {'queue': 'periodic_tasks'},
'peizhi.tasks.*': {'queue': 'periodic_tasks'},
}
# 任务序列化
app.conf.accept_content = ['json']
app.conf.task_serializer = 'json'
app.conf.result_serializer = 'json'
# 任务超时设置
app.conf.task_time_limit = 300 # 任务最大执行时间300秒
app.conf.task_soft_time_limit = 240 # 软超时240秒
# Worker并发设置
app.conf.worker_concurrency = 4
app.conf.worker_prefetch_multiplier = 1
# 任务确认设置
app.conf.task_acks_late = True
app.conf.worker_disable_rate_limits = True
# 结果过期时间
app.conf.result_expires = 3600 # 任务结果保留1小时

View File

@@ -0,0 +1,10 @@
# celery_app.py
import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'a_long_dianjing.settings')
app = Celery('a_long_dianjing')
app.config_from_object('django.conf:settings', namespace='CELERY')
# 手动导入任务模块,避免与已有 tasks.py 冲突
app.autodiscover_tasks(['dingdan.tongzhi_tasks'])

541
a_long_dianjing/settings.py Normal file
View File

@@ -0,0 +1,541 @@
"""
Django settings for a_long_dianjing project.
开发环境配置 - 参照旧项目结构精简
"""
from pathlib import Path
from datetime import timedelta
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
SECRET_KEY = 'django-insecure-9+xu)m%#!#qblgby5sq!sjyg5nj9lir78+&*f$28$459#yktej' # 开发环境可暂用,生产必须改
DEBUG = False # 开发环境为 True
ALLOWED_HOSTS = ['localhost', '127.0.0.1','www.abas.asia'] # 根据开发前端地址可添加
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 第三方应用 (根据 requirements.txt 已安装的)
'rest_framework',
'rest_framework_simplejwt',
'corsheaders',
'django_celery_results',
# 你的自定义应用 (按已创建的拼音命名)
'celery',
'yonghu',
'dingdan',
'shangpin',
'peizhi',
'houtai',
# 'games', # 【待确认】你提到 games 还没创建,暂不添加
'utils',
'shangdian',
'dengji',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # 跨域中间件放最前
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', # 默认开启如API无需可注释
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# 'guanapi.middleware.OpLogMiddleware', # 【待确认】这是旧项目的自定义中间件,新项目需要吗?
]
ROOT_URLCONF = 'a_long_dianjing.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'a_long_dianjing.wsgi.application'
# Database
# 根据你提供的信息配置 (本地开发端口3307)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'xaio_cheng_xu', # 你创建的数据库名
'USER': 'root', # 你的本地MySQL用户名
#'PASSWORD': 'Dujieduze6363....', # 你的本地MySQL密码
#'PASSWORD': 'ahgs673g2h63hgawwdqw',
#'HOST': '172.19.0.2',
'HOST': '172.17.0.15',
'PASSWORD':'sajksh.sdfGH3YUge.wjkd+',
#'HOST': 'sh-cdb-hmyxpv1m.sql.tencentcdb.com',
'PORT': '3306', # 注意端口是3307
#'PORT': '20950',
'OPTIONS': {
'charset': 'utf8mb4',
'init_command': "SET time_zone='+08:00'", # 保持与旧项目一致
}
}
}
# Password validation
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
LANGUAGE_CODE = 'zh-Hans' # 使用中文
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_TZ = False # 保持与旧项目一致不使用UTC时区
# Static files (CSS, JavaScript, Images)
STATIC_URL = 'static/'
# 开发环境静态文件目录,可参照旧项目添加 MEDIA 配置
# MEDIA_URL = 'media/'
# MEDIA_ROOT = BASE_DIR / 'media'
# Default primary key field type
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# ==================== 以下为第三方库及自定义配置 ====================
# settings.py
# 修改SIMPLE_JWT配置
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=36500),
'REFRESH_TOKEN_LIFETIME': timedelta(days=36500),
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': False,
'UPDATE_LAST_LOGIN': False,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,
'JWK_URL': None,
'LEEWAY': 0,
'AUTH_HEADER_TYPES': ('Bearer',),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
# 修改这里使用yonghuid而不是id
'USER_ID_FIELD': 'yonghuid',
'USER_ID_CLAIM': 'yonghuid',
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',
'JTI_CLAIM': 'jti',
}
# 添加自定义认证后端
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
]
# 必须指定用户模型即使它不继承AbstractBaseUser
AUTH_USER_MODEL = 'yonghu.UserMain'
# DRF配置也非常简单
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle', # 匿名用户限制
'rest_framework.throttling.UserRateThrottle', # 登录用户限制
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/minute', # 匿名用户每分钟100次
'user': '1000/minute', # 登录用户每分钟1000次
},
# 🔥 关键:使用自定义异常处理器
'EXCEPTION_HANDLER': 'utils.exception_handler.custom_exception_handler',
# 🔥 生产环境只返回JSON不返回HTML更安全
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
],
}
# settings.py
# H5静态页面域名配置
H5_DOMAIN = 'https://www.abas.asia:666' # 生产环境
# H5_DOMAIN = 'https://test-h5.yourdomain.com' # 测试环境
# CORS settings
CORS_ALLOWED_ORIGINS = [
"http://localhost:8080", # 假设前端开发服务器地址
"http://127.0.0.1:8080",
"https://www.abas.asia",
#"http://192.168.240.1:5173/",
"https://www.abas.asia:666",
"https://www.abas.asia:1234",
"https://www.abas.asia:963",
"http://localhost:5174",
"http://localhost:5173",
"http://localhost:3000",
"http://localhost:3001",
#"www.abas.asia",
]
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_METHODS = (
"GET",
"POST",
"PUT",
"PATCH",
"DELETE",
"OPTIONS",
)
CORS_ALLOW_HEADERS = [
"accept",
"accept-encoding",
"authorization",
"content-type",
"dnt",
"origin",
"user-agent",
"x-csrftoken",
"x-requested-with",
]
# 【待确认】自定义用户模型,你需要在 yonghu/models.py 中创建 class Yonghu
#AUTH_USER_MODEL = 'yonghu.Yonghu'
# OSS配置
COS_SECRET_ID ='AKIDU6WikZBcz96xesxpVM3XoslS3TjogLDx'
COS_SECRET_KEY ='qcJzVqLdkeKyXwKckjV5SCmncFfrqdu2'
COS_REGION = 'ap-shanghai'
COS_BUCKET = 'xingque999qygwuyq-1404472910'
COS_SCHEME = 'https' # 使用HTTPS
COS_DOMAIN = f'https://{COS_BUCKET}.cos.{COS_REGION}.myqcloud.com'
# settings.py - 在COS配置部分添加
COS_APPID = '1404472910' # 你的腾讯云APPIDjulebu-1361527063中的数字部分
COS_STS_DURATION = 7200 # STS临时凭证有效期
# ==================== 业务相关配置 (值先空着,结构可保留) ====================
# 微信小程序配置 (开发环境可先留空或使用测试号)
WEIXIN_APPID = 'wx0e4be86faac4a8d1' # 你的小程序APPID
WEIXIN_SECRET = '3c3673cf052571dea9e3bea88e198434' # 你的小程序SECRET
WEIXIN_MCHID = '1746545364' # 商户号,支付功能需要
WEIXIN_SHANGHUMIYAO = 'Dujieduze53631383771411017633490' # 商户密钥,支付功能需要
# 🆕【新增】服务号配置
WEIXIN_OFFICIAL_APPID = 'wxd400adf1911c534f' # 服务号APPID
WEIXIN_OFFICIAL_SECRET = '19c946cf02ac6ef13f40333fc8029b92' # 服务号SECRET
WEIXIN_OFFICIAL_TOKEN = '78324hjwewehrui3yferhf34' # 服务号Token
WEIXIN_OFFICIAL_ENCODING_AES_KEY = 'NimrG1Fmq2ydplHJrXeg34NqbCJerltAGVx9xJ5WBx2' # 可选
WEIXIN_TEMPLATE_ID = 'jZ73G_0KbmFK864WVUr0QFNnqdH-ILBp_hUPMiwx4YY' # 模板ID
# 🆕【新增】模板消息配置
WEIXIN_TEMPLATE_MAX_PER_MINUTE = 950 # 每分钟最大发送量留50缓冲
WEIXIN_TEMPLATE_BATCH_SIZE = 100 # 每批发送数量
WEIXIN_CERT_PATH = '/opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.1/tuikuanzhengshu/apiclient_cert.pem' # 证书文件路径
WEIXIN_KEY_PATH = '/opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.1/tuikuanzhengshu/apiclient_key.pem' # 密钥文件路径
WEIXIN_NOTIFY_URL = 'https://www.abas.asia/hqhd/dingdan/pay-notify/' # 支付回调地址http://你的域名/api/pay/callback/
# settings.py 或相应配置文件中
WEIXIN_PAY_DESCRIPTION = "网络服务" # 支付描述
# 押金充值相关配置
YAJIN_PAY_DESCRIPTION = "打手押金充值" # 押金支付描述
YAJIN_PAY_NOTIFY_URL = "https://www.abas.asia/hqhd/shangpin/yajinhuitiao" # 押金支付回调地址
# 积分充值相关配置
JIFEN_PAY_DESCRIPTION = "打手积分补充" # 积分支付描述
JIFEN_PAY_NOTIFY_URL = "https://www.abas.asia/hqhd/shangpin/jifenhuitiao" # 积分支付回调地址
# 会员充值相关配置
HUIYUAN_PAY_DESCRIPTION = "打手会员充值" # 会员支付描述
HUIYUAN_PAY_NOTIFY_URL = "https://www.abas.asia/hqhd/shangpin/huiyuanhuitiao" # 会员支付回调地址
# 考核支付配置
KAOHE_PAY_DESCRIPTION = '考核金牌-缴纳考核费'
KAOHE_PAY_NOTIFY_URL = 'https://www.abas.asia/hqhd/yonghu/kh_huitiao' # 替换为实际回调地址
KAOHE_PAY_DEFAULT_FEE = 5.00 # 未配置费用时的默认金额
# settings.py
SHANGJIA_CZ_PREFIX = "CzSJ" # 商家充值订单前缀
SHANGJIA_CZ_PAY_DESCRIPTION = "阿龙电竞-商家余额充值"
SHANGJIA_CZ_PAY_NOTIFY_URL = "https://www.abas.asia/hqhd/shangpin/sjhuitiao"
SHANGJIA_CZ_MIN_AMOUNT = 1
SHANGJIA_CZ_MAX_AMOUNT = 10000
# 罚款缴纳微信支付配置
FAKUAN_PAY_DESCRIPTION = '罚款缴纳' # 支付描述
FAKUAN_PAY_NOTIFY_URL = 'https://www.abas.asia/hqhd/yonghu/fk_huitiao' # 微信支付回调地址(根据实际域名调整)
# 订单ID前缀配置
ORDER_PREFIX = {
'yajin': 'CzYJ', # 押金订单前缀
'jifen': 'CzJF', # 积分订单前缀
'huiyuan': 'CzHY', # 会员订单前缀
}
# 腾讯云即时通讯 (IM) - 开发环境可暂不配置
TENCENT_IM_CONFIG = {
'SDK_APP_ID': 0, # 整数
'SECRET_KEY': '',
}
# 阿里云短信 - 开发环境可暂不配置
ALIYUN_SMS_CONFIG = {
'ACCESS_KEY_ID': '',
'ACCESS_KEY_SECRET': '',
'SIGN_NAME': '',
'TEMPLATE_CODE': '',
'REGION_ID': 'cn-hangzhou',
}
# HashID (用于加密ID旧项目有新项目需要吗)
# HASHID_FIELD_SALT = 'a_long_dianjing_2025_salt' # 【待确认】请设置你的盐值
# settings.py 尾部添加
# settings.py
# settings.py
# settings.py 中添加配置
GOEASY_APPKEY = 'BC-32e2991c69bc47e1b538e73aaed37fc1' # 你的AppKey
GOEASY_SECRET = 'A469ABAA3cc34D64' # 从GoEasy控制台获取
GOEASY_REST_URL = 'https://rest-hz.goeasy.io/v2/im/message' # 正式环境
"""
阿龙电竞 - Django设置文件中的Celery配置部分
请在您的settings.py中添加以下配置
"""
# ==================== Celery配置 ====================
# Redis作为消息代理和结果后端
# 生产环境请修改这里的Redis连接信息
REDIS_HOST = '172.19.0.2' # Redis服务器IP
REDIS_PORT = 6379
REDIS_PASSWORD = 'Dujieduze5.3' # 如果有密码,替换为你的密码
REDIS_DB_CELERY = 0
REDIS_DB_RESULT = 1
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': f'redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/2',
'TIMEOUT': 300,
'OPTIONS': {
# 不需要 CLIENT_CLASS
}
}
}
# 构建Redis连接字符串
if REDIS_PASSWORD:
CELERY_BROKER_URL = f'redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB_CELERY}'
CELERY_RESULT_BACKEND = f'redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB_RESULT}'
else:
CELERY_BROKER_URL = f'redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB_CELERY}'
CELERY_RESULT_BACKEND = f'redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB_RESULT}'
# 任务接受的内容类型
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
# 时区设置
CELERY_TIMEZONE = 'Asia/Shanghai'
CELERY_ENABLE_UTC = True
# settings.py 末尾添加(在其他 Celery 配置之后)
CELERY_IMPORTS = ('dingdan.tongzhi_tasks',)
# 任务超时设置
CELERY_TASK_TIME_LIMIT = 300 # 任务最大执行时间300秒
CELERY_TASK_SOFT_TIME_LIMIT = 240 # 软超时240秒
# Worker并发设置
CELERY_WORKER_CONCURRENCY = 4 # 根据服务器CPU核心数调整
CELERY_WORKER_PREFETCH_MULTIPLIER = 1
# 任务确认设置
CELERY_TASK_ACKS_LATE = True
CELERY_WORKER_DISABLE_RATE_LIMITS = True
# 队列配置
CELERY_TASK_DEFAULT_QUEUE = 'default'
CELERY_TASK_QUEUES = {
'default': {
'exchange': 'default',
'routing_key': 'default',
},
'order_tasks': {
'exchange': 'order_tasks',
'routing_key': 'order_tasks',
},
'periodic_tasks': {
'exchange': 'periodic_tasks',
'routing_key': 'periodic_tasks',
},
}
# ==================== 定时任务时间配置 ====================
# 所有时间配置都在这里,方便修改
# 订单超时时间(秒) - 48小时
ORDER_EXPIRE_SECONDS = 48000 * 60 * 60 # 172800秒
# 每日任务执行时间24小时制
DAILY_TASK_HOUR = 0 # 0点
DAILY_TASK_MINUTE = 30 # 0分
# 每月任务执行时间
MONTHLY_TASK_DAY = 1 # 每月1日
MONTHLY_TASK_HOUR = 0 # 0点
MONTHLY_TASK_MINUTE = 0 # 0分
# 订单检查频率(分钟)
ORDER_CHECK_INTERVAL = 5 # 每5分钟检查一次
# ==================== 跨平台订单互通配置 ====================
# 接口认证令牌(双方约定,用于简单验证)
CROSS_PLATFORM_TOKEN = 'jdjdjshwjqk'
# 对方平台接口基础URL需根据实际部署修改
PARTNER_BASE_URL = 'https://ios808.top/hqhd'
# 对方平台各接口路径(可根据实际情况调整,但双方需保持一致)
PARTNER_SYNC_ORDER_URL = '/dingdan/partner_sync_order' # 接收派单
PARTNER_CHECK_CLAIM_URL = '/dingdan/partner_check_claim' # 抢单询问
PARTNER_NOTIFY_CLAIM_URL = '/dingdan/partner_notify_claim' # 抢单成功通知
PARTNER_REFUND_NOTIFY_URL = '/dingdan/partner_refund_notify' # 退款通知
# 我方代表对方发单的商家ID需在数据库中存在且具有商家扩展表
AGENT_SHANGJIA_ID = '9106027'
# 用于验证的会员ID根据业务约定
CROSS_PLATFORM_MEMBER_ID = 'V00QQI'
# 默认游戏类型ID当对方未传递时使用
CROSS_PLATFORM_GAME_TYPE_ID = 1
# 允许跨平台的商家ID列表白名单
CROSS_PLATFORM_WHITELIST = ['5438898']
# 特殊管事ID列表yonghuid这些管事的首次分红金额从 UserGuanshi.fenhong_yici 字段获取
SPECIAL_GUANSHI_IDS = [
'2295516',
'9106027',
'2244062',
# ... 按需添加
]
WECHAT_PAY_V3_CONFIG = {
'MCHID': '1746545364', # 商户号
'APPID': 'wx0e4be86faac4a8d1', # 小程序APPID
'API_V3_KEY': 'dujie4637rufjd98ijuyrgcvbvbn4789', # APIv3密钥32字节
'PRIVATE_KEY_PATH': '/opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.443/huizhifuzhengshu/apiclient_key.pem', # 商户私钥文件路径
'CERT_SERIAL_NO': '13D0CECB42C48DE78FF35A8A72EF4341FE769FE9', # 商户证书序列号
'PLATFORM_CERT_DIR': '/opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.443/huizhifuzhengshu/pub_key.pem', # 存放微信平台证书公钥的目录(文件名:序列号.pem
'NOTIFY_URL': 'https://www.abas.asia/hqhd/yonghu/tixiancallback', # 回调地址
'TRANSFER_SCENE_ID': '1005', # 替换为你的实际场景ID
}
CROSS_PLATFORM_SYNC_TOKEN = 'xK9mP2vL8qW5rT7y'

58
a_long_dianjing/urls.py Normal file
View File

@@ -0,0 +1,58 @@
"""
URL configuration for a_long_dianjing project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/6.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
'''from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]'''
# a_long_dianjing/urls.py
"""
项目根路由配置 - 按照生产环境格式连接所有APP路由
注意本项目使用MinIO模拟OSS文件上传不经过Django MEDIA_URL所以不需要static(settings.MEDIA_URL)配置
"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
# Django管理后台
path('admin/', admin.site.urls),
# 用户相关路由
path('yonghu/', include('yonghu.urls')),
# 订单相关路由
path('dingdan/', include('dingdan.urls')),
# 商品/陪玩服务相关路由
path('shangpin/', include('shangpin.urls')),
# 配置相关路由(系统配置)
path('peizhi/', include('peizhi.urls')),
path('houtai/', include('houtai.urls')),
path('shangdian/', include('shangdian.urls')),
path('dengji/', include('dengji.urls')),
]
# 重要说明:
# 1. 本项目使用MinIO模拟OSS作为对象存储文件上传通过MinIO的S3接口直接处理
# 2. 文件存储不经过Django的MEDIA_URL所以不需要配置 static(settings.MEDIA_URL, ...)
# 3. 未来迁移到腾讯云COS/阿里云OSS时也通过django-storages直接处理不经过Django静态文件服务
# 4. 所有文件访问将通过MinIO/COS的CDN域名直接访问不需要Django路由转发

16
a_long_dianjing/wsgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
WSGI config for a_long_dianjing project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/6.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'a_long_dianjing.settings')
application = get_wsgi_application()

BIN
celerybeat-schedule Normal file

Binary file not shown.

0
dengji/__init__.py Normal file
View File

3
dengji/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
dengji/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class DengjiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'dengji'

View File

@@ -0,0 +1,111 @@
# Generated by Django 4.2.27 on 2026-05-12 02:17
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('dingdan', '0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more'),
]
operations = [
migrations.CreateModel(
name='Bankuai',
fields=[
('bankuai_id', models.AutoField(primary_key=True, serialize=False, verbose_name='板块ID')),
('mingcheng', models.CharField(db_index=True, max_length=50, unique=True, verbose_name='板块名称')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
],
options={
'verbose_name': '板块',
'verbose_name_plural': '板块',
'db_table': 'bankuai',
},
),
migrations.CreateModel(
name='Chenghao',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('mingcheng', models.CharField(max_length=50, unique=True, verbose_name='称号名称')),
('leixing', models.CharField(choices=[('dashou', '打手'), ('boss', '老板'), ('shangjia', '商家'), ('guanshi', '管事'), ('zuzhang', '组长')], max_length=10, verbose_name='称号类型')),
('tubiao_url', models.CharField(blank=True, max_length=200, null=True, verbose_name='称号图标相对URL')),
('texiao_miaoshu', models.TextField(blank=True, null=True, verbose_name='特效描述(JSON)')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('bankuai', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dengji.bankuai', verbose_name='所属板块(空=全板块通用)')),
],
options={
'verbose_name': '称号',
'verbose_name_plural': '称号',
'db_table': 'chenghao',
},
),
migrations.CreateModel(
name='YonghuChenghao',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('huode_time', models.DateTimeField(auto_now_add=True, verbose_name='获得时间')),
('chenghao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dengji.chenghao', verbose_name='称号')),
('yonghu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chenghao_list', to=settings.AUTH_USER_MODEL, verbose_name='用户')),
],
options={
'verbose_name': '用户称号关联',
'verbose_name_plural': '用户称号关联',
'db_table': 'yonghu_chenghao',
'indexes': [models.Index(fields=['yonghu', 'chenghao'], name='yonghu_chen_yonghu__cac0a1_idx')],
'unique_together': {('yonghu', 'chenghao')},
},
),
migrations.CreateModel(
name='JingyanJilu',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('yonghu_id', models.CharField(db_index=True, max_length=7, verbose_name='用户ID')),
('yonghu_leixing', models.CharField(choices=[('dashou', '打手'), ('boss', '老板'), ('shangjia', '商家'), ('guanshi', '管事'), ('zuzhang', '组长')], max_length=10, verbose_name='用户类型')),
('sudu_jingyan', models.DecimalField(decimal_places=2, default=0, max_digits=8, verbose_name='上线速度经验')),
('shili_jingyan', models.DecimalField(decimal_places=2, default=0, max_digits=8, verbose_name='打手实力经验')),
('taidu_jingyan', models.DecimalField(decimal_places=2, default=0, max_digits=8, verbose_name='服务态度经验')),
('zonghe_jingyan', models.DecimalField(decimal_places=2, default=0, max_digits=8, verbose_name='综合经验')),
('dingdan_id', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='关联订单ID')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('bankuai', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='dengji.bankuai', verbose_name='所属板块')),
],
options={
'verbose_name': '经验变动记录',
'verbose_name_plural': '经验变动记录',
'db_table': 'jingyan_jilu',
'indexes': [models.Index(fields=['yonghu_id', 'bankuai', 'create_time'], name='jingyan_jil_yonghu__786992_idx')],
},
),
migrations.CreateModel(
name='DashouBiaoxian',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('dashou_id', models.CharField(db_index=True, max_length=32, verbose_name='打手ID')),
('shangxian_sudu', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='上线速度评价(1-5)')),
('fuwu_taidu', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='服务态度评价(1-5)')),
('fanche_cishu', models.PositiveIntegerField(blank=True, null=True, verbose_name='翻车次数')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('jiedan_time', models.DateTimeField(blank=True, null=True, verbose_name='接单时间')),
('tijiao_time', models.DateTimeField(blank=True, null=True, verbose_name='提交时间')),
('bankuai', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='dengji.bankuai', verbose_name='所属板块')),
('dingdan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='biaoxian', to='dingdan.dingdan', verbose_name='关联订单')),
],
options={
'verbose_name': '打手订单表现',
'verbose_name_plural': '打手订单表现',
'db_table': 'dashou_biaoxian',
'indexes': [models.Index(fields=['dashou_id', 'bankuai', 'create_time'], name='dashou_biao_dashou__36e1c3_idx')],
},
),
migrations.AddIndex(
model_name='chenghao',
index=models.Index(fields=['leixing', 'bankuai'], name='chenghao_leixing_8f4371_idx'),
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 4.2.27 on 2026-05-12 02:25
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more'),
('dengji', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='dashoubiaoxian',
name='dingdan',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='biaoxian', to='dingdan.dingdan', to_field='dingdan_id', verbose_name='关联订单'),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 4.2.27 on 2026-05-12 02:32
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dengji', '0002_alter_dashoubiaoxian_dingdan'),
]
operations = [
migrations.AlterField(
model_name='dashoubiaoxian',
name='bankuai',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='dengji.bankuai', verbose_name='所属板块'),
),
]

View File

@@ -0,0 +1,26 @@
# dengji/migrations/0004_dingdanbiaoqian.py
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more'), # 保持你之前的依赖
('dengji', '0003_alter_dashoubiaoxian_bankuai'),
]
operations = [
migrations.RunSQL(
sql="""
CREATE TABLE IF NOT EXISTS dingdan_biaoqian (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
create_time DATETIME(6) NOT NULL,
chenghao_id BIGINT NOT NULL,
dingdan_id VARCHAR(32) NOT NULL,
CONSTRAINT fk_dingdanbiaoqian_chenghao FOREIGN KEY (chenghao_id) REFERENCES chenghao (id),
CONSTRAINT fk_dingdanbiaoqian_dingdan FOREIGN KEY (dingdan_id) REFERENCES dingdan (dingdan_id),
UNIQUE KEY uniq_dingdan_chenghao (dingdan_id, chenghao_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""",
reverse_sql="DROP TABLE IF EXISTS dingdan_biaoqian;"
),
]

View File

@@ -0,0 +1,36 @@
# dengji/migrations/0005_fix_state.py
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more'), # 保持你之前的依赖
('dengji', '0004_dingdanbiaoqian'),
]
operations = [
migrations.SeparateDatabaseAndState(
database_operations=[], # 表已存在,不做任何数据库操作
state_operations=[
migrations.CreateModel(
name='DingdanBiaoqian',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('chenghao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dengji.chenghao', verbose_name='标签')),
('dingdan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dingdan.dingdan', to_field='dingdan_id', verbose_name='订单')),
],
options={
'verbose_name': '订单需求标签',
'verbose_name_plural': '订单需求标签',
'db_table': 'dingdan_biaoqian',
'unique_together': {('dingdan', 'chenghao')},
'indexes': [
models.Index(fields=['dingdan'], name='dingdan_bia_dingdan_4d046b_idx'),
],
},
),
],
),
]

View File

@@ -0,0 +1,35 @@
# Generated by Django 4.2.27 on 2026-05-14 18:42
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dengji', '0005_fix_state'),
]
operations = [
migrations.CreateModel(
name='ShenheJilu',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('shenqingren_id', models.CharField(max_length=7, verbose_name='申请人用户ID')),
('shenheguan_id', models.CharField(max_length=7, null=True, verbose_name='审核官用户ID')),
('zhuangtai', models.IntegerField(choices=[(0, '审核中'), (1, '通过'), (2, '未通过')], default=0, verbose_name='审核状态')),
('cishu', models.IntegerField(default=1, verbose_name='第几次审核')),
('jiaofei_jine', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='缴纳费用')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('bankuai', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dengji.bankuai', verbose_name='审核板块')),
('chenghao', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dengji.chenghao', verbose_name='审核标签')),
],
options={
'verbose_name': '审核记录',
'verbose_name_plural': '审核记录',
'db_table': 'shenhe_jilu',
'indexes': [models.Index(fields=['shenqingren_id', 'zhuangtai'], name='shenhe_jilu_shenqin_502e9b_idx'), models.Index(fields=['shenheguan_id'], name='shenhe_jilu_shenheg_5c9959_idx')],
},
),
]

View File

@@ -0,0 +1,29 @@
# Generated by Django 4.2.27 on 2026-05-15 00:31
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dengji', '0006_shenhejilu'),
]
operations = [
migrations.CreateModel(
name='KaoheguanBankuai',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('kaoheguan_id', models.CharField(db_index=True, max_length=7, verbose_name='考核官用户ID')),
('bankuai', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dengji.bankuai', verbose_name='所属板块')),
],
options={
'verbose_name': '考核官板块关联',
'verbose_name_plural': '考核官板块关联',
'db_table': 'kaoheguan_bankuai',
'indexes': [models.Index(fields=['kaoheguan_id'], name='kaoheguan_b_kaohegu_00f7c5_idx')],
'unique_together': {('kaoheguan_id', 'bankuai')},
},
),
]

View File

@@ -0,0 +1,48 @@
# Generated by Django 4.2.27 on 2026-05-15 04:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dengji', '0007_kaoheguanbankuai'),
]
operations = [
migrations.AddField(
model_name='chenghao',
name='kaohe_guize',
field=models.TextField(blank=True, null=True, verbose_name='考核规则'),
),
migrations.AddField(
model_name='shenhejilu',
name='dashou_beizhu',
field=models.TextField(blank=True, null=True, verbose_name='打手备注'),
),
migrations.AddField(
model_name='shenhejilu',
name='dashou_youxi_id',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='打手游戏ID'),
),
migrations.AddField(
model_name='shenhejilu',
name='guize_neirong',
field=models.TextField(blank=True, null=True, verbose_name='规则内容'),
),
migrations.AddField(
model_name='shenhejilu',
name='jilu_id',
field=models.CharField(db_index=True, max_length=32, null=True, verbose_name='记录ID'),
),
migrations.AddField(
model_name='shenhejilu',
name='shenheguan_youxi_id',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='审核官游戏ID'),
),
migrations.AlterField(
model_name='shenhejilu',
name='zhuangtai',
field=models.IntegerField(choices=[(0, '审核中'), (1, '通过'), (2, '未通过'), (3, '待审核')], default=0, verbose_name='审核状态'),
),
]

View File

@@ -0,0 +1,58 @@
# Generated by Django 4.2.27 on 2026-05-15 13:20
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dengji', '0008_chenghao_kaohe_guize_shenhejilu_dashou_beizhu_and_more'),
]
operations = [
migrations.AddField(
model_name='chenghao',
name='kaioi_jinpai',
field=models.BooleanField(default=False, verbose_name='是否同时开启金牌'),
),
migrations.AlterField(
model_name='shenhejilu',
name='jilu_id',
field=models.CharField(db_index=True, default='Sh111', max_length=32, unique=True, verbose_name='记录ID'),
preserve_default=False,
),
migrations.CreateModel(
name='KaoheJujueJilu',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('cishu', models.IntegerField(verbose_name='对应次数')),
('shenheguan_id', models.CharField(max_length=7, verbose_name='考核官用户ID')),
('yuanyin', models.TextField(verbose_name='拒绝原因')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('jilu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dengji.shenhejilu', to_field='jilu_id', verbose_name='审核记录')),
],
options={
'verbose_name': '拒绝原因记录',
'verbose_name_plural': '拒绝原因记录',
'db_table': 'kaohe_jujue_jilu',
'indexes': [models.Index(fields=['jilu_id', 'cishu'], name='kaohe_jujue_jilu_id_1f2b29_idx')],
},
),
migrations.CreateModel(
name='KaoheCishuFeiyong',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('cishu', models.IntegerField(verbose_name='第几次考核')),
('feiyong', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='需缴纳金额')),
('chenghao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dengji.chenghao', verbose_name='标签')),
],
options={
'verbose_name': '考核次数费用',
'verbose_name_plural': '考核次数费用',
'db_table': 'kaohe_cishu_feiyong',
'indexes': [models.Index(fields=['chenghao', 'cishu'], name='kaohe_cishu_chengha_edc272_idx')],
'unique_together': {('chenghao', 'cishu')},
},
),
]

View File

@@ -0,0 +1,29 @@
# Generated by Django 4.2.27 on 2026-05-15 16:10
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dengji', '0009_chenghao_kaioi_jinpai_alter_shenhejilu_jilu_id_and_more'),
]
operations = [
migrations.CreateModel(
name='KaoheJiluFeiyong',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('cishu', models.IntegerField(verbose_name='第几次考核')),
('jine', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='缴纳金额')),
('jilu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dengji.shenhejilu', to_field='jilu_id', verbose_name='考核记录')),
],
options={
'verbose_name': '考核记录费用',
'verbose_name_plural': '考核记录费用',
'db_table': 'kaohe_jilu_feiyong',
'unique_together': {('jilu', 'cishu')},
},
),
]

View File

@@ -0,0 +1,35 @@
# Generated by Django 4.2.27 on 2026-05-19 13:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dengji', '0010_kaohejilufeiyong'),
]
operations = [
migrations.CreateModel(
name='KaohePayTemp',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('dingdan_id', models.CharField(db_index=True, max_length=50, unique=True, verbose_name='订单ID')),
('yonghuid', models.CharField(db_index=True, max_length=7, verbose_name='用户ID')),
('tag_id', models.IntegerField(verbose_name='标签ID')),
('game_nick', models.CharField(max_length=100, verbose_name='游戏昵称')),
('remark', models.TextField(blank=True, verbose_name='备注')),
('reapply', models.BooleanField(default=False, verbose_name='是否再次申请')),
('jilu_id', models.CharField(blank=True, max_length=32, null=True, verbose_name='原记录ID(再次申请时)')),
('shenheguan_id', models.CharField(blank=True, max_length=7, verbose_name='指定考核官ID')),
('cishu', models.IntegerField(verbose_name='本次考核次数(预计算)')),
('fee', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='本次费用(分账单位)')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
],
options={
'verbose_name': '考核支付临时表',
'verbose_name_plural': '考核支付临时表',
'db_table': 'kaohe_pay_temp',
},
),
]

View File

224
dengji/models.py Normal file
View File

@@ -0,0 +1,224 @@
from django.db import models
from django.conf import settings
from dingdan.models import Dingdan
# ==================== 板块表 ====================
class Bankuai(models.Model):
bankuai_id = models.AutoField(primary_key=True, verbose_name='板块ID')
mingcheng = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='板块名称')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
class Meta:
db_table = 'bankuai'
verbose_name = '板块'
verbose_name_plural = verbose_name
def __str__(self):
return self.mingcheng
# ==================== 打手订单表现记录 ====================
class DashouBiaoxian(models.Model):
dingdan = models.ForeignKey('dingdan.Dingdan', to_field='dingdan_id', on_delete=models.CASCADE, related_name='biaoxian', verbose_name='关联订单', db_index=True)
dashou_id = models.CharField(max_length=32, db_index=True, verbose_name='打手ID')
bankuai = models.ForeignKey(Bankuai, on_delete=models.PROTECT, to_field='bankuai_id', verbose_name='所属板块', db_index=True,null=True)
shangxian_sudu = models.PositiveSmallIntegerField(null=True, blank=True, verbose_name='上线速度评价(1-5)')
fuwu_taidu = models.PositiveSmallIntegerField(null=True, blank=True, verbose_name='服务态度评价(1-5)')
fanche_cishu = models.PositiveIntegerField(null=True, blank=True, verbose_name='翻车次数')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
jiedan_time = models.DateTimeField(null=True, blank=True, verbose_name='接单时间')
tijiao_time = models.DateTimeField(null=True, blank=True, verbose_name='提交时间')
class Meta:
db_table = 'dashou_biaoxian'
verbose_name = '打手订单表现'
verbose_name_plural = verbose_name
indexes = [models.Index(fields=['dashou_id', 'bankuai', 'create_time'])]
# ==================== 经验变动记录 ====================
class JingyanJilu(models.Model):
yonghu_id = models.CharField(max_length=7, db_index=True, verbose_name='用户ID')
yonghu_leixing = models.CharField(max_length=10, choices=[('dashou','打手'),('boss','老板'),('shangjia','商家'),('guanshi','管事'),('zuzhang','组长')], verbose_name='用户类型')
bankuai = models.ForeignKey(Bankuai, on_delete=models.PROTECT, to_field='bankuai_id', verbose_name='所属板块', db_index=True)
sudu_jingyan = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name='上线速度经验')
shili_jingyan = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name='打手实力经验')
taidu_jingyan = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name='服务态度经验')
zonghe_jingyan = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name='综合经验')
dingdan_id = models.CharField(max_length=32, null=True, blank=True, db_index=True, verbose_name='关联订单ID')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'jingyan_jilu'
verbose_name = '经验变动记录'
verbose_name_plural = verbose_name
indexes = [models.Index(fields=['yonghu_id', 'bankuai', 'create_time'])]
# ==================== 称号主表 ====================
class Chenghao(models.Model):
mingcheng = models.CharField(max_length=50, unique=True, verbose_name='称号名称')
leixing = models.CharField(max_length=10, choices=[('dashou','打手'),('boss','老板'),('shangjia','商家'),('guanshi','管事'),('zuzhang','组长')], verbose_name='称号类型')
bankuai = models.ForeignKey(Bankuai, on_delete=models.PROTECT, to_field='bankuai_id', null=True, blank=True, verbose_name='所属板块(空=全板块通用)')
tubiao_url = models.CharField(max_length=200, null=True, blank=True, verbose_name='称号图标相对URL')
texiao_miaoshu = models.TextField(null=True, blank=True, verbose_name='特效描述(JSON)')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
kaohe_guize = models.TextField(null=True, blank=True, verbose_name='考核规则') # 新增
kaioi_jinpai = models.BooleanField(default=False, verbose_name='是否同时开启金牌')
class Meta:
db_table = 'chenghao'
verbose_name = '称号'
verbose_name_plural = verbose_name
indexes = [models.Index(fields=['leixing', 'bankuai'])]
# ==================== 用户 - 称号关联 ====================
class YonghuChenghao(models.Model):
yonghu = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='chenghao_list', verbose_name='用户', db_index=True)
chenghao = models.ForeignKey(Chenghao, on_delete=models.CASCADE, verbose_name='称号')
huode_time = models.DateTimeField(auto_now_add=True, verbose_name='获得时间')
class Meta:
db_table = 'yonghu_chenghao'
verbose_name = '用户称号关联'
verbose_name_plural = verbose_name
unique_together = ('yonghu', 'chenghao')
# 修复:只索引本表字段
indexes = [models.Index(fields=['yonghu', 'chenghao'])]
class DingdanBiaoqian(models.Model):
"""订单需求标签关联表"""
dingdan = models.ForeignKey(
Dingdan,
to_field='dingdan_id', # 关联到字符串字段 dingdan_id
on_delete=models.CASCADE,
verbose_name='订单',
db_index=True
)
chenghao = models.ForeignKey('dengji.Chenghao', on_delete=models.CASCADE, verbose_name='标签')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
class Meta:
db_table = 'dingdan_biaoqian'
verbose_name = '订单需求标签'
verbose_name_plural = verbose_name
unique_together = ('dingdan', 'chenghao')
indexes = [models.Index(fields=['dingdan'])]
class ShenheJilu(models.Model):
"""审核记录表"""
shenqingren_id = models.CharField(max_length=7, verbose_name='申请人用户ID')
shenheguan_id = models.CharField(max_length=7, null = True,verbose_name='审核官用户ID')
zhuangtai = models.IntegerField(choices=[(0, '审核中'), (1, '通过'), (2, '未通过'), (3, '待审核')], default=0, verbose_name='审核状态')
bankuai = models.ForeignKey(Bankuai, on_delete=models.PROTECT, to_field='bankuai_id', null=True, blank=True, verbose_name='审核板块')
chenghao = models.ForeignKey(Chenghao, on_delete=models.PROTECT, null=True, blank=True, verbose_name='审核标签')
cishu = models.IntegerField(default=1, verbose_name='第几次审核')
jiaofei_jine = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='缴纳费用')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
jilu_id = models.CharField(max_length=32, unique=True, db_index=True, verbose_name='记录ID') # 新增
guize_neirong = models.TextField(null=True, blank=True, verbose_name='规则内容')
shenheguan_youxi_id = models.CharField(max_length=50, null=True, blank=True, verbose_name='审核官游戏ID')
dashou_youxi_id = models.CharField(max_length=50, null=True, blank=True, verbose_name='打手游戏ID')
dashou_beizhu = models.TextField(null=True, blank=True, verbose_name='打手备注')
class Meta:
db_table = 'shenhe_jilu'
verbose_name = '审核记录'
verbose_name_plural = verbose_name
indexes = [
models.Index(fields=['shenqingren_id', 'zhuangtai']),
models.Index(fields=['shenheguan_id']),
]
class KaohePayTemp(models.Model):
"""考核支付临时上下文(仅用于支付成功后回调取参,不删除数据)"""
dingdan_id = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='订单ID')
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='用户ID')
tag_id = models.IntegerField(verbose_name='标签ID')
game_nick = models.CharField(max_length=100, verbose_name='游戏昵称')
remark = models.TextField(blank=True, verbose_name='备注')
reapply = models.BooleanField(default=False, verbose_name='是否再次申请')
jilu_id = models.CharField(max_length=32, null=True, blank=True, verbose_name='原记录ID(再次申请时)')
shenheguan_id = models.CharField(max_length=7, blank=True, verbose_name='指定考核官ID')
cishu = models.IntegerField(verbose_name='本次考核次数(预计算)')
fee = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='本次费用(分账单位)')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
class Meta:
db_table = 'kaohe_pay_temp'
verbose_name = '考核支付临时表'
verbose_name_plural = verbose_name
class KaoheCishuFeiyong(models.Model):
"""各标签各考核次数所需缴纳金额"""
chenghao = models.ForeignKey(Chenghao, on_delete=models.CASCADE, verbose_name='标签')
cishu = models.IntegerField(verbose_name='第几次考核')
feiyong = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='需缴纳金额')
class Meta:
db_table = 'kaohe_cishu_feiyong'
verbose_name = '考核次数费用'
verbose_name_plural = verbose_name
unique_together = ('chenghao', 'cishu')
indexes = [models.Index(fields=['chenghao', 'cishu'])]
class KaoheJujueJilu(models.Model):
"""考核拒绝原因记录"""
jilu = models.ForeignKey(ShenheJilu, on_delete=models.CASCADE, to_field='jilu_id', verbose_name='审核记录')
cishu = models.IntegerField(verbose_name='对应次数')
shenheguan_id = models.CharField(max_length=7, verbose_name='考核官用户ID')
yuanyin = models.TextField(verbose_name='拒绝原因')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
class Meta:
db_table = 'kaohe_jujue_jilu'
verbose_name = '拒绝原因记录'
verbose_name_plural = verbose_name
indexes = [models.Index(fields=['jilu_id', 'cishu'])]
class KaoheJiluFeiyong(models.Model):
"""每次考核记录实际缴纳的费用明细"""
jilu = models.ForeignKey(ShenheJilu, on_delete=models.CASCADE, to_field='jilu_id', verbose_name='考核记录')
cishu = models.IntegerField(verbose_name='第几次考核')
jine = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='缴纳金额')
class Meta:
db_table = 'kaohe_jilu_feiyong'
verbose_name = '考核记录费用'
verbose_name_plural = verbose_name
unique_together = ('jilu', 'cishu')
class KaoheguanBankuai(models.Model):
"""考核官与板块多对多关联表"""
kaoheguan_id = models.CharField(max_length=7, db_index=True, verbose_name='考核官用户ID')
bankuai = models.ForeignKey(Bankuai, on_delete=models.CASCADE, to_field='bankuai_id', verbose_name='所属板块')
class Meta:
db_table = 'kaoheguan_bankuai'
verbose_name = '考核官板块关联'
verbose_name_plural = verbose_name
unique_together = ('kaoheguan_id', 'bankuai')
indexes = [models.Index(fields=['kaoheguan_id'])]

87
dengji/services.py Normal file
View File

@@ -0,0 +1,87 @@
# dengji/services.py
import logging
from django.db import transaction, IntegrityError
from django.utils import timezone
from .models import DashouBiaoxian
logger = logging.getLogger('dengji')
def record_dashou_biaoxian(dingdan_id, dashou_id, xingwei, **kwargs):
"""
统一记录打手订单表现
:param dingdan_id: 订单ID
:param dashou_id: 打手ID
:param xingwei: 行为类型 1-抢单 2-提交订单 3-结算
:param kwargs: 可选字段(与模型字段同名):
jiedan_time, tijiao_time,
bankuai, shangxian_sudu, fuwu_taidu, fanche_cishu
注意不管是哪种行为dingdan_id 和 dashou_id 都是必传的,
如果没传时间且业务需要时间,则自动取当前时间。
"""
try:
# ----- 抢单:创建记录 -----
if xingwei == 1:
jiedan_time = kwargs.get('jiedan_time') or timezone.now()
bankuai = kwargs.get('bankuai') # 可为 None
# 避免重复创建(同一订单只会有一条表现记录)
obj, created = DashouBiaoxian.objects.get_or_create(
dingdan_id=dingdan_id,
dashou_id=dashou_id,
defaults={
'bankuai': bankuai,
'jiedan_time': jiedan_time,
}
)
if not created:
logger.info(f"表现记录已存在,跳过创建: dingdan={dingdan_id}, dashou={dashou_id}")
else:
logger.info(f"创建抢单表现记录: dingdan={dingdan_id}, dashou={dashou_id}")
# ----- 提交订单:更新提交时间 -----
elif xingwei == 2:
tijiao_time = kwargs.get('tijiao_time') or timezone.now()
# 只更新已存在的记录,找不到就直接跳过
updated = DashouBiaoxian.objects.filter(
dingdan_id=dingdan_id,
dashou_id=dashou_id
).update(tijiao_time=tijiao_time)
if updated:
logger.info(f"更新提交时间: dingdan={dingdan_id}, dashou={dashou_id}")
else:
logger.info(f"无对应记录,跳过提交时间更新: dingdan={dingdan_id}, dashou={dashou_id}")
# ----- 结算:更新评价信息 -----
elif xingwei == 3:
# 构建要更新的字段,只更新 kwargs 中传入的非 None 字段
update_fields = {}
for field in ['shangxian_sudu', 'fuwu_taidu', 'fanche_cishu']:
val = kwargs.get(field)
if val is not None:
update_fields[field] = val
# 如果还传了板块也可以更新
bankuai = kwargs.get('bankuai')
if bankuai is not None:
update_fields['bankuai'] = bankuai
if update_fields:
updated = DashouBiaoxian.objects.filter(
dingdan_id=dingdan_id,
dashou_id=dashou_id
).update(**update_fields)
if updated:
logger.info(f"更新评价信息: dingdan={dingdan_id}, dashou={dashou_id}, fields={update_fields}")
else:
logger.info(f"无对应记录,跳过评价更新: dingdan={dingdan_id}, dashou={dashou_id}")
else:
logger.info(f"结算时未传入任何评价字段,跳过更新")
else:
logger.warning(f"未知的行为类型: {xingwei}")
except IntegrityError as e:
# 唯一约束冲突get_or_create 理论上已处理,但并发时仍可能撞上)
logger.warning(f"表现记录写入冲突,可能并发创建: {e}")
except Exception as e:
# 无论发生什么错误,都不影响主流程
logger.error(f"记录打手表现异常: dingdan={dingdan_id}, dashou={dashou_id}, xingwei={xingwei}, error={e}", exc_info=True)

3
dengji/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

22
dengji/urls.py Normal file
View File

@@ -0,0 +1,22 @@
from django.urls import path
from .views import DashouBiaoqianView , BankuaiBiaoqianView, KaoheguanRegisterView, \
KaoheguanRefreshView , ShenheDatingModuleView, ShenheDatingDataView, ShenheQiangdanView, ShenheCaozuoView,ShenheXiangqingView, KaoheJinpaiTagsView, KaoheJiluView, KaoheFreeApplyView,\
KaoheJiluListView
urlpatterns = [
path('dsbqhq', DashouBiaoqianView.as_view(), name='打手标签获取'),
# 其他已有路由保持不变
path('bkqhbq', BankuaiBiaoqianView.as_view(), name='抢单页面获取标签'),
path('khgzc', KaoheguanRegisterView.as_view(), name='kaoheguan_register'),
path('khgsx', KaoheguanRefreshView.as_view(), name='kaoheguan_refresh'),
path('shdthqmk', ShenheDatingModuleView.as_view(), name='审核官获取板块'),
path('hqshsj', ShenheDatingDataView.as_view(), name='审核官获取待审核列表'),
path('shqd', ShenheQiangdanView.as_view(), name='审核官接待审核'),
path('hqkhxq', ShenheXiangqingView.as_view(), name='获取审核内容'),
path('khgxgkh', ShenheCaozuoView.as_view(), name='修改审核状态'),
path('dskhhq', KaoheJinpaiTagsView.as_view(), name='考核金牌页面标签'),
path('dskhjl', KaoheJiluView.as_view(), name='打手考核记录'),
path('dssqkh', KaoheFreeApplyView.as_view(), name='免费考核申请接口'),
path('khghqkhjl', KaoheJiluListView.as_view(), name='考核官获取考核记录'),
]

414
dengji/utils.py Normal file
View File

@@ -0,0 +1,414 @@
# dengji/utils.py
from .models import Chenghao, KaoheCishuFeiyong, ShenheJilu, KaoheJiluFeiyong, YonghuChenghao, KaoheguanBankuai
from yonghu.models import UserMain, UserShenheguan, UserDashou
from django.db import models
from decimal import Decimal
from django.db.models import Q, Max
import time
import random
import logging
from django.db.models import F, Max
logger = logging.getLogger('dengji')
# dingdan/utils.py
from .models import DingdanBiaoqian
from yonghu.models import UserMain
from dengji.models import YonghuChenghao
def check_dashou_biaoqian_required(order_id, dashou_yonghuid):
"""
检查打手是否满足订单的标签需求。
- 如果订单没有需求标签,直接放行。
- 如果有,则要求打手至少拥有其中一个标签。
返回 (allowed: bool, message: str)
"""
try:
# 1. 获取订单的需求标签ID集合
required_ids = set(
DingdanBiaoqian.objects.filter(dingdan_id=order_id)
.values_list('chenghao_id', flat=True)
)
if not required_ids:
return True, ''
# 2. 找到打手对应的用户记录
try:
user = UserMain.objects.get(yonghuid=dashou_yonghuid)
except UserMain.DoesNotExist:
return False, '用户信息异常'
# 3. 获取打手已拥有的标签ID集合只取打手类型
owned_ids = set(
YonghuChenghao.objects.filter(
yonghu=user,
chenghao__leixing='dashou'
).values_list('chenghao_id', flat=True)
)
if not owned_ids:
return False, '您未拥有任何打手标签,无法抢此订单'
# 4. 交集判断
if required_ids.intersection(owned_ids):
return True, ''
else:
return False, '您的标签不符合此订单要求,无法抢单'
except Exception as e:
# 标签校验失败不影响主流程,记录日志并保守放行(也可保守拒绝,按需求选择)
import logging
logger = logging.getLogger('dingdan')
logger.error(f"标签校验异常: {e}", exc_info=True)
# 安全起见,如果校验出错,选择拒绝,避免越权
return False, '订单标签校验异常,请稍后重试'
def get_tag_fee(chenghao, cishu):
"""
获取标签在指定考核次数下的费用。
- 若有对应次数的配置,直接返回。
- 若没有该次数,取该标签下已有最大次数的费用(作为后续次数的价格)。
- 若该标签完全没有费用记录,返回 0.00(免费)。
"""
fee_obj = KaoheCishuFeiyong.objects.filter(chenghao=chenghao, cishu=cishu).first()
if fee_obj:
return fee_obj.feiyong
max_obj = KaoheCishuFeiyong.objects.filter(chenghao=chenghao).order_by('-cishu').first()
if max_obj:
return max_obj.feiyong
return Decimal('0.00')
def generate_unique_jilu_id():
"""
生成唯一的审核记录 ID格式SH + 时间戳(10位) + 随机数(4位)
循环检查数据库避免重复,最多尝试 10 次。
"""
for _ in range(10):
timestamp = str(int(time.time() * 1000))[-10:]
random_part = str(random.randint(1000, 9999))
jilu_id = f'SH{timestamp}{random_part}'
if not ShenheJilu.objects.filter(jilu_id=jilu_id).exists():
return jilu_id
raise Exception('无法生成唯一记录ID请稍后重试')
def validate_shenheguan(shenheguan_id, bankuai, fee, applicant_user):
"""
校验指定考核官是否合法
- 考核官必须存在且未被禁用
- 必须属于对应板块
- 必须处于空闲状态
- 若费用>0则要求已认证
返回 (valid, error_msg)
"""
if not shenheguan_id:
return True, ''
try:
kg = UserShenheguan.objects.select_related('user').get(
user__yonghuid=shenheguan_id,
zhuangtai=1
)
except UserShenheguan.DoesNotExist:
return False, '考核官不存在或已被禁用'
# 板块权限
if not KaoheguanBankuai.objects.filter(kaoheguan_id=shenheguan_id, bankuai=bankuai).exists():
return False, '该考核官不属于此板块'
# 空闲状态
if kg.shenhe_zhuangtai != 0:
return False, '该考核官当前忙碌,无法指定'
# 费用>0需要认证
if float(fee) > 0 and not kg.is_renzheng:
return False, '费用大于0时只能指定已认证的考核官'
return True, ''
def create_shenhe_jilu(user, tag_id, game_nick, remark, reapply, jilu_id=None, shenheguan_id=''):
"""
创建或更新审核记录。
- 首次申请:创建新记录,生成唯一 jilu_id。
- 再次申请:基于原记录(必须状态为未通过)更新次数、累加总费用、更新备注和游戏昵称,
保留审核官信息,状态重置为待审核。不创建新记录。
- 每次申请均创建一条费用明细记录KaoheJiluFeiyong记录本次缴费。
- 若原记录有考核官,则将其状态改为审核中。
- 新增 shenheguan_id 参数指定考核官ID仅在原记录无考核官时可传入。
返回 (success, message, record)
"""
try:
# 1. 校验标签存在
try:
chenghao = Chenghao.objects.get(id=tag_id)
except Chenghao.DoesNotExist:
return False, '标签不存在', None
# 2. 判断用户是否已拥有该标签
if YonghuChenghao.objects.filter(yonghu=user, chenghao=chenghao).exists():
return False, '您已拥有该标签,无需重复考核', None
# 3. 检查是否有进行中的审核记录(待审核、审核中)
has_pending = ShenheJilu.objects.filter(
shenqingren_id=user.yonghuid,
chenghao=chenghao,
zhuangtai__in=[0, 3]
).exists()
if has_pending:
return False, '您有未完成的考核申请,请等待结果', None
last_record = None
current_fee = Decimal('0.00')
# 4. 处理再次申请
if reapply and jilu_id:
# 查询原记录(未通过状态)
last_record = ShenheJilu.objects.filter(
jilu_id=jilu_id,
shenqingren_id=user.yonghuid,
zhuangtai=2
).first()
if not last_record:
return False, '原记录不存在或状态不是未通过', None
new_cishu = last_record.cishu + 1
current_fee = get_tag_fee(chenghao, new_cishu)
# 确定最终状态:有考核官 = 审核中(0),否则待审核(3)
final_kg_id = last_record.shenheguan_id # 保留原来的
if shenheguan_id and not last_record.shenheguan_id:
final_kg_id = shenheguan_id # 新指定
final_zhuangtai = 0 if final_kg_id else 3 # 有考核官就是审核中
if final_zhuangtai == 3:
final_kg_id = None
# 原子化更新
ShenheJilu.objects.filter(pk=last_record.pk).update(
cishu=new_cishu,
jiaofei_jine=F('jiaofei_jine') + current_fee,
dashou_youxi_id=game_nick,
dashou_beizhu=remark,
guize_neirong=chenghao.kaohe_guize or '',
zhuangtai=final_zhuangtai,
shenheguan_id=final_kg_id,
)
last_record.refresh_from_db()
record = last_record
# 如果原考核官被替换或清除,释放旧考核官
if last_record.shenheguan_id != final_kg_id:
if last_record.shenheguan_id:
UserShenheguan.objects.filter(
user__yonghuid=last_record.shenheguan_id
).update(shenhe_zhuangtai=0)
else:
# 首次申请
max_cishu = ShenheJilu.objects.filter(
shenqingren_id=user.yonghuid,
chenghao=chenghao,
zhuangtai__in=[1, 2]
).aggregate(max=Max('cishu'))['max'] or 0
new_cishu = max_cishu + 1
current_fee = get_tag_fee(chenghao, new_cishu)
bankuai = chenghao.bankuai
rule_text = chenghao.kaohe_guize or ''
new_jilu_id = generate_unique_jilu_id()
initial_zhuangtai = 0 if shenheguan_id else 3 # 指定→审核中,否则待审核
record = ShenheJilu.objects.create(
jilu_id=new_jilu_id,
shenqingren_id=user.yonghuid,
zhuangtai=initial_zhuangtai,
bankuai=bankuai,
chenghao=chenghao,
cishu=new_cishu,
jiaofei_jine=current_fee,
dashou_youxi_id=game_nick,
dashou_beizhu=remark,
guize_neirong=rule_text,
shenheguan_id=shenheguan_id if shenheguan_id else None,
)
# 5. 创建费用明细(防重复)
KaoheJiluFeiyong.objects.get_or_create(
jilu=record,
cishu=record.cishu,
defaults={'jine': current_fee}
)
# 6. 更新考核官统计(仅当状态为审核中且有考核官)
if record.zhuangtai == 0 and record.shenheguan_id:
UserShenheguan.objects.filter(
user__yonghuid=record.shenheguan_id
).update(
shenhe_zongshu=F('shenhe_zongshu') + 1,
shenhe_zhuangtai=1
)
return True, '申请成功', record
except Exception as e:
logger.error(f"创建审核记录异常: {e}", exc_info=True)
return False, '系统异常', None
def create_shenhe_jilu_from_temp(user, temp_data):
"""
根据临时表数据创建审核记录(专供支付回调使用)
参数 temp_data 是 KaohePayTemp 对象
返回 (success, message, record)
"""
from .models import ShenheJilu, KaoheJiluFeiyong, Chenghao, YonghuChenghao
from django.db.models import F
import logging
logger = logging.getLogger('dengji')
try:
logger.info(f"开始从临时表创建审核记录: 用户={user.yonghuid}, 订单={temp_data.dingdan_id}")
chenghao = Chenghao.objects.get(id=temp_data.tag_id)
logger.info(f"获取标签成功: {chenghao.mingcheng}")
# 检查是否已拥有标签
if YonghuChenghao.objects.filter(yonghu=user, chenghao=chenghao).exists():
logger.warning(f"用户 {user.yonghuid} 已拥有标签 {chenghao.mingcheng}")
return False, '您已拥有该标签,无需重复考核', None
# 检查是否有进行中的审核
if ShenheJilu.objects.filter(
shenqingren_id=user.yonghuid,
chenghao=chenghao,
zhuangtai__in=[0, 3]
).exists():
logger.warning(f"用户 {user.yonghuid} 有未完成的考核申请")
return False, '您有未完成的考核申请,请等待结果', None
# 使用临时表中预计算好的次数和费用
new_cishu = temp_data.cishu
current_fee = temp_data.fee
reapply = temp_data.reapply
jilu_id = temp_data.jilu_id
shenheguan_id = temp_data.shenheguan_id
logger.info(f"临时表数据: cishu={new_cishu}, fee={current_fee}, reapply={reapply}, jilu_id={jilu_id}, shenheguan_id={shenheguan_id}")
# 再次申请校验
if reapply:
if not jilu_id:
logger.error("再次申请但未提供原记录ID")
return False, '再次申请必须提供原记录ID', None
last = ShenheJilu.objects.filter(
jilu_id=jilu_id,
shenqingren_id=user.yonghuid,
zhuangtai=2
).first()
if not last:
logger.error(f"原记录不存在或状态不是未通过: jilu_id={jilu_id}")
return False, '原记录不存在或状态不是未通过', None
if new_cishu != last.cishu + 1:
logger.error(f"考核次数不一致: 临时表={new_cishu}, 原记录次数={last.cishu}")
return False, '考核次数不一致', None
logger.info(f"再次申请校验通过原记录ID: {jilu_id}")
# 创建或更新审核记录
if reapply and jilu_id:
# 更新原记录
final_kg_id = last.shenheguan_id
if shenheguan_id and not last.shenheguan_id:
final_kg_id = shenheguan_id
final_zhuangtai = 0 if final_kg_id else 3
if final_zhuangtai == 3:
final_kg_id = None
ShenheJilu.objects.filter(pk=last.pk).update(
cishu=new_cishu,
jiaofei_jine=F('jiaofei_jine') + current_fee,
dashou_youxi_id=temp_data.game_nick,
dashou_beizhu=temp_data.remark,
guize_neirong=chenghao.kaohe_guize or '',
zhuangtai=final_zhuangtai,
shenheguan_id=final_kg_id,
)
last.refresh_from_db()
record = last
# 释放旧考核官
if last.shenheguan_id != final_kg_id and last.shenheguan_id:
UserShenheguan.objects.filter(user__yonghuid=last.shenheguan_id).update(shenhe_zhuangtai=0)
logger.info(f"释放旧考核官: {last.shenheguan_id}")
logger.info(f"更新审核记录成功: jilu_id={record.jilu_id}, 新次数={new_cishu}")
else:
# 首次申请
bankuai = chenghao.bankuai
rule_text = chenghao.kaohe_guize or ''
new_jilu_id = generate_unique_jilu_id()
initial_zhuangtai = 0 if shenheguan_id else 3
record = ShenheJilu.objects.create(
jilu_id=new_jilu_id,
shenqingren_id=user.yonghuid,
zhuangtai=initial_zhuangtai,
bankuai=bankuai,
chenghao=chenghao,
cishu=new_cishu,
jiaofei_jine=current_fee,
dashou_youxi_id=temp_data.game_nick,
dashou_beizhu=temp_data.remark,
guize_neirong=rule_text,
shenheguan_id=shenheguan_id if shenheguan_id else None,
)
logger.info(f"创建审核记录成功: jilu_id={new_jilu_id}, 状态={initial_zhuangtai}")
# 记录费用明细
fee_detail, created = KaoheJiluFeiyong.objects.get_or_create(
jilu=record,
cishu=record.cishu,
defaults={'jine': current_fee}
)
if created:
logger.info(f"创建费用明细成功: 记录ID={record.jilu_id}, 金额={current_fee}")
else:
logger.warning(f"费用明细已存在: 记录ID={record.jilu_id}, 次数={record.cishu}")
# 更新考核官状态
if record.zhuangtai == 0 and record.shenheguan_id:
UserShenheguan.objects.filter(user__yonghuid=record.shenheguan_id).update(
shenhe_zongshu=F('shenhe_zongshu') + 1,
shenhe_zhuangtai=1
)
logger.info(f"更新考核官状态: {record.shenheguan_id}, 审核总数+1, 状态=忙碌")
logger.info(f"从临时表创建审核记录完成: jilu_id={record.jilu_id}")
return True, '申请成功', record
except Chenghao.DoesNotExist:
logger.error(f"标签不存在: tag_id={temp_data.tag_id}")
return False, '标签不存在', None
except Exception as e:
logger.error(f"从临时表创建审核记录异常: {str(e)}", exc_info=True)
return False, '系统异常', None

975
dengji/views.py Normal file
View File

@@ -0,0 +1,975 @@
# dengji/views.py
import logging
import json
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from shangpin.models import ShangpinLeixing
from .models import YonghuChenghao, Chenghao, KaoheCishuFeiyong, KaoheJujueJilu
from .utils import get_tag_fee, create_shenhe_jilu
logger = logging.getLogger('dengji')
class DashouBiaoqianView(APIView):
"""获取当前打手所有称号标签"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
yonghu = request.user
# 查询用户的所有称号关联,一次连表取出
relations = YonghuChenghao.objects.filter(
yonghu=yonghu
).select_related('chenghao')
chenghao_list = []
for rel in relations:
ch = rel.chenghao
# 解析特效描述 JSON前端需要对象
texiao_json = {}
if ch.texiao_miaoshu:
try:
texiao_json = json.loads(ch.texiao_miaoshu)
except json.JSONDecodeError:
texiao_json = {}
chenghao_list.append({
'mingcheng': ch.mingcheng,
'tubiao_url': ch.tubiao_url or '',
'texiao_json': texiao_json,
})
return Response({
'code': 0,
'msg': '成功',
'data': {'chenghao_list': chenghao_list}
})
except Exception as e:
logger.error(f"获取打手标签失败: {e}", exc_info=True)
return Response({'code': 1, 'msg': '服务器错误', 'data': None})
from shangpin.models import ShangpinLeixing
from .models import YonghuChenghao, Chenghao
class BankuaiBiaoqianView(APIView):
"""获取商品类型所属板块的所有标签"""
permission_classes = [IsAuthenticated]
def post(self, request):
leixing_id = request.data.get('leixing_id')
if not leixing_id:
return Response({'code': 1, 'msg': '缺少leixing_id'})
try:
leixing = ShangpinLeixing.objects.select_related('bankuai').get(id=leixing_id)
except ShangpinLeixing.DoesNotExist:
return Response({'code': 2, 'msg': '商品类型不存在'})
bankuai = leixing.bankuai
if not bankuai:
return Response({'code': 0, 'data': {'biaoqian_list': []}})
# 获取该板块下所有类型的标签leixing可以为dashou/shangjia等这里先返回所有
chenghao_list = Chenghao.objects.filter(bankuai=bankuai)
data = []
for ch in chenghao_list:
texiao_json = {}
if ch.texiao_miaoshu:
try:
texiao_json = json.loads(ch.texiao_miaoshu)
except:
pass
data.append({
'id': ch.id,
'mingcheng': ch.mingcheng,
'texiao_json': texiao_json
})
return Response({'code': 0, 'data': {'biaoqian_list': data}})
# dengji/views.py
import logging
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from .models import KaoheguanBankuai, Bankuai
from django.db import transaction
from yonghu.models import UserShenheguan, UserMain, UserDashou
logger = logging.getLogger('dengji')
# 硬编码邀请码
KAOHEGUAN_INVITE_CODE = 'KHG2026VIP'
class KaoheguanRegisterView(APIView):
"""
考核官注册接口
POST /dengji/khgzc
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
yonghu = request.user
invite_code = request.data.get('inviteCode', '').strip()
if not invite_code:
return Response({'code': 400, 'msg': '邀请码不能为空'})
if invite_code != KAOHEGUAN_INVITE_CODE:
return Response({'code': 400, 'msg': '邀请码错误'})
kg = UserShenheguan.objects.filter(user=yonghu).first()
if kg:
if kg.zhuangtai == 1:
return Response({'code': 200, 'msg': '您已是考核官', 'data': self._build_data(kg)})
else:
return Response({'code': 400, 'msg': '账号已被封禁'})
with transaction.atomic():
kg = UserShenheguan.objects.create(
user=yonghu,
zhuangtai=1,
shenhe_zhuangtai=0,
shenhe_zongshu=0,
tongguo_zongshu=0,
yue=0.00,
zonge=0.00
)
return Response({'code': 200, 'msg': '注册成功', 'data': self._build_data(kg)})
except Exception as e:
logger.error(f"考核官注册失败: {e}", exc_info=True)
return Response({'code': 500, 'msg': '服务器内部错误'})
def _build_data(self, kg):
bankuai_qs = KaoheguanBankuai.objects.filter(kaoheguan_id=kg.user.yonghuid).select_related('bankuai')
bankuai_list = [{'bankuai_id': b.bankuai.bankuai_id, 'mingcheng': b.bankuai.mingcheng} for b in bankuai_qs]
return {
'zhuangtai': kg.zhuangtai,
'shenhe_zongshu': kg.shenhe_zongshu,
'tongguo_zongshu': kg.tongguo_zongshu,
'yue': str(kg.yue),
'zonge': str(kg.zonge),
'shenhe_zhuangtai': kg.shenhe_zhuangtai,
'bankuai_list': bankuai_list,
'kaoheguanstatus': 1
}
class KaoheguanRefreshView(APIView):
"""
考核官数据刷新接口
POST /dengji/khgsx
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
yonghu = request.user
kg = UserShenheguan.objects.filter(user=yonghu).first()
if not kg:
return Response({'code': 400, 'msg': '您还未注册考核官'})
if kg.zhuangtai != 1:
return Response({'code': 200, 'data': {'zhuangtai': kg.zhuangtai, 'kaoheguanstatus': 0}})
# 查询所属板块
bankuai_qs = KaoheguanBankuai.objects.filter(kaoheguan_id=kg.user.yonghuid).select_related('bankuai')
bankuai_list = [{'bankuai_id': b.bankuai.bankuai_id, 'mingcheng': b.bankuai.mingcheng} for b in bankuai_qs]
# 统计待审核 / 审核中的记录数量
pending_count = ShenheJilu.objects.filter(
shenheguan_id=yonghu.yonghuid,
zhuangtai=0 # 审核中
).count()
data = {
'zhuangtai': kg.zhuangtai,
'shenhe_zongshu': kg.shenhe_zongshu,
'tongguo_zongshu': kg.tongguo_zongshu,
'yue': str(kg.yue),
'zonge': str(kg.zonge),
'shenhe_zhuangtai': kg.shenhe_zhuangtai,
'bankuai_list': bankuai_list,
'kaoheguanstatus': 1,
'is_renzheng': kg.is_renzheng, # 🆕 是否认证
'pending_shenhe_count': pending_count # 🆕 审核中数量
}
return Response({'code': 200, 'data': data})
except Exception as e:
logger.error(f"考核官刷新失败: {e}", exc_info=True)
return Response({'code': 500, 'msg': '服务器内部错误'})
import json
from django.db import transaction, IntegrityError
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from .models import Bankuai, ShenheJilu, KaoheguanBankuai, Chenghao
class ShenheDatingModuleView(APIView):
"""审核大厅获取模块列表(板块 + 待审核数量 + 是否关联)"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
yonghu = request.user
related_ids = set(KaoheguanBankuai.objects.filter(kaoheguan_id=yonghu.yonghuid)
.values_list('bankuai_id', flat=True))
bankuai_list = []
for bk in Bankuai.objects.all():
count = ShenheJilu.objects.filter(bankuai=bk, zhuangtai=3, shenheguan_id__isnull=True).count()
bankuai_list.append({
'bankuai_id': bk.bankuai_id,
'mingcheng': bk.mingcheng,
'count': count,
'is_related': bk.bankuai_id in related_ids
})
return Response({'code': 0, 'data': {'modules': bankuai_list}})
except Exception as e:
return Response({'code': 500, 'msg': str(e)}, status=500)
class ShenheDatingDataView(APIView):
"""获取审核大厅待审核记录列表"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
data = request.data
bankuai_id = data.get('bankuai_id')
page = int(data.get('page', 1))
page_size = int(data.get('page_size', 5))
query = ShenheJilu.objects.filter(zhuangtai=3, shenheguan_id__isnull=True)
if bankuai_id:
query = query.filter(bankuai_id=bankuai_id)
total = query.count()
offset = (page - 1) * page_size
jilu_list = query.select_related('bankuai', 'chenghao').order_by('-create_time')[
offset:offset + page_size]
records = []
for jl in jilu_list:
user = UserMain.objects.filter(yonghuid=jl.shenqingren_id).first()
avatar = user.avatar if user else ''
texiao_json = {}
if jl.chenghao and jl.chenghao.texiao_miaoshu:
try:
texiao_json = json.loads(jl.chenghao.texiao_miaoshu)
except:
pass
# 新增字段:本次考核费用
kaohe_feiyong = float(jl.jiaofei_jine) if jl.jiaofei_jine is not None else 0.0
records.append({
'jilu_id': jl.jilu_id,
'shenqingren_id': jl.shenqingren_id,
'shenqingren_avatar': avatar,
'dashou_youxi_id': jl.dashou_youxi_id or '',
'dashou_beizhu': jl.dashou_beizhu or '',
'bankuai_id': jl.bankuai.bankuai_id if jl.bankuai else None,
'bankuai_mingcheng': jl.bankuai.mingcheng if jl.bankuai else '',
'chenghao_id': jl.chenghao_id,
'chenghao_mingcheng': jl.chenghao.mingcheng if jl.chenghao else '',
'chenghao_texiao_json': texiao_json,
'guize_neirong': jl.guize_neirong or '',
'kaohe_feiyong': kaohe_feiyong, # 新增
'create_time': jl.create_time.strftime('%Y-%m-%d %H:%M:%S') if jl.create_time else '',
})
has_more = (page * page_size) < total
return Response({'code': 0, 'data': {'list': records, 'has_more': has_more, 'total': total}})
except Exception as e:
return Response({'code': 500, 'msg': str(e)}, status=500)
class ShenheQiangdanView(APIView):
"""
审核官抢单(接审核任务)
校验:身份、账号封禁、已有进行中审核、记录状态、不能审核自己、板块权限
成功后更新考核官扩展表:审核总数+1审核状态设为“审核中”
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
yonghu = request.user
jilu_id = request.data.get('jilu_id')
if not jilu_id:
return Response({'code': 400, 'msg': '记录ID不能为空'})
# 1. 检查考核官身份及账号是否被禁用
kg = UserShenheguan.objects.filter(user=yonghu, zhuangtai=1).first()
if not kg:
return Response({'code': 403, 'msg': '您不是考核官或账号已被禁用'})
# 2. 检查是否有正在审核中的记录zhuangtai=0防止同时审核多个
ongoing = ShenheJilu.objects.filter(
shenheguan_id=yonghu.yonghuid, zhuangtai=0
).exists()
if ongoing:
return Response({'code': 400, 'msg': '您有正在审核中的任务,请完成后再接新任务'})
with transaction.atomic():
# 3. 行锁抢单:仅当状态为待审核(3)且无审核官时才能抢
jilu = ShenheJilu.objects.select_for_update().filter(
jilu_id=jilu_id,
zhuangtai=3,
shenheguan_id__isnull=True
).first()
if not jilu:
return Response({'code': 400, 'msg': '该审核任务已被接走、状态变更或不存在'})
# 4. 不能审核自己
if jilu.shenqingren_id == yonghu.yonghuid:
return Response({'code': 400, 'msg': '不能审核自己提交的申请'})
# 5. 板块权限:考核官必须属于该板块
is_related = KaoheguanBankuai.objects.filter(
kaoheguan_id=yonghu.yonghuid,
bankuai=jilu.bankuai
).exists()
if not is_related:
return Response({'code': 400, 'msg': '您不属于该板块,无法审核'})
# 新增6. 费用>0时要求考核官已认证
if jilu.jiaofei_jine and float(jilu.jiaofei_jine) > 0:
if not kg.is_renzheng:
return Response({'code': 400, 'msg': '本考核需要认证考核官接单,您尚未认证'})
# 7. 更新审核记录:分配给当前考核官,状态变为审核中
jilu.zhuangtai = 0 # 审核中
jilu.shenheguan_id = yonghu.yonghuid
jilu.save()
# 8. 更新考核官扩展表:审核总数 +1审核状态置为审核中1
UserShenheguan.objects.filter(user=yonghu).update(
shenhe_zongshu=F('shenhe_zongshu') + 1,
shenhe_zhuangtai=1
)
return Response({
'code': 0,
'msg': '抢单成功,请前往游戏内审核,审核完成后请到考核打分页面进行评定。',
'data': {'bankuai_id': jilu.bankuai.bankuai_id}
})
except Exception as e:
logger.error(f"审核官抢单异常: {str(e)}", exc_info=True)
return Response({'code': 500, 'msg': '服务器内部错误'}, status=500)
import json
import logging
from decimal import Decimal
from django.db import transaction, models
from django.db.models import F, Q, OuterRef, Subquery, Count, Max
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
class ShenheXiangqingView(APIView):
"""获取当前考核官审核中的记录详情(新增待收考核费字段)"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
yonghu = request.user
kg = UserShenheguan.objects.filter(user=yonghu, zhuangtai=1).first()
if not kg:
return Response({'code': 403, 'msg': '您不是考核官或账号异常'})
page = int(request.data.get('page', 1))
page_size = int(request.data.get('page_size', 10))
# 查询当前考核官名下状态为“审核中”的记录
base_query = ShenheJilu.objects.filter(
shenheguan_id=yonghu.yonghuid,
zhuangtai=0
).select_related('bankuai', 'chenghao')
total = base_query.count()
offset = (page - 1) * page_size
jilu_list = base_query[offset:offset + page_size]
records = []
for jl in jilu_list:
user = UserMain.objects.filter(yonghuid=jl.shenqingren_id).first()
avatar = user.avatar if user else ''
texiao_json = {}
if jl.chenghao and jl.chenghao.texiao_miaoshu:
try:
texiao_json = json.loads(jl.chenghao.texiao_miaoshu)
except:
pass
# 是否开启金牌
is_jinpai = jl.chenghao.kaioi_jinpai if jl.chenghao else False
# 本次待收考核费(基于记录中的当前次数,从费用明细表查询)
daishou_fei = 0.00
fee_detail = KaoheJiluFeiyong.objects.filter(
jilu=jl,
cishu=jl.cishu
).first()
if fee_detail:
daishou_fei = float(fee_detail.jine)
else:
# 兜底:若明细表无记录,尝试用通用费用表计算
daishou_fei = 0.00
# 上次失败原因
last_fail_reason = ''
last_fail = KaoheJujueJilu.objects.filter(
jilu=jl,
cishu=jl.cishu - 1
).order_by('-create_time').first()
if last_fail:
last_fail_reason = last_fail.yuanyin
records.append({
'jilu_id': jl.jilu_id,
'shenqingren_id': jl.shenqingren_id,
'shenqingren_avatar': avatar,
'bankuai_mingcheng': jl.bankuai.mingcheng if jl.bankuai else '',
'chenghao_mingcheng': jl.chenghao.mingcheng if jl.chenghao else '',
'chenghao_texiao_json': texiao_json,
'guize_neirong': jl.guize_neirong or '',
'dashou_beizhu': jl.dashou_beizhu or '',
'dashou_youxi_id': jl.dashou_youxi_id or '',
'shenheguan_youxi_id': jl.shenheguan_youxi_id or '',
'cishu': jl.cishu,
'daishou_fei': daishou_fei, # 新增:待收考核费
'last_fail_reason': last_fail_reason,
'is_jinpai': is_jinpai,
'create_time': jl.create_time.strftime('%Y-%m-%d %H:%M:%S') if jl.create_time else '',
})
has_more = (page * page_size) < total
return Response({'code': 0, 'data': {'list': records, 'has_more': has_more, 'total': total}})
except Exception as e:
return Response({'code': 500, 'msg': str(e)}, status=500)
class ShenheCaozuoView(APIView):
"""
考核官执行操作(同意/拒绝/转移/修改昵称),并更新考核官收益
- 只有“同意”或“拒绝”才增加余额和总额,金额严格从 KaoheJiluFeiyong 表获取
- 查不到费用则加 0 元,绝不影响流程
- 转移、修改昵称等操作不涉及收益
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
yonghu = request.user
# 1. 验证考核官身份及账号状态
kg = UserShenheguan.objects.filter(user=yonghu, zhuangtai=1).first()
if not kg:
return Response({'code': 403, 'msg': '账号异常,无法操作'})
jilu_id = request.data.get('jilu_id')
caozuo = request.data.get('caozuo') # agree / reject / transfer / modify_nick
if not jilu_id or not caozuo:
return Response({'code': 400, 'msg': '缺少必要参数'})
with transaction.atomic():
# 2. 锁定审核记录,确保属于当前考核官且状态为审核中(0)
jilu = ShenheJilu.objects.select_for_update().filter(
jilu_id=jilu_id,
shenheguan_id=yonghu.yonghuid,
zhuangtai=0
).first()
if not jilu:
return Response({'code': 400, 'msg': '该记录不存在或不属于您'})
# 3. 本次操作产生的收入(仅通过/拒绝时有意义)
daishou_fei = 0.00
# 4. 根据操作类型执行对应业务逻辑
if caozuo == 'agree':
# --- 通过考核 ---
# 查询本次考核应得的费用基于记录ID和当前次数
fee_detail = KaoheJiluFeiyong.objects.filter(
jilu=jilu,
cishu=jilu.cishu
).first()
if fee_detail:
daishou_fei = float(fee_detail.jine)
# 找不到就保持 0绝不使用任何默认值
jilu.zhuangtai = 1
jilu.save()
# 授予标签
if jilu.chenghao:
yh = UserMain.objects.get(yonghuid=jilu.shenqingren_id)
if not YonghuChenghao.objects.filter(yonghu=yh, chenghao=jilu.chenghao).exists():
YonghuChenghao.objects.create(yonghu=yh, chenghao=jilu.chenghao)
# 如果标签开启了金牌,同步更新打手称号
if jilu.chenghao.kaioi_jinpai:
dashou_prof = UserDashou.objects.filter(user=yh).first()
if dashou_prof:
dashou_prof.chenghao = '金牌选手'
dashou_prof.save()
elif caozuo == 'reject':
# --- 拒绝考核 ---
yuanyin = request.data.get('yuanyin', '')
if not yuanyin.strip():
return Response({'code': 400, 'msg': '请填写拒绝原因'})
# 查询本次考核应得的费用(拒绝时同样获得收入)
fee_detail = KaoheJiluFeiyong.objects.filter(
jilu=jilu,
cishu=jilu.cishu
).first()
if fee_detail:
daishou_fei = float(fee_detail.jine)
# 找不到就保持 0
jilu.zhuangtai = 2
jilu.save()
# 记录拒绝原因
KaoheJujueJilu.objects.create(
jilu=jilu,
cishu=jilu.cishu,
shenheguan_id=yonghu.yonghuid,
yuanyin=yuanyin
)
# 新增不合格时如果考核官未认证则清空考核官ID和游戏昵称
if not kg.is_renzheng:
jilu.shenheguan_id = None
jilu.shenheguan_youxi_id = ''
jilu.save(update_fields=['shenheguan_id', 'shenheguan_youxi_id'])
elif caozuo == 'transfer':
# 转移考核官:清空考核官信息,状态回退为待审核
jilu.shenheguan_id = None
jilu.shenheguan_youxi_id = None
jilu.zhuangtai = 3
jilu.save()
# 转移不更新收益,直接返回
elif caozuo == 'modify_nick':
# 修改考核官游戏昵称
youxi_id = request.data.get('youxi_id', '').strip()
if not youxi_id:
return Response({'code': 400, 'msg': '请输入游戏昵称'})
jilu.shenheguan_youxi_id = youxi_id
jilu.save()
return Response({'code': 0, 'msg': '修改成功'})
else:
return Response({'code': 400, 'msg': '无效的操作类型'})
# 5. 更新考核官收益(仅“通过”或“拒绝”时执行)
if caozuo in ('transfer'):
update_fields = {
'shenhe_zhuangtai': 0, # 设为空闲
}
UserShenheguan.objects.filter(user=yonghu).update(**update_fields)
if caozuo in ('agree', 'reject'):
update_fields = {
'yue': F('yue') + Decimal(str(daishou_fei)),
'zonge': F('zonge') + Decimal(str(daishou_fei)),
'shenhe_zhuangtai': 0, # 设为空闲
}
# 只有同意时才增加“通过总数”
if caozuo == 'agree':
update_fields['tongguo_zongshu'] = F('tongguo_zongshu') + 1
# 审核总数不再增加
UserShenheguan.objects.filter(user=yonghu).update(**update_fields)
return Response({'code': 0, 'msg': '操作成功'})
except Exception as e:
logger.error(f"考核操作异常: {str(e)}", exc_info=True)
return Response({'code': 500, 'msg': '服务器内部错误'}, status=500)
# dengji/views.py
class KaoheJinpaiTagsView(APIView):
"""获取考核金牌页面的标签和板块信息"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
user = request.user
# 获取用户已拥有的标签名称集合
owned_names = set(
YonghuChenghao.objects.filter(yonghu=user, chenghao__leixing='dashou')
.values_list('chenghao__mingcheng', flat=True)
)
# 板块列表
plate_list = []
for bk in Bankuai.objects.all():
tags = Chenghao.objects.filter(bankuai=bk, leixing='dashou')
tag_list = []
for tag in tags:
# 费用列表
fee_objs = KaoheCishuFeiyong.objects.filter(chenghao=tag).order_by('cishu')
fee_list = [{'cishu': f.cishu, 'feiyong': float(f.feiyong)} for f in fee_objs]
# 若没有记录,补充默认费用
if not fee_list:
fee_list = [{'cishu': 1, 'feiyong': 5.00}]
tag_list.append({
'id': tag.id,
'name': tag.mingcheng,
'texiao_json': json.loads(tag.texiao_miaoshu) if tag.texiao_miaoshu else {},
'jinpai': tag.kaioi_jinpai,
'rule': tag.kaohe_guize or '',
'fee_list': fee_list,
'owned': tag.mingcheng in owned_names,
})
if tag_list:
plate_list.append({
'bankuai_id': bk.bankuai_id,
'mingcheng': bk.mingcheng,
'tags': tag_list
})
# 新增:返回所有在职考核官信息(用于指定考核官)
kaoheguan_list = []
for kg in UserShenheguan.objects.filter(zhuangtai=1).select_related('user'):
uid = kg.user.yonghuid
avatar = kg.user.avatar or ''
# 获取考核官的打手昵称(从打手扩展表)
dashou_nick = ''
dashou_prof = UserDashou.objects.filter(user=kg.user).first()
if dashou_prof:
dashou_nick = dashou_prof.nicheng or ''
kaoheguan_list.append({
'yonghuid': uid,
'avatar': avatar,
'dashou_nick': dashou_nick,
'is_renzheng': kg.is_renzheng,
'shenhe_zhuangtai': kg.shenhe_zhuangtai,
})
return Response({
'code': 0,
'data': {
'plate_list': plate_list,
'my_tags': list(owned_names),
'kaoheguan_list': kaoheguan_list, # 新增
}
})
except Exception as e:
return Response({'code': 500, 'msg': str(e)}, status=500)
# dengji/utils.py
from .models import Chenghao, KaoheCishuFeiyong, ShenheJilu, KaoheJiluFeiyong, YonghuChenghao
from yonghu.models import UserMain
from django.db import models
class KaoheJiluView(APIView):
"""获取打手本人的考核记录(支持筛选状态)"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
user = request.user
status = request.data.get('status', 'pending') # pending / passed
page = int(request.data.get('page', 1))
page_size = int(request.data.get('page_size', 5))
if status == 'pending':
# 进行中/未通过:审核中(0)、待审核(3)、未通过(2)
query = ShenheJilu.objects.filter(
shenqingren_id=user.yonghuid,
zhuangtai__in=[0, 2, 3]
)
else:
query = ShenheJilu.objects.filter(
shenqingren_id=user.yonghuid,
zhuangtai=1 # 已通过
)
total = query.count()
records = query.order_by('-create_time')[(page-1)*page_size : page*page_size]
data = []
for jl in records:
# 汇总缴纳总额
total_fee = KaoheJiluFeiyong.objects.filter(jilu=jl).aggregate(
total=models.Sum('jine')
)['total'] or 0.00
# 上次拒绝原因(最近一条)
last_fail = KaoheJujueJilu.objects.filter(jilu=jl).order_by('-create_time').first()
fail_reason = last_fail.yuanyin if last_fail else ''
# 考核官头像
kg_user = UserMain.objects.filter(yonghuid=jl.shenheguan_id).first()
kg_avatar = kg_user.avatar if kg_user else ''
data.append({
'jilu_id': jl.jilu_id,
'chenghao_name': jl.chenghao.mingcheng if jl.chenghao else '',
'bankuai_name': jl.bankuai.mingcheng if jl.bankuai else '',
'zhuangtai': jl.zhuangtai,
'statusText': dict(ShenheJilu._meta.get_field('zhuangtai').choices).get(jl.zhuangtai, '未知'),
'statusColor': {0: '#FFA500', 1: '#27ae60', 2: '#e74c3c', 3: '#3498db'}.get(jl.zhuangtai, '#888'),
'shenheguan_id': jl.shenheguan_id,
'shenheguan_avatar': kg_avatar,
'dashou_youxi_id': jl.dashou_youxi_id,
'dashou_beizhu': jl.dashou_beizhu,
'cishu': jl.cishu,
'total_fee': float(total_fee),
'last_fail_reason': fail_reason,
'create_time': jl.create_time.strftime('%Y-%m-%d %H:%M:%S'),
# 为再次申请预留标签对象数据(前端可能用到)
'tag_obj': {
'id': jl.chenghao_id,
'name': jl.chenghao.mingcheng if jl.chenghao else '',
# 其他字段按需,但申请时会重新拉取
} if jl.chenghao else None,
})
has_more = (page * page_size) < total
return Response({
'code': 0,
'data': {'list': data, 'has_more': has_more, 'total': total}
})
except Exception as e:
return Response({'code': 500, 'msg': str(e)}, status=500)
from .utils import validate_shenheguan
class KaoheFreeApplyView(APIView):
"""免费申请考核(首次/再次费用为0时调用"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
user = request.user
tag_id = request.data.get('tag_id')
game_nick = request.data.get('game_nick', '').strip()
remark = request.data.get('remark', '').strip()
reapply = request.data.get('reapply', False)
jilu_id = request.data.get('jilu_id') if reapply else None
shenheguan_id = request.data.get('shenheguan_id', '').strip() # 新增
if not tag_id or not game_nick:
return Response({'code': 400, 'msg': '标签ID和游戏昵称必填'})
# 校验费用是否为0
try:
chenghao = Chenghao.objects.get(id=tag_id)
except Chenghao.DoesNotExist:
return Response({'code': 400, 'msg': '标签不存在'})
# 计算本次次数
if reapply and jilu_id:
last_record = ShenheJilu.objects.filter(
jilu_id=jilu_id, shenqingren_id=user.yonghuid, zhuangtai=2
).first()
if not last_record:
return Response({'code': 400, 'msg': '原记录不存在或状态不是未通过'})
new_cishu = last_record.cishu + 1
else:
max_cishu = ShenheJilu.objects.filter(
shenqingren_id=user.yonghuid, chenghao=chenghao, zhuangtai__in=[1,2]
).aggregate(max=models.Max('cishu'))['max'] or 0
new_cishu = max_cishu + 1
fee = get_tag_fee(chenghao, new_cishu)
if fee > 0:
return Response({'code': 400, 'msg': '您有未通过的记录请查看并重考'})
# 新增:如果指定了考核官,进行合法性校验
if shenheguan_id:
valid, err = validate_shenheguan(shenheguan_id, chenghao.bankuai, fee, user)
if not valid:
return Response({'code': 400, 'msg': err})
# 调用公共创建逻辑
success, msg, record = create_shenhe_jilu(
user, tag_id, game_nick, remark, reapply, jilu_id,
shenheguan_id=shenheguan_id # 新增参数
)
if success:
return Response({'code': 0, 'msg': msg, 'data': {'jilu_id': record.jilu_id}})
else:
return Response({'code': 400, 'msg': msg})
except Exception as e:
return Response({'code': 500, 'msg': str(e)}, status=500)
class KaoheJiluListView(APIView):
"""
获取当前考核官的历史审核记录
路径POST /dengji/khghqkhjl
权限JWT Token (考核官)
参数:
bankuai_id : int (可选0或空表示全部)
status : int (可选0-全部 1-审核中 2-已通过 3-未通过)
page : int (默认1)
page_size : int (默认5)
返回:
list, has_more, total
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
yonghu = request.user
# 1. 验证考核官身份及账号状态
kg = UserShenheguan.objects.filter(user=yonghu, zhuangtai=1).first()
if not kg:
return Response({'code': 403, 'msg': '您不是考核官或账号异常'})
# 2. 获取参数
bankuai_id = request.data.get('bankuai_id')
status = int(request.data.get('status', 0)) # 0全部 1审核中 2已通过 3未通过
page = int(request.data.get('page', 1))
page_size = int(request.data.get('page_size', 5))
# 3. 构建查询条件:只查当前考核官审核的记录
base_query = ShenheJilu.objects.filter(shenheguan_id=yonghu.yonghuid)
# 板块筛选
if bankuai_id and int(bankuai_id) > 0:
base_query = base_query.filter(bankuai_id=int(bankuai_id))
# 状态筛选前端传的状态映射到后端zhuangtai字段
status_map = {
0: None, # 全部不筛选
1: 0, # 审核中
2: 1, # 已通过
3: 2, # 未通过
}
if status_map.get(status) is not None:
base_query = base_query.filter(zhuangtai=status_map[status])
# 4. 计数与分页
total = base_query.count()
offset = (page - 1) * page_size
jilu_list = base_query.order_by('-create_time')[offset:offset + page_size]
# 5. 预取关联数据,减少查询次数
jilu_list = jilu_list.select_related('bankuai', 'chenghao')
# 为每条记录预取费用明细和拒绝记录(通过 related_name 或手动过滤)
# 由于 KaoheJiluFeiyong 和 KaoheJujueJilu 通过外键指向 ShenheJilu (to_field='jilu_id')
# 可以使用 prefetch_related
jilu_ids = [j.jilu_id for j in jilu_list]
fee_dict = {}
reject_dict = {}
if jilu_ids:
# 批量获取所有费用明细
fee_qs = KaoheJiluFeiyong.objects.filter(jilu_id__in=jilu_ids).order_by('cishu')
for fee in fee_qs:
if fee.jilu_id not in fee_dict:
fee_dict[fee.jilu_id] = []
fee_dict[fee.jilu_id].append({'cishu': fee.cishu, 'jine': float(fee.jine)})
# 批量获取所有拒绝记录
reject_qs = KaoheJujueJilu.objects.filter(jilu_id__in=jilu_ids).order_by('cishu')
for rej in reject_qs:
if rej.jilu_id not in reject_dict:
reject_dict[rej.jilu_id] = []
reject_dict[rej.jilu_id].append({
'cishu': rej.cishu,
'yuanyin': rej.yuanyin,
'create_time': rej.create_time.strftime('%Y-%m-%d %H:%M:%S') if rej.create_time else ''
})
# 6. 批量获取申请人信息(昵称和头像)
applicant_ids = {j.shenqingren_id for j in jilu_list}
user_map = {}
if applicant_ids:
users = UserMain.objects.filter(yonghuid__in=applicant_ids).select_related('dashou_profile')
for u in users:
avatar = u.avatar or ''
nicheng = ''
try:
# 打手扩展表可能有 nicheng
nicheng = u.dashou_profile.nicheng if u.dashou_profile else ''
except UserDashou.DoesNotExist:
pass
user_map[u.yonghuid] = {
'avatar': avatar,
'nicheng': nicheng or f'用户{u.yonghuid}'
}
# 7. 组装数据
records = []
for jl in jilu_list:
user_info = user_map.get(jl.shenqingren_id, {'avatar': '', 'nicheng': '未知'})
# 标签特效JSON
texiao_json = {}
if jl.chenghao and jl.chenghao.texiao_miaoshu:
try:
texiao_json = json.loads(jl.chenghao.texiao_miaoshu)
except:
pass
# 汇总总缴纳费用
fee_items = fee_dict.get(jl.jilu_id, [])
total_fee = sum(item['jine'] for item in fee_items)
records.append({
'jilu_id': jl.jilu_id,
'shenqingren_id': jl.shenqingren_id,
'shenqingren_avatar': user_info['avatar'],
'shenqingren_nicheng': user_info['nicheng'],
'bankuai_mingcheng': jl.bankuai.mingcheng if jl.bankuai else '',
'chenghao_mingcheng': jl.chenghao.mingcheng if jl.chenghao else '',
'chenghao_texiao_json': texiao_json,
'zhuangtai': jl.zhuangtai, # 0审核中 1通过 2未通过 3待审核
'cishu': jl.cishu,
'total_fee': total_fee,
'dashou_youxi_id': jl.dashou_youxi_id or '',
'dashou_beizhu': jl.dashou_beizhu or '',
'shenheguan_youxi_id': jl.shenheguan_youxi_id or '',
'guize_neirong': jl.guize_neirong or '',
'create_time': jl.create_time.strftime('%Y-%m-%d %H:%M:%S') if jl.create_time else '',
'update_time': jl.update_time.strftime('%Y-%m-%d %H:%M:%S') if jl.update_time else '',
'fee_list': fee_items,
'reject_list': reject_dict.get(jl.jilu_id, []),
})
has_more = (page * page_size) < total
return Response({
'code': 0,
'data': {
'list': records,
'has_more': has_more,
'total': total
}
})
except Exception as e:
logger.error(f"获取考核记录异常: {str(e)}", exc_info=True)
return Response({'code': 500, 'msg': '服务器内部错误'}, status=500)

0
dingdan/__init__.py Normal file
View File

3
dingdan/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

10
dingdan/apps.py Normal file
View File

@@ -0,0 +1,10 @@
from django.apps import AppConfig
class DingdanConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'dingdan'
def ready(self):
# 导入信号处理,确保信号被注册
import dingdan.signals

View File

@@ -0,0 +1,38 @@
# dingdan/cross_platform_tasks.py
import logging
import requests
from django.conf import settings
logger = logging.getLogger(__name__)
def send_partner_sync_order(partner_order_id, jine, dashou_fencheng, jieshao, beizhu, create_time):
"""
异步发送派单通知给对方平台使用简单线程非Celery
注意:此函数应在线程中调用,避免阻塞主请求。
生产环境建议使用Celery此处提供线程兼容版本。
"""
url = f"{settings.PARTNER_BASE_URL}/dingdan/partner_sync_order"
payload = {
'token': settings.CROSS_PLATFORM_TOKEN,
'partner_order_id': partner_order_id,
'jine': jine,
'dashou_fencheng': dashou_fencheng,
'jieshao': jieshao,
'beizhu': beizhu,
'create_time': create_time,
}
try:
response = requests.post(url, json=payload, timeout=5)
if response.status_code == 200:
data = response.json()
if data.get('code') == 0:
logger.info(f"跨平台派单同步成功: {partner_order_id}")
# 可在此处保存对方返回的 local_order_id 到本地订单(如果需要)
# local_order_id = data['data'].get('local_order_id')
# Dingdan.objects.filter(dingdan_id=partner_order_id).update(partner_order_id=local_order_id)
else:
logger.error(f"跨平台派单同步业务失败: {data}")
else:
logger.error(f"跨平台派单同步HTTP失败: {response.status_code}")
except Exception as e:
logger.error(f"跨平台派单同步异常: {str(e)}", exc_info=True)

View File

@@ -0,0 +1,205 @@
# Generated by Django 5.2.10 on 2026-01-14 19:59
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Lilubiao',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('lilu', models.DecimalField(decimal_places=4, max_digits=5, null=True, verbose_name='平台分成利率1是平台,2是商家')),
('fadanpingtai', models.CharField(max_length=10, null=True, verbose_name='订单所属平台,(商家或平台)')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'db_table': 'lilubiao',
},
),
migrations.CreateModel(
name='Ptliushui',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('zonge', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='收取总金额')),
('zhichuzonge', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='支出总金额')),
('zongshouyi', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='总收益')),
('rizonge', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='日收取')),
('rizhichu', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='日支出')),
('rishouyi', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='日收益')),
('zongshoucishu', models.IntegerField(default=0, verbose_name='总收取次数')),
('rishoucishu', models.IntegerField(default=0, verbose_name='日收取次数')),
('zongzhichucishu', models.IntegerField(default=0, verbose_name='总支出次数')),
('rizhichucishu', models.IntegerField(default=0, verbose_name='日支出次数')),
],
options={
'db_table': 'ptliushui',
},
),
migrations.CreateModel(
name='Chufajilu',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('dashouid', models.CharField(db_index=True, max_length=7, null=True, verbose_name='被罚打手ID')),
('qingqiuid', models.CharField(db_index=True, max_length=7, null=True, verbose_name='请求处罚ID')),
('chuliid', models.CharField(db_index=True, max_length=7, null=True, verbose_name='处理处罚ID')),
('cfliyou', models.TextField(blank=True, null=True, verbose_name='处罚理由')),
('sqzhuangtai', models.IntegerField(blank=True, default=0, null=True, verbose_name='处罚申请状态:0待处理,1成功,2驳回')),
('bhliyou', models.TextField(blank=True, null=True, verbose_name='驳回处罚理由')),
('jifen', models.IntegerField(default=0, verbose_name='扣除打手积分')),
('dingdan_id', models.CharField(db_index=True, max_length=32, null=True, verbose_name='订单ID')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'db_table': 'chufajilu',
'indexes': [models.Index(fields=['dashouid'], name='chufajilu_dashoui_07aac0_idx'), models.Index(fields=['qingqiuid'], name='chufajilu_qingqiu_ac160e_idx'), models.Index(fields=['chuliid'], name='chufajilu_chuliid_cd63ac_idx'), models.Index(fields=['dingdan_id'], name='chufajilu_dingdan_b8f0c2_idx')],
},
),
migrations.CreateModel(
name='Dashoutupian',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('dingdan_id', models.CharField(db_index=True, max_length=32, null=True, verbose_name='订单ID')),
('dashou', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='接单打手ID')),
('tupian', models.CharField(blank=True, max_length=500, null=True, verbose_name='打手提交图片URL')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'db_table': 'dashoutupian',
'indexes': [models.Index(fields=['dashou'], name='dashoutupia_dashou_7de8bd_idx'), models.Index(fields=['dingdan_id'], name='dashoutupia_dingdan_97c685_idx')],
},
),
migrations.CreateModel(
name='Dingdan',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('dingdan_id', models.CharField(db_index=True, max_length=32, unique=True, verbose_name='订单ID')),
('zhuangtai', models.IntegerField(blank=True, db_index=True, default=1, null=True, verbose_name='订单状态')),
('fadan_pingtai', models.IntegerField(blank=True, null=True, verbose_name='发单平台')),
('jine', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='订单金额')),
('dashou_fencheng', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='打手分成')),
('jiedan_dashou_id', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='接单打手ID')),
('dashou_liuyan', models.TextField(blank=True, null=True, verbose_name='打手留言')),
('zhiding_id', models.CharField(blank=True, max_length=32, null=True, verbose_name='指定ID')),
('shangpin_id', models.IntegerField(blank=True, null=True, verbose_name='商品ID')),
('leixing_id', models.PositiveIntegerField(blank=True, null=True, verbose_name='商品类型ID')),
('tupian', models.CharField(blank=True, max_length=500, null=True, verbose_name='商品图片URL')),
('yaoqiuleixing', models.PositiveIntegerField(blank=True, null=True, verbose_name='抢单要求类型')),
('huiyuan_id', models.CharField(blank=True, max_length=6, null=True, verbose_name='会员ID')),
('yongjin', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='佣金要求')),
('jieshao', models.TextField(blank=True, null=True, verbose_name='订单介绍')),
('beizhu', models.TextField(blank=True, null=True, verbose_name='订单备注')),
('nicheng', models.CharField(blank=True, max_length=50, null=True, verbose_name='游戏昵称')),
('user1_id', models.CharField(db_index=True, max_length=30, null=True, verbose_name='用户1标识')),
('user2_id', models.CharField(db_index=True, max_length=30, null=True, verbose_name='用户2打手标识')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'verbose_name': '订单',
'verbose_name_plural': '订单',
'db_table': 'dingdan',
'indexes': [models.Index(fields=['zhuangtai'], name='dingdan_zhuangt_2febe8_idx'), models.Index(fields=['dingdan_id'], name='dingdan_dingdan_83dcb6_idx'), models.Index(fields=['jiedan_dashou_id'], name='dingdan_jiedan__921bfa_idx')],
},
),
migrations.CreateModel(
name='Liaotian',
fields=[
('id', models.BigAutoField(primary_key=True, serialize=False, verbose_name='主键ID')),
('user1_id', models.CharField(db_index=True, max_length=30, verbose_name='用户1标识')),
('user2_id', models.CharField(db_index=True, max_length=30, verbose_name='用户2标识')),
('dingdan_id', models.CharField(max_length=32, unique=True, verbose_name='订单ID')),
('zhuangtai', models.IntegerField(choices=[(1, '开启中'), (0, '已关闭')], db_index=True, default=1, verbose_name='聊天状态')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'db_table': 'liaotian',
'indexes': [models.Index(fields=['user1_id'], name='liaotian_user1_i_6be6cd_idx'), models.Index(fields=['user2_id'], name='liaotian_user2_i_9f61a4_idx'), models.Index(fields=['zhuangtai'], name='liaotian_zhuangt_5e8be5_idx')],
},
),
migrations.CreateModel(
name='Pingfen',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('dashou', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='接单打手ID')),
('zongfen', models.PositiveIntegerField(default=0, verbose_name='总分')),
('cishu', models.PositiveIntegerField(default=0, verbose_name='评分次数')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'verbose_name': '评分表',
'db_table': 'pingfenbiao',
'indexes': [models.Index(fields=['dashou'], name='pingfenbiao_dashou_f62f10_idx')],
},
),
migrations.CreateModel(
name='Tuikuanjilu',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('dingdan_id', models.CharField(max_length=32, unique=True, verbose_name='订单ID')),
('dashouid', models.CharField(max_length=7, null=True, verbose_name='接单打手ID')),
('qingqiuid', models.CharField(max_length=7, null=True, verbose_name='请求用户ID')),
('chuliid', models.CharField(db_index=True, max_length=7, null=True, verbose_name='处理人ID')),
('liyou', models.TextField(blank=True, null=True, verbose_name='理由')),
('sqzhuangtai', models.IntegerField(blank=True, db_index=True, default=0, null=True, verbose_name='申请状态:0待处理,1成功,2驳回')),
('bhliyou', models.TextField(blank=True, null=True, verbose_name='驳回理由')),
('jine', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='订单金额')),
('dashou_fencheng', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='打手分成')),
('jieshao', models.TextField(blank=True, null=True, verbose_name='订单介绍')),
('beizhu', models.TextField(blank=True, null=True, verbose_name='订单备注')),
('nicheng', models.CharField(blank=True, max_length=50, null=True, verbose_name='游戏昵称')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'db_table': 'tuikuanjilu',
'indexes': [models.Index(fields=['chuliid'], name='tuikuanjilu_chuliid_0b0f16_idx'), models.Index(fields=['sqzhuangtai'], name='tuikuanjilu_sqzhuan_d89dc9_idx')],
},
),
migrations.CreateModel(
name='DingdanPingtai',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('laoban_id', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='老板ID')),
('laoban_pingjia', models.TextField(blank=True, null=True, verbose_name='老板评价')),
('dingdan', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='pingtai_kuozhan', to='dingdan.dingdan', verbose_name='订单')),
],
options={
'verbose_name': '平台订单扩展',
'verbose_name_plural': '平台订单扩展',
'db_table': 'dingdan_pingtai',
'indexes': [models.Index(fields=['laoban_id'], name='dingdan_pin_laoban__d55e6f_idx')],
},
),
migrations.CreateModel(
name='DingdanShangjia',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('shangjia_id', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='商家ID')),
('shangjia_pingjia', models.TextField(blank=True, null=True, verbose_name='商家评价')),
('cfliyou', models.TextField(blank=True, null=True, verbose_name='商家处罚理由')),
('sqzhuangtai', models.IntegerField(blank=True, default=0, null=True, verbose_name='处罚申请状态:0待处理,1成功,2驳回')),
('bhliyou', models.TextField(blank=True, null=True, verbose_name='驳回商家处罚理由')),
('sjnicheng', models.CharField(max_length=50, null=True, verbose_name='商家昵称')),
('dingdan', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='shangjia_kuozhan', to='dingdan.dingdan', verbose_name='订单')),
],
options={
'verbose_name': '商家订单扩展',
'verbose_name_plural': '商家订单扩展',
'db_table': 'dingdan_shangjia',
'indexes': [models.Index(fields=['shangjia_id'], name='dingdan_sha_shangji_64d28b_idx')],
},
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.27 on 2026-01-17 15:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='dingdan',
name='auto_expire_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='计划过期时间'),
),
migrations.AddField(
model_name='dingdan',
name='auto_task_id',
field=models.CharField(blank=True, default='', max_length=255, verbose_name='延时任务ID'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.27 on 2026-01-17 16:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0002_dingdan_auto_expire_at_dingdan_auto_task_id'),
]
operations = [
migrations.AddField(
model_name='dingdan',
name='pending_dispatch',
field=models.BooleanField(db_index=True, default=False, verbose_name='待调度标记'),
),
migrations.AddField(
model_name='dingdan',
name='status_8_time',
field=models.DateTimeField(blank=True, null=True, verbose_name='状态变为8的时间'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.27 on 2026-01-17 23:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0003_dingdan_pending_dispatch_dingdan_status_8_time'),
]
operations = [
migrations.AlterField(
model_name='tuikuanjilu',
name='chuliid',
field=models.CharField(db_index=True, max_length=11, null=True, verbose_name='处理人ID'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.27 on 2026-01-20 19:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0004_alter_tuikuanjilu_chuliid'),
]
operations = [
migrations.AlterField(
model_name='chufajilu',
name='chuliid',
field=models.CharField(db_index=True, max_length=11, null=True, verbose_name='处理处罚ID'),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.2.27 on 2026-02-07 19:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0005_alter_chufajilu_chuliid'),
]
operations = [
migrations.CreateModel(
name='Chufatupian',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('dingdan_id', models.CharField(db_index=True, max_length=32, null=True, verbose_name='订单ID')),
('yonghuid', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='用户ID')),
('tupian', models.CharField(blank=True, max_length=500, null=True, verbose_name='证据图片URL')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'db_table': 'chufatupian',
'indexes': [models.Index(fields=['yonghuid'], name='chufatupian_yonghui_71e585_idx'), models.Index(fields=['dingdan_id'], name='chufatupian_dingdan_ce0dd4_idx')],
},
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.27 on 2026-02-08 00:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0006_chufatupian'),
]
operations = [
migrations.AddField(
model_name='chufajilu',
name='ssliyou',
field=models.TextField(blank=True, null=True, verbose_name='打手申诉理由'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.27 on 2026-02-14 18:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0007_chufajilu_ssliyou'),
]
operations = [
migrations.AddField(
model_name='dingdan',
name='is_cross',
field=models.IntegerField(default=0, verbose_name='是否跨平台订单(0否,1是)'),
),
migrations.AddField(
model_name='dingdan',
name='partner_order_id',
field=models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='对方平台订单ID'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.27 on 2026-02-21 14:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0008_dingdan_is_cross_dingdan_partner_order_id'),
]
operations = [
migrations.AddField(
model_name='dingdanpingtai',
name='clkf',
field=models.CharField(db_index=True, max_length=20, null=True, verbose_name='处理客服'),
),
migrations.AddField(
model_name='dingdanpingtai',
name='jjjs_biaoshi',
field=models.IntegerField(default=0, verbose_name='拒绝结算标记(1表示拒绝结算)'),
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 4.2.27 on 2026-02-21 16:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0009_dingdanpingtai_clkf_dingdanpingtai_jjjs_biaoshi'),
]
operations = [
migrations.RemoveField(
model_name='dingdanpingtai',
name='clkf',
),
migrations.AddField(
model_name='dingdan',
name='clkf',
field=models.CharField(db_index=True, max_length=20, null=True, verbose_name='处理客服'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.27 on 2026-02-21 17:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0010_remove_dingdanpingtai_clkf_dingdan_clkf'),
]
operations = [
migrations.AddField(
model_name='dingdan',
name='tkly',
field=models.CharField(blank=True, max_length=500, null=True, verbose_name='退款/拒绝理由'),
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 4.2.27 on 2026-02-22 12:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0011_dingdan_tkly'),
]
operations = [
migrations.AddField(
model_name='dingdan',
name='wechat_transaction_id',
field=models.CharField(blank=True, db_index=True, max_length=64, null=True, verbose_name='微信支付订单号'),
),
migrations.AddIndex(
model_name='dingdan',
index=models.Index(fields=['wechat_transaction_id'], name='dingdan_wechat__278b79_idx'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.27 on 2026-02-22 19:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0012_dingdan_wechat_transaction_id_and_more'),
]
operations = [
migrations.AlterField(
model_name='chufajilu',
name='qingqiuid',
field=models.CharField(db_index=True, max_length=11, null=True, verbose_name='请求处罚ID'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.27 on 2026-03-24 17:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0013_alter_chufajilu_qingqiuid'),
]
operations = [
migrations.AddField(
model_name='dingdan',
name='dispatch_type',
field=models.IntegerField(choices=[(1, '我方派单'), (2, '对方派单')], default=0, verbose_name='派发情况'),
),
migrations.AddField(
model_name='dingdan',
name='partner_club_id',
field=models.CharField(blank=True, db_index=True, max_length=6, null=True, verbose_name='对方俱乐部ID'),
),
]

View File

@@ -0,0 +1,73 @@
# Generated by Django 4.2.27 on 2026-04-01 09:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0014_dingdan_dispatch_type_dingdan_partner_club_id'),
]
operations = [
migrations.CreateModel(
name='PreSettlement',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('dingdan_id', models.CharField(db_index=True, max_length=32, unique=True, verbose_name='我方订单ID')),
('partner_order_id', models.CharField(db_index=True, max_length=32, verbose_name='对方订单ID')),
('partner_club_id', models.CharField(db_index=True, max_length=6, verbose_name='对方俱乐部ID')),
('dashou_id', models.CharField(db_index=True, max_length=7, verbose_name='接单打手ID')),
('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='打手应分金额')),
('order_amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='订单原金额')),
('status', models.SmallIntegerField(choices=[(0, '待结算'), (1, '已结算'), (2, '已拒绝')], default=0, verbose_name='结算状态')),
('process_time', models.DateTimeField(blank=True, null=True, verbose_name='处理时间')),
('operator', models.CharField(blank=True, default='', max_length=32, verbose_name='操作人')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'verbose_name': '预结算表',
'verbose_name_plural': '预结算表',
'db_table': 'pre_settlement',
'indexes': [models.Index(fields=['dingdan_id'], name='pre_settlem_dingdan_4f3541_idx'), models.Index(fields=['partner_order_id'], name='pre_settlem_partner_45b29e_idx'), models.Index(fields=['partner_club_id'], name='pre_settlem_partner_d2c44c_idx'), models.Index(fields=['dashou_id'], name='pre_settlem_dashou__d5fe7c_idx'), models.Index(fields=['status'], name='pre_settlem_status_0aa711_idx')],
},
),
migrations.CreateModel(
name='OrderDashouHistory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('dingdan_id', models.CharField(db_index=True, max_length=32, verbose_name='订单ID')),
('dashou_id', models.CharField(db_index=True, max_length=7, verbose_name='打手ID')),
('times', models.PositiveSmallIntegerField(verbose_name='第几次接单从1开始')),
('operator', models.CharField(max_length=11, verbose_name='操作客服ID')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
],
options={
'verbose_name': '订单打手变更历史',
'verbose_name_plural': '订单打手变更历史',
'db_table': 'order_dashou_history',
'indexes': [models.Index(fields=['dingdan_id'], name='order_dasho_dingdan_1e79ab_idx'), models.Index(fields=['dashou_id'], name='order_dasho_dashou__0a637c_idx')],
},
),
migrations.CreateModel(
name='CrossPlatformOrderData',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('dingdan_id', models.CharField(db_index=True, max_length=32, unique=True, verbose_name='我方订单ID')),
('partner_order_id', models.CharField(db_index=True, max_length=32, verbose_name='对方平台订单ID')),
('partner_club_id', models.CharField(db_index=True, max_length=6, verbose_name='对方俱乐部ID')),
('partner_order_status', models.IntegerField(default=0, verbose_name='对方平台订单状态')),
('partner_dashou_id', models.CharField(blank=True, default='', max_length=32, verbose_name='对方平台接单打手ID')),
('partner_amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='对方平台订单金额')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'verbose_name': '跨平台订单扩展',
'verbose_name_plural': '跨平台订单扩展',
'db_table': 'cross_platform_order_data',
'indexes': [models.Index(fields=['dingdan_id'], name='cross_platf_dingdan_498d08_idx'), models.Index(fields=['partner_order_id'], name='cross_platf_partner_5d5615_idx'), models.Index(fields=['partner_club_id'], name='cross_platf_partner_1054e1_idx')],
},
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 4.2.27 on 2026-04-04 15:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0015_presettlement_orderdashouhistory_and_more'),
]
operations = [
migrations.AlterField(
model_name='presettlement',
name='dingdan_id',
field=models.CharField(db_index=True, max_length=32, verbose_name='我方订单ID'),
),
migrations.AlterUniqueTogether(
name='presettlement',
unique_together={('dingdan_id', 'dashou_id')},
),
]

View File

@@ -0,0 +1,32 @@
# Generated by Django 4.2.27 on 2026-04-23 14:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0016_alter_presettlement_dingdan_id_and_more'),
]
operations = [
migrations.AddField(
model_name='dingdanpingtai',
name='dianpu_id',
field=models.PositiveIntegerField(blank=True, db_index=True, null=True, verbose_name='所属店铺ID'),
),
migrations.AddField(
model_name='dingdanpingtai',
name='dianpu_shouyi',
field=models.DecimalField(blank=True, decimal_places=2, default=0.0, max_digits=10, null=True, verbose_name='店铺收益'),
),
migrations.AddField(
model_name='dingdanpingtai',
name='pingtai_shouyi',
field=models.DecimalField(blank=True, decimal_places=2, default=0.0, max_digits=10, null=True, verbose_name='平台收益'),
),
migrations.AddIndex(
model_name='dingdanpingtai',
index=models.Index(fields=['dianpu_id'], name='dingdan_pin_dianpu__9fbe27_idx'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.27 on 2026-04-30 02:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0017_dingdanpingtai_dianpu_id_and_more'),
]
operations = [
migrations.AddField(
model_name='dingdanshangjia',
name='partner_yonghu_id',
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='对方平台用户ID'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.27 on 2026-05-05 10:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0018_dingdanshangjia_partner_yonghu_id'),
]
operations = [
migrations.AddField(
model_name='dingdanpingtai',
name='shangji_dianpu_id',
field=models.PositiveIntegerField(blank=True, db_index=True, null=True, verbose_name='该订单的上级店铺ID'),
),
migrations.AddField(
model_name='dingdanpingtai',
name='shangji_fenhong',
field=models.DecimalField(blank=True, decimal_places=2, default=0.0, max_digits=10, null=True, verbose_name='上级分红金额'),
),
]

View File

@@ -0,0 +1,58 @@
# Generated by Django 4.2.27 on 2026-05-06 11:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0019_dingdanpingtai_shangji_dianpu_id_and_more'),
]
operations = [
migrations.CreateModel(
name='FadanFenhong',
fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='自增主键')),
('fenhongzhe_id', models.CharField(db_index=True, max_length=20, verbose_name='分红者ID')),
('zonge', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='分红总额')),
('ketixian_fenhong', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='可提现分红')),
('shenfen', models.IntegerField(choices=[(1, '商家'), (2, '客服'), (3, '平台管理者')], default=1, verbose_name='分红者身份')),
('fenhong_cishu', models.IntegerField(default=0, verbose_name='分红次数')),
('yingde_lilv', models.DecimalField(decimal_places=4, default=0.0, max_digits=5, verbose_name='分红应得利率')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'verbose_name': '罚款分红',
'verbose_name_plural': '罚款分红',
'db_table': 'fadan_fenhong',
'indexes': [models.Index(fields=['fenhongzhe_id'], name='fadan_fenhong_zhe_idx'), models.Index(fields=['shenfen'], name='fadan_fenhong_sf_idx')],
},
),
migrations.CreateModel(
name='Fadan',
fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='自增主键')),
('beichufa_id', models.CharField(db_index=True, max_length=20, verbose_name='被处罚用户ID')),
('shenqing_chufa', models.CharField(blank=True, max_length=20, null=True, verbose_name='申请处罚者ID')),
('shenfen', models.IntegerField(choices=[(1, '打手'), (2, '管事'), (3, '商家'), (4, '组长')], default=1, verbose_name='被处罚者身份')),
('chufaliyou', models.TextField(blank=True, null=True, verbose_name='处罚理由')),
('fakuanjine', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='罚款金额')),
('guanliandingdan_id', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='关联订单ID')),
('zhuangtai', models.IntegerField(choices=[(1, '待缴纳'), (2, '已缴纳'), (3, '申诉中'), (4, '已驳回')], db_index=True, default=1, verbose_name='罚单状态')),
('yingxiang_qiangdan', models.IntegerField(choices=[(1, '影响抢单'), (0, '不影响抢单')], db_index=True, default=1, verbose_name='是否影响抢单')),
('shensuliyou', models.TextField(blank=True, null=True, verbose_name='申诉理由')),
('tongyi_chufaliyou', models.TextField(blank=True, null=True, verbose_name='同意处罚理由')),
('bohuiliyou', models.TextField(blank=True, null=True, verbose_name='驳回理由')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'verbose_name': '罚单',
'verbose_name_plural': '罚单',
'db_table': 'fadan',
'indexes': [models.Index(fields=['beichufa_id', 'yingxiang_qiangdan', 'zhuangtai'], name='fadan_qiangdan_check'), models.Index(fields=['guanliandingdan_id'], name='fadan_dingdan_idx')],
},
),
]

View File

@@ -0,0 +1,30 @@
# Generated by Django 4.2.27 on 2026-05-07 11:15
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0020_fadanfenhong_fadan'),
]
operations = [
migrations.CreateModel(
name='FadanShensuTupian',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('beichufa_id', models.CharField(db_index=True, max_length=7, verbose_name='被处罚用户ID')),
('tupian_url', models.CharField(max_length=500, verbose_name='图片相对URL')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('fadan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shensu_tupians', to='dingdan.fadan', verbose_name='关联罚单')),
],
options={
'verbose_name': '罚款申诉图片',
'verbose_name_plural': '罚款申诉图片',
'db_table': 'fadan_shensu_tupian',
'indexes': [models.Index(fields=['fadan_id'], name='fadan_shens_fadan_i_78fab9_idx'), models.Index(fields=['beichufa_id'], name='fadan_shens_beichuf_59748e_idx')],
},
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.27 on 2026-05-07 16:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0021_fadanshensutupian'),
]
operations = [
migrations.AddField(
model_name='fadan',
name='partner_club_id',
field=models.CharField(blank=True, db_index=True, max_length=6, null=True, verbose_name='对方俱乐部ID'),
),
migrations.AddField(
model_name='fadan',
name='shenqingren_shenfen',
field=models.IntegerField(choices=[(1, '客服'), (2, '售后'), (3, '管理员')], default=1, verbose_name='申请处罚人身份'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.27 on 2026-05-07 17:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0022_fadan_partner_club_id_fadan_shenqingren_shenfen'),
]
operations = [
migrations.AddField(
model_name='fadanshensutupian',
name='yongtu',
field=models.IntegerField(choices=[(1, '处罚证据图片'), (2, '申诉图片')], default=2, verbose_name='图片用途'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.27 on 2026-05-07 19:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0023_fadanshensutupian_yongtu'),
]
operations = [
migrations.AddField(
model_name='fadan',
name='chulizhe',
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='处理者'),
),
migrations.AddField(
model_name='fadan',
name='chulizhe_shenfen',
field=models.IntegerField(choices=[(1, '客服'), (2, '售后'), (3, '管理员')], default=2, verbose_name='处理者身份'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.27 on 2026-05-10 00:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0024_fadan_chulizhe_fadan_chulizhe_shenfen'),
]
operations = [
migrations.AlterField(
model_name='fadan',
name='shenqingren_shenfen',
field=models.IntegerField(choices=[(1, '客服'), (2, '售后'), (3, '管理员'), (4, '店铺')], default=1, verbose_name='申请处罚人身份'),
),
]

View File

@@ -0,0 +1,37 @@
# Generated by Django 4.2.27 on 2026-05-10 17:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0025_alter_fadan_shenqingren_shenfen'),
]
operations = [
migrations.CreateModel(
name='FadanFenhongLilv',
fields=[
('shenfen', models.IntegerField(choices=[(1, '客服'), (2, '售后'), (3, '管理员'), (4, '店铺'), (5, '商家')], primary_key=True, serialize=False, verbose_name='申请处罚人身份')),
('lilv', models.DecimalField(decimal_places=4, default=0.0, max_digits=5, verbose_name='分红费率')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'verbose_name': '罚款分红费率配置',
'verbose_name_plural': '罚款分红费率配置',
'db_table': 'fadan_fenhong_lilv',
},
),
migrations.AlterField(
model_name='fadan',
name='shenqingren_shenfen',
field=models.IntegerField(choices=[(1, '客服'), (2, '售后'), (3, '管理员'), (4, '店铺'), (5, '商家')], default=1, verbose_name='申请处罚人身份'),
),
migrations.AlterField(
model_name='fadanfenhong',
name='shenfen',
field=models.IntegerField(choices=[(1, '商家'), (2, '客服'), (3, '平台管理者'), (4, '店铺'), (5, '售后')], default=1, verbose_name='分红者身份'),
),
]

View File

@@ -0,0 +1,42 @@
# Generated by Django 4.2.27 on 2026-05-22 15:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0026_fadanfenhonglilv_alter_fadan_shenqingren_shenfen_and_more'),
]
operations = [
migrations.CreateModel(
name='DingdanPeihu',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('dingdan_id', models.CharField(db_index=True, max_length=32, unique=True, verbose_name='订单ID')),
('zhudashou_id', models.CharField(db_index=True, max_length=32, verbose_name='主打手ID')),
('peihu1_id', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='陪护1打手ID')),
('peihu2_id', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='陪护2打手ID')),
('zhuangtai', models.IntegerField(default=1, verbose_name='陪护订单状态')),
('peihu_xuqiu_shuliang', models.IntegerField(default=1, verbose_name='陪护需求数量')),
('yongjin_yaoqiu', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='抢单佣金要求')),
('peihu1_dengji_xuqiu', models.PositiveIntegerField(blank=True, null=True, verbose_name='陪护1等级需求(称号ID)')),
('peihu2_dengji_xuqiu', models.PositiveIntegerField(blank=True, null=True, verbose_name='陪护2等级需求(称号ID)')),
('peihu1_zhadan_danze', models.CharField(blank=True, max_length=10, null=True, verbose_name='陪护1炸单担责比例')),
('peihu2_zhadan_danze', models.CharField(blank=True, max_length=10, null=True, verbose_name='陪护2炸单担责比例')),
('peihu1_fenhong', models.CharField(blank=True, max_length=10, null=True, verbose_name='陪护1分红比例')),
('peihu2_fenhong', models.CharField(blank=True, max_length=10, null=True, verbose_name='陪护2分红比例')),
('zhiding_peihu1', models.CharField(blank=True, max_length=32, null=True, verbose_name='指定陪护1 ID')),
('zhiding_peihu2', models.CharField(blank=True, max_length=32, null=True, verbose_name='指定陪护2 ID')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'verbose_name': '陪护订单',
'verbose_name_plural': '陪护订单',
'db_table': 'dingdan_peihu',
'indexes': [models.Index(fields=['zhudashou_id'], name='dingdan_pei_zhudash_583f4e_idx'), models.Index(fields=['peihu1_id'], name='dingdan_pei_peihu1__728279_idx'), models.Index(fields=['peihu2_id'], name='dingdan_pei_peihu2__77e45e_idx'), models.Index(fields=['zhuangtai'], name='dingdan_pei_zhuangt_22eaf6_idx')],
},
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.27 on 2026-05-29 17:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0027_dingdanpeihu'),
]
operations = [
migrations.AddField(
model_name='dingdanshangjia',
name='shangyige_dingdan_id',
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='上一个订单ID'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.27 on 2026-05-31 22:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0028_dingdanshangjia_shangyige_dingdan_id'),
]
operations = [
migrations.AddField(
model_name='fadanfenhong',
name='is_individual_rate',
field=models.BooleanField(default=False, verbose_name='是否单独开启分红利率'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.27 on 2026-06-01 00:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0029_fadanfenhong_is_individual_rate'),
]
operations = [
migrations.AddField(
model_name='fadan',
name='shenqingren_fenhong_jine',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='申请人分红金额'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.27 on 2026-06-12 01:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dingdan', '0030_fadan_shenqingren_fenhong_jine'),
]
operations = [
migrations.AddField(
model_name='dingdan',
name='guanshi_fencheng',
field=models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=10, null=True, verbose_name='管事分成'),
),
]

View File

588
dingdan/models.py Normal file
View File

@@ -0,0 +1,588 @@
from django.db import models
class Dingdan(models.Model):
"""订单主表"""
# 核心编号:唯一且不可为空[citation:3][citation:9]
dingdan_id = models.CharField(max_length=32, unique=True,db_index=True, verbose_name='订单ID')
# 状态与平台:用整数表示,具体含义由业务逻辑定义
zhuangtai = models.IntegerField(default=1, null=True, blank=True,db_index=True, verbose_name='订单状态')
fadan_pingtai = models.IntegerField(null=True, blank=True, verbose_name='发单平台')
# 金额相关
jine = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='订单金额')
dashou_fencheng = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='打手分成')
guanshi_fencheng = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, default=0, verbose_name='管事分成')
# 用户与商品关联ID
jiedan_dashou_id = models.CharField(max_length=32, null=True, db_index=True,blank=True, verbose_name='接单打手ID')
dashou_liuyan = models.TextField(null=True, blank=True, verbose_name='打手留言')
zhiding_id = models.CharField(max_length=32, null=True, blank=True, verbose_name='指定ID')
shangpin_id = models.IntegerField(null=True, blank=True, verbose_name='商品ID')
leixing_id = models.PositiveIntegerField(null=True, blank=True, verbose_name='商品类型ID') # 直接存数字
tupian = models.CharField(max_length=500, null=True, blank=True, verbose_name='商品图片URL')
yaoqiuleixing = models.PositiveIntegerField(null=True, blank=True, verbose_name='抢单要求类型')
huiyuan_id = models.CharField(max_length=6, null=True, blank=True, verbose_name='会员ID')
yongjin = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='佣金要求')
# 订单描述信息
jieshao = models.TextField(null=True, blank=True, verbose_name='订单介绍')
beizhu = models.TextField(null=True, blank=True, verbose_name='订单备注')
partner_order_id = models.CharField(max_length=32, null=True, blank=True, db_index=True,verbose_name='对方平台订单ID')
is_cross = models.IntegerField(default=0, verbose_name='是否跨平台订单(0否,1是)')
dispatch_type = models.IntegerField(default=0, choices=[(1, '我方派单'), (2, '对方派单')], verbose_name='派发情况')
# 🔥 新增对方俱乐部ID
partner_club_id = models.CharField(max_length=6, null=True, blank=True, db_index=True, verbose_name='对方俱乐部ID')
# 新增字段:游戏昵称(前端提交的游戏昵称)
nicheng = models.CharField(max_length=50, null=True, blank=True, verbose_name='游戏昵称')
user1_id = models.CharField(max_length=30,null=True, db_index=True, verbose_name='用户1标识')
user2_id = models.CharField(max_length=30, null=True,db_index=True, verbose_name='用户2打手标识')
#liaotian_pindao = models.CharField(max_length=255, blank=True, null=True, verbose_name='聊天频道名')
auto_task_id = models.CharField('延时任务ID', max_length=255, blank=True, default='')
auto_expire_at = models.DateTimeField('计划过期时间', null=True, blank=True)
pending_dispatch = models.BooleanField('待调度标记', default=False, db_index=True)
status_8_time = models.DateTimeField('状态变为8的时间', null=True, blank=True)
# 时间戳
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
clkf = models.CharField(max_length=20, null=True, db_index=True, verbose_name='处理客服')
tkly = models.CharField(max_length=500, blank=True, null=True, verbose_name='退款/拒绝理由')
wechat_transaction_id = models.CharField(
max_length=64,
blank=True,
null=True,
db_index=True,
verbose_name='微信支付订单号'
)
class Meta:
db_table = 'dingdan'
verbose_name = '订单'
verbose_name_plural = '订单'
# 为高频查询字段添加索引
indexes = [
models.Index(fields=['zhuangtai']),
models.Index(fields=['dingdan_id']),
models.Index(fields=['jiedan_dashou_id']),
models.Index(fields=['wechat_transaction_id']),
]
def __str__(self):
return self.dingdan_id
class DingdanPingtai(models.Model):
"""平台订单扩展表 (当 fadan_pingtai=1 时使用)"""
dingdan = models.OneToOneField(Dingdan, on_delete=models.CASCADE, related_name='pingtai_kuozhan', verbose_name='订单')
laoban_id = models.CharField(max_length=32, null=True, blank=True, db_index=True,verbose_name='老板ID')
laoban_pingjia = models.TextField(null=True, blank=True, verbose_name='老板评价')
jjjs_biaoshi = models.IntegerField(default=0, verbose_name='拒绝结算标记(1表示拒绝结算)')
# ========== 新增字段 ==========
pingtai_shouyi = models.DecimalField(max_digits=10,decimal_places=2,null=True,blank=True,default=0.00,verbose_name='平台收益')
dianpu_shouyi = models.DecimalField(max_digits=10,decimal_places=2, null=True,blank=True,default=0.00,verbose_name='店铺收益')
dianpu_id = models.PositiveIntegerField(null=True,blank=True,db_index=True,verbose_name='所属店铺ID')
# ---------- 上级分红快照 ----------
shangji_dianpu_id = models.PositiveIntegerField(null=True, blank=True, db_index=True,
verbose_name='该订单的上级店铺ID')
shangji_fenhong = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, default=0.00,
verbose_name='上级分红金额')
# =============================
class Meta:
db_table = 'dingdan_pingtai'
verbose_name = '平台订单扩展'
verbose_name_plural = '平台订单扩展'
indexes = [
models.Index(fields=['laoban_id']),
models.Index(fields=['dianpu_id']), # 为新字段加索引
]
def __str__(self):
return f"平台扩展-{self.dingdan.dingdan_id}"
class DingdanShangjia(models.Model):
"""商家发单扩展表 (当 fadan_pingtai=2 时使用)"""
dingdan = models.OneToOneField(Dingdan, on_delete=models.CASCADE, related_name='shangjia_kuozhan', verbose_name='订单')
shangjia_id = models.CharField(max_length=32, null=True, blank=True,db_index=True, verbose_name='商家ID')
shangjia_pingjia = models.TextField(null=True, blank=True, verbose_name='商家评价')
cfliyou = models.TextField(null=True, blank=True, verbose_name='商家处罚理由')
sqzhuangtai =models.IntegerField(default=0, null=True, blank=True, verbose_name='处罚申请状态:0待处理,1成功,2驳回')
bhliyou = models.TextField(null=True, blank=True, verbose_name='驳回商家处罚理由')
sjnicheng =models.CharField(max_length=50,null=True, verbose_name='商家昵称')
partner_yonghu_id = models.CharField(max_length=32, blank=True, null=True, verbose_name='对方平台用户ID')
# 在 dingdan/models.py 的 DingdanShangjia 类中添加
shangyige_dingdan_id = models.CharField(
max_length=32,
null=True,
blank=True,
verbose_name='上一个订单ID'
)
class Meta:
db_table = 'dingdan_shangjia'
verbose_name = '商家订单扩展'
verbose_name_plural = '商家订单扩展'
indexes = [
models.Index(fields=['shangjia_id']),
]
def __str__(self):
return f"商家扩展-{self.dingdan.dingdan_id}"
class DingdanPeihu(models.Model):
"""陪护订单表一个订单最多2个陪护"""
# 关联订单(一对一,陪护订单依附于原订单)
dingdan_id = models.CharField(max_length=32, unique=True, db_index=True, verbose_name='订单ID')
# 主打手ID冗余方便查询必须与订单的 jiedan_dashou_id 一致)
zhudashou_id = models.CharField(max_length=32, db_index=True, verbose_name='主打手ID')
# 实际接单陪护
peihu1_id = models.CharField(max_length=32, null=True, blank=True, db_index=True, verbose_name='陪护1打手ID')
peihu2_id = models.CharField(max_length=32, null=True, blank=True, db_index=True, verbose_name='陪护2打手ID')
# 订单状态1=待抢单,2=已抢待确认,3=进行中,4=已完成,5=已取消,7=指定状态...
zhuangtai = models.IntegerField(default=1, verbose_name='陪护订单状态')
# 需求配置
peihu_xuqiu_shuliang = models.IntegerField(default=1, verbose_name='陪护需求数量')
yongjin_yaoqiu = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='抢单佣金要求')
# 等级需求关联称号ID可空表示无限制
peihu1_dengji_xuqiu = models.PositiveIntegerField(null=True, blank=True, verbose_name='陪护1等级需求(称号ID)')
peihu2_dengji_xuqiu = models.PositiveIntegerField(null=True, blank=True, verbose_name='陪护2等级需求(称号ID)')
# 处罚与分红比例(格式:"1/5" 或 "2/5"
peihu1_zhadan_danze = models.CharField(max_length=10, null=True, blank=True, verbose_name='陪护1炸单担责比例')
peihu2_zhadan_danze = models.CharField(max_length=10, null=True, blank=True, verbose_name='陪护2炸单担责比例')
peihu1_fenhong = models.CharField(max_length=10, null=True, blank=True, verbose_name='陪护1分红比例')
peihu2_fenhong = models.CharField(max_length=10, null=True, blank=True, verbose_name='陪护2分红比例')
# 指定陪护可预设打手ID可为空
zhiding_peihu1 = models.CharField(max_length=32, null=True, blank=True, verbose_name='指定陪护1 ID')
zhiding_peihu2 = models.CharField(max_length=32, null=True, blank=True, verbose_name='指定陪护2 ID')
# 时间戳
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'dingdan_peihu'
verbose_name = '陪护订单'
verbose_name_plural = verbose_name
indexes = [
models.Index(fields=['zhudashou_id']),
models.Index(fields=['peihu1_id']),
models.Index(fields=['peihu2_id']),
models.Index(fields=['zhuangtai']),
]
# 建议路径dingdan/models.py 或 独立 fadan/models.py
from django.db import models
class Fadan(models.Model):
"""罚单表——平台各类处罚信息"""
id = models.AutoField(primary_key=True, verbose_name='自增主键')
beichufa_id = models.CharField(max_length=20, db_index=True, verbose_name='被处罚用户ID') # 直接用yonghuid后续可改为外键
shenqing_chufa = models.CharField(max_length=20, blank=True, null=True, verbose_name='申请处罚者ID')
shenfen = models.IntegerField(choices=[(1, '打手'), (2, '管事'), (3, '商家'), (4, '组长')], default=1, verbose_name='被处罚者身份')
chufaliyou = models.TextField(blank=True, null=True, verbose_name='处罚理由')
fakuanjine = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='罚款金额')
guanliandingdan_id = models.CharField(max_length=32, blank=True, null=True, db_index=True, verbose_name='关联订单ID')
zhuangtai = models.IntegerField(choices=[(1, '待缴纳'), (2, '已缴纳'), (3, '申诉中'), (4, '已驳回')], default=1, db_index=True, verbose_name='罚单状态')
yingxiang_qiangdan = models.IntegerField(choices=[(1, '影响抢单'), (0, '不影响抢单')], default=1, db_index=True, verbose_name='是否影响抢单')
shensuliyou = models.TextField(blank=True, null=True, verbose_name='申诉理由')
tongyi_chufaliyou = models.TextField(blank=True, null=True, verbose_name='同意处罚理由')
bohuiliyou = models.TextField(blank=True, null=True, verbose_name='驳回理由')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
# 在 Fadan 模型中添加
partner_club_id = models.CharField(max_length=6, blank=True, null=True, db_index=True, verbose_name='对方俱乐部ID')
# 在 class Fadan 中添加
shenqingren_shenfen = models.IntegerField(
choices=[(1, '客服'), (2, '售后'), (3, '管理员'),(4,'店铺'),(5,'商家')], # 按实际扩展
default=1,
verbose_name='申请处罚人身份'
)
chulizhe_shenfen = models.IntegerField(
choices=[(1, '客服'), (2, '售后'), (3, '管理员')],
default=2,
verbose_name='处理者身份'
)
chulizhe = models.CharField(max_length=20, blank=True, null=True, verbose_name='处理者')
# ========== 新增字段 ==========
shenqingren_fenhong_jine = models.DecimalField(
max_digits=12, decimal_places=2, default=0.00,
verbose_name='申请人分红金额'
)
class Meta:
db_table = 'fadan'
verbose_name = '罚单'
verbose_name_plural = '罚单'
indexes = [
models.Index(fields=['beichufa_id', 'yingxiang_qiangdan', 'zhuangtai'], name='fadan_qiangdan_check'),
models.Index(fields=['guanliandingdan_id'], name='fadan_dingdan_idx'),
]
def __str__(self):
return f"{self.beichufa_id} - 罚款{self.fakuanjine}元 - 状态{self.zhuangtai}"
# ==================== 罚款申诉图片表 ====================
class FadanShensuTupian(models.Model):
"""罚款申诉图片,关联罚单"""
fadan = models.ForeignKey(Fadan, on_delete=models.CASCADE, related_name='shensu_tupians', verbose_name='关联罚单')
beichufa_id = models.CharField(max_length=7, db_index=True, verbose_name='被处罚用户ID') # 冗余,便于直接按用户查
tupian_url = models.CharField(max_length=500, verbose_name='图片相对URL')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
# 在 FadanShensuTupian 模型中添加
yongtu = models.IntegerField(
choices=[(1, '处罚证据图片'), (2, '申诉图片')],
default=2,
verbose_name='图片用途'
)
class Meta:
db_table = 'fadan_shensu_tupian'
verbose_name = '罚款申诉图片'
verbose_name_plural = '罚款申诉图片'
indexes = [
models.Index(fields=['fadan_id']),
models.Index(fields=['beichufa_id']),
]
# 与 Fadan 放在同一个文件或独立的 fadan/models.py
class FadanFenhong(models.Model):
"""罚款分红表——记录罚款产生的分红信息"""
id = models.AutoField(primary_key=True, verbose_name='自增主键')
fenhongzhe_id = models.CharField(max_length=20, db_index=True, verbose_name='分红者ID')
zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='分红总额')
ketixian_fenhong = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='可提现分红')
shenfen = models.IntegerField(choices=[(1, '商家'), (2, '客服'), (3, '平台管理者'),(4,'店铺'),(5, '售后')], default=1, verbose_name='分红者身份')
fenhong_cishu = models.IntegerField(default=0, verbose_name='分红次数')
# ✅ 新增字段:是否单独开启分红利率(若开启,则使用本记录的 yingde_lilv否则使用全局配置
is_individual_rate = models.BooleanField(default=False, verbose_name='是否单独开启分红利率')
yingde_lilv = models.DecimalField(max_digits=5, decimal_places=4, default=0.0000, verbose_name='分红应得利率')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'fadan_fenhong'
verbose_name = '罚款分红'
verbose_name_plural = '罚款分红'
indexes = [
models.Index(fields=['fenhongzhe_id'], name='fadan_fenhong_zhe_idx'),
models.Index(fields=['shenfen'], name='fadan_fenhong_sf_idx'),
]
def __str__(self):
return f"{self.fenhongzhe_id} - 分红{self.ketixian_fenhong}"
class FadanFenhongLilv(models.Model):
shenfen = models.IntegerField(primary_key=True, choices=[(1, '客服'), (2, '售后'), (3, '管理员'), (4, '店铺'), (5, '商家')], verbose_name='申请处罚人身份')
lilv = models.DecimalField(max_digits=5, decimal_places=4, default=0.0000, verbose_name='分红费率')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'fadan_fenhong_lilv'
verbose_name = '罚款分红费率配置'
verbose_name_plural = verbose_name
def __str__(self):
return f"身份{self.shenfen} - 费率{self.lilv}"
class CrossPlatformOrderData(models.Model):
"""
跨平台订单扩展数据表
存储我方订单与对方平台订单的关联及对方订单状态
"""
dingdan_id = models.CharField(max_length=32, unique=True, db_index=True, verbose_name='我方订单ID')
partner_order_id = models.CharField(max_length=32, db_index=True, verbose_name='对方平台订单ID')
partner_club_id = models.CharField(max_length=6, db_index=True, verbose_name='对方俱乐部ID')
partner_order_status = models.IntegerField(default=0, verbose_name='对方平台订单状态') # 对方订单当前状态码
partner_dashou_id = models.CharField(max_length=32, blank=True, default='', verbose_name='对方平台接单打手ID')
partner_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='对方平台订单金额')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'cross_platform_order_data'
verbose_name = '跨平台订单扩展'
verbose_name_plural = '跨平台订单扩展'
indexes = [
models.Index(fields=['dingdan_id']),
models.Index(fields=['partner_order_id']),
models.Index(fields=['partner_club_id']),
]
def __str__(self):
return f"{self.dingdan_id} -> {self.partner_order_id}"
# apps/dingdan/models.py 中添加
class PreSettlement(models.Model):
"""
预结算表
用于记录对方平台派单订单的待结算款项,等待对方确认后才能真正入账
"""
STATUS_CHOICES = (
(0, '待结算'),
(1, '已结算'),
(2, '已拒绝'),
)
# 我方订单ID
dingdan_id = models.CharField(max_length=32, db_index=True, verbose_name='我方订单ID')
# 对方订单ID
partner_order_id = models.CharField(max_length=32, db_index=True, verbose_name='对方订单ID')
# 对方俱乐部ID
partner_club_id = models.CharField(max_length=6, db_index=True, verbose_name='对方俱乐部ID')
# 接单打手ID
dashou_id = models.CharField(max_length=7, db_index=True, verbose_name='接单打手ID')
# 打手应分金额
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='打手应分金额')
# 订单金额(原金额,可选)
order_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='订单原金额')
# 结算状态
status = models.SmallIntegerField(choices=STATUS_CHOICES, default=0, verbose_name='结算状态')
# 处理时间(结算或拒绝的时间)
process_time = models.DateTimeField(null=True, blank=True, verbose_name='处理时间')
# 处理客服/对方操作人(可选)
operator = models.CharField(max_length=32, blank=True, default='', verbose_name='操作人')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'pre_settlement'
verbose_name = '预结算表'
verbose_name_plural = verbose_name
unique_together = [['dingdan_id', 'dashou_id']]
indexes = [
models.Index(fields=['dingdan_id']),
models.Index(fields=['partner_order_id']),
models.Index(fields=['partner_club_id']),
models.Index(fields=['dashou_id']),
models.Index(fields=['status']),
]
def __str__(self):
return f"预结算-{self.dingdan_id} -> {self.partner_order_id}"
# apps/dingdan/models.py 中添加
class OrderDashouHistory(models.Model):
"""
订单打手变更历史表
记录订单每次更换打手的操作
"""
dingdan_id = models.CharField(max_length=32, db_index=True, verbose_name='订单ID')
dashou_id = models.CharField(max_length=7, db_index=True, verbose_name='打手ID')
times = models.PositiveSmallIntegerField(verbose_name='第几次接单从1开始')
operator = models.CharField(max_length=11, verbose_name='操作客服ID')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
class Meta:
db_table = 'order_dashou_history'
verbose_name = '订单打手变更历史'
verbose_name_plural = '订单打手变更历史'
indexes = [
models.Index(fields=['dingdan_id']),
models.Index(fields=['dashou_id']),
]
def __str__(self):
return f"{self.dingdan_id} -> {self.dashou_id}{self.times}"
class Dashoutupian(models.Model):
dingdan_id = models.CharField(max_length=32,db_index=True, null=True, verbose_name='订单ID')
dashou = models.CharField(max_length=32, null=True, blank=True,db_index=True, verbose_name='接单打手ID')
tupian = models.CharField(max_length=500, null=True, blank=True, verbose_name='打手提交图片URL')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'dashoutupian'
indexes = [
models.Index(fields=['dashou']),
models.Index(fields=['dingdan_id']),
]
class Pingfen(models.Model):
dashou = models.CharField(max_length=32, null=True, blank=True, db_index=True,verbose_name='接单打手ID')
zongfen = models.PositiveIntegerField(default=0, verbose_name='总分')
cishu = models.PositiveIntegerField(default=0, verbose_name='评分次数')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'pingfenbiao'
verbose_name = '评分表'
indexes = [
models.Index(fields=['dashou']),
]
def __str__(self):
return f"{self.dashou} - 平均分: {self.pingjun_fen():.2f}"
def pingjun_fen(self):
"""计算平均分"""
if self.cishu > 0:
return float(self.zongfen) / self.cishu
return 0.0
class Lilubiao(models.Model):
lilu = models.DecimalField(max_digits=5, decimal_places=4,null=True, verbose_name='平台分成利率1是平台,2是商家')
#sahngjialilu = models.DecimalField(max_digits=5, decimal_places=4,null=True, verbose_name='商家利率')
fadanpingtai = models.CharField(max_length=10,null=True,verbose_name='订单所属平台,(商家或平台)')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'lilubiao'
class Chufajilu(models.Model):
dashouid = models.CharField(max_length=7, null=True, db_index=True, verbose_name='被罚打手ID')
qingqiuid = models.CharField(max_length=11, null=True, db_index=True, verbose_name='请求处罚ID')
chuliid = models.CharField(max_length=11, null=True, db_index=True, verbose_name='处理处罚ID')
cfliyou = models.TextField(null=True, blank=True, verbose_name='处罚理由')
ssliyou = models.TextField(null=True, blank=True, verbose_name='打手申诉理由')
sqzhuangtai = models.IntegerField(default=0, null=True, blank=True, verbose_name='处罚申请状态:0待处理,1成功,2驳回')
bhliyou = models.TextField(null=True, blank=True, verbose_name='驳回处罚理由')
jifen = models.IntegerField(default=0, verbose_name='扣除打手积分')
dingdan_id = models.CharField(max_length=32, null=True, db_index=True, verbose_name='订单ID')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'chufajilu'
indexes = [
models.Index(fields=['dashouid']),
models.Index(fields=['qingqiuid']),
models.Index(fields=['chuliid']),
models.Index(fields=['dingdan_id']),
]
class Chufatupian(models.Model):
dingdan_id = models.CharField(max_length=32,db_index=True, null=True, verbose_name='订单ID')
yonghuid = models.CharField(max_length=32, null=True, blank=True,db_index=True, verbose_name='用户ID')
tupian = models.CharField(max_length=500, null=True, blank=True, verbose_name='证据图片URL')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'chufatupian'
indexes = [
models.Index(fields=['yonghuid']),
models.Index(fields=['dingdan_id']),
]
class Tuikuanjilu(models.Model):
dingdan_id = models.CharField(max_length=32, unique=True, verbose_name='订单ID')
dashouid = models.CharField(max_length=7, null=True, verbose_name='接单打手ID')
qingqiuid = models.CharField(max_length=7, null=True, verbose_name='请求用户ID')
chuliid = models.CharField(max_length=11, null=True, db_index=True, verbose_name='处理人ID')
liyou = models.TextField(null=True, blank=True, verbose_name='理由')
sqzhuangtai = models.IntegerField(default=0, null=True, blank=True,db_index=True, verbose_name='申请状态:0待处理,1成功,2驳回')
bhliyou = models.TextField(null=True, blank=True, verbose_name='驳回理由')
jine = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='订单金额')
dashou_fencheng = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='打手分成')
jieshao = models.TextField(null=True, blank=True, verbose_name='订单介绍')
beizhu = models.TextField(null=True, blank=True, verbose_name='订单备注')
# 新增字段:游戏昵称(前端提交的游戏昵称)
nicheng = models.CharField(max_length=50, null=True, blank=True, verbose_name='游戏昵称')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'tuikuanjilu'
indexes = [
models.Index(fields=['chuliid']),
models.Index(fields=['sqzhuangtai']),
]
class Ptliushui(models.Model):
zonge = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='收取总金额')
zhichuzonge = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='支出总金额')
zongshouyi = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='总收益')
rizonge = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='日收取')
rizhichu = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='日支出')
rishouyi = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='日收益')
zongshoucishu = models.IntegerField(default=0, verbose_name='总收取次数')
rishoucishu = models.IntegerField(default=0, verbose_name='日收取次数')
zongzhichucishu = models.IntegerField(default=0, verbose_name='总支出次数')
rizhichucishu = models.IntegerField(default=0, verbose_name='日支出次数')
class Meta:
db_table = 'ptliushui'
# models.py - ChatRelation 模型
class Liaotian(models.Model):
"""聊天关系表 - 极简设计,不设置唯一约束"""
# 主键
id = models.BigAutoField(primary_key=True, verbose_name='主键ID')
# 聊天双方的用户标识GoEasy的user_id
user1_id = models.CharField(max_length=30, db_index=True, verbose_name='用户1标识')
user2_id = models.CharField(max_length=30, db_index=True, verbose_name='用户2标识')
# 关联的订单(可以为空,但实际业务中应该关联)
dingdan_id = models.CharField(max_length=32, unique=True, verbose_name='订单ID')
# 状态与平台:用整数表示,具体含义由业务逻辑定义
zhuangtai = models.IntegerField(default=1,db_index=True,choices=[(1, '开启中'), (0, '已关闭')],verbose_name='聊天状态')
# 时间戳
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'liaotian'
indexes = [
models.Index(fields=['user1_id']),
models.Index(fields=['user2_id']),
models.Index(fields=['zhuangtai']),
]
def __str__(self):
return f'{self.user1_id}{self.user2_id} ({self.zhuangtai})'

614
dingdan/signals.py Normal file
View File

@@ -0,0 +1,614 @@
"""
阿龙电竞 - 信号处理器修复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)}")
# 不抛出异常,避免影响订单保存的主流程
# 信号处理失败不影响订单保存,有周期性检查作为备用方案'''

216
dingdan/tasks.py Normal file
View File

@@ -0,0 +1,216 @@
"""
阿龙电竞 - 订单定时任务
处理订单超时自动确认等逻辑
完整版本,包含所有必要的异常处理
"""
from celery import shared_task
from django.db import transaction
from django.db.models import F, Q
from django.conf import settings
from django.utils import timezone
from datetime import timedelta
from decimal import Decimal
import logging
# 导入模型 - 确保路径正确
from dingdan.models import Dingdan, DingdanShangjia
from yonghu.models import UserMain, UserDashou, UserShangjia
from utils.celery_utils import safe_decimal_operation, log_task_execution, rollback_on_failure
logger = logging.getLogger(__name__)
@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def process_expired_order(self, dingdan_id):
"""
处理单个超时订单(订单状态=8超过48小时未处理
"""
try:
# 使用select_for_update锁定记录防止并发修改
with transaction.atomic():
# 获取订单,同时锁定记录
order = Dingdan.objects.select_for_update().get(
dingdan_id=dingdan_id
)
# 其余代码不变...
# ... 保持原有代码
# 再次检查订单状态是否为8
if order.zhuangtai != 8:
logger.info(f"订单{dingdan_id}状态已不是8当前:{order.zhuangtai}),跳过处理")
return f"订单{dingdan_id}状态已变更,跳过处理"
# 获取发单平台类型
fadan_pingtai = order.fadan_pingtai
# 获取打手ID和分成金额
dashou_id = order.jiedan_dashou_id
dashou_fencheng = safe_decimal_operation(order.dashou_fencheng)
# 1. 更新订单状态为3已完成
order.zhuangtai = 3
order.save(update_fields=['zhuangtai'])
# 2. 更新打手信息(如果存在)
updated_dashou = False
if dashou_id:
try:
# 查询打手用户主表
dashou_user = UserMain.objects.filter(yonghuid=dashou_id).first()
if dashou_user:
# 获取打手扩展表
dashou_profile = getattr(dashou_user, 'dashou_profile', None)
if dashou_profile:
# 使用F表达式原子更新
UserDashou.objects.filter(pk=dashou_profile.pk).update(
chengjiaozongliang=F('chengjiaozongliang') + 1,
yue=F('yue') + dashou_fencheng,
zonge=F('zonge') + dashou_fencheng,
jinrishouyi=F('jinrishouyi') + dashou_fencheng,
jinyueshouyi=F('jinyueshouyi') + dashou_fencheng
)
updated_dashou = True
logger.info(f"更新打手{dashou_id}信息成功")
except Exception as e:
logger.error(f"更新打手信息失败打手ID: {dashou_id}, 错误: {str(e)}")
# 继续执行,不中断流程
# 3. 如果是商家发单,更新商家信息
updated_shangjia = False
if fadan_pingtai == 2: # 商家发单
try:
# 获取商家扩展信息
shangjia_kuozhan = getattr(order, 'shangjia_kuozhan', None)
if shangjia_kuozhan:
shangjia_id = shangjia_kuozhan.shangjia_id
if shangjia_id:
# 查询商家用户
shangjia_user = UserMain.objects.filter(yonghuid=shangjia_id).first()
if shangjia_user:
shangjia_profile = getattr(shangjia_user, 'shop_profile', None)
if shangjia_profile:
# 更新商家成交订单数量
UserShangjia.objects.filter(pk=shangjia_profile.pk).update(
chengjiao=F('chengjiao') + 1
)
updated_shangjia = True
logger.info(f"更新商家{shangjia_id}信息成功")
except Exception as e:
logger.error(f"更新商家信息失败订单ID: {dingdan_id}, 错误: {str(e)}")
# 继续执行,不中断流程
# 记录任务执行结果
result_msg = f"订单{dingdan_id}自动结算完成"
if updated_dashou:
result_msg += ",打手信息已更新"
if updated_shangjia:
result_msg += ",商家信息已更新"
if fadan_pingtai == 1:
try:
from dingdan.utils import update_platform_profit # 导入函数
update_platform_profit(dingdan_id)
except Exception as e:
# 平台收益更新失败不影响主流程,但记录日志
logger.error(f"强制结单后更新平台收益失败: {str(e)}")
log_task_execution(f"自动结算订单 {dingdan_id}", True, result_msg)
return result_msg
except Dingdan.DoesNotExist:
error_msg = f"订单{dingdan_id}不存在"
logger.warning(error_msg)
return error_msg
except Exception as e:
error_msg = f"处理订单{dingdan_id}时发生错误: {str(e)}"
logger.error(error_msg)
# 重试机制
if self.request.retries < self.max_retries:
logger.info(f"订单{dingdan_id}处理失败,准备第{self.request.retries + 1}次重试")
raise self.retry(exc=e)
log_task_execution(f"自动结算订单 {dingdan_id}", False, error_msg)
return error_msg
@shared_task
@rollback_on_failure("批量检查超时订单")
def check_order_expire_task():
"""
批量检查超时订单(补偿机制)
每5分钟执行一次作为信号处理的备用方案
查找状态为8且创建时间超过72小时的订单宽松时间防止误处理
"""
try:
# 计算更宽松的时间点当前时间减去72小时48+24小时
# 这样避免误处理刚变为状态8的订单
check_time = timezone.now() - timedelta(seconds=settings.ORDER_EXPIRE_SECONDS + 86400) # 48+24=72小时
# 查询状态为8且创建时间早于检查时间的订单
expired_orders = Dingdan.objects.filter(
zhuangtai=8,
create_time__lt=check_time
).values_list('dingdan_id', flat=True)
expired_count = expired_orders.count()
if expired_count == 0:
logger.info("补偿检查:无超时订单")
return {"success": True, "message": "无超时订单", "count": 0}
# 分批处理超时订单(避免一次处理太多)
batch_size = 10
processed_count = 0
for i in range(0, expired_count, batch_size):
batch = expired_orders[i:i+batch_size]
# 异步处理每个超时订单
for dingdan_id in batch:
process_expired_order.apply_async(
args=[dingdan_id],
queue='order_tasks',
priority=8 # 补偿任务的优先级低一些
)
processed_count += 1
result_msg = f"补偿检查:发现{expired_count}个可能超时的订单,已提交处理{processed_count}"
log_task_execution("批量检查超时订单", True, result_msg)
return {
"success": True,
"message": result_msg,
"total_count": expired_count,
"processed_count": processed_count
}
except Exception as e:
error_msg = f"批量检查超时订单失败: {str(e)}"
logger.error(error_msg)
log_task_execution("批量检查超时订单", False, error_msg)
return {
"success": False,
"message": error_msg,
"error": str(e)
}

3
dingdan/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

71
dingdan/tongzhi_tasks.py Normal file
View File

@@ -0,0 +1,71 @@
# dingdan/tongzhi_tasks.py
import logging
from concurrent.futures import ThreadPoolExecutor, as_completed
from a_long_dianjing.celery_app import app
from yonghu.models import OfficialAccountUser
from utils.weixin_broadcast import WeixinBroadcastSender
logger = logging.getLogger('weixin_broadcast')
@app.task(bind=True, max_retries=3, default_retry_delay=60)
def dingdan_guangbo(self, order_info):
"""
异步并发广播订单模板消息给所有关注服务号的用户
order_info 字典字段示例:
{
'dingdan_id': 'PT123456',
'game_type': '王者荣耀',
'amount': '88.00',
'order_desc': '星耀上王者'
}
"""
sender = WeixinBroadcastSender()
template_data = sender.build_order_message_data(order_info)
order_id = order_info.get('dingdan_id')
# 查询所有已关注用户
try:
openids = list(
OfficialAccountUser.objects
.filter(is_subscribed=True)
.values_list('official_openid', flat=True)
)
except Exception as e:
logger.error(f'查询订阅用户失败: {e}')
raise self.retry(exc=e)
if not openids:
return {'success': True, 'msg': '无订阅用户'}
total = len(openids)
logger.info(f'📊 Celery 广播订单 {order_id} 开始,目标人数: {total}')
success = 0
fail = 0
# 20 个线程并发发送,微信接口耗时 200-500ms1000 人约 15 秒完成
with ThreadPoolExecutor(max_workers=20) as executor:
future_map = {
executor.submit(sender.send_to_user, openid, template_data, order_id): openid
for openid in openids
}
for future in as_completed(future_map):
openid = future_map[future]
try:
res = future.result(timeout=30)
if res.get('success'):
success += 1
else:
fail += 1
except Exception as e:
fail += 1
logger.error(f'发送异常 {openid[:10]}...: {e}')
logger.info(f'🏁 广播完成:成功 {success},失败 {fail},总计 {total}')
return {
'success': True,
'total': total,
'success_count': success,
'fail_count': fail
}

91
dingdan/urls.py Normal file
View File

@@ -0,0 +1,91 @@
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from .views import CreateOrderView, WechatPayNotifyView, \
PaymentFailView, PaymentVerifyView, DingdanHuoquView, DingdanXiangqingView, JiedanView, \
ShangpinLeixingView, ShangjiaPaifaView, ShangjiaDingdanHuoquView, ShangjiaJiesuanView, \
ShangjiaDingdanXiangqingView, ShangjiaChufaShenqingView, ShangjiaTuikuanShenqingView, \
ShangjiaChexiaoView, DashouDingdanHuoquView, ShangpinLeixingHuoquView, QiangdanView, \
DashouDingdanHuoquView1, DashouCOSZhengshuView, DashouTijiaoView, DashouDingdanXiangqingView, AdGetDingdanXiangQing, \
AdTongYiTuiKuanPingTai, AdTongYiTuiKuanShangJia, AdQiangZhiJieDan, AdJuJueTuiKuan,\
AdGengHuanDaShou,ShangjiaCOSZhengshuView, DashouHuoquLeixingView,PartnerNotifyClaimView, PartnerSyncOrderView, PartnerCheckClaimView, PartnerRefundNotifyView,AdJuJueJieSuan, AdZhuanYiDaTing, AdQuXiaoZhiDing,DashouXiugaiView,DaiLiQunLiaoXiaoXiView,\
ShangjiaLianjieLiuYanZhuanFaView, DingdanXiangqingView2, JiedanView2, ZxsjghdsView, \
ShangjiaFakuanApplyView, ShangjiaFakuaiXiugaiView
urlpatterns = [
path('xiadan', CreateOrderView.as_view(), name='下单订单生成接口'),
#path('pay-notify/', WechatPayNotifyView.as_view(), name='老板下单微信支付回调'),
path('pay-notify/', csrf_exempt(WechatPayNotifyView.as_view()), name='老板下单微信支付回调'),
path('shibai', PaymentFailView.as_view(), name='老板支付失败接口'),
path('fucha', PaymentVerifyView.as_view(), name='老板支付成功复查接口'),
path('dingdanhuoqu', DingdanHuoquView.as_view(), name='老板订单获取'),
path('dingdanxiangqing', DingdanXiangqingView.as_view(), name='老板订单详情获取'),
path('jiedan', JiedanView.as_view(), name='老板结单'),
path('sjspleixing', ShangpinLeixingView.as_view(), name='派单获取商品类型'),
path('sjpaifa', ShangjiaPaifaView.as_view(), name='商家发单'),
path('sjdingdanhq', ShangjiaDingdanHuoquView.as_view(), name='商家订单获取'),
path('sjddxq', ShangjiaDingdanXiangqingView.as_view(), name='商家订单详情获取'),
path('sjjiesuan', ShangjiaJiesuanView.as_view(), name='商家结单'),
path('sjchexiao', ShangjiaChexiaoView.as_view(), name='商家撤销订单'),
path('sjtkshenqing', ShangjiaTuikuanShenqingView.as_view(), name='商家退款申请'),
path('sjchufa', ShangjiaChufaShenqingView.as_view(), name='商家处罚打手申请'),
path('ddlxhq', ShangpinLeixingHuoquView.as_view(), name='打手抢单页商品类型获取'),
path('dshqdingdan', DashouDingdanHuoquView1.as_view(), name='打手订单列表页面获取'),
# 获取订单列表
path('ddhq', DashouDingdanHuoquView.as_view(), name='打手抢单页获取订单'),
path('qiangdan', QiangdanView.as_view(), name='打手抢单'),
path('dsscpz', DashouCOSZhengshuView.as_view(), name='打手上传图片OSS参数获取接口'),
# 打手提交订单完成接口
path('dstijiao', DashouTijiaoView.as_view(), name='打手订单提交'),
# 打手订单详情获取接口
path('dsddxq', DashouDingdanXiangqingView.as_view(), name='打手获取订单详情'),
path('adddxq', AdGetDingdanXiangQing.as_view(), name='管理员获取订单详情页面具体信息'),
path('adddpttk', AdTongYiTuiKuanPingTai.as_view(), name='同意平台订单退款'),
# 商家订单退款接口(之前写的)
path('adddsjtk', AdTongYiTuiKuanShangJia.as_view(), name='同意商家退款'),
# 强制结单接口
#path('adddqzjd', AdQiangZhiJieDan.as_view(), name='强制结单'),
# 拒绝退款接口
path('adddjj', AdJuJueTuiKuan.as_view(), name='拒绝退款'),
# 更换打手接口
path('adddgh', AdGengHuanDaShou.as_view(), name='管理更换打手'),
path('sjscpz', ShangjiaCOSZhengshuView.as_view(), name='商家上传证据图片获取密钥'),
path('dsqdhqddlx', DashouHuoquLeixingView.as_view(), name='打手抢单页面获取订单类型'),
path('adddjjjs', AdJuJueJieSuan.as_view(), name='管理员拒绝给打手钱'),
#path('adddzydt', AdZhuanYiDaTing.as_view(), name='订单转移大厅'),
path('adddqxzd', AdQuXiaoZhiDing.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_notify_claim', csrf_exempt(PartnerNotifyClaimView.as_view()), name='东道主订单被抢通知接口'),
#path('partner_refund_notify', csrf_exempt(PartnerRefundNotifyView.as_view()), name='接受东道主退款通知'),
path('dsxiugaidd', DashouXiugaiView.as_view(), name='打手修改订单详情'),
path('kptxxfs', DaiLiQunLiaoXiaoXiView.as_view(), name='前端聊天转发'), # 群聊消息代理发送
path('sjljlbxxzf', ShangjiaLianjieLiuYanZhuanFaView.as_view(), name='商家链接老板聊天转发'),
path('dingdanxiangqing2', DingdanXiangqingView2.as_view(), name='老板获取订单详情新街口'),
path('jiedan2', JiedanView2.as_view(), name='老板结单新街口'),
path('zxsjghds', ZxsjghdsView.as_view(), name='商家更换打手'), # 商家更换打手
path('sjfksq', ShangjiaFakuanApplyView.as_view(), name='商家罚款'),
path('sjfkxgycx', ShangjiaFakuaiXiugaiView.as_view(), name='商家罚款和退款修改'),
]

443
dingdan/utils.py Normal file
View File

@@ -0,0 +1,443 @@
import threading
import requests
from django.conf import settings
from peizhi.models import Club
from shangpin.models import ShangpinLeixing
from dingdan.models import Dingdan # 根据实际路径导入
import logging
from datetime import date
from django.db import transaction
from django.db.models import F
from peizhi.models import DailyDispatchStat # 假设模型在 peizhi 应用中
logger = logging.getLogger(__name__)
# 可选:线程池限制(避免大量线程同时创建,建议使用 Celery这里简单用线程
THREAD_POOL_SIZE = 10
_semaphore = threading.Semaphore(THREAD_POOL_SIZE)
def sync_order_to_partners(dingdan_id):
"""
异步向所有开启互通的合作平台同步订单。
使用信号量限制并发线程数。
"""
def _task():
_semaphore.acquire()
try:
_sync_order_task(dingdan_id)
finally:
_semaphore.release()
thread = threading.Thread(target=_task)
thread.daemon = True
thread.start()
def _sync_order_task(dingdan_id):
"""实际同步任务(在线程中执行)"""
try:
# 获取订单对象(只取必要字段,提升性能)
dingdan = Dingdan.objects.only(
'dingdan_id', 'zhuangtai', 'jine', 'dashou_fencheng', 'jieshao',
'beizhu', 'nicheng', 'leixing_id', 'yaoqiuleixing', 'huiyuan_id',
'yongjin', 'zhiding_id', 'create_time'
).get(dingdan_id=dingdan_id)
# 基本条件判断
if dingdan.zhuangtai != 1:
logger.info(f"订单 {dingdan_id} 状态 {dingdan.zhuangtai} 不是1不同步")
return
if dingdan.zhiding_id:
logger.info(f"订单 {dingdan_id} 是指定单,不同步")
return
if dingdan.yaoqiuleixing != 1:
logger.info(f"订单 {dingdan_id} 抢单要求类型不是会员抢单,不同步")
return
if not dingdan.leixing_id:
logger.info(f"订单 {dingdan_id} 无商品类型,不同步")
return
try:
leixing = ShangpinLeixing.objects.get(id=dingdan.leixing_id)
if not leixing.is_cross_enabled:
logger.info(f"订单 {dingdan_id} 的商品类型未开启互通,不同步")
return
except ShangpinLeixing.DoesNotExist:
logger.info(f"订单 {dingdan_id} 的商品类型不存在,不同步")
return
# 查询所有开启互通的俱乐部
clubs = Club.objects.filter(is_interop_enabled=True).select_related()
if not clubs.exists():
return
# 为每个俱乐部发送订单
for club in clubs:
_send_to_single_partner(dingdan, club)
except Exception as e:
logger.error(f"同步订单任务异常: {e}", exc_info=True)
def _send_to_single_partner(dingdan, club):
"""向单个俱乐部发送订单(带重试,幂等处理)"""
# 计算实际金额:订单打手分成 × 对方费率
actual_amount = dingdan.dashou_fencheng * club.dashou_rate
# 构建请求数据
payload = {
'token': settings.CROSS_PLATFORM_TOKEN,
'our_club_id': club.club_id, # 我方在对方系统中的标识(存储于对方表的 partner_club_id
'partner_club_id': club.partner_club_id, # 对方自己的俱乐部ID
'partner_order_id': dingdan.dingdan_id, # 我方订单ID作为对方的外部订单号
'jine': str(actual_amount), # 对方平台应展示的订单金额
'dashou_fencheng': str(actual_amount), # 对方平台打手分成(与金额一致)
'jieshao': dingdan.jieshao,
'beizhu': dingdan.beizhu,
'create_time': dingdan.create_time.isoformat(),
'nicheng': dingdan.nicheng,
'leixing_id': dingdan.leixing_id, # 我方商品类型ID对方需要映射
'user_id': get_order_user_id(dingdan), # 对方平台下单用户ID不带前缀
}
# 重试机制最多3次指数退避
for attempt in range(1, 4):
try:
response = requests.post(
club.partner_domain + '/dingdan/partner_sync_order',
json=payload,
timeout=5
)
if response.status_code == 200:
data = response.json()
# 对方返回 code=0 表示成功(包括新建或已存在)
if data.get('code') == 0:
# 判断本次响应是新创建还是已存在
status = data.get('data', {}).get('status', '')
if status == 'existed':
# 订单已经存在于对方系统,不重复统计
logger.info(f"订单 {dingdan.dingdan_id} 在俱乐部 {club.club_id} 已存在,跳过统计")
else:
# 新创建成功,更新统计
try:
update_daily_dispatch_stat(
direction=1, # 我方派单
partner_club_id=club.partner_club_id,
dispatch_amount=actual_amount,
)
logger.info(f"成功同步订单 {dingdan.dingdan_id} 到俱乐部 {club.club_id}")
except Exception as e:
logger.error(f"更新派单统计失败: {e}", exc_info=True)
# 标记订单为跨平台(仅当第一次成功时标记,避免重复标记)
if not dingdan.is_cross:
Dingdan.objects.filter(dingdan_id=dingdan.dingdan_id).update(
is_cross=1,
dispatch_type=1 # 我方派单
)
logger.info(f"订单 {dingdan.dingdan_id} 已标记为跨平台订单")
break
else:
# 对方返回业务错误(如映射不存在等),不重试
logger.error(f"同步订单失败(业务): {data}")
break
else:
logger.error(f"同步订单HTTP失败: {response.status_code}")
except Exception as e:
logger.error(f"同步订单异常(尝试 {attempt}/3: {e}")
if attempt < 3:
import time
time.sleep(2 ** attempt) # 指数退避
else:
logger.error(f"同步订单 {dingdan.dingdan_id} 到俱乐部 {club.club_id} 最终失败")
# 获取下单方ID根据订单类型
def get_order_user_id(order):
from dingdan.models import DingdanPingtai, DingdanShangjia
if order.fadan_pingtai == 1:
try:
ext = DingdanPingtai.objects.get(dingdan=order)
return ext.laoban_id # 老板ID
except DingdanPingtai.DoesNotExist:
return None
elif order.fadan_pingtai == 2:
try:
ext = DingdanShangjia.objects.get(dingdan=order)
return ext.shangjia_id # 商家ID
except DingdanShangjia.DoesNotExist:
return None
return None
# 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,
dispatch_amount=None, dispatch_count=1,
claimed_amount=None, claimed_count=1,
success_amount=None, success_count=1):
"""
原子更新每日派单统计(支持派单、抢单、成交三种类型)。
只更新传递了非None参数的字段未传递的字段保持不变。
参数:
direction: 1=我方派单2=对方派单
partner_club_id: 对方俱乐部ID
dispatch_amount: 派单金额Decimal如果提供则累加到 dispatch_amount 和 dispatch_count
dispatch_count: 派单数量默认为1与 dispatch_amount 同时提供时生效
claimed_amount: 抢单金额Decimal如果提供则累加到 claimed_amount 和 claimed_count
claimed_count: 抢单数量默认为1与 claimed_amount 同时提供时生效
success_amount: 成交金额Decimal如果提供则累加到 success_amount 和 success_count
success_count: 成交数量默认为1与 success_amount 同时提供时生效
"""
today = date.today()
year, month, day = today.year, today.month, today.day
with transaction.atomic():
# 使用 select_for_update 锁定行,避免并发累加冲突
stat, created = DailyDispatchStat.objects.select_for_update().get_or_create(
date=today,
year=year,
month=month,
day=day,
direction=direction,
partner_club_id=partner_club_id,
defaults={
'dispatch_count': dispatch_count if dispatch_amount is not None else 0,
'dispatch_amount': dispatch_amount or Decimal('0.00'),
'claimed_count': claimed_count if claimed_amount is not None else 0,
'claimed_amount': claimed_amount or Decimal('0.00'),
'success_count': success_count if success_amount is not None else 0,
'success_amount': success_amount or Decimal('0.00'),
}
)
if not created:
update_fields = []
if dispatch_amount is not None:
stat.dispatch_count = F('dispatch_count') + dispatch_count
stat.dispatch_amount = F('dispatch_amount') + dispatch_amount
update_fields.extend(['dispatch_count', 'dispatch_amount'])
if claimed_amount is not None:
stat.claimed_count = F('claimed_count') + claimed_count
stat.claimed_amount = F('claimed_amount') + claimed_amount
update_fields.extend(['claimed_count', 'claimed_amount'])
if success_amount is not None:
stat.success_count = F('success_count') + success_count
stat.success_amount = F('success_amount') + success_amount
update_fields.extend(['success_count', 'success_amount'])
if update_fields:
stat.save(update_fields=update_fields)
logger.info(f"更新派单统计: 日期={today}, 方向={direction}, 俱乐部={partner_club_id}, "
f"派单+{dispatch_count if dispatch_amount else 0}单/{dispatch_amount or 0}, "
f"抢单+{claimed_count if claimed_amount else 0}单/{claimed_amount or 0}, "
f"成交+{success_count if success_amount else 0}单/{success_amount or 0}")
from peizhi.models import DailyIncomeStat
logger = logging.getLogger(__name__)
def update_daily_income(amount):
"""
原子更新当日收入统计金额累加笔数加1
用于微信支付成功回调。
参数:
amount: Decimal 本次收入金额
"""
today = date.today()
year, month, day = today.year, today.month, today.day
with transaction.atomic():
stat, created = DailyIncomeStat.objects.select_for_update().get_or_create(
date=today,
defaults={
'year': year,
'month': month,
'day': day,
'total_amount': amount,
'total_count': 1
}
)
if not created:
# 原子累加
stat.total_amount = F('total_amount') + amount
stat.total_count = F('total_count') + 1
stat.save(update_fields=['total_amount', 'total_count'])
logger.info(
f"每日收入更新: {today}, +{amount}元, 总金额={stat.total_amount if created else stat.total_amount + amount}, 总笔数={stat.total_count if created else stat.total_count + 1}")
from peizhi.models import DailyPayoutStat
logger = logging.getLogger(__name__)
def update_daily_payout(amount):
"""
原子更新当日出款统计金额累加笔数加1
用于提现成功、结算打款等场景。
参数:
amount: Decimal 本次出款金额
"""
today = date.today()
year, month, day = today.year, today.month, today.day
with transaction.atomic():
stat, created = DailyPayoutStat.objects.select_for_update().get_or_create(
date=today,
defaults={
'year': year,
'month': month,
'day': day,
'total_amount': amount,
'total_count': 1
}
)
if not created:
# 原子累加
stat.total_amount = F('total_amount') + amount
stat.total_count = F('total_count') + 1
stat.save(update_fields=['total_amount', 'total_count'])
logger.info(f"每日出款更新: {today}, +{amount}元, 总金额={stat.total_amount if created else stat.total_amount + amount}, 总笔数={stat.total_count if created else stat.total_count + 1}")
from decimal import Decimal
from dingdan.models import Lilubiao
def calc_shangjia_order_fencheng(jine):
"""
商家发单时计算打手/管事分成。
打手优先:打手+管事 > 订单金额时,管事分成为 0。
"""
jine = Decimal(str(jine))
try:
lilu_obj = Lilubiao.objects.get(fadanpingtai='3')
rate_dashou = Decimal(str(lilu_obj.lilu)) if lilu_obj.lilu and lilu_obj.lilu > 0 else Decimal('1')
except Lilubiao.DoesNotExist:
rate_dashou = Decimal('1')
lilu_guanshi_obj = Lilubiao.objects.filter(fadanpingtai='13').first()
rate_guanshi = (
Decimal(str(lilu_guanshi_obj.lilu))
if lilu_guanshi_obj and lilu_guanshi_obj.lilu is not None
else Decimal('0')
)
dashou_fencheng = (jine * rate_dashou).quantize(Decimal('0.01'))
guanshi_raw = (jine * rate_guanshi).quantize(Decimal('0.01'))
if dashou_fencheng + guanshi_raw > jine:
guanshi_fencheng = Decimal('0.00')
else:
guanshi_fencheng = guanshi_raw
return dashou_fencheng, guanshi_fencheng
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:
return
guanshi_fencheng = order.guanshi_fencheng or Decimal('0')
if guanshi_fencheng <= 0:
return
if Gsfenhong.objects.filter(dingdan_id=order.dingdan_id).exists():
logger.info(f"商家订单管事分红已处理: {order.dingdan_id}")
return
if not dashou_id:
return
try:
dashou = UserDashou.objects.select_related('user').get(user__yonghuid=dashou_id)
except UserDashou.DoesNotExist:
logger.info(f"商家订单管事分红跳过: 打手{dashou_id}不存在")
return
guanshi_id = dashou.yaoqingren
if not guanshi_id:
logger.info(f"商家订单管事分红跳过: 打手{dashou_id}无邀请管事")
return
try:
with transaction.atomic():
guanshi = UserGuanshi.objects.select_for_update().get(user__yonghuid=guanshi_id)
UserGuanshi.objects.filter(id=guanshi.id).update(
yue=F('yue') + guanshi_fencheng,
chongzhifenrun=F('chongzhifenrun') + guanshi_fencheng,
)
user_main = dashou.user
Gsfenhong.objects.create(
dingdan_id=order.dingdan_id,
guanshi=guanshi_id,
dashouid=dashou_id,
shuoming='商家订单分红',
fenhong=guanshi_fencheng,
avatar=user_main.avatar if user_main else None,
nicheng=dashou.nicheng or '未知打手',
fenhong_leixing=3,
)
logger.info(
f"商家订单管事分红成功: 订单{order.dingdan_id}, 管事{guanshi_id}, "
f"打手{dashou_id}, 金额{guanshi_fencheng}"
)
except UserGuanshi.DoesNotExist:
logger.warning(f"商家订单管事分红跳过: 管事{guanshi_id}不存在")
return
except Exception as e:
logger.error(f"商家订单管事分红失败: {e}", exc_info=True)
return
try:
from houtai.utils import update_guanshi_daily_by_action
update_guanshi_daily_by_action(
yonghuid=guanshi_id,
action=3,
amount=guanshi_fencheng,
)
except Exception as e:
logger.error(f"商家订单管事日统计更新失败: {e}")

11494
dingdan/views.py Normal file

File diff suppressed because it is too large Load Diff

52
gunicorn.conf.py Normal file
View File

@@ -0,0 +1,52 @@
# /opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.443/django/gunicorn.conf.py
# 🔥 绑定的IP和端口根据你的Nginx配置后端在8001端口
bind = "127.0.0.1:8001"
# 🔥 工作进程数根据CPU核心数调整建议CPU核心数*2+1
# 查看CPU核心数cat /proc/cpuinfo | grep "processor" | wc -l
workers = 4
# 🔥 工作模式使用gevent支持异步更高效
#worker_class = "gevent"
worker_class = "sync" # 改成这个
# 🔥 每个worker的最大请求数防止内存泄漏
max_requests = 1000
max_requests_jitter = 50 # 随机抖动避免所有worker同时重启
# 🔥 超时时间(秒)
timeout = 30
keepalive = 2
# 🔥 预加载应用(减少内存占用,加快请求处理)
preload_app = True
# 🔥 日志配置
# 访问日志输出到标准输出便于OnePanel查看
accesslog = "-"
# 错误日志:输出到标准错误
errorlog = "-"
# 日志级别
loglevel = "info"
# 🔥 进程名称(便于识别)
proc_name = "a_long_dianjing_gunicorn"
# 🔥 工作进程的用户/组安全考虑用非root用户运行
# user = "www-data"
# group = "www-data"
# 🔥 工作进程临时目录(避免权限问题)
worker_tmp_dir = "/dev/shm"
# 🔥 限制请求数据大小(防止大文件攻击)
limit_request_line = 4094
limit_request_fields = 100
limit_request_field_size = 8190
# 🔥 优雅退出时间
graceful_timeout = 30
# 🔥 进程守护如果用systemd管理这里设为False
daemon = False

0
houtai/_init_.py Normal file
View File

5
houtai/apps.py Normal file
View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class HoutaiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'houtai'

View File

@@ -0,0 +1,296 @@
# Generated by Django 4.2.27 on 2026-04-06 23:31
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="AbnormalUserLog",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"account_id",
models.CharField(
db_index=True, max_length=20, verbose_name="账号IDphone"
),
),
(
"ip_address",
models.GenericIPAddressField(
blank=True, null=True, verbose_name="IP地址"
),
),
(
"real_ip",
models.GenericIPAddressField(
blank=True, null=True, verbose_name="真实IP穿透代理后"
),
),
(
"x_forwarded_for",
models.CharField(
blank=True,
default="",
max_length=255,
verbose_name="X-Forwarded-For",
),
),
(
"user_agent",
models.CharField(
blank=True,
default="",
max_length=500,
verbose_name="User-Agent",
),
),
(
"request_method",
models.CharField(
blank=True, default="", max_length=10, verbose_name="请求方法"
),
),
(
"request_path",
models.CharField(
blank=True, default="", max_length=500, verbose_name="请求路径"
),
),
(
"request_params",
models.TextField(
blank=True, default="", verbose_name="请求参数GET/POST"
),
),
(
"request_body",
models.TextField(
blank=True, default="", verbose_name="请求体(原始)"
),
),
(
"request_headers",
models.TextField(
blank=True, default="", verbose_name="请求头JSON格式"
),
),
(
"attempt_type",
models.CharField(
blank=True, default="", max_length=50, verbose_name="攻击类型"
),
),
(
"detail",
models.TextField(blank=True, default="", verbose_name="详细描述"),
),
(
"create_time",
models.DateTimeField(auto_now_add=True, verbose_name="创建时间"),
),
],
options={
"verbose_name": "异常用户日志",
"verbose_name_plural": "异常用户日志",
"db_table": "abnormal_user_log",
},
),
migrations.CreateModel(
name="Permission",
fields=[
(
"perm_id",
models.AutoField(
primary_key=True, serialize=False, verbose_name="权限ID"
),
),
(
"perm_code",
models.CharField(
db_index=True,
max_length=100,
unique=True,
verbose_name="权限编码",
),
),
(
"perm_name",
models.CharField(max_length=100, verbose_name="权限名称"),
),
(
"description",
models.CharField(
blank=True, default="", max_length=200, verbose_name="权限描述"
),
),
(
"create_time",
models.DateTimeField(auto_now_add=True, verbose_name="创建时间"),
),
(
"update_time",
models.DateTimeField(auto_now=True, verbose_name="更新时间"),
),
],
options={
"verbose_name": "权限",
"verbose_name_plural": "权限",
"db_table": "permission",
},
),
migrations.CreateModel(
name="Role",
fields=[
(
"role_id",
models.AutoField(
primary_key=True, serialize=False, verbose_name="角色ID"
),
),
(
"role_code",
models.CharField(
db_index=True,
max_length=50,
unique=True,
verbose_name="角色编码",
),
),
("role_name", models.CharField(max_length=50, verbose_name="角色名称")),
(
"description",
models.CharField(
blank=True, default="", max_length=200, verbose_name="角色描述"
),
),
(
"create_time",
models.DateTimeField(auto_now_add=True, verbose_name="创建时间"),
),
(
"update_time",
models.DateTimeField(auto_now=True, verbose_name="更新时间"),
),
],
options={
"verbose_name": "角色",
"verbose_name_plural": "角色",
"db_table": "role",
},
),
migrations.CreateModel(
name="UserRole",
fields=[
("id", models.AutoField(primary_key=True, serialize=False)),
(
"account_id",
models.CharField(
db_index=True, max_length=11, verbose_name="账号ID"
),
),
(
"create_time",
models.DateTimeField(auto_now_add=True, verbose_name="创建时间"),
),
(
"update_time",
models.DateTimeField(auto_now=True, verbose_name="更新时间"),
),
(
"role",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="houtai.role",
verbose_name="角色",
),
),
],
options={
"verbose_name": "用户角色关联",
"verbose_name_plural": "用户角色关联",
"db_table": "user_role",
},
),
migrations.CreateModel(
name="RolePermission",
fields=[
("id", models.AutoField(primary_key=True, serialize=False)),
(
"create_time",
models.DateTimeField(auto_now_add=True, verbose_name="创建时间"),
),
(
"permission",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="houtai.permission",
verbose_name="权限",
),
),
(
"role",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="houtai.role",
verbose_name="角色",
),
),
],
options={
"verbose_name": "角色权限关联",
"verbose_name_plural": "角色权限关联",
"db_table": "role_permission",
},
),
migrations.AddIndex(
model_name="role",
index=models.Index(fields=["role_code"], name="role_role_co_b9f497_idx"),
),
migrations.AddIndex(
model_name="permission",
index=models.Index(
fields=["perm_code"], name="permission_perm_co_56cbe3_idx"
),
),
migrations.AddIndex(
model_name="abnormaluserlog",
index=models.Index(
fields=["account_id"], name="abnormal_us_account_425899_idx"
),
),
migrations.AddIndex(
model_name="abnormaluserlog",
index=models.Index(
fields=["ip_address"], name="abnormal_us_ip_addr_3d612b_idx"
),
),
migrations.AddIndex(
model_name="abnormaluserlog",
index=models.Index(
fields=["create_time"], name="abnormal_us_create__ee16da_idx"
),
),
migrations.AlterUniqueTogether(
name="userrole",
unique_together={("account_id", "role")},
),
migrations.AlterUniqueTogether(
name="rolepermission",
unique_together={("role", "permission")},
),
]

View File

@@ -0,0 +1,339 @@
# Generated by Django 4.2.27 on 2026-04-09 14:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("houtai", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="ZuzhangRiTongji",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"yonghuid",
models.CharField(
db_index=True, max_length=7, verbose_name="组长用户ID"
),
),
("riqi", models.DateField(db_index=True, verbose_name="日期")),
(
"yaoqing_guanshi_shu",
models.IntegerField(default=0, verbose_name="邀请管事总数"),
),
(
"shouru_zonge",
models.DecimalField(
decimal_places=2,
default=0.0,
max_digits=12,
verbose_name="收入总额(分佣)",
),
),
(
"fenyong_jine",
models.DecimalField(
decimal_places=2,
default=0.0,
max_digits=12,
verbose_name="分佣金额",
),
),
],
options={
"verbose_name": "组长每日统计",
"db_table": "zuzhang_ri_tongji",
"indexes": [
models.Index(
fields=["riqi", "-yaoqing_guanshi_shu"],
name="zuzhang_ri__riqi_87169e_idx",
),
models.Index(
fields=["riqi", "-shouru_zonge"],
name="zuzhang_ri__riqi_1d7e51_idx",
),
],
"unique_together": {("yonghuid", "riqi")},
},
),
migrations.CreateModel(
name="ShangjiaRiTongji",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"yonghuid",
models.CharField(
db_index=True, max_length=7, verbose_name="商家用户ID"
),
),
("riqi", models.DateField(db_index=True, verbose_name="日期")),
(
"paifa_dingdan_shu",
models.IntegerField(default=0, verbose_name="派发订单总数"),
),
(
"paifa_jine",
models.DecimalField(
decimal_places=2,
default=0.0,
max_digits=12,
verbose_name="派发订单总金额",
),
),
(
"jiesuan_dingdan_shu",
models.IntegerField(default=0, verbose_name="结算订单总数"),
),
(
"jiesuan_jine",
models.DecimalField(
decimal_places=2,
default=0.0,
max_digits=12,
verbose_name="结算总金额",
),
),
(
"tuikuan_dingdan_shu",
models.IntegerField(default=0, verbose_name="退款订单总数"),
),
(
"tuikuan_jine",
models.DecimalField(
decimal_places=2,
default=0.0,
max_digits=12,
verbose_name="退款总金额",
),
),
],
options={
"verbose_name": "商家每日统计",
"db_table": "shangjia_ri_tongji",
"indexes": [
models.Index(
fields=["riqi", "-paifa_jine"],
name="shangjia_ri_riqi_d00f7f_idx",
),
models.Index(
fields=["riqi", "-jiesuan_jine"],
name="shangjia_ri_riqi_25772a_idx",
),
models.Index(
fields=["riqi", "-paifa_dingdan_shu"],
name="shangjia_ri_riqi_e05c54_idx",
),
],
"unique_together": {("yonghuid", "riqi")},
},
),
migrations.CreateModel(
name="GuanshiXufeiRiTongji",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"yonghuid",
models.CharField(
db_index=True, max_length=7, verbose_name="管事用户ID"
),
),
("riqi", models.DateField(db_index=True, verbose_name="日期")),
(
"xufei_zongshu",
models.IntegerField(default=0, verbose_name="今日续费总数"),
),
(
"xufei_shouyi",
models.DecimalField(
decimal_places=2,
default=0.0,
max_digits=12,
verbose_name="续费收益金额",
),
),
],
options={
"verbose_name": "管事续费每日统计",
"db_table": "guanshi_xufei_ri_tongji",
"indexes": [
models.Index(
fields=["riqi", "-xufei_zongshu"],
name="guanshi_xuf_riqi_7c128f_idx",
),
models.Index(
fields=["riqi", "-xufei_shouyi"],
name="guanshi_xuf_riqi_8f09b5_idx",
),
],
"unique_together": {("yonghuid", "riqi")},
},
),
migrations.CreateModel(
name="GuanshiRiTongji",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"yonghuid",
models.CharField(
db_index=True, max_length=7, verbose_name="管事用户ID"
),
),
("riqi", models.DateField(db_index=True, verbose_name="日期")),
(
"yaoqing_dashou_shu",
models.IntegerField(default=0, verbose_name="邀请打手总数"),
),
(
"chongzhi_dashou_shu",
models.IntegerField(default=0, verbose_name="充值打手数量"),
),
(
"shouru_zonge",
models.DecimalField(
decimal_places=2,
default=0.0,
max_digits=12,
verbose_name="收入总额",
),
),
],
options={
"verbose_name": "管事每日统计",
"db_table": "guanshi_ri_tongji",
"indexes": [
models.Index(
fields=["riqi", "-yaoqing_dashou_shu"],
name="guanshi_ri__riqi_075e1a_idx",
),
models.Index(
fields=["riqi", "-chongzhi_dashou_shu"],
name="guanshi_ri__riqi_78a79d_idx",
),
models.Index(
fields=["riqi", "-shouru_zonge"],
name="guanshi_ri__riqi_334f52_idx",
),
],
"unique_together": {("yonghuid", "riqi")},
},
),
migrations.CreateModel(
name="DashouRiTongji",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"yonghuid",
models.CharField(
db_index=True, max_length=7, verbose_name="打手用户ID"
),
),
("riqi", models.DateField(db_index=True, verbose_name="日期")),
(
"jiedan_zongliang",
models.IntegerField(default=0, verbose_name="接单总量"),
),
(
"chengjiao_zongliang",
models.IntegerField(default=0, verbose_name="成交单总量"),
),
(
"jiedan_zonge",
models.DecimalField(
decimal_places=2,
default=0.0,
max_digits=12,
verbose_name="接单总金额",
),
),
(
"chengjiao_zonge",
models.DecimalField(
decimal_places=2,
default=0.0,
max_digits=12,
verbose_name="成交总金额",
),
),
(
"tuikuan_liang",
models.IntegerField(default=0, verbose_name="退款订单量"),
),
(
"tuikuan_jine",
models.DecimalField(
decimal_places=2,
default=0.0,
max_digits=12,
verbose_name="退款总金额",
),
),
],
options={
"verbose_name": "打手每日统计",
"db_table": "dashou_ri_tongji",
"indexes": [
models.Index(
fields=["riqi", "-jiedan_zongliang"],
name="dashou_ri_t_riqi_95a785_idx",
),
models.Index(
fields=["riqi", "-chengjiao_zongliang"],
name="dashou_ri_t_riqi_ad9c1a_idx",
),
models.Index(
fields=["riqi", "-jiedan_zonge"],
name="dashou_ri_t_riqi_5da703_idx",
),
models.Index(
fields=["riqi", "-chengjiao_zonge"],
name="dashou_ri_t_riqi_0a3156_idx",
),
],
"unique_together": {("yonghuid", "riqi")},
},
),
]

View File

@@ -0,0 +1,54 @@
# Generated by Django 4.2.27 on 2026-04-10 18:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("houtai", "0002_zuzhangritongji_shangjiaritongji_and_more"),
]
operations = [
migrations.CreateModel(
name="TixianRiTongji",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("riqi", models.DateField(db_index=True, verbose_name="日期")),
(
"leixing",
models.IntegerField(
choices=[(1, "打手"), (2, "管事"), (3, "组长")],
verbose_name="用户类型",
),
),
(
"total_amount",
models.DecimalField(
decimal_places=2,
default=0.0,
max_digits=12,
verbose_name="当天提现总额(元)",
),
),
],
options={
"verbose_name": "每日提现统计",
"db_table": "tixian_ri_tongji",
"indexes": [
models.Index(
fields=["riqi", "leixing"], name="tixian_ri_t_riqi_c18bb2_idx"
)
],
"unique_together": {("riqi", "leixing")},
},
),
]

View File

View File

269
houtai/models.py Normal file
View File

@@ -0,0 +1,269 @@
from django.db import models
# ==================== 1. 角色表 ====================
class Role(models.Model):
"""角色表"""
role_id = models.AutoField(primary_key=True, verbose_name='角色ID')
role_code = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='角色编码')
role_name = models.CharField(max_length=50, verbose_name='角色名称')
description = models.CharField(max_length=200, blank=True, default='', verbose_name='角色描述')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'role'
verbose_name = '角色'
verbose_name_plural = '角色'
indexes = [
models.Index(fields=['role_code']),
]
def __str__(self):
return self.role_name
# ==================== 2. 权限表 ====================
class Permission(models.Model):
"""权限表"""
perm_id = models.AutoField(primary_key=True, verbose_name='权限ID')
perm_code = models.CharField(max_length=100, unique=True, db_index=True, verbose_name='权限编码')
perm_name = models.CharField(max_length=100, verbose_name='权限名称')
description = models.CharField(max_length=200, blank=True, default='', verbose_name='权限描述')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'permission'
verbose_name = '权限'
verbose_name_plural = '权限'
indexes = [
models.Index(fields=['perm_code']),
]
def __str__(self):
return f"{self.perm_name} ({self.perm_code})"
# ==================== 3. 角色-权限关联表 ====================
class RolePermission(models.Model):
"""角色权限关联表"""
id = models.AutoField(primary_key=True)
role = models.ForeignKey(Role, on_delete=models.CASCADE, db_index=True, verbose_name='角色')
permission = models.ForeignKey(Permission, on_delete=models.CASCADE, db_index=True, verbose_name='权限')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
class Meta:
db_table = 'role_permission'
verbose_name = '角色权限关联'
verbose_name_plural = '角色权限关联'
unique_together = [['role', 'permission']] # 防止重复授权
def __str__(self):
return f"{self.role.role_name} -> {self.permission.perm_name}"
# ==================== 4. 用户-角色关联表 ====================
class UserRole(models.Model):
"""用户角色关联表"""
id = models.AutoField(primary_key=True)
account_id = models.CharField(max_length=11, db_index=True, verbose_name='账号ID') # 对应 UserMain 的 phone 字段
role = models.ForeignKey(Role, on_delete=models.CASCADE, db_index=True, verbose_name='角色')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'user_role'
verbose_name = '用户角色关联'
verbose_name_plural = '用户角色关联'
unique_together = [['account_id', 'role']] # 同一用户同一角色只能有一条
def __str__(self):
return f"{self.account_id} -> {self.role.role_name}"
class AbnormalUserLog(models.Model):
"""记录恶意尝试越权操作的用户信息"""
# 账号信息
account_id = models.CharField(max_length=20, db_index=True, verbose_name='账号IDphone')
# 网络信息
ip_address = models.GenericIPAddressField(null=True, blank=True, verbose_name='IP地址')
real_ip = models.GenericIPAddressField(null=True, blank=True, verbose_name='真实IP穿透代理后')
x_forwarded_for = models.CharField(max_length=255, blank=True, default='', verbose_name='X-Forwarded-For')
user_agent = models.CharField(max_length=500, blank=True, default='', verbose_name='User-Agent')
# 地理位置信息需借助第三方API这里只存储请求时的IP后续可离线解析
# 但我们可以存储更多请求细节
request_method = models.CharField(max_length=10, blank=True, default='', verbose_name='请求方法')
request_path = models.CharField(max_length=500, blank=True, default='', verbose_name='请求路径')
request_params = models.TextField(blank=True, default='', verbose_name='请求参数GET/POST')
request_body = models.TextField(blank=True, default='', verbose_name='请求体(原始)')
request_headers = models.TextField(blank=True, default='', verbose_name='请求头JSON格式')
# 攻击特征
attempt_type = models.CharField(max_length=50, blank=True, default='', verbose_name='攻击类型')
detail = models.TextField(blank=True, default='', verbose_name='详细描述')
# 时间戳
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
class Meta:
db_table = 'abnormal_user_log'
verbose_name = '异常用户日志'
verbose_name_plural = verbose_name
indexes = [
models.Index(fields=['account_id']),
models.Index(fields=['ip_address']),
models.Index(fields=['create_time']),
]
def __str__(self):
return f"{self.account_id} @ {self.ip_address} - {self.create_time}"
class ShangjiaRiTongji(models.Model):
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='商家用户ID')
riqi = models.DateField(db_index=True, verbose_name='日期') # 年月日
# 派发统计
paifa_dingdan_shu = models.IntegerField(default=0, verbose_name='派发订单总数')
paifa_jine = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='派发订单总金额')
# 结算统计(成交)
jiesuan_dingdan_shu = models.IntegerField(default=0, verbose_name='结算订单总数')
jiesuan_jine = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='结算总金额')
# 退款统计(可选)
tuikuan_dingdan_shu = models.IntegerField(default=0, verbose_name='退款订单总数')
tuikuan_jine = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='退款总金额')
class Meta:
db_table = 'shangjia_ri_tongji'
verbose_name = '商家每日统计'
unique_together = [['yonghuid', 'riqi']] # 每人每天一条记录
indexes = [
models.Index(fields=['riqi', '-paifa_jine']),
models.Index(fields=['riqi', '-jiesuan_jine']),
models.Index(fields=['riqi', '-paifa_dingdan_shu']),
]
class DashouRiTongji(models.Model):
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='打手用户ID')
riqi = models.DateField(db_index=True, verbose_name='日期') # 年月日
# 接单统计
jiedan_zongliang = models.IntegerField(default=0, verbose_name='接单总量')
chengjiao_zongliang = models.IntegerField(default=0, verbose_name='成交单总量')
# 金额统计
jiedan_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='接单总金额')
chengjiao_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='成交总金额')
# 退款统计
tuikuan_liang = models.IntegerField(default=0, verbose_name='退款订单量')
tuikuan_jine = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='退款总金额')
class Meta:
db_table = 'dashou_ri_tongji'
verbose_name = '打手每日统计'
unique_together = [['yonghuid', 'riqi']]
indexes = [
models.Index(fields=['riqi', '-jiedan_zongliang']),
models.Index(fields=['riqi', '-chengjiao_zongliang']),
models.Index(fields=['riqi', '-jiedan_zonge']),
models.Index(fields=['riqi', '-chengjiao_zonge']),
]
class ZuzhangRiTongji(models.Model):
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='组长用户ID')
riqi = models.DateField(db_index=True, verbose_name='日期') # 年月日
yaoqing_guanshi_shu = models.IntegerField(default=0, verbose_name='邀请管事总数')
shouru_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='收入总额(分佣)')
# 可选明细
fenyong_jine = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='分佣金额')
class Meta:
db_table = 'zuzhang_ri_tongji'
verbose_name = '组长每日统计'
unique_together = [['yonghuid', 'riqi']]
indexes = [
models.Index(fields=['riqi', '-yaoqing_guanshi_shu']),
models.Index(fields=['riqi', '-shouru_zonge']),
]
class GuanshiRiTongji(models.Model):
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='管事用户ID')
riqi = models.DateField(db_index=True, verbose_name='日期') # 年月日
yaoqing_dashou_shu = models.IntegerField(default=0, verbose_name='邀请打手总数')
chongzhi_dashou_shu = models.IntegerField(default=0, verbose_name='充值打手数量')
shouru_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='收入总额')
class Meta:
db_table = 'guanshi_ri_tongji'
verbose_name = '管事每日统计'
unique_together = [['yonghuid', 'riqi']]
indexes = [
models.Index(fields=['riqi', '-yaoqing_dashou_shu']),
models.Index(fields=['riqi', '-chongzhi_dashou_shu']),
models.Index(fields=['riqi', '-shouru_zonge']),
]
class GuanshiXufeiRiTongji(models.Model):
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='管事用户ID')
riqi = models.DateField(db_index=True, verbose_name='日期') # 年月日
xufei_zongshu = models.IntegerField(default=0, verbose_name='今日续费总数') # 续费订单数(或续费打手数)
xufei_shouyi = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='续费收益金额')
class Meta:
db_table = 'guanshi_xufei_ri_tongji'
verbose_name = '管事续费每日统计'
unique_together = [['yonghuid', 'riqi']]
indexes = [
models.Index(fields=['riqi', '-xufei_zongshu']),
models.Index(fields=['riqi', '-xufei_shouyi']),
]
# models.py 中添加
class TixianRiTongji(models.Model):
"""
每日提现统计表(按身份统计当天提现总额)
"""
riqi = models.DateField(db_index=True, verbose_name='日期')
leixing = models.IntegerField(
choices=[(1, '打手'), (2, '管事'), (3, '组长')],
verbose_name='用户类型'
)
total_amount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='当天提现总额(元)')
class Meta:
db_table = 'tixian_ri_tongji'
verbose_name = '每日提现统计'
unique_together = [['riqi', 'leixing']] # 每天每个身份一条记录
indexes = [models.Index(fields=['riqi', 'leixing'])]
def __str__(self):
return f"{self.riqi} - {self.get_leixing_display()} 提现总额: {self.total_amount}"

39
houtai/serializers.py Normal file
View File

@@ -0,0 +1,39 @@
# serializers.py
#小程序弹窗序列化器
from rest_framework import serializers
from peizhi.models import PopupPage, PopupConfig, PopupImage
class PopupImageSerializer(serializers.ModelSerializer):
"""图片序列化器(输出用)"""
class Meta:
model = PopupImage
fields = ['id', 'image_url', 'text', 'sort_order']
class PopupConfigSerializer(serializers.ModelSerializer):
"""弹窗配置序列化器(输出用)"""
images = PopupImageSerializer(many=True, read_only=True)
class Meta:
model = PopupConfig
fields = [
'id', 'popup_id', 'title', 'description',
'strategy_type', 'max_count', 'reset_interval',
'duration', 'force_even_muted', 'sort_order',
'is_active', 'start_time', 'end_time',
'created_at', 'updated_at', 'images'
]
class PopupPageSerializer(serializers.ModelSerializer):
"""页面序列化器(输出用)"""
popups = PopupConfigSerializer(many=True, read_only=True)
class Meta:
model = PopupPage
fields = [
'id', 'page_key', 'name', 'description',
'is_active', 'ignore_user_mute',
'created_at', 'updated_at', 'popups'
]

137
houtai/urls.py Normal file
View File

@@ -0,0 +1,137 @@
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from .view import GetClubConfigView, GetCrossOrderListView, PartnerGetOrderDataView,PartnerRefundNotifyView,\
CrossPlatformRefundView, GetCrossOrderDetailView, CrossPlatformForceCompleteView, CrossPlatformTransferHallView,\
PartnerTransferHallNotifyView, GetSettingsView, UpdateSettingsView, GetMappingsView, UpdateMappingsView,\
PartnerUpdateSelfConfigView, GetPartnerClubsView, GetStatsView, PunishDashouView, PartnerPunishNotifyView,\
GetClubConfigForPreSettlementView, PreSettlementActionView, GetPreSettlementDataView, GetRolePermissionView,\
ModifyRolePermissionView, AddRoleView, GetAdminRolesView, GetAdminUserListView, AddAdminUserView,\
ModifyAdminUserView, KefuGetDashouListView, KefuGetDashouDetailView, KefuUpdateDashouView, KefuGetShangjiaListView,\
KefuGetShangjiaDetailView, KefuUpdateShangjiaView, GetProductBaseDataView, GetProductListView, SaveProductOrderView,\
AddProductView, DeleteProductView, GetProductDetailView, UpdateProductView, UpdateMemberView, GetMemberListView,\
AddMemberView, GetGuanliListView, GetGuanliDetailView, UpdateGuanliView, GetWithdrawSettingsView,\
UpdateWithdrawSettingsView, GetZuzhangListView, GetZuzhangDetailView, UpdateZuzhangView, GetProductTypeZoneView,\
ModifyProductTypeZoneView, GetRateView, ModifyRateView, PopupNoticeListAPIView, PopupNoticeModifyAPIView,ShopListView, ShopModifyView, ShopPublicTypeAndAuditView, ShopListForProductView, ShopProductModifyView,\
ShopProductTypeMappingView, ProductListView, ProductModifyView, KefuChatPermissionsView,\
PartnerFineNotifyView,FineApplyView, FaKuanTongJiView, FaKuanLieBiaoView, FaKuanChuLiView, FaKuanChuangJianView,\
HqbkxxView,BkxgView,CaiwuView,CwhybkhqView, HybkjtsjView, SzxxView, CwddhqlxView, CwhqjtddsjView,\
CwqtczhqView, KhpzhqView, ChzsgcView,KhgglView, ShgxgsjView, ZxkfghdsView, KptxwztjbView
urlpatterns = [
path('kpthq', GetClubConfigView.as_view(), name='跨平台订单页面获取俱乐部数据'),
path('hqddlb', GetCrossOrderListView.as_view(), name='获取跨平台订单'),
path('kpthqtp',csrf_exempt(PartnerGetOrderDataView.as_view()), name='跨平台接受对方服务器获取订单数据'),
# 对方平台退款通知接口
path('dfddtk', csrf_exempt(PartnerRefundNotifyView.as_view()), name='跨平台接受对方服务器更新订单状态'),
path('kptk', CrossPlatformRefundView.as_view(), name='跨平台订单退款'),
path('hqkptddxq', GetCrossOrderDetailView.as_view(), name='获取跨平台订单数据详情'),
path('kpqzjd', CrossPlatformForceCompleteView.as_view(), name='跨平台订单结算'),
path('kpzydt', CrossPlatformTransferHallView.as_view(), name='跨平台转移大厅'),
path('kpttzzydt', csrf_exempt(PartnerTransferHallNotifyView.as_view()), name='跨平台接受对方转移大厅通知'),
path('hqkptsz', GetSettingsView.as_view(), name='获取俱乐部数据信息'),
path('xgjlbsj', UpdateSettingsView.as_view(), name='更新俱乐部数据信息'),
path('hqqtjlbsj', GetMappingsView.as_view(), name='获取俱乐部订单类型映射'),
path('xgdfddys', UpdateMappingsView.as_view(), name='工薪俱乐部订单类型映射'),
path('xgzjsjcs', csrf_exempt(PartnerUpdateSelfConfigView.as_view()), name='partner_update_self_config'),
path('kpthqpt', GetPartnerClubsView.as_view(), name='前端获取俱乐部数据展示派发数据'),
path('hqpfsj', GetStatsView.as_view(), name='前端派发统计表获取'),
path('kpcf', PunishDashouView.as_view(), name='跨平台处罚打手'),
path('kptcfds', PartnerPunishNotifyView.as_view(), name='接受跨平台对方平台处罚申请'),
path('hqkttjsyjs', GetClubConfigForPreSettlementView.as_view(), name='预处理获取俱乐部数据'),
path('hqyjssj', GetPreSettlementDataView.as_view(), name='获取预处理数据'),
path('yjsjjhdk', PreSettlementActionView.as_view(), name='操作预处理'),
path('gljshq', GetRolePermissionView.as_view(), name='获取就角色信息'),
path('sctjjsnr', ModifyRolePermissionView.as_view(), name='修改角色数据'),
path('tjjs', AddRoleView.as_view(), name='添加角色'),
path('hqyhjsgl', GetAdminRolesView.as_view(), name='管理用户页面获取用户角色'),
path('hqjsyhsj', GetAdminUserListView.as_view(), name='管理用户页面获取用户数据'),
path('xghtyhsj', ModifyAdminUserView.as_view(), name='管理用户页面修改用户数据'),
path('tjyhjs', AddAdminUserView.as_view(), name='管理用户页面添加用户'),
path('kefuhqdslb', KefuGetDashouListView.as_view(), name='客服获取打手数据'),
path('kefuhqdsxq', KefuGetDashouDetailView.as_view(), name='客服获取打手具体信息'),
path('kefuxgds', KefuUpdateDashouView.as_view(), name='客服修改打手数据'),
path('hqsjgllb', KefuGetShangjiaListView.as_view(), name='后台获取商家列表'),
path('hthqsjxq', KefuGetShangjiaDetailView.as_view(), name='后台获取商家详情'),
path('xgsjxx', KefuUpdateShangjiaView.as_view(), name='后台修改商家数据'),
path('htsphqjcsx', GetProductBaseDataView.as_view(), name='后台获取商品类型专区'),
path('hthqsplb', GetProductListView.as_view(), name='后台获取商品数据'),
path('htsppaixu', SaveProductOrderView.as_view(), name='后台修改商品类型排序'),
path('htfbsp', AddProductView.as_view(), name='后台添加商品'),
path('htscsp', DeleteProductView.as_view(), name='后台删除商品'),
path('htsphqxq', GetProductDetailView.as_view(), name='后台获取商品详情'),
path('bcspxg', UpdateProductView.as_view(), name='后台更新商品数据'),
path('hthqhylb', GetMemberListView.as_view(), name='后台获取会员列表'),
path('htxghyxx', UpdateMemberView.as_view(), name='后台更新会员信息'),
path('httjhy', AddMemberView.as_view(), name='后台添加会员'),
path('hthqgslb', GetGuanliListView.as_view(), name='后台获取管事列表'),
path('hthqgsxq', GetGuanliDetailView.as_view(), name='后台获取管事详情信息'),
path('htxggsxx', UpdateGuanliView.as_view(), name='后台更新管事数据'),
path('hthqtxpz', GetWithdrawSettingsView.as_view(), name='后台获取提现配置'),
path('htxgtxsz', UpdateWithdrawSettingsView.as_view(), name='后台修改提现配置'),
path('hthqzzlb', GetZuzhangListView.as_view(), name='后台获取组长列表'),
path('hthqzzxq', GetZuzhangDetailView.as_view(), name='后台获取组长详情页面'),
path('htxgzzxx', UpdateZuzhangView.as_view(), name='后台修改组长信息'),
path('hthqsplxzq', GetProductTypeZoneView.as_view(), name='后台获取商品类型专区'),
path('htxgsplx', ModifyProductTypeZoneView.as_view(), name='后台修改添加商品类型专区'),
path('hthqfhpz', GetRateView.as_view(), name='后台获取费率'),
path('htxgfl', ModifyRateView.as_view(), name='后台修改费率'),
path('hthqtcxx', PopupNoticeListAPIView.as_view(), name='后台获取弹窗配置'),
path('htxgtcxx', PopupNoticeModifyAPIView.as_view(), name='后台更新创建弹窗配置'),
path('hqdplbsj', ShopListView.as_view(), name='获取店铺列表数据'),
path('xgdpxx', ShopModifyView.as_view(), name='修改或添加店铺数据'),
path('htdphqsplx', ShopPublicTypeAndAuditView.as_view(), name='后台获取店铺公共商品类型,默认审核配置'),
path('htdphqdpys', ShopListForProductView.as_view(), name='后台获取商品店铺'),
path('htdphqyssplx', ShopProductTypeMappingView.as_view(), name='后台获取商品店铺类型'),
path('htxgdpsplx', ShopProductModifyView.as_view(), name='后台修改店铺商品类型和默认审核模式'),
path('hqhtdpsplx', ProductListView.as_view(), name='后台获取店铺商品数据'),
path('htxgdpspsj', ProductModifyView.as_view(), name='后台修改店铺商品数据'),
path('kfhqltqx', KefuChatPermissionsView.as_view(), name='后台获取客服聊天配置'),
path('kffkdssq', FineApplyView.as_view(), name='跨平台处罚罚款申请接口'),
path('kffkdssq_notify', PartnerFineNotifyView.as_view(), name='跨屏要对方平台处罚通知接口'),
path('htglyhqcfsltj', FaKuanTongJiView.as_view(),name='后台罚单统计图片'), # 罚款统计
path('hthqfklb', FaKuanLieBiaoView.as_view(),name='后台罚单列表'), # 罚款列表
path('glyclfk', FaKuanChuLiView.as_view(),name='后台罚单操作'), # 处理罚款
path('htfksc', FaKuanChuangJianView.as_view(),name='后台生成罚单'), # 创建罚款
path('hqbkxx', HqbkxxView.as_view(), name='获取板块数据'), # 获取板块数据
path('bkxg', BkxgView.as_view(), name='板块增删改查'), # 板块增删改
path('caiwu', CaiwuView.as_view(), name='财务页面基础数据'),
path('cwhybkhq', CwhybkhqView.as_view(), name='财务会员模块'),
path('hybkjtsj', HybkjtsjView.as_view(), name='财务会员统计'),
path('szxx', SzxxView.as_view(), name='详细收支记录'),
path('cwddhqlx', CwddhqlxView.as_view(), name='后台财务获取订单类型'),
path('cwhqjtddsj', CwhqjtddsjView.as_view(), name='后台财务获取订单数据'),
path('cwqtczhq', CwqtczhqView.as_view(), name='财务获取其他充值统计'),
path('khpzhq', KhpzhqView.as_view(), name='后台获取称号配置'),
path('chzsgc', ChzsgcView.as_view(), name='后台对称号增删改查'),
path('khggl', KhgglView.as_view(), name='获取考核管信息'),
path('shgxgsj', ShgxgsjView.as_view(), name='修改考核管数据'),
path('zxkfghds', ZxkfghdsView.as_view(), name='链接换打手或重新下单'),
path('kptxwztjb', csrf_exempt(KptxwztjbView.as_view()), name='跨平台脚本询问订单状态'),
]

424
houtai/utils.py Normal file
View File

@@ -0,0 +1,424 @@
# houtai/utils.py
import logging
import json
from django.core.exceptions import ObjectDoesNotExist
from rest_framework.response import Response
from .models import AbnormalUserLog, UserRole, RolePermission, Permission, DashouRiTongji, ShangjiaRiTongji, \
GuanshiXufeiRiTongji, GuanshiRiTongji, ZuzhangRiTongji, TixianRiTongji
from django.db import transaction
from django.db.models import F
from datetime import date
from decimal import Decimal
logger = logging.getLogger(__name__)
def _get_real_ip(request):
"""获取真实IP考虑代理"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0].strip()
return ip, x_forwarded_for
else:
ip = request.META.get('REMOTE_ADDR')
return ip, ''
def _record_abnormal(account_id, request, attempt_type, detail=''):
"""记录异常操作(仅用于越权等攻击行为)"""
try:
real_ip, xff = _get_real_ip(request)
headers = {k: v for k, v in request.META.items() if k.startswith('HTTP_')}
headers_json = json.dumps(headers, ensure_ascii=False)[:2000]
request_params = json.dumps(request.query_params.dict(), ensure_ascii=False) if request.query_params else ''
request_body = request.body.decode('utf-8', errors='ignore')[:5000]
AbnormalUserLog.objects.create(
account_id=account_id,
ip_address=real_ip,
real_ip=real_ip,
x_forwarded_for=xff,
user_agent=request.META.get('HTTP_USER_AGENT', ''),
request_method=request.method,
request_path=request.path,
request_params=request_params,
request_body=request_body,
request_headers=headers_json,
attempt_type=attempt_type,
detail=detail,
)
logger.warning(f"记录异常用户: {account_id} IP:{real_ip} 类型:{attempt_type}")
except Exception as e:
logger.error(f"记录异常日志失败: {e}")
def verify_kefu_permission(request, username_from_frontend=None):
"""
验证客服身份并返回该用户的所有权限列表基于新RBAC表
只有前端传递的username与当前登录用户不匹配时才记录异常并警告前端。
参数:
request: DRF request 对象
username_from_frontend: 前端传递的账号(用于防越权)
返回:
(kefu_obj, permissions_list) # 验证通过
(None, Response) # 验证失败直接返回Response
"""
current_user = request.user
# ---------- 1. 必须是客服 ----------
if current_user.user_type != 'kefu':
return None, Response({'code': 403, 'msg': '身份错误,非客服账号'}, status=403)
# ---------- 2. 客服扩展表存在且状态正常 ----------
try:
kefu = current_user.kefu_profile
if kefu.zhuangtai != 1:
return None, Response({'code': 403, 'msg': '客服账号已被禁用'}, status=403)
except ObjectDoesNotExist:
return None, Response({'code': 403, 'msg': '客服账号不存在'}, status=403)
# ---------- 3. 越权检测只记录前端篡改username的情况 ----------
if username_from_frontend and current_user.phone != username_from_frontend:
detail = f'前端username: {username_from_frontend},实际登录账号: {current_user.phone}'
_record_abnormal(current_user.phone, request, '越权尝试篡改username', detail=detail)
return None, Response({
'code': 403,
'msg': '身份错误,无权操作其他账号,相关操作已记录'
}, status=403)
# ---------- 4. 获取用户的所有权限(通过角色) ----------
# 步骤1获取用户的所有角色ID
role_ids = UserRole.objects.filter(account_id=current_user.phone).values_list('role_id', flat=True)
# 步骤2通过角色ID获取权限编码
permissions = list(Permission.objects.filter(
rolepermission__role_id__in=role_ids
).values_list('perm_code', flat=True).distinct())
if not permissions:
logger.warning(f"用户 {current_user.phone} 没有任何权限,请检查角色分配")
return kefu, permissions
def update_dashou_daily_by_action(yonghuid, amount, action):
"""
根据行为类型更新打手每日统计(接单、成交、退款)
参数:
yonghuid: 打手用户ID
amount: 本单涉及的金额Decimal
action: 1 = 接单, 2 = 成交(结算), 3 = 退款
"""
amount = Decimal(str(amount)) if amount else Decimal('0.00')
if amount <= 0:
return # 金额非正不统计
today = date.today()
with transaction.atomic():
stat, created = DashouRiTongji.objects.select_for_update().get_or_create(
yonghuid=yonghuid,
riqi=today,
defaults={
'yonghuid': yonghuid,
'riqi': today,
'jiedan_zongliang': 0,
'jiedan_zonge': Decimal('0.00'),
'chengjiao_zongliang': 0,
'chengjiao_zonge': Decimal('0.00'),
'tuikuan_liang': 0,
'tuikuan_jine': Decimal('0.00'),
}
)
update_fields = {}
if action == 1: # 接单
update_fields['jiedan_zongliang'] = F('jiedan_zongliang') + 1
update_fields['jiedan_zonge'] = F('jiedan_zonge') + amount
elif action == 2: # 成交
update_fields['chengjiao_zongliang'] = F('chengjiao_zongliang') + 1
update_fields['chengjiao_zonge'] = F('chengjiao_zonge') + amount
elif action == 3: # 退款
update_fields['tuikuan_liang'] = F('tuikuan_liang') + 1
update_fields['tuikuan_jine'] = F('tuikuan_jine') + amount
else:
return # 无效行为
if update_fields:
DashouRiTongji.objects.filter(id=stat.id).update(**update_fields)
def update_shangjia_daily(yonghuid, amount, action):
"""
根据操作行为更新商家每日统计(派发、结算、退款)
参数:
yonghuid: 商家用户ID
amount: 当前订单涉及的金额Decimal
action: 操作类型1 = 派发2 = 结算成交3 = 退款
说明:
- 金额必须大于0否则不执行任何操作
- 使用 select_for_update + F() 表达式保证原子性和并发安全
- 每天每个商家只会有一条统计记录,不存在则自动创建
"""
amount = Decimal(str(amount)) if amount else Decimal('0.00')
if amount <= 0:
return # 金额非正,不统计
today = date.today()
with transaction.atomic():
# 获取或创建当天统计记录,同时加锁防止并发
stat, created = ShangjiaRiTongji.objects.select_for_update().get_or_create(
yonghuid=yonghuid,
riqi=today,
defaults={
'yonghuid': yonghuid,
'riqi': today,
'paifa_dingdan_shu': 0,
'paifa_jine': Decimal('0.00'),
'jiesuan_dingdan_shu': 0,
'jiesuan_jine': Decimal('0.00'),
'tuikuan_dingdan_shu': 0,
'tuikuan_jine': Decimal('0.00'),
}
)
# 构建本次更新的字段,使用 F 表达式进行原子累加
update_fields = {}
if action == 1: # 派发
update_fields['paifa_dingdan_shu'] = F('paifa_dingdan_shu') + 1
update_fields['paifa_jine'] = F('paifa_jine') + amount
elif action == 2: # 结算(成交)
update_fields['jiesuan_dingdan_shu'] = F('jiesuan_dingdan_shu') + 1
update_fields['jiesuan_jine'] = F('jiesuan_jine') + amount
elif action == 3: # 退款
update_fields['tuikuan_dingdan_shu'] = F('tuikuan_dingdan_shu') + 1
update_fields['tuikuan_jine'] = F('tuikuan_jine') + amount
else:
# 无效的行为类型,直接返回
return
# 执行原子更新
ShangjiaRiTongji.objects.filter(id=stat.id).update(**update_fields)
def update_guanshi_daily_by_action(yonghuid, action, amount=Decimal('0.00')):
"""
根据操作行为更新管事每日统计(邀请打手、充值会员、订单/押金分红)
action: 1 = 邀请打手, 2 = 充值会员, 3 = 商家订单分红, 4 = 押金分红
"""
from django.db import transaction
from django.db.models import F
from datetime import date
today = date.today()
with transaction.atomic():
stat, created = GuanshiRiTongji.objects.select_for_update().get_or_create(
yonghuid=yonghuid,
riqi=today,
defaults={
'yonghuid': yonghuid,
'riqi': today,
'yaoqing_dashou_shu': 0,
'chongzhi_dashou_shu': 0,
'shouru_zonge': Decimal('0.00'),
}
)
if action == 1:
# 邀请打手:无论新创建还是已存在,直接 +1
if created:
stat.yaoqing_dashou_shu = 1
stat.save()
else:
GuanshiRiTongji.objects.filter(id=stat.id).update(
yaoqing_dashou_shu=F('yaoqing_dashou_shu') + 1
)
elif action == 2:
amount = Decimal(str(amount)) if amount else Decimal('0.00')
if amount <= 0:
return
if created:
stat.chongzhi_dashou_shu = 1
stat.shouru_zonge = amount
stat.save()
else:
GuanshiRiTongji.objects.filter(id=stat.id).update(
chongzhi_dashou_shu=F('chongzhi_dashou_shu') + 1,
shouru_zonge=F('shouru_zonge') + amount
)
elif action in (3, 4):
amount = Decimal(str(amount)) if amount else Decimal('0.00')
if amount <= 0:
return
if created:
stat.shouru_zonge = amount
stat.save()
else:
GuanshiRiTongji.objects.filter(id=stat.id).update(
shouru_zonge=F('shouru_zonge') + amount
)
'''def update_guanshi_daily_by_action(yonghuid, action, amount=Decimal('0.00')):
"""
根据操作行为更新管事每日统计(邀请打手、充值会员)
参数:
yonghuid: 管事用户ID
action: 操作类型
1 = 邀请打手(次数自动+1无需传其他参数
2 = 充值会员(需要传 amount 参数)
amount: 充值金额Decimal仅在 action=2 时有效
说明:
- 每次调用只代表一次操作,不会批量处理。
- 使用 select_for_update + F() 保证原子性和并发安全。
"""
today = date.today()
# 邀请打手:次数+1金额不变
if action == 1:
count_field = 'yaoqing_dashou_shu'
amount = Decimal('0.00')
# 充值会员:次数+1金额累加
elif action == 2:
amount = Decimal(str(amount)) if amount else Decimal('0.00')
if amount <= 0:
return
count_field = 'chongzhi_dashou_shu'
else:
return # 无效行为,直接返回
with transaction.atomic():
stat, created = GuanshiRiTongji.objects.select_for_update().get_or_create(
yonghuid=yonghuid,
riqi=today,
defaults={
'yonghuid': yonghuid,
'riqi': today,
'yaoqing_dashou_shu': 0,
'chongzhi_dashou_shu': 0,
'shouru_zonge': Decimal('0.00'),
}
)
if created:
# 新记录直接赋初值
if action == 1:
stat.yaoqing_dashou_shu = 1
else:
# 已存在记录,使用 F 表达式原子累加次数
update_fields = {count_field: F(count_field) + 1}
if action == 2:
update_fields['shouru_zonge'] = F('shouru_zonge') + amount
GuanshiRiTongji.objects.filter(id=stat.id).update(**update_fields)'''
def update_guanshi_xufei_daily(yonghuid, xufei_jine=Decimal('0.00')):
"""
管事续费统计(单独表)
用法: update_guanshi_xufei_daily('300001', Decimal('30'))
"""
today = date.today()
with transaction.atomic():
stat, created = GuanshiXufeiRiTongji.objects.select_for_update().get_or_create(
yonghuid=yonghuid,
riqi=today,
defaults={
'yonghuid': yonghuid,
'riqi': today,
'xufei_zongshu': 1,
'xufei_shouyi': xufei_jine,
}
)
if not created:
GuanshiXufeiRiTongji.objects.filter(id=stat.id).update(
xufei_zongshu=F('xufei_zongshu') + 1,
xufei_shouyi=F('xufei_shouyi') + xufei_jine
)
def update_zuzhang_daily_by_action(yonghuid, action, amount=Decimal('0.00')):
"""
根据操作行为更新组长每日统计(邀请管事、分佣收入)
参数:
yonghuid: 组长用户ID
action: 操作类型
1 = 邀请管事(次数+1无金额
2 = 分佣收入(累加金额)
amount: 分佣金额Decimal仅在 action=2 时有效
"""
today = date.today()
with transaction.atomic():
stat, created = ZuzhangRiTongji.objects.select_for_update().get_or_create(
yonghuid=yonghuid,
riqi=today,
defaults={
'yonghuid': yonghuid,
'riqi': today,
'yaoqing_guanshi_shu': 0,
'shouru_zonge': Decimal('0.00'),
'fenyong_jine': Decimal('0.00'),
}
)
if created:
# 新记录直接设置初始值
if action == 1:
stat.yaoqing_guanshi_shu = 1
elif action == 2:
amount = Decimal(str(amount)) if amount else Decimal('0.00')
stat.shouru_zonge = amount
stat.fenyong_jine = amount
stat.save()
else:
# 已存在记录,使用 F 表达式原子累加
if action == 1:
ZuzhangRiTongji.objects.filter(id=stat.id).update(
yaoqing_guanshi_shu=F('yaoqing_guanshi_shu') + 1
)
elif action == 2:
amount = Decimal(str(amount)) if amount else Decimal('0.00')
if amount > 0:
ZuzhangRiTongji.objects.filter(id=stat.id).update(
shouru_zonge=F('shouru_zonge') + amount,
fenyong_jine=F('fenyong_jine') + amount
)
def update_tixian_daily_stat(leixing, amount):
"""
更新每日提现统计(平台维度)
leixing: 1=打手, 2=管事, 3=组长
amount: 本次提现金额(申请金额,含手续费)
"""
today = date.today()
with transaction.atomic():
stat, created = TixianRiTongji.objects.select_for_update().get_or_create(
riqi=today,
leixing=leixing,
defaults={'total_amount': amount}
)
if not created:
TixianRiTongji.objects.filter(id=stat.id).update(
total_amount=F('total_amount') + amount
)

12489
houtai/view.py Normal file

File diff suppressed because it is too large Load Diff

22
manage.py Normal file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'a_long_dianjing.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

0
peizhi/__init__.py Normal file
View File

3
peizhi/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
peizhi/apps.py Normal file
View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class PeizhiConfig(AppConfig):
name = "peizhi"

View File

@@ -0,0 +1,92 @@
# Generated by Django 5.2.10 on 2026-01-14 19:59
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Gonggao',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('leixing', models.IntegerField(blank=True, null=True, verbose_name='公告类型')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更改时间')),
('jieshao', models.TextField(blank=True, null=True, verbose_name='公告介绍')),
],
options={
'verbose_name': '公告',
'verbose_name_plural': '公告',
'db_table': 'gonggao',
},
),
migrations.CreateModel(
name='Lunbo',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('tupian_url', models.CharField(blank=True, max_length=500, null=True, verbose_name='轮播图片URL')),
('leixing', models.IntegerField(blank=True, null=True, verbose_name='图片类型:1轮播2个人中心背景')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更改时间')),
],
options={
'verbose_name': '轮播',
'verbose_name_plural': '轮播',
'db_table': 'lunbo',
},
),
migrations.CreateModel(
name='Qunpeizhi',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('peizhiid', models.IntegerField(blank=True, null=True, verbose_name='群类型:1.打手 2.管事')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更改时间')),
('neirong', models.CharField(blank=True, max_length=200, null=True, verbose_name='群介绍')),
('qunid', models.CharField(blank=True, max_length=100, null=True, verbose_name='群id')),
('jieshao', models.CharField(blank=True, max_length=200, null=True, verbose_name='介绍')),
],
options={
'db_table': 'qunpeizhi',
},
),
migrations.CreateModel(
name='Szjilu',
fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='记录ID')),
('zongsy', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='总收益')),
('zongls', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='总流水')),
('zongzc', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='总支出')),
('jrzc', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='今日支出')),
('jrls', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='今日流水')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更改时间')),
],
options={
'verbose_name': '收支记录',
'verbose_name_plural': '收支记录',
'db_table': 'szjilu',
},
),
migrations.CreateModel(
name='Tupianpeizhi',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('tupian', models.CharField(blank=True, max_length=500, null=True, verbose_name='配置图片URL')),
('leixing', models.PositiveIntegerField(blank=True, null=True, verbose_name='提现类型1:打手规则图片2默认头像图片')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更改时间')),
],
options={
'verbose_name': '配置图片',
'verbose_name_plural': '配置图片',
'db_table': 'tupianpeizhi',
},
),
]

View File

@@ -0,0 +1,56 @@
# Generated by Django 4.2.27 on 2026-01-27 12:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('peizhi', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='ShangjiaMoban',
fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='模板ID')),
('yonghu_id', models.CharField(db_index=True, max_length=7, verbose_name='用户ID')),
('leixing_id', models.PositiveIntegerField(db_index=True, verbose_name='商品类型ID')),
('moban_jieshao', models.CharField(max_length=500, verbose_name='模板介绍')),
('jiage', models.DecimalField(decimal_places=2, help_text='单位:元', max_digits=10, verbose_name='模板价格')),
('fabu_shuliang', models.PositiveIntegerField(default=0, verbose_name='发布数量')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'verbose_name': '商家订单模板',
'verbose_name_plural': '商家订单模板',
'db_table': 'shangjia_moban',
'indexes': [models.Index(fields=['yonghu_id', 'leixing_id'], name='shangjia_mo_yonghu__bb287d_idx'), models.Index(fields=['yonghu_id', 'leixing_id', 'id'], name='shangjia_mo_yonghu__4240d0_idx')],
'unique_together': {('yonghu_id', 'leixing_id', 'moban_jieshao')},
},
),
migrations.CreateModel(
name='ShangjiaLianjie',
fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='链接ID')),
('yonghu_id', models.CharField(db_index=True, max_length=7, verbose_name='用户ID')),
('leixing_id', models.PositiveIntegerField(db_index=True, verbose_name='商品类型ID')),
('moban_id', models.PositiveIntegerField(db_index=True, verbose_name='模板ID')),
('lianjie', models.CharField(max_length=500, unique=True, verbose_name='链接地址')),
('dingdan_id', models.CharField(blank=True, db_index=True, max_length=32, null=True, verbose_name='订单ID')),
('is_used', models.BooleanField(db_index=True, default=False, verbose_name='是否已使用')),
('shiyongzhe_ip', models.CharField(blank=True, max_length=50, null=True, verbose_name='使用者IP')),
('youxiao_shijian', models.DateTimeField(blank=True, null=True, verbose_name='有效截止时间')),
('lianjie_token', models.CharField(blank=True, db_index=True, max_length=32, null=True, unique=True, verbose_name='链接Token')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'verbose_name': '商家链接',
'verbose_name_plural': '商家链接',
'db_table': 'shangjia_lianjie',
'indexes': [models.Index(fields=['yonghu_id', 'moban_id', 'leixing_id'], name='shangjia_li_yonghu__9f2b25_idx'), models.Index(fields=['lianjie_token'], name='shangjia_li_lianjie_99014c_idx'), models.Index(fields=['dingdan_id'], name='shangjia_li_dingdan_95b682_idx')],
},
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.27 on 2026-03-16 17:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('peizhi', '0002_shangjiamoban_shangjialianjie'),
]
operations = [
migrations.AddField(
model_name='tupianpeizhi',
name='fangwen_cishu',
field=models.PositiveIntegerField(default=0, verbose_name='访问次数'),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.2.27 on 2026-03-20 15:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('peizhi', '0003_tupianpeizhi_fangwen_cishu'),
]
operations = [
migrations.CreateModel(
name='TixianQuotaDefault',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('leixing', models.IntegerField(choices=[(1, '打手'), (2, '管事')], unique=True, verbose_name='用户类型')),
('default_quota', models.DecimalField(decimal_places=2, default=20.0, max_digits=10, verbose_name='默认每日提现限额(元)')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'verbose_name': '默认提现限额',
'verbose_name_plural': '默认提现限额',
'db_table': 'tixian_quota_default',
},
),
]

View File

@@ -0,0 +1,76 @@
# Generated by Django 4.2.27 on 2026-03-24 17:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('peizhi', '0004_tixianquotadefault'),
]
operations = [
migrations.CreateModel(
name='OrderTypeMapping',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('partner_club_id', models.CharField(db_index=True, max_length=6, verbose_name='对方俱乐部ID')),
('partner_type_id', models.PositiveIntegerField(verbose_name='对方订单类型ID')),
('partner_type_name', models.CharField(max_length=100, verbose_name='对方订单类型名称')),
('our_type_id', models.PositiveIntegerField(verbose_name='我方订单类型ID')),
('our_type_name', models.CharField(max_length=100, verbose_name='我方订单类型名称')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'verbose_name': '订单类型映射',
'verbose_name_plural': '订单类型映射',
'db_table': 'order_type_mapping',
'unique_together': {('partner_club_id', 'partner_type_id')},
},
),
migrations.CreateModel(
name='DailyDispatchStat',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(db_index=True, verbose_name='统计日期')),
('year', models.PositiveSmallIntegerField(verbose_name='年份')),
('month', models.PositiveSmallIntegerField(verbose_name='月份')),
('day', models.PositiveSmallIntegerField(verbose_name='日期')),
('direction', models.PositiveSmallIntegerField(choices=[(1, '我方派单'), (2, '对方派单')], verbose_name='派单方向')),
('partner_club_id', models.CharField(db_index=True, max_length=6, verbose_name='对方俱乐部ID')),
('dispatch_count', models.PositiveIntegerField(default=0, verbose_name='派单数')),
('dispatch_amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='派单总额')),
('success_count', models.PositiveIntegerField(default=0, verbose_name='成交订单数')),
('success_amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='成交总额')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'verbose_name': '每日派单统计',
'verbose_name_plural': '每日派单统计',
'db_table': 'daily_dispatch_stat',
'indexes': [models.Index(fields=['year', 'month', 'day', 'direction', 'partner_club_id'], name='daily_dispa_year_f98f91_idx'), models.Index(fields=['date'], name='daily_dispa_date_9ac7fe_idx')],
},
),
migrations.CreateModel(
name='Club',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('club_id', models.CharField(db_index=True, max_length=6, verbose_name='本俱乐部ID')),
('partner_club_id', models.CharField(max_length=6, unique=True, verbose_name='对方俱乐部ID')),
('is_interop_enabled', models.BooleanField(default=False, verbose_name='是否开启平台互通')),
('is_receive_enabled', models.BooleanField(default=False, verbose_name='是否同意接收对方订单')),
('dashou_rate', models.DecimalField(decimal_places=4, default=1.0, max_digits=5, verbose_name='打手分红费率')),
('partner_domain', models.URLField(blank=True, verbose_name='对方服务器域名')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'verbose_name': '俱乐部',
'verbose_name_plural': '俱乐部',
'db_table': 'club',
'indexes': [models.Index(fields=['club_id'], name='club_club_id_e75636_idx'), models.Index(fields=['partner_club_id'], name='club_partner_b76039_idx')],
},
),
]

View File

@@ -0,0 +1,52 @@
# Generated by Django 4.2.27 on 2026-04-01 09:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('peizhi', '0005_ordertypemapping_dailydispatchstat_club'),
]
operations = [
migrations.CreateModel(
name='ClubConfig',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('club_id', models.CharField(db_index=True, max_length=6, verbose_name='俱乐部ID')),
('club_nickname', models.CharField(blank=True, default='', max_length=50, verbose_name='俱乐部昵称')),
('club_avatar', models.CharField(blank=True, default='', max_length=500, verbose_name='俱乐部头像地址')),
('backend_domain', models.CharField(blank=True, default='', max_length=200, verbose_name='后台域名')),
('chat_api_key', models.CharField(blank=True, default='', max_length=200, verbose_name='聊天API密钥')),
('chat_api_id', models.CharField(blank=True, default='', max_length=200, verbose_name='聊天API ID')),
('customer_service_api_url', models.CharField(blank=True, default='', max_length=200, verbose_name='客服API URL')),
('customer_service_corp_id', models.CharField(blank=True, default='', max_length=100, verbose_name='客服企业ID')),
('storage_bucket_domain', models.CharField(blank=True, default='', max_length=500, verbose_name='存储桶域名')),
('is_self', models.SmallIntegerField(choices=[(0, '对方'), (1, '我方')], db_index=True, default=0, verbose_name='是否我方俱乐部')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'verbose_name': '俱乐部配置',
'verbose_name_plural': '俱乐部配置',
'db_table': 'club_config',
'unique_together': {('club_id',)},
},
),
migrations.CreateModel(
name='AccountPermission',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('account_id', models.CharField(db_index=True, max_length=11, verbose_name='账号ID')),
('permission_code', models.IntegerField(verbose_name='权限代码')),
('is_enabled', models.BooleanField(default=False, verbose_name='是否开启')),
],
options={
'verbose_name': '账号权限',
'verbose_name_plural': '账号权限',
'db_table': 'account_permission',
'unique_together': {('account_id', 'permission_code')},
},
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.2.27 on 2026-04-02 13:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('peizhi', '0006_clubconfig_accountpermission'),
]
operations = [
migrations.AddField(
model_name='club',
name='receive_rate',
field=models.DecimalField(decimal_places=4, default=1.0, max_digits=5, verbose_name='接收打手分红费率'),
),
migrations.AddField(
model_name='dailydispatchstat',
name='claimed_amount',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='抢单总额'),
),
migrations.AddField(
model_name='dailydispatchstat',
name='claimed_count',
field=models.PositiveIntegerField(default=0, verbose_name='抢单数'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.27 on 2026-04-04 18:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('peizhi', '0007_club_receive_rate_dailydispatchstat_claimed_amount_and_more'),
]
operations = [
migrations.AddField(
model_name='clubconfig',
name='storage_bucket_name',
field=models.CharField(blank=True, default='', max_length=100, verbose_name='存储桶名称'),
),
]

Some files were not shown because too many files have changed in this diff Show More