做了一些有限修正

This commit is contained in:
2026-06-14 00:24:53 +08:00
parent 624c168120
commit 1fd0301e0e
13 changed files with 8391 additions and 14768 deletions

2
.gitignore vendored
View File

@@ -4,7 +4,7 @@ db.sqlite3
/venv/
/env/
.env
_secrets.py
app_secrets.py
staticfiles/
media/
*.log

View File

@@ -1,73 +0,0 @@
"""
阿龙电竞 - 密钥与敏感配置文件
此文件包含所有敏感信息,不应提交到版本控制。
部署时请根据实际环境修改对应值。
"""
# ==================== Django 核心 ====================
SECRET_KEY = 'django-insecure-9+xu)m%#!#qblgby5sq!sjyg5nj9lir78+&*f$28$459#yktej'
# ==================== 数据库 ====================
DATABASE_NAME = 'xaio_cheng_xu'
DATABASE_USER = 'root'
DATABASE_PASSWORD = 'sajksh.sdfGH3YUge.wjkd+'
DATABASE_HOST = '172.17.0.15'
DATABASE_PORT = '3306'
# ==================== Redis ====================
REDIS_HOST = '172.19.0.2'
REDIS_PORT = 6379
REDIS_PASSWORD = 'Dujieduze5.3'
REDIS_DB_CELERY = 0
REDIS_DB_RESULT = 1
# ==================== 腾讯云 COS ====================
COS_SECRET_ID = 'AKIDU6WikZBcz96xesxpVM3XoslS3TjogLDx'
COS_SECRET_KEY = 'qcJzVqLdkeKyXwKckjV5SCmncFfrqdu2'
COS_REGION = 'ap-shanghai'
COS_BUCKET = 'xingque999qygwuyq-1404472910'
COS_APPID = '1404472910'
# ==================== 微信小程序 ====================
WEIXIN_APPID = 'wx0e4be86faac4a8d1'
WEIXIN_SECRET = '3c3673cf052571dea9e3bea88e198434'
# ==================== 微信支付 V2 ====================
WEIXIN_MCHID = '1746545364'
WEIXIN_SHANGHUMIYAO = 'Dujieduze53631383771411017633490'
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_OFFICIAL_APPID = 'wxd400adf1911c534f'
WEIXIN_OFFICIAL_SECRET = '19c946cf02ac6ef13f40333fc8029b92'
WEIXIN_OFFICIAL_TOKEN = '78324hjwewehrui3yferhf34'
WEIXIN_OFFICIAL_ENCODING_AES_KEY = 'NimrG1Fmq2ydplHJrXeg34NqbCJerltAGVx9xJ5WBx2'
WEIXIN_TEMPLATE_ID = 'jZ73G_0KbmFK864WVUr0QFNnqdH-ILBp_hUPMiwx4YY'
# ==================== 微信支付 V3 ====================
WECHAT_PAY_V3_MCHID = '1746545364'
WECHAT_PAY_V3_APPID = 'wx0e4be86faac4a8d1'
WECHAT_PAY_V3_API_V3_KEY = 'dujie4637rufjd98ijuyrgcvbvbn4789'
WECHAT_PAY_V3_PRIVATE_KEY_PATH = '/opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.443/huizhifuzhengshu/apiclient_key.pem'
WECHAT_PAY_V3_CERT_SERIAL_NO = '13D0CECB42C48DE78FF35A8A72EF4341FE769FE9'
WECHAT_PAY_V3_PLATFORM_CERT_DIR = '/opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.443/huizhifuzhengshu/pub_key.pem'
# ==================== GoEasy IM ====================
GOEASY_APPKEY = 'BC-32e2991c69bc47e1b538e73aaed37fc1'
GOEASY_SECRET = 'A469ABAA3cc34D64'
# ==================== 跨平台互通 ====================
CROSS_PLATFORM_TOKEN = 'jdjdjshwjqk'
CROSS_PLATFORM_SYNC_TOKEN = 'xK9mP2vL8qW5rT7y'
PARTNER_BASE_URL = 'https://ios808.top/hqhd'
AGENT_SHANGJIA_ID = '9106027'
CROSS_PLATFORM_MEMBER_ID = 'V00QQI'
CROSS_PLATFORM_WHITELIST = ['5438898']
# ==================== 特殊管事ID ====================
SPECIAL_GUANSHI_IDS = [
'2295516',
'9106027',
'2244062',
]

View File

@@ -1,6 +1,5 @@
"""
Django settings for a_long_dianjing project.
开发环境配置 - 参照旧项目结构精简
"""
from pathlib import Path
from datetime import timedelta
@@ -8,19 +7,17 @@ import os
# 从密钥配置文件导入敏感信息
try:
import _secrets
import app_secrets
except ImportError:
raise ImportError("缺少 secrets.py 密钥配置文件,请参照项目说明创建")
raise ImportError("缺少 appapp_secrets.py 密钥配置文件,请参照项目说明创建")
# 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 = _secrets.SECRET_KEY
DEBUG = False # 开发环境为 True
ALLOWED_HOSTS = ['localhost', '127.0.0.1','www.abas.asia'] # 根据开发前端地址可添加
SECRET_KEY = app_secrets.SECRET_KEY
DEBUG = False
ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'www.abas.asia']
# Application definition
# ==================== 应用配置 ====================
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
@@ -28,34 +25,30 @@ INSTALLED_APPS = [
'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', # 跨域中间件放最前
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', # 默认开启如API无需可注释
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# 'guanapi.middleware.OpLogMiddleware', # 【待确认】这是旧项目的自定义中间件,新项目需要吗?
]
ROOT_URLCONF = 'a_long_dianjing.urls'
@@ -77,57 +70,40 @@ TEMPLATES = [
WSGI_APPLICATION = 'a_long_dianjing.wsgi.application'
# Database
# 根据你提供的信息配置 (本地开发端口3307)
# ==================== 数据库 ====================
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': _secrets.DATABASE_NAME,
'USER': _secrets.DATABASE_USER,
'HOST': _secrets.DATABASE_HOST,
'PASSWORD': _secrets.DATABASE_PASSWORD,
'PORT': _secrets.DATABASE_PORT,
'NAME': app_secrets.DATABASE_NAME,
'USER': app_secrets.DATABASE_USER,
'HOST': app_secrets.DATABASE_HOST,
'PASSWORD': app_secrets.DATABASE_PASSWORD,
'PORT': app_secrets.DATABASE_PORT,
'OPTIONS': {
'charset': 'utf8mb4',
'init_command': "SET time_zone='+08:00'", # 保持与旧项目一致
'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',
},
{'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' # 使用中文
# ==================== 国际化 ====================
LANGUAGE_CODE = 'zh-Hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_TZ = False # 保持与旧项目一致不使用UTC时区
USE_TZ = False
# 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配置
# ==================== JWT 配置 ====================
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=36500),
'REFRESH_TOKEN_LIFETIME': timedelta(days=36500),
@@ -143,7 +119,6 @@ SIMPLE_JWT = {
'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',
@@ -153,16 +128,13 @@ SIMPLE_JWT = {
'JTI_CLAIM': 'jti',
}
# 添加自定义认证后端
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
]
# 必须指定用户模型即使它不继承AbstractBaseUser
AUTH_USER_MODEL = 'yonghu.UserMain'
# DRF配置也非常简单
# ==================== DRF 配置 ====================
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
@@ -171,48 +143,27 @@ REST_FRAMEWORK = {
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle', # 匿名用户限制
'rest_framework.throttling.UserRateThrottle', # 登录用户限制
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/minute', # 匿名用户每分钟100次
'user': '1000/minute', # 登录用户每分钟1000次
'anon': '100/minute',
'user': '1000/minute',
},
# 🔥 关键:使用自定义异常处理器
'EXCEPTION_HANDLER': 'utils.exception_handler.custom_exception_handler',
# 🔥 生产环境只返回JSON不返回HTML更安全
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
],
}
# ==================== H5 域名 ====================
H5_DOMAIN = 'https://www.abas.asia:666'
# settings.py
# H5静态页面域名配置
H5_DOMAIN = 'https://www.abas.asia:666' # 生产环境
# H5_DOMAIN = 'https://test-h5.yourdomain.com' # 测试环境
# CORS settings
# ==================== CORS 配置 ====================
CORS_ALLOWED_ORIGINS = [
"http://localhost:8080", # 假设前端开发服务器地址
"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",
@@ -220,126 +171,73 @@ CORS_ALLOWED_ORIGINS = [
"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_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",
"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 = _secrets.COS_SECRET_ID
COS_SECRET_KEY = _secrets.COS_SECRET_KEY
COS_REGION = _secrets.COS_REGION
COS_BUCKET = _secrets.COS_BUCKET
COS_SCHEME = 'https' # 使用HTTPS
# ==================== 腾讯云 COS ====================
COS_SECRET_ID = app_secrets.COS_SECRET_ID
COS_SECRET_KEY = app_secrets.COS_SECRET_KEY
COS_REGION = app_secrets.COS_REGION
COS_BUCKET = app_secrets.COS_BUCKET
COS_SCHEME = 'https'
COS_DOMAIN = f'https://{COS_BUCKET}.cos.{COS_REGION}.myqcloud.com'
COS_APPID = _secrets.COS_APPID
COS_STS_DURATION = 7200 # STS临时凭证有效期
COS_APPID = app_secrets.COS_APPID
COS_STS_DURATION = 7200
# ==================== 业务相关配置 (值先空着,结构可保留) ====================
# ==================== 微信小程序 & 服务号 ====================
WEIXIN_APPID = app_secrets.WEIXIN_APPID
WEIXIN_SECRET = app_secrets.WEIXIN_SECRET
WEIXIN_MCHID = app_secrets.WEIXIN_MCHID
WEIXIN_SHANGHUMIYAO = app_secrets.WEIXIN_SHANGHUMIYAO
WEIXIN_OFFICIAL_APPID = app_secrets.WEIXIN_OFFICIAL_APPID
WEIXIN_OFFICIAL_SECRET = app_secrets.WEIXIN_OFFICIAL_SECRET
WEIXIN_OFFICIAL_TOKEN = app_secrets.WEIXIN_OFFICIAL_TOKEN
WEIXIN_OFFICIAL_ENCODING_AES_KEY = app_secrets.WEIXIN_OFFICIAL_ENCODING_AES_KEY
WEIXIN_TEMPLATE_ID = app_secrets.WEIXIN_TEMPLATE_ID
WEIXIN_TEMPLATE_MAX_PER_MINUTE = 950
WEIXIN_TEMPLATE_BATCH_SIZE = 100
WEIXIN_CERT_PATH = app_secrets.WEIXIN_CERT_PATH
WEIXIN_KEY_PATH = app_secrets.WEIXIN_KEY_PATH
# 微信小程序配置
WEIXIN_APPID = _secrets.WEIXIN_APPID
WEIXIN_SECRET = _secrets.WEIXIN_SECRET
WEIXIN_MCHID = _secrets.WEIXIN_MCHID
WEIXIN_SHANGHUMIYAO = _secrets.WEIXIN_SHANGHUMIYAO
# 服务号配置
WEIXIN_OFFICIAL_APPID = _secrets.WEIXIN_OFFICIAL_APPID
WEIXIN_OFFICIAL_SECRET = _secrets.WEIXIN_OFFICIAL_SECRET
WEIXIN_OFFICIAL_TOKEN = _secrets.WEIXIN_OFFICIAL_TOKEN
WEIXIN_OFFICIAL_ENCODING_AES_KEY = _secrets.WEIXIN_OFFICIAL_ENCODING_AES_KEY
WEIXIN_TEMPLATE_ID = _secrets.WEIXIN_TEMPLATE_ID
# 🆕【新增】模板消息配置
WEIXIN_TEMPLATE_MAX_PER_MINUTE = 950 # 每分钟最大发送量留50缓冲
WEIXIN_TEMPLATE_BATCH_SIZE = 100 # 每批发送数量
WEIXIN_CERT_PATH = _secrets.WEIXIN_CERT_PATH
WEIXIN_KEY_PATH = _secrets.WEIXIN_KEY_PATH
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" # 会员支付回调地址
# 考核支付配置
# ==================== 微信支付回调地址 ====================
WEIXIN_NOTIFY_URL = 'https://www.abas.asia/hqhd/dingdan/pay-notify/'
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" # 商家充值订单前缀
KAOHE_PAY_NOTIFY_URL = 'https://www.abas.asia/hqhd/yonghu/kh_huitiao'
KAOHE_PAY_DEFAULT_FEE = 5.00
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'
# 罚款缴纳微信支付配置
FAKUAN_PAY_DESCRIPTION = '罚款缴纳' # 支付描述
FAKUAN_PAY_NOTIFY_URL = 'https://www.abas.asia/hqhd/yonghu/fk_huitiao' # 微信支付回调地址(根据实际域名调整)
# 订单ID前缀配置
# ==================== 订单ID前缀 ====================
ORDER_PREFIX = {
'yajin': 'CzYJ', # 押金订单前缀
'jifen': 'CzJF', # 积分订单前缀
'huiyuan': 'CzHY', # 会员订单前缀
'yajin': 'CzYJ',
'jifen': 'CzJF',
'huiyuan': 'CzHY',
}
# 腾讯云即时通讯 (IM) - 开发环境可暂不配置
# ==================== 腾讯云 IM ====================
TENCENT_IM_CONFIG = {
'SDK_APP_ID': 0, # 整数
'SDK_APP_ID': 0,
'SECRET_KEY': '',
}
# 阿里云短信 - 开发环境可暂不配置
# ==================== 阿里云短信 ====================
ALIYUN_SMS_CONFIG = {
'ACCESS_KEY_ID': '',
'ACCESS_KEY_SECRET': '',
@@ -348,59 +246,27 @@ ALIYUN_SMS_CONFIG = {
'REGION_ID': 'cn-hangzhou',
}
# HashID (用于加密ID旧项目有新项目需要吗)
# HASHID_FIELD_SALT = 'a_long_dianjing_2025_salt' # 【待确认】请设置你的盐值
# settings.py 尾部添加
# settings.py
# settings.py
# settings.py 中添加配置
GOEASY_APPKEY = _secrets.GOEASY_APPKEY
GOEASY_SECRET = _secrets.GOEASY_SECRET
GOEASY_REST_URL = 'https://rest-hz.goeasy.io/v2/im/message' # 正式环境
"""
阿龙电竞 - Django设置文件中的Celery配置部分
请在您的settings.py中添加以下配置
"""
# ==================== GoEasy IM ====================
GOEASY_APPKEY = app_secrets.GOEASY_APPKEY
GOEASY_SECRET = app_secrets.GOEASY_SECRET
GOEASY_REST_URL = 'https://rest-hz.goeasy.io/v2/im/message'
# ==================== Celery 配置 ====================
# Redis作为消息代理和结果后端
REDIS_HOST = _secrets.REDIS_HOST
REDIS_PORT = _secrets.REDIS_PORT
REDIS_PASSWORD = _secrets.REDIS_PASSWORD
REDIS_DB_CELERY = _secrets.REDIS_DB_CELERY
REDIS_DB_RESULT = _secrets.REDIS_DB_RESULT
REDIS_HOST = app_secrets.REDIS_HOST
REDIS_PORT = app_secrets.REDIS_PORT
REDIS_PASSWORD = app_secrets.REDIS_PASSWORD
REDIS_DB_CELERY = app_secrets.REDIS_DB_CELERY
REDIS_DB_RESULT = app_secrets.REDIS_DB_RESULT
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': f'redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/2',
'TIMEOUT': 300,
'OPTIONS': {
# 不需要 CLIENT_CLASS
}
'OPTIONS': {}
}
}
# 构建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}'
@@ -408,120 +274,56 @@ 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_TASK_TIME_LIMIT = 300
CELERY_TASK_SOFT_TIME_LIMIT = 240
CELERY_WORKER_CONCURRENCY = 4
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',
},
'default': {'exchange': 'default', 'routing_key': 'default'},
'order_tasks': {'exchange': 'order_tasks', 'routing_key': 'order_tasks'},
'periodic_tasks': {'exchange': 'periodic_tasks', 'routing_key': 'periodic_tasks'},
}
# ==================== 定时任务时间配置 ====================
# 所有时间配置都在这里,方便修改
ORDER_EXPIRE_SECONDS = 48 * 60 * 60 # 48小时 = 172800秒
DAILY_TASK_HOUR = 0
DAILY_TASK_MINUTE = 30
MONTHLY_TASK_DAY = 1
MONTHLY_TASK_HOUR = 0
MONTHLY_TASK_MINUTE = 0
ORDER_CHECK_INTERVAL = 5
# 订单超时时间(秒) - 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 = _secrets.CROSS_PLATFORM_TOKEN
PARTNER_BASE_URL = _secrets.PARTNER_BASE_URL
# 对方平台各接口路径(可根据实际情况调整,但双方需保持一致)
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' # 退款通知
AGENT_SHANGJIA_ID = _secrets.AGENT_SHANGJIA_ID
CROSS_PLATFORM_MEMBER_ID = _secrets.CROSS_PLATFORM_MEMBER_ID
# 默认游戏类型ID当对方未传递时使用
# ==================== 跨平台订单互通 ====================
CROSS_PLATFORM_TOKEN = app_secrets.CROSS_PLATFORM_TOKEN
CROSS_PLATFORM_SYNC_TOKEN = app_secrets.CROSS_PLATFORM_SYNC_TOKEN
PARTNER_BASE_URL = app_secrets.PARTNER_BASE_URL
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'
AGENT_SHANGJIA_ID = app_secrets.AGENT_SHANGJIA_ID
CROSS_PLATFORM_MEMBER_ID = app_secrets.CROSS_PLATFORM_MEMBER_ID
CROSS_PLATFORM_GAME_TYPE_ID = 1
CROSS_PLATFORM_WHITELIST = app_secrets.CROSS_PLATFORM_WHITELIST
SPECIAL_GUANSHI_IDS = app_secrets.SPECIAL_GUANSHI_IDS
CROSS_PLATFORM_WHITELIST = _secrets.CROSS_PLATFORM_WHITELIST
SPECIAL_GUANSHI_IDS = _secrets.SPECIAL_GUANSHI_IDS
# ==================== 微信支付 V3 ====================
WECHAT_PAY_V3_CONFIG = {
'MCHID': _secrets.WECHAT_PAY_V3_MCHID,
'APPID': _secrets.WECHAT_PAY_V3_APPID,
'API_V3_KEY': _secrets.WECHAT_PAY_V3_API_V3_KEY,
'PRIVATE_KEY_PATH': _secrets.WECHAT_PAY_V3_PRIVATE_KEY_PATH,
'CERT_SERIAL_NO': _secrets.WECHAT_PAY_V3_CERT_SERIAL_NO,
'PLATFORM_CERT_DIR': _secrets.WECHAT_PAY_V3_PLATFORM_CERT_DIR,
'MCHID': app_secrets.WECHAT_PAY_V3_MCHID,
'APPID': app_secrets.WECHAT_PAY_V3_APPID,
'API_V3_KEY': app_secrets.WECHAT_PAY_V3_API_V3_KEY,
'PRIVATE_KEY_PATH': app_secrets.WECHAT_PAY_V3_PRIVATE_KEY_PATH,
'CERT_SERIAL_NO': app_secrets.WECHAT_PAY_V3_CERT_SERIAL_NO,
'PLATFORM_CERT_DIR': app_secrets.WECHAT_PAY_V3_PLATFORM_CERT_DIR,
'NOTIFY_URL': 'https://www.abas.asia/hqhd/yonghu/tixiancallback',
'TRANSFER_SCENE_ID': '1005',
}
CROSS_PLATFORM_SYNC_TOKEN = _secrets.CROSS_PLATFORM_SYNC_TOKEN

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
import hmac
import threading
import traceback
from calendar import monthrange
@@ -1234,6 +1235,9 @@ class PartnerGetOrderDataView(APIView):
parser_classes = [JSONParser]
def post(self, request):
# token = request.data.get('token')
# if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN):
# return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200)
try:
# 1. 获取参数
partner_order_id = request.data.get('order_id', '').strip()
@@ -1339,6 +1343,9 @@ class PartnerRefundNotifyView(APIView):
parser_classes = [JSONParser]
def post(self, request):
# token = request.data.get('token')
# if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN):
# return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200)
# 1. 参数校验(事务外执行)
partner_order_id = request.data.get('order_id', '').strip()
partner_club_id = request.data.get('club_id', '').strip()
@@ -2093,6 +2100,9 @@ class PartnerTransferHallNotifyView(APIView):
permission_classes = [AllowAny]
def post(self, request):
# token = request.data.get('token')
# if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN):
# return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200)
try:
# 1. 获取参数
partner_order_id = request.data.get('order_id', '').strip()
@@ -2618,6 +2628,9 @@ class PartnerUpdateSelfConfigView(APIView):
parser_classes = [JSONParser]
def post(self, request):
# token = request.data.get('token')
# if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN):
# return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200)
# 1. 获取参数
club_id = request.data.get('club_id', '').strip()
updates = request.data.get('updates', {})
@@ -3174,6 +3187,9 @@ class PartnerPunishNotifyView(APIView):
parser_classes = [JSONParser]
def post(self, request):
# token = request.data.get('token')
# if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN):
# return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200)
try:
order_id = request.data.get('order_id', '').strip()
club_id = request.data.get('club_id', '').strip()
@@ -9722,6 +9738,9 @@ class PartnerFineNotifyView(APIView):
parser_classes = [JSONParser]
def post(self, request):
# token = request.data.get('token')
# if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN):
# return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200)
try:
order_id = request.data.get('order_id', '').strip() # 对方平台订单ID
club_id = request.data.get('club_id', '').strip()
@@ -12453,8 +12472,8 @@ class KptxwztjbView(APIView):
return Response({'code': 400, 'msg': '参数不完整'}, status=status.HTTP_400_BAD_REQUEST)
# 2. 验证同步令牌
SYNC_TOKEN = getattr(settings, 'CROSS_PLATFORM_SYNC_TOKEN', 'xK9mP2vL8qW5rT7y')
if token != SYNC_TOKEN:
SYNC_TOKEN = getattr(settings, 'CROSS_PLATFORM_SYNC_TOKEN', None)
if not SYNC_TOKEN or token != SYNC_TOKEN:
logger.warning(f"无效的同步令牌: {token[:8]}...")
return Response({'code': 403, 'msg': '无效令牌'}, status=status.HTTP_403_FORBIDDEN)

View File

@@ -11,6 +11,7 @@ click-repl==0.3.0
cos_python_sdk_v5==1.9.40
crcmod==1.7
cron_descriptor==2.0.6
defusedxml
Django==4.2.27
django-celery-beat==2.5.0
django-celery-results==2.5.0

View File

@@ -465,7 +465,7 @@ import random
import string
import time
import hashlib
import xml.etree.ElementTree as ET
import defusedxml.ElementTree as ET
import requests
from django.conf import settings
from rest_framework.views import APIView
@@ -731,7 +731,7 @@ from django.views import View
from django.http import HttpResponse
from yonghu.models import UserDashou, UserMain
from shangpin.models import Czjilu
import xml.etree.ElementTree as ET
import defusedxml.ElementTree as ET
import hashlib
from django.conf import settings
import logging
@@ -1686,7 +1686,7 @@ import random
import string
import time
import hashlib
import xml.etree.ElementTree as ET
import defusedxml.ElementTree as ET
import requests
from django.conf import settings
import logging
@@ -2387,7 +2387,7 @@ class HuiyuanHuitiao(View):
from django.http import HttpResponse
from yonghu.models import UserDashou, UserMain, UserGuanshi
from shangpin.models import Czjilu, Huiyuan, Huiyuangoumai, Gsfenhong
import xml.etree.ElementTree as ET
import defusedxml.ElementTree as ET
import hashlib
from django.conf import settings
from django.utils import timezone
@@ -3016,7 +3016,7 @@ import time
import random
import string
import hashlib
import xml.etree.ElementTree as ET
import defusedxml.ElementTree as ET
import requests
from decimal import Decimal
from django.conf import settings

View File

@@ -54,5 +54,26 @@ def custom_exception_handler(exc, context):
return Response(standard_response, status=response.status_code)
# 对于非DRF异常,这里可以根据需要处理
# 对于非DRF异常response为None生产环境返回通用错误信息
if response is None:
from django.conf import settings
request = context.get('request')
user_info = '未知'
if request:
user_info = f"用户: {request.user.id if request.user.is_authenticated else '未登录'}"
logger.error(
f"未处理异常 - 路径: {request.path if request else '未知'}, "
f"方法: {request.method if request else '未知'}, {user_info}, "
f"错误类型: {type(exc).__name__}, 错误: {str(exc)}"
)
if not settings.DEBUG:
return Response(
{'code': 500, 'message': '服务器内部错误,请稍后重试'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
return Response(
{'code': 500, 'message': str(exc)},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
return response

View File

@@ -5,255 +5,6 @@ from django.conf import settings
from PIL import Image, UnidentifiedImageError
import io
'''try:
from qcloud_cos import CosConfig, CosS3Client
TENCENT_COS_AVAILABLE = True
except ImportError:
TENCENT_COS_AVAILABLE = False
try:
import oss2
ALIYUN_OSS_AVAILABLE = True
except ImportError:
ALIYUN_OSS_AVAILABLE = False
def validate_image(file):
"""
验证图片文件
使用Pillow检查图片是否有效
"""
try:
# 打开图片但不保存到磁盘
img = Image.open(file)
img.verify() # 验证文件完整性
# 重置文件指针因为PIL读取后会移动指针
file.seek(0)
# 检查图片格式
allowed_formats = ['JPEG', 'PNG', 'GIF', 'WEBP', 'BMP']
if img.format not in allowed_formats:
return False, f"不支持{img.format}格式请使用JPEG、PNG、GIF、WEBP或BMP格式"
# 检查图片大小最大5MB
file_size = len(file.read())
file.seek(0)
if file_size > 5 * 1024 * 1024:
return False, "图片大小不能超过5MB"
# 检查图片尺寸最大2000x2000
img = Image.open(file)
file.seek(0)
if img.width > 3000 or img.height > 3000:
return False, "图片尺寸不能超过2000x2000像素"
return True, "图片验证通过"
except UnidentifiedImageError:
return False, "文件不是有效的图片"
except Exception as e:
return False, f"图片验证失败: {str(e)}"
def get_oss_client():
"""
获取OSS客户端
根据配置自动选择云厂商
"""
oss_type = getattr(settings, 'OSS_TYPE', 'tencent') # 默认为腾讯云
if oss_type == 'tencent' and TENCENT_COS_AVAILABLE:
# 腾讯云COS
config = CosConfig(
Region=getattr(settings, 'COS_REGION', 'ap-shanghai'),
SecretId=getattr(settings, 'COS_SECRET_ID', ''),
SecretKey=getattr(settings, 'COS_SECRET_KEY', ''),
Scheme=getattr(settings, 'COS_SCHEME', 'https')
)
return CosS3Client(config)
elif oss_type == 'aliyun' and ALIYUN_OSS_AVAILABLE:
# 阿里云OSS
auth = oss2.Auth(
getattr(settings, 'ALIYUN_ACCESS_KEY_ID', ''),
getattr(settings, 'ALIYUN_ACCESS_KEY_SECRET', '')
)
endpoint = getattr(settings, 'ALIYUN_OSS_ENDPOINT', '')
bucket_name = getattr(settings, 'ALIYUN_OSS_BUCKET', '')
return oss2.Bucket(auth, endpoint, bucket_name)
else:
raise ImportError(f"不支持{oss_type}云存储或相关SDK未安装")
def upload_to_oss(file_obj, file_path):
"""
通用上传文件到OSS
file_obj: Django的UploadedFile对象或类文件对象
file_path: 在OSS中的相对路径'avatar/123/abc.jpg'
返回: 文件访问URL
"""
try:
oss_type = getattr(settings, 'OSS_TYPE', 'tencent')
if oss_type == 'tencent':
return _upload_to_tencent_cos(file_obj, file_path)
elif oss_type == 'aliyun':
return _upload_to_aliyun_oss(file_obj, file_path)
else:
raise ValueError(f"不支持的OSS类型: {oss_type}")
except Exception as e:
print(f"OSS上传失败: {e}")
return None
def _upload_to_tencent_cos(file_obj, file_path):
"""
上传到腾讯云COS
"""
client = get_oss_client()
# 获取文件扩展名
ext = file_path.split('.')[-1].lower() if '.' in file_path else ''
content_type_map = {
'jpg': 'image/jpeg', 'jpeg': 'image/jpeg',
'png': 'image/png', 'gif': 'image/gif',
'webp': 'image/webp', 'bmp': 'image/bmp'
}
content_type = content_type_map.get(ext, 'image/jpeg')
# 如果是Django的UploadedFile需要读取内容
if hasattr(file_obj, 'read'):
file_content = file_obj.read()
else:
file_content = file_obj
# 上传文件
response = client.put_object(
Bucket=getattr(settings, 'COS_BUCKET', ''),
Body=file_content,
Key=file_path,
ContentType=content_type,
ContentDisposition='inline'
)
# 返回完整URL
domain = getattr(settings, 'COS_DOMAIN', '')
return f"{domain}/{file_path}"
def _upload_to_aliyun_oss(file_obj, file_path):
"""
上传到阿里云OSS
"""
bucket = get_oss_client()
# 如果是Django的UploadedFile需要读取内容
if hasattr(file_obj, 'read'):
file_content = file_obj.read()
else:
file_content = file_obj
# 上传文件
result = bucket.put_object(file_path, file_content)
if result.status == 200:
# 阿里云OSS返回的URL
domain = getattr(settings, 'ALIYUN_OSS_DOMAIN', '')
return f"{domain}/{file_path}"
else:
return None
def delete_from_oss(file_url):
"""
从OSS删除文件
file_url: 可以是完整URL或相对路径
"""
try:
if not file_url:
return False
oss_type = getattr(settings, 'OSS_TYPE', 'tencent')
# 从URL中提取文件路径
if oss_type == 'tencent':
domain = getattr(settings, 'COS_DOMAIN', '')
# 判断是完整URL还是相对路径
if file_url.startswith('http'):
# 完整URL
if domain not in file_url:
return False # URL不属于我们的域名
file_key = file_url.replace(f"{domain}/", "")
else:
# 相对路径,直接使用
file_key = file_url
return _delete_from_tencent_cos(file_key)
elif oss_type == 'aliyun':
domain = getattr(settings, 'ALIYUN_OSS_DOMAIN', '')
if file_url.startswith('http'):
if domain not in file_url:
return False
file_key = file_url.replace(f"{domain}/", "")
else:
file_key = file_url
return _delete_from_aliyun_oss(file_key)
else:
return False
except Exception as e:
print(f"OSS删除失败: {e}")
return False
def _delete_from_tencent_cos(file_key):
"""
从腾讯云COS删除文件
"""
client = get_oss_client()
try:
client.delete_object(
Bucket=getattr(settings, 'COS_BUCKET', ''),
Key=file_key
)
return True
except Exception as e:
print(f"腾讯云COS删除失败: {e}")
return False
def _delete_from_aliyun_oss(file_key):
"""
从阿里云OSS删除文件
"""
bucket = get_oss_client()
try:
bucket.delete_object(file_key)
return True
except Exception as e:
print(f"阿里云OSS删除失败: {e}")
return False'''
# utils/oss_utils.py
import os
import uuid
from django.conf import settings
from PIL import Image, UnidentifiedImageError
import io
try:
from qcloud_cos import CosConfig, CosS3Client

View File

@@ -1,18 +1,57 @@
import redis
import uuid
import logging
from django.conf import settings
from django.core.cache import cache
redis_client = redis.Redis(
logger = logging.getLogger(__name__)
# Redis客户端延迟初始化
_redis_client = None
def _get_redis_client():
global _redis_client
if _redis_client is None:
import redis
_redis_client = redis.Redis(
host=settings.REDIS_HOST,
port=settings.REDIS_PORT,
password=settings.REDIS_PASSWORD,
db=2,
decode_responses=True
db=settings.REDIS_DB_CELERY,
decode_responses=True,
)
return _redis_client
def acquire_lock(lock_key, timeout=10):
"""获取锁,返回是否成功"""
return redis_client.set(lock_key, '1', nx=True, ex=timeout)
"""获取分布式锁,返回锁标识符失败返回None"""
client = _get_redis_client()
identifier = str(uuid.uuid4())
result = client.set(lock_key, identifier, nx=True, ex=timeout)
if result:
logger.debug(f"获取锁成功: {lock_key}, 标识: {identifier}")
return identifier
return None
def release_lock(lock_key):
"""释放锁"""
redis_client.delete(lock_key)
def release_lock(lock_key, identifier):
"""释放分布式锁(仅当标识符匹配时才释放)"""
if identifier is None:
return False
client = _get_redis_client()
lua_script = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
"""
try:
result = client.eval(lua_script, 1, lock_key, identifier)
if result:
logger.debug(f"释放锁成功: {lock_key}")
else:
logger.warning(f"释放锁失败(标识不匹配或锁已过期): {lock_key}")
return bool(result)
except Exception as e:
logger.error(f"释放锁异常: {lock_key}, 错误: {e}")
return False

View File

@@ -1,112 +1,4 @@
# utils/wechat_v3.py
'''import time
import hashlib
import base64
import json
from urllib.parse import urlparse
import rsa
from Crypto.Cipher import AES
from django.conf import settings
def load_private_key():
"""加载商户私钥PEM格式"""
with open(settings.WECHAT_PAY_V3_CONFIG['PRIVATE_KEY_PATH'], 'rb') as f:
key_data = f.read()
return rsa.PrivateKey.load_pkcs1(key_data)
def build_authorization(method, url, body):
"""
生成V3接口的Authorization头
:param method: GET/POST
:param url: 完整URL含域名和路径
:param body: 请求体JSON字符串
:return: Authorization字符串
"""
private_key = load_private_key()
mchid = settings.WECHAT_PAY_V3_CONFIG['MCHID']
serial_no = settings.WECHAT_PAY_V3_CONFIG['CERT_SERIAL_NO']
# 提取URL路径不含query
parsed = urlparse(url)
path = parsed.path
if parsed.query:
path += '?' + parsed.query
timestamp = str(int(time.time()))
nonce = hashlib.md5((timestamp + 'wechat').encode()).hexdigest()[:32]
# 构造签名串
message = '\n'.join([method, path, timestamp, nonce, body]) + '\n'
# 使用私钥签名SHA256
signature = rsa.sign(message.encode('utf-8'), private_key, 'SHA-256')
signature_b64 = base64.b64encode(signature).decode('utf-8')
# 组装Authorization
auth = f'WECHATPAY2-SHA256-RSA2048 mchid="{mchid}",nonce_str="{nonce}",timestamp="{timestamp}",serial_no="{serial_no}",signature="{signature_b64}"'
return auth
def verify_wechat_sign(headers, body):
"""
验证微信回调的签名
需要提前从微信平台下载证书公钥,存放在 PLATFORM_CERT_DIR 下,文件名 = 序列号.pem
:param headers: 请求头
:param body: 原始请求体字符串
:return: bool
"""
serial = headers.get('Wechatpay-Serial')
signature = headers.get('Wechatpay-Signature')
timestamp = headers.get('Wechatpay-Timestamp')
nonce = headers.get('Wechatpay-Nonce')
if not all([serial, signature, timestamp, nonce]):
return False
# 构造验签名串
message = '\n'.join([timestamp, nonce, body]) + '\n'
# 读取对应序列号的平台证书公钥
cert_path = f"{settings.WECHAT_PAY_V3_CONFIG['PLATFORM_CERT_DIR']}/{serial}.pem"
try:
with open(cert_path, 'rb') as f:
pub_key = rsa.PublicKey.load_pkcs1_openssl_pem(f.read())
except FileNotFoundError:
# 如果证书不存在,可尝试下载(生产环境建议定时下载)
return False
signature_bytes = base64.b64decode(signature)
try:
rsa.verify(message.encode('utf-8'), signature_bytes, pub_key)
return True
except rsa.VerificationError:
return False
def decrypt_callback_ciphertext(associated_data, nonce, ciphertext):
"""
使用APIv3密钥解密回调中的加密数据
:param associated_data: 附加数据
:param nonce: 随机串
:param ciphertext: 密文Base64
:return: 解密后的JSON字符串
"""
api_v3_key = settings.WECHAT_PAY_V3_CONFIG['API_V3_KEY'].encode('utf-8')
nonce_bytes = nonce.encode('utf-8')
ciphertext_bytes = base64.b64decode(ciphertext)
# AES-GCM解密
cipher = AES.new(api_v3_key, AES.MODE_GCM, nonce=nonce_bytes)
if associated_data:
cipher.update(associated_data.encode('utf-8'))
plaintext = cipher.decrypt_and_verify(ciphertext_bytes[:-16], ciphertext_bytes[-16:])
return plaintext.decode('utf-8')'''
# utils/wechat_v3.py
import re
import time
import hashlib
import base64
@@ -178,6 +70,16 @@ def verify_wechat_sign(headers, body):
if not all([serial, signature, timestamp, nonce]):
return False
if not re.match(r'^[A-Za-z0-9-]+$', serial):
return False
try:
ts = int(headers.get('Wechatpay-Timestamp', '0'))
if abs(time.time() - ts) > 300: # 5分钟有效期
return False
except (ValueError, TypeError):
return False
message = '\n'.join([timestamp, nonce, body]) + '\n'
cert_path = f"{settings.WECHAT_PAY_V3_CONFIG['PLATFORM_CERT_DIR']}/{serial}.pem"
if not os.path.exists(cert_path):

View File

@@ -141,7 +141,8 @@ class TixianZddkshApplyView(APIView):
user_main = request.user
lock_key = f'tixian_audit_apply:{user_main.yonghuid}'
if not acquire_lock(lock_key, timeout=10):
identifier = acquire_lock(lock_key, timeout=10)
if not identifier:
return Response({'code': 429, 'msg': '操作频繁,请稍后重试'})
try:
@@ -172,7 +173,7 @@ class TixianZddkshApplyView(APIView):
return Response({'code': 0, 'msg': '提现申请已提交,请等待审核', 'data': data})
finally:
release_lock(lock_key)
release_lock(lock_key, identifier)
def process_audit_collect(request):
@@ -233,7 +234,8 @@ def process_audit_collect(request):
return quota_resp
lock_key = f'tixian_collect:{shenhe_danhao}'
if not acquire_lock(lock_key, timeout=15):
identifier = acquire_lock(lock_key, timeout=15)
if not identifier:
return Response({'code': 429, 'msg': '操作频繁,请稍后重试'})
tixian_id = None
@@ -372,7 +374,7 @@ def process_audit_collect(request):
})
finally:
release_lock(lock_key)
release_lock(lock_key, identifier)
except Exception as e:
logger.error(

File diff suppressed because it is too large Load Diff