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, normalize_page_key page_key = normalize_page_key(request.data.get('page_key'), image_type=1) 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=1) 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): updated_groups = [] for group_data in qq_groups: peizhiid = group_data.get('peizhiid') if not peizhiid: continue # 🔴 修复:使用filter()确保查询到正确的记录 qun_configs = Qunpeizhi.query.filter(GroupType=peizhiid) if qun_configs.exists(): # 如果存在多个相同peizhiid的记录,取第一个(应该只有一个) qun_config = qun_configs.first() # 更新字段 neirong = group_data.get('neirong') qunid = group_data.get('qunid') jieshao = group_data.get('jieshao') 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 '' qun_config.save() else: # 如果不存在,创建新的(使用提供的peizhiid) qun_config = Qunpeizhi.query.create( GroupType=peizhiid, GroupContent=group_data.get('neirong', '') or '', GroupID=group_data.get('qunid', '') or '', Description=group_data.get('jieshao', '') or '' ) updated_groups.append({ 'peizhiid': qun_config.GroupType, 'neirong': qun_config.GroupContent, 'qunid': qun_config.GroupID, 'jieshao': qun_config.Description }) if updated_groups: response_data['qq_groups'] = updated_groups # 🔴 修复:更新利率配置 - 确保更新现有记录而不是创建新记录 rates = request.data.get('rates') if rates and isinstance(rates, dict): updated_rates = {} # 平台发单利率(类型1,fadanpingtai='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) # 商家发单利率(类型2,fadanpingtai='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) # 🔴【新增】打手提现抽成利率(类型5,fadanpingtai='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) # 🔴【新增】管事提现抽成利率(类型6,fadanpingtai='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)