Files
Django/config/views/admin_config.py
XingQue 43ad221c67 feat: 点单横幅/个人中心背景 page_key,并 seed 转盘菜单
新增 accept_order_banner2、boss_center;菜单定义写入 product.turntable,不影响现有俱乐部数据隔离。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 20:21:16 +08:00

632 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import io
import os
import re
import time
import random
import secrets
import hashlib
import threading
import urllib.parse
from decimal import Decimal, InvalidOperation
from django.conf import settings
from django.db import transaction, connection
from django.db.models import Q, F, Max, Prefetch
from gvsdsdk.fluent import db, func, FQ
from django.core.cache import cache
from django.core.paginator import Paginator
from django.utils import timezone
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status, permissions
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
from rest_framework.throttling import AnonRateThrottle, SimpleRateThrottle
from utils.oss_utils import upload_to_oss, delete_from_oss, validate_image
from utils.weixin_broadcast import WeixinBroadcastSender
from utils.weixin_token import get_weixin_mini_access_token, is_weixin_token_invalid
# from utils.ip_security import *
from utils.invitationcode_utils import CreateInvitationCode, VerifyInvitationCode
from backend.utils import update_shangjia_daily
from utils.chat_utils import subscribe_merchant_link_chat
from utils.pdd_order_validator import validate_pdd_order_id, validate_cn_mobile
from ..models import (
Gonggao, Lunbo, Tupianpeizhi, Qunpeizhi,
ShangjiaMoban, ShangjiaLianjie, PopupPage, PopupConfig, WithdrawConfig,
MiniappScriptScene, MiniappScriptAutoReply,
)
from users.models import (
AdminProfile, UserDashou, UserBoss,
UserShangjia, UserGuanshi, UserZuzhang
)
from users.business_models import User
from orders.models import CommissionRate, Order, MerchantOrderExt
from orders.notice_tasks import dingdan_guangbo
from rank.models import Chenghao, DingdanBiaoqian
from products.models import ShangpinLeixing, Huiyuangoumai
from ..serializers import PopupConfigSerializer
import traceback
import requests
import logging
logger = logging.getLogger(__name__)
class ShangpinGonggaoView(APIView):
"""
商品公告和轮播图接口
获取类型为1的公告和轮播图
"""
throttle_classes = [AnonRateThrottle]
permission_classes = [AllowAny]
authentication_classes = []
def post(self, request):
"""
处理POST请求返回公告和轮播图数据
请求: POST /api/peizhi/shangpingonggao/
返回: {
"shangpingonggao": "公告内容字符串",
"shangpinlunbo": ["轮播图URL1", "轮播图URL2"]
}
"""
try:
logger.info("收到商品公告和轮播图请求")
from jituan.services.display_config import (
get_gonggao_content,
get_lunbo_urls,
lunbo_image_type_for_page,
normalize_page_key,
)
page_key = normalize_page_key(request.data.get('page_key'), image_type=1)
image_type = lunbo_image_type_for_page(page_key)
gonggao_content = get_gonggao_content(request, notice_type=1, page_key=page_key)
lunbo_urls = get_lunbo_urls(request, page_key=page_key, image_type=image_type)
response_data = {
"shangpingonggao": gonggao_content,
"shangpinlunbo": lunbo_urls,
"page_key": page_key,
}
logger.info(f"返回数据:公告长度{len(gonggao_content)},轮播图数量{len(lunbo_urls)} page_key={page_key}")
# 4. 返回响应
return Response(response_data, status=status.HTTP_200_OK)
except Exception as e:
# 记录异常
logger.error(f"获取商品公告和轮播图时发生错误:{str(e)}")
# 返回错误响应
error_data = {
"error": "服务器内部错误",
"detail": "获取数据失败,请稍后重试"
}
return Response(error_data, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class AdminConfigQueryView(APIView):
"""
管理员获取系统配置接口
URL: /peizhi/adpzhq
方法: POST
权限: JWT Token认证 + 管理员权限
参数: zhanghao (管理员账号)
"""
permission_classes = [permissions.IsAuthenticated]
def post(self, request):
try:
# 获取前端传递的账号
zhanghao = request.data.get('zhanghao')
if not zhanghao:
return Response({
'code': 1,
'msg': '账号不能为空',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 获取当前用户User实例
user_main = request.user
# 验证账号比较user_main的phone与前端传递的zhanghao
if user_main.Phone != zhanghao:
return Response({
'code': 1,
'msg': '账号不匹配',
'data': None
}, status=status.HTTP_401_UNAUTHORIZED)
# 验证用户类型是否为管理员
if user_main.UserType != 'admin':
return Response({
'code': 1,
'msg': '无权限',
'data': None
}, status=status.HTTP_401_UNAUTHORIZED)
# 验证管理员扩展表是否存在
try:
admin_profile = user_main.AdminProfile
except AdminProfile.DoesNotExist:
return Response({
'code': 1,
'msg': '无权限',
'data': None
}, status=status.HTTP_401_UNAUTHORIZED)
# 查询图片配置
rule_image = Tupianpeizhi.query.filter(ImageType=1).first()
default_avatar = Tupianpeizhi.query.filter(ImageType=2).first()
# 查询群配置
qun_configs = Qunpeizhi.query.all()
qq_groups = []
for config in qun_configs:
qq_groups.append({
'peizhiid': config.GroupType,
'neirong': config.GroupContent if config.GroupContent else '',
'qunid': config.GroupID if config.GroupID else '',
'jieshao': config.Description if config.Description else ''
})
# 查询利率配置
platform_rate_obj = CommissionRate.query.filter(Platform='1').first()
shop_rate_obj = CommissionRate.query.filter(Platform='3').first()
# 🔴【新增】打手提现抽成利率字符5
dashou_withdraw_obj = CommissionRate.query.filter(Platform='5').first()
# 🔴【新增】管事提现抽成利率字符6
guanshi_withdraw_obj = CommissionRate.query.filter(Platform='6').first()
# 构建返回数据
data = {
'images': {
'rule_image': rule_image.ImageURL if rule_image and rule_image.ImageURL else '',
'default_avatar': default_avatar.ImageURL if default_avatar and default_avatar.ImageURL else ''
},
'qq_groups': qq_groups,
'rates': {
'platform_rate': float(platform_rate_obj.Rate) if platform_rate_obj and platform_rate_obj.Rate else 0.0,
'shop_rate': float(shop_rate_obj.Rate) if shop_rate_obj and shop_rate_obj.Rate else 0.0,
# 🔴【新增】打手提现抽成利率
'dashou_withdraw_rate': float(
dashou_withdraw_obj.Rate) if dashou_withdraw_obj and dashou_withdraw_obj.Rate else 0.0,
# 🔴【新增】管事提现抽成利率
'guanshi_withdraw_rate': float(
guanshi_withdraw_obj.Rate) if guanshi_withdraw_obj and guanshi_withdraw_obj.Rate else 0.0
}
}
return Response({
'code': 0,
'msg': 'success',
'data': data
})
except Exception as e:
return Response({
'code': 99,
'msg': f'系统错误: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class AdminUploadImageView(APIView):
"""
管理员上传配置图片接口
URL: /peizhi/adxgtp
方法: POST
权限: JWT Token认证 + 管理员权限
参数: zhanghao, leixing (1:打手规则图片, 2:默认头像图片), image (文件)
"""
permission_classes = [permissions.IsAuthenticated]
parser_classes = (MultiPartParser, FormParser)
def post(self, request):
try:
zhanghao = request.data.get('zhanghao')
leixing = request.data.get('leixing')
image_file = request.FILES.get('image')
if not zhanghao:
return Response({
'code': 1,
'msg': '账号不能为空',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
if not leixing:
return Response({
'code': 2,
'msg': '图片类型不能为空',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
if not image_file:
return Response({
'code': 3,
'msg': '请选择图片文件',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 验证用户权限
user_main = request.user
if user_main.Phone != zhanghao:
return Response({
'code': 1,
'msg': '账号不匹配',
'data': None
}, status=status.HTTP_401_UNAUTHORIZED)
if user_main.UserType != 'admin':
return Response({
'code': 1,
'msg': '无权限',
'data': None
}, status=status.HTTP_401_UNAUTHORIZED)
try:
admin_profile = user_main.AdminProfile
except AdminProfile.DoesNotExist:
return Response({
'code': 1,
'msg': '无权限',
'data': None
}, status=status.HTTP_401_UNAUTHORIZED)
# 验证图片类型
if leixing not in ['1', '2']:
return Response({
'code': 4,
'msg': '图片类型错误',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 验证图片文件
is_valid, error_msg = validate_image(image_file)
if not is_valid:
return Response({
'code': 5,
'msg': f'图片验证失败: {error_msg}',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 根据类型确定固定相对URL和文件名
if leixing == '1':
# 打手规则图片
fixed_relative_url = 'a_long/dashouguize.jpg'
oss_file_name = 'dashouguize.jpg'
else:
# 默认头像图片
fixed_relative_url = 'a_long/morentouxiang.jpg'
oss_file_name = 'morentouxiang.jpg'
# 构建OSS文件路径
oss_file_path = fixed_relative_url # a_long/dashouguize.jpg 或 a_long/morentouxiang.jpg
# 上传到OSS覆盖原有文件
image_file.seek(0) # 重置文件指针
full_url = upload_to_oss(image_file, oss_file_path)
if not full_url:
return Response({
'code': 6,
'msg': '图片上传到存储桶失败',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# 更新数据库
with transaction.atomic():
# 获取或创建图片配置记录
tupian_peizhi, created = Tupianpeizhi.query.get_or_create(ImageType=int(leixing))
tupian_peizhi.ImageURL = fixed_relative_url
tupian_peizhi.save()
return Response({
'code': 0,
'msg': '图片上传成功',
'data': {
'image_url': fixed_relative_url
}
})
except Exception as e:
return Response({
'code': 99,
'msg': f'系统错误: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class AdminUpdateConfigView(APIView):
"""
管理员更新系统配置接口QQ群、利率- 已修复
URL: /peizhi/adupdate
方法: POST
权限: JWT Token认证 + 管理员权限
参数:
- zhanghao: 管理员账号
- qq_groups: [{"peizhiid": 1, "neirong": "", "qunid": "", "jieshao": ""}, ...] (可选)
- rates: {"platform_rate": 0.1, "shop_rate": 0.2} (可选)
"""
permission_classes = [permissions.IsAuthenticated]
def post(self, request):
try:
zhanghao = request.data.get('zhanghao')
if not zhanghao:
return Response({
'code': 1,
'msg': '账号不能为空',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 验证用户权限
user_main = request.user
if user_main.Phone != zhanghao:
return Response({
'code': 1,
'msg': '账号不匹配',
'data': None
}, status=status.HTTP_401_UNAUTHORIZED)
if user_main.UserType != 'admin':
return Response({
'code': 1,
'msg': '无权限',
'data': None
}, status=status.HTTP_401_UNAUTHORIZED)
try:
admin_profile = user_main.AdminProfile
except AdminProfile.DoesNotExist:
return Response({
'code': 1,
'msg': '无权限',
'data': None
}, status=status.HTTP_401_UNAUTHORIZED)
response_data = {}
# 🔴 修复更新QQ群配置 - 确保根据peizhiid正确更新对应的记录
qq_groups = request.data.get('qq_groups')
if qq_groups and isinstance(qq_groups, list):
# 收集所有 peizhiid批量预取已有记录避免循环内 N+1
_peizhiids = [g.get('peizhiid') for g in qq_groups if g.get('peizhiid')]
_existing_map = {}
if _peizhiids:
for q in Qunpeizhi.query.filter(GroupType__in=_peizhiids):
# 如果存在多个相同 peizhiid 的记录,取第一个(应该只有一个)
if q.GroupType not in _existing_map:
_existing_map[q.GroupType] = q
_to_create = []
_to_update = []
updated_groups = []
for group_data in qq_groups:
peizhiid = group_data.get('peizhiid')
if not peizhiid:
continue
neirong = group_data.get('neirong')
qunid = group_data.get('qunid')
jieshao = group_data.get('jieshao')
qun_config = _existing_map.get(peizhiid)
if qun_config is not None:
# 更新已有记录
if neirong is not None:
qun_config.GroupContent = neirong or ''
if qunid is not None:
qun_config.GroupID = qunid or ''
if jieshao is not None:
qun_config.Description = jieshao or ''
_to_update.append(qun_config)
else:
# 不存在则创建新的
qun_config = Qunpeizhi(
GroupType=peizhiid,
GroupContent=neirong or '',
GroupID=qunid or '',
Description=jieshao or '',
)
_to_create.append(qun_config)
updated_groups.append({
'peizhiid': qun_config.GroupType,
'neirong': qun_config.GroupContent,
'qunid': qun_config.GroupID,
'jieshao': qun_config.Description
})
if _to_create:
Qunpeizhi.objects.bulk_create(_to_create)
if _to_update:
Qunpeizhi.objects.bulk_update(
_to_update, ['GroupContent', 'GroupID', 'Description']
)
if updated_groups:
response_data['qq_groups'] = updated_groups
# 🔴 修复:更新利率配置 - 确保更新现有记录而不是创建新记录
rates = request.data.get('rates')
if rates and isinstance(rates, dict):
updated_rates = {}
# 平台发单利率类型1fadanpingtai='1'
platform_rate = rates.get('platform_rate')
if platform_rate is not None:
try:
platform_rate_float = float(platform_rate)
if 0 <= platform_rate_float <= 1:
# 🔴 修复:查询现有记录,如果不存在则创建
platform_rate_obj = CommissionRate.query.filter(Platform='1').first()
if platform_rate_obj:
# 更新现有记录
platform_rate_obj.Rate = platform_rate_float
platform_rate_obj.save()
else:
# 创建新记录
platform_rate_obj = CommissionRate.query.create(
Rate=platform_rate_float,
Platform='1'
)
updated_rates['platform_rate'] = platform_rate_float
else:
return Response({
'code': 3,
'msg': '平台发单利率必须在0-1之间',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
except ValueError:
return Response({
'code': 4,
'msg': '平台发单利率格式错误',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 商家发单利率类型2fadanpingtai='2'
shop_rate = rates.get('shop_rate')
if shop_rate is not None:
try:
shop_rate_float = float(shop_rate)
if 0 <= shop_rate_float <= 1:
# 🔴 修复:查询现有记录,如果不存在则创建
shop_rate_obj = CommissionRate.query.filter(Platform='3').first()
if shop_rate_obj:
# 更新现有记录
shop_rate_obj.Rate = shop_rate_float
shop_rate_obj.save()
else:
# 创建新记录
shop_rate_obj = CommissionRate.query.create(
Rate=shop_rate_float,
Platform='3'
)
updated_rates['shop_rate'] = shop_rate_float
else:
return Response({
'code': 5,
'msg': '商家发单利率必须在0-1之间',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
except ValueError:
return Response({
'code': 6,
'msg': '商家发单利率格式错误',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 🔴【新增】打手提现抽成利率类型5fadanpingtai='5'
dashou_withdraw_rate = rates.get('dashou_withdraw_rate')
if dashou_withdraw_rate is not None:
try:
dashou_withdraw_rate_float = float(dashou_withdraw_rate)
if 0 <= dashou_withdraw_rate_float <= 1:
# 查询现有记录,如果不存在则创建
dashou_withdraw_obj = CommissionRate.query.filter(Platform='5').first()
if dashou_withdraw_obj:
# 更新现有记录
dashou_withdraw_obj.Rate = dashou_withdraw_rate_float
dashou_withdraw_obj.save()
else:
# 创建新记录
dashou_withdraw_obj = CommissionRate.query.create(
Rate=dashou_withdraw_rate_float,
Platform='5'
)
updated_rates['dashou_withdraw_rate'] = dashou_withdraw_rate_float
else:
return Response({
'code': 7, # 注意:这里需要调整错误码,不要重复
'msg': '打手提现抽成利率必须在0-1之间',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
except ValueError:
return Response({
'code': 8,
'msg': '打手提现抽成利率格式错误',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 🔴【新增】管事提现抽成利率类型6fadanpingtai='6'
guanshi_withdraw_rate = rates.get('guanshi_withdraw_rate')
if guanshi_withdraw_rate is not None:
try:
guanshi_withdraw_rate_float = float(guanshi_withdraw_rate)
if 0 <= guanshi_withdraw_rate_float <= 1:
# 查询现有记录,如果不存在则创建
guanshi_withdraw_obj = CommissionRate.query.filter(Platform='6').first()
if guanshi_withdraw_obj:
# 更新现有记录
guanshi_withdraw_obj.Rate = guanshi_withdraw_rate_float
guanshi_withdraw_obj.save()
else:
# 创建新记录
guanshi_withdraw_obj = CommissionRate.query.create(
Rate=guanshi_withdraw_rate_float,
Platform='6'
)
updated_rates['guanshi_withdraw_rate'] = guanshi_withdraw_rate_float
else:
return Response({
'code': 9,
'msg': '管事提现抽成利率必须在0-1之间',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
except ValueError:
return Response({
'code': 10,
'msg': '管事提现抽成利率格式错误',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
if updated_rates:
response_data['rates'] = updated_rates
# 如果没有任何更新
if not response_data:
return Response({
'code': 7,
'msg': '没有需要更新的数据',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
return Response({
'code': 0,
'msg': '更新成功',
'data': response_data
})
except Exception as e:
return Response({
'code': 99,
'msg': f'系统错误: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)