尝试将 backend 做了视图拆分,来保证可维护性,若此版本存在生产环境问题应该回退
This commit is contained in:
939
backend/views/shops.py
Normal file
939
backend/views/shops.py
Normal file
@@ -0,0 +1,939 @@
|
||||
import hmac
|
||||
import threading
|
||||
import traceback
|
||||
import uuid
|
||||
import hashlib
|
||||
import xmltodict
|
||||
import time
|
||||
import logging
|
||||
import requests
|
||||
import os
|
||||
import secrets
|
||||
import random
|
||||
import string
|
||||
from calendar import monthrange
|
||||
from decimal import Decimal
|
||||
from datetime import date, datetime, timedelta
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.paginator import Paginator
|
||||
from django.db import models, transaction, IntegrityError
|
||||
from django.db.models import Q, Count, Sum, F, Exists, OuterRef
|
||||
from gvsdsdk.fluent import db, func, FQ
|
||||
from django.db.models.functions import TruncDate, TruncMonth, TruncYear
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.utils.decorators import method_decorator
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
|
||||
|
||||
# 工具类导入
|
||||
from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss
|
||||
from backend.utils import (
|
||||
update_dashou_daily_by_action,
|
||||
update_shangjia_daily
|
||||
)
|
||||
from jituan.services.club_context import filter_club_char_field, filter_queryset_by_club, filter_user_related_by_club, filter_user_qs_by_club, orders_for_request
|
||||
from jituan.services.catalog_scope import block_non_group_catalog_scope
|
||||
from jituan.services.club_penalty import (
|
||||
filter_penalty_qs, resolve_penalty_club_id, filter_gsfenhong_qs,
|
||||
forbid_penalty_out_of_scope, resolve_penalty_record_club_id,
|
||||
)
|
||||
from jituan.services.club_user_access import forbid_if_user_out_of_scope, list_response_meta
|
||||
from shop.utils import update_dianpu_daily_stat
|
||||
from products.utils import update_shangpin_daily_stat
|
||||
from orders.notice_tasks import dingdan_guangbo
|
||||
from ..utils import (
|
||||
verify_kefu_permission,
|
||||
PERMISSION_TO_SHENFEN,
|
||||
SHENFEN_PROFILE_MAP,
|
||||
has_fadan_view_permission,
|
||||
has_merchant_order_permission,
|
||||
check_fadan_permission,
|
||||
pick_order_id,
|
||||
pick_penalty_create_params,
|
||||
write_xiugai_log,
|
||||
XIUGAI_LEIXING_DASHOU,
|
||||
XIUGAI_LEIXING_GUANSHI,
|
||||
XIUGAI_LEIXING_SHANGJIA,
|
||||
XIUGAI_LEIXING_ZUZHANG,
|
||||
XIUGAI_LEIXING_LABEL,
|
||||
)
|
||||
|
||||
# models 集中导入
|
||||
## gvsdsdk RBAC 模型(使用 PascalCase 字段名,匹配实际数据库表结构)
|
||||
from gvsdsdk.models import Role, Permission, RolePermission, UserRole
|
||||
## backend
|
||||
from backend.models import WithdrawalDailyStats
|
||||
|
||||
## users
|
||||
from users.models import (
|
||||
UserGuanshi, UserBoss, UserZuzhang,
|
||||
UserKefu, UserDashou, Xiugaijilu, UserShangjia, UserShenheguan
|
||||
)
|
||||
from users.business_models import User
|
||||
|
||||
## orders
|
||||
from orders.models import (
|
||||
CommissionRate, PlayerDeliveryImage, PenaltyRecord,
|
||||
OrderPlayerHistory, Order, RefundRecord,
|
||||
MerchantOrderExt, Penalty, PenaltyAppealImage, PenaltyBonus
|
||||
)
|
||||
|
||||
## shop
|
||||
from shop.models import Dianpu, DianpuMorenPeizhi, YonghuPingzheng, ShangpinLeixingDianpu, DianpuShangpinShenheShezhi
|
||||
|
||||
## products
|
||||
from products.models import (
|
||||
Shangpin, ShangpinLeixing, ShangpinZhuanqu, Huiyuan,
|
||||
DuociFenhong, Czjilu, Huiyuangoumai, Gsfenhong
|
||||
)
|
||||
|
||||
## config
|
||||
from config.models import (
|
||||
WithdrawConfig, ShangjiaLianjie, AccountPermission,
|
||||
TixianQuotaDefault,
|
||||
DailyIncomeStat, DailyPayoutStat, PopupPage, PopupConfig, PopupImage
|
||||
)
|
||||
|
||||
## rank
|
||||
from rank.models import (
|
||||
KaoheguanBankuai, Bankuai, Chenghao, KaoheCishuFeiyong,
|
||||
YonghuChenghao, ShenheJilu, KaoheJiluFeiyong, KaoheJujueJilu
|
||||
)
|
||||
|
||||
# 序列化器
|
||||
from ..serializers import PopupPageSerializer
|
||||
|
||||
# 全局常量、日志对象
|
||||
logger = logging.getLogger('houtai')
|
||||
SHOP_PRODUCT_PERMS = ['1199ab', '1199abc', '1199abd']
|
||||
|
||||
class ShopListView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
# 1. 调用公共权限验证(与您给的示例完全一致)
|
||||
username_frontend = request.data.get('username')
|
||||
kefu, permissions = verify_kefu_permission(request, username_frontend)
|
||||
if kefu is None:
|
||||
return Response({'code': 403, 'msg': '身份验证失败,请检查登录状态'})
|
||||
|
||||
# 2. 检查是否拥有所需权限(999a~999e 任意一个)
|
||||
required_perms = {'999a', '999b', '999c', '999d', '999e'}
|
||||
if not required_perms.intersection(set(permissions)):
|
||||
return Response({'code': 403, 'msg': '您没有权限访问店铺管理功能'})
|
||||
|
||||
# 3. 解析请求参数
|
||||
page = int(request.data.get('page', 1))
|
||||
page_size = int(request.data.get('page_size', 10))
|
||||
dianpu_mingcheng = request.data.get('dianpu_mingcheng', '').strip()
|
||||
zhuangtai = request.data.get('zhuangtai')
|
||||
lianxi_dianhua = request.data.get('lianxi_dianhua', '').strip()
|
||||
user_id = request.data.get('user_id', '').strip()
|
||||
ketixian_yue_min = request.data.get('ketixian_yue_min')
|
||||
ketixian_yue_max = request.data.get('ketixian_yue_max')
|
||||
zhengqu_zonge_min = request.data.get('zhengqu_zonge_min')
|
||||
zhengqu_zonge_max = request.data.get('zhengqu_zonge_max')
|
||||
|
||||
# 4. 构建查询集(先查店铺,预加载关联凭证)
|
||||
queryset = Dianpu.query.select_related('pingzheng').all()
|
||||
|
||||
if dianpu_mingcheng:
|
||||
queryset = queryset.filter(dianpu_mingcheng__icontains=dianpu_mingcheng)
|
||||
if zhuangtai is not None:
|
||||
queryset = queryset.filter(zhuangtai=zhuangtai)
|
||||
if lianxi_dianhua:
|
||||
queryset = queryset.filter(lianxi_dianhua__icontains=lianxi_dianhua)
|
||||
if user_id:
|
||||
queryset = queryset.filter(pingzheng__yonghu=user_id)
|
||||
|
||||
# 金额范围过滤
|
||||
if ketixian_yue_min is not None:
|
||||
queryset = queryset.filter(ketixian_yue__gte=ketixian_yue_min)
|
||||
if ketixian_yue_max is not None:
|
||||
queryset = queryset.filter(ketixian_yue__lte=ketixian_yue_max)
|
||||
if zhengqu_zonge_min is not None:
|
||||
queryset = queryset.filter(zhengqu_zonge__gte=zhengqu_zonge_min)
|
||||
if zhengqu_zonge_max is not None:
|
||||
queryset = queryset.filter(zhengqu_zonge__lte=zhengqu_zonge_max)
|
||||
|
||||
# 5. 分页
|
||||
paginator = Paginator(queryset, page_size)
|
||||
page_obj = paginator.get_page(page)
|
||||
|
||||
# 6. 构造返回数据
|
||||
shop_list = []
|
||||
for dianpu in page_obj:
|
||||
pingzheng = dianpu.pingzheng
|
||||
shop_list.append({
|
||||
'id': dianpu.id,
|
||||
'dianpu_mingcheng': dianpu.dianpu_mingcheng,
|
||||
'dianpu_touxiang': dianpu.dianpu_touxiang or '',
|
||||
'lianxi_dianhua': dianpu.lianxi_dianhua or '',
|
||||
'weixinhao': dianpu.weixinhao or '',
|
||||
'zhuangtai': dianpu.zhuangtai,
|
||||
'bangding_yonghushu': dianpu.bangding_yonghushu,
|
||||
'erweima_url': dianpu.erweima_url or '',
|
||||
'kaiqi_shouyi_choucheng': dianpu.kaiqi_shouyi_choucheng,
|
||||
'shouyi_choucheng_feilv': dianpu.shouyi_choucheng_feilv or 0,
|
||||
'ketixian_yue': dianpu.ketixian_yue,
|
||||
'zhengqu_zonge': dianpu.zhengqu_zonge,
|
||||
'meiri_tixian_xiane': dianpu.meiri_tixian_xiane or 0,
|
||||
'kaiqi_meiri_xiane': dianpu.kaiqi_meiri_xiane,
|
||||
'pingzheng__yonghu': pingzheng.yonghu, # 用户ID
|
||||
'pingzheng__zhanghao': pingzheng.zhanghao, # 登录账号
|
||||
})
|
||||
|
||||
# 获取默认配置(ID=1,不存在则创建)
|
||||
default_config, _ = DianpuMorenPeizhi.query.get_or_create(id=1, defaults={
|
||||
'moren_choucheng_feilv': 0.1,
|
||||
'moren_tixian_xiane': 1000.00
|
||||
})
|
||||
|
||||
return Response({
|
||||
'code': 0,
|
||||
'msg': 'success',
|
||||
'data': {
|
||||
'list': shop_list,
|
||||
'total': paginator.count,
|
||||
'default_config': {
|
||||
'moren_choucheng_feilv': default_config.moren_choucheng_feilv,
|
||||
'moren_tixian_xiane': default_config.moren_tixian_xiane,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
class ShopModifyView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
# 1. 身份验证
|
||||
username_frontend = request.data.get('username')
|
||||
kefu, permissions = verify_kefu_permission(request, username_frontend)
|
||||
if kefu is None:
|
||||
return Response({'code': 403, 'msg': '身份验证失败,请检查登录状态'})
|
||||
|
||||
# 转换权限为集合方便判断
|
||||
user_perms = set(permissions)
|
||||
action = request.data.get('action', '')
|
||||
|
||||
# 2. 根据 action 分派任务
|
||||
if action == 'update_dianpu':
|
||||
return self._update_dianpu(request, user_perms)
|
||||
elif action == 'add_dianpu':
|
||||
return self._add_dianpu(request, user_perms)
|
||||
elif action == 'update_default':
|
||||
return self._update_default(request, user_perms)
|
||||
else:
|
||||
return Response({'code': 400, 'msg': '未知操作类型'})
|
||||
|
||||
def _update_dianpu(self, request, user_perms):
|
||||
"""修改店铺信息,精确权限控制"""
|
||||
dianpu_id = request.data.get('dianpu_id')
|
||||
if not dianpu_id:
|
||||
return Response({'code': 400, 'msg': '缺少店铺ID'})
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
dianpu = Dianpu.objects.select_for_update().get(id=dianpu_id)
|
||||
|
||||
# 基础信息修改 (999e)
|
||||
if any(k in request.data for k in ['dianpu_mingcheng', 'lianxi_dianhua', 'weixinhao']):
|
||||
if '999e' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限修改店铺基本信息(999e)'})
|
||||
if 'dianpu_mingcheng' in request.data:
|
||||
dianpu.dianpu_mingcheng = request.data['dianpu_mingcheng']
|
||||
if 'lianxi_dianhua' in request.data:
|
||||
dianpu.lianxi_dianhua = request.data['lianxi_dianhua']
|
||||
if 'weixinhao' in request.data:
|
||||
dianpu.weixinhao = request.data['weixinhao']
|
||||
|
||||
# 店铺状态 (999a)
|
||||
if 'zhuangtai' in request.data:
|
||||
if '999a' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限修改店铺状态(999a)'})
|
||||
new_status = int(request.data['zhuangtai'])
|
||||
if new_status not in (0, 1):
|
||||
return Response({'code': 400, 'msg': '店铺状态无效'})
|
||||
dianpu.zhuangtai = new_status
|
||||
|
||||
# 可提现余额 (999b)
|
||||
if 'ketixian_yue' in request.data:
|
||||
if '999b' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限修改可提现余额(999b)'})
|
||||
dianpu.ketixian_yue = request.data['ketixian_yue']
|
||||
|
||||
# 提现限额相关 (999c)
|
||||
if 'kaiqi_meiri_xiane' in request.data or 'meiri_tixian_xiane' in request.data:
|
||||
if '999c' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限修改提现限额配置(999c)'})
|
||||
if 'kaiqi_meiri_xiane' in request.data:
|
||||
dianpu.kaiqi_meiri_xiane = request.data['kaiqi_meiri_xiane']
|
||||
if 'meiri_tixian_xiane' in request.data:
|
||||
dianpu.meiri_tixian_xiane = request.data['meiri_tixian_xiane']
|
||||
|
||||
# 抽成相关 (999d)
|
||||
if 'kaiqi_shouyi_choucheng' in request.data or 'shouyi_choucheng_feilv' in request.data:
|
||||
if '999d' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限修改抽成配置(999d)'})
|
||||
if 'kaiqi_shouyi_choucheng' in request.data:
|
||||
dianpu.kaiqi_shouyi_choucheng = request.data['kaiqi_shouyi_choucheng']
|
||||
if 'shouyi_choucheng_feilv' in request.data:
|
||||
dianpu.shouyi_choucheng_feilv = request.data['shouyi_choucheng_feilv']
|
||||
|
||||
dianpu.save() # Django 会自动判断更新哪些字段
|
||||
return Response({'code': 0, 'msg': '店铺信息更新成功'})
|
||||
|
||||
except Dianpu.DoesNotExist:
|
||||
return Response({'code': 404, 'msg': '店铺不存在'})
|
||||
except Exception as e:
|
||||
logger.exception("修改店铺失败")
|
||||
return Response({'code': 500, 'msg': '修改失败,请稍后重试'})
|
||||
|
||||
def _add_dianpu(self, request, user_perms):
|
||||
"""添加店铺,需要权限 999b"""
|
||||
if '999b' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限添加店铺(999b)'})
|
||||
|
||||
yonghu_id = request.data.get('yonghu_id')
|
||||
zhanghao = request.data.get('zhanghao')
|
||||
mima = request.data.get('mima')
|
||||
dianpu_mingcheng = request.data.get('dianpu_mingcheng')
|
||||
|
||||
if not all([yonghu_id, zhanghao, mima, dianpu_mingcheng]):
|
||||
return Response({'code': 400, 'msg': '必填字段缺失(用户ID、账号、密码、店铺名称)'})
|
||||
|
||||
# 检查用户是否存在
|
||||
if not User.query.filter(UserUID=yonghu_id).exists():
|
||||
return Response({'code': 404, 'msg': '用户不存在'})
|
||||
|
||||
# 检查凭证是否已存在
|
||||
if YonghuPingzheng.query.filter(zhanghao=zhanghao).exists():
|
||||
return Response({'code': 400, 'msg': '登录账号已存在'})
|
||||
if YonghuPingzheng.query.filter(yonghu=yonghu_id).exists():
|
||||
return Response({'code': 400, 'msg': '该用户ID已有店铺凭证'})
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
pingzheng = YonghuPingzheng.query.create(
|
||||
yonghu=yonghu_id,
|
||||
zhanghao=zhanghao,
|
||||
mima=mima,
|
||||
is_active=True
|
||||
)
|
||||
dianpu = Dianpu.query.create(
|
||||
pingzheng=pingzheng,
|
||||
dianpu_mingcheng=dianpu_mingcheng,
|
||||
lianxi_dianhua=request.data.get('lianxi_dianhua', ''),
|
||||
weixinhao=request.data.get('weixinhao', ''),
|
||||
kaiqi_shouyi_choucheng=request.data.get('kaiqi_shouyi_choucheng', False),
|
||||
shouyi_choucheng_feilv=request.data.get('shouyi_choucheng_feilv', 0),
|
||||
kaiqi_meiri_xiane=request.data.get('kaiqi_meiri_xiane', False),
|
||||
meiri_tixian_xiane=request.data.get('meiri_tixian_xiane', 0),
|
||||
)
|
||||
return Response({'code': 0, 'msg': '店铺创建成功', 'dianpu_id': dianpu.id})
|
||||
except IntegrityError as e:
|
||||
logger.error("创建店铺违反唯一性约束: %s", e)
|
||||
return Response({'code': 400, 'msg': '数据冲突,请检查账号或用户ID'})
|
||||
except Exception as e:
|
||||
logger.exception("创建店铺失败")
|
||||
return Response({'code': 500, 'msg': '创建失败,请稍后重试'})
|
||||
|
||||
def _update_default(self, request, user_perms):
|
||||
"""修改默认配置,按修改内容校验权限"""
|
||||
feilv = request.data.get('moren_choucheng_feilv')
|
||||
xiane = request.data.get('moren_tixian_xiane')
|
||||
modify_commission = feilv is not None
|
||||
modify_withdraw = xiane is not None
|
||||
|
||||
if modify_commission and '999d' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限修改默认抽成费率(999d)'})
|
||||
if modify_withdraw and '999c' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限修改默认可提现限额(999c)'})
|
||||
|
||||
try:
|
||||
config, _ = DianpuMorenPeizhi.query.get_or_create(id=1)
|
||||
if modify_commission:
|
||||
config.moren_choucheng_feilv = feilv
|
||||
if modify_withdraw:
|
||||
config.moren_tixian_xiane = xiane
|
||||
config.save(update_fields=(['moren_choucheng_feilv'] if modify_commission else []) +
|
||||
(['moren_tixian_xiane'] if modify_withdraw else []))
|
||||
return Response({'code': 0, 'msg': '默认配置更新成功'})
|
||||
except Exception as e:
|
||||
logger.exception("更新默认配置失败")
|
||||
return Response({'code': 500, 'msg': '更新失败'})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ==================== 1. 公共商品类型 + 审核模式 ====================
|
||||
class ShopPublicTypeAndAuditView(APIView):
|
||||
"""
|
||||
返回:①正常状态的公共商品类型列表 ②全局商品审核模式开关
|
||||
前端请求路径:/houtai/htdphqsplx
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
# ---------- 身份与权限校验 ----------
|
||||
username = request.data.get('username')
|
||||
kefu, permissions = verify_kefu_permission(request, username)
|
||||
if kefu is None:
|
||||
return Response({'code': 403, 'msg': '身份验证失败,请检查登录状态'})
|
||||
|
||||
# 拥有 1199ab/1199abc/1199abd 任一个即可访问
|
||||
if not set(permissions).intersection(SHOP_PRODUCT_PERMS):
|
||||
return Response({'code': 403, 'msg': '无权访问店铺商品管理功能'})
|
||||
|
||||
# ---------- 查询公共商品类型(仅正常审核状态的) ----------
|
||||
try:
|
||||
# shenhezhuangtai = 1 表示正常 (根据模型默认值)
|
||||
types_qs = ShangpinLeixing.query.filter(shenhezhuangtai=1).values(
|
||||
'id', 'jieshao', 'tupian_url', 'yaoqiuleixing', 'huiyuan_id', 'yongjin'
|
||||
)
|
||||
type_list = []
|
||||
for t in types_qs:
|
||||
type_list.append({
|
||||
'id': t['id'],
|
||||
'jieshao': t['jieshao'] or '',
|
||||
'tupian_url': t['tupian_url'] or '',
|
||||
'yaoqiuleixing': t['yaoqiuleixing'],
|
||||
'huiyuan_id': t['huiyuan_id'],
|
||||
'yongjin': t['yongjin'],
|
||||
})
|
||||
except Exception as e:
|
||||
logger.exception("查询公共商品类型失败")
|
||||
return Response({'code': 500, 'msg': '服务器错误,请稍后重试'})
|
||||
|
||||
# ---------- 获取审核模式配置 ----------
|
||||
audit_config = DianpuShangpinShenheShezhi.query.filter(id=1).first()
|
||||
kaiqi_shenhe = audit_config.kaiqi_shenhe if audit_config else False
|
||||
|
||||
return Response({
|
||||
'code': 0,
|
||||
'data': {
|
||||
'public_types': type_list,
|
||||
'kaiqi_shenhe': kaiqi_shenhe
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
# ==================== 2. 店铺列表(支持搜索、筛选、分页) ====================
|
||||
class ShopListForProductView(APIView):
|
||||
"""
|
||||
返回店铺列表,用于商品管理页面选择店铺。
|
||||
支持:关键词搜索(店铺ID/名称/用户ID)、店铺状态筛选、分页。
|
||||
前端请求路径:/houtai/htdphqdpys
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
username = request.data.get('username')
|
||||
kefu, permissions = verify_kefu_permission(request, username)
|
||||
if kefu is None:
|
||||
return Response({'code': 403, 'msg': '身份验证失败'})
|
||||
if not set(permissions).intersection(SHOP_PRODUCT_PERMS):
|
||||
return Response({'code': 403, 'msg': '无权访问店铺列表'})
|
||||
|
||||
# ---------- 安全解析分页参数 ----------
|
||||
try:
|
||||
page = int(request.data.get('page', 1))
|
||||
page_size = int(request.data.get('page_size', 10))
|
||||
except (TypeError, ValueError):
|
||||
return Response({'code': 400, 'msg': '分页参数格式错误'})
|
||||
|
||||
# 搜索关键词(字符串,ORM 自动防注入)
|
||||
keyword = str(request.data.get('keyword', '')).strip()
|
||||
# 店铺状态筛选(1正常,0封禁,不传则全部)
|
||||
zhuangtai = request.data.get('zhuangtai')
|
||||
|
||||
# ---------- 构建查询集(关联凭证表获取用户ID) ----------
|
||||
shops = Dianpu.query.select_related('pingzheng').all()
|
||||
|
||||
# 状态筛选:只接受 0 或 1
|
||||
if zhuangtai is not None:
|
||||
try:
|
||||
zhuangtai = int(zhuangtai)
|
||||
if zhuangtai in [0, 1]:
|
||||
shops = shops.filter(zhuangtai=zhuangtai)
|
||||
else:
|
||||
return Response({'code': 400, 'msg': '状态参数只能为0或1'})
|
||||
except (TypeError, ValueError):
|
||||
return Response({'code': 400, 'msg': '状态参数必须为整数'})
|
||||
|
||||
# 关键词搜索(店铺ID/名称/用户ID)
|
||||
if keyword:
|
||||
shops = shops.filter(
|
||||
models.Q(id__icontains=keyword) |
|
||||
models.Q(dianpu_mingcheng__icontains=keyword) |
|
||||
models.Q(pingzheng__yonghu__icontains=keyword)
|
||||
)
|
||||
|
||||
# ---------- 分页 ----------
|
||||
paginator = Paginator(shops, page_size)
|
||||
page_obj = paginator.get_page(page)
|
||||
|
||||
shop_list = []
|
||||
for shop in page_obj:
|
||||
shop_list.append({
|
||||
'id': shop.id,
|
||||
'dianpu_mingcheng': shop.dianpu_mingcheng,
|
||||
'zhuangtai': shop.zhuangtai,
|
||||
'yonghu_id': shop.pingzheng.yonghu if shop.pingzheng else '',
|
||||
'lianxi_dianhua': shop.lianxi_dianhua or '',
|
||||
'weixinhao': shop.weixinhao or '',
|
||||
})
|
||||
|
||||
return Response({
|
||||
'code': 0,
|
||||
'data': {
|
||||
'list': shop_list,
|
||||
'total': paginator.count
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
# ==================== 3. 店铺商品类型映射列表(含筛选、待审核计数) ====================
|
||||
class ShopProductTypeMappingView(APIView):
|
||||
"""
|
||||
返回店铺商品类型映射(ShangpinLeixingDianpu)列表。
|
||||
支持:店铺ID、公共类型ID、上架/封禁/审核状态筛选,关键字搜索(介绍/ID/店铺名/公共类型名)。
|
||||
每次请求同时返回“待审核总数”(shenhe_zhuangtai=False 的记录数)。
|
||||
前端请求路径:/houtai/htdphqyssplx
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
username = request.data.get('username')
|
||||
kefu, permissions = verify_kefu_permission(request, username)
|
||||
if kefu is None:
|
||||
return Response({'code': 403, 'msg': '身份验证失败'})
|
||||
if not set(permissions).intersection(SHOP_PRODUCT_PERMS):
|
||||
return Response({'code': 403, 'msg': '无权访问商品类型映射'})
|
||||
|
||||
# ---------- 安全解析参数 ----------
|
||||
try:
|
||||
page = int(request.data.get('page', 1))
|
||||
page_size = int(request.data.get('page_size', 10))
|
||||
except (TypeError, ValueError):
|
||||
return Response({'code': 400, 'msg': '分页参数格式错误'})
|
||||
|
||||
keyword = str(request.data.get('keyword', '')).strip()
|
||||
shop_id = request.data.get('shop_id')
|
||||
public_type_id = request.data.get('public_type_id')
|
||||
|
||||
# 布尔型筛选字段处理:None 表示不筛选,否则转为布尔值
|
||||
def parse_bool(val):
|
||||
if val is None:
|
||||
return None
|
||||
return bool(val)
|
||||
|
||||
shangjia = parse_bool(request.data.get('shangjia_zhuangtai'))
|
||||
fengjin = parse_bool(request.data.get('fengjin_zhuangtai'))
|
||||
shenhe = parse_bool(request.data.get('shenhe_zhuangtai'))
|
||||
|
||||
# ---------- 查询(连表店铺和公共类型) ----------
|
||||
mappings = ShangpinLeixingDianpu.query.select_related('dianpu', 'gonggong_leixing').all()
|
||||
|
||||
# 筛选条件:均为参数化,完全防注入
|
||||
if shop_id is not None:
|
||||
mappings = mappings.filter(dianpu_id=int(shop_id))
|
||||
if public_type_id is not None:
|
||||
mappings = mappings.filter(gonggong_leixing_id=int(public_type_id))
|
||||
if shangjia is not None:
|
||||
mappings = mappings.filter(shangjia_zhuangtai=shangjia)
|
||||
if fengjin is not None:
|
||||
mappings = mappings.filter(fengjin_zhuangtai=fengjin)
|
||||
if shenhe is not None:
|
||||
mappings = mappings.filter(shenhe_zhuangtai=shenhe)
|
||||
|
||||
# 关键字搜索(介绍、ID、店铺名、公共类型名)
|
||||
if keyword:
|
||||
mappings = mappings.filter(
|
||||
models.Q(jieshao__icontains=keyword) |
|
||||
models.Q(id__icontains=keyword) |
|
||||
models.Q(dianpu__dianpu_mingcheng__icontains=keyword) |
|
||||
models.Q(gonggong_leixing__jieshao__icontains=keyword)
|
||||
)
|
||||
|
||||
# ---------- 待审核总数(不受分页影响) ----------
|
||||
pending_total = mappings.filter(shenhe_zhuangtai=False).count()
|
||||
|
||||
# ---------- 分页 ----------
|
||||
paginator = Paginator(mappings, page_size)
|
||||
page_obj = paginator.get_page(page)
|
||||
|
||||
mapping_list = []
|
||||
for m in page_obj:
|
||||
mapping_list.append({
|
||||
'id': m.id,
|
||||
'dianpu_id': m.dianpu_id,
|
||||
'dianpu_name': m.dianpu.dianpu_mingcheng if m.dianpu else '',
|
||||
'gonggong_leixing_id': m.gonggong_leixing_id,
|
||||
'gonggong_leixing_name': m.gonggong_leixing.jieshao if m.gonggong_leixing else '',
|
||||
'jieshao': m.jieshao,
|
||||
'tupian_url': m.tupian_url or '',
|
||||
'paixu': m.paixu,
|
||||
'shangjia_zhuangtai': m.shangjia_zhuangtai,
|
||||
'fengjin_zhuangtai': m.fengjin_zhuangtai,
|
||||
'shenhe_zhuangtai': m.shenhe_zhuangtai,
|
||||
})
|
||||
|
||||
return Response({
|
||||
'code': 0,
|
||||
'data': {
|
||||
'list': mapping_list,
|
||||
'total': paginator.count,
|
||||
'pending_total': pending_total
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
# ==================== 统一修改接口(已修正事务) ====================
|
||||
class ShopProductModifyView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
username = request.data.get('username')
|
||||
kefu, permissions = verify_kefu_permission(request, username)
|
||||
if kefu is None:
|
||||
return Response({'code': 403, 'msg': '身份验证失败'})
|
||||
|
||||
user_perms = set(permissions)
|
||||
action = request.data.get('action')
|
||||
|
||||
if action == 'update_audit_mode':
|
||||
return self._update_audit_mode(request, user_perms)
|
||||
elif action == 'update_mapping_status':
|
||||
return self._update_mapping_status(request, user_perms)
|
||||
else:
|
||||
return Response({'code': 400, 'msg': '未知操作'})
|
||||
|
||||
@transaction.atomic # ✅ 事务修复点
|
||||
def _update_audit_mode(self, request, user_perms):
|
||||
if '1199ab' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限修改审核模式'})
|
||||
|
||||
kaiqi = request.data.get('kaiqi_shenhe')
|
||||
if kaiqi is None:
|
||||
return Response({'code': 400, 'msg': '缺少参数 kaiqi_shenhe'})
|
||||
|
||||
try:
|
||||
config, _ = DianpuShangpinShenheShezhi.objects.select_for_update().get_or_create(id=1)
|
||||
config.kaiqi_shenhe = bool(kaiqi)
|
||||
config.save(update_fields=['kaiqi_shenhe'])
|
||||
return Response({'code': 0, 'msg': '审核模式已更新'})
|
||||
except Exception as e:
|
||||
logger.exception("更新审核模式失败")
|
||||
return Response({'code': 500, 'msg': '服务器错误'})
|
||||
|
||||
@transaction.atomic
|
||||
def _update_mapping_status(self, request, user_perms):
|
||||
mapping_id = request.data.get('mapping_id')
|
||||
if not mapping_id:
|
||||
return Response({'code': 400, 'msg': '缺少 mapping_id'})
|
||||
|
||||
try:
|
||||
mapping = ShangpinLeixingDianpu.objects.select_for_update().get(id=int(mapping_id))
|
||||
except (ShangpinLeixingDianpu.DoesNotExist, ValueError):
|
||||
return Response({'code': 404, 'msg': '映射不存在'})
|
||||
|
||||
fengjin = request.data.get('fengjin_zhuangtai')
|
||||
shangjia = request.data.get('shangjia_zhuangtai')
|
||||
shenhe = request.data.get('shenhe_zhuangtai')
|
||||
|
||||
# 权限判断
|
||||
if fengjin is not None:
|
||||
if fengjin:
|
||||
if '1199abc' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限封禁'})
|
||||
else:
|
||||
if '1199abd' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限解封'})
|
||||
mapping.fengjin_zhuangtai = bool(fengjin)
|
||||
|
||||
if shangjia is not None:
|
||||
if not shangjia:
|
||||
if '1199abc' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限下架'})
|
||||
else:
|
||||
if '1199abd' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限上架'})
|
||||
mapping.shangjia_zhuangtai = bool(shangjia)
|
||||
|
||||
if shenhe is not None:
|
||||
if shenhe:
|
||||
if '1199abd' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限审核通过'})
|
||||
mapping.shenhe_zhuangtai = bool(shenhe)
|
||||
|
||||
mapping.save()
|
||||
return Response({'code': 0, 'msg': '修改成功'})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 商品管理相关权限组(拥有任意一个即可查看列表)
|
||||
PRODUCT_PERMS = ['1199ab', '1199abc', '1199abd', '1199abe']
|
||||
|
||||
|
||||
class ProductListView(APIView):
|
||||
"""
|
||||
商品列表接口,支持多条件筛选与分页。
|
||||
请求路径: /houtai/hqhtdpsplx
|
||||
权限要求: 1199ab / 1199abc / 1199abd / 1199abe 任一即可
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
# ----- 身份与权限验证 -----
|
||||
username = request.data.get('username')
|
||||
kefu, permissions = verify_kefu_permission(request, username)
|
||||
if kefu is None:
|
||||
return Response({'code': 403, 'msg': '身份验证失败'})
|
||||
|
||||
if not set(permissions).intersection(PRODUCT_PERMS):
|
||||
return Response({'code': 403, 'msg': '无商品管理权限'})
|
||||
|
||||
# ----- 安全解析分页与筛选参数 -----
|
||||
try:
|
||||
page = int(request.data.get('page', 1))
|
||||
page_size = int(request.data.get('page_size', 15))
|
||||
except (TypeError, ValueError):
|
||||
return Response({'code': 400, 'msg': '分页参数格式错误'})
|
||||
|
||||
keyword = str(request.data.get('keyword', '')).strip()
|
||||
shop_id = request.data.get('shop_id') # 店铺ID (可选)
|
||||
shop_product_type_id = request.data.get('shop_product_type_id') # 店铺商品类型映射ID (可选)
|
||||
public_type_id = request.data.get('public_type_id') # 公共商品类型ID (可选)
|
||||
|
||||
# 布尔型状态筛选
|
||||
def parse_bool(val):
|
||||
if val is None:
|
||||
return None
|
||||
return bool(val)
|
||||
|
||||
shenhe = parse_bool(request.data.get('shenhe_zhuangtai'))
|
||||
shangjia = parse_bool(request.data.get('shangjia_zhuangtai'))
|
||||
fengjin = parse_bool(request.data.get('fengjin_zhuangtai'))
|
||||
|
||||
# ----- 构建查询(预加载店铺和店铺商品类型) -----
|
||||
products = Shangpin.query.select_related('dianpu', 'dianpu_leixing').all()
|
||||
|
||||
# 按店铺筛选
|
||||
if shop_id is not None:
|
||||
try:
|
||||
products = products.filter(dianpu_id=int(shop_id))
|
||||
except (ValueError, TypeError):
|
||||
return Response({'code': 400, 'msg': 'shop_id 格式错误'})
|
||||
|
||||
# 按店铺商品类型映射筛选
|
||||
if shop_product_type_id is not None:
|
||||
try:
|
||||
products = products.filter(dianpu_leixing_id=int(shop_product_type_id))
|
||||
except (ValueError, TypeError):
|
||||
return Response({'code': 400, 'msg': 'shop_product_type_id 格式错误'})
|
||||
|
||||
# 按公共商品类型筛选(leixing_id 存储的是公共类型的ID)
|
||||
if public_type_id is not None:
|
||||
try:
|
||||
products = products.filter(leixing_id=int(public_type_id))
|
||||
except (ValueError, TypeError):
|
||||
return Response({'code': 400, 'msg': 'public_type_id 格式错误'})
|
||||
|
||||
# 平台审核状态筛选 (BooleanField)
|
||||
if shenhe is not None:
|
||||
products = products.filter(shenhe_zhuangtai=shenhe)
|
||||
|
||||
# 上架状态筛选
|
||||
if shangjia is not None:
|
||||
products = products.filter(shangjia_zhuangtai=shangjia)
|
||||
|
||||
# 封禁状态筛选
|
||||
if fengjin is not None:
|
||||
products = products.filter(fengjin_zhuangtai=fengjin)
|
||||
|
||||
# 关键词搜索:商品ID或商品标题(搜索具有模糊性,使用icontains)
|
||||
if keyword:
|
||||
# 尝试纯数字可能是商品ID,使用精确匹配与标题模糊搜索
|
||||
products = products.filter(
|
||||
models.Q(id__icontains=keyword) |
|
||||
models.Q(biaoqian__icontains=keyword)
|
||||
)
|
||||
|
||||
# ----- 获取待审核总数(平台审核未通过的数量) -----
|
||||
pending_total = products.filter(shenhe_zhuangtai=False).count()
|
||||
|
||||
# ----- 分页 -----
|
||||
paginator = Paginator(products, page_size)
|
||||
page_obj = paginator.get_page(page)
|
||||
|
||||
# ----- 构造返回数据(批量获取公共类型名称,避免N+1) -----
|
||||
# 提取所有公共商品类型ID
|
||||
type_ids = {p.leixing_id for p in page_obj if p.leixing_id is not None}
|
||||
public_type_map = {}
|
||||
if type_ids:
|
||||
public_types = ShangpinLeixing.query.filter(id__in=type_ids).values('id', 'jieshao')
|
||||
public_type_map = {t['id']: t['jieshao'] for t in public_types}
|
||||
|
||||
product_list = []
|
||||
for p in page_obj:
|
||||
product_list.append({
|
||||
'id': p.id,
|
||||
'biaoqian': p.biaoqian or '',
|
||||
'jiage': p.jiage,
|
||||
'kucun': p.kucun,
|
||||
'leixing_id': p.leixing_id,
|
||||
'public_type_name': public_type_map.get(p.leixing_id, ''),
|
||||
'zhenshi_xiaoliang': p.zhenshi_xiaoliang,
|
||||
'duiwai_xiaoliang': p.duiwai_xiaoliang,
|
||||
'jieshao': p.jieshao or '',
|
||||
'xiadan_xuzhi': p.xiadan_xuzhi or '',
|
||||
'guize_tupian': p.guize_tupian or '',
|
||||
'yaoqiuleixing': p.yaoqiuleixing,
|
||||
'huiyuan_id': p.huiyuan_id or '',
|
||||
'yongjin': p.yongjin,
|
||||
'kaioi_ewai_PlayerCommission': p.kaioi_ewai_dashou_fencheng,
|
||||
'ewai_PlayerCommission': p.ewai_dashou_fencheng,
|
||||
'CreateTime': p.CreateTime.isoformat() if p.CreateTime else '',
|
||||
'UpdateTime': p.UpdateTime.isoformat() if p.UpdateTime else '',
|
||||
'paixu': p.paixu,
|
||||
'dianpu_id': p.dianpu_id,
|
||||
'dianpu_name': p.dianpu.dianpu_mingcheng if p.dianpu else '',
|
||||
'dianpu_leixing_id': p.dianpu_leixing_id,
|
||||
'dianpu_leixing_name': p.dianpu_leixing.jieshao if p.dianpu_leixing else '',
|
||||
'shangjia_zhuangtai': p.shangjia_zhuangtai,
|
||||
'fengjin_zhuangtai': p.fengjin_zhuangtai,
|
||||
'shenhe_zhuangtai': p.shenhe_zhuangtai,
|
||||
})
|
||||
|
||||
return Response({
|
||||
'code': 0,
|
||||
'data': {
|
||||
'list': product_list,
|
||||
'total': paginator.count,
|
||||
'pending_total': pending_total, # 待审核商品总数
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
class ProductModifyView(APIView):
|
||||
"""
|
||||
商品修改接口,根据修改内容细粒度鉴权。
|
||||
请求路径: /houtai/htxgdpspsj
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
username = request.data.get('username')
|
||||
kefu, permissions = verify_kefu_permission(request, username)
|
||||
if kefu is None:
|
||||
return Response({'code': 403, 'msg': '身份验证失败'})
|
||||
|
||||
user_perms = set(permissions)
|
||||
action = request.data.get('action', '')
|
||||
|
||||
if action != 'update_product':
|
||||
return Response({'code': 400, 'msg': '未知操作'})
|
||||
|
||||
return self._update_product(request, user_perms)
|
||||
|
||||
@transaction.atomic
|
||||
def _update_product(self, request, user_perms):
|
||||
"""执行商品修改,严格校验权限"""
|
||||
product_id = request.data.get('product_id')
|
||||
if not product_id:
|
||||
return Response({'code': 400, 'msg': '缺少商品ID'})
|
||||
|
||||
try:
|
||||
# 行级锁防止并发修改
|
||||
product = Shangpin.objects.select_for_update().get(id=int(product_id))
|
||||
except (Shangpin.DoesNotExist, ValueError):
|
||||
return Response({'code': 404, 'msg': '商品不存在'})
|
||||
|
||||
# 需要修改的字段
|
||||
fields_to_update = {}
|
||||
# 将要修改的状态标志用于后续权限判断
|
||||
status_changes = {
|
||||
'shangjia': request.data.get('shangjia_zhuangtai'),
|
||||
'fengjin': request.data.get('fengjin_zhuangtai'),
|
||||
'shenhe': request.data.get('shenhe_zhuangtai'),
|
||||
}
|
||||
|
||||
# ---------- 权限分类检查 ----------
|
||||
# 1. 封禁/下架操作 (消极操作) -> 1199abc
|
||||
if any(status_changes.get(k) is not None and status_changes[k] == False for k in ['shangjia', 'shenhe']):
|
||||
if '1199abc' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限执行下架/封禁/驳回审核操作(1199abc)'})
|
||||
if status_changes.get('fengjin') is not None and status_changes['fengjin'] == True:
|
||||
if '1199abc' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限执行封禁操作(1199abc)'})
|
||||
|
||||
# 2. 解封/上架/过审 (积极操作) -> 1199abd
|
||||
if any(status_changes.get(k) is not None and status_changes[k] == True for k in ['shangjia', 'shenhe']):
|
||||
if '1199abd' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限执行上架/解封/审核通过操作(1199abd)'})
|
||||
if status_changes.get('fengjin') is not None and status_changes['fengjin'] == False:
|
||||
if '1199abd' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限执行解封操作(1199abd)'})
|
||||
|
||||
# 3. 修改其他数据(非状态字段)-> 1199abe
|
||||
# 除了上架、封禁、审核之外的字段都属于“其他数据”
|
||||
other_fields = [k for k in request.data.keys() if k not in ['action', 'product_id', 'username',
|
||||
'shangjia_zhuangtai', 'fengjin_zhuangtai',
|
||||
'shenhe_zhuangtai']]
|
||||
if other_fields and '1199abe' not in user_perms:
|
||||
return Response({'code': 403, 'msg': '无权限修改商品基本信息(1199abe)'})
|
||||
|
||||
# ---------- 应用修改 ----------
|
||||
# 允许修改的字段白名单(防止注入)
|
||||
# 前端参数名 → 模型字段名映射
|
||||
field_name_map = {
|
||||
'kaioi_ewai_PlayerCommission': 'kaioi_ewai_dashou_fencheng',
|
||||
'ewai_PlayerCommission': 'ewai_dashou_fencheng',
|
||||
}
|
||||
allowed_fields = [
|
||||
'biaoqian', 'jiage', 'kucun', 'leixing_id', 'duiwai_xiaoliang',
|
||||
'jieshao', 'xiadan_xuzhi', 'yaoqiuleixing', 'yongjin',
|
||||
'kaioi_ewai_PlayerCommission', 'ewai_PlayerCommission',
|
||||
'shangjia_zhuangtai', 'fengjin_zhuangtai', 'shenhe_zhuangtai'
|
||||
]
|
||||
|
||||
update_fields = []
|
||||
for field in allowed_fields:
|
||||
value = request.data.get(field)
|
||||
if value is not None:
|
||||
model_field = field_name_map.get(field, field)
|
||||
# 简单类型校验(根据字段类型)
|
||||
if field in ['jiage', 'kucun', 'duiwai_xiaoliang', 'yongjin', 'ewai_PlayerCommission', 'paixu']:
|
||||
try:
|
||||
value = float(value) if 'jiage' in field or 'yongjin' in field or 'ewai' in field else int(value)
|
||||
except (ValueError, TypeError):
|
||||
return Response({'code': 400, 'msg': f'字段 {field} 值格式错误'})
|
||||
setattr(product, model_field, value)
|
||||
update_fields.append(model_field)
|
||||
|
||||
if update_fields:
|
||||
product.save(update_fields=update_fields)
|
||||
return Response({'code': 0, 'msg': '商品修改成功'})
|
||||
|
||||
|
||||
|
||||
|
||||
# 与前端约定的聊天权限码
|
||||
CHAT_PERM_CODES = ['abca1', 'baac2', 'cabc3', 'cb3a2', 'bcaa4']
|
||||
|
||||
Reference in New Issue
Block a user