1
0
forked from erp-dev/erp

feat: sse && multi_merchant completed

This commit is contained in:
2025-11-11 10:56:20 +08:00
parent b2078dfa46
commit 2aafb93aad
43 changed files with 2445 additions and 244 deletions

View File

@@ -0,0 +1,185 @@
"""
产品图片上传视图
处理前后端分离场景下的图片上传
"""
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: 产品 IDURL 路径参数)
- 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: 产品 IDURL 路径参数)
"""
# 检查用户权限
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
)