Files
Django/users/views/user_info.py
XingQue 86e7a60642 fix: 纯后端适配老小程序抢单池会员展示
/ddhq 有权限时返回 yaoqiuleixing=0;clumber 统一 huiyuanid+daoqi 字符串并补齐 ID 变体;读取前自动补写已付未落库的体验/正式会员。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-08 14:05:22 +08:00

1099 lines
42 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""users.views.user_info - 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__)
def huoquYonghuDaili(request):
"""
获取用户浏览器/设备信息
"""
user_agent = request.META.get('HTTP_USER_AGENT', '')
# 这里可以添加更复杂的用户代理解析
# 为简化我们只返回原始字符串的前100个字符
return user_agent[:100] if user_agent else ''
def huoquKehuduanIP(request):
"""
强化版安全、兼容地获取客户端真实IP地址
"""
ip = None
# 1. 从标准头部获取IP链
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
# 按逗号分割取第一个即最原始的客户端IP
ip = x_forwarded_for.split(',')[0].strip()
# 2. 如果上面没有则用Nginx传递的X-Real-IP
if not ip:
ip = request.META.get('HTTP_X_REAL_IP')
# 3. 最后降级到连接地址
if not ip:
ip = request.META.get('REMOTE_ADDR')
# ===== 核心清洗和验证IP格式 =====
if ip:
# 情况A: 处理IPv4映射的IPv6地址 (::ffff:192.168.1.1)
if ip.startswith('::ffff:'):
ip = ip[7:]
# 情况B: 处理带端口的IPv4地址 (223.91.110.123:62417)
# 这是导致您记录“三段”的最可能原因!
# 如果IP字符串中有冒号且最后一个冒号前的部分是IPv4则分离端口。
if ':' in ip and '.' in ip:
# 尝试按冒号分割,取第一部分
possible_ip = ip.rsplit(':', 1)[0] # 从右边分割一次
# 验证它是否是有效的IPv4地址
try:
ipaddress.IPv4Address(possible_ip) # 如果通过验证,说明格式正确
ip = possible_ip
except (ipaddress.AddressValueError, ValueError):
# 如果验证失败说明可能不是IPv4带端口保持原样或进行其他处理
pass
# 情况C: 验证最终结果是否为有效IPv4可选但推荐
try:
# 此步骤会抛出异常如果ip变量此时仍是“223.91.110.”这种格式
ipaddress.IPv4Address(ip)
except (ipaddress.AddressValueError, ValueError):
# 记录一个警告日志并将IP标记为无效或存为原始字符串
print(f"警告获取到无效或非标准IP地址格式: {ip}")
# 您可以选择在此处 return None 或存储原始字符串以供排查
return ip
class UserInfoUpdateView(APIView):
permission_classes = [permissions.IsAuthenticated]
# 进程内缓存防止同一code被重复调用微信单进程安全多进程小概率重复但用户极少可忽略
_phone_cache = {}
def post(self, request):
try:
user = request.user
boss = self._get_or_create_boss(user)
result = {}
# 头像
av = request.FILES.get('avatar')
if av:
ok, msg = validate_image(av)
if not ok:
return Response({'code': 1, 'msg': msg, 'data': None}, status=400)
url = self._upload_avatar(user, av)
if url:
result['touxiang'] = url
user.Avatar = url
else:
return Response({'code': 2, 'msg': '头像上传失败', 'data': None}, status=500)
# 昵称
nick = request.data.get('nicheng')
if nick:
nick = str(nick).strip()
if len(nick) > 20:
return Response({'code': 3, 'msg': '昵称过长', 'data': None}, status=400)
boss.nickname = nick
result['nicheng'] = nick
# 手机号 —— 极简可靠版
code = request.data.get('shoujihao_code')
if code:
phone = self._get_phone_safe(code, request)
if phone is None:
return Response({'code': 6, 'msg': '手机号获取失败,请重新授权', 'data': None}, status=400)
user.Phone = phone
user.PhoneVerified = True
result['phone'] = phone
if not result:
return Response({'code': 5, 'msg': '无更新', 'data': None}, status=400)
with transaction.atomic():
user.save()
boss.save()
return Response({'code': 0, 'msg': '更新成功', 'data': result})
except Exception as e:
logger.exception("系统异常")
return Response({'code': 99, 'msg': str(e), 'data': None}, status=500)
# ---------- 安全手机号(内存去重) ----------
def _get_phone_safe(self, code, request):
cache_key = f'{code}:{self._resolve_wx_club_id(request, request.user)}'
if cache_key in self._phone_cache:
cached = self._phone_cache[cache_key]
return cached if cached != '__FAIL__' else None
self._phone_cache[cache_key] = '__PROCESSING__'
try:
phone = self._call_wechat(code, request)
self._phone_cache[cache_key] = phone
return phone
except Exception as e:
logger.error(f"微信调用失败: {str(e)}")
self._phone_cache[cache_key] = '__FAIL__'
return None
def _resolve_wx_club_id(self, request, user):
"""手机号 code 与小程序 appid 绑定,须用对应俱乐部的 wx 配置换 token。"""
from jituan.constants import CLUB_ID_DEFAULT
from jituan.services.club_context import resolve_club_id_from_request
from jituan.services.club_user import get_user_club_id
club_id = resolve_club_id_from_request(request)
if club_id == CLUB_ID_DEFAULT:
user_club = get_user_club_id(user)
if user_club:
return user_club
return club_id
# ---------- 微信接口调用(按俱乐部 appid 换 token ----------
def _call_wechat(self, code, request):
club_id = self._resolve_wx_club_id(request, request.user)
token = self._get_new_token(club_id, force_refresh=True)
url = f'https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={token}'
resp = requests.post(url, json={'code': code}, timeout=10)
data = resp.json()
errcode = data.get('errcode')
if errcode == 0:
return data['phone_info']['purePhoneNumber']
if errcode == 40001:
token = self._get_new_token(club_id, force_refresh=True)
url = f'https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={token}'
resp = requests.post(url, json={'code': code}, timeout=10)
data = resp.json()
if data.get('errcode') == 0:
return data['phone_info']['purePhoneNumber']
raise Exception(f"微信错误(errcode={errcode}): {data.get('errmsg')}")
def _get_new_token(self, club_id, force_refresh=False):
from utils.weixin_token import get_club_mini_access_token
token = get_club_mini_access_token(club_id, force_refresh=force_refresh)
if token:
return token
raise Exception(f'俱乐部 {club_id} 缺少微信配置或获取 access_token 失败')
# ---------- 工具 ----------
def _get_or_create_boss(self, user):
try:
return UserBoss.query.get(user=user)
except UserBoss.DoesNotExist:
return UserBoss.query.create(user=user, nickname='微信用户')
def _upload_avatar(self, user, file):
try:
uid = user.UserUID
if not uid: return None
old = user.Avatar
if old: delete_from_oss(old)
ext = os.path.splitext(file.name)[1] or '.jpg'
name = f"{uuid.uuid4().hex}{ext}"
path = f"avatar/{uid}/{name}"
return path if upload_to_oss(file, path) else None
except:
return None
class DashouXinxiAPIView(APIView):
"""
获取打手信息接口
请求方式POST
认证方式JWT Token
返回:打手基本信息、资产信息、会员信息,金牌打手额外返回专属字段
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# 1. 获取当前登录用户
current_user = request.user
yonghuid = current_user.UserUID
# 2. 查询打手扩展表
try:
dashou_profile = current_user.DashouProfile
except UserDashou.DoesNotExist:
return Response({
'code': 400,
'message': '您不是打手身份',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 3. 构建基础信息响应(原有字段完全保留)
data = {
'dashounicheng': dashou_profile.nicheng or '',
'zhanghaostatus': dashou_profile.zhanghaozhuangtai,
'zaixianzhuangtai': dashou_profile.zaixianzhuangtai,
'dashouzhuangtai': dashou_profile.zhuangtai,
'yongjin': str(dashou_profile.yue) if dashou_profile.yue is not None else '0.00',
'zonge': str(dashou_profile.zonge) if dashou_profile.zonge is not None else '0.00',
'yajin': str(dashou_profile.yajin) if dashou_profile.yajin is not None else '0.00',
'jiedanZongliang':dashou_profile.jiedanzongliang if dashou_profile.jiedanzongliang is not None else '0',
'zuijinTixian': str(dashou_profile.jinritixian_jine) if dashou_profile.jinritixian_jine is not None else '0.00',
'chengjiaoliang': dashou_profile.chengjiaozongliang,
'jifen': dashou_profile.jifen,
'chenghao': dashou_profile.chenghao or '',
}
# 4. 查询会员信息(老端 clumberhuiyuanid + daoqi 字符串)
try:
from jituan.services.huiyuan_bundle import build_user_clumber
huiyuan_list = build_user_clumber(yonghuid)
except Exception:
huiyuan_list = []
data['clumber'] = huiyuan_list
# 5. 如果是金牌打手,添加专属字段
if dashou_profile.chenghao == "金牌打手":
data.update({
'jiedanZongliang': dashou_profile.jiedanzongliang, # 接单总量
'jinriJiedan': str(dashou_profile.jinrishouyi) if dashou_profile.jinrishouyi is not None else '0.00', # 今日接单金额
'zuijinTixian': str(dashou_profile.jinritixian_jine) if dashou_profile.jinritixian_jine is not None else '0.00', # 最近提现金额(今日已提现)
})
# 6. 返回成功响应
return Response({
'code': 200,
'message': '获取成功',
'data': data
}, status=status.HTTP_200_OK)
except Exception as e:
# 生产环境应使用 logging 记录错误,而不是 print
# logger.error(f"获取打手信息失败: {e}", exc_info=True)
return Response({
'code': 500,
'message': f'服务器内部错误: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class HuoQuYaoQingRenView(APIView):
"""
获取当前打手的邀请人信息
POST /yonghu/hqwdyqr
Headers: Authorization: Bearer <token>
返回邀请人的用户ID、昵称来自老板扩展表、头像相对URL
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
current_user = request.user # 当前登录用户
yonghuid = current_user.UserUID # 7位用户ID
# 1. 校验打手身份及账号状态(只检查账号状态,不检查接单状态)
try:
dashou = UserDashou.query.get(user=current_user)
if dashou.zhanghaozhuangtai != 1:
return Response({
'code': 403,
'message': '您的账号已被封禁,无法查看邀请人',
'data': None
})
except UserDashou.DoesNotExist:
return Response({
'code': 404,
'message': '您还不是打手,无法查看邀请人',
'data': None
})
# 2. 获取邀请人ID
yaoqingren_id = dashou.yaoqingren
if not yaoqingren_id:
return Response({
'code': 404,
'message': '您没有邀请人',
'data': None
})
# 3. 查询邀请人主表(头像)
try:
inviter_main = User.query.get(UserUID=yaoqingren_id)
touxiang = inviter_main.Avatar or ''
except User.DoesNotExist:
return Response({
'code': 404,
'message': '邀请人信息不存在',
'data': None
})
# 4. 查询老板扩展表(昵称),如果没有则使用空字符串
nicheng = ''
try:
boss = UserBoss.query.get(user=inviter_main)
nicheng = boss.nickname or ''
except UserBoss.DoesNotExist:
pass # 邀请人可能不是老板,昵称留空
response_data = {
'uid': yaoqingren_id,
'nicheng': nicheng,
'touxiang': touxiang # 相对路径,前端自行拼接
}
return Response({
'code': 200,
'message': '获取成功',
'data': response_data
})
except Exception as e:
logger.error(f'获取邀请人失败: {str(e)}', exc_info=True)
return Response({
'code': 500,
'message': '系统繁忙,请稍后重试',
'data': None
})
class ZaixianZhuangtaiAPIView(APIView):
"""
更改打手在线状态接口
请求方式POST
认证方式JWT Token
请求参数status0=离线1=在线)
返回:更新后的状态信息
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# 1. 获取请求参数
new_status = request.data.get('status')
if new_status is None:
return Response({
'code': 400,
'message': '缺少状态参数',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 转换为整数确保是0或1
try:
new_status = int(new_status)
if new_status not in [0, 1]:
return Response({
'code': 400,
'message': '状态参数必须是0或1',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
except ValueError:
return Response({
'code': 400,
'message': '状态参数必须是整数',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 2. 获取当前登录用户
current_user = request.user
yonghuid = current_user.UserUID
# 3. 查询打手扩展表
try:
dashou_profile = current_user.DashouProfile
except UserDashou.DoesNotExist:
return Response({
'code': 400,
'message': '您不是打手身份',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 4. 检查账号状态(是否被封禁)
if dashou_profile.zhanghaozhuangtai != 1:
return Response({
'code': 403,
'message': '账号状态异常,无法更改在线状态',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
# 5. 更新在线状态
old_status = dashou_profile.zaixianzhuangtai
# 如果状态没有变化,直接返回成功
if old_status == new_status:
return Response({
'code': 200,
'message': '状态未变化',
'data': {
'zaixianzhuangtai': new_status,
'status_text': '在线' if new_status == 1 else '离线'
}
}, status=status.HTTP_200_OK)
# 更新状态
dashou_profile.zaixianzhuangtai = new_status
dashou_profile.save()
# 6. 返回成功响应
return Response({
'code': 200,
'message': '状态更新成功',
'data': {
'zaixianzhuangtai': new_status,
'status_text': '在线' if new_status == 1 else '离线'
}
}, status=status.HTTP_200_OK)
except Exception as e:
return Response({
'code': 500,
'message': f'服务器内部错误: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class GuanshiXinxiView(APIView):
"""
获取管事信息接口
路径:/yonghu/guanshixinxi
方法POST
权限JWT认证
功能:获取当前管事的详细信息,包括基本信息、分红信息以及已充值打手数量
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# 获取当前用户通过JWT认证
current_user = request.user
# 查询管事扩展表
try:
guanshi_profile = UserGuanshi.query.get(user=current_user)
# 🔥【新增】查询已充值打手数量(从分红表中统计)
# Gsfenhong 表中 guanshi 字段存储管事IDyonghuid
yichongzhi_count = Gsfenhong.query.filter(guanshi=current_user.UserUID).count()
# 返回管事信息
return Response({
'code': 200,
'msg': '获取成功',
'data': {
'gszhstatus': guanshi_profile.zhuangtai, # 管事账号状态
'yaoqingzongshu': guanshi_profile.yaogingshuliang, # 邀请打手总数
'fenyongzonge': float(guanshi_profile.chongzhifenrun), # 充值分佣总额
'fenyongtixian': float(guanshi_profile.yue), # 可提现余额
'yichongzhiDashou': yichongzhi_count, # 已充值打手数量
}
})
except UserGuanshi.DoesNotExist:
# 用户不是管事,返回错误
return Response({
'code': 400,
'msg': '您还不是管事,请先注册',
'data': None
}, status=400)
except Exception as e:
# 捕获所有异常,返回错误信息并记录日志
logger.error(f"获取管事信息异常: {str(e)}", exc_info=True)
return Response({
'code': 500,
'msg': f'获取信息失败: {str(e)}',
'data': None
}, status=500)
class ShangJiaXinXiView(APIView):
"""
获取商家信息接口 (POST /yonghu/shangjiaxinxi)
权限需JWT认证
返回:商家基本信息 + 称号列表 + 今日/本月统计
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# 获取商家扩展资料
shangjia = request.user.ShopProfile
except ObjectDoesNotExist:
return Response({
'code': 4001,
'msg': '您还未注册成为商家或商家资料不存在',
'data': None
}, status=status.HTTP_200_OK)
user = request.user
yonghuid = user.UserUID
today = timezone.now().date()
this_month_start = today.replace(day=1)
# ------------------- 1. 从 UserShangjia 获取基础字段 -------------------
response_data = {
'nicheng': shangjia.nicheng or '',
'sjzhzhuangtai': shangjia.zhuangtai, # 商家账号状态
'fadanzong': shangjia.fabu, # 发单总量(累计)
'fabu': shangjia.fabu,
'tuikuanzong': shangjia.tuikuan, # 退款总量(累计)
'sjyue': float(shangjia.yue), # 商家余额
# 'riliushui' 和 'yueliushui' 将从统计表计算,不再使用原字段
}
# ------------------- 2. 获取称号列表(仅限商家称号) -------------------
chenghao_list = []
# 查询当前用户的所有称号关联
yonghu_chenghao_qs = YonghuChenghao.query.filter(yonghu=user).select_related('chenghao')
for yc in yonghu_chenghao_qs:
chenghao_obj = yc.chenghao
# 只保留类型为“商家”的称号
if chenghao_obj.leixing == 'shangjia':
chenghao_list.append({
'mingcheng': chenghao_obj.mingcheng,
'texiao_json': chenghao_obj.texiao_miaoshu, # 前端需要的特效描述
# 如果还需要其他字段如图标URL可在此添加打手端有 texiao_json
})
response_data['chenghao_list'] = chenghao_list
# ------------------- 3. 从 MerchantDailyStats 获取今日统计 -------------------
try:
today_stat = MerchantDailyStats.query.get(MerchantID=yonghuid, Date=today)
jinri_paifa_dingdan = today_stat.AssignedOrderCount
jinri_paifa_jine = float(today_stat.AssignedAmount)
jinri_tuikuan_dingdan = today_stat.RefundOrderCount
except MerchantDailyStats.DoesNotExist:
jinri_paifa_dingdan = 0
jinri_paifa_jine = 0.00
jinri_tuikuan_dingdan = 0
response_data['jinripaidan'] = jinri_paifa_dingdan # 今日派单数(新增)
response_data['jinrituikuan'] = jinri_tuikuan_dingdan # 今日退款数(新增)
response_data['riliushui'] = jinri_paifa_jine # 今日流水(派发总金额)
# ------------------- 4. 从 MerchantDailyStats 获取本月统计 -------------------
month_stats = MerchantDailyStats.query.filter(
MerchantID=yonghuid,
Date__gte=this_month_start,
Date__lte=today
).aggregate(
total_paifa_jine=Sum('AssignedAmount'),
total_paifa_dingdan=Sum('AssignedOrderCount')
)
yue_paifa_jine = float(month_stats.get('total_paifa_jine') or 0.00)
# yue_paifa_dingdan = month_stats.get('total_paifa_dingdan') or 0 # 如果前端不需要可不加
response_data['yueliushui'] = yue_paifa_jine # 今月流水(派发总金额)
# (可选)如果前端还需要今月派单总数,取消下面注释
# response_data['jinyuepaidan'] = yue_paifa_dingdan
return Response({
'code': 200,
'msg': '获取商家信息成功',
'data': response_data
}, status=status.HTTP_200_OK)
class ShangjiaNichengGengxinView(APIView):
"""商家老板修改昵称 POST /yonghu/shangjiagxnc"""
permission_classes = [IsAuthenticated]
def post(self, request):
from merchant_ops.services.authz import is_merchant_owner
if not is_merchant_owner(request.user):
return Response({'code': 403, 'msg': '仅商家老板可修改昵称', 'data': None}, status=status.HTTP_200_OK)
nicheng = (request.data.get('nicheng') or '').strip()
if not nicheng:
return Response({'code': 400, 'msg': '昵称不能为空', 'data': None}, status=status.HTTP_200_OK)
if len(nicheng) > 20:
return Response({'code': 400, 'msg': '昵称不能超过20字', 'data': None}, status=status.HTTP_200_OK)
try:
shangjia = request.user.ShopProfile
except ObjectDoesNotExist:
return Response({'code': 4001, 'msg': '商家资料不存在', 'data': None}, status=status.HTTP_200_OK)
if UserShangjia.query.filter(nicheng=nicheng).exclude(user=request.user).exists():
return Response({'code': 400, 'msg': '该昵称已被使用', 'data': None}, status=status.HTTP_200_OK)
shangjia.nicheng = nicheng
shangjia.save(update_fields=['nicheng'])
return Response({'code': 200, 'msg': '昵称已更新', 'data': {'nicheng': nicheng}}, status=status.HTTP_200_OK)
class BossBiaoshiView(APIView):
"""
获取用户的GoEasy标识 - 极简实现
POST /yonghu/bossbiaoshi
需要JWT Token认证
"""
permission_classes = [IsAuthenticated]
def post(self, request):
# 1. 直接从request.user获取用户
user = request.user
# 2. 获取yonghuid确保用户已登录
if not user or not hasattr(user, 'UserUID'):
return Response({
'code': 401,
'msg': '用户未认证或用户信息不完整'
}, status=401)
# 3. 生成标识Boss + yonghuid与前端约定
boss_biaoshi = f"Boss{user.UserUID}"
# 4. 返回给前端
return Response({
'code': 200,
'msg': '获取成功',
'data': {
'boss_biaoshi': boss_biaoshi,
'yonghuid': user.UserUID,
'user_type': user.UserType
}
})
class YaoqingmaView(APIView):
"""
获取或生成管事邀请码
访问路径: /api/yonghu/yaoqingma/
请求方法: GET
权限要求: JWT Token认证通过的用户
"""
permission_classes = [IsAuthenticated]
def post(self, request):
"""
获取当前用户的邀请码
逻辑:
1. 通过JWT获取当前用户 (request.user)
2. 反向查询UserGuanshi扩展表
3. 验证管事状态(zhuangtai=1)
4. 已有邀请码直接返回,没有则生成并保存
"""
# 获取当前认证用户
current_user = request.user
# 1. 使用反向查询获取管事扩展信息 (性能最优方式)
try:
guanshi_profile = current_user.GuanshiProfile
except UserGuanshi.DoesNotExist:
# 用户不是管事,没有扩展表记录
return Response({
'code': 403,
'message': '当前用户不是管事身份,无法生成邀请码',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
# 2. 验证管事状态 (zhuangtai 必须等于 1)
if guanshi_profile.zhuangtai != 1:
return Response({
'code': 403,
'message': '管事账号状态异常,无法生成邀请码',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
# 3. 检查是否已存在邀请码
existing_yaoqingma = guanshi_profile.yaoqingma
# 如果存在且非空,直接返回
if existing_yaoqingma and existing_yaoqingma.strip() != '':
return Response({
'code': 200,
'message': '成功获取现有邀请码',
'data': {
'yaoqingma': existing_yaoqingma,
'is_new': False # 标记是否为新生成的
}
})
# 4. 生成新的邀请码 (使用用户ID字符串)
# 注意: 这里使用yonghuid不是主键id如你要求的"123456"这种形式
yonghuid_str = str(current_user.UserUID)
new_yaoqingma = CreateInvitationCode(yonghuid_str)
# 5. 验证生成的邀请码格式
if not VerifyInvitationCode(new_yaoqingma):
return Response({
'code': 500,
'message': '邀请码生成失败,格式验证错误',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# 6. 保存到数据库 (使用事务确保数据一致性)
try:
with transaction.atomic():
guanshi_profile.yaoqingma = new_yaoqingma
guanshi_profile.save()
except Exception as e:
# 这里可以记录日志
return Response({
'code': 500,
'message': f'保存邀请码到数据库失败: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# 7. 返回新生成的邀请码
return Response({
'code': 200,
'message': '成功生成新的邀请码',
'data': {
'yaoqingma': new_yaoqingma,
'is_new': True # 标记是新生成的
}
})
class DashouZiliaoHuoquView(APIView):
"""
打手资料获取接口
URL: /yonghu/dszjxxhq
方法: POST
认证: JWT Token
返回: 打手扩展表信息
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# 使用select_related高效查询避免N+1问题
dashou = UserDashou.query.select_related('user').get(user=request.user)
# 构建返回数据
data = {
'nicheng': dashou.nicheng if dashou.nicheng else '',
'jieshao': dashou.jieshao if dashou.jieshao else '',
'dianhua': dashou.dianhua if dashou.dianhua else '',
'wechat': dashou.wechat if dashou.wechat else '',
'uid': request.user.UserUID, # 从主表获取UID
'avatar': request.user.Avatar if request.user.Avatar else '', # 头像相对URL
}
return Response({
'code': 200,
'msg': 'success',
'data': data
}, status=status.HTTP_200_OK)
except UserDashou.DoesNotExist:
logger.warning(f'打手扩展表不存在: user_id={request.user.id}')
return Response({
'code': 404,
'msg': '打手信息不存在'
}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
logger.error(f'获取打手资料异常: {str(e)}', exc_info=True)
return Response({
'code': 500,
'msg': '服务器内部错误'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class DashouGengxinView(APIView):
"""
打手信息更新接口
URL: /yonghu/dsgxxx
方法: POST
认证: JWT Token
参数: nicheng(昵称), dianhua(电话), wechat(微信), jieshao(介绍)
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# 获取请求数据
nicheng = request.data.get('nicheng', '').strip()
dianhua = request.data.get('dianhua', '').strip()
wechat = request.data.get('wechat', '').strip()
jieshao = request.data.get('jieshao', '').strip()
# 数据验证
validation_errors = self.validate_data(nicheng, dianhua, wechat, jieshao)
if validation_errors:
return Response({
'code': 400,
'msg': validation_errors
}, status=status.HTTP_400_BAD_REQUEST)
# 获取打手扩展表
try:
dashou = UserDashou.query.get(user=request.user)
except UserDashou.DoesNotExist:
return Response({
'code': 404,
'msg': '打手信息不存在'
}, status=status.HTTP_404_NOT_FOUND)
# 更新数据
update_fields = []
if nicheng is not None:
dashou.nicheng = nicheng if nicheng != '' else None
update_fields.append('nicheng')
if dianhua is not None:
dashou.dianhua = dianhua if dianhua != '' else None
update_fields.append('dianhua')
if wechat is not None:
dashou.wechat = wechat if wechat != '' else None
update_fields.append('wechat')
if jieshao is not None:
dashou.jieshao = jieshao if jieshao != '' else None
update_fields.append('jieshao')
# 保存更新(只更新修改的字段)
if update_fields:
dashou.save(update_fields=update_fields)
return Response({
'code': 200,
'msg': '更新成功',
'data': {
'nicheng': dashou.nicheng or '',
'dianhua': dashou.dianhua or '',
'wechat': dashou.wechat or '',
'jieshao': dashou.jieshao or ''
}
}, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f'更新打手信息异常: {str(e)}', exc_info=True)
return Response({
'code': 500,
'msg': '服务器内部错误'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def validate_data(self, nicheng, dianhua, wechat, jieshao):
"""数据验证方法"""
errors = []
# 1. 昵称验证(必填)
if not nicheng:
errors.append('昵称不能为空')
elif len(nicheng) > 20:
errors.append('昵称不能超过20个字符')
# 2. 手机号验证(非必填,但填写时必须正确)
if dianhua:
# 手机号正则1开头11位数字
phone_pattern = r'^1[3-9]\d{9}$'
if not re.match(phone_pattern, dianhua):
errors.append('手机号格式不正确')
# 3. 微信号验证(非必填)
if wechat and len(wechat) > 30:
errors.append('微信号不能超过30个字符')
# 4. 个人介绍验证
if jieshao and len(jieshao) > 200:
errors.append('个人介绍不能超过200个字符')
return '; '.join(errors) if errors else None
class DashouJianquanView(APIView):
"""打手消息权限鉴权"""
permission_classes = [IsAuthenticated]
def post(self, request):
# ========== 1. 通过 JWT 获取当前登录用户 ==========
user = request.user # User 实例
# ========== 2. 检查是否为已注册打手 ==========
try:
dashou = user.DashouProfile # OneToOneField 反向查询
except UserDashou.DoesNotExist:
return Response({
'code': 0,
'allow': 0,
'msg': '您尚未注册打手身份,请先注册'
})
# ========== 3. 条件一:押金 >= 5 元 ==========
if dashou.yajin and dashou.yajin >= 5:
return Response({
'code': 0,
'allow': 1,
'msg': '鉴权通过(押金满足条件)'
})
# ========== 4. 条件二:存在未过期会员 ==========
# 高效查询:利用 yonghu_id + huiyuan_zhuangtai 复合索引
huiyuan_list = Huiyuangoumai.query.filter(
yonghu_id=user.UserUID,
huiyuan_zhuangtai=1 # 只查状态为"可用"的,减少内存遍历
).only('id', 'daoqi_time', 'huiyuan_zhuangtai')
for hy in huiyuan_list:
if not hy.jiance_shifou_daoqi(): # 未过期返回 False
return Response({
'code': 0,
'allow': 1,
'msg': '鉴权通过(会员未过期)'
})
# ========== 5. 条件三:存在订单状态为 2 或 8 的订单 ==========
# 利用 jiedan_dashou_id + zhuangtai 复合索引,只查 1 条
has_valid_order = Order.query.filter(
PlayerID=user.UserUID,
Status__in=[2, 8]
).only('id').exists() # exists() 高性能判断,只发 SELECT 1 LIMIT 1
if has_valid_order:
return Response({
'code': 0,
'allow': 1,
'msg': '鉴权通过(存在有效订单)'
})
# ========== 6. 以上均不满足 → 拒绝 ==========
return Response({
'code': 0,
'allow': 0,
'msg': '消息权限不足,请充值会员或联系管理员'
})
class LaobanJianquanView(APIView):
"""老板消息权限鉴权"""
permission_classes = [IsAuthenticated]
def post(self, request):
# ========== 1. 通过 JWT 获取当前登录用户 ==========
user = request.user # User 实例
# ========== 2. 高效查询平台订单扩展表 ==========
# BossID 为模型字段名db_column=laoban_id订单状态用 Order.Status
has_valid_order = PlatformOrderExt.query.filter(
BossID=user.UserUID
).filter(
Order__Status__in=[1, 2, 3, 4, 5, 6, 7, 8]
).only('id').exists()
if has_valid_order:
return Response({
'code': 0,
'allow': 1,
'msg': '鉴权通过'
})
return Response({
'code': 0,
'allow': 0,
'msg': '您暂无订单记录,消息功能暂不可用'
})