排行榜按收益/流水排序;小程序配置与页面资源入库可后台管理
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
"""
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from utils.xcx_sys_config import wx_cfg
|
||||
|
||||
logger = logging.getLogger('weixin_broadcast')
|
||||
|
||||
@@ -29,7 +29,7 @@ def build_order_broadcast_payload(dingdan, order_type='普通订单', game_type=
|
||||
|
||||
def submit_dingdan_guangbo(order_info):
|
||||
"""提交 Celery 广播任务(broadcast 优先队列)"""
|
||||
if not getattr(settings, 'WEIXIN_BROADCAST_ENABLED', True):
|
||||
if not wx_cfg.WEIXIN_BROADCAST_ENABLED:
|
||||
logger.info('服务号广播已关闭,跳过')
|
||||
return False
|
||||
if not order_info or not order_info.get('dingdan_id'):
|
||||
|
||||
91
utils/page_ziyuan_service.py
Normal file
91
utils/page_ziyuan_service.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""小程序页面静态资源(背景图/图标)— 数据库配置 + 默认值"""
|
||||
from django.core.cache import cache
|
||||
|
||||
_CACHE_PREFIX = 'xcx_page_ziyuan_v1'
|
||||
_CACHE_TTL = 120
|
||||
|
||||
# 默认资源(相对 OSS 路径,不含域名)
|
||||
DEFAULT_ZIYUAN = {
|
||||
'paihangbang.pageBg': 'beijing/paihangbang/page-bg.jpg',
|
||||
'paihangbang.cardBg': 'beijing/paihangbang/list-card-bg.png',
|
||||
'paihangbang.emptyIcon': 'beijing/paihangbang/empty.png',
|
||||
'paihangbang.refreshIcon': 'beijing/paihangbang/icon-refresh.png',
|
||||
'shangjiaduan.pageBg': 'beijing/shangjiaduan/page_bg.png',
|
||||
'shangjiaduan.avatarCardBg': 'beijing/shangjiaduan/avatar_card_bg.png',
|
||||
'shangjiaduan.assetCardBg': 'beijing/shangjiaduan/asset_card_bg.png',
|
||||
'shangjiaduan.statCardBg': 'beijing/shangjiaduan/stat_card_bg.png',
|
||||
'shangjiaduan.funcItemBg': 'beijing/shangjiaduan/func_item_bg.png',
|
||||
'shangjiaduan.primaryFuncBg': 'beijing/shangjiaduan/primary_func_bg.png',
|
||||
'shangjiaduan.highlightFuncBg': 'beijing/shangjiaduan/highlight_func_bg.png',
|
||||
'shangjiaduan.iconRelease': 'beijing/shangjiaduan/icon_release.png',
|
||||
'shangjiaduan.iconFast': 'beijing/shangjiaduan/icon_fast.png',
|
||||
'shangjiaduan.iconOrders': 'beijing/shangjiaduan/icon_orders.png',
|
||||
'shangjiaduan.iconService': 'beijing/shangjiaduan/icon_service.png',
|
||||
'shangjiaduan.iconPunish': 'beijing/shangjiaduan/icon_punish.png',
|
||||
'shangjiaduan.iconRank': 'beijing/shangjiaduan/icon_rank.png',
|
||||
'shangjiaduan.iconRecharge': 'beijing/shangjiaduan/icon_recharge.png',
|
||||
'shangjiaduan.iconWithdraw': 'beijing/shangjiaduan/icon_withdraw.png',
|
||||
'shangjiaduan.iconRefresh': 'beijing/shangjiaduan/icon_refresh.png',
|
||||
'shangjiaduan.iconCopy': 'beijing/shangjiaduan/icon_copy.png',
|
||||
'shangjiaduan.avatarFrame': 'beijing/shangjiaduan/avatar_frame.png',
|
||||
}
|
||||
|
||||
|
||||
def clear_page_ziyuan_cache():
|
||||
cache.delete_many([f'{_CACHE_PREFIX}:all', f'{_CACHE_PREFIX}:paihangbang', f'{_CACHE_PREFIX}:shangjiaduan'])
|
||||
|
||||
|
||||
def _load_all_from_db():
|
||||
try:
|
||||
from peizhi.models import XcxPageZiyuan
|
||||
rows = XcxPageZiyuan.objects.all()
|
||||
data = {}
|
||||
for r in rows:
|
||||
key = f'{r.ziyuan_zu}.{r.ziyuan_key}'
|
||||
if r.ziyuan_path:
|
||||
data[key] = r.ziyuan_path.lstrip('/')
|
||||
return data
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def get_all_ziyuan_merged():
|
||||
cached = cache.get(f'{_CACHE_PREFIX}:all')
|
||||
if cached is not None:
|
||||
return cached
|
||||
merged = dict(DEFAULT_ZIYUAN)
|
||||
merged.update(_load_all_from_db())
|
||||
cache.set(f'{_CACHE_PREFIX}:all', merged, _CACHE_TTL)
|
||||
return merged
|
||||
|
||||
|
||||
def get_ziyuan_group(zu: str) -> dict:
|
||||
cache_key = f'{_CACHE_PREFIX}:{zu}'
|
||||
cached = cache.get(cache_key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
merged = get_all_ziyuan_merged()
|
||||
group = {}
|
||||
prefix = f'{zu}.'
|
||||
for k, v in merged.items():
|
||||
if k.startswith(prefix):
|
||||
group[k[len(prefix):]] = v
|
||||
cache.set(cache_key, group, _CACHE_TTL)
|
||||
return group
|
||||
|
||||
|
||||
def build_paihang_assets_for_api():
|
||||
g = get_ziyuan_group('paihangbang')
|
||||
return {
|
||||
'pageBg': g.get('pageBg', DEFAULT_ZIYUAN['paihangbang.pageBg']),
|
||||
'cardBg': g.get('cardBg', DEFAULT_ZIYUAN['paihangbang.cardBg']),
|
||||
'emptyIcon': g.get('emptyIcon', DEFAULT_ZIYUAN['paihangbang.emptyIcon']),
|
||||
'refreshIcon': g.get('refreshIcon', DEFAULT_ZIYUAN['paihangbang.refreshIcon']),
|
||||
}
|
||||
|
||||
|
||||
def build_page_assets_for_api():
|
||||
return {
|
||||
'shangjiaduan': get_ziyuan_group('shangjiaduan'),
|
||||
'paihangbang': get_ziyuan_group('paihangbang'),
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import requests
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
|
||||
from utils.xcx_sys_config import wx_cfg
|
||||
from yonghu.models import OfficialAccountUser
|
||||
|
||||
logger = logging.getLogger('weixin_broadcast')
|
||||
@@ -18,9 +19,9 @@ class WeixinBroadcastSender:
|
||||
"""微信服务号模板消息发送器(连接复用 + 线程池并发)"""
|
||||
|
||||
def __init__(self):
|
||||
self.appid = getattr(settings, 'WEIXIN_OFFICIAL_APPID', '')
|
||||
self.secret = getattr(settings, 'WEIXIN_OFFICIAL_SECRET', '')
|
||||
self.template_id = getattr(settings, 'WEIXIN_TEMPLATE_ID', '')
|
||||
self.appid = wx_cfg.WEIXIN_OFFICIAL_APPID
|
||||
self.secret = wx_cfg.WEIXIN_OFFICIAL_SECRET
|
||||
self.template_id = wx_cfg.WEIXIN_TEMPLATE_ID
|
||||
self._session = requests.Session()
|
||||
|
||||
def get_access_token(self):
|
||||
@@ -65,9 +66,9 @@ class WeixinBroadcastSender:
|
||||
'template_id': self.template_id,
|
||||
'data': template_data,
|
||||
}
|
||||
if order_id and getattr(settings, 'WEIXIN_APPID', None):
|
||||
if order_id and wx_cfg.WEIXIN_APPID:
|
||||
request_data['miniprogram'] = {
|
||||
'appid': settings.WEIXIN_APPID,
|
||||
'appid': wx_cfg.WEIXIN_APPID,
|
||||
'pagepath': f'pages/jiedan/jiedan?id={order_id}',
|
||||
}
|
||||
|
||||
@@ -98,7 +99,7 @@ class WeixinBroadcastSender:
|
||||
return {'success': True, 'msg': '无订阅用户', 'total': 0, 'success_count': 0, 'fail_count': 0}
|
||||
|
||||
max_workers = min(
|
||||
int(getattr(settings, 'WEIXIN_BROADCAST_WORKERS', 30)),
|
||||
int(wx_cfg.WEIXIN_BROADCAST_WORKERS),
|
||||
max(8, total),
|
||||
)
|
||||
logger.info(f'广播开始 订单={order_id} 人数={total} 并发={max_workers}')
|
||||
|
||||
123
utils/xcx_sys_config.py
Normal file
123
utils/xcx_sys_config.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""
|
||||
小程序/微信系统配置 — 优先读数据库 XcxSysPeizhi,空则回退 settings.py(兼容旧部署)。
|
||||
"""
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_CACHE_KEY = 'xcx_sys_peizhi_row_v1'
|
||||
_CACHE_TTL = 120
|
||||
|
||||
|
||||
def clear_xcx_sys_peizhi_cache():
|
||||
cache.delete(_CACHE_KEY)
|
||||
|
||||
|
||||
def _load_row():
|
||||
cached = cache.get(_CACHE_KEY)
|
||||
if cached is not _CACHE_NONE and cached is not None:
|
||||
return cached
|
||||
try:
|
||||
from peizhi.models import XcxSysPeizhi
|
||||
row = XcxSysPeizhi.objects.first()
|
||||
except Exception as e:
|
||||
logger.warning('读取 XcxSysPeizhi 失败,使用 settings 回退: %s', e)
|
||||
row = None
|
||||
cache.set(_CACHE_KEY, row if row else _CACHE_NONE, _CACHE_TTL)
|
||||
return row if row else None
|
||||
|
||||
|
||||
_CACHE_NONE = object()
|
||||
|
||||
|
||||
def _val(db_attr, settings_attr, default=None):
|
||||
row = _load_row()
|
||||
if row is not None:
|
||||
v = getattr(row, db_attr, None)
|
||||
if v is not None and v != '':
|
||||
return v
|
||||
if settings_attr:
|
||||
v = getattr(settings, settings_attr, default)
|
||||
return v if v is not None and v != '' else default
|
||||
return default
|
||||
|
||||
|
||||
class WxConfig:
|
||||
"""微信/小程序配置代理(属性名与 settings 保持一致,便于渐进替换)"""
|
||||
|
||||
@property
|
||||
def WEIXIN_APPID(self):
|
||||
return _val('weixin_appid', 'WEIXIN_APPID', '')
|
||||
|
||||
@property
|
||||
def WEIXIN_SECRET(self):
|
||||
return _val('weixin_secret', 'WEIXIN_SECRET', '')
|
||||
|
||||
@property
|
||||
def WEIXIN_OFFICIAL_APPID(self):
|
||||
return _val('weixin_official_appid', 'WEIXIN_OFFICIAL_APPID', '')
|
||||
|
||||
@property
|
||||
def WEIXIN_OFFICIAL_SECRET(self):
|
||||
return _val('weixin_official_secret', 'WEIXIN_OFFICIAL_SECRET', '')
|
||||
|
||||
@property
|
||||
def WEIXIN_OFFICIAL_TOKEN(self):
|
||||
return _val('weixin_official_token', 'WEIXIN_OFFICIAL_TOKEN', '')
|
||||
|
||||
@property
|
||||
def WEIXIN_OFFICIAL_ENCODING_AES_KEY(self):
|
||||
return _val('weixin_official_encoding_aes_key', 'WEIXIN_OFFICIAL_ENCODING_AES_KEY', '')
|
||||
|
||||
@property
|
||||
def WEIXIN_TEMPLATE_ID(self):
|
||||
return _val('weixin_template_id', 'WEIXIN_TEMPLATE_ID', '')
|
||||
|
||||
@property
|
||||
def WEIXIN_TEMPLATE_MAX_PER_MINUTE(self):
|
||||
return _val('weixin_template_max_per_minute', 'WEIXIN_TEMPLATE_MAX_PER_MINUTE', 950)
|
||||
|
||||
@property
|
||||
def WEIXIN_TEMPLATE_BATCH_SIZE(self):
|
||||
return _val('weixin_template_batch_size', 'WEIXIN_TEMPLATE_BATCH_SIZE', 100)
|
||||
|
||||
@property
|
||||
def WEIXIN_BROADCAST_ENABLED(self):
|
||||
row = _load_row()
|
||||
if row is not None:
|
||||
return bool(row.weixin_broadcast_enabled)
|
||||
return getattr(settings, 'WEIXIN_BROADCAST_ENABLED', True)
|
||||
|
||||
@property
|
||||
def WEIXIN_BROADCAST_WORKERS(self):
|
||||
return _val('weixin_broadcast_workers', 'WEIXIN_BROADCAST_WORKERS', 30)
|
||||
|
||||
@property
|
||||
def WEIXIN_MCHID(self):
|
||||
return _val('weixin_mchid', 'WEIXIN_MCHID', '')
|
||||
|
||||
@property
|
||||
def WEIXIN_SHANGHUMIYAO(self):
|
||||
return _val('weixin_shanghu_miyao', 'WEIXIN_SHANGHUMIYAO', '')
|
||||
|
||||
@property
|
||||
def WEIXIN_NOTIFY_URL(self):
|
||||
return _val('weixin_notify_url', 'WEIXIN_NOTIFY_URL', '')
|
||||
|
||||
@property
|
||||
def WEIXIN_CERT_PATH(self):
|
||||
return _val('weixin_cert_path', 'WEIXIN_CERT_PATH', '')
|
||||
|
||||
@property
|
||||
def WEIXIN_KEY_PATH(self):
|
||||
return _val('weixin_key_path', 'WEIXIN_KEY_PATH', '')
|
||||
|
||||
@property
|
||||
def NOTIFY_BASE_URL(self):
|
||||
return _val('notify_base_url', None, 'https://wenhe.nmslb.com/hqhd').rstrip('/')
|
||||
|
||||
|
||||
wx_cfg = WxConfig()
|
||||
Reference in New Issue
Block a user