"""users.views.auth - auto-generated by split script.""" import os import sys import json import re import time import uuid import random import string import requests import hashlib import traceback import decimal import jwt import ipaddress import xmltodict from datetime import datetime, timedelta from decimal import Decimal import defusedxml.ElementTree as ET from django.conf import settings from django.db import models, transaction, IntegrityError, DatabaseError, connection from django.db.models import Q, F, Sum, Count, Case, When, IntegerField, Prefetch from django.core.cache import cache from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.exceptions import ObjectDoesNotExist from django.shortcuts import get_object_or_404 from django.utils import timezone from django.http import HttpResponse from django.views import View from django.views.decorators.csrf import csrf_exempt from django.contrib.auth.hashers import make_password from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import permissions, status from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.parsers import MultiPartParser, FormParser, JSONParser from rest_framework.throttling import AnonRateThrottle from rest_framework_simplejwt.tokens import RefreshToken from rest_framework_simplejwt.authentication import JWTAuthentication from gvsdsdk.fluent import db, func, FQ from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss from utils.money import yuan_to_fen from utils.fadan_utils import check_fadan_qiangdan_eligible from utils.invitationcode_utils import CreateInvitationCode, VerifyInvitationCode from users.fadan_fenhong_utils import process_fadan_fenhong from orders.utils import ( update_daily_payout, settle_shangjia_order_guanshi_fenhong ) from backend.utils import ( update_dashou_daily_by_action, update_guanshi_daily_by_action, update_shangjia_daily, update_zuzhang_daily_by_action, verify_kefu_permission ) from rank.utils import get_tag_fee, create_shenhe_jilu, validate_shenheguan from ..models import ( UserBoss, UserDashou, UserShangjia, UserGuanshi, UserZuzhang, UserKefu, AdminProfile, OfficialAccountUser, TixianAutoRecord, TixianShenheJilu, Tixianjilu, Xiugaijilu, RankingRecord ) from users.business_models import User from ..tixian_shenhe_services import ( load_audit_meta_map, refund_balance, sync_audit_and_jilu_status, mark_transfer_success, release_collect_quota_for_record, close_audit_wechat_failed, query_wechat_transfer_bill, check_shijidaozhang_within_limit, check_dashou_trial_blocks_commission_withdraw, WX_STATE_FAIL, WX_STATE_SUCCESS, WX_STATE_WAIT, WX_STATE_CANCELING, ) from ..tixian_shenhe_views import process_audit_collect from orders.models import ( Order, PlatformOrderExt, MerchantOrderExt, CommissionRate, PenaltyRecord, PenaltyEvidenceImage, RefundRecord, PlayerDeliveryImage, Penalty, PenaltyAppealImage ) from products.models import ( ShangpinLeixing, Huiyuan, Huiyuangoumai, Gsfenhong, Czjilu ) from config.models import Qunpeizhi from backend.models import MerchantDailyStats from rank.models import ( KaohePayTemp, Chenghao, ShenheJilu, KaoheCishuFeiyong, YonghuChenghao ) from users.models import UserShenheguan from products.utils import update_shangpin_daily_stat from shop.utils import update_dianpu_daily_stat from rank.utils import create_shenhe_jilu_from_temp import logging logger = logging.getLogger(__name__) class WechatMiniProgramLoginView(APIView): """ 微信小程序登录接口 """ throttle_classes = [AnonRateThrottle] permission_classes = [AllowAny] def post(self, request): try: code = request.data.get('code', '').strip() if not code: return Response({'code': 1, 'msg': '微信授权码不能为空', 'data': None}, status=status.HTTP_400_BAD_REQUEST) # 获取微信openid和unionid(按俱乐部小程序配置) from jituan.services.club_resolver import wechat_jscode2session_for_request wechat_data = wechat_jscode2session_for_request(request, code) if not wechat_data or 'openid' not in wechat_data: error_msg = wechat_data.get('errmsg', '微信授权失败') return Response({'code': 2, 'msg': f'微信登录失败: {error_msg}', 'data': None}, status=status.HTTP_400_BAD_REQUEST) openid = wechat_data['openid'] session_key = wechat_data.get('session_key', '') unionid = wechat_data.get('unionid', '') # 🆕【新增】获取unionid # 获取客户端真实IP kehuduan_ip = self.huoquKehuduanIP(request) with transaction.atomic(): # 创建或获取用户 user_main, created = User.objects.select_for_update().get_or_create( OpenID=openid, defaults={'UserUID': self.shengchengYonghuID(), 'UserName': openid} ) # 保存unionid if unionid and unionid != user_main.UnionID: user_main.UnionID = unionid user_main.IP = kehuduan_ip user_main.UserLastLoginDate = timezone.now() user_main.save() if created: UserBoss.query.create(user=user_main, nickname='板板大人') # 新用户默认分配消费者角色 from users.business_models import UserRole, Role normal_role = Role.objects.filter(RoleName='消费者').first() if normal_role: UserRole.objects.create( UserRoleUUID=uuid.uuid4().bytes, UserUUID=user_main.UserUUID, RoleUUID=normal_role.RoleUUID, CompanyUUID=uuid.UUID('b0000000-0000-0000-0000-000000000001').bytes, ) # 检查扩展表 dashou_status = 0 shangjia_status = 0 guanshi_status = 0 boss_nickname = '微信用户' try: boss_profile = UserBoss.query.get(user=user_main) boss_nickname = boss_profile.nickname if boss_profile.nickname else '微信用户' except UserBoss.DoesNotExist: pass try: UserDashou.query.get(user=user_main) dashou_status = 1 except UserDashou.DoesNotExist: dashou_status = None try: UserShangjia.query.get(user=user_main) shangjia_status = 1 except UserShangjia.DoesNotExist: shangjia_status = None try: UserGuanshi.query.get(user=user_main) guanshi_status = 1 except UserGuanshi.DoesNotExist: guanshi_status = None try: UserZuzhang.query.get(user=user_main) zuzhang_status = 1 except UserZuzhang.DoesNotExist: zuzhang_status = None # 在检查 zuzhang 扩展表之后,插入以下代码 try: kg = UserShenheguan.query.get(user=user_main) kaoheguan_status = 1 if kg.zhuangtai == 1 else 0 except UserShenheguan.DoesNotExist: kaoheguan_status = 0 # 生成JWT token refresh = RefreshToken.for_user(user_main) token = str(refresh.access_token) # 计算订单数量 order_counts = self.jisuanOrderShuliang(user_main.UserUID) # 获取群配置 group_info = self.huoquQunPeizhi() from merchant_ops.services.authz import staff_fields_for_login staff_fields = staff_fields_for_login(user_main) # 返回数据 response_data = { 'token': token, 'nicheng': boss_nickname, 'uid': user_main.UserUID, 'touxiang': user_main.Avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, 'zuzhangstatus': zuzhang_status, 'kaoheguanstatus': kaoheguan_status, 'dingdantiaoshu': order_counts, 'dashouqun': group_info.get('dashouqun', ''), 'dashouqunid': group_info.get('dashouqunid', ''), 'guanshiqun': group_info.get('guanshiqun', ''), 'guanshiqunid': group_info.get('guanshiqunid', ''), **staff_fields, } return Response({'code': 0, 'msg': '登录成功', 'data': response_data}) except Exception as e: error_openid = locals().get('openid', 'N/A') logger.error(f"微信登录异常 - openid: {error_openid}, 错误类型: {type(e).__name__}, 错误详情: {str(e)}", exc_info=True) return Response({'code': 99, 'msg': '系统繁忙,请稍后重试', 'data': None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) def get_wechat_openid(self, code): """ 调用微信接口获取openid和unionid """ try: appid = getattr(settings, 'WEIXIN_APPID', '') secret = getattr(settings, 'WEIXIN_SECRET', '') if not appid or not secret: raise ValueError('微信配置未设置') url = 'https://api.weixin.qq.com/sns/jscode2session' params = {'appid': appid, 'secret': secret, 'js_code': code, 'grant_type': 'authorization_code'} response = requests.get(url, params=params, timeout=10) result = response.json() if 'openid' in result: return {'openid': result['openid'], 'session_key': result.get('session_key', ''), 'unionid': result.get('unionid', '')} else: errcode = result.get('errcode', 'unknown') errmsg = result.get('errmsg', '未知错误') return {'errmsg': f'[{errcode}]{errmsg}'} except requests.exceptions.Timeout: return {'errmsg': '请求微信接口超时'} except Exception as e: return {'errmsg': f'请求微信接口异常: {str(e)}'} def shengchengYonghuID(self): """ 生成7位数字用户ID """ for _ in range(10): timestamp_part = str(int(time.time()))[-5:].zfill(5) random_part = str(random.randint(0, 99)).zfill(2) user_id = timestamp_part + random_part if len(user_id) == 7 and user_id.isdigit(): if not User.query.filter(UserUID=user_id).exists(): return user_id raise Exception('生成用户ID失败') def huoquKehuduanIP(self, request): """ 获取客户端真实IP地址 """ x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: ip = x_forwarded_for.split(',')[0].strip() if ip: return ip ip = request.META.get('REMOTE_ADDR', '') return ip if ip else '0.0.0.0' def jisuanOrderShuliang(self, yonghuid): """ 统计订单数量 """ try: if not PlatformOrderExt.query.filter(BossID=yonghuid).exists(): return {'daifuwu': 0, 'fuwuzhong': 0, 'yiwancheng': 0, 'yituikuan': 0} dingdan_ids = PlatformOrderExt.query.filter(BossID=yonghuid).values_list('Order__id', flat=True) results = Order.query.filter(id__in=dingdan_ids).aggregate( daifuwu=Count(Case(When(Q(Status=1) | Q(Status=7), then=1), output_field=IntegerField())), fuwuzhong=Count(Case(When(Status=2, then=1), output_field=IntegerField())), yiwancheng=Count(Case(When(Status=3, then=1), output_field=IntegerField())), yituikuan=Count(Case(When(Status=5, then=1), output_field=IntegerField())) ) return { 'daifuwu': results['daifuwu'] or 0, 'fuwuzhong': results['fuwuzhong'] or 0, 'yiwancheng': results['yiwancheng'] or 0, 'yituikuan': results['yituikuan'] or 0 } except Exception: return {'daifuwu': 0, 'fuwuzhong': 0, 'yiwancheng': 0, 'yituikuan': 0} def huoquQunPeizhi(self): """ 获取群配置信息 """ try: qun_configs = Qunpeizhi.query.filter(id__in=[1, 2]) result = { 'dashouqun': '', 'dashouqunid': '', 'guanshiqun': '', 'guanshiqunid': '' } for config in qun_configs: if config.id == 1: result['dashouqun'] = config.GroupContent or '' result['dashouqunid'] = config.GroupID or '' elif config.id == 2: result['guanshiqun'] = config.GroupContent or '' result['guanshiqunid'] = config.GroupID or '' return result except Exception: return { 'dashouqun': '', 'dashouqunid': '', 'guanshiqun': '', 'guanshiqunid': '' } class WeixinOfficialCallbackView(APIView): """ 微信服务号回调接口 处理用户关注、取消关注等事件 核心:有关注事件就存,不管有没有UnionID """ throttle_classes = [] permission_classes = [AllowAny] authentication_classes = [] def get(self, request): """验证服务器配置(微信官方要求)""" try: signature = request.GET.get('signature', '') timestamp = request.GET.get('timestamp', '') nonce = request.GET.get('nonce', '') echostr = request.GET.get('echostr', '') token = getattr(settings, 'WEIXIN_OFFICIAL_TOKEN', '') if not token: logger.error("服务号Token未配置") return HttpResponse('Token not configured', status=500) # 验证签名 tmp_list = sorted([token, timestamp, nonce]) tmp_str = ''.join(tmp_list) hash_str = hashlib.sha1(tmp_str.encode()).hexdigest() if hash_str == signature: return HttpResponse(echostr) else: logger.error(f"签名验证失败") return HttpResponse('Signature verification failed', status=403) except Exception as e: logger.error(f"验证服务器异常: {str(e)}") return HttpResponse('Internal Server Error', status=500) def post(self, request): """处理事件推送(关注、取关)""" try: xml_str = request.body.decode('utf-8') logger.info(f"收到服务号事件,原始数据(前200字符): {xml_str[:200]}") root = ET.fromstring(xml_str) msg_type = root.find('MsgType').text from_user = root.find('FromUserName').text # 服务号OpenID if msg_type == 'event': event = root.find('Event').text if event == 'subscribe': return self.handle_subscribe_event(root, from_user) elif event == 'unsubscribe': return self.handle_unsubscribe_event(root, from_user) else: # 其他事件(如菜单点击)直接返回success return HttpResponse('success') # 非事件消息也直接返回success return HttpResponse('success') except Exception as e: logger.error(f"处理服务号事件异常: {str(e)}", exc_info=True) # 即使异常也返回success,避免微信服务器重试 return HttpResponse('success') def handle_subscribe_event(self, root, from_user): """处理用户关注事件 - 简化版:有就存,没有拉倒""" try: # 尝试获取UnionID(有就有,没有也没关系) unionid_elem = root.find('UnionId') unionid = unionid_elem.text if unionid_elem is not None else None logger.info(f"用户关注,OpenID: {from_user}, UnionID: {unionid}") # 核心逻辑:更新或创建记录 obj, created = OfficialAccountUser.query.update_or_create( official_openid=from_user, defaults={ 'unionid': unionid, 'is_subscribed': True, 'subscribe_time': timezone.now() } ) action = "新建" if created else "更新" logger.info(f"{action}用户记录成功: OpenID={from_user}") # 回复欢迎消息(可选) return self.generate_text_response(root, "感谢关注星阙网络服务!平台有新订单时您会收到通知。") except Exception as e: logger.error(f"处理关注事件异常: {str(e)}", exc_info=True) # 依然返回success,不让微信重试 return HttpResponse('success') def handle_unsubscribe_event(self, root, from_user): """处理用户取消关注事件 - 简化版:标记为未订阅""" try: updated_count = OfficialAccountUser.query.filter( official_openid=from_user ).update(is_subscribed=False) if updated_count > 0: logger.info(f"用户取消关注,已标记为未订阅: {from_user}") else: logger.info(f"用户取消关注,但记录不存在(无需处理): {from_user}") return HttpResponse('success') except Exception as e: logger.error(f"处理取消关注事件异常: {str(e)}", exc_info=True) return HttpResponse('success') def generate_text_response(self, root, content): """生成文本消息响应""" try: to_user = root.find('ToUserName').text from_user = root.find('FromUserName').text response_xml = f""" {int(time.time())} """ return HttpResponse(response_xml, content_type='application/xml') except Exception as e: logger.error(f"生成回复消息异常: {str(e)}") # 即使生成回复失败,也返回一个空的success return HttpResponse('success') class DashouZhuceView(APIView): """ 打手注册接口 (通过管事邀请码) 访问路径: /api/yonghu/dashouzhuce/ 请求方法: POST 权限要求: JWT Token认证通过的用户 请求体: { "inviteCode": "AbC123..." } """ permission_classes = [IsAuthenticated] def post(self, request): # 1. 获取并验证邀请码参数 yaoqingma = request.data.get('inviteCode') if not yaoqingma: return Response({ 'code': 400, 'message': '邀请码不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) yaoqingma = yaoqingma.strip() if len(yaoqingma) > 100: return Response({ 'code': 400, 'message': '邀请码长度超过限制', 'data': None }, status=status.HTTP_400_BAD_REQUEST) current_user = request.user # 注意:这里request.user是User实例 # 2. 检查用户是否已是打手 (使用反向查询,性能最优) try: dashou_profile = current_user.DashouProfile # 用户已是打手,直接返回现有信息 return self.fanhuiXianyouDashouXinxi(dashou_profile, current_user) except UserDashou.DoesNotExist: # 用户不是打手,继续注册流程 pass # 3. 根据邀请码查找对应的管事 try: # 使用select_related一次性获取管事及其关联的主表用户信息,性能最高 guanshi_profile = UserGuanshi.query.select_related('user').get(yaoqingma=yaoqingma) except UserGuanshi.DoesNotExist: return Response({ 'code': 404, 'message': '邀请码无效或不存在', 'data': None }, status=status.HTTP_404_NOT_FOUND) from jituan.services.invite_guard import assert_invite_same_club from jituan.services.club_resolver import resolve_club_id_for_wechat_request from jituan.services.club_user import ensure_user_club_id ok, msg = assert_invite_same_club(request, guanshi_profile.user, current_user) if not ok: return Response({ 'code': 403, 'message': msg, 'data': None }, status=status.HTTP_403_FORBIDDEN) club_id = resolve_club_id_for_wechat_request(request) # 4. 验证管事状态 # 5. 核心:在数据库事务中创建用户的所有身份 try: with transaction.atomic(): # 获取管事的用户ID (yonghuid) guanshi_yonghuid = guanshi_profile.user.UserUID # 5.1 创建打手扩展表 dashou_profile = UserDashou.query.create( user=current_user, nicheng='大手子', # 默认昵称 chenghao='普通大手', # 默认称号 yaoqingren=guanshi_yonghuid, # 设置邀请人ID # 其他字段全部使用模型定义的默认值 ) # 5.2 创建商家扩展表 try: boss_profile = current_user.BossProfile if boss_profile.nickname: boss_nickname = boss_profile.nickname except: pass # 5.3 创建管事扩展表 # 5.4 更新主表的用户类型 (如果需要,可以设为'dashou',或保持原逻辑) # current_user.UserType = 'dashou' # 根据你的业务逻辑决定 # current_user.save() # 5.5 更新原管事的邀请数量 (使用行锁防止并发问题) # 重新查询并锁定该行记录 locked_guanshi = UserGuanshi.objects.select_for_update().get(pk=guanshi_profile.pk) locked_guanshi.yaogingshuliang += 1 locked_guanshi.save() # ========== 新增:管事每日统计(邀请打手) ========== try: update_guanshi_daily_by_action( yonghuid=guanshi_yonghuid, action=1, # 1 = 邀请打手 amount=Decimal('0.00') ) logger.info(f"管事每日统计更新成功(邀请打手):管事{guanshi_yonghuid}") except Exception as e: logger.error(f"管事每日统计更新失败(邀请打手):{str(e)}") except Exception as e: # 事务会自动回滚 # 这里可以记录更详细的日志 return Response({ 'code': 500, 'message': f'注册过程中发生系统错误: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) ensure_user_club_id(current_user, club_id) # 6. 注册成功,返回信息 (新注册用户没有会员记录,clumber返回空列表) fanhui_data = self.zhuangbeiFanhuiShuju(dashou_profile, current_user, is_new=True) return Response({ 'code': 200, 'message': '注册成功!已开通打手、管事身份。', 'data': fanhui_data }) def fanhuiXianyouDashouXinxi(self, dashou_profile, current_user): """处理已是打手的用户,返回其现有信息""" fanhui_data = self.zhuangbeiFanhuiShuju(dashou_profile, current_user, is_new=False) return Response({ 'code': 200, 'message': '您已是打手,返回当前信息。', 'data': fanhui_data }) def zhuangbeiFanhuiShuju(self, dashou_profile, current_user, is_new=False): """准备返回给前端的打手数据,字段名严格匹配前端""" # 构建基础信息 data = { 'dashounicheng': dashou_profile.nicheng, 'zhanghaostatus': dashou_profile.zhanghaozhuangtai, 'yongjin': str(dashou_profile.yue), # Decimal转字符串 'zonge': str(dashou_profile.zonge), 'yajin': str(dashou_profile.yajin), 'chenghao': dashou_profile.chenghao, 'jinfen': dashou_profile.jifen, 'chengjiaoliang': dashou_profile.chengjiaozongliang, 'zaixianzhuangtai': dashou_profile.zaixianzhuangtai, 'dashouzhuangtai': dashou_profile.zhuangtai, } # 查询并处理会员列表 (clumber) from jituan.services.huiyuan_bundle import build_auth_clumber if is_new: huiyuan_goumai_list = [] else: cid = getattr(current_user, 'ClubID', None) or None huiyuan_goumai_list = build_auth_clumber(current_user.UserUID, club_id=cid) data['clumber'] = huiyuan_goumai_list return data class GuanshiYaoqingDashouListView(APIView): """ 管事获取已邀请打手列表(支持筛选、搜索、分页) POST /api/yonghu/gshqyqds/ Headers: Authorization: Bearer Body: { "page": 1, // 页码,从1开始 "pageSize": 30, // 每页条数,最大100 "keyword": "", // 搜索关键词(用户ID或昵称) "zhanghaozhuangtai": 1, // 账号状态:1=正常,2=封禁(可选) "zaixianzhuangtai": 1 // 在线状态:1=在线,2=离线(可选) } """ permission_classes = [IsAuthenticated] def post(self, request): try: current_user = request.user guanshi_yonghuid = current_user.UserUID # 1. 分页参数 page = int(request.data.get('page', 1)) page_size = int(request.data.get('pageSize', 30)) if page < 1 or page_size < 1: return Response({'code': 400, 'message': '分页参数必须大于0'}, status=400) if page_size > 100: page_size = 100 # 2. 筛选参数 keyword = request.data.get('keyword', '').strip() zhanghaozhuangtai = request.data.get('zhanghaozhuangtai') # 可选:1=正常,2=封禁 zaixianzhuangtai = request.data.get('zaixianzhuangtai') # 可选:1=在线,2=离线 # 3. 基础查询:当前管事邀请的所有打手 base_qs = UserDashou.query.select_related('user').filter( yaoqingren=guanshi_yonghuid ) # 4. 全局统计(不受筛选影响,直接基于 base_qs) total_all = base_qs.count() zaixian_all = base_qs.filter(zaixianzhuangtai=1).count() zhengchang_all = base_qs.filter(zhanghaozhuangtai=1).count() fengjin_all = total_all - zhengchang_all # 5. 应用筛选条件 if keyword: base_qs = base_qs.filter( Q(nicheng__icontains=keyword) | Q(user__UserUID__icontains=keyword) ) if zhanghaozhuangtai is not None: try: val = int(zhanghaozhuangtai) if val == 1: base_qs = base_qs.filter(zhanghaozhuangtai=1) else: base_qs = base_qs.exclude(zhanghaozhuangtai=1) except (ValueError, TypeError): pass if zaixianzhuangtai is not None: try: val = int(zaixianzhuangtai) if val == 1: base_qs = base_qs.filter(zaixianzhuangtai=1) else: base_qs = base_qs.exclude(zaixianzhuangtai=1) except (ValueError, TypeError): pass # 6. 排序和分页 base_qs = base_qs.order_by('-CreateTime') paginator = Paginator(base_qs, page_size) try: page_obj = paginator.page(page) except EmptyPage: page_obj = [] has_more = False filtered_total = paginator.count if paginator.count else 0 else: has_more = page_obj.has_next() filtered_total = paginator.count # 7. 构建打手列表 dashou_list = [] for dashou in page_obj: user_main = dashou.user dashou_list.append({ 'uid': user_main.UserUID, 'nicheng': dashou.nicheng or '打手', 'zaixianzhuangtai': dashou.zaixianzhuangtai, 'zhanghaozhuangtai': dashou.zhanghaozhuangtai, 'touxiang': user_main.Avatar or '', 'jiedanzongliang': dashou.jiedanzongliang, 'chengjiaozongliang': dashou.chengjiaozongliang, 'tuikuanliang': dashou.tuikuanliang, 'yue': str(dashou.zonge), }) # 8. 获取管事邀请总数(来自管事扩展表) try: yaoqingzongshu = current_user.GuanshiProfile.yaogingshuliang except Exception: yaoqingzongshu = total_all response_data = { 'dashouList': dashou_list, 'yaoqingzongshu': yaoqingzongshu, # 邀请总数(全局) 'totalCount': filtered_total, # 当前筛选条件下的总数 'zaixianCount': zaixian_all, # 全局在线 'zhengchangCount': zhengchang_all, # 全局正常 'fengjinCount': fengjin_all, # 全局封禁 'currentPage': page, 'pageSize': page_size, 'hasMore': has_more, } return Response({'code': 200, 'message': '获取成功', 'data': response_data}) except ValueError: return Response({'code': 400, 'message': '分页参数格式错误'}, status=400) except Exception as e: logger.error(f'获取打手列表失败: {str(e)}', exc_info=True) return Response({'code': 500, 'message': '系统错误'}, status=500) class WechatLoginAndDashouRegisterView(APIView): """ 微信登录并注册打手一体化接口 访问路径: /api/yonghu/wdlyhdl 请求方法: POST 权限要求: 无需登录 请求体: { "code": "微信登录code", "inviteCode": "邀请码" } 功能: 用户扫码进入但未登录时,一次性完成微信登录和打手注册 """ throttle_classes = [AnonRateThrottle] permission_classes = [AllowAny] def post(self, request): """ 处理微信登录并注册打手请求 """ try: # 1. 获取并验证参数 code = request.data.get('code', '').strip() yaoqingma = request.data.get('inviteCode', '').strip() if not code: return Response({ 'code': 1, 'msg': '微信授权码不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) if not yaoqingma: return Response({ 'code': 2, 'msg': '邀请码不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) if len(yaoqingma) > 100: return Response({ 'code': 3, 'msg': '邀请码长度超过限制', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 2. 获取微信openid(按俱乐部小程序 appid,勿用全局星阙配置) from jituan.services.club_resolver import ( resolve_club_id_for_wechat_request, wechat_jscode2session_for_request, ) club_id = resolve_club_id_for_wechat_request(request) wechat_data = wechat_jscode2session_for_request(request, code) if not wechat_data or 'openid' not in wechat_data: error_msg = wechat_data.get('errmsg', '微信授权失败') logger.warning('wdlyhdl 微信登录失败 club=%s err=%s', club_id, error_msg) return Response({ 'code': 4, 'msg': f'微信登录失败: {error_msg}', 'data': None }, status=status.HTTP_400_BAD_REQUEST) openid = wechat_data['openid'] unionid = wechat_data.get('unionid', '') # 3. 获取客户端真实IP kehuduan_ip = self.huoquKehuduanIP(request) from jituan.constants import CLUB_ID_DEFAULT from jituan.services.club_user import ( ensure_user_club_id, ensure_wx_openid_binding, find_user_by_wx_openid, ) # 4. 开始数据库事务(确保登录和注册的原子性) with transaction.atomic(): # 4.1 定位或创建用户(兼容俱乐部登录建立的 user_wx_openid) user_main = find_user_by_wx_openid(club_id, openid) created = False if not user_main: try: user_main = User( UserUUID=uuid.uuid4().bytes, UserUID=self.shengchengYonghuID(), UserName=openid, OpenID=openid if club_id == CLUB_ID_DEFAULT else None, UnionID=unionid or None, ) user_main.save() created = True UserBoss.query.create(user=user_main, nickname='微信用户') except IntegrityError: user_main = find_user_by_wx_openid(club_id, openid) if not user_main: raise ensure_wx_openid_binding(club_id, openid, user_main, unionid) # 更新用户IP和最后登录时间 cunchu_ip = kehuduan_ip user_main.IP = cunchu_ip user_main.UserLastLoginDate = timezone.now() if unionid and unionid != (user_main.UnionID or ''): user_main.UnionID = unionid user_main.save() # 4.2 验证邀请码对应的管事(注册逻辑) try: # 使用select_related获取管事及其关联的主表用户信息 guanshi_profile = UserGuanshi.query.select_related('user').get(yaoqingma=yaoqingma) except UserGuanshi.DoesNotExist: # 邀请码无效,但用户已登录,可以返回登录成功但注册失败 # 这里我们继续执行,但只返回登录信息 return self.fanhuiZhihouDengluXinxi(user_main, yaoqingma_wuxiao=True) from jituan.services.invite_guard import assert_invite_same_club from jituan.services.club_resolver import resolve_club_id_for_wechat_request ok, invite_msg = assert_invite_same_club( request, guanshi_profile.user, user_main, ) if not ok: return Response({ 'code': 403, 'msg': invite_msg, 'data': None }, status=status.HTTP_403_FORBIDDEN) ensure_user_club_id(user_main, resolve_club_id_for_wechat_request(request)) # 验证管事状态 # 4.3 检查用户是否已是打手 dashou_exists = UserDashou.query.filter(user=user_main).exists() if dashou_exists: # 用户已是打手,直接返回信息 dashou_profile = UserDashou.query.get(user=user_main) return self.fanhuiDengluZhuceshuju(user_main, dashou_profile) # 4.4 核心:为用户创建所有身份(打手、商家、管事) guanshi_yonghuid = guanshi_profile.user.UserUID # 创建打手扩展表 dashou_profile = UserDashou.query.create( user=user_main, nicheng='大神', chenghao='普通大神', yaoqingren=guanshi_yonghuid, # 其他字段使用模型默认值 ) # 创建商家扩展表 # 创建管事扩展表 # 更新原管事的邀请数量 locked_guanshi = UserGuanshi.objects.select_for_update().get(pk=guanshi_profile.pk) locked_guanshi.yaogingshuliang += 1 locked_guanshi.save() # ========== 新增:管事每日统计(邀请打手) ========== try: update_guanshi_daily_by_action( yonghuid=guanshi_yonghuid, action=1, # 1 = 邀请打手 amount=Decimal('0.00') ) logger.info(f"管事每日统计更新成功(邀请打手):管事{guanshi_yonghuid}") except Exception as e: logger.error(f"管事每日统计更新失败(邀请打手):{str(e)}") # 5. 生成JWT token refresh = RefreshToken.for_user(user_main) token = str(refresh.access_token) # 6. 准备返回数据 response_data = self.zhuangbeiFanhuiShuju(user_main, dashou_profile, token) return Response({ 'code': 0, 'msg': '登录并注册成功!已开通打手、管事等身份。', 'data': response_data }) except Exception as e: # 记录错误日志 error_openid = locals().get('openid', 'N/A') logger.error( f"微信登录注册一体化接口异常 - openid: {error_openid}, " f"错误类型: {type(e).__name__}, 错误详情: {str(e)}", exc_info=True ) # 返回友好错误信息 return Response({ 'code': 99, 'msg': '系统繁忙,请稍后重试', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) def get_wechat_openid(self, code): """ 调用微信接口获取openid """ try: appid = getattr(settings, 'WEIXIN_APPID', '') secret = getattr(settings, 'WEIXIN_SECRET', '') if not appid or not secret: raise ValueError('微信配置未设置') url = 'https://api.weixin.qq.com/sns/jscode2session' params = { 'appid': appid, 'secret': secret, 'js_code': code, 'grant_type': 'authorization_code' } response = requests.get(url, params=params, timeout=10) result = response.json() if 'openid' in result: return result else: errcode = result.get('errcode', 'unknown') errmsg = result.get('errmsg', '未知错误') return {'errmsg': f'[{errcode}]{errmsg}'} except requests.exceptions.Timeout: return {'errmsg': '请求微信接口超时'} except Exception as e: return {'errmsg': f'请求微信接口异常: {str(e)}'} def shengchengYonghuID(self): """ 生成7位数字用户ID """ for _ in range(10): timestamp_part = str(int(time.time()))[-5:].zfill(5) random_part = str(random.randint(0, 99)).zfill(2) user_id = timestamp_part + random_part if len(user_id) == 7 and user_id.isdigit(): if not User.query.filter(UserUID=user_id).exists(): return user_id raise Exception('生成用户ID失败') def huoquKehuduanIP(self, request): """ 获取客户端真实IP地址 """ x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: ip = x_forwarded_for.split(',')[0].strip() if ip: return ip ip = request.META.get('REMOTE_ADDR', '') return ip if ip else '0.0.0.0' def huoquQunPeizhi(self): """ 获取群配置信息 """ try: qun_configs = Qunpeizhi.query.filter(id__in=[1, 2]) result = { 'dashouqun': '', 'dashouqunid': '', 'guanshiqun': '', 'guanshiqunid': '' } for config in qun_configs: if config.id == 1: result['dashouqun'] = config.GroupContent or '' result['dashouqunid'] = config.GroupID or '' elif config.id == 2: result['guanshiqun'] = config.GroupContent or '' result['guanshiqunid'] = config.GroupID or '' return result except Exception: return { 'dashouqun': '', 'dashouqunid': '', 'guanshiqun': '', 'guanshiqunid': '' } def fanhuiZhihouDengluXinxi(self, user_main, yaoqingma_wuxiao=False, guanshi_jinyong=False): """ 返回只登录成功的信息(当邀请码无效或管事禁用时) """ # 获取老板昵称 boss_nickname = '微信用户' try: boss_profile = UserBoss.query.get(user=user_main) if boss_profile.nickname: boss_nickname = boss_profile.nickname except UserBoss.DoesNotExist: pass # 检查各身份状态 dashou_status = 1 if UserDashou.query.filter(user=user_main).exists() else 0 shangjia_status = 1 if UserShangjia.query.filter(user=user_main).exists() else 0 guanshi_status = 1 if UserGuanshi.query.filter(user=user_main).exists() else 0 # 生成token refresh = RefreshToken.for_user(user_main) token = str(refresh.access_token) # 获取群配置 group_info = self.huoquQunPeizhi() # 构建返回数据 response_data = { 'token': token, 'nicheng': boss_nickname, 'uid': user_main.UserUID, 'touxiang': user_main.Avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, # 🟢 注意:这里没有返回dingdantiaoshu(订单数量),与前端需求一致 'dashouqun': group_info.get('dashouqun', ''), 'dashouqunid': group_info.get('dashouqunid', ''), 'guanshiqun': group_info.get('guanshiqun', ''), 'guanshiqunid': group_info.get('guanshiqunid', '') } # 根据情况设置返回消息 if yaoqingma_wuxiao: msg = '登录成功,但邀请码无效' elif guanshi_jinyong: msg = '登录成功,但邀请码对应的管事账号已被禁用' else: msg = '登录成功' return Response({ 'code': 0, 'msg': msg, 'data': response_data }) def fanhuiDengluZhuceshuju(self, user_main, dashou_profile): """ 返回登录并注册的数据(用户已是打手的情况) """ # 获取老板昵称 boss_nickname = '微信用户' try: boss_profile = UserBoss.query.get(user=user_main) if boss_profile.nickname: boss_nickname = boss_profile.nickname except UserBoss.DoesNotExist: pass # 检查各身份状态(这里用户已是打手,所以都是1) dashou_status = 1 shangjia_status = 1 if UserShangjia.query.filter(user=user_main).exists() else 1 # 确保是1 guanshi_status = 1 if UserGuanshi.query.filter(user=user_main).exists() else 1 # 确保是1 # 生成token refresh = RefreshToken.for_user(user_main) token = str(refresh.access_token) # 获取群配置 group_info = self.huoquQunPeizhi() # 准备打手信息 dashou_data = self.zhuangbeiDashouFanhuiShuju(dashou_profile) # 合并所有数据 response_data = { 'token': token, 'nicheng': boss_nickname, 'uid': user_main.UserUID, 'touxiang': user_main.Avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, 'dashouqun': group_info.get('dashouqun', ''), 'dashouqunid': group_info.get('dashouqunid', ''), 'guanshiqun': group_info.get('guanshiqun', ''), 'guanshiqunid': group_info.get('guanshiqunid', ''), # 合并打手信息 **dashou_data } return Response({ 'code': 0, 'msg': '您已是打手,登录成功', 'data': response_data }) def zhuangbeiFanhuiShuju(self, user_main, dashou_profile, token): """ 准备完整的返回数据(登录+注册成功的情况) """ # 获取老板昵称 boss_nickname = '微信用户' try: boss_profile = UserBoss.query.get(user=user_main) if boss_profile.nickname: boss_nickname = boss_profile.nickname except UserBoss.DoesNotExist: pass # 身份状态(刚注册成功,应该都是1) dashou_status = 1 shangjia_status = 1 guanshi_status = 1 # 获取群配置 group_info = self.huoquQunPeizhi() # 准备打手信息 dashou_data = self.zhuangbeiDashouFanhuiShuju(dashou_profile) # 合并所有数据 response_data = { 'token': token, 'nicheng': boss_nickname, 'uid': user_main.UserUID, 'touxiang': user_main.Avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, 'club_id': getattr(user_main, 'ClubID', None) or '', 'dashouqun': group_info.get('dashouqun', ''), 'dashouqunid': group_info.get('dashouqunid', ''), 'guanshiqun': group_info.get('guanshiqun', ''), 'guanshiqunid': group_info.get('guanshiqunid', ''), # 合并打手信息 **dashou_data } return response_data def zhuangbeiDashouFanhuiShuju(self, dashou_profile): """ 准备打手返回数据(字段名严格匹配前端) """ # 构建基础信息 data = { 'dashounicheng': dashou_profile.nicheng, 'zhanghaostatus': dashou_profile.zhanghaozhuangtai, 'yongjin': str(dashou_profile.yue), # Decimal转字符串 'zonge': str(dashou_profile.zonge), 'yajin': str(dashou_profile.yajin), 'chenghao': dashou_profile.chenghao, 'jinfen': dashou_profile.jifen, 'chengjiaoliang': dashou_profile.chengjiaozongliang, 'zaixianzhuangtai': dashou_profile.zaixianzhuangtai, 'dashouzhuangtai': dashou_profile.zhuangtai, } # 查询并处理会员列表 (clumber) from jituan.services.huiyuan_bundle import build_auth_clumber cid = getattr(dashou_profile.user, 'ClubID', None) or None huiyuan_goumai_list = build_auth_clumber(dashou_profile.user.UserUID, club_id=cid) data['clumber'] = huiyuan_goumai_list return data class WechatLoginAndGuanshiRegisterView(APIView): """ 未登录用户微信登录+管事注册一体化接口 路径: /api/yonghu/zuzhangyqmzc 方法: POST 权限: 允许所有 请求体: { "code": "微信登录code", "inviteCode": "组长邀请码" } 成功返回 code: 200,数据同 GuanshiRegisterView """ permission_classes = [AllowAny] def post(self, request): code = request.data.get('code', '').strip() yaoqingma = request.data.get('inviteCode', '').strip() if not code: return Response({'code': 400, 'msg': '微信授权码不能为空', 'data': None}, status=status.HTTP_400_BAD_REQUEST) if not yaoqingma: return Response({'code': 400, 'msg': '邀请码不能为空', 'data': None}, status=status.HTTP_400_BAD_REQUEST) if len(yaoqingma) > 100: return Response({'code': 400, 'msg': '邀请码长度超过限制', 'data': None}, status=status.HTTP_400_BAD_REQUEST) from jituan.services.club_resolver import wechat_jscode2session_for_request wechat_data = wechat_jscode2session_for_request(request, code) if not wechat_data or 'openid' not in wechat_data: error_msg = wechat_data.get('errmsg', '微信授权失败') return Response({'code': 400, 'msg': f'微信登录失败: {error_msg}', 'data': None}, status=status.HTTP_400_BAD_REQUEST) openid = wechat_data['openid'] kehuduan_ip = self._huoqu_kehuduan_ip(request) with transaction.atomic(): user_main, created = User.objects.select_for_update().get_or_create( OpenID=openid, defaults={ 'UserUID': self._shengcheng_yonghu_id(), 'UserName': openid, } ) user_main.IP = kehuduan_ip user_main.UserLastLoginDate = timezone.now() user_main.save() if created: UserBoss.query.create(user=user_main, nickname='微信用户') if hasattr(user_main, 'GuanshiProfile'): return self._fanhui_xianyou_guanshi_info(user_main) try: zuzhang = UserZuzhang.query.select_related('user').get(yaoqingma=yaoqingma) except UserZuzhang.DoesNotExist: return Response({'code': 404, 'msg': '邀请码无效或不存在', 'data': None}, status=status.HTTP_404_NOT_FOUND) if zuzhang.zhuangtai != 1: return Response({'code': 403, 'msg': '该邀请码对应的组长账号已被禁用', 'data': None}, status=status.HTTP_403_FORBIDDEN) zuzhang_yonghuid = zuzhang.user.UserUID guanshi_yaoqingma = CreateInvitationCode(str(user_main.UserUID)) guanshi_profile = UserGuanshi.query.create( user=user_main, yaoqingma=guanshi_yaoqingma, yaoqingren=zuzhang_yonghuid, ) UserZuzhang.query.filter(pk=zuzhang.pk).update( yaoqing_zongshu=models.F('yaoqing_zongshu') + 1 ) # ========== 新增:组长每日统计(邀请管事 action=1) ========== try: update_zuzhang_daily_by_action( yonghuid=zuzhang_yonghuid, action=1 # 1 = 邀请管事 ) logger.info(f"组长每日统计更新成功(邀请管事):组长{zuzhang_yonghuid}") except Exception as e: logger.error(f"组长每日统计更新失败(邀请管事):{str(e)}") if not hasattr(user_main, 'DashouProfile'): dashou_profile = UserDashou.query.create( user=user_main, nicheng='大神', chenghao='普通大神', yaoqingren=zuzhang_yonghuid, ) else: dashou_profile = user_main.DashouProfile refresh = RefreshToken.for_user(user_main) token = str(refresh.access_token) response_data = self._zhuangbei_fanhui_shuju( user_main, dashou_profile, guanshi_profile, token ) return Response({'code': 0, 'msg': '登录并注册成功!', 'data': response_data}) # ---------- 辅助方法(与接口1完全相同,完整列出以保证完整性) ---------- def _get_wechat_openid(self, code): try: appid = getattr(settings, 'WEIXIN_APPID', '') secret = getattr(settings, 'WEIXIN_SECRET', '') if not appid or not secret: raise ValueError('微信配置未设置') url = 'https://api.weixin.qq.com/sns/jscode2session' params = { 'appid': appid, 'secret': secret, 'js_code': code, 'grant_type': 'authorization_code' } response = requests.get(url, params=params, timeout=10) result = response.json() if 'openid' in result: return result else: errcode = result.get('errcode', 'unknown') errmsg = result.get('errmsg', '未知错误') return {'errmsg': f'[{errcode}]{errmsg}'} except requests.exceptions.Timeout: logger.error('请求微信接口超时') return {'errmsg': '请求微信接口超时'} except Exception as e: logger.error(f'请求微信接口异常: {e}') return {'errmsg': f'请求微信接口异常: {str(e)}'} def _shengcheng_yonghu_id(self): for _ in range(10): timestamp_part = str(int(time.time()))[-5:].zfill(5) random_part = str(random.randint(0, 99)).zfill(2) user_id = timestamp_part + random_part if len(user_id) == 7 and user_id.isdigit(): if not User.query.filter(UserUID=user_id).exists(): return user_id raise Exception('生成用户ID失败') def _huoqu_kehuduan_ip(self, request): x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: ip = x_forwarded_for.split(',')[0].strip() if ip: return ip ip = request.META.get('REMOTE_ADDR', '') return ip if ip else '0.0.0.0' def _huoqu_qun_peizhi(self): try: qun_configs = Qunpeizhi.query.filter(id__in=[1, 2]) result = { 'dashouqun': '', 'dashouqunid': '', 'guanshiqun': '', 'guanshiqunid': '' } for config in qun_configs: if config.id == 1: result['dashouqun'] = config.GroupContent or '' result['dashouqunid'] = config.GroupID or '' elif config.id == 2: result['guanshiqun'] = config.GroupContent or '' result['guanshiqunid'] = config.GroupID or '' return result except Exception as e: logger.error(f"获取群配置失败: {e}") return {'dashouqun': '', 'dashouqunid': '', 'guanshiqun': '', 'guanshiqunid': ''} def _zhuangbei_dashou_fanhui(self, dashou_profile): data = { 'dashounicheng': dashou_profile.nicheng, 'zhanghaostatus': dashou_profile.zhanghaozhuangtai, 'yongjin': str(dashou_profile.yue), 'zonge': str(dashou_profile.zonge), 'yajin': str(dashou_profile.yajin), 'chenghao': dashou_profile.chenghao, 'jinfen': dashou_profile.jifen, 'chengjiaoliang': dashou_profile.chengjiaozongliang, 'zaixianzhuangtai': dashou_profile.zaixianzhuangtai, 'dashouzhuangtai': dashou_profile.zhuangtai, } from jituan.services.huiyuan_bundle import build_auth_clumber try: cid = getattr(dashou_profile.user, 'ClubID', None) or None huiyuan_list = build_auth_clumber(dashou_profile.user.UserUID, club_id=cid) except Exception as e: logger.warning(f"获取会员列表失败: {e}") huiyuan_list = [] data['clumber'] = huiyuan_list return data def _fanhui_xianyou_guanshi_info(self, user_main): guanshi_profile = user_main.GuanshiProfile dashou_profile = getattr(user_main, 'DashouProfile', None) group_info = self._huoqu_qun_peizhi() dashou_status = 1 if dashou_profile else 0 shangjia_status = 1 if hasattr(user_main, 'ShopProfile') else 0 guanshi_status = 1 try: nicheng = user_main.BossProfile.nickname or '微信用户' except: nicheng = '微信用户' refresh = RefreshToken.for_user(user_main) token = str(refresh.access_token) data = { 'token': token, 'nicheng': nicheng, 'uid': user_main.UserUID, 'touxiang': user_main.Avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, **group_info, } guanshi_data = { 'gszhstatus': guanshi_profile.zhuangtai, 'yaoqingzongshu': guanshi_profile.yaogingshuliang, 'fenyongzonge': str(guanshi_profile.chongzhifenrun), 'fenyongtixian': str(guanshi_profile.yue), 'yichongzhiDashou': guanshi_profile.jinrichongzhi, } data.update(guanshi_data) if dashou_profile: dashou_data = self._zhuangbei_dashou_fanhui(dashou_profile) data.update(dashou_data) else: data.update({ 'dashounicheng': '', 'zhanghaostatus': 0, 'yongjin': '0.00', 'zonge': '0.00', 'yajin': '0.00', 'chenghao': '', 'jinfen': 0, 'chengjiaoliang': 0, 'zaixianzhuangtai': 0, 'dashouzhuangtai': 0, 'clumber': [] }) return Response({'code': 0, 'msg': '您已是管事', 'data': data}) def _zhuangbei_fanhui_shuju(self, user_main, dashou_profile, guanshi_profile, token): try: nicheng = user_main.BossProfile.nickname or '微信用户' except: nicheng = '微信用户' dashou_status = 1 shangjia_status = 1 if hasattr(user_main, 'ShopProfile') else 0 guanshi_status = 1 group_info = self._huoqu_qun_peizhi() data = { 'token': token, 'nicheng': nicheng, 'uid': user_main.UserUID, 'touxiang': user_main.Avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, **group_info, } guanshi_data = { 'gszhstatus': guanshi_profile.zhuangtai, 'yaoqingzongshu': guanshi_profile.yaogingshuliang, 'fenyongzonge': str(guanshi_profile.chongzhifenrun), 'fenyongtixian': str(guanshi_profile.yue), 'yichongzhiDashou': guanshi_profile.jinrichongzhi, } data.update(guanshi_data) dashou_data = self._zhuangbei_dashou_fanhui(dashou_profile) data.update(dashou_data) return data class GuanshiRegisterView(APIView): """ 已登录用户注册管事接口 路径: /api/yonghu/guanshizhuce 方法: POST 权限: JWT认证 请求体: { "inviteCode": "组长邀请码" } 成功返回 code: 200 """ permission_classes = [IsAuthenticated] def post(self, request): # 1. 获取并验证邀请码 yaoqingma = request.data.get('inviteCode', '').strip() if not yaoqingma: return Response({'code': 400, 'msg': '邀请码不能为空', 'data': None}, status=status.HTTP_400_BAD_REQUEST) if len(yaoqingma) > 100: return Response({'code': 400, 'msg': '邀请码长度超过限制', 'data': None}, status=status.HTTP_400_BAD_REQUEST) current_user = request.user # 2. 检查用户是否已是管事 if hasattr(current_user, 'GuanshiProfile'): # 用户已是管事,直接返回现有信息 return self._fanhui_xianyou_guanshi_info(current_user) # 3. 验证邀请码对应的组长 try: zuzhang = UserZuzhang.query.select_related('user').get(yaoqingma=yaoqingma) except UserZuzhang.DoesNotExist: return Response({'code': 404, 'msg': '邀请码无效或不存在', 'data': None}, status=status.HTTP_404_NOT_FOUND) if zuzhang.zhuangtai != 1: return Response({'code': 403, 'msg': '该邀请码对应的组长账号已被禁用', 'data': None}, status=status.HTTP_403_FORBIDDEN) from jituan.services.invite_guard import assert_invite_same_club ok, invite_msg = assert_invite_same_club(request, zuzhang.user, current_user) if not ok: return Response({'code': 403, 'msg': invite_msg, 'data': None}, status=status.HTTP_403_FORBIDDEN) zuzhang_yonghuid = zuzhang.user.UserUID # 4. 事务内创建管事实体和打手(如果需要) with transaction.atomic(): # 创建管事扩展表 guanshi_yaoqingma = CreateInvitationCode(str(current_user.UserUID)) guanshi_profile = UserGuanshi.query.create( user=current_user, yaoqingma=guanshi_yaoqingma, yaoqingren=zuzhang_yonghuid, ) # 更新组长的邀请总数 UserZuzhang.query.filter(pk=zuzhang.pk).update( yaoqing_zongshu=models.F('yaoqing_zongshu') + 1 ) # ========== 新增:组长每日统计(邀请管事 action=1) ========== try: update_zuzhang_daily_by_action( yonghuid=zuzhang_yonghuid, action=1 # 1 = 邀请管事 ) logger.info(f"组长每日统计更新成功(邀请管事):组长{zuzhang_yonghuid}") except Exception as e: logger.error(f"组长每日统计更新失败(邀请管事):{str(e)}") # 检查用户是否已是打手,如果不是则创建 if not hasattr(current_user, 'DashouProfile'): dashou_profile = UserDashou.query.create( user=current_user, nicheng='大手子', chenghao='普通大手', yaoqingren=zuzhang_yonghuid, ) else: dashou_profile = current_user.DashouProfile # 5. 生成新token refresh = RefreshToken.for_user(current_user) token = str(refresh.access_token) # 6. 准备返回数据(包含所有字段) response_data = self._zhuangbei_fanhui_shuju( current_user, dashou_profile, guanshi_profile, token ) return Response({'code': 200, 'msg': '注册成功!', 'data': response_data}) # ---------- 辅助方法 ---------- def _huoqu_qun_peizhi(self): """获取群配置信息""" try: qun_configs = Qunpeizhi.query.filter(id__in=[1, 2]) result = { 'dashouqun': '', 'dashouqunid': '', 'guanshiqun': '', 'guanshiqunid': '' } for config in qun_configs: if config.id == 1: result['dashouqun'] = config.GroupContent or '' result['dashouqunid'] = config.GroupID or '' elif config.id == 2: result['guanshiqun'] = config.GroupContent or '' result['guanshiqunid'] = config.GroupID or '' return result except Exception as e: logger.error(f"获取群配置失败: {e}") return {'dashouqun': '', 'dashouqunid': '', 'guanshiqun': '', 'guanshiqunid': ''} def _zhuangbei_dashou_fanhui(self, dashou_profile): """组装打手信息(字段名与前端完全匹配)""" data = { 'dashounicheng': dashou_profile.nicheng, 'zhanghaostatus': dashou_profile.zhanghaozhuangtai, 'yongjin': str(dashou_profile.yue), 'zonge': str(dashou_profile.zonge), 'yajin': str(dashou_profile.yajin), 'chenghao': dashou_profile.chenghao, 'jinfen': dashou_profile.jifen, 'chengjiaoliang': dashou_profile.chengjiaozongliang, 'zaixianzhuangtai': dashou_profile.zaixianzhuangtai, 'dashouzhuangtai': dashou_profile.zhuangtai, } # 会员列表 from jituan.services.huiyuan_bundle import build_auth_clumber try: cid = getattr(dashou_profile.user, 'ClubID', None) or None huiyuan_list = build_auth_clumber(dashou_profile.user.UserUID, club_id=cid) except Exception as e: logger.warning(f"获取会员列表失败: {e}") huiyuan_list = [] data['clumber'] = huiyuan_list return data def _fanhui_xianyou_guanshi_info(self, user_main): """用户已是管事时的返回""" guanshi_profile = user_main.GuanshiProfile dashou_profile = getattr(user_main, 'DashouProfile', None) group_info = self._huoqu_qun_peizhi() dashou_status = 1 if dashou_profile else 0 shangjia_status = 1 if hasattr(user_main, 'ShopProfile') else 0 guanshi_status = 1 try: nicheng = user_main.BossProfile.nickname or '微信用户' except: nicheng = '微信用户' refresh = RefreshToken.for_user(user_main) token = str(refresh.access_token) data = { 'token': token, 'nicheng': nicheng, 'uid': user_main.UserUID, 'touxiang': user_main.Avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, **group_info, } # 管事信息 guanshi_data = { 'gszhstatus': guanshi_profile.zhuangtai, 'yaoqingzongshu': guanshi_profile.yaogingshuliang, 'fenyongzonge': str(guanshi_profile.chongzhifenrun), 'fenyongtixian': str(guanshi_profile.yue), 'yichongzhiDashou': guanshi_profile.jinrichongzhi, } data.update(guanshi_data) if dashou_profile: dashou_data = self._zhuangbei_dashou_fanhui(dashou_profile) data.update(dashou_data) else: data.update({ 'dashounicheng': '', 'zhanghaostatus': 0, 'yongjin': '0.00', 'zonge': '0.00', 'yajin': '0.00', 'chenghao': '', 'jinfen': 0, 'chengjiaoliang': 0, 'zaixianzhuangtai': 0, 'dashouzhuangtai': 0, 'clumber': [] }) return Response({'code': 200, 'msg': '您已是管事', 'data': data}) def _zhuangbei_fanhui_shuju(self, user_main, dashou_profile, guanshi_profile, token): """组装完整返回数据(新注册)""" try: nicheng = user_main.BossProfile.nickname or '微信用户' except: nicheng = '微信用户' dashou_status = 1 shangjia_status = 1 if hasattr(user_main, 'ShopProfile') else 0 guanshi_status = 1 group_info = self._huoqu_qun_peizhi() data = { 'token': token, 'nicheng': nicheng, 'uid': user_main.UserUID, 'touxiang': user_main.Avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, **group_info, } # 管事信息 guanshi_data = { 'gszhstatus': guanshi_profile.zhuangtai, 'yaoqingzongshu': guanshi_profile.yaogingshuliang, 'fenyongzonge': str(guanshi_profile.chongzhifenrun), 'fenyongtixian': str(guanshi_profile.yue), 'yichongzhiDashou': guanshi_profile.jinrichongzhi, } data.update(guanshi_data) dashou_data = self._zhuangbei_dashou_fanhui(dashou_profile) data.update(dashou_data) return data class ZuzhangZhuceView(APIView): """ 组长注册/激活接口 POST /yonghu/zuzhangzhuce 请求体: {"inviteCode": "xxx"} 返回: { "code": 200, "msg": "success", "data": { "ketixian": "0.00", # 可提现金额 "guanshiCount": 0, # 邀请的管事数量 "fenhongZonge": "0.00" # 分红总额 } } 或错误码 """ permission_classes = [IsAuthenticated] # 硬编码激活码(注意转义特殊字符) ACTIVATION_CODE = "237huwehdw77e777eyw224fr" def post(self, request): user = request.user invite_code = request.data.get('inviteCode', '').strip() if not invite_code: return Response({'code': 400, 'msg': '邀请码不能为空'}, status=400) # 检查是否已是组长 try: zuzhang = UserZuzhang.query.select_related('user').get(user=user) # 已注册,直接返回数据 data = self._build_response_data(zuzhang, user) return Response({'code':200, 'msg': '已注册', 'data': data}) except UserZuzhang.DoesNotExist: pass # 比对激活码 if invite_code != self.ACTIVATION_CODE: return Response({'code': 400, 'msg': '邀请码无效'}, status=400) from jituan.services.club_user import ensure_user_club_id from jituan.services.club_context import resolve_club_id_from_request ensure_user_club_id(user, resolve_club_id_from_request(request)) # 生成唯一组长邀请码:时间戳 + 用户ID后6位 + 随机字符 yaoqingma = self._generate_unique_yaoqingma(user.UserUID) # 创建组长扩展记录 try: with transaction.atomic(): zuzhang = UserZuzhang.query.create( user=user, yaoqingma=yaoqingma, fenyong_zonge=0.00, ketixian_jine=0.00, yaoqing_zongshu=0, jinri_fenyong=0.00, jinyue_fenyong=0.00, zhuangtai=1, jinri_tixian=0.00, ewai_tixian_xiane=0.00, kaioi_ewai_tixian=False, kaioi_ewai_fenhong=False, ewai_fenhong_jine=0.00 ) except IntegrityError: # 极低概率重复,重试一次 yaoqingma = self._generate_unique_yaoqingma(user.UserUID, retry=True) zuzhang = UserZuzhang.query.create( user=user, yaoqingma=yaoqingma, # 其他字段默认同上 fenyong_zonge=0.00, ketixian_jine=0.00, yaoqing_zongshu=0, jinri_fenyong=0.00, jinyue_fenyong=0.00, zhuangtai=1, jinri_tixian=0.00, ewai_tixian_xiane=0.00, kaioi_ewai_tixian=False, kaioi_ewai_fenhong=False, ewai_fenhong_jine=0.00 ) data = self._build_response_data(zuzhang, user) return Response({'code':200, 'msg': '注册成功', 'data': data}) def _generate_unique_yaoqingma(self, yonghuid, retry=False): """生成唯一邀请码:时间戳+用户ID后6位+随机2位字符""" timestamp = str(int(time.time()))[-6:] # 取后6位 uid_part = yonghuid[-6:] if len(yonghuid) >= 6 else yonghuid.rjust(6, '0') rand_str = ''.join(random.choices(string.ascii_uppercase + string.digits, k=2)) base = f"Z{timestamp}{uid_part}{rand_str}" # 确保唯一性(若已存在则追加随机数,最多尝试3次) for _ in range(3): if not UserZuzhang.query.filter(yaoqingma=base).exists(): return base base = base + random.choice(string.digits) # 极端情况,再附加时间戳毫秒 millis = str(time.time()).replace('.', '')[-4:] return base + millis def _build_response_data(self, zuzhang, user): """组装前端所需数据""" # 计算邀请的管事数量(通过UserGuanshi表的yaoqingren字段关联到user的yonghuid) guanshi_count = UserGuanshi.query.filter(yaoqingren=user.UserUID).count() return { 'ketixian': str(zuzhang.ketixian_jine), # 可提现余额 'guanshiCount': guanshi_count, # 邀请管事数 'fenhongZonge': str(zuzhang.fenyong_zonge) # 分红总额 } class ZuzhangXinxiView(APIView): """ 组长信息查询接口 POST /yonghu/zuzhangxinxi 请求体: 无 返回: 同注册接口成功返回格式 """ permission_classes = [IsAuthenticated] def post(self, request): user = request.user try: zuzhang = UserZuzhang.query.get(user=user) except UserZuzhang.DoesNotExist: return Response({'code': 404, 'msg': '用户不是组长'}, status=404) guanshi_count = UserGuanshi.query.filter(yaoqingren=user.UserUID).count() data = { 'ketixian': str(zuzhang.ketixian_jine), 'guanshiCount': guanshi_count, 'fenhongZonge': str(zuzhang.fenyong_zonge) } return Response({'code': 200, 'msg': 'success', 'data': data})