chore: 拉取远程前保留本地改动

含 kefu_base 修改及 club_payment_channel 相关文件。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-10 15:11:53 +08:00
parent 2a99e2ea10
commit a3cf7803d8
3 changed files with 664 additions and 352 deletions

View File

@@ -1,352 +1,352 @@
"""users.views.kefu_base - auto-generated by split script."""
"""users.views.kefu - 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,
)
from jituan.services.admin_context import is_kefu_backend_account
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 KefuLoginView(APIView):
"""
客服/管理员登录接口(支持手机号重复)
请求POST /yonghu/kefujinru
请求体:{"phone": "13800138000", "password": "xxx", "erjimima": "yyy"}
返回:
code=0 成功data包含token,nicheng,yonghuid
code=1 失败msg区分用户不存在/密码错误/二级密码错误)
code=4 账号被封禁
管理员IsSuperuser 或 UserType='admin')无需 erjimima 和 KefuProfile。
"""
throttle_classes = [AnonRateThrottle] # 限制匿名请求频率
permission_classes = [AllowAny] # 任何人都可访问
def post(self, request):
# 1. 获取参数
phone = request.data.get('phone', '').strip()
password = request.data.get('password', '')
erjimima = request.data.get('erjimima', '')
if not phone or not password:
return Response({
'code': 1,
'msg': '请填写手机号和密码',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 2. 获取客户端真实IP
client_ip = self.get_client_ip(request)
# 3. 查询该手机号下所有账号(客服 + 管理员)
# 先查客服(有 KefuProfile再查管理员IsSuperuser 或 UserType='admin'
user_mains = list(
User.query.filter(
Phone=phone,
KefuProfile__isnull=False,
).select_related('KefuProfile')
)
# 如果没有客服账号,尝试查找管理员账号
if not user_mains:
admin_candidates = User.query.filter(Phone=phone)
for admin_user in admin_candidates:
if admin_user.IsSuperuser or admin_user.UserType == 'admin':
user_mains = [admin_user]
break
if not user_mains:
logger.warning(f"登录失败,用户不存在: {phone}, IP: {client_ip}")
return Response({
'code': 1,
'msg': '用户不存在',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 4. 遍历所有匹配的账号,验证密码和二级密码
target_user = None
target_kefu = None
password_matched = False
for user in user_mains:
# 验证主密码bcrypt 哈希验证)
if not user.CheckPassword(password):
continue
password_matched = True
# 管理员:跳过二级密码和客服扩展表检查
is_admin = user.IsSuperuser or user.UserType == 'admin'
if is_admin:
target_user = user
target_kefu = None
break
# 客服:验证二级密码
kefu = user.KefuProfile
stored_erji = kefu.erjimima or ''
if stored_erji.startswith(('$2b$', '$2a$')) and len(stored_erji) == 60:
import bcrypt as _bcrypt
if not _bcrypt.checkpw(erjimima.encode('utf-8'), stored_erji.encode('utf-8')):
continue
elif stored_erji != erjimima:
continue
# 如果账号被禁用,记录但继续查找
if kefu.zhuangtai != 1:
if target_user is None:
target_user = user
target_kefu = kefu
continue
# 找到完全匹配且状态正常的账号,立即使用
target_user = user
target_kefu = kefu
break
else:
# 循环结束未找到正常账号
if target_user and target_kefu:
# 只有被禁用的账号匹配,返回封禁提示
logger.warning(f"登录失败,客服账号已禁用: {phone}, IP: {client_ip}")
return Response({
'code': 4,
'msg': '账号已被封禁',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
elif password_matched:
# 密码正确但二级密码错误
logger.warning(f"登录失败,二级密码错误: {phone}, IP: {client_ip}")
return Response({
'code': 1,
'msg': '二级密码错误',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
else:
# 密码错误
logger.warning(f"登录失败,密码错误: {phone}, IP: {client_ip}")
return Response({
'code': 1,
'msg': '密码错误',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 5. 更新IP和最后登录时间
target_user.IP = client_ip
target_user.UserLastLoginDate = timezone.now()
target_user.save(update_fields=['IP', 'UserLastLoginDate'])
# 6. 生成JWT token
refresh = RefreshToken.for_user(target_user)
token = str(refresh.access_token)
# 7. 返回成功数据(管理员无 KefuProfilenicheng 用 UserName 或 Phone 兜底)
nicheng = target_kefu.nicheng if target_kefu else (target_user.UserName or target_user.Phone)
return Response({
'code': 0,
'msg': '登录成功',
'data': {
'token': token,
'nicheng': nicheng,
'yonghuid': target_user.UserUID,
}
})
def get_client_ip(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
return request.META.get('REMOTE_ADDR', '0.0.0.0')
class KefuStatsView(APIView):
"""
客服统计数据接口
请求POST /yonghu/kfjrhqzl
请求体:{"phone": "13800138000"}
认证:必须携带有效的 JWT token放在 Authorization 头)
返回:
code=0 成功data 包含 jinrichuli, jinyuechuli, zongchuli
验证失败统一返回 401不透露具体原因
"""
permission_classes = [IsAuthenticated] # 必须登录
def post(self, request):
# 1. 获取请求中的 phone
phone = request.data.get('phone', '').strip()
if not phone:
# 缺少参数,但按约定返回 401可以改为 400但为了统一用401
logger.warning(f"统计接口缺少 phone 参数,用户: {request.user.UserUID}")
return Response({'detail': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
# 2. 获取当前登录用户(从 JWT 中解析)
current_user = request.user
# 3. 验证前端传来的 phone 是否与当前用户的 phone 一致
if current_user.Phone != phone:
# 不一致,可能 token 被冒用或参数错误
logger.warning(f"统计接口 phone 不匹配: 请求phone={phone}, 用户phone={current_user.Phone}")
return Response({'detail': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
# 4. 验证是否为后台客服账号
from jituan.services.admin_context import is_kefu_backend_account, is_system_super_admin
if not is_kefu_backend_account(current_user):
logger.warning(f"统计接口用户类型非客服: {current_user.UserUID}")
return Response({'detail': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
# 5. 尝试获取客服扩展表数据(管理员跳过)
is_admin = is_system_super_admin(current_user)
if is_admin:
return Response({
'code': 0,
'data': {
'jinrichuli': 0,
'jinyuechuli': 0,
'zongchuli': 0,
}
}, status=status.HTTP_200_OK)
try:
kefu_profile = current_user.KefuProfile # OneToOneField 反向关系
except UserKefu.DoesNotExist:
logger.warning(f"统计接口客服扩展表不存在: {current_user.UserUID}")
return Response({'detail': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
# 6. 返回统计数据(字段名与前端约定一致)
return Response({
'code': 0,
'data': {
'jinrichuli': kefu_profile.jinrichuli,
'jinyuechuli': kefu_profile.jinyuechuli,
'zongchuli': kefu_profile.zongchuli,
}
}, status=status.HTTP_200_OK)
class KefuGetOrderTypesView(APIView):
"""
客服获取订单类型列表(平台/商家/跨平台订单页共用)
请求POST /yonghu/kfhqptddlx
认证JWT + 与 menu-access 相同的 is_kefu_backend_account
返回code=0 + 类型列表
"""
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
if not is_kefu_backend_account(request.user):
return Response({'code': 403, 'msg': '非后台账号'}, status=403)
try:
types_qs = ShangpinLeixing.query.all().only('id', 'jieshao', 'tupian_url')
type_list = [
{
'id': t.id,
'biaoti': t.jieshao or '',
'tupian_url': t.tupian_url or ''
}
for t in types_qs
]
return Response({'code': 0, 'data': type_list})
except Exception as e:
logger.error(f"查询商品类型表失败: {str(e)}")
return Response({'code': 0, 'data': []})
"""users.views.kefu_base - auto-generated by split script."""
"""users.views.kefu - 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,
)
from jituan.services.admin_context import is_kefu_backend_account
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 KefuLoginView(APIView):
"""
客服/管理员登录接口(支持手机号重复)
请求POST /yonghu/kefujinru
请求体:{"phone": "13800138000", "password": "xxx", "erjimima": "yyy"}
返回:
code=0 成功data包含token,nicheng,yonghuid
code=1 失败msg区分用户不存在/密码错误/二级密码错误)
code=4 账号被封禁
管理员IsSuperuser 或 UserType='admin')无需 erjimima 和 KefuProfile。
"""
throttle_classes = [AnonRateThrottle] # 限制匿名请求频率
permission_classes = [AllowAny] # 任何人都可访问
def post(self, request):
# 1. 获取参数
phone = request.data.get('phone', '').strip()
password = request.data.get('password', '')
erjimima = request.data.get('erjimima', '')
if not phone or not password:
return Response({
'code': 1,
'msg': '请填写手机号和密码',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 2. 获取客户端真实IP
client_ip = self.get_client_ip(request)
# 3. 查询该手机号下所有账号(客服 + 管理员)
# 先查客服(有 KefuProfile再查管理员IsSuperuser 或 UserType='admin'
user_mains = list(
User.query.filter(
Phone=phone,
KefuProfile__isnull=False,
).select_related('KefuProfile')
)
# 如果没有客服账号,尝试查找管理员账号
if not user_mains:
admin_candidates = User.query.filter(Phone=phone)
for admin_user in admin_candidates:
if admin_user.IsSuperuser or admin_user.UserType == 'admin':
user_mains = [admin_user]
break
if not user_mains:
logger.warning(f"登录失败,用户不存在: {phone}, IP: {client_ip}")
return Response({
'code': 1,
'msg': '用户不存在',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 4. 遍历所有匹配的账号,验证密码和二级密码
target_user = None
target_kefu = None
password_matched = False
for user in user_mains:
# 验证主密码bcrypt 哈希验证)
if not user.CheckPassword(password):
continue
password_matched = True
# 管理员:跳过二级密码和客服扩展表检查
is_admin = user.IsSuperuser or user.UserType == 'admin'
if is_admin:
target_user = user
target_kefu = None
break
# 客服:验证二级密码
kefu = user.KefuProfile
stored_erji = kefu.erjimima or ''
if stored_erji.startswith(('$2b$', '$2a$')) and len(stored_erji) == 60:
import bcrypt as _bcrypt
if not _bcrypt.checkpw(erjimima.encode('utf-8'), stored_erji.encode('utf-8')):
continue
elif stored_erji != erjimima:
continue
# 如果账号被禁用,记录但继续查找
if kefu.zhuangtai != 1:
if target_user is None:
target_user = user
target_kefu = kefu
continue
# 找到完全匹配且状态正常的账号,立即使用
target_user = user
target_kefu = kefu
break
else:
# 循环结束未找到正常账号
if target_user and target_kefu:
# 只有被禁用的账号匹配,返回封禁提示
logger.warning(f"登录失败,客服账号已禁用: {phone}, IP: {client_ip}")
return Response({
'code': 4,
'msg': '账号已被封禁',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
elif password_matched:
# 密码正确但二级密码错误
logger.warning(f"登录失败,二级密码错误: {phone}, IP: {client_ip}")
return Response({
'code': 1,
'msg': '二级密码错误',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
else:
# 密码错误
logger.warning(f"登录失败,密码错误: {phone}, IP: {client_ip}")
return Response({
'code': 1,
'msg': '密码错误',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 5. 更新IP和最后登录时间
target_user.IP = client_ip
target_user.UserLastLoginDate = timezone.now()
target_user.save(update_fields=['IP', 'UserLastLoginDate'])
# 6. 生成JWT token
refresh = RefreshToken.for_user(target_user)
token = str(refresh.access_token)
# 7. 返回成功数据(管理员无 KefuProfilenicheng 用 UserName 或 Phone 兜底)
nicheng = target_kefu.nicheng if target_kefu else (target_user.UserName or target_user.Phone)
return Response({
'code': 0,
'msg': '登录成功',
'data': {
'token': token,
'nicheng': nicheng,
'yonghuid': target_user.UserUID,
}
})
def get_client_ip(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
return request.META.get('REMOTE_ADDR', '0.0.0.0')
class KefuStatsView(APIView):
"""
客服统计数据接口
请求POST /yonghu/kfjrhqzl
请求体:{"phone": "13800138000"}
认证:必须携带有效的 JWT token放在 Authorization 头)
返回:
code=0 成功data 包含 jinrichuli, jinyuechuli, zongchuli
验证失败统一返回 401不透露具体原因
"""
permission_classes = [IsAuthenticated] # 必须登录
def post(self, request):
# 1. 获取请求中的 phone
phone = request.data.get('phone', '').strip()
if not phone:
# 缺少参数,但按约定返回 401可以改为 400但为了统一用401
logger.warning(f"统计接口缺少 phone 参数,用户: {request.user.UserUID}")
return Response({'detail': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
# 2. 获取当前登录用户(从 JWT 中解析)
current_user = request.user
# 3. 验证前端传来的 phone 是否与当前用户的 phone 一致
if current_user.Phone != phone:
# 不一致,可能 token 被冒用或参数错误
logger.warning(f"统计接口 phone 不匹配: 请求phone={phone}, 用户phone={current_user.Phone}")
return Response({'detail': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
# 4. 验证是否为后台客服账号
from jituan.services.admin_context import is_kefu_backend_account, is_system_super_admin
if not is_kefu_backend_account(current_user):
logger.warning(f"统计接口用户类型非客服: {current_user.UserUID}")
return Response({'detail': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
# 5. 尝试获取客服扩展表数据(管理员跳过)
is_admin = is_system_super_admin(current_user)
if is_admin:
return Response({
'code': 0,
'data': {
'jinrichuli': 0,
'jinyuechuli': 0,
'zongchuli': 0,
}
}, status=status.HTTP_200_OK)
try:
kefu_profile = current_user.KefuProfile # OneToOneField 反向关系
except UserKefu.DoesNotExist:
logger.warning(f"统计接口客服扩展表不存在: {current_user.UserUID}")
return Response({'detail': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
# 6. 返回统计数据(字段名与前端约定一致)
return Response({
'code': 0,
'data': {
'jinrichuli': kefu_profile.jinrichuli,
'jinyuechuli': kefu_profile.jinyuechuli,
'zongchuli': kefu_profile.zongchuli,
}
}, status=status.HTTP_200_OK)
class KefuGetOrderTypesView(APIView):
"""
客服获取订单类型列表(平台/商家/跨平台订单页共用)
请求POST /yonghu/kfhqptddlx
认证JWT + 与 menu-access 相同的 is_kefu_backend_account
返回code=0 + 类型列表
"""
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
if not is_kefu_backend_account(request.user):
return Response({'code': 403, 'msg': '非后台账号'}, status=403)
try:
types_qs = ShangpinLeixing.query.all().only('id', 'jieshao', 'tupian_url')
type_list = [
{
'id': t.id,
'biaoti': t.jieshao or '',
'tupian_url': t.tupian_url or ''
}
for t in types_qs
]
return Response({'code': 0, 'data': type_list})
except Exception as e:
logger.error(f"查询商品类型表失败: {str(e)}")
return Response({'code': 0, 'data': []})