新增转盘活动/奖品/机会表与 Czjilu leixing=7 履约;专区 tupian_url;hqsp/详情下发 shi_zhuanpan。普通点单与抢单主链路保持不变。 Co-authored-by: Cursor <cursoragent@cursor.com>
2559 lines
103 KiB
Python
2559 lines
103 KiB
Python
"""商品发布与管理后台视图。
|
||
|
||
包含商品类型/专区/商品发布、规则图片上传、会员信息查询/修改/发布、
|
||
管理员商品/公告/专区/轮播/类型修改、商品内容获取与修改、规则修改。
|
||
"""
|
||
# ==================== 标准库 ====================
|
||
import os
|
||
import json
|
||
import time
|
||
import random
|
||
import string
|
||
import uuid
|
||
import hashlib
|
||
import requests
|
||
import logging
|
||
import decimal
|
||
from datetime import timedelta
|
||
from decimal import Decimal, InvalidOperation, ROUND_CEILING
|
||
import defusedxml.ElementTree as ET
|
||
|
||
# ==================== Django 内置 ====================
|
||
from django.conf import settings
|
||
from django.db import transaction, connection
|
||
from django.db.models import Q
|
||
from gvsdsdk.fluent import db, func, FQ
|
||
from django.shortcuts import render, get_object_or_404
|
||
from django.http import HttpResponse
|
||
from django.utils import timezone
|
||
|
||
# ==================== DRF 框架 ====================
|
||
from rest_framework.views import APIView, View
|
||
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 utils.oss_utils import validate_image, upload_to_oss, delete_from_oss
|
||
from utils.money import format_yuan, quantize_yuan, yuan_to_fen
|
||
from backend.utils import (
|
||
update_guanshi_daily_by_action,
|
||
update_guanshi_xufei_daily,
|
||
update_zuzhang_daily_by_action
|
||
)
|
||
from users.fadan_fenhong_utils import process_fadan_fenhong
|
||
|
||
# ==================== 各App模型 ====================
|
||
from ..models import (
|
||
ShangpinLeixing, ShangpinZhuanqu, Shangpin, Huiyuan,
|
||
Czjilu, Gsfenhong, Huiyuangoumai, DuociFenhong
|
||
)
|
||
from users.models import (
|
||
AdminProfile, UserDashou, UserGuanshi, UserZuzhang, UserShangjia
|
||
)
|
||
from users.business_models import User
|
||
|
||
from orders.models import CommissionRate
|
||
from config.models import Gonggao, Lunbo
|
||
|
||
from orders.models import Penalty # 用于罚款类型
|
||
from jituan.services.club_config import (
|
||
get_commission_rate,
|
||
get_commission_rate_object,
|
||
get_huiyuan_price,
|
||
get_huiyuan_fenchong,
|
||
)
|
||
from jituan.services.club_context import resolve_effective_club_id
|
||
from jituan.services.club_write import resolve_club_id_for_write
|
||
from jituan.services.club_user import get_payment_openid
|
||
from jituan.services.club_penalty import resolve_gsfenhong_club_id
|
||
|
||
import traceback
|
||
|
||
# ==================== 日志实例 ====================
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class ShangpinLeixingFabuView(APIView):
|
||
"""
|
||
商品类型发布接口
|
||
接收图片、类型介绍、抢单要求类型等字段
|
||
需要管理员权限
|
||
"""
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
parser_classes = (MultiPartParser, FormParser) # 需要处理文件上传
|
||
|
||
def post(self, request):
|
||
try:
|
||
# === 1. 权限验证 ===
|
||
# 获取当前用户
|
||
user_main = request.user
|
||
|
||
# 1.1 验证前端传递的账号是否匹配(使用phone字段)
|
||
zhanghao = request.data.get('zhanghao', '').strip()
|
||
if not zhanghao:
|
||
return Response(
|
||
{'code': 401, 'msg': '未提供账号信息', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 验证账号是否匹配(使用phone字段比较)
|
||
if user_main.Phone != zhanghao:
|
||
return Response(
|
||
{'code': 401, 'msg': '账号验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 1.2 验证是否为管理员(检查user_type字段)
|
||
if user_main.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'msg': '权限不足', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 1.3 验证管理员扩展表(使用AdminProfile模型)
|
||
try:
|
||
admin_profile = AdminProfile.query.get(user=user_main)
|
||
except AdminProfile.DoesNotExist:
|
||
return Response(
|
||
{'code': 401, 'msg': '管理员信息不存在', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# === 2. 接收并验证字段 ===
|
||
# 2.1 必填字段验证
|
||
jieshao = request.data.get('jieshao', '').strip()
|
||
yaoqiuleixing = request.data.get('yaoqiuleixing')
|
||
|
||
|
||
if not jieshao:
|
||
return Response(
|
||
{'code': 400, 'msg': '类型介绍不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
if yaoqiuleixing not in ['1', '2']:
|
||
return Response(
|
||
{'code': 400, 'msg': '抢单要求类型无效', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2.2 验证图片
|
||
tupian_file = request.FILES.get('file')
|
||
if not tupian_file:
|
||
|
||
return Response(
|
||
{'code': 400, 'msg': '请上传类型图片', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 验证图片格式和大小
|
||
is_valid, error_msg = validate_image(tupian_file)
|
||
if not is_valid:
|
||
return Response(
|
||
{'code': 400, 'msg': f'图片验证失败: {error_msg}', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2.3 根据抢单要求类型验证对应字段
|
||
huiyuan_id = request.data.get('huiyuan_id', '').strip()
|
||
yongjin = request.data.get('yongjin', '').strip()
|
||
|
||
|
||
# 转换yaoqiuleixing为整数便于处理
|
||
yaoqiuleixing_int = int(yaoqiuleixing)
|
||
|
||
if yaoqiuleixing_int == 1: # 会员抢单
|
||
if not huiyuan_id:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员抢单必须选择会员', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
# 验证会员ID是否存在
|
||
try:
|
||
huiyuan_obj = Huiyuan.query.get(huiyuan_id=huiyuan_id)
|
||
except Huiyuan.DoesNotExist:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员ID不存在', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
# 会员ID为字符串,佣金为空
|
||
yongjin_value = None
|
||
huiyuan_id_value = huiyuan_id
|
||
else: # yaoqiuleixing_int == 2 佣金抢单
|
||
if not yongjin:
|
||
return Response(
|
||
{'code': 400, 'msg': '佣金抢单必须填写佣金金额', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
try:
|
||
# 验证佣金为有效数字
|
||
yongjin_value = float(yongjin)
|
||
if yongjin_value <= 0:
|
||
return Response(
|
||
{'code': 400, 'msg': '佣金金额必须大于0', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
except ValueError:
|
||
return Response(
|
||
{'code': 400, 'msg': '佣金金额格式错误', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
huiyuan_id_value = None
|
||
|
||
# === 3. 创建商品类型记录 ===
|
||
with transaction.atomic():
|
||
# 3.1 创建商品类型对象
|
||
leixing_obj = ShangpinLeixing(
|
||
jieshao=jieshao,
|
||
yaoqiuleixing=yaoqiuleixing_int,
|
||
huiyuan_id=huiyuan_id_value,
|
||
yongjin=yongjin_value,
|
||
# tupian_url 先留空,上传成功后再更新
|
||
)
|
||
|
||
# 保存到数据库获取ID
|
||
leixing_obj.save()
|
||
leixing_id = leixing_obj.id
|
||
|
||
# === 4. 处理图片上传 ===
|
||
try:
|
||
# 4.1 生成文件名和路径
|
||
file_extension = os.path.splitext(tupian_file.name)[1].lower()
|
||
if not file_extension:
|
||
file_extension = '.jpg'
|
||
|
||
# 生成唯一文件名:商品类型ID + 随机UUID + 后缀
|
||
unique_filename = f"{leixing_id}_{uuid.uuid4().hex}{file_extension}"
|
||
|
||
# 构建OSS文件路径:a_long/leixing/{leixing_id}/{filename}
|
||
oss_folder = 'a_long' # 根文件夹
|
||
leixing_folder = 'leixing' # 类型文件夹
|
||
|
||
# 构建完整路径
|
||
oss_file_path = f"{oss_folder}/{leixing_folder}/{leixing_id}/{unique_filename}"
|
||
|
||
# 4.2 上传到OSS
|
||
tupian_url = upload_to_oss(tupian_file, oss_file_path)
|
||
|
||
if not tupian_url:
|
||
# 上传失败,抛出异常触发回滚
|
||
raise Exception("图片上传到OSS失败")
|
||
|
||
# 4.3 更新商品类型的图片URL(存储相对路径)
|
||
leixing_obj.tupian_url = oss_file_path # 存相对路径,方便拼接
|
||
leixing_obj.save()
|
||
|
||
except Exception as oss_error:
|
||
# 图片上传失败,事务会回滚,商品类型记录也会被删除
|
||
print(f"OSS上传失败: {oss_error}")
|
||
# 手动抛出异常触发事务回滚
|
||
raise Exception(f"图片上传失败: {str(oss_error)}")
|
||
|
||
# === 5. 构建返回数据 ===
|
||
response_data = {
|
||
'id': leixing_id,
|
||
'jieshao': jieshao,
|
||
'yaoqiuleixing': yaoqiuleixing_int,
|
||
'tupian_url': leixing_obj.tupian_url, # 相对路径
|
||
'huiyuan_id': huiyuan_id_value,
|
||
'yongjin': str(yongjin_value) if yongjin_value else None
|
||
}
|
||
|
||
# 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '商品类型添加成功',
|
||
'data': response_data
|
||
})
|
||
|
||
except Exception as e:
|
||
# 捕获所有异常,返回500错误
|
||
traceback.print_exc()
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'系统错误: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
class ShangpinZhuanquFabuView(APIView):
|
||
"""
|
||
商品专区发布接口
|
||
接收专区名称、类型ID
|
||
需要管理员权限
|
||
"""
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# === 1. 权限验证 ===
|
||
user_main = request.user
|
||
|
||
# 1.1 验证账号(使用phone字段)
|
||
zhanghao = request.data.get('zhanghao', '').strip()
|
||
if not zhanghao:
|
||
return Response(
|
||
{'code': 401, 'msg': '未提供账号信息', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
if user_main.Phone != zhanghao:
|
||
return Response(
|
||
{'code': 401, 'msg': '账号验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 1.2 验证是否为管理员(检查user_type字段)
|
||
if user_main.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'msg': '权限不足', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 1.3 验证管理员扩展表(使用AdminProfile模型)
|
||
try:
|
||
admin_profile = AdminProfile.query.get(user=user_main)
|
||
except AdminProfile.DoesNotExist:
|
||
return Response(
|
||
{'code': 401, 'msg': '管理员信息不存在', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# === 2. 接收并验证字段 ===
|
||
mingzi = request.data.get('mingzi', '').strip()
|
||
leixing_id = request.data.get('leixing_id')
|
||
tupian_url = (request.data.get('tupian_url') or '').strip()
|
||
|
||
# 2.1 验证必填字段
|
||
if not mingzi:
|
||
return Response(
|
||
{'code': 400, 'msg': '专区名称不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
if not leixing_id:
|
||
return Response(
|
||
{'code': 400, 'msg': '类型ID不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2.2 验证类型ID是否存在
|
||
try:
|
||
# 验证商品类型是否存在
|
||
leixing_obj = ShangpinLeixing.query.get(id=leixing_id)
|
||
except ShangpinLeixing.DoesNotExist:
|
||
return Response(
|
||
{'code': 400, 'msg': '商品类型不存在', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2.3 验证名称长度(15汉字以内)
|
||
if len(mingzi) > 45: # 数据库字段最大长度
|
||
return Response(
|
||
{'code': 400, 'msg': '专区名称过长', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# === 3. 创建商品专区记录 ===
|
||
club_id = resolve_club_id_for_write(request)
|
||
zhuanqu_obj = ShangpinZhuanqu(
|
||
mingzi=mingzi,
|
||
leixing_id=leixing_id,
|
||
club_id=club_id,
|
||
tupian_url=tupian_url,
|
||
)
|
||
|
||
zhuanqu_obj.save()
|
||
|
||
# === 4. 构建返回数据 ===
|
||
response_data = {
|
||
'id': zhuanqu_obj.id,
|
||
'mingzi': mingzi,
|
||
'leixing_id': leixing_id,
|
||
'tupian_url': tupian_url,
|
||
}
|
||
|
||
# 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '商品专区添加成功',
|
||
'data': response_data
|
||
})
|
||
|
||
except Exception as e:
|
||
traceback.print_exc()
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'系统错误: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
class ShangpinFabuView(APIView):
|
||
"""
|
||
商品发布接口
|
||
接收商品信息、图片等字段
|
||
需要管理员权限
|
||
"""
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
parser_classes = (MultiPartParser, FormParser) # 需要处理文件上传
|
||
|
||
def post(self, request):
|
||
try:
|
||
# === 1. 权限验证 ===
|
||
user_main = request.user
|
||
|
||
# 1.1 验证账号(使用phone字段)
|
||
zhanghao = request.data.get('zhanghao', '').strip()
|
||
if not zhanghao:
|
||
return Response(
|
||
{'code': 401, 'msg': '未提供账号信息', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
if user_main.Phone != zhanghao:
|
||
return Response(
|
||
{'code': 401, 'msg': '账号验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 1.2 验证是否为管理员(检查user_type字段)
|
||
if user_main.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'msg': '权限不足', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 1.3 验证管理员扩展表(使用AdminProfile模型)
|
||
try:
|
||
admin_profile = AdminProfile.query.get(user=user_main)
|
||
except AdminProfile.DoesNotExist:
|
||
return Response(
|
||
{'code': 401, 'msg': '管理员信息不存在', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# === 2. 接收并验证字段 ===
|
||
# 2.1 验证必填字段
|
||
biaoti = request.data.get('biaoti', '').strip()
|
||
jiage = request.data.get('jiage', '').strip()
|
||
kucun = request.data.get('kucun', '').strip()
|
||
leixing_id = request.data.get('leixing_id')
|
||
zhuanqu_id = request.data.get('zhuanqu_id')
|
||
yaoqiuleixing = request.data.get('yaoqiuleixing')
|
||
jieshao = request.data.get('jieshao', '').strip()
|
||
|
||
# 验证图片
|
||
shangpin_tupian = request.FILES.get('shangpin_tupian')
|
||
if not shangpin_tupian:
|
||
|
||
return Response(
|
||
{'code': 400, 'msg': '请上传商品图片', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 验证图片格式和大小
|
||
is_valid, error_msg = validate_image(shangpin_tupian)
|
||
if not is_valid:
|
||
return Response(
|
||
{'code': 400, 'msg': f'商品图片验证失败: {error_msg}', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2.2 验证必填字段是否为空
|
||
required_fields = {
|
||
'biaoti': biaoti,
|
||
'jiage': jiage,
|
||
'kucun': kucun,
|
||
'leixing_id': leixing_id,
|
||
'zhuanqu_id': zhuanqu_id,
|
||
'yaoqiuleixing': yaoqiuleixing,
|
||
'jieshao': jieshao
|
||
}
|
||
|
||
for field_name, field_value in required_fields.items():
|
||
if not field_value:
|
||
return Response(
|
||
{'code': 400, 'msg': f'{field_name}不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2.3 验证数值字段
|
||
try:
|
||
jiage_value = float(jiage)
|
||
if jiage_value <= 0:
|
||
return Response(
|
||
{'code': 400, 'msg': '商品价格必须大于0', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
except ValueError:
|
||
return Response(
|
||
{'code': 400, 'msg': '商品价格格式错误', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
try:
|
||
kucun_value = int(kucun)
|
||
if kucun_value < 0:
|
||
return Response(
|
||
{'code': 400, 'msg': '库存不能为负数', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
except ValueError:
|
||
return Response(
|
||
{'code': 400, 'msg': '库存必须为整数', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 验证抢单要求类型
|
||
if yaoqiuleixing not in ['1', '2']:
|
||
return Response(
|
||
{'code': 400, 'msg': '抢单要求类型无效', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# === 3. 验证关联数据 ===
|
||
# 3.1 验证商品类型
|
||
try:
|
||
leixing_obj = ShangpinLeixing.query.get(id=leixing_id)
|
||
except ShangpinLeixing.DoesNotExist:
|
||
return Response(
|
||
{'code': 400, 'msg': '商品类型不存在', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 3.2 根据抢单要求类型验证商品类型
|
||
yaoqiuleixing_int = int(yaoqiuleixing)
|
||
|
||
if yaoqiuleixing_int == 1: # 会员抢单
|
||
# 需要验证商品类型的抢单要求类型是否为1
|
||
if leixing_obj.yaoqiuleixing != 1:
|
||
return Response(
|
||
{'code': 400, 'msg': '商品类型与抢单要求类型不匹配', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 获取会员ID(从前端或商品类型)
|
||
huiyuan_id = request.data.get('huiyuan_id', leixing_obj.huiyuan_id)
|
||
if not huiyuan_id:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员ID不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 验证会员是否存在
|
||
try:
|
||
huiyuan_obj = Huiyuan.query.get(huiyuan_id=huiyuan_id)
|
||
except Huiyuan.DoesNotExist:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员不存在', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 设置会员相关字段
|
||
huiyuan_id_value = huiyuan_id
|
||
yongjin_value = None
|
||
else: # 佣金抢单
|
||
# 需要验证商品类型的抢单要求类型是否为2
|
||
if leixing_obj.yaoqiuleixing != 2:
|
||
return Response(
|
||
{'code': 400, 'msg': '商品类型与抢单要求类型不匹配', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 获取佣金(从前端或商品类型)
|
||
yongjin = request.data.get('yongjin', leixing_obj.yongjin)
|
||
if not yongjin:
|
||
return Response(
|
||
{'code': 400, 'msg': '佣金金额不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
try:
|
||
yongjin_value = float(yongjin)
|
||
if yongjin_value <= 0:
|
||
return Response(
|
||
{'code': 400, 'msg': '佣金金额必须大于0', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
except ValueError:
|
||
return Response(
|
||
{'code': 400, 'msg': '佣金金额格式错误', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
huiyuan_id_value = None
|
||
|
||
# 3.3 验证商品专区
|
||
try:
|
||
zhuanqu_obj = ShangpinZhuanqu.query.get(id=zhuanqu_id, leixing_id=leixing_id)
|
||
except ShangpinZhuanqu.DoesNotExist:
|
||
return Response(
|
||
{'code': 400, 'msg': '商品专区不存在或与商品类型不匹配', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# === 4. 创建商品记录 ===
|
||
with transaction.atomic():
|
||
# 4.1 获取其他字段
|
||
xuzhi = request.data.get('xuzhi', '').strip()
|
||
xiaoliang = request.data.get('xiaoliang', '0').strip()
|
||
|
||
# 转换销量为整数
|
||
try:
|
||
duiwai_xiaoliang_value = int(xiaoliang) if xiaoliang else 0
|
||
except ValueError:
|
||
duiwai_xiaoliang_value = 0
|
||
|
||
# 4.2 创建商品对象
|
||
club_id = resolve_club_id_for_write(request)
|
||
shangpin_obj = Shangpin(
|
||
biaoqian=biaoti,
|
||
jiage=jiage_value,
|
||
kucun=kucun_value,
|
||
leixing_id=leixing_id,
|
||
zhuanqu_id=zhuanqu_id,
|
||
duiwai_xiaoliang=duiwai_xiaoliang_value,
|
||
jieshao=jieshao,
|
||
xiadan_xuzhi=xuzhi,
|
||
yaoqiuleixing=yaoqiuleixing_int,
|
||
huiyuan_id=huiyuan_id_value,
|
||
yongjin=yongjin_value,
|
||
club_id=club_id,
|
||
# tupian_url 先留空,上传成功后再更新
|
||
)
|
||
|
||
# 保存到数据库获取ID
|
||
shangpin_obj.save()
|
||
shangpin_id = shangpin_obj.id
|
||
|
||
# === 5. 处理商品图片上传 ===
|
||
try:
|
||
# 5.1 生成文件名和路径
|
||
file_extension = os.path.splitext(shangpin_tupian.name)[1].lower()
|
||
if not file_extension:
|
||
file_extension = '.jpg'
|
||
|
||
# 构建OSS文件路径:a_long/Spcc{shangpin_id}/{filename}
|
||
oss_folder = 'a_long' # 根文件夹
|
||
spcc_folder = f"Spcc{shangpin_id}" # 商品图片文件夹
|
||
|
||
# 生成文件名:Spcc{shangpin_id}{file_extension}
|
||
unique_filename = f"Spcc{shangpin_id}{file_extension}"
|
||
|
||
# 构建完整路径
|
||
oss_file_path = f"{oss_folder}/{spcc_folder}/{unique_filename}"
|
||
|
||
# 5.2 上传到OSS
|
||
tupian_url = upload_to_oss(shangpin_tupian, oss_file_path)
|
||
|
||
if not tupian_url:
|
||
# 上传失败,抛出异常触发回滚
|
||
raise Exception("商品图片上传到OSS失败")
|
||
|
||
# 5.3 更新商品的图片URL(存储相对路径)
|
||
shangpin_obj.tupian_url = oss_file_path # 存相对路径
|
||
shangpin_obj.save()
|
||
|
||
except Exception as oss_error:
|
||
# 图片上传失败,事务会回滚,商品记录也会被删除
|
||
#print(f"商品图片OSS上传失败: {oss_error}")
|
||
# 手动抛出异常触发事务回滚
|
||
raise Exception(f"商品图片上传失败: {str(oss_error)}")
|
||
|
||
# === 6. 构建返回数据 ===
|
||
response_data = {
|
||
'shangpin_id': shangpin_id,
|
||
'biaoti': biaoti,
|
||
'jiage': str(jiage_value),
|
||
'kucun': kucun_value,
|
||
'leixing_id': leixing_id,
|
||
'zhuanqu_id': zhuanqu_id,
|
||
'xiaoliang': duiwai_xiaoliang_value,
|
||
'jieshao': jieshao,
|
||
'xuzhi': xuzhi,
|
||
'yaoqiuleixing': yaoqiuleixing_int,
|
||
'huiyuan_id': huiyuan_id_value,
|
||
'yongjin': str(yongjin_value) if yongjin_value else None,
|
||
'tupian_url': shangpin_obj.tupian_url # 相对路径
|
||
}
|
||
|
||
# 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '商品发布成功',
|
||
'data': response_data
|
||
})
|
||
|
||
except Exception as e:
|
||
# 捕获所有异常,返回500错误
|
||
traceback.print_exc()
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'系统错误: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
class GuizeTupianShangchuanView(APIView):
|
||
"""
|
||
规则图片上传接口
|
||
接收商品ID和规则图片
|
||
需要管理员权限
|
||
"""
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
parser_classes = (MultiPartParser, FormParser) # 需要处理文件上传
|
||
|
||
def post(self, request):
|
||
try:
|
||
# === 1. 权限验证 ===
|
||
user_main = request.user
|
||
|
||
# 1.1 验证账号(使用phone字段)
|
||
zhanghao = request.data.get('zhanghao', '').strip()
|
||
if not zhanghao:
|
||
return Response(
|
||
{'code': 401, 'msg': '未提供账号信息', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
if user_main.Phone != zhanghao:
|
||
return Response(
|
||
{'code': 401, 'msg': '账号验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 1.2 验证是否为管理员(检查user_type字段)
|
||
if user_main.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'msg': '权限不足', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 1.3 验证管理员扩展表(使用AdminProfile模型)
|
||
try:
|
||
admin_profile = AdminProfile.query.get(user=user_main)
|
||
except AdminProfile.DoesNotExist:
|
||
return Response(
|
||
{'code': 401, 'msg': '管理员信息不存在', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# === 2. 接收并验证字段 ===
|
||
# 2.1 验证商品ID
|
||
shangpin_id = request.data.get('shangpin_id')
|
||
if not shangpin_id:
|
||
return Response(
|
||
{'code': 400, 'msg': '商品ID不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2.2 验证规则图片
|
||
guize_tupian = request.FILES.get('guize_tupian')
|
||
if not guize_tupian:
|
||
return Response(
|
||
{'code': 400, 'msg': '请上传规则图片', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 验证图片格式和大小
|
||
is_valid, error_msg = validate_image(guize_tupian)
|
||
if not is_valid:
|
||
return Response(
|
||
{'code': 400, 'msg': f'规则图片验证失败: {error_msg}', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# === 3. 验证商品是否存在 ===
|
||
try:
|
||
shangpin_obj = Shangpin.query.get(id=shangpin_id)
|
||
except Shangpin.DoesNotExist:
|
||
return Response(
|
||
{'code': 400, 'msg': '商品不存在', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# === 4. 处理规则图片上传 ===
|
||
try:
|
||
# 4.1 生成文件名和路径
|
||
file_extension = os.path.splitext(guize_tupian.name)[1].lower()
|
||
if not file_extension:
|
||
file_extension = '.jpg'
|
||
|
||
# 构建OSS文件路径:a_long/Spgz{shangpin_id}/{filename}
|
||
oss_folder = 'a_long' # 根文件夹
|
||
spgz_folder = f"Spgz{shangpin_id}" # 规则图片文件夹
|
||
|
||
# 生成文件名:Spgz{shangpin_id}{file_extension}
|
||
unique_filename = f"Spgz{shangpin_id}{file_extension}"
|
||
|
||
# 构建完整路径
|
||
oss_file_path = f"{oss_folder}/{spgz_folder}/{unique_filename}"
|
||
|
||
# 4.2 上传到OSS
|
||
tupian_url = upload_to_oss(guize_tupian, oss_file_path)
|
||
|
||
if not tupian_url:
|
||
# 上传失败
|
||
return Response({
|
||
'code': 500,
|
||
'msg': '规则图片上传失败',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
# 4.3 更新商品的规则图片URL(存储相对路径)
|
||
shangpin_obj.guize_tupian = oss_file_path # 存相对路径
|
||
shangpin_obj.save()
|
||
|
||
except Exception as oss_error:
|
||
# OSS上传失败
|
||
print(f"规则图片OSS上传失败: {oss_error}")
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'规则图片上传失败: {str(oss_error)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
# === 5. 构建返回数据 ===
|
||
response_data = {
|
||
'shangpin_id': shangpin_id,
|
||
'guize_tupian_url': oss_file_path # 相对路径
|
||
}
|
||
|
||
# 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '规则图片上传成功',
|
||
'data': response_data
|
||
})
|
||
|
||
except Exception as e:
|
||
# 捕获所有异常,返回500错误
|
||
traceback.print_exc()
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'系统错误: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
class AdHuiyuanHqView(APIView):
|
||
"""
|
||
管理员获取会员信息和商品类型接口
|
||
需要管理员权限
|
||
"""
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# === 1. 权限验证 ===
|
||
# 获取当前用户
|
||
user_main = request.user
|
||
|
||
# 1.1 验证前端传递的账号是否匹配(使用phone字段)
|
||
zhanghao = request.data.get('zhanghao', '').strip()
|
||
if not zhanghao:
|
||
return Response(
|
||
{'code': 401, 'msg': '权限验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 验证账号是否匹配(使用phone字段比较)
|
||
if user_main.Phone != zhanghao:
|
||
return Response(
|
||
{'code': 401, 'msg': '权限验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 1.2 验证是否为管理员(检查user_type字段)
|
||
if user_main.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'msg': '权限验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 1.3 验证管理员扩展表(使用AdminProfile模型)
|
||
try:
|
||
admin_profile = AdminProfile.query.get(user=user_main)
|
||
except AdminProfile.DoesNotExist:
|
||
return Response(
|
||
{'code': 401, 'msg': '权限验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# === 2. 查询会员表数据 ===
|
||
# 查询所有会员数据,按创建时间倒序排列
|
||
huiyuan_queryset = Huiyuan.query.all().order_by('-CreateTime')
|
||
|
||
# 构建会员数据列表
|
||
huiyuan_list = []
|
||
for huiyuan in huiyuan_queryset:
|
||
huiyuan_data = {
|
||
'huiyuan_id': huiyuan.huiyuan_id, # 会员ID
|
||
'jieshao': huiyuan.jieshao, # 会员介绍(会员标题)
|
||
'jtjieshao': huiyuan.jtjieshao if huiyuan.jtjieshao else '', # 会员规则介绍(具体介绍)
|
||
'jiage': str(huiyuan.jiage), # 会员价格(转换为字符串,避免Decimal序列化问题)
|
||
'guanshifc': str(huiyuan.guanshifc), # 管事分成(转换为字符串)
|
||
'goumai_cishu': huiyuan.goumai_cishu, # 购买次数(销量)
|
||
'CreateTime': huiyuan.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if huiyuan.CreateTime else None,
|
||
# 创建时间
|
||
'UpdateTime': huiyuan.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if huiyuan.UpdateTime else None
|
||
# 更新时间
|
||
}
|
||
huiyuan_list.append(huiyuan_data)
|
||
|
||
# === 3. 查询商品类型表数据 ===
|
||
# 筛选抢单要求类型等于数字1的所有数据(yaoqiuleixing=1)
|
||
shangpinleixing_queryset = ShangpinLeixing.query.filter(
|
||
yaoqiuleixing=1 # 抢单要求类型为1(会员抢单)
|
||
).order_by('id') # 按ID排序
|
||
|
||
# 构建商品类型数据列表
|
||
shangpinleixing_list = []
|
||
for leixing in shangpinleixing_queryset:
|
||
# 注意:这里只需要特定的字段
|
||
leixing_data = {
|
||
'id': leixing.id, # 商品类型ID
|
||
'huiyuan_id': leixing.huiyuan_id, # 会员ID(关联字段)
|
||
'jieshao': leixing.jieshao, # 类型介绍(商品类型名称)
|
||
'tupian_url': leixing.tupian_url if leixing.tupian_url else '' # 商品类型图片相对URL
|
||
}
|
||
shangpinleixing_list.append(leixing_data)
|
||
|
||
# === 4. 构建返回数据 ===
|
||
response_data = {
|
||
'huiyuanList': huiyuan_list,
|
||
'shangpinleixingList': shangpinleixing_list
|
||
}
|
||
|
||
# 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '获取数据成功',
|
||
'data': response_data
|
||
})
|
||
|
||
except Exception as e:
|
||
# 捕获所有异常,返回500错误
|
||
traceback.print_exc()
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'系统错误: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
class HuiyuanXiugaiView(APIView):
|
||
"""
|
||
会员修改接口
|
||
需要管理员权限
|
||
"""
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# === 1. 权限验证 ===
|
||
user_main = request.user
|
||
|
||
# 1.1 验证前端传递的账号是否匹配(使用phone字段)
|
||
zhanghao = request.data.get('zhanghao', '').strip()
|
||
if not zhanghao:
|
||
return Response(
|
||
{'code': 401, 'msg': '权限验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
if user_main.Phone != zhanghao:
|
||
return Response(
|
||
{'code': 401, 'msg': '权限验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 1.2 验证是否为管理员(检查user_type字段)
|
||
if user_main.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'msg': '权限验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 1.3 验证管理员扩展表
|
||
try:
|
||
admin_profile = AdminProfile.query.get(user=user_main)
|
||
except AdminProfile.DoesNotExist:
|
||
return Response(
|
||
{'code': 401, 'msg': '权限验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# === 2. 接收并验证必填字段 ===
|
||
huiyuan_id = request.data.get('huiyuan_id', '').strip()
|
||
if not huiyuan_id:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员ID不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# === 3. 查询会员数据 ===
|
||
try:
|
||
huiyuan_obj = Huiyuan.query.get(huiyuan_id=huiyuan_id)
|
||
except Huiyuan.DoesNotExist:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员不存在', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# === 4. 更新会员信息 ===
|
||
# 4.1 获取前端传递的字段(只更新有值的字段)
|
||
jieshao = request.data.get('jieshao', '').strip()
|
||
jtjieshao = request.data.get('jtjieshao', '').strip()
|
||
jiage = request.data.get('jiage', '').strip()
|
||
guanshifc = request.data.get('guanshifc', '').strip()
|
||
|
||
# 4.2 更新字段
|
||
if jieshao:
|
||
# 验证名称长度(30个字符以内)
|
||
if len(jieshao) > 30:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员名称不能超过30个字符', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
huiyuan_obj.jieshao = jieshao
|
||
|
||
if jtjieshao:
|
||
# 验证介绍长度(没有长度限制,但做一下基本检查)
|
||
if len(jtjieshao) > 2000:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员介绍过长', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
huiyuan_obj.jtjieshao = jtjieshao
|
||
|
||
if jiage:
|
||
try:
|
||
jiage_value = float(jiage)
|
||
if jiage_value < 0:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员价格不能为负数', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
if jiage_value > 100000:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员价格不能超过10万元', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
huiyuan_obj.jiage = jiage_value
|
||
except ValueError:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员价格格式错误', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
if guanshifc:
|
||
try:
|
||
guanshifc_value = float(guanshifc)
|
||
if guanshifc_value < 0:
|
||
return Response(
|
||
{'code': 400, 'msg': '管事分成不能为负数', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
# 检查管事分成是否超过会员价格
|
||
if hasattr(huiyuan_obj, 'jiage') and huiyuan_obj.jiage:
|
||
if guanshifc_value > float(huiyuan_obj.jiage):
|
||
return Response(
|
||
{'code': 400, 'msg': '管事分成不能超过会员价格', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
huiyuan_obj.guanshifc = guanshifc_value
|
||
except ValueError:
|
||
return Response(
|
||
{'code': 400, 'msg': '管事分成格式错误', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 4.3 检查是否有任何字段需要更新
|
||
if not (jieshao or jtjieshao or jiage or guanshifc):
|
||
return Response(
|
||
{'code': 400, 'msg': '没有需要更新的字段', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 4.4 保存更新
|
||
huiyuan_obj.save()
|
||
|
||
# === 5. 构建返回数据 ===
|
||
response_data = {
|
||
'huiyuan_id': huiyuan_obj.huiyuan_id,
|
||
'jieshao': huiyuan_obj.jieshao,
|
||
'jtjieshao': huiyuan_obj.jtjieshao if huiyuan_obj.jtjieshao else '',
|
||
'jiage': str(huiyuan_obj.jiage),
|
||
'guanshifc': str(huiyuan_obj.guanshifc),
|
||
'goumai_cishu': huiyuan_obj.goumai_cishu,
|
||
'CreateTime': huiyuan_obj.CreateTime.strftime(
|
||
'%Y-%m-%d %H:%M:%S') if huiyuan_obj.CreateTime else None,
|
||
'UpdateTime': huiyuan_obj.UpdateTime.strftime(
|
||
'%Y-%m-%d %H:%M:%S') if huiyuan_obj.UpdateTime else None
|
||
}
|
||
|
||
# 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '会员信息修改成功',
|
||
'data': response_data
|
||
})
|
||
|
||
except Exception as e:
|
||
# 捕获所有异常,返回500错误
|
||
traceback.print_exc()
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'系统错误: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class AdHuiyuanFbView(APIView):
|
||
"""
|
||
会员发布接口
|
||
需要管理员权限
|
||
"""
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# === 1. 权限验证 ===
|
||
user_main = request.user
|
||
|
||
# 1.1 验证前端传递的账号是否匹配
|
||
zhanghao = request.data.get('zhanghao', '').strip()
|
||
if not zhanghao:
|
||
return Response(
|
||
{'code': 401, 'msg': '权限验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
if user_main.Phone != zhanghao:
|
||
return Response(
|
||
{'code': 401, 'msg': '权限验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 1.2 验证是否为管理员
|
||
if user_main.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'msg': '权限验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 1.3 验证管理员扩展表
|
||
try:
|
||
admin_profile = AdminProfile.query.get(user=user_main)
|
||
except AdminProfile.DoesNotExist:
|
||
return Response(
|
||
{'code': 401, 'msg': '权限验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# === 2. 接收并验证字段 ===
|
||
jieshao = request.data.get('jieshao', '').strip()
|
||
jtjieshao = request.data.get('jtjieshao', '').strip()
|
||
jiage = request.data.get('jiage', '').strip()
|
||
guanshifc = request.data.get('guanshifc', '').strip()
|
||
|
||
# 2.1 验证必填字段
|
||
if not jieshao:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员名称不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
if not jiage:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员价格不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
if not guanshifc:
|
||
return Response(
|
||
{'code': 400, 'msg': '管事分成不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2.2 验证字段长度和格式
|
||
if len(jieshao) > 30:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员名称不能超过30个字符', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
if jtjieshao and len(jtjieshao) > 2000:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员介绍过长', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2.3 验证价格和分成格式
|
||
try:
|
||
jiage_value = float(jiage)
|
||
if jiage_value <= 0:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员价格必须大于0', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
if jiage_value > 100000:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员价格不能超过10万元', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
except ValueError:
|
||
return Response(
|
||
{'code': 400, 'msg': '会员价格格式错误', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
try:
|
||
guanshifc_value = float(guanshifc)
|
||
if guanshifc_value < 0:
|
||
return Response(
|
||
{'code': 400, 'msg': '管事分成不能为负数', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
if guanshifc_value > jiage_value:
|
||
return Response(
|
||
{'code': 400, 'msg': '管事分成不能超过会员价格', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
except ValueError:
|
||
return Response(
|
||
{'code': 400, 'msg': '管事分成格式错误', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# === 3. 生成会员ID ===
|
||
# 生成6位唯一会员ID,格式:V + 5位随机数字字母
|
||
def generate_huiyuan_id():
|
||
# 使用V开头,后跟5位随机字符(数字和大写字母)
|
||
chars = string.digits + string.ascii_uppercase
|
||
while True:
|
||
random_part = ''.join(random.choices(chars, k=5))
|
||
huiyuan_id = f"V{random_part}"
|
||
# 检查是否已存在
|
||
if not Huiyuan.query.filter(huiyuan_id=huiyuan_id).exists():
|
||
return huiyuan_id
|
||
|
||
huiyuan_id = generate_huiyuan_id()
|
||
|
||
# === 4. 创建会员记录 ===
|
||
with transaction.atomic():
|
||
huiyuan_obj = Huiyuan(
|
||
huiyuan_id=huiyuan_id,
|
||
jieshao=jieshao,
|
||
jtjieshao=jtjieshao if jtjieshao else '',
|
||
jiage=jiage_value,
|
||
guanshifc=guanshifc_value,
|
||
goumai_cishu=0, # 初始购买次数为0
|
||
)
|
||
|
||
huiyuan_obj.save()
|
||
|
||
# === 5. 构建返回数据 ===
|
||
response_data = {
|
||
'huiyuan_id': huiyuan_obj.huiyuan_id,
|
||
'jieshao': huiyuan_obj.jieshao,
|
||
'jtjieshao': huiyuan_obj.jtjieshao,
|
||
'jiage': str(huiyuan_obj.jiage),
|
||
'guanshifc': str(huiyuan_obj.guanshifc),
|
||
'goumai_cishu': huiyuan_obj.goumai_cishu,
|
||
'CreateTime': huiyuan_obj.CreateTime.strftime(
|
||
'%Y-%m-%d %H:%M:%S') if huiyuan_obj.CreateTime else None,
|
||
'UpdateTime': huiyuan_obj.UpdateTime.strftime(
|
||
'%Y-%m-%d %H:%M:%S') if huiyuan_obj.UpdateTime else None
|
||
}
|
||
|
||
# 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '会员发布成功',
|
||
'data': response_data
|
||
})
|
||
|
||
except Exception as e:
|
||
# 捕获所有异常,返回500错误
|
||
traceback.print_exc()
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'系统错误: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
class AdShangpinHuQu(APIView):
|
||
"""
|
||
后台商品管理页面数据获取接口
|
||
路径: /shangpin/adsphq/
|
||
方法: POST
|
||
权限: JWT认证 + 管理员权限
|
||
功能: 获取商品管理页面需要的所有数据
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 获取前端传递的账号
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
if not qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败'},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 2. 从JWT Token获取当前用户
|
||
dangqianyonghu = request.user
|
||
|
||
# 3. 验证账号匹配
|
||
if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败'},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 验证用户类型为管理员
|
||
if dangqianyonghu.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败'},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 5. 验证管理员扩展表数据
|
||
try:
|
||
guanliyuan = dangqianyonghu.AdminProfile
|
||
except AdminProfile.DoesNotExist:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败'},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 6. 查询公告与轮播(按俱乐部,默认抢单池)
|
||
from jituan.services.display_config import get_gonggao_content, get_lunbo_urls
|
||
shangpingonggao = get_gonggao_content(request, notice_type=1)
|
||
shangpinlunbo = get_lunbo_urls(request, page_key='order_pool', image_type=1)
|
||
|
||
# 8. 查询商品类型数据
|
||
shangpinleixing_data = ShangpinLeixing.query.all().values(
|
||
'id', # 类型ID
|
||
'jieshao', # 类型介绍(类型名称)
|
||
'tupian_url', # 类型图片URL
|
||
'yaoqiuleixing', # 抢单要求类型(1=会员抢单,2=佣金抢单)
|
||
'huiyuan_id', # 会员ID(如果yaoqiuleixing=1)
|
||
'yongjin' # 佣金要求(如果yaoqiuleixing=2)
|
||
)
|
||
|
||
# 9. 查询商品专区数据(按当前俱乐部)
|
||
admin_club_id = resolve_effective_club_id(request, getattr(request, 'user', None)) or 'xq'
|
||
shangpinzhuanqu_data = ShangpinZhuanqu.query.filter(club_id=admin_club_id).values(
|
||
'id', # 专区ID
|
||
'mingzi', # 专区名称
|
||
'leixing_id', # 所属类型ID
|
||
'club_id',
|
||
)
|
||
|
||
# 10. 查询会员数据
|
||
huiyuan_data = Huiyuan.query.all().values(
|
||
'huiyuan_id', # 会员ID
|
||
'jieshao', # 会员介绍(会员名称)
|
||
'jiage' # 会员价格
|
||
)
|
||
|
||
# 11. 查询商品数据(按当前俱乐部)
|
||
shangpin_data = Shangpin.query.filter(club_id=admin_club_id).values(
|
||
'id', # 商品ID
|
||
'biaoqian', # 商品标题/标签
|
||
'jiage', # 商品价格
|
||
'leixing_id', # 商品类型ID
|
||
'zhuanqu_id', # 商品专区ID
|
||
'tupian_url', # 商品图片URL
|
||
'duiwai_xiaoliang', # 商品对外销量
|
||
'club_id',
|
||
)
|
||
|
||
# 12. 构造返回数据
|
||
response_data = {
|
||
'code': 0,
|
||
'message': '获取成功',
|
||
'data': {
|
||
'shangpingonggao': shangpingonggao, # 商品公告
|
||
'shangpinlunbo': shangpinlunbo, # 轮播图相对URL数组
|
||
'shangpinleixing': list(shangpinleixing_data), # 商品类型列表
|
||
'shangpinzhuanqu': list(shangpinzhuanqu_data), # 商品专区列表
|
||
'huiyuanliebiao': list(huiyuan_data), # 会员列表
|
||
'shangpinliebiao': list(shangpin_data), # 商品列表
|
||
}
|
||
}
|
||
|
||
return Response(response_data, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
print(f"获取商品管理数据异常: {e}")
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误'},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
|
||
# shangpin/views.py
|
||
|
||
class AdGonggaoXiuGai(APIView):
|
||
"""
|
||
后台编辑公告接口
|
||
路径: /shangpin/adggxg/
|
||
方法: POST
|
||
权限: JWT认证 + 管理员权限
|
||
功能: 修改公告内容
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 获取前端传递的账号
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
if not qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败'},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 2. 从JWT Token获取当前用户
|
||
dangqianyonghu = request.user
|
||
|
||
# 3. 验证账号匹配
|
||
if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败'},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 验证用户类型为管理员
|
||
if dangqianyonghu.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败'},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 5. 验证管理员扩展表数据
|
||
try:
|
||
guanliyuan = dangqianyonghu.AdminProfile
|
||
except AdminProfile.DoesNotExist:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败'},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 6. 获取公告内容
|
||
gonggao_neirong = request.data.get('gonggao', '').strip()
|
||
|
||
# 7. 验证公告内容
|
||
if not gonggao_neirong:
|
||
return Response(
|
||
{'code': 400, 'message': '公告内容不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
if len(gonggao_neirong) > 300:
|
||
return Response(
|
||
{'code': 400, 'message': '公告内容不能超过300字', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 8. 查询公告表,类型为1的公告
|
||
gonggao_obj = Gonggao.query.filter(NoticeType=1).first()
|
||
|
||
# 如果不存在,则创建
|
||
if not gonggao_obj:
|
||
gonggao_obj = Gonggao(NoticeType=1)
|
||
|
||
# 9. 更新公告内容
|
||
gonggao_obj.Content = gonggao_neirong
|
||
gonggao_obj.save()
|
||
|
||
# 10. 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'message': '公告更新成功',
|
||
'data': {
|
||
'gonggao': gonggao_neirong
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
print(f"编辑公告异常: {e}")
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
class AdZhuanquXiuGai(APIView):
|
||
"""
|
||
后台修改专区名称接口
|
||
路径: /shangpin/adzqxg/
|
||
方法: POST
|
||
权限: JWT认证 + 管理员权限
|
||
功能: 修改专区名称
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 权限验证
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
if not qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败'},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
dangqianyonghu = request.user
|
||
|
||
if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败'},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
if dangqianyonghu.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败'},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
try:
|
||
guanliyuan = dangqianyonghu.AdminProfile
|
||
except AdminProfile.DoesNotExist:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败'},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 2. 获取专区ID和新的专区名称(可选图标)
|
||
zhuanqu_id = request.data.get('zhuanqu_id')
|
||
mingzi = request.data.get('mingzi', '').strip()
|
||
tupian_url = request.data.get('tupian_url', None)
|
||
|
||
if not zhuanqu_id:
|
||
return Response(
|
||
{'code': 400, 'message': '专区ID不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
if not mingzi:
|
||
return Response(
|
||
{'code': 400, 'message': '专区名称不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
if len(mingzi) > 10:
|
||
return Response(
|
||
{'code': 400, 'message': '专区名称不能大于10个字', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 3. 查询专区
|
||
try:
|
||
zhuanqu_obj = ShangpinZhuanqu.query.get(id=zhuanqu_id)
|
||
except ShangpinZhuanqu.DoesNotExist:
|
||
return Response(
|
||
{'code': 400, 'message': '专区不存在', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 4. 更新专区名称(及可选图标;未传 tupian_url 则不改图)
|
||
zhuanqu_obj.mingzi = mingzi
|
||
if tupian_url is not None:
|
||
zhuanqu_obj.tupian_url = (tupian_url or '').strip()
|
||
zhuanqu_obj.save()
|
||
|
||
# 5. 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'message': '专区名称修改成功',
|
||
'data': {
|
||
'id': zhuanqu_obj.id,
|
||
'mingzi': zhuanqu_obj.mingzi,
|
||
'leixing_id': zhuanqu_obj.leixing_id,
|
||
'tupian_url': getattr(zhuanqu_obj, 'tupian_url', '') or '',
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
print(f"修改专区名称异常: {e}")
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
|
||
|
||
class AdLunboXiuGai(APIView):
|
||
"""
|
||
后台编辑轮播图接口
|
||
路径: /shangpin/adlunboxg/
|
||
方法: POST
|
||
权限: JWT认证 + 管理员权限
|
||
功能: 添加或删除轮播图
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = (MultiPartParser, FormParser,JSONParser)
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 权限验证
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
if not qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败'},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
dangqianyonghu = request.user
|
||
|
||
if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败'},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
if dangqianyonghu.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败'},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
try:
|
||
guanliyuan = dangqianyonghu.AdminProfile
|
||
except AdminProfile.DoesNotExist:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败'},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 2. 获取操作类型
|
||
action = request.data.get('action', '').strip() # 'add' 或 'delete'
|
||
|
||
if action not in ['add', 'delete']:
|
||
return Response(
|
||
{'code': 400, 'message': '操作类型无效', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 3. 处理删除操作
|
||
if action == 'delete':
|
||
tupian_url = request.data.get('tupian_url', '').strip()
|
||
|
||
if not tupian_url:
|
||
return Response(
|
||
{'code': 400, 'message': '图片URL不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 查询轮播图记录
|
||
lunbo_obj = Lunbo.query.filter(ImageURL=tupian_url, ImageType=1).first()
|
||
|
||
if not lunbo_obj:
|
||
return Response(
|
||
{'code': 400, 'message': '轮播图不存在', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 从OSS删除图片
|
||
delete_success = delete_from_oss(tupian_url)
|
||
|
||
if not delete_success:
|
||
return Response(
|
||
{'code': 500, 'message': 'OSS删除失败', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
# 从数据库删除记录
|
||
lunbo_obj.delete()
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'message': '删除成功',
|
||
'data': None
|
||
})
|
||
|
||
# 4. 处理添加操作
|
||
else: # action == 'add'
|
||
tupian_file = request.FILES.get('file')
|
||
|
||
if not tupian_file:
|
||
|
||
|
||
return Response(
|
||
{'code': 400, 'message': '请上传图片', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 验证图片
|
||
is_valid, error_msg = validate_image(tupian_file)
|
||
if not is_valid:
|
||
#print(f"[轮播图上传] 图片验证失败详情: {error_msg}")
|
||
# 还可以记录文件大小、名称等信息
|
||
#print(f"[轮播图上传] 问题文件: {tupian_file.name}, Size: {tupian_file.size}")
|
||
#print(is_valid)
|
||
return Response(
|
||
{'code': 400, 'message': f'图片验证失败: {error_msg}', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 生成文件名和路径
|
||
file_extension = os.path.splitext(tupian_file.name)[1].lower()
|
||
if not file_extension:
|
||
file_extension = '.jpg'
|
||
|
||
# 生成6位随机字符串作为文件名
|
||
random_str = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6))
|
||
unique_filename = f"{random_str}{file_extension}"
|
||
|
||
# 构建OSS文件路径:a_long/lunbo/{filename}
|
||
oss_folder = 'a_long'
|
||
lunbo_folder = 'lunbo'
|
||
|
||
oss_file_path = f"{oss_folder}/{lunbo_folder}/{unique_filename}"
|
||
|
||
# 上传到OSS
|
||
tupian_url = upload_to_oss(tupian_file, oss_file_path)
|
||
|
||
if not tupian_url:
|
||
return Response(
|
||
{'code': 500, 'message': '图片上传失败', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
# 创建轮播图记录
|
||
lunbo_obj = Lunbo.query.create(
|
||
ImageURL=oss_file_path, # 存相对路径
|
||
ImageType=1
|
||
)
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'message': '添加成功',
|
||
'data': {
|
||
'tupian_url': lunbo_obj.ImageURL
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
#print(f"编辑轮播图异常: {e}")
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
|
||
|
||
|
||
class AdLeixingXiuGai(APIView):
|
||
"""
|
||
后台编辑商品类型接口 - 优化版
|
||
支持:只改图片、只改字段、两者都改
|
||
拒绝:两者都不改
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = (MultiPartParser, FormParser, JSONParser) # 添加JSONParser支持
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 权限验证(保持不变)
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
if not qianzhanghao:
|
||
return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
||
|
||
dangqianyonghu = request.user
|
||
if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao:
|
||
return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
||
|
||
if dangqianyonghu.UserType != 'admin':
|
||
return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
||
|
||
try:
|
||
guanliyuan = dangqianyonghu.AdminProfile
|
||
except AdminProfile.DoesNotExist:
|
||
return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
||
|
||
# 2. 获取商品类型ID
|
||
leixing_id = request.data.get('leixing_id')
|
||
if not leixing_id:
|
||
return Response({'code': 400, 'message': '商品类型ID不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 3. 查询商品类型
|
||
try:
|
||
leixing_obj = ShangpinLeixing.query.get(id=leixing_id)
|
||
except ShangpinLeixing.DoesNotExist:
|
||
return Response({'code': 400, 'message': '商品类型不存在', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 4. 获取字段并初始化变更标记
|
||
has_changed = False
|
||
jieshao = request.data.get('jieshao', '').strip()
|
||
yaoqiuleixing = request.data.get('yaoqiuleixing')
|
||
huiyuan_id = request.data.get('huiyuan_id', '').strip()
|
||
yongjin = request.data.get('yongjin', '').strip()
|
||
tupian_url = request.data.get('tupian_url', '').strip() # 原有图片URL
|
||
|
||
# 5. 检查非图片字段是否有变更
|
||
if jieshao and jieshao != leixing_obj.jieshao:
|
||
leixing_obj.jieshao = jieshao
|
||
has_changed = True
|
||
|
||
if yaoqiuleixing and int(yaoqiuleixing) != leixing_obj.yaoqiuleixing:
|
||
leixing_obj.yaoqiuleixing = int(yaoqiuleixing)
|
||
has_changed = True
|
||
|
||
# 根据类型清空另一个字段
|
||
if leixing_obj.yaoqiuleixing == 1:
|
||
leixing_obj.yongjin = None
|
||
if huiyuan_id:
|
||
leixing_obj.huiyuan_id = huiyuan_id
|
||
# 验证会员存在
|
||
try:
|
||
Huiyuan.query.get(huiyuan_id=huiyuan_id)
|
||
except Huiyuan.DoesNotExist:
|
||
return Response({'code': 400, 'msg': '会员ID不存在', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
else: # 类型2
|
||
leixing_obj.huiyuan_id = None
|
||
if yongjin:
|
||
try:
|
||
leixing_obj.yongjin = float(yongjin)
|
||
if leixing_obj.yongjin <= 0:
|
||
return Response({'code': 400, 'message': '佣金金额必须大于0', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
except ValueError:
|
||
return Response({'code': 400, 'message': '佣金金额格式错误', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# ===== 在现有代码的第5步后面添加 =====
|
||
|
||
# 5.5 检查抢单要求类型不变时,会员ID或佣金是否有变更
|
||
if yaoqiuleixing and int(yaoqiuleixing) == leixing_obj.yaoqiuleixing:
|
||
# 类型没有变化,但需要检查对应字段的变化
|
||
|
||
# 情况1:会员抢单 (yaoqiuleixing == 1)
|
||
if leixing_obj.yaoqiuleixing == 1:
|
||
# 检查会员ID是否变更
|
||
if huiyuan_id and huiyuan_id != leixing_obj.huiyuan_id:
|
||
# 验证会员存在
|
||
try:
|
||
Huiyuan.query.get(huiyuan_id=huiyuan_id)
|
||
except Huiyuan.DoesNotExist:
|
||
return Response({'code': 400, 'msg': '会员ID不存在', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
leixing_obj.huiyuan_id = huiyuan_id
|
||
has_changed = True
|
||
# print(f"会员ID变更: {leixing_obj.huiyuan_id} -> {huiyuan_id}")
|
||
|
||
# 情况2:佣金抢单 (yaoqiuleixing == 2)
|
||
elif leixing_obj.yaoqiuleixing == 2:
|
||
# 检查佣金是否变更
|
||
if yongjin:
|
||
try:
|
||
new_yongjin = float(yongjin)
|
||
if new_yongjin <= 0:
|
||
return Response({'code': 400, 'message': '佣金金额必须大于0', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 判断是否有变化(考虑None的情况)
|
||
current_yongjin = leixing_obj.yongjin
|
||
if (current_yongjin is None and new_yongjin != 0) or \
|
||
(current_yongjin is not None and abs(
|
||
float(current_yongjin) - new_yongjin) > 0.001):
|
||
leixing_obj.yongjin = new_yongjin
|
||
has_changed = True
|
||
# print(f"佣金变更: {current_yongjin} -> {new_yongjin}")
|
||
|
||
except ValueError:
|
||
return Response({'code': 400, 'message': '佣金金额格式错误', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# ===== 添加结束 =====
|
||
|
||
# 6. 处理图片上传(如果有新图片)
|
||
tupian_file = request.FILES.get('file')
|
||
if tupian_file:
|
||
# 验证图片
|
||
is_valid, error_msg = validate_image(tupian_file)
|
||
if not is_valid:
|
||
return Response({'code': 400, 'message': f'图片验证失败: {error_msg}', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 删除旧图片(如果存在)
|
||
if leixing_obj.tupian_url:
|
||
delete_from_oss(leixing_obj.tupian_url)
|
||
|
||
# 生成文件名和路径
|
||
file_extension = os.path.splitext(tupian_file.name)[1].lower() or '.jpg'
|
||
oss_folder = 'a_long'
|
||
leixing_folder = 'leixing'
|
||
|
||
# 使用原文件名或生成新文件名
|
||
if leixing_obj.tupian_url:
|
||
original_filename = leixing_obj.tupian_url.split('/')[-1]
|
||
else:
|
||
original_filename = f"{leixing_id}_{uuid.uuid4().hex}{file_extension}"
|
||
|
||
oss_file_path = f"{oss_folder}/{leixing_folder}/{leixing_id}/{original_filename}"
|
||
|
||
# 上传到OSS
|
||
new_tupian_url = upload_to_oss(tupian_file, oss_file_path)
|
||
if not new_tupian_url:
|
||
return Response({'code': 500, 'message': '图片上传失败', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
leixing_obj.tupian_url = oss_file_path # 存相对路径
|
||
has_changed = True
|
||
elif tupian_url and tupian_url != leixing_obj.tupian_url:
|
||
# 前端传了新URL(可能是从其他地方选择的)
|
||
leixing_obj.tupian_url = tupian_url
|
||
has_changed = True
|
||
|
||
# 7. 验证是否至少有变更
|
||
if not has_changed:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '没有检测到任何更改',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 8. 保存并返回
|
||
leixing_obj.save()
|
||
# 8. 保存商品类型
|
||
leixing_obj.save()
|
||
|
||
# 🔴 ===========【新增:同步更新关联商品】===========
|
||
try:
|
||
# 1. 准备商品更新字典
|
||
shangpin_update_fields = {}
|
||
|
||
# 始终同步抢单要求类型
|
||
shangpin_update_fields['yaoqiuleixing'] = leixing_obj.yaoqiuleixing
|
||
|
||
# 根据抢单类型,同步对应字段并清空另一个
|
||
if leixing_obj.yaoqiuleixing == 1: # 会员抢单
|
||
if leixing_obj.huiyuan_id: # 仅当会员ID非空时更新
|
||
shangpin_update_fields['huiyuan_id'] = leixing_obj.huiyuan_id
|
||
# 清空佣金字段
|
||
shangpin_update_fields['yongjin'] = None
|
||
elif leixing_obj.yaoqiuleixing == 2: # 佣金抢单
|
||
if leixing_obj.yongjin: # 仅当佣金非空时更新
|
||
shangpin_update_fields['yongjin'] = leixing_obj.yongjin
|
||
# 清空会员字段
|
||
shangpin_update_fields['huiyuan_id'] = None
|
||
|
||
# 2. 执行批量更新(确保只有实际有变化的字段才会执行)
|
||
if shangpin_update_fields:
|
||
# 查询所有属于该类型的商品(使用filter而非all以提高效率)
|
||
affected_count = Shangpin.query.filter(
|
||
leixing_id=leixing_obj.id
|
||
).update(**shangpin_update_fields)
|
||
|
||
# 可选:记录日志以便追踪
|
||
# logger.info(f"商品类型{leixing_obj.id}修改成功,已同步更新{affected_count}个关联商品")
|
||
|
||
except Exception as sync_error:
|
||
# 商品同步失败不影响商品类型本身的修改,但记录错误
|
||
# 可根据需求决定是否抛出异常回滚整个事务
|
||
# 当前设计:记录错误但允许商品类型修改成功
|
||
logger.error(f"修改十五")
|
||
|
||
# 可在此添加更详细的错误日志记录
|
||
# 🔴 ===========【新增结束】===========
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
response_data = {
|
||
'id': leixing_obj.id,
|
||
'jieshao': leixing_obj.jieshao,
|
||
'tupian_url': leixing_obj.tupian_url,
|
||
'yaoqiuleixing': leixing_obj.yaoqiuleixing,
|
||
'huiyuan_id': leixing_obj.huiyuan_id,
|
||
'yongjin': str(leixing_obj.yongjin) if leixing_obj.yongjin else None
|
||
}
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'message': '商品类型修改成功',
|
||
'data': response_data
|
||
})
|
||
|
||
except Exception as e:
|
||
print(f"编辑商品类型异常: {e}")
|
||
return Response({'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
class AdShangpinNeiRongHuoQu(APIView):
|
||
"""
|
||
后台获取商品详情接口
|
||
路径: /shangpin/adspnrhq/
|
||
方法: POST
|
||
权限: JWT认证 + 管理员权限
|
||
功能: 获取指定商品的详细信息
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 获取前端数据
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
shangpin_id = request.data.get('shangpin_id')
|
||
|
||
if not qianzhanghao or not shangpin_id:
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2. 权限验证
|
||
dangqianyonghu = request.user
|
||
|
||
# 验证phone字段匹配
|
||
if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao:
|
||
logger.warning(
|
||
f"账号不匹配: 当前用户phone={getattr(dangqianyonghu, 'Phone', '无')}, 前端账号={qianzhanghao}")
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 验证用户类型
|
||
if dangqianyonghu.UserType != 'admin':
|
||
logger.warning(f"非管理员访问: user_type={dangqianyonghu.UserType}")
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 3. 验证管理员扩展表
|
||
try:
|
||
guanliyuan = dangqianyonghu.AdminProfile
|
||
except AdminProfile.DoesNotExist:
|
||
logger.warning(f"管理员扩展表不存在: user_id={dangqianyonghu.id}")
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 查询商品数据
|
||
try:
|
||
shangpin_obj = Shangpin.query.get(id=shangpin_id)
|
||
except Shangpin.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '商品不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 5. 构建返回数据(与前端字段对应)
|
||
response_data = {
|
||
'tupian_url': shangpin_obj.tupian_url or '', # 商品图片相对URL
|
||
'biaoti': shangpin_obj.biaoqian or '', # 商品标题(前端用biaoti,对应biaoqian)
|
||
'jieshao': shangpin_obj.jieshao or '', # 商品介绍
|
||
'jiage': str(shangpin_obj.jiage) if shangpin_obj.jiage else '0.00', # 商品价格
|
||
'kucun': shangpin_obj.kucun, # 商品库存
|
||
'zhenshixiaoliang': shangpin_obj.zhenshi_xiaoliang, # 商品真实销量
|
||
'duiwaixiaoliang': shangpin_obj.duiwai_xiaoliang, # 商品对外销量
|
||
'xiadanxuzhi': shangpin_obj.xiadan_xuzhi or '', # 下单须知
|
||
'guizetupian_url': shangpin_obj.guize_tupian or '', # 规则图片相对URL
|
||
'yajin': str(shangpin_obj.yajin) if hasattr(shangpin_obj, 'yajin') and shangpin_obj.yajin else None,
|
||
# 押金(可能不存在)
|
||
'chuangjianshijian': shangpin_obj.CreateTime.strftime(
|
||
'%Y-%m-%d %H:%M:%S') if shangpin_obj.CreateTime else '',
|
||
'genggaishijian': shangpin_obj.UpdateTime.strftime(
|
||
'%Y-%m-%d %H:%M:%S') if shangpin_obj.UpdateTime else '',
|
||
# 额外字段,前端可能不需要但可以保留
|
||
'leixing_id': shangpin_obj.leixing_id,
|
||
'zhuanqu_id': shangpin_obj.zhuanqu_id,
|
||
'yaoqiuleixing': shangpin_obj.yaoqiuleixing,
|
||
'huiyuan_id': shangpin_obj.huiyuan_id,
|
||
'yongjin': str(shangpin_obj.yongjin) if shangpin_obj.yongjin else None,
|
||
}
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'message': '获取成功',
|
||
'data': response_data
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取商品详情异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class AdShangpinXiuGai(APIView):
|
||
"""
|
||
后台更改商品数据接口 - 修复版
|
||
修复:1. 每次上传生成新文件名 2. 解决浏览器缓存问题
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = (MultiPartParser, FormParser, JSONParser)
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 权限验证
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
shangpin_id = request.data.get('shangpin_id')
|
||
|
||
if not qianzhanghao or not shangpin_id:
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 验证身份
|
||
dangqianyonghu = request.user
|
||
|
||
if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
if dangqianyonghu.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
try:
|
||
guanliyuan = dangqianyonghu.AdminProfile
|
||
except AdminProfile.DoesNotExist:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 2. 判断操作类型:删除还是修改
|
||
action = request.data.get('action', 'update').strip().lower()
|
||
|
||
if action == 'delete':
|
||
return self._delete_shangpin(shangpin_id)
|
||
else:
|
||
return self._update_shangpin(request, shangpin_id)
|
||
|
||
except Exception as e:
|
||
logger.error(f"更改商品数据异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
def _delete_shangpin(self, shangpin_id):
|
||
"""删除商品逻辑"""
|
||
try:
|
||
# 查询商品
|
||
try:
|
||
shangpin_obj = Shangpin.query.get(id=shangpin_id)
|
||
except Shangpin.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '商品不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 获取图片URL(如果有)
|
||
tupian_url = shangpin_obj.tupian_url
|
||
guizetupian_url = shangpin_obj.guize_tupian
|
||
|
||
# 异步或同步删除OSS图片
|
||
if tupian_url:
|
||
try:
|
||
delete_from_oss(tupian_url)
|
||
except Exception as e:
|
||
logger.warning(f"删除商品图片失败: {tupian_url}, 错误: {e}")
|
||
|
||
if guizetupian_url:
|
||
try:
|
||
delete_from_oss(guizetupian_url)
|
||
except Exception as e:
|
||
logger.warning(f"删除规则图片失败: {guizetupian_url}, 错误: {e}")
|
||
|
||
# 删除数据库记录
|
||
shangpin_obj.delete()
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'message': '删除成功',
|
||
'data': {
|
||
'shangpin_id': shangpin_id,
|
||
'deleted': True
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"删除商品异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '删除失败', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
def _update_shangpin(self, request, shangpin_id):
|
||
"""修改商品逻辑 - 修复:每次上传生成新文件名"""
|
||
try:
|
||
# 查询商品
|
||
try:
|
||
shangpin_obj = Shangpin.query.get(id=shangpin_id)
|
||
except Shangpin.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '商品不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
has_changes = False
|
||
|
||
# 获取原图片URL(如果有)
|
||
old_tupian_url = shangpin_obj.tupian_url
|
||
|
||
# 3. 处理商品图片上传(如果有新商品图片)
|
||
tupian_file = request.FILES.get('tupian_file')
|
||
new_tupian_url = None
|
||
|
||
if tupian_file:
|
||
# 验证图片
|
||
is_valid, error_msg = validate_image(tupian_file)
|
||
if not is_valid:
|
||
return Response(
|
||
{'code': 400, 'message': f'图片验证失败: {error_msg}', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 生成新文件名:时间戳 + 随机数 + 原扩展名
|
||
timestamp = int(time.time() * 1000) % 100000000 # 8位时间戳
|
||
random_num = random.randint(1000, 9999)
|
||
file_extension = os.path.splitext(tupian_file.name)[1].lower() or '.jpg'
|
||
new_filename = f"sp_{timestamp}_{random_num}{file_extension}"
|
||
|
||
# 确定OSS路径
|
||
oss_folder = 'a_long'
|
||
shangpin_folder = 'shangpin'
|
||
oss_file_path = f"{oss_folder}/{shangpin_folder}/{new_filename}"
|
||
|
||
# 删除旧图片(如果存在)
|
||
if old_tupian_url:
|
||
try:
|
||
delete_from_oss(old_tupian_url)
|
||
logger.info(f"已删除旧商品图片: {old_tupian_url}")
|
||
except Exception as e:
|
||
logger.warning(f"删除旧商品图片失败: {old_tupian_url}, 错误: {e}")
|
||
# 继续上传新图片,不中断流程
|
||
|
||
# 上传到OSS
|
||
new_tupian_url = upload_to_oss(tupian_file, oss_file_path)
|
||
|
||
if not new_tupian_url:
|
||
return Response(
|
||
{'code': 500, 'message': '商品图片上传失败', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
# 更新数据库中的图片URL
|
||
shangpin_obj.tupian_url = oss_file_path # 存相对路径
|
||
has_changes = True
|
||
logger.info(f"商品图片更新成功: {oss_file_path}")
|
||
|
||
# 4. 处理其他字段(只更新有变化的字段)
|
||
# 注意:这里同时处理 request.data(文本字段)和可能有文件的情况
|
||
field_mapping = {
|
||
'biaoti': 'biaoqian',
|
||
'jieshao': 'jieshao',
|
||
'jiage': 'jiage',
|
||
'kucun': 'kucun',
|
||
'duiwaixiaoliang': 'duiwai_xiaoliang',
|
||
'xiadanxuzhi': 'xiadan_xuzhi',
|
||
'yajin': 'yajin',
|
||
}
|
||
|
||
for front_field, db_field in field_mapping.items():
|
||
# 注意:request.data 可能包含文件和其他字段,但DRF会处理好
|
||
if front_field in request.data:
|
||
new_value = request.data.get(front_field)
|
||
|
||
# 空字符串处理
|
||
if new_value == '':
|
||
new_value = None
|
||
|
||
if new_value is not None:
|
||
# 获取当前值
|
||
current_value = getattr(shangpin_obj, db_field)
|
||
|
||
# 类型转换
|
||
try:
|
||
if db_field in ['jiage', 'yajin']:
|
||
# 价格和押金
|
||
if new_value:
|
||
new_value = Decimal(str(new_value))
|
||
else:
|
||
new_value = None
|
||
elif db_field in ['kucun', 'duiwai_xiaoliang']:
|
||
# 库存和对外销量
|
||
if new_value:
|
||
new_value = int(float(new_value))
|
||
else:
|
||
new_value = 0
|
||
except (ValueError, TypeError, InvalidOperation) as e:
|
||
logger.warning(f"字段{front_field}转换失败: {new_value}, 错误: {e}")
|
||
continue
|
||
|
||
# 检查是否有变化(处理None值)
|
||
if current_value != new_value:
|
||
setattr(shangpin_obj, db_field, new_value)
|
||
has_changes = True
|
||
|
||
# 5. 如果没有检测到任何更改
|
||
if not has_changes:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '没有检测到任何更改',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 6. 保存更改
|
||
shangpin_obj.save()
|
||
|
||
# 7. 返回更新后的数据
|
||
response_data = {
|
||
'tupian_url': shangpin_obj.tupian_url or '',
|
||
'biaoti': shangpin_obj.biaoqian or '',
|
||
'jieshao': shangpin_obj.jieshao or '',
|
||
'jiage': str(shangpin_obj.jiage) if shangpin_obj.jiage else '0.00',
|
||
'kucun': shangpin_obj.kucun,
|
||
'duiwaixiaoliang': shangpin_obj.duiwai_xiaoliang,
|
||
'xiadanxuzhi': shangpin_obj.xiadan_xuzhi or '',
|
||
'yajin': str(shangpin_obj.yongjin) if shangpin_obj.yongjin else None,
|
||
'genggaishijian': shangpin_obj.UpdateTime.strftime(
|
||
'%Y-%m-%d %H:%M:%S') if shangpin_obj.UpdateTime else '',
|
||
}
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'message': '更新成功',
|
||
'data': response_data
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"更新商品异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '更新失败', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
class AdShangpinGuizeXiuGai(APIView):
|
||
"""
|
||
后台规则图片更换接口 - 修复版
|
||
修复:每次上传生成新文件名,避免浏览器缓存
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = (MultiPartParser, FormParser, JSONParser)
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 权限验证
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
shangpin_id = request.data.get('shangpin_id')
|
||
action = request.data.get('action', 'update').strip().lower()
|
||
|
||
if not qianzhanghao or not shangpin_id:
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 验证身份
|
||
dangqianyonghu = request.user
|
||
|
||
if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
if dangqianyonghu.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
try:
|
||
guanliyuan = dangqianyonghu.AdminProfile
|
||
except AdminProfile.DoesNotExist:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 2. 查询商品
|
||
try:
|
||
shangpin_obj = Shangpin.query.get(id=shangpin_id)
|
||
except Shangpin.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '商品不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 3. 处理不同操作
|
||
if action == 'delete':
|
||
return self._delete_guize_image(shangpin_obj)
|
||
elif action == 'update':
|
||
return self._update_guize_image(request, shangpin_obj)
|
||
else:
|
||
return Response(
|
||
{'code': 400, 'message': '操作类型无效', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"规则图片操作异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
def _delete_guize_image(self, shangpin_obj):
|
||
"""删除规则图片"""
|
||
guizetupian_url = shangpin_obj.guize_tupian
|
||
|
||
if guizetupian_url:
|
||
# 从OSS删除
|
||
try:
|
||
delete_success = delete_from_oss(guizetupian_url)
|
||
'''if delete_success:
|
||
logger.info(f"已删除规则图片: {guizetupian_url}")
|
||
else:
|
||
logger.warning(f"OSS删除失败,但继续删除数据库记录: {guizetupian_url}")'''
|
||
except Exception as e:
|
||
logger.warning(f"删除规则图片异常: {e}")
|
||
|
||
# 清除数据库记录
|
||
shangpin_obj.guize_tupian = ''
|
||
shangpin_obj.save()
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'message': '删除成功',
|
||
'data': {
|
||
'guizetupian_url': ''
|
||
}
|
||
})
|
||
|
||
def _update_guize_image(self, request, shangpin_obj):
|
||
"""更新规则图片 - 修复:每次上传生成新文件名"""
|
||
guize_file = request.FILES.get('guize_file')
|
||
|
||
if not guize_file:
|
||
return Response(
|
||
{'code': 400, 'message': '请上传规则图片', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 验证图片
|
||
is_valid, error_msg = validate_image(guize_file)
|
||
if not is_valid:
|
||
return Response(
|
||
{'code': 400, 'message': f'图片验证失败: {error_msg}', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 获取原规则图片URL
|
||
old_guizetupian_url = shangpin_obj.guize_tupian
|
||
|
||
# 生成新文件名:时间戳 + 随机数 + 原扩展名
|
||
timestamp = int(time.time() * 1000) % 100000000 # 8位时间戳
|
||
random_num = random.randint(1000, 9999)
|
||
file_extension = os.path.splitext(guize_file.name)[1].lower() or '.jpg'
|
||
new_filename = f"gz_{timestamp}_{random_num}{file_extension}"
|
||
|
||
# 确定OSS路径
|
||
oss_folder = 'a_long'
|
||
guize_folder = 'guize' # 修改为你的要求:a_long/guize/
|
||
oss_file_path = f"{oss_folder}/{guize_folder}/{new_filename}"
|
||
|
||
# 删除旧图片(如果存在)
|
||
if old_guizetupian_url:
|
||
try:
|
||
delete_from_oss(old_guizetupian_url)
|
||
#logger.info(f"已删除旧规则图片: {old_guizetupian_url}")
|
||
except Exception as e:
|
||
logger.warning(f"删除旧规则图片失败: {old_guizetupian_url}, 错误: {e}")
|
||
# 继续上传新图片,不中断流程
|
||
|
||
# 上传到OSS
|
||
new_guizetupian_url = upload_to_oss(guize_file, oss_file_path)
|
||
|
||
if not new_guizetupian_url:
|
||
return Response(
|
||
{'code': 500, 'message': '规则图片上传失败', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
# 更新数据库(存相对路径)
|
||
shangpin_obj.guize_tupian = oss_file_path
|
||
shangpin_obj.save()
|
||
|
||
#logger.info(f"规则图片更新成功: {oss_file_path}")
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'message': '规则图片更新成功',
|
||
'data': {
|
||
'guizetupian_url': oss_file_path # 返回相对路径
|
||
}
|
||
})
|
||
|
||
|
||
|
||
|
||
# views.py
|
||
|
||
|