Files
chunyu_project/chat/views.py
T
2026-08-05 23:59:15 +08:00

722 lines
34 KiB
Python

from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
from rest_framework.response import Response
from rest_framework import status
from django.db.models import Q
from django.utils import timezone
from django.conf import settings
from django.core.files.base import ContentFile
from datetime import timedelta
import uuid
import os
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from chunyu_project.common_schemas import success_response, error_response, unauthorized_response, not_found_response
from .models import FriendRequest, Friendship, Conversation, ConversationParticipant, ChatMessage, FavoriteSticker
from .serializers import (
FriendRequestSerializer, FriendshipSerializer,
ConversationSerializer, ChatMessageSerializer, UserBriefSerializer
)
from utils.response_codes import ResponseCode, create_standardized_response, create_standardized_error_response
from user.models import FUser
class FriendRequestListView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='获取好友请求列表',
operation_description='获取当前用户收到或发送的好友请求列表',
manual_parameters=[
openapi.Parameter('direction', openapi.IN_QUERY, description='方向: received/sent', type=openapi.TYPE_STRING),
openapi.Parameter('status', openapi.IN_QUERY, description='状态: pending/accepted/rejected', type=openapi.TYPE_STRING),
],
responses={200: success_response, 401: unauthorized_response},
)
def get(self, request):
direction = request.query_params.get('direction', 'received')
req_status = request.query_params.get('status', 'pending')
if direction == 'sent':
queryset = FriendRequest.objects.filter(from_user=request.user, status=req_status)
else:
queryset = FriendRequest.objects.filter(to_user=request.user, status=req_status)
serializer = FriendRequestSerializer(queryset, many=True, context={'request': request})
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS)
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='发送好友请求',
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'to_user_id': openapi.Schema(type=openapi.TYPE_INTEGER),
'message': openapi.Schema(type=openapi.TYPE_STRING),
},
),
responses={201: success_response, 400: error_response, 401: unauthorized_response, 404: not_found_response},
)
def post(self, request):
to_user_id = request.data.get('to_user_id')
message = request.data.get('message', '')
if not to_user_id:
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='to_user_id 不能为空', status_code=status.HTTP_400_BAD_REQUEST)
if int(to_user_id) == request.user.id:
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='不能向自己发送好友请求', status_code=status.HTTP_400_BAD_REQUEST)
try:
to_user = FUser.objects.get(pk=to_user_id)
except FUser.DoesNotExist:
return create_standardized_error_response(code=ResponseCode.NOT_FOUND, message='用户不存在', status_code=status.HTTP_404_NOT_FOUND)
if Friendship.objects.filter(
(Q(user1=request.user) & Q(user2=to_user)) | (Q(user1=to_user) & Q(user2=request.user))
).exists():
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='已经是好友关系', status_code=status.HTTP_400_BAD_REQUEST)
existing = FriendRequest.objects.filter(from_user=request.user, to_user=to_user, status='pending').first()
if existing:
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='已发送过好友请求', status_code=status.HTTP_400_BAD_REQUEST)
reverse = FriendRequest.objects.filter(from_user=to_user, to_user=request.user, status='pending').first()
if reverse:
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='对方已向你发送好友请求,请直接接受', status_code=status.HTTP_400_BAD_REQUEST)
friend_request = FriendRequest.objects.create(from_user=request.user, to_user=to_user, message=message)
serializer = FriendRequestSerializer(friend_request, context={'request': request})
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS, status_code=status.HTTP_201_CREATED)
class FriendRequestAcceptView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='接受好友请求',
operation_description='接受指定的好友请求并建立好友关系',
manual_parameters=[openapi.Parameter('pk', openapi.IN_PATH, description='好友请求ID', type=openapi.TYPE_INTEGER, required=True)],
responses={200: success_response, 401: unauthorized_response, 404: not_found_response},
)
def post(self, request, pk):
try:
friend_request = FriendRequest.objects.get(pk=pk, to_user=request.user, status='pending')
except FriendRequest.DoesNotExist:
return create_standardized_error_response(code=ResponseCode.NOT_FOUND, message='好友请求不存在', status_code=status.HTTP_404_NOT_FOUND)
friend_request.status = 'accepted'
friend_request.save()
u1, u2 = sorted([friend_request.from_user, friend_request.to_user], key=lambda u: u.id)
Friendship.objects.get_or_create(user1=u1, user2=u2)
return create_standardized_response(data={'status': 'accepted'}, code=ResponseCode.SUCCESS)
class FriendRequestRejectView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='拒绝好友请求',
operation_description='拒绝指定的好友请求',
manual_parameters=[openapi.Parameter('pk', openapi.IN_PATH, description='好友请求ID', type=openapi.TYPE_INTEGER, required=True)],
responses={200: success_response, 401: unauthorized_response, 404: not_found_response},
)
def post(self, request, pk):
try:
friend_request = FriendRequest.objects.get(pk=pk, to_user=request.user, status='pending')
except FriendRequest.DoesNotExist:
return create_standardized_error_response(code=ResponseCode.NOT_FOUND, message='好友请求不存在', status_code=status.HTTP_404_NOT_FOUND)
friend_request.status = 'rejected'
friend_request.save()
return create_standardized_response(data={'status': 'rejected'}, code=ResponseCode.SUCCESS)
class FriendRequestCancelView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='取消好友请求',
operation_description='取消自己发送的好友请求',
manual_parameters=[openapi.Parameter('pk', openapi.IN_PATH, description='好友请求ID', type=openapi.TYPE_INTEGER, required=True)],
responses={200: success_response, 401: unauthorized_response, 404: not_found_response},
)
def post(self, request, pk):
try:
friend_request = FriendRequest.objects.get(pk=pk, from_user=request.user, status='pending')
except FriendRequest.DoesNotExist:
return create_standardized_error_response(code=ResponseCode.NOT_FOUND, message='好友请求不存在', status_code=status.HTTP_404_NOT_FOUND)
friend_request.status = 'cancelled'
friend_request.save()
return create_standardized_response(data={'status': 'cancelled'}, code=ResponseCode.SUCCESS)
class FriendListView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='获取好友列表',
operation_description='获取当前用户的好友列表',
responses={200: success_response, 401: unauthorized_response},
)
def get(self, request):
friendships = Friendship.objects.filter(
Q(user1=request.user) | Q(user2=request.user)
).select_related('user1', 'user2')
serializer = FriendshipSerializer(friendships, many=True, context={'request': request})
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS)
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='删除好友',
operation_description='解除与指定用户的好友关系',
manual_parameters=[openapi.Parameter('user_id', openapi.IN_PATH, description='目标用户ID', type=openapi.TYPE_INTEGER, required=True)],
responses={204: success_response, 401: unauthorized_response, 404: not_found_response},
)
def delete(self, request, user_id):
try:
target_user = FUser.objects.get(pk=user_id)
except FUser.DoesNotExist:
return create_standardized_error_response(code=ResponseCode.NOT_FOUND, message='用户不存在', status_code=status.HTTP_404_NOT_FOUND)
u1, u2 = sorted([request.user, target_user], key=lambda u: u.id)
deleted, _ = Friendship.objects.filter(user1=u1, user2=u2).delete()
if deleted:
return create_standardized_response(code=ResponseCode.SUCCESS, status_code=status.HTTP_204_NO_CONTENT)
return create_standardized_error_response(code=ResponseCode.NOT_FOUND, message='好友关系不存在', status_code=status.HTTP_404_NOT_FOUND)
class FriendCheckView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='检查好友关系',
operation_description='检查与指定用户的好友关系及待定好友请求',
manual_parameters=[openapi.Parameter('user_id', openapi.IN_PATH, description='目标用户ID', type=openapi.TYPE_INTEGER, required=True)],
responses={200: success_response, 401: unauthorized_response, 404: not_found_response},
)
def get(self, request, user_id):
try:
target_user = FUser.objects.get(pk=user_id)
except FUser.DoesNotExist:
return create_standardized_error_response(code=ResponseCode.NOT_FOUND, message='用户不存在', status_code=status.HTTP_404_NOT_FOUND)
u1, u2 = sorted([request.user, target_user], key=lambda u: u.id)
is_friend = Friendship.objects.filter(user1=u1, user2=u2).exists()
pending_request = FriendRequest.objects.filter(
(Q(from_user=request.user, to_user=target_user) | Q(from_user=target_user, to_user=request.user)),
status='pending'
).first()
return create_standardized_response(data={
'is_friend': is_friend,
'pending_request': pending_request.id if pending_request else None,
}, code=ResponseCode.SUCCESS)
class UserSearchView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='搜索用户',
operation_description='按用户名或昵称搜索用户',
manual_parameters=[openapi.Parameter('q', openapi.IN_QUERY, description='搜索关键词', type=openapi.TYPE_STRING)],
responses={200: success_response, 401: unauthorized_response},
)
def get(self, request):
q = request.query_params.get('q', '').strip()
if not q:
return create_standardized_response(data=[], code=ResponseCode.SUCCESS)
users = FUser.objects.filter(
Q(username__icontains=q) | Q(nickname__icontains=q)
).exclude(id=request.user.id)[:20]
friend_ids = set()
friendships = Friendship.objects.filter(
Q(user1=request.user) | Q(user2=request.user)
)
for f in friendships:
friend_ids.add(f.user2_id if f.user1 == request.user else f.user1_id)
results = []
for user in users:
avatar_url = ''
if user.avatar and hasattr(user.avatar, 'url'):
avatar_url = request.build_absolute_uri(user.avatar.url)
results.append({
'id': user.id,
'username': user.username,
'nickname': getattr(user, 'nickname', '') or user.username,
'avatar': avatar_url,
'is_friend': user.id in friend_ids,
})
return create_standardized_response(data=results, code=ResponseCode.SUCCESS)
class ConversationListView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='获取会话列表',
operation_description='获取当前用户参与的所有会话',
responses={200: success_response, 401: unauthorized_response},
)
def get(self, request):
participations = ConversationParticipant.objects.filter(
user=request.user
).select_related('conversation').order_by('-conversation__created_at')
conversations = [p.conversation for p in participations]
serializer = ConversationSerializer(conversations, many=True, context={'request': request})
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS)
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='创建会话',
operation_description='与指定好友创建私聊会话',
request_body=openapi.Schema(type=openapi.TYPE_OBJECT, properties={'user_id': openapi.Schema(type=openapi.TYPE_INTEGER, description='目标用户ID')}),
responses={201: success_response, 400: error_response, 401: unauthorized_response, 404: not_found_response},
)
def post(self, request):
user_id = request.data.get('user_id')
if not user_id:
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='user_id 不能为空', status_code=status.HTTP_400_BAD_REQUEST)
if int(user_id) == request.user.id:
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='不能和自己聊天', status_code=status.HTTP_400_BAD_REQUEST)
try:
target_user = FUser.objects.get(pk=user_id)
except FUser.DoesNotExist:
return create_standardized_error_response(code=ResponseCode.NOT_FOUND, message='用户不存在', status_code=status.HTTP_404_NOT_FOUND)
u1, u2 = sorted([request.user, target_user], key=lambda u: u.id)
if not Friendship.objects.filter(user1=u1, user2=u2).exists():
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='只能与好友聊天', status_code=status.HTTP_400_BAD_REQUEST)
my_participations = ConversationParticipant.objects.filter(
user=request.user, conversation__type='private'
).values_list('conversation_id', flat=True)
existing = ConversationParticipant.objects.filter(
user=target_user, conversation_id__in=my_participations, conversation__type='private'
).first()
if existing:
conversation = existing.conversation
else:
conversation = Conversation.objects.create(type='private')
ConversationParticipant.objects.create(conversation=conversation, user=request.user)
ConversationParticipant.objects.create(conversation=conversation, user=target_user)
serializer = ConversationSerializer(conversation, context={'request': request})
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS, status_code=status.HTTP_201_CREATED)
class ConversationMessageView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='获取会话消息列表',
operation_description='分页获取指定会话的消息列表',
manual_parameters=[
openapi.Parameter('pk', openapi.IN_PATH, description='会话ID', type=openapi.TYPE_INTEGER, required=True),
openapi.Parameter('page', openapi.IN_QUERY, description='页码', type=openapi.TYPE_INTEGER),
openapi.Parameter('page_size', openapi.IN_QUERY, description='每页数量', type=openapi.TYPE_INTEGER),
],
responses={200: success_response, 401: unauthorized_response, 404: not_found_response},
)
def get(self, request, pk):
try:
conversation = Conversation.objects.get(pk=pk)
except Conversation.DoesNotExist:
return create_standardized_error_response(code=ResponseCode.NOT_FOUND, message='会话不存在', status_code=status.HTTP_404_NOT_FOUND)
if not ConversationParticipant.objects.filter(conversation=conversation, user=request.user).exists():
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='你不是该会话的参与者', status_code=status.HTTP_403_FORBIDDEN)
page = int(request.query_params.get('page', 1))
page_size = int(request.query_params.get('page_size', 50))
offset = (page - 1) * page_size
messages = ChatMessage.objects.filter(conversation=conversation).select_related('sender', 'reply_to', 'reply_to__sender')
total = messages.count()
messages = messages[offset:offset + page_size]
serializer = ChatMessageSerializer(messages, many=True, context={'request': request})
return create_standardized_response(data={
'results': serializer.data,
'total': total,
'page': page,
'page_size': page_size,
}, code=ResponseCode.SUCCESS)
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='发送会话消息',
operation_description='向指定会话发送文本消息,支持回复引用',
manual_parameters=[openapi.Parameter('pk', openapi.IN_PATH, description='会话ID', type=openapi.TYPE_INTEGER, required=True)],
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'content': openapi.Schema(type=openapi.TYPE_STRING, description='消息内容'),
'msg_type': openapi.Schema(type=openapi.TYPE_STRING, description='消息类型 text/image/file'),
'file_url': openapi.Schema(type=openapi.TYPE_STRING, description='文件URL'),
'reply_to': openapi.Schema(type=openapi.TYPE_INTEGER, description='回复的消息ID'),
},
),
responses={201: success_response, 400: error_response, 401: unauthorized_response, 404: not_found_response},
)
def post(self, request, pk):
try:
conversation = Conversation.objects.get(pk=pk)
except Conversation.DoesNotExist:
return create_standardized_error_response(code=ResponseCode.NOT_FOUND, message='会话不存在', status_code=status.HTTP_404_NOT_FOUND)
if not ConversationParticipant.objects.filter(conversation=conversation, user=request.user).exists():
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='你不是该会话的参与者', status_code=status.HTTP_403_FORBIDDEN)
content = request.data.get('content', '').strip()
if not content:
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='消息内容不能为空', status_code=status.HTTP_400_BAD_REQUEST)
msg_type = request.data.get('msg_type', 'text')
file_url = request.data.get('file_url', '')
reply_to_id = request.data.get('reply_to')
reply_to = None
if reply_to_id:
try:
reply_to = ChatMessage.objects.get(pk=reply_to_id, conversation=conversation)
except ChatMessage.DoesNotExist:
pass
message = ChatMessage.objects.create(
conversation=conversation,
sender=request.user,
content=content,
msg_type=msg_type,
file_url=file_url,
reply_to=reply_to,
)
serializer = ChatMessageSerializer(message, context={'request': request})
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS, status_code=status.HTTP_201_CREATED)
class ConversationClearView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='清空会话聊天记录',
operation_description='删除指定会话下的所有消息(仅当前用户的视图)',
manual_parameters=[openapi.Parameter('pk', openapi.IN_PATH, description='会话ID', type=openapi.TYPE_INTEGER, required=True)],
responses={200: success_response, 401: unauthorized_response, 404: not_found_response},
)
def delete(self, request, pk):
try:
conversation = Conversation.objects.get(pk=pk)
except Conversation.DoesNotExist:
return create_standardized_error_response(code=ResponseCode.NOT_FOUND, message='会话不存在', status_code=status.HTTP_404_NOT_FOUND)
if not ConversationParticipant.objects.filter(conversation=conversation, user=request.user).exists():
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='你不是该会话的参与者', status_code=status.HTTP_403_FORBIDDEN)
# 物理删除该会话下所有消息
deleted_count, _ = ChatMessage.objects.filter(conversation=conversation).delete()
return create_standardized_response(data={'deleted': deleted_count}, code=ResponseCode.SUCCESS)
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='标记会话已读',
operation_description='标记指定会话为已读状态',
manual_parameters=[openapi.Parameter('pk', openapi.IN_PATH, description='会话ID', type=openapi.TYPE_INTEGER, required=True)],
responses={200: success_response, 401: unauthorized_response, 404: not_found_response},
)
def post(self, request, pk):
try:
participant = ConversationParticipant.objects.get(conversation_id=pk, user=request.user)
except ConversationParticipant.DoesNotExist:
return create_standardized_error_response(code=ResponseCode.NOT_FOUND, message='会话不存在', status_code=status.HTTP_404_NOT_FOUND)
participant.last_read_at = timezone.now()
participant.save(update_fields=['last_read_at'])
return create_standardized_response(data={'read': True}, code=ResponseCode.SUCCESS)
class MessageRecallView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='撤回消息',
operation_description='在发送后2分钟内可撤回自己发送的消息',
manual_parameters=[openapi.Parameter('pk', openapi.IN_PATH, description='消息ID', type=openapi.TYPE_INTEGER, required=True)],
responses={200: success_response, 400: error_response, 401: unauthorized_response, 404: not_found_response},
)
def post(self, request, pk):
try:
message = ChatMessage.objects.get(pk=pk)
except ChatMessage.DoesNotExist:
return create_standardized_error_response(code=ResponseCode.NOT_FOUND, message='消息不存在', status_code=status.HTTP_404_NOT_FOUND)
if message.sender != request.user:
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='只能撤回自己发送的消息', status_code=status.HTTP_403_FORBIDDEN)
if timezone.now() - message.created_at > timedelta(minutes=2):
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='超过2分钟无法撤回', status_code=status.HTTP_400_BAD_REQUEST)
message.is_recalled = True
message.content = '你撤回了一条消息'
message.save(update_fields=['is_recalled', 'content'])
return create_standardized_response(data={'recalled': True}, code=ResponseCode.SUCCESS)
class MessageDeleteView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='删除消息',
operation_description='删除自己发送的消息(物理删除)',
manual_parameters=[openapi.Parameter('pk', openapi.IN_PATH, description='消息ID', type=openapi.TYPE_INTEGER, required=True)],
responses={200: success_response, 401: unauthorized_response, 403: error_response, 404: not_found_response},
)
def delete(self, request, pk):
try:
message = ChatMessage.objects.get(pk=pk)
except ChatMessage.DoesNotExist:
return create_standardized_error_response(code=ResponseCode.NOT_FOUND, message='消息不存在', status_code=status.HTTP_404_NOT_FOUND)
if message.sender != request.user:
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='只能删除自己发送的消息', status_code=status.HTTP_403_FORBIDDEN)
message.delete()
return create_standardized_response(data={'deleted': True}, code=ResponseCode.SUCCESS)
class FileUploadView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [MultiPartParser, FormParser]
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='上传聊天文件',
operation_description='上传聊天消息中的文件,返回可访问的URL',
manual_parameters=[openapi.Parameter('file', openapi.IN_FORM, description='文件', type=openapi.TYPE_FILE, required=True)],
responses={200: success_response, 400: error_response, 401: unauthorized_response},
)
def post(self, request):
file = request.FILES.get('file')
if not file:
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='请选择文件', status_code=status.HTTP_400_BAD_REQUEST)
ext = os.path.splitext(file.name)[1]
date_path = timezone.now().strftime('%Y/%m/%d')
filename = f'{uuid.uuid4().hex}{ext}'
filepath = f'chat_files/{date_path}/{filename}'
from django.core.files.storage import default_storage
saved_path = default_storage.save(filepath, file)
url = request.build_absolute_uri(settings.MEDIA_URL + saved_path)
return create_standardized_response(data={
'url': url,
'file_name': file.name,
'file_size': file.size,
}, code=ResponseCode.SUCCESS)
class StickerUploadView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [MultiPartParser, FormParser]
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='上传表情',
operation_description='上传图片作为个人表情收藏',
manual_parameters=[openapi.Parameter('file', openapi.IN_FORM, description='图片文件', type=openapi.TYPE_FILE, required=True)],
responses={200: success_response, 400: error_response, 401: unauthorized_response},
)
def post(self, request):
file = request.FILES.get('file')
if not file:
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='请选择文件', status_code=status.HTTP_400_BAD_REQUEST)
if not file.content_type.startswith('image/'):
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='只能上传图片文件', status_code=status.HTTP_400_BAD_REQUEST)
ext = os.path.splitext(file.name)[1] or '.png'
filename = f'{uuid.uuid4().hex}{ext}'
filepath = f'chat_stickers/{request.user.id}/{filename}'
from django.core.files.storage import default_storage
saved_path = default_storage.save(filepath, file)
sticker = FavoriteSticker.objects.create(
user=request.user,
image=saved_path,
)
url = request.build_absolute_uri(sticker.image.url)
return create_standardized_response(data={
'id': sticker.id,
'url': url,
}, code=ResponseCode.SUCCESS)
class FavoriteStickerView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [MultiPartParser, FormParser, JSONParser]
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='获取收藏表情列表',
responses={200: success_response, 401: unauthorized_response},
)
def get(self, request):
stickers = FavoriteSticker.objects.filter(user=request.user)
data = []
for s in stickers:
data.append({
'id': s.id,
'url': request.build_absolute_uri(s.image.url) if s.image else '',
})
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='添加收藏表情',
manual_parameters=[openapi.Parameter('image', openapi.IN_FORM, description='图片文件', type=openapi.TYPE_FILE, required=True)],
responses={200: success_response, 400: error_response, 401: unauthorized_response},
)
def post(self, request):
file = request.FILES.get('image')
if not file:
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='请选择图片', status_code=status.HTTP_400_BAD_REQUEST)
if not file.content_type.startswith('image/'):
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='只能上传图片', status_code=status.HTTP_400_BAD_REQUEST)
ext = os.path.splitext(file.name)[1] or '.png'
filename = f'{uuid.uuid4().hex}{ext}'
filepath = f'chat_stickers/{request.user.id}/{filename}'
from django.core.files.storage import default_storage
saved_path = default_storage.save(filepath, file)
sticker = FavoriteSticker.objects.create(
user=request.user,
image=saved_path,
)
url = request.build_absolute_uri(sticker.image.url)
return create_standardized_response(data={
'id': sticker.id,
'url': url,
}, code=ResponseCode.SUCCESS)
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='删除收藏表情',
manual_parameters=[openapi.Parameter('pk', openapi.IN_PATH, description='表情ID', type=openapi.TYPE_INTEGER, required=True)],
responses={204: success_response, 401: unauthorized_response, 404: not_found_response},
)
def delete(self, request, pk):
try:
sticker = FavoriteSticker.objects.get(pk=pk, user=request.user)
sticker.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except FavoriteSticker.DoesNotExist:
return create_standardized_error_response(code=ResponseCode.NOT_FOUND, message='收藏不存在', status_code=status.HTTP_404_NOT_FOUND)
class FavoriteStickerFromMessageView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['AI对话'],
operation_summary='从消息收藏表情',
operation_description='将消息中的图片收藏为个人表情',
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'message_id': openapi.Schema(type=openapi.TYPE_INTEGER, description='消息ID'),
},
),
responses={200: success_response, 400: error_response, 401: unauthorized_response, 404: not_found_response},
)
def post(self, request):
message_id = request.data.get('message_id')
if not message_id:
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='message_id 不能为空', status_code=status.HTTP_400_BAD_REQUEST)
try:
message = ChatMessage.objects.get(pk=message_id)
except ChatMessage.DoesNotExist:
return create_standardized_error_response(code=ResponseCode.NOT_FOUND, message='消息不存在', status_code=status.HTTP_404_NOT_FOUND)
file_url = message.file_url or message.content
if not file_url:
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='该消息没有可收藏的图片', status_code=status.HTTP_400_BAD_REQUEST)
try:
import urllib.request
if file_url.startswith('http'):
req = urllib.request.Request(file_url, headers={'User-Agent': 'Mozilla/5.0'})
response = urllib.request.urlopen(req, timeout=10)
image_data = response.read()
else:
local_path = os.path.join(settings.MEDIA_ROOT, file_url.replace(settings.MEDIA_URL, ''))
with open(local_path, 'rb') as f:
image_data = f.read()
ext = '.png'
if '.' in file_url.split('/')[-1]:
ext = '.' + file_url.split('/')[-1].split('.')[-1].split('?')[0]
filename = f'{uuid.uuid4().hex}{ext}'
filepath = f'chat_stickers/{request.user.id}/{filename}'
from django.core.files.storage import default_storage
saved_path = default_storage.save(filepath, ContentFile(image_data))
sticker = FavoriteSticker.objects.create(
user=request.user,
image=saved_path,
)
url = request.build_absolute_uri(sticker.image.url)
return create_standardized_response(data={
'id': sticker.id,
'url': url,
}, code=ResponseCode.SUCCESS)
except Exception as e:
return create_standardized_error_response(code=ResponseCode.VALIDATION_ERROR, message='保存失败', status_code=status.HTTP_400_BAD_REQUEST)