operations_project/apps/gmail/views.py
2025-05-13 18:36:06 +08:00

821 lines
31 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
import json
import base64
import threading
# 配置日志记录器,用于记录视图操作的调试、警告和错误信息
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({
'code': 200,
'message': '成功生成授权URL',
'data': {'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({
'code': 500,
'message': f'认证初始化错误:{str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# 记录无效请求数据并返回错误响应
logger.warning(f"Invalid request data: {serializer.errors}")
return Response({
'code': 400,
'message': '请求数据无效',
'data': 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({
'code': 201,
'message': '认证完成并成功保存凭证',
'data': 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({
'code': 500,
'message': f'完成认证时发生错误:{str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# 记录无效请求数据并返回错误响应
logger.warning(f"Invalid request data: {serializer.errors}")
return Response({
'code': 400,
'message': '请求数据无效',
'data': 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({
'code': 200,
'message': '成功获取凭证列表',
'data': 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({
'code': 200,
'message': '成功获取凭证详情',
'data': 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({
'code': 200,
'message': '成功更新凭证',
'data': serializer.data
}, status=status.HTTP_200_OK)
# 返回无效数据错误
return Response({
'code': 400,
'message': '请求数据无效',
'data': 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({
'code': 204,
'message': '凭证已成功删除',
'data': None
}, 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({
'code': 400,
'message': '必须提供Gmail邮箱地址',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 检查用户是否有此Gmail账户的凭证
credential = GmailCredential.objects.filter(user=request.user, email=email).first()
if not credential:
return Response({
'code': 404,
'message': f'未找到{email}的授权信息',
'data': None
}, status=status.HTTP_404_NOT_FOUND)
# 设置Pub/Sub通知
success, error = GmailService.setup_gmail_push_notification(request.user, email)
if success:
return Response({
'code': 200,
'message': f'已成功为{email}设置实时通知',
'data': None
}, status=status.HTTP_200_OK)
else:
return Response({
'code': 500,
'message': error,
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
except Exception as e:
logger.error(f"设置Gmail Pub/Sub通知失败: {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请求获取用户所有已设置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({
'code': 200,
'message': '获取账户列表成功',
'data': {'accounts': accounts}
}, status=status.HTTP_200_OK)
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({
'code': 400,
'message': '缺少必要参数请提供email、to和subject字段',
'data': None
}, 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({
'code': 404,
'message': f'未找到{user_email}的有效授权信息',
'data': None
}, 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({
'code': 200,
'message': '邮件发送成功',
'data': {'message_id': result}
}, status=status.HTTP_200_OK)
else:
return Response({
'code': 500,
'message': result,
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
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账户列表。
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({
'code': 200,
'message': '获取账户列表成功',
'data': {'accounts': accounts}
}, status=status.HTTP_200_OK)
class GmailWebhookView(APIView):
"""
API视图用于接收Gmail Pub/Sub推送通知。
这个端点不需要认证因为它由Google的Pub/Sub服务调用。
"""
permission_classes = [] # 不需要认证
def post(self, request):
"""
处理POST请求接收Gmail Pub/Sub推送通知。
Args:
request: Django REST Framework请求对象包含Pub/Sub消息。
Returns:
Response: 接收结果的JSON响应。
"""
try:
logger.info(f"收到Gmail推送通知: {request.data}")
# 解析推送消息
message = request.data.get('message', {})
data = message.get('data', '')
if not data:
return Response({
'code': 400,
'message': '无效的推送消息格式',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# Base64解码消息数据
try:
decoded_data = json.loads(base64.b64decode(data).decode('utf-8'))
logger.info(f"解码后的推送数据: {decoded_data}")
# 处理Gmail通知
email_address = decoded_data.get('emailAddress')
history_id = decoded_data.get('historyId')
if not email_address:
return Response({
'code': 400,
'message': '推送数据缺少邮箱地址',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 查找对应的Gmail凭证
credential = GmailCredential.objects.filter(email=email_address, is_valid=True).first()
if credential:
# 即使没有history_id也尝试处理因为我们现在有了备用机制
if not history_id:
logger.warning(f"推送通知中没有historyId将使用凭证中保存的历史ID")
# 启动后台任务处理新邮件
thread = threading.Thread(
target=GmailService.process_new_emails,
args=(credential.user, credential, history_id)
)
thread.daemon = True
thread.start()
else:
logger.warning(f"收到推送通知但未找到对应的Gmail凭证: {email_address}")
# 确认接收
return Response({
'code': 200,
'message': '成功接收推送通知',
'data': None
})
except Exception as e:
logger.error(f"解析推送数据失败: {str(e)}")
return Response({
'code': 400,
'message': f'解析推送数据失败: {str(e)}',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
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)
class GmailConversationSummaryView(APIView):
"""
API视图用于获取Gmail对话的总结。
"""
permission_classes = [IsAuthenticated] # 限制访问,仅允许已认证用户
def get(self, request, conversation_id=None):
"""
处理GET请求获取指定Gmail对话的总结。
Args:
request: Django REST Framework请求对象。
conversation_id: 对话ID。如果不提供则返回所有对话的摘要列表。
Returns:
Response: 包含对话总结的JSON响应。
Status Codes:
200: 成功获取对话总结。
400: 请求参数无效。
404: 未找到指定对话。
500: 服务器内部错误。
"""
try:
# 如果提供了conversation_id获取单个对话总结
if conversation_id:
# 获取对话总结
summary, error = GmailService.get_conversation_summary(request.user, conversation_id)
if error:
return Response({
'code': 400,
'message': error,
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
return Response({
'code': 200,
'message': '成功获取对话总结',
'data': {
'conversation_id': conversation_id,
'summary': summary
}
})
# 否则,获取所有对话的摘要列表
conversations = GmailConversation.objects.filter(user=request.user, is_active=True)
results = []
for conversation in conversations:
# 检查是否已经有缓存的总结
has_summary = (conversation.metadata and
'summary' in conversation.metadata and
'summary_updated_at' in conversation.metadata)
results.append({
'id': str(conversation.id),
'conversation_id': conversation.conversation_id,
'user_email': conversation.user_email,
'influencer_email': conversation.influencer_email,
'title': conversation.title,
'has_summary': has_summary,
'last_sync_time': conversation.last_sync_time.strftime('%Y-%m-%d %H:%M:%S'),
})
return Response({
'code': 200,
'message': '成功获取对话总结列表',
'data': results
})
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)