operations_project/apps/gmail/views.py
2025-05-13 11:58:17 +08:00

599 lines
23 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.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework import status
from .serializers import GmailCredentialSerializer
from .services.gmail_service import GmailService
from .models import GmailCredential, GmailConversation, GmailAttachment
from django.shortcuts import get_object_or_404
import logging
import os
from django.conf import settings
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
# 配置日志记录器,用于记录视图操作的调试、警告和错误信息
logger = logging.getLogger(__name__)
class GmailAuthInitiateView(APIView):
"""
API 视图,用于启动 Gmail OAuth2 认证流程。
"""
permission_classes = [IsAuthenticated] # 限制访问,仅允许已认证用户
def post(self, request):
"""
处理 POST 请求,启动 Gmail OAuth2 认证并返回授权 URL。
Args:
request: Django REST Framework 请求对象,包含客户端密钥 JSON 数据。
Returns:
Response: 包含授权 URL 的 JSON 响应(成功时),或错误信息(失败时)。
Status Codes:
200: 成功生成授权 URL。
400: 请求数据无效。
500: 服务器内部错误(如认证服务失败)。
"""
logger.debug(f"Received auth initiate request: {request.data}")
serializer = GmailCredentialSerializer(data=request.data, context={'request': request})
if serializer.is_valid():
try:
# 从请求数据中提取客户端密钥 JSON
client_secret_json = serializer.validated_data['client_secret_json']
# 调用 GmailService 生成授权 URL
auth_url = GmailService.initiate_authentication(request.user, client_secret_json)
logger.info(f"Generated auth URL for user {request.user.id}")
return Response({'auth_url': auth_url}, status=status.HTTP_200_OK)
except Exception as e:
# 记录错误并返回服务器错误响应
logger.error(f"Error initiating authentication for user {request.user.id}: {str(e)}")
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# 记录无效请求数据并返回错误响应
logger.warning(f"Invalid request data: {serializer.errors}")
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class GmailAuthCompleteView(APIView):
"""
API 视图,用于完成 Gmail OAuth2 认证流程。
"""
permission_classes = [IsAuthenticated] # 限制访问,仅允许已认证用户
def post(self, request):
"""
处理 POST 请求,使用授权代码完成 Gmail OAuth2 认证并保存凭证。
Args:
request: Django REST Framework 请求对象,包含授权代码和客户端密钥 JSON。
Returns:
Response: 包含已保存凭证数据的 JSON 响应(成功时),或错误信息(失败时)。
Status Codes:
201: 成功保存凭证。
400: 请求数据无效。
500: 服务器内部错误(如认证失败)。
"""
logger.debug(f"Received auth complete request: {request.data}")
serializer = GmailCredentialSerializer(data=request.data, context={'request': request})
if serializer.is_valid():
try:
# 提取授权代码和客户端密钥 JSON
auth_code = serializer.validated_data['auth_code']
client_secret_json = serializer.validated_data['client_secret_json']
# 完成认证并保存凭证
credential = GmailService.complete_authentication(request.user, auth_code, client_secret_json)
# 序列化凭证数据以返回
serializer = GmailCredentialSerializer(credential, context={'request': request})
logger.info(f"Authentication completed for user {request.user.id}, email: {credential.email}")
return Response(serializer.data, status=status.HTTP_201_CREATED)
except Exception as e:
# 记录错误并返回服务器错误响应
logger.error(f"Error completing authentication for user {request.user.id}: {str(e)}")
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# 记录无效请求数据并返回错误响应
logger.warning(f"Invalid request data: {serializer.errors}")
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class GmailCredentialListView(APIView):
"""
API 视图,用于列出用户的所有 Gmail 凭证。
"""
permission_classes = [IsAuthenticated] # 限制访问,仅允许已认证用户
def get(self, request):
"""
处理 GET 请求,返回用户的所有 Gmail 凭证列表。
Args:
request: Django REST Framework 请求对象。
Returns:
Response: 包含凭证列表的 JSON 响应。
Status Codes:
200: 成功返回凭证列表。
"""
# 获取用户关联的所有 Gmail 凭证
credentials = request.user.gmail_credentials.all()
# 序列化凭证数据
serializer = GmailCredentialSerializer(credentials, many=True, context={'request': request})
return Response(serializer.data, status=status.HTTP_200_OK)
class GmailCredentialDetailView(APIView):
"""
API 视图,用于管理特定 Gmail 凭证的获取、更新和删除。
"""
permission_classes = [IsAuthenticated] # 限制访问,仅允许已认证用户
def get(self, request, pk):
"""
处理 GET 请求,获取特定 Gmail 凭证的详细信息。
Args:
request: Django REST Framework 请求对象。
pk: 凭证的主键 ID。
Returns:
Response: 包含凭证详细信息的 JSON 响应。
Status Codes:
200: 成功返回凭证信息。
404: 未找到指定凭证。
"""
# 获取用户拥有的指定凭证,未找到则返回 404
credential = get_object_or_404(GmailCredential, pk=pk, user=request.user)
serializer = GmailCredentialSerializer(credential, context={'request': request})
return Response(serializer.data, status=status.HTTP_200_OK)
def patch(self, request, pk):
"""
处理 PATCH 请求,更新特定 Gmail 凭证(如设置为默认凭证)。
Args:
request: Django REST Framework 请求对象,包含更新数据。
pk: 凭证的主键 ID。
Returns:
Response: 包含更新后凭证数据的 JSON 响应,或错误信息。
Status Codes:
200: 成功更新凭证。
400: 请求数据无效。
404: 未找到指定凭证。
"""
# 获取用户拥有的指定凭证
credential = get_object_or_404(GmailCredential, pk=pk, user=request.user)
serializer = GmailCredentialSerializer(credential, data=request.data, partial=True, context={'request': request})
if serializer.is_valid():
# 如果设置为默认凭证,清除其他凭证的默认状态
if serializer.validated_data.get('is_default', False):
GmailCredential.objects.filter(user=request.user).exclude(id=credential.id).update(is_default=False)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
# 返回无效数据错误
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
"""
处理 DELETE 请求,删除特定 Gmail 凭证。
Args:
request: Django REST Framework 请求对象。
pk: 凭证的主键 ID。
Returns:
Response: 空响应,表示删除成功。
Status Codes:
204: 成功删除凭证。
404: 未找到指定凭证。
"""
# 获取并删除用户拥有的指定凭证
credential = get_object_or_404(GmailCredential, pk=pk, user=request.user)
credential.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class GmailConversationView(APIView):
"""
API视图用于获取和保存Gmail对话。
"""
permission_classes = [IsAuthenticated] # 限制访问,仅允许已认证用户
def post(self, request):
"""
处理POST请求获取Gmail对话并保存到聊天历史。
请求参数:
user_email: 用户Gmail邮箱
influencer_email: 达人Gmail邮箱
kb_id: [可选] 知识库ID不提供则使用默认知识库
返回:
conversation_id: 创建的会话ID
"""
try:
# 验证必填参数
user_email = request.data.get('user_email')
influencer_email = request.data.get('influencer_email')
if not user_email or not influencer_email:
return Response({
'code': 400,
'message': '缺少必填参数: user_email 或 influencer_email',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 可选参数
kb_id = request.data.get('kb_id')
# 调用服务保存对话
conversation_id, error = GmailService.save_conversations_to_chat(
request.user,
user_email,
influencer_email,
kb_id
)
if error:
return Response({
'code': 400,
'message': error,
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
return Response({
'code': 200,
'message': '获取Gmail对话成功',
'data': {
'conversation_id': conversation_id
}
})
except Exception as e:
logger.error(f"获取Gmail对话失败: {str(e)}")
return Response({
'code': 500,
'message': f'获取Gmail对话失败: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def get(self, request):
"""
处理GET请求获取用户的Gmail对话列表。
"""
try:
conversations = GmailConversation.objects.filter(user=request.user, is_active=True)
data = []
for conversation in conversations:
# 获取附件计数
attachments_count = GmailAttachment.objects.filter(
conversation=conversation
).count()
data.append({
'id': str(conversation.id),
'conversation_id': conversation.conversation_id,
'user_email': conversation.user_email,
'influencer_email': conversation.influencer_email,
'title': conversation.title,
'last_sync_time': conversation.last_sync_time.strftime('%Y-%m-%d %H:%M:%S'),
'created_at': conversation.created_at.strftime('%Y-%m-%d %H:%M:%S'),
'attachments_count': attachments_count
})
return Response({
'code': 200,
'message': '获取对话列表成功',
'data': data
})
except Exception as e:
logger.error(f"获取对话列表失败: {str(e)}")
return Response({
'code': 500,
'message': f'获取对话列表失败: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class GmailAttachmentListView(APIView):
"""
API视图用于获取Gmail附件列表。
"""
permission_classes = [IsAuthenticated] # 限制访问,仅允许已认证用户
def get(self, request, conversation_id=None):
"""
处理GET请求获取指定对话的附件列表。
"""
try:
if conversation_id:
# 获取指定对话的附件
conversation = get_object_or_404(GmailConversation, conversation_id=conversation_id, user=request.user)
attachments = GmailAttachment.objects.filter(conversation=conversation)
else:
# 获取用户的所有附件
conversations = GmailConversation.objects.filter(user=request.user, is_active=True)
attachments = GmailAttachment.objects.filter(conversation__in=conversations)
data = []
for attachment in attachments:
data.append({
'id': str(attachment.id),
'conversation_id': attachment.conversation.conversation_id,
'filename': attachment.filename,
'content_type': attachment.content_type,
'size': attachment.size,
'sender_email': attachment.sender_email,
'created_at': attachment.created_at.strftime('%Y-%m-%d %H:%M:%S'),
'url': attachment.get_absolute_url()
})
return Response({
'code': 200,
'message': '获取附件列表成功',
'data': data
})
except Exception as e:
logger.error(f"获取附件列表失败: {str(e)}")
return Response({
'code': 500,
'message': f'获取附件列表失败: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class GmailPubSubView(APIView):
"""
API视图用于设置Gmail的Pub/Sub实时通知。
"""
permission_classes = [IsAuthenticated] # 限制访问,仅允许已认证用户
def post(self, request):
"""
处理POST请求为用户的Gmail账户设置Pub/Sub推送通知。
Args:
request: Django REST Framework请求对象包含Gmail邮箱信息。
Returns:
Response: 设置结果的JSON响应。
Status Codes:
200: 成功设置Pub/Sub通知。
400: 请求数据无效。
404: 未找到指定Gmail凭证。
500: 服务器内部错误。
"""
try:
# 获取请求参数
email = request.data.get('email')
if not email:
return Response({'error': '必须提供Gmail邮箱地址'}, status=status.HTTP_400_BAD_REQUEST)
# 检查用户是否有此Gmail账户的凭证
credential = GmailCredential.objects.filter(user=request.user, email=email).first()
if not credential:
return Response({'error': f'未找到{email}的授权信息'}, status=status.HTTP_404_NOT_FOUND)
# 设置Pub/Sub通知
success, error = GmailService.setup_gmail_push_notification(request.user, email)
if success:
return Response({'message': f'已成功为{email}设置实时通知'}, status=status.HTTP_200_OK)
else:
return Response({'error': error}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
except Exception as e:
logger.error(f"设置Gmail Pub/Sub通知失败: {str(e)}")
return Response({'error': f'设置Gmail实时通知失败: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def get(self, request):
"""
处理GET请求获取用户所有已设置Pub/Sub通知的Gmail账户。
这个方法目前仅返回用户的所有Gmail凭证未来可以扩展为返回推送通知的详细状态。
Args:
request: Django REST Framework请求对象。
Returns:
Response: 包含Gmail账户列表的JSON响应。
Status Codes:
200: 成功返回账户列表。
"""
# 获取用户所有Gmail凭证
credentials = request.user.gmail_credentials.filter(is_valid=True)
# 构建响应数据
accounts = []
for cred in credentials:
accounts.append({
'id': cred.id,
'email': cred.email,
'is_default': cred.is_default
})
return Response({'accounts': accounts}, status=status.HTTP_200_OK)
class GmailNotificationStartView(APIView):
"""
API视图用于启动Gmail Pub/Sub监听器。
通常由系统管理员或后台任务调用,而非普通用户。
"""
permission_classes = [IsAuthenticated] # 可根据需要更改为更严格的权限
def post(self, request):
"""
处理POST请求启动Gmail Pub/Sub监听器。
Args:
request: Django REST Framework请求对象。
Returns:
Response: 启动结果的JSON响应。
Status Codes:
200: 成功启动监听器。
500: 服务器内部错误。
"""
try:
# 可选指定要监听的用户ID
user_id = request.data.get('user_id')
# 在后台线程中启动监听器
thread = GmailService.start_pubsub_listener_thread(user_id)
return Response({'message': '已成功启动Gmail实时通知监听器'}, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f"启动Gmail Pub/Sub监听器失败: {str(e)}")
return Response({'error': f'启动Gmail实时通知监听器失败: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class GmailSendEmailView(APIView):
"""
API视图用于发送Gmail邮件支持附件
"""
permission_classes = [IsAuthenticated] # 限制访问,仅允许已认证用户
def post(self, request):
"""
处理POST请求发送Gmail邮件。
请求应包含以下字段:
- email: 发件人Gmail邮箱
- to: 收件人邮箱
- subject: 邮件主题
- body: 邮件正文
- attachments: 附件文件IDs列表 (可选)
Args:
request: Django REST Framework请求对象。
Returns:
Response: 发送结果的JSON响应。
Status Codes:
200: 成功发送邮件。
400: 请求数据无效。
404: 未找到Gmail凭证。
500: 服务器内部错误。
"""
try:
# 获取请求参数
user_email = request.data.get('email')
to_email = request.data.get('to')
subject = request.data.get('subject')
body = request.data.get('body')
attachment_ids = request.data.get('attachments', [])
# 验证必填字段
if not all([user_email, to_email, subject]):
return Response({
'error': '缺少必要参数请提供email、to和subject字段'
}, status=status.HTTP_400_BAD_REQUEST)
# 检查是否有此Gmail账户的凭证
credential = GmailCredential.objects.filter(
user=request.user,
email=user_email,
is_valid=True
).first()
if not credential:
return Response({
'error': f'未找到{user_email}的有效授权信息'
}, status=status.HTTP_404_NOT_FOUND)
# 处理附件
attachments = []
if attachment_ids and isinstance(attachment_ids, list):
for file_id in attachment_ids:
# 查找已上传的文件
file_obj = request.FILES.get(f'file_{file_id}')
if file_obj:
# 保存临时文件
tmp_path = os.path.join(settings.MEDIA_ROOT, 'tmp', f'{file_id}_{file_obj.name}')
os.makedirs(os.path.dirname(tmp_path), exist_ok=True)
with open(tmp_path, 'wb+') as destination:
for chunk in file_obj.chunks():
destination.write(chunk)
attachments.append({
'path': tmp_path,
'filename': file_obj.name
})
else:
# 检查是否为已有的Gmail附件ID
try:
attachment = GmailAttachment.objects.get(id=file_id)
if attachment.conversation.user_id == request.user.id:
attachments.append({
'path': attachment.file_path,
'filename': attachment.filename
})
except (GmailAttachment.DoesNotExist, ValueError):
logger.warning(f"无法找到附件: {file_id}")
# 发送邮件
success, result = GmailService.send_email(
request.user,
user_email,
to_email,
subject,
body or '',
attachments
)
if success:
return Response({
'message': '邮件发送成功',
'message_id': result
}, status=status.HTTP_200_OK)
else:
return Response({
'error': result
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
except Exception as e:
logger.error(f"发送Gmail邮件失败: {str(e)}")
return Response({
'error': f'发送Gmail邮件失败: {str(e)}'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def get(self, request):
"""
处理GET请求获取用户可用于发送邮件的Gmail账户列表。
Args:
request: Django REST Framework请求对象。
Returns:
Response: 包含Gmail账户列表的JSON响应。
Status Codes:
200: 成功返回账户列表。
"""
# 获取用户所有可用Gmail凭证
credentials = request.user.gmail_credentials.filter(is_valid=True)
# 构建响应数据
accounts = []
for cred in credentials:
accounts.append({
'id': cred.id,
'email': cred.email,
'is_default': cred.is_default
})
return Response({'accounts': accounts}, status=status.HTTP_200_OK)