""" 产品图片上传视图 处理前后端分离场景下的图片上传 """ from rest_framework.views import APIView from rest_framework.parsers import MultiPartParser, FormParser from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from rest_framework import status from basic_info.models import Product import logging logger = logging.getLogger(__name__) class ProductImageUploadView(APIView): """ 产品图片上传接口 支持两种方式: 1. 直接上传图片文件(multipart/form-data) 2. 更新已有产品的图片 """ permission_classes = [IsAuthenticated] parser_classes = [MultiPartParser, FormParser] def post(self, request, product_id): """ 上传/更新产品图片 请求参数: - product_id: 产品 ID(URL 路径参数) - image: 图片文件(multipart/form-data) 前端使用示例: ```javascript const formData = new FormData(); formData.append('image', file); fetch('/api/v1/products/{id}/image/', { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, body: formData }); ``` """ # 检查用户权限 if not hasattr(request.user, 'employee'): return Response( {'error': '用户无员工信息,无权操作'}, status=status.HTTP_403_FORBIDDEN ) merchant_id = request.user.employee.merchant_id # 获取产品 try: product = Product.objects.get(id=product_id, merchant_id=merchant_id) except Product.DoesNotExist: return Response( {'error': f'产品 ID {product_id} 不存在或不属于当前商户'}, status=status.HTTP_404_NOT_FOUND ) # 获取上传的图片 image_file = request.FILES.get('image') if not image_file: return Response( {'error': '请提供图片文件(字段名:image)'}, status=status.HTTP_400_BAD_REQUEST ) # 验证文件类型 allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'] if image_file.content_type not in allowed_types: return Response( { 'error': f'不支持的图片格式:{image_file.content_type}', 'allowed_types': allowed_types }, status=status.HTTP_400_BAD_REQUEST ) # 验证文件大小(最大 5MB) max_size = 5 * 1024 * 1024 # 5MB if image_file.size > max_size: return Response( { 'error': f'图片大小超过限制:{image_file.size} bytes', 'max_size': f'{max_size / 1024 / 1024}MB' }, status=status.HTTP_400_BAD_REQUEST ) try: # 保存图片(会自动上传到七牛云) old_image = product.image product.image = image_file product.save(update_fields=['image', 'updated_at']) # 删除旧图片(如果存在) # 注意:七牛云的 delete 需要特殊处理,这里只是示例 if old_image: try: old_image.delete(save=False) except Exception as e: logger.warning(f"删除旧图片失败: {e}") # 构建图片 URL image_url = request.build_absolute_uri(product.image.url) logger.info( f"产品 {product_id} 图片上传成功," f"用户:{request.user.username},大小:{image_file.size} bytes" ) return Response({ 'message': '图片上传成功', 'product_id': product.id, 'image_url': image_url, 'image_name': product.image.name, 'size': image_file.size, }, status=status.HTTP_200_OK) except Exception as e: logger.error(f"图片上传失败: {str(e)}", exc_info=True) return Response( {'error': '图片上传失败', 'message': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) def delete(self, request, product_id): """ 删除产品图片 请求参数: - product_id: 产品 ID(URL 路径参数) """ # 检查用户权限 if not hasattr(request.user, 'employee'): return Response( {'error': '用户无员工信息,无权操作'}, status=status.HTTP_403_FORBIDDEN ) merchant_id = request.user.employee.merchant_id # 获取产品 try: product = Product.objects.get(id=product_id, merchant_id=merchant_id) except Product.DoesNotExist: return Response( {'error': f'产品 ID {product_id} 不存在或不属于当前商户'}, status=status.HTTP_404_NOT_FOUND ) if not product.image: return Response( {'message': '产品没有图片'}, status=status.HTTP_200_OK ) try: # 删除图片 product.image.delete(save=False) product.image = None product.save(update_fields=['image', 'updated_at']) logger.info(f"产品 {product_id} 图片已删除,用户:{request.user.username}") return Response({ 'message': '图片删除成功', 'product_id': product.id }, status=status.HTTP_200_OK) except Exception as e: logger.error(f"图片删除失败: {str(e)}", exc_info=True) return Response( {'error': '图片删除失败', 'message': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR )