2025-05-13 11:58:17 +08:00
|
|
|
|
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
|
2025-05-13 18:36:06 +08:00
|
|
|
|
import json
|
|
|
|
|
import base64
|
|
|
|
|
import threading
|
2025-05-07 18:01:48 +08:00
|
|
|
|
|
2025-05-13 11:58:17 +08:00
|
|
|
|
# 配置日志记录器,用于记录视图操作的调试、警告和错误信息
|
|
|
|
|
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}")
|
2025-05-13 18:36:06 +08:00
|
|
|
|
return Response({
|
|
|
|
|
'code': 200,
|
|
|
|
|
'message': '成功生成授权URL',
|
|
|
|
|
'data': {'auth_url': auth_url}
|
|
|
|
|
}, status=status.HTTP_200_OK)
|
2025-05-13 11:58:17 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
# 记录错误并返回服务器错误响应
|
|
|
|
|
logger.error(f"Error initiating authentication for user {request.user.id}: {str(e)}")
|
2025-05-13 18:36:06 +08:00
|
|
|
|
return Response({
|
|
|
|
|
'code': 500,
|
|
|
|
|
'message': f'认证初始化错误:{str(e)}',
|
|
|
|
|
'data': None
|
|
|
|
|
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
2025-05-13 11:58:17 +08:00
|
|
|
|
# 记录无效请求数据并返回错误响应
|
|
|
|
|
logger.warning(f"Invalid request data: {serializer.errors}")
|
2025-05-13 18:36:06 +08:00
|
|
|
|
return Response({
|
|
|
|
|
'code': 400,
|
|
|
|
|
'message': '请求数据无效',
|
|
|
|
|
'data': serializer.errors
|
|
|
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
2025-05-13 11:58:17 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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}")
|
2025-05-13 18:36:06 +08:00
|
|
|
|
return Response({
|
|
|
|
|
'code': 201,
|
|
|
|
|
'message': '认证完成并成功保存凭证',
|
|
|
|
|
'data': serializer.data
|
|
|
|
|
}, status=status.HTTP_201_CREATED)
|
2025-05-13 11:58:17 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
# 记录错误并返回服务器错误响应
|
|
|
|
|
logger.error(f"Error completing authentication for user {request.user.id}: {str(e)}")
|
2025-05-13 18:36:06 +08:00
|
|
|
|
return Response({
|
|
|
|
|
'code': 500,
|
|
|
|
|
'message': f'完成认证时发生错误:{str(e)}',
|
|
|
|
|
'data': None
|
|
|
|
|
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
2025-05-13 11:58:17 +08:00
|
|
|
|
# 记录无效请求数据并返回错误响应
|
|
|
|
|
logger.warning(f"Invalid request data: {serializer.errors}")
|
2025-05-13 18:36:06 +08:00
|
|
|
|
return Response({
|
|
|
|
|
'code': 400,
|
|
|
|
|
'message': '请求数据无效',
|
|
|
|
|
'data': serializer.errors
|
|
|
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
2025-05-13 11:58:17 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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})
|
2025-05-13 18:36:06 +08:00
|
|
|
|
return Response({
|
|
|
|
|
'code': 200,
|
|
|
|
|
'message': '成功获取凭证列表',
|
|
|
|
|
'data': serializer.data
|
|
|
|
|
}, status=status.HTTP_200_OK)
|
2025-05-13 11:58:17 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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})
|
2025-05-13 18:36:06 +08:00
|
|
|
|
return Response({
|
|
|
|
|
'code': 200,
|
|
|
|
|
'message': '成功获取凭证详情',
|
|
|
|
|
'data': serializer.data
|
|
|
|
|
}, status=status.HTTP_200_OK)
|
2025-05-13 11:58:17 +08:00
|
|
|
|
|
|
|
|
|
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()
|
2025-05-13 18:36:06 +08:00
|
|
|
|
return Response({
|
|
|
|
|
'code': 200,
|
|
|
|
|
'message': '成功更新凭证',
|
|
|
|
|
'data': serializer.data
|
|
|
|
|
}, status=status.HTTP_200_OK)
|
2025-05-13 11:58:17 +08:00
|
|
|
|
# 返回无效数据错误
|
2025-05-13 18:36:06 +08:00
|
|
|
|
return Response({
|
|
|
|
|
'code': 400,
|
|
|
|
|
'message': '请求数据无效',
|
|
|
|
|
'data': serializer.errors
|
|
|
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
2025-05-13 11:58:17 +08:00
|
|
|
|
|
|
|
|
|
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()
|
2025-05-13 18:36:06 +08:00
|
|
|
|
return Response({
|
|
|
|
|
'code': 204,
|
|
|
|
|
'message': '凭证已成功删除',
|
|
|
|
|
'data': None
|
|
|
|
|
}, status=status.HTTP_204_NO_CONTENT)
|
|
|
|
|
|
2025-05-13 11:58:17 +08:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
2025-05-13 18:36:06 +08:00
|
|
|
|
|
2025-05-13 11:58:17 +08:00
|
|
|
|
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)
|
|
|
|
|
|
2025-05-13 18:36:06 +08:00
|
|
|
|
|
2025-05-13 11:58:17 +08:00
|
|
|
|
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:
|
2025-05-13 18:36:06 +08:00
|
|
|
|
return Response({
|
|
|
|
|
'code': 400,
|
|
|
|
|
'message': '必须提供Gmail邮箱地址',
|
|
|
|
|
'data': None
|
|
|
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
2025-05-13 11:58:17 +08:00
|
|
|
|
|
|
|
|
|
# 检查用户是否有此Gmail账户的凭证
|
|
|
|
|
credential = GmailCredential.objects.filter(user=request.user, email=email).first()
|
|
|
|
|
if not credential:
|
2025-05-13 18:36:06 +08:00
|
|
|
|
return Response({
|
|
|
|
|
'code': 404,
|
|
|
|
|
'message': f'未找到{email}的授权信息',
|
|
|
|
|
'data': None
|
|
|
|
|
}, status=status.HTTP_404_NOT_FOUND)
|
2025-05-13 11:58:17 +08:00
|
|
|
|
|
|
|
|
|
# 设置Pub/Sub通知
|
|
|
|
|
success, error = GmailService.setup_gmail_push_notification(request.user, email)
|
|
|
|
|
|
|
|
|
|
if success:
|
2025-05-13 18:36:06 +08:00
|
|
|
|
return Response({
|
|
|
|
|
'code': 200,
|
|
|
|
|
'message': f'已成功为{email}设置实时通知',
|
|
|
|
|
'data': None
|
|
|
|
|
}, status=status.HTTP_200_OK)
|
2025-05-13 11:58:17 +08:00
|
|
|
|
else:
|
2025-05-13 18:36:06 +08:00
|
|
|
|
return Response({
|
|
|
|
|
'code': 500,
|
|
|
|
|
'message': error,
|
|
|
|
|
'data': None
|
|
|
|
|
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
2025-05-13 11:58:17 +08:00
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"设置Gmail Pub/Sub通知失败: {str(e)}")
|
2025-05-13 18:36:06 +08:00
|
|
|
|
return Response({
|
|
|
|
|
'code': 500,
|
|
|
|
|
'message': f'设置Gmail实时通知失败: {str(e)}',
|
|
|
|
|
'data': None
|
|
|
|
|
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
2025-05-13 11:58:17 +08:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
})
|
|
|
|
|
|
2025-05-13 18:36:06 +08:00
|
|
|
|
return Response({
|
|
|
|
|
'code': 200,
|
|
|
|
|
'message': '获取账户列表成功',
|
|
|
|
|
'data': {'accounts': accounts}
|
|
|
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
|
|
2025-05-13 11:58:17 +08:00
|
|
|
|
|
|
|
|
|
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({
|
2025-05-13 18:36:06 +08:00
|
|
|
|
'code': 400,
|
|
|
|
|
'message': '缺少必要参数,请提供email、to和subject字段',
|
|
|
|
|
'data': None
|
2025-05-13 11:58:17 +08:00
|
|
|
|
}, 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({
|
2025-05-13 18:36:06 +08:00
|
|
|
|
'code': 404,
|
|
|
|
|
'message': f'未找到{user_email}的有效授权信息',
|
|
|
|
|
'data': None
|
2025-05-13 11:58:17 +08:00
|
|
|
|
}, 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({
|
2025-05-13 18:36:06 +08:00
|
|
|
|
'code': 200,
|
2025-05-13 11:58:17 +08:00
|
|
|
|
'message': '邮件发送成功',
|
2025-05-13 18:36:06 +08:00
|
|
|
|
'data': {'message_id': result}
|
2025-05-13 11:58:17 +08:00
|
|
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
|
else:
|
|
|
|
|
return Response({
|
2025-05-13 18:36:06 +08:00
|
|
|
|
'code': 500,
|
|
|
|
|
'message': result,
|
|
|
|
|
'data': None
|
2025-05-13 11:58:17 +08:00
|
|
|
|
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"发送Gmail邮件失败: {str(e)}")
|
|
|
|
|
return Response({
|
2025-05-13 18:36:06 +08:00
|
|
|
|
'code': 500,
|
|
|
|
|
'message': f'发送Gmail邮件失败: {str(e)}',
|
|
|
|
|
'data': None
|
2025-05-13 11:58:17 +08:00
|
|
|
|
}, 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
|
|
|
|
|
})
|
|
|
|
|
|
2025-05-13 18:36:06 +08:00
|
|
|
|
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)
|
|
|
|
|
|