1912 lines
76 KiB
Python
1912 lines
76 KiB
Python
"""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"""<xml>
|
||
<ToUserName><![CDATA[{from_user}]]></ToUserName>
|
||
<FromUserName><![CDATA[{to_user}]]></FromUserName>
|
||
<CreateTime>{int(time.time())}</CreateTime>
|
||
<MsgType><![CDATA[text]]></MsgType>
|
||
<Content><![CDATA[{content}]]></Content>
|
||
</xml>"""
|
||
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):
|
||
from jituan.services.api_compat import legacy_miniapp_error
|
||
|
||
# 1. 获取并验证邀请码参数
|
||
yaoqingma = request.data.get('inviteCode')
|
||
if not yaoqingma:
|
||
return legacy_miniapp_error(400, '邀请码不能为空')
|
||
|
||
yaoqingma = yaoqingma.strip()
|
||
if len(yaoqingma) > 100:
|
||
return legacy_miniapp_error(400, '邀请码长度超过限制')
|
||
|
||
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 legacy_miniapp_error(404, '邀请码无效或不存在')
|
||
|
||
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:
|
||
logger.warning(
|
||
'dashouzhuce 俱乐部校验失败 uid=%s club=%s msg=%s',
|
||
current_user.UserUID, resolve_club_id_for_wechat_request(request), msg,
|
||
)
|
||
return legacy_miniapp_error(403, msg)
|
||
|
||
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:
|
||
|
||
# 事务会自动回滚
|
||
logger.exception('dashouzhuce 注册异常 uid=%s', current_user.UserUID)
|
||
return legacy_miniapp_error(500, f'注册过程中发生系统错误: {str(e)}')
|
||
|
||
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': '注册成功!已开通打手、管事身份。',
|
||
'msg': '注册成功!已开通打手、管事身份。',
|
||
'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
|
||
data['dashoustatus'] = 1
|
||
data['guanshistatus'] = 1 if hasattr(current_user, 'GuanshiProfile') else 0
|
||
data['shangjiastatus'] = 1 if hasattr(current_user, 'ShangjiaProfile') else 0
|
||
return data
|
||
|
||
|
||
class GuanshiYaoqingDashouListView(APIView):
|
||
"""
|
||
管事获取已邀请打手列表(支持筛选、搜索、分页)
|
||
POST /api/yonghu/gshqyqds/
|
||
Headers: Authorization: Bearer <token>
|
||
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:
|
||
from jituan.services.api_compat import legacy_miniapp_error
|
||
|
||
# 1. 获取并验证参数
|
||
code = request.data.get('code', '').strip()
|
||
yaoqingma = request.data.get('inviteCode', '').strip()
|
||
|
||
if not code:
|
||
return legacy_miniapp_error(1, '微信授权码不能为空')
|
||
|
||
if not yaoqingma:
|
||
return legacy_miniapp_error(2, '邀请码不能为空')
|
||
|
||
if len(yaoqingma) > 100:
|
||
return legacy_miniapp_error(3, '邀请码长度超过限制')
|
||
|
||
# 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 legacy_miniapp_error(4, f'微信登录失败: {error_msg}')
|
||
|
||
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:
|
||
ensure_user_club_id(user_main, club_id)
|
||
return legacy_miniapp_error(404, '邀请码无效或不存在')
|
||
|
||
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:
|
||
logger.warning(
|
||
'wdlyhdl 俱乐部校验失败 uid=%s club=%s msg=%s invite=%s',
|
||
user_main.UserUID, club_id, invite_msg, yaoqingma[:8],
|
||
)
|
||
return legacy_miniapp_error(403, invite_msg)
|
||
|
||
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
|
||
)
|
||
|
||
# 返回友好错误信息(HTTP 200 以便老端读出 msg)
|
||
from jituan.services.api_compat import legacy_miniapp_error
|
||
return legacy_miniapp_error(99, '系统繁忙,请稍后重试')
|
||
|
||
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})
|