feat: 分俱乐部推广海报背景与二维码生成

This commit is contained in:
XingQue
2026-06-26 15:52:17 +08:00
parent aaf14264d8
commit 992f4f4f18
4 changed files with 112 additions and 18 deletions

View File

@@ -3,8 +3,8 @@ from django.urls import path
from .views import ShangpinGonggaoView, AdminUpdateConfigView, AdminUploadImageView, AdminConfigQueryView, \
DashouPaihangView,GetAdminZhanghaoView, GetXiugaiJiluView,ShangjiaTianjiaMobanView, ShangjiaMobanListView, \
ShangjiaGengxinMobanView, ShangjiaShanchuMobanView, ShangjiaGenerateLinkView,KehuGetDingdanLianjieView, \
KehuTianxieDingdanView,GuanZhuAListView, ZuzhangHaibaoView,GuanshiQRCodeView,\
GetDynamicConfigView, PopupConfigView, GetWithdrawModeView,CheckPhoneAuthView,\
KehuTianxieDingdanView, GuanZhuAListView, ZuzhangHaibaoView,GuanshiQRCodeView,\
GetDynamicConfigView, HaibaoPeizhiView, PopupConfigView, GetWithdrawModeView,CheckPhoneAuthView,\
ShangjiaLianjieListView
@@ -32,6 +32,7 @@ urlpatterns = [
path('guanshiewm', GuanshiQRCodeView.as_view(), name='管事生成二维码'),
path('zuzhanghb', ZuzhangHaibaoView.as_view(), name='组长二维码'),
path('qdpzhq', GetDynamicConfigView.as_view(), name='小程序获取配置'),
path('haibaopz', HaibaoPeizhiView.as_view(), name='推广海报配置'),
path('tanchuang', PopupConfigView.as_view(), name='微信小程序获取弹窗数据'),
path('hqtxzsym', GetWithdrawModeView.as_view(), name='小程序获取提现方式'),

View File

@@ -115,6 +115,31 @@ class GetDynamicConfigView(APIView):
return Response({"code": 500, "msg": "服务器内部错误"}, status=500)
class HaibaoPeizhiView(APIView):
"""小程序推广海报背景配置(按俱乐部)。"""
permission_classes = [AllowAny]
parser_classes = [JSONParser]
def post(self, request):
try:
from jituan.services.display_config import _display_club_id
from jituan.services.miniapp_assets import get_poster_payload
club_id = _display_club_id(request)
poster = get_poster_payload(club_id)
return Response({
'code': 0,
'msg': 'success',
'data': {
'club_id': club_id,
'poster': poster,
},
})
except Exception as e:
logger.error('获取海报配置失败: %s', e, exc_info=True)
return Response({'code': 500, 'msg': '服务器内部错误'}, status=500)
class ShangpinGonggaoView(APIView):
@@ -2782,6 +2807,10 @@ class GuanshiQRCodeView(APIView):
logger.info(f"当前用户ID: {current_user.UserUID}")
try:
from jituan.services.club_context import resolve_effective_club_id
from utils.weixin_token import get_club_mini_access_token, is_weixin_token_invalid
club_id = resolve_effective_club_id(request, current_user)
try:
guanshi = current_user.GuanshiProfile
logger.info(f"管事扩展表查询成功,状态: {guanshi.zhuangtai}")
@@ -2815,15 +2844,15 @@ class GuanshiQRCodeView(APIView):
else:
logger.info(f"使用已有邀请码: {invite_code}")
# 直接获取新的 access_token,不缓存
access_token = self.get_fresh_access_token()
# 按俱乐部使用对应小程序 appid 的 access_token
access_token = get_club_mini_access_token(club_id)
if not access_token:
logger.error("获取 access_token 失败")
logger.error("获取 access_token 失败 club_id=%s", club_id)
return Response(
{'code': 500, 'message': '获取微信access_token失败', 'data': None},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
logger.info(f"获取到 access_token (前10位): {access_token[:10]}...")
logger.info(f"获取到 access_token (前10位): {access_token[:10]}... club={club_id}")
encoded_invite = urllib.parse.quote(invite_code, safe='')
page_path = f'pages/dashouduan/dashouduan?inviteCode={encoded_invite}'
@@ -2836,16 +2865,35 @@ class GuanshiQRCodeView(APIView):
}
logger.info(f"调用微信接口生成小程序码path: {page_path}")
try:
wx_resp = requests.post(wx_url, json=post_data, timeout=10)
logger.info(f"微信接口响应状态码: {wx_resp.status_code}")
except Exception as e:
logger.error(f"请求微信接口异常: {str(e)}", exc_info=True)
def _request_qr(token):
try:
return requests.post(
f'https://api.weixin.qq.com/wxa/getwxacode?access_token={token}',
json=post_data, timeout=10,
)
except Exception as e:
logger.error(f"请求微信接口异常: {str(e)}", exc_info=True)
return None
wx_resp = _request_qr(access_token)
if wx_resp is None:
return Response(
{'code': 500, 'message': '调用微信服务失败', 'data': None},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
if wx_resp.headers.get('content-type', '').startswith('application/json'):
try:
err = wx_resp.json()
if is_weixin_token_invalid(err.get('errcode'), err.get('errmsg', '')):
access_token = get_club_mini_access_token(club_id, force_refresh=True)
if access_token:
wx_resp = _request_qr(access_token)
except Exception:
pass
logger.info(f"微信接口响应状态码: {wx_resp.status_code}")
if wx_resp.status_code != 200:
error_info = {}
if wx_resp.headers.get('content-type') == 'application/json':
@@ -2881,7 +2929,7 @@ class GuanshiQRCodeView(APIView):
timestamp = int(time.time())
filename = f"{current_user.UserUID}_{timestamp}.png"
oss_path = f"erweima/guanshi/{filename}"
oss_path = f"erweima/guanshi/{club_id}/{filename}"
file_obj = io.BytesIO(wx_resp.content)
logger.info(f"开始上传图片到COS: {oss_path}")
@@ -2994,6 +3042,10 @@ class ZuzhangHaibaoView(APIView):
def post(self, request):
current_user = request.user
from jituan.services.club_context import resolve_effective_club_id
from utils.weixin_token import get_club_mini_access_token
club_id = resolve_effective_club_id(request, current_user)
# 1. 验证组长身份
try:
@@ -3027,8 +3079,8 @@ class ZuzhangHaibaoView(APIView):
zuzhang.yaoqingma = invite_code
zuzhang.save(update_fields=['yaoqingma'])
# 4. 获取微信 access_token优先 stable_tokentoken 失效时自动刷新重试
access_token = get_weixin_mini_access_token(force_refresh=False)
# 4. 获取微信 access_token按俱乐部对应小程序
access_token = get_club_mini_access_token(club_id, force_refresh=False)
if not access_token:
return Response({
'code': 500,
@@ -3042,8 +3094,8 @@ class ZuzhangHaibaoView(APIView):
wx_resp, wx_error = self._request_wx_qrcode(page_path, access_token)
if wx_error and is_weixin_token_invalid(wx_error.get('errcode'), wx_error.get('errmsg', '')):
logger.warning('组长海报二维码 token 失效,强制刷新后重试')
access_token = get_weixin_mini_access_token(force_refresh=True)
logger.warning('组长海报二维码 token 失效,强制刷新后重试 club=%s', club_id)
access_token = get_club_mini_access_token(club_id, force_refresh=True)
if not access_token:
return Response({
'code': 500,
@@ -3071,7 +3123,7 @@ class ZuzhangHaibaoView(APIView):
# 5. 上传到腾讯云 COS
timestamp = int(time.time())
filename = f"{current_user.UserUID}_{timestamp}.png"
oss_path = f"erweima/zuzhang/{filename}"
oss_path = f"erweima/zuzhang/{club_id}/{filename}"
file_obj = io.BytesIO(wx_resp.content)
try:

View File

@@ -19,6 +19,8 @@ MINIAPP_ICON_META = [
('fighter_recharge_deposit', '打手中心-保证金充值横幅(右侧)'),
('mine_pindao', '频道入口'),
('icon_refresh', '通用刷新'),
('guanshi_poster_bg', '管事推广海报背景'),
('zuzhang_poster_bg', '组长推广海报背景'),
]
MINIAPP_ICON_KEYS = [k for k, _ in MINIAPP_ICON_META]
@@ -35,6 +37,19 @@ MINIAPP_ICON_DEFAULTS['merchant_gold_banner'] = 'beijing/tubiao/chengxutubiao/me
MINIAPP_ICON_DEFAULTS['fighter_recharge_member'] = f'{ICON_FOLDER}/fighter_recharge_member.png'
MINIAPP_ICON_DEFAULTS['fighter_recharge_deposit'] = f'{ICON_FOLDER}/fighter_recharge_deposit.png'
MINIAPP_ICON_DEFAULTS['mine_pindao'] = 'beijing/tubiao/grzx_guanzhualong.jpg'
MINIAPP_ICON_DEFAULTS['guanshi_poster_bg'] = 'beijing/haibaobeijing.jpg'
MINIAPP_ICON_DEFAULTS['zuzhang_poster_bg'] = 'beijing/zuzhangbeijing.jpg'
POSTER_BG_FIELDS = {
'guanshi_bg': 'guanshi_poster_bg',
'zuzhang_bg': 'zuzhang_poster_bg',
}
POSTER_BG_DEFAULTS = {
'guanshi_bg': 'beijing/haibaobeijing.jpg',
'zuzhang_bg': 'beijing/zuzhangbeijing.jpg',
}
POSTER_FOLDER = 'beijing/haibao'
POSTER_ICON_KEYS = frozenset(POSTER_BG_FIELDS.values())
def default_icon_path(icon_key: str) -> str:
@@ -70,11 +85,31 @@ def get_pindao_payload(club_id: str) -> dict:
}
def get_poster_payload(club_id: str) -> dict:
"""各俱乐部推广海报背景相对路径(后台上传后覆盖默认)。"""
from config.models import ClubMiniappIcon
rows = {
r.icon_key: r
for r in ClubMiniappIcon.query.filter(club_id=club_id, is_active=True)
}
result = {}
for field, icon_key in POSTER_BG_FIELDS.items():
default = POSTER_BG_DEFAULTS[field]
row = rows.get(icon_key)
if row and row.is_uploaded and (row.image_path or '').strip():
result[field] = row.image_path.strip()
else:
result[field] = default
return result
def build_miniapp_assets_payload(request) -> dict:
club_id = _display_club_id(request)
return {
'icons': get_miniapp_icons_dict(club_id),
'pindao': get_pindao_payload(club_id),
'poster': get_poster_payload(club_id),
}

View File

@@ -14,9 +14,12 @@ from jituan.services.miniapp_assets import (
ICON_FOLDER,
MINIAPP_ICON_LABELS,
MINIAPP_ICON_META,
POSTER_FOLDER,
POSTER_ICON_KEYS,
default_icon_path,
get_miniapp_icons_dict,
get_pindao_payload,
get_poster_payload,
)
from products.views import delete_from_oss, upload_to_oss, validate_image
@@ -115,7 +118,10 @@ class ClubMiniappIconManageView(APIView):
ext = os.path.splitext(file_obj.name)[1].lower() or '.png'
if ext not in ('.png', '.jpg', '.jpeg', '.webp'):
ext = '.png'
oss_path = f'{ICON_FOLDER}/{icon_key}{ext}'
if icon_key in POSTER_ICON_KEYS:
oss_path = f'{POSTER_FOLDER}/{club_id}_{icon_key}{ext}'
else:
oss_path = f'{ICON_FOLDER}/{icon_key}{ext}'
row = ClubMiniappIcon.query.filter(club_id=club_id, icon_key=icon_key).first()
if row and row.image_path and row.image_path != oss_path: