Files
chunyu_project/message/views.py
T

299 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from rest_framework import status
from adrf.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.parsers import JSONParser
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
from django.db.models import Count, Q
from asgiref.sync import sync_to_async
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 utils.response_codes import ResponseCode, create_standardized_response, create_standardized_error_response
from .models import Message, SystemMessage, SystemMessageRead
from .serializers import MessageListSerializer, SystemMessageSerializer, SystemMessageDetailSerializer
class MessagePagination(PageNumberPagination):
page_size = 20
page_size_query_param = 'page_size'
max_page_size = 100
class MessageListView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['消息'],
operation_summary='获取消息列表',
operation_description='获取当前用户的消息列表,支持按类型筛选: reply/at_me/like/system',
manual_parameters=[
openapi.Parameter('type', openapi.IN_QUERY, description='消息类型: reply/at_me/like/system', type=openapi.TYPE_STRING),
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}
)
async def get(self, request):
msg_type = request.query_params.get('type', '')
if msg_type == 'system':
queryset = SystemMessage.objects.all()
def _paginate_system():
paginator = MessagePagination()
page = paginator.paginate_queryset(queryset, request)
read_ids = set(
SystemMessageRead.objects.filter(
user=request.user,
system_message__in=page
).values_list('system_message_id', flat=True)
)
serializer = SystemMessageSerializer(page, many=True, context={'request': request, 'read_ids': read_ids})
return paginator.get_paginated_response(serializer.data).data
# 兜底:DRF 分页器与 SystemMessageSerializer 内部为同步调用
data = await sync_to_async(_paginate_system)()
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
valid_types = ['reply', 'at_me', 'like']
if msg_type and msg_type in valid_types:
queryset = Message.objects.filter(recipient=request.user, msg_type=msg_type)
else:
queryset = Message.objects.filter(recipient=request.user)
def _paginate_messages():
paginator = MessagePagination()
page = paginator.paginate_queryset(queryset, request)
serializer = MessageListSerializer(page, many=True, context={'request': request})
return paginator.get_paginated_response(serializer.data).data
# 兜底:DRF 分页器与 MessageListSerializer(obj.sender 外键访问)内部为同步调用
data = await sync_to_async(_paginate_messages)()
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
class UnreadCountView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['消息'],
operation_summary='获取未读消息数量',
operation_description='获取当前用户各类型未读消息的数量统计',
responses={200: success_response, 401: unauthorized_response}
)
async def get(self, request):
user = request.user
message_counts = Message.objects.filter(
recipient=user, is_read=False
).values('msg_type').annotate(count=Count('id'))
counts = {'reply': 0, 'at_me': 0, 'like': 0}
async for item in message_counts:
if item['msg_type'] in counts:
counts[item['msg_type']] = item['count']
read_system_ids = [rid async for rid in SystemMessageRead.objects.filter(user=user).values_list('system_message_id', flat=True)]
system_unread = await SystemMessage.objects.filter(is_global=True).exclude(id__in=read_system_ids).acount()
counts['system'] = system_unread
counts['total'] = counts['reply'] + counts['at_me'] + counts['like'] + counts['system']
return create_standardized_response(data=counts, code=ResponseCode.SUCCESS)
class MessageReadView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['消息'],
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}
)
async def post(self, request, pk):
try:
message = await Message.objects.aget(pk=pk, recipient=request.user)
message.is_read = True
await message.asave(update_fields=['is_read'])
return create_standardized_response(data={'is_read': True}, code=ResponseCode.SUCCESS)
except Message.DoesNotExist:
pass
try:
system_message = await SystemMessage.objects.aget(pk=pk)
await SystemMessageRead.objects.aget_or_create(
user=request.user,
system_message=system_message
)
return create_standardized_response(data={'is_read': True}, code=ResponseCode.SUCCESS)
except SystemMessage.DoesNotExist:
return create_standardized_error_response(
code=ResponseCode.NOT_FOUND,
message='消息不存在',
status_code=status.HTTP_404_NOT_FOUND
)
class MessageReadAllView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['消息'],
operation_summary='全部标记为已读',
operation_description='将指定类型或所有消息标记为已读',
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'type': openapi.Schema(type=openapi.TYPE_STRING, description='消息类型: reply/at_me/like/system,不传则标记全部'),
}
),
responses={200: success_response, 401: unauthorized_response}
)
async def post(self, request):
msg_type = request.data.get('type', '')
user = request.user
affected = 0
if msg_type == 'system':
system_messages = SystemMessage.objects.filter(is_global=True)
async for sm in system_messages:
_, created = await SystemMessageRead.objects.aget_or_create(
user=user,
system_message=sm
)
if created:
affected += 1
elif msg_type and msg_type in ['reply', 'at_me', 'like']:
affected = await Message.objects.filter(
recipient=user, msg_type=msg_type, is_read=False
).aupdate(is_read=True)
else:
affected = await Message.objects.filter(
recipient=user, is_read=False
).aupdate(is_read=True)
system_messages = SystemMessage.objects.filter(is_global=True)
async for sm in system_messages:
_, created = await SystemMessageRead.objects.aget_or_create(
user=user,
system_message=sm
)
if created:
affected += 1
return create_standardized_response(data={'affected': affected}, code=ResponseCode.SUCCESS)
class MessageDeleteView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['消息'],
operation_summary='删除消息',
operation_description='删除指定消息,支持普通消息和系统消息',
manual_parameters=[
openapi.Parameter('pk', openapi.IN_PATH, description='消息ID', type=openapi.TYPE_INTEGER, required=True),
],
responses={204: '删除成功', 401: unauthorized_response, 404: not_found_response}
)
async def delete(self, request, pk):
try:
message = await Message.objects.aget(pk=pk, recipient=request.user)
await message.adelete()
return Response(status=status.HTTP_204_NO_CONTENT)
except Message.DoesNotExist:
pass
try:
system_message = await SystemMessage.objects.aget(pk=pk)
await SystemMessageRead.objects.aget_or_create(
user=request.user,
system_message=system_message
)
return Response(status=status.HTTP_204_NO_CONTENT)
except SystemMessage.DoesNotExist:
return create_standardized_error_response(
code=ResponseCode.NOT_FOUND,
message='消息不存在',
status_code=status.HTTP_404_NOT_FOUND
)
class MessageClearAllView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['消息'],
operation_summary='清空所有消息',
operation_description='删除指定类型或全部消息,可选类型: reply/at_me/like/system',
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'type': openapi.Schema(type=openapi.TYPE_STRING, description='消息类型: reply/at_me/like/system,不传则清空全部'),
}
),
responses={200: success_response, 401: unauthorized_response}
)
async def delete(self, request):
msg_type = request.data.get('type', '')
user = request.user
deleted_count = 0
if msg_type == 'system':
# 删除系统消息的已读记录(相当于清空系统消息)
deleted_count = (await SystemMessageRead.objects.filter(user=user).adelete())[0]
elif msg_type and msg_type in ['reply', 'at_me', 'like']:
deleted_count = (await Message.objects.filter(recipient=user, msg_type=msg_type).adelete())[0]
else:
# 删除所有普通消息
deleted_count = (await Message.objects.filter(recipient=user).adelete())[0]
# 同时删除所有系统消息已读记录
deleted_count += (await SystemMessageRead.objects.filter(user=user).adelete())[0]
return create_standardized_response(data={'deleted': deleted_count}, code=ResponseCode.SUCCESS)
class SystemMessageDetailView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
@swagger_auto_schema(
tags=['消息'],
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}
)
async def get(self, request, pk):
try:
system_message = await SystemMessage.objects.aget(pk=pk)
except SystemMessage.DoesNotExist:
return create_standardized_error_response(
code=ResponseCode.NOT_FOUND,
message='系统消息不存在',
status_code=status.HTTP_404_NOT_FOUND
)
await SystemMessageRead.objects.aget_or_create(
user=request.user,
system_message=system_message
)
serializer = SystemMessageDetailSerializer(system_message, context={'request': request})
# 兜底:SystemMessageDetailSerializer.get_is_read 内部有同步 ORM exists() 查询
data = await sync_to_async(lambda: serializer.data)()
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)