1475 lines
58 KiB
Python
1475 lines
58 KiB
Python
import traceback
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from rest_framework import status, viewsets
|
||
from rest_framework.decorators import action
|
||
|
||
from apps.user.services.utils import validate_uuid_param
|
||
from apps.gmail.services.goal_service import generate_recommended_reply, get_conversation_summary, get_last_message
|
||
from .serializers import GmailCredentialSerializer, UserGoalSerializer, AutoReplyConfigSerializer
|
||
from .services.gmail_service import GmailService
|
||
from .models import GmailCredential, GmailConversation, GmailAttachment, UserGoal, ConversationSummary
|
||
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
|
||
from apps.feishu.services.auto_gmail_conversation_service import AutoGmailConversationService
|
||
from django.utils import timezone
|
||
from django.http import HttpResponse, Http404
|
||
from django.db.models import Q, Prefetch
|
||
import uuid
|
||
from apps.common.services.ai_service import AIService
|
||
from apps.chat.models import ChatHistory
|
||
from apps.user.authentication import CustomTokenAuthentication
|
||
|
||
# 配置日志记录器,用于记录视图操作的调试、警告和错误信息
|
||
logger = logging.getLogger(__name__)
|
||
|
||
class GmailAuthInitiateView(APIView):
|
||
"""
|
||
API 视图,用于启动 Gmail OAuth2 认证流程。
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
authentication_classes = [CustomTokenAuthentication] # 限制访问,仅允许已认证用户
|
||
|
||
def post(self, request):
|
||
"""
|
||
处理 POST 请求,启动 Gmail OAuth2 认证并返回授权 URL。
|
||
|
||
支持两种方式提供客户端密钥:
|
||
1. 在请求体中提供client_secret_json字段
|
||
2. 上传名为client_secret_file的JSON文件
|
||
|
||
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}")
|
||
|
||
# 检查是否是文件上传方式
|
||
client_secret_json = None
|
||
if 'client_secret_file' in request.FILES:
|
||
try:
|
||
# 读取上传的JSON文件内容
|
||
client_secret_file = request.FILES['client_secret_file']
|
||
client_secret_json = json.loads(client_secret_file.read().decode('utf-8'))
|
||
logger.info(f"从上传文件读取到客户端密钥JSON")
|
||
except json.JSONDecodeError as e:
|
||
logger.error(f"解析客户端密钥JSON文件失败: {str(e)}")
|
||
return Response({
|
||
'code': 400,
|
||
'message': f'无效的JSON文件格式: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
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)
|
||
|
||
# 如果不是文件上传,则尝试从请求数据中提取JSON
|
||
if not client_secret_json:
|
||
serializer = GmailCredentialSerializer(data=request.data, context={'request': request})
|
||
if serializer.is_valid():
|
||
try:
|
||
# 从请求数据中提取客户端密钥 JSON
|
||
client_secret_json = serializer.validated_data['client_secret_json']
|
||
except Exception as e:
|
||
logger.error(f"未提供客户端密钥JSON: {str(e)}")
|
||
return Response({
|
||
'code': 400,
|
||
'message': '请提供client_secret_json字段或上传client_secret_file文件',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
else:
|
||
# 记录无效请求数据并返回错误响应
|
||
logger.warning(f"Invalid request data: {serializer.errors}")
|
||
return Response({
|
||
'code': 400,
|
||
'message': '请求数据无效,请提供client_secret_json字段或上传client_secret_file文件',
|
||
'data': serializer.errors
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 如果此时仍然没有client_secret_json,返回错误
|
||
if not client_secret_json:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '请提供client_secret_json字段或上传client_secret_file文件',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
# 调用 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)
|
||
|
||
|
||
class GmailAuthCompleteView(APIView):
|
||
"""
|
||
API 视图,用于完成 Gmail OAuth2 认证流程。
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
authentication_classes = [CustomTokenAuthentication] # 限制访问,仅允许已认证用户
|
||
|
||
def post(self, request):
|
||
"""
|
||
处理 POST 请求,使用授权代码完成 Gmail OAuth2 认证并保存凭证。
|
||
|
||
支持两种方式提供客户端密钥:
|
||
1. 在请求体中提供client_secret_json字段
|
||
2. 上传名为client_secret_file的JSON文件
|
||
|
||
Args:
|
||
request: Django REST Framework 请求对象,包含授权代码和客户端密钥 JSON 或文件。
|
||
|
||
Returns:
|
||
Response: 包含已保存凭证数据的 JSON 响应(成功时),或错误信息(失败时)。
|
||
|
||
Status Codes:
|
||
201: 成功保存凭证。
|
||
400: 请求数据无效。
|
||
500: 服务器内部错误(如认证失败)。
|
||
"""
|
||
logger.debug(f"Received auth complete request: {request.data}")
|
||
|
||
# 检查是否是文件上传方式
|
||
client_secret_json = None
|
||
if 'client_secret_file' in request.FILES:
|
||
try:
|
||
# 读取上传的JSON文件内容
|
||
client_secret_file = request.FILES['client_secret_file']
|
||
client_secret_json = json.loads(client_secret_file.read().decode('utf-8'))
|
||
logger.info(f"从上传文件读取到客户端密钥JSON")
|
||
except json.JSONDecodeError as e:
|
||
logger.error(f"解析客户端密钥JSON文件失败: {str(e)}")
|
||
return Response({
|
||
'code': 400,
|
||
'message': f'无效的JSON文件格式: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
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)
|
||
|
||
# 获取授权码,无论是哪种方式都需要
|
||
auth_code = request.data.get('auth_code')
|
||
if not auth_code:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '必须提供授权码',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 如果不是文件上传,则尝试从请求数据中提取JSON
|
||
if not client_secret_json:
|
||
serializer = GmailCredentialSerializer(data=request.data, context={'request': request})
|
||
if serializer.is_valid():
|
||
try:
|
||
# 从请求数据中提取客户端密钥 JSON
|
||
client_secret_json = serializer.validated_data['client_secret_json']
|
||
except Exception as e:
|
||
logger.error(f"未提供客户端密钥JSON: {str(e)}")
|
||
return Response({
|
||
'code': 400,
|
||
'message': '请提供client_secret_json字段或上传client_secret_file文件',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
else:
|
||
# 记录无效请求数据并返回错误响应
|
||
logger.warning(f"Invalid request data: {serializer.errors}")
|
||
return Response({
|
||
'code': 400,
|
||
'message': '请求数据无效,请提供client_secret_json字段或上传client_secret_file文件',
|
||
'data': serializer.errors
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 如果此时仍然没有client_secret_json,返回错误
|
||
if not client_secret_json:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '请提供client_secret_json字段或上传client_secret_file文件',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
# 完成认证并保存凭证
|
||
credential = GmailService.complete_authentication(request.user, auth_code, client_secret_json)
|
||
# 序列化凭证数据以返回
|
||
return_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': return_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)
|
||
|
||
|
||
class GmailCredentialViewSet(viewsets.ModelViewSet):
|
||
"""
|
||
Gmail凭证管理视图集,提供对Gmail账户凭证的完整CRUD操作
|
||
|
||
此视图集替代了旧的GmailCredentialListView和GmailCredentialDetailView,
|
||
提供了更完整的功能和更统一的API接口。
|
||
|
||
支持以下操作:
|
||
- list: 获取凭证列表
|
||
- create: 创建新凭证(初始化OAuth或完成OAuth)
|
||
- retrieve: 获取单个凭证详情
|
||
- update: 完全更新凭证
|
||
- partial_update: 部分更新凭证
|
||
- destroy: 删除凭证
|
||
- set_default: 设置默认凭证(自定义操作)
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
authentication_classes = [CustomTokenAuthentication]
|
||
serializer_class = GmailCredentialSerializer
|
||
|
||
def get_queryset(self):
|
||
"""获取当前用户的Gmail凭证"""
|
||
return GmailCredential.objects.filter(user=self.request.user)
|
||
|
||
def perform_create(self, serializer):
|
||
"""创建凭证时自动关联当前用户"""
|
||
serializer.save(user=self.request.user)
|
||
|
||
def list(self, request, *args, **kwargs):
|
||
"""
|
||
列出用户的Gmail账号凭证
|
||
|
||
返回当前用户所有Gmail账号凭证的列表。
|
||
"""
|
||
queryset = self.get_queryset()
|
||
serializer = self.get_serializer(queryset, many=True)
|
||
return Response({
|
||
'code': 200,
|
||
'message': '获取Gmail账号列表成功',
|
||
'data': serializer.data
|
||
})
|
||
|
||
def retrieve(self, request, *args, **kwargs):
|
||
"""
|
||
获取特定Gmail凭证的详细信息
|
||
|
||
根据ID返回单个Gmail账号凭证的详细信息。
|
||
"""
|
||
instance = self.get_object()
|
||
serializer = self.get_serializer(instance)
|
||
return Response({
|
||
'code': 200,
|
||
'message': '成功获取凭证详情',
|
||
'data': serializer.data
|
||
})
|
||
|
||
def create(self, request, *args, **kwargs):
|
||
"""
|
||
创建Gmail账号凭证 - 初始化或完成OAuth授权
|
||
|
||
此接口有两个功能:
|
||
1. 如果未提供auth_code,则初始化OAuth并返回授权URL
|
||
2. 如果提供了auth_code,则完成OAuth并保存凭证
|
||
|
||
请求参数:
|
||
- client_secret_json: Google Cloud项目的客户端密钥
|
||
- auth_code: [可选] 授权码,用于完成OAuth流程
|
||
"""
|
||
try:
|
||
serializer = self.get_serializer(data=request.data)
|
||
serializer.is_valid(raise_exception=True)
|
||
|
||
auth_code = serializer.validated_data.get('auth_code')
|
||
client_secret_json = serializer.validated_data.get('client_secret_json')
|
||
|
||
if not auth_code: # 初始化OAuth
|
||
auth_url = GmailService.initiate_authentication(request.user, client_secret_json)
|
||
return Response({
|
||
'code': 200,
|
||
'message': '授权URL生成成功',
|
||
'data': {'auth_url': auth_url}
|
||
})
|
||
else: # 完成OAuth
|
||
credential = GmailService.complete_authentication(request.user, auth_code, client_secret_json)
|
||
return Response({
|
||
'code': 201,
|
||
'message': '授权成功',
|
||
'data': {
|
||
'id': credential.id,
|
||
'email': credential.email,
|
||
'is_valid': credential.is_valid
|
||
}
|
||
}, status=status.HTTP_201_CREATED)
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建Gmail凭证失败: {str(e)}")
|
||
logger.error(traceback.format_exc())
|
||
return Response({
|
||
'code': 500,
|
||
'message': f'创建Gmail凭证失败: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
def update(self, request, *args, **kwargs):
|
||
"""
|
||
完全更新Gmail凭证信息
|
||
|
||
更新Gmail凭证的所有可编辑字段。
|
||
如果设置is_default=True,会自动将其他凭证设为非默认。
|
||
"""
|
||
try:
|
||
instance = self.get_object()
|
||
serializer = self.get_serializer(instance, data=request.data)
|
||
serializer.is_valid(raise_exception=True)
|
||
|
||
# 如果设置为默认凭证,清除其他凭证的默认状态
|
||
if serializer.validated_data.get('is_default', False):
|
||
GmailCredential.objects.filter(user=request.user).exclude(id=instance.id).update(is_default=False)
|
||
|
||
serializer.save()
|
||
return Response({
|
||
'code': 200,
|
||
'message': '成功更新凭证',
|
||
'data': serializer.data
|
||
})
|
||
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 partial_update(self, request, *args, **kwargs):
|
||
"""
|
||
部分更新Gmail凭证信息
|
||
|
||
更新Gmail凭证的部分字段。
|
||
如果设置is_default=True,会自动将其他凭证设为非默认。
|
||
"""
|
||
try:
|
||
instance = self.get_object()
|
||
serializer = self.get_serializer(instance, data=request.data, partial=True)
|
||
serializer.is_valid(raise_exception=True)
|
||
|
||
# 如果设置为默认凭证,清除其他凭证的默认状态
|
||
if serializer.validated_data.get('is_default', False):
|
||
GmailCredential.objects.filter(user=request.user).exclude(id=instance.id).update(is_default=False)
|
||
|
||
serializer.save()
|
||
return Response({
|
||
'code': 200,
|
||
'message': '成功更新凭证',
|
||
'data': serializer.data
|
||
})
|
||
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 destroy(self, request, *args, **kwargs):
|
||
"""
|
||
删除Gmail账号凭证
|
||
|
||
根据ID删除指定的Gmail凭证。
|
||
"""
|
||
try:
|
||
instance = self.get_object()
|
||
self.perform_destroy(instance)
|
||
return Response({
|
||
'code': 200,
|
||
'message': '删除Gmail账号成功',
|
||
'data': None
|
||
})
|
||
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)
|
||
|
||
@action(detail=True, methods=['post'])
|
||
def set_default(self, request, pk=None):
|
||
"""设置默认Gmail账号"""
|
||
try:
|
||
credential = self.get_object()
|
||
|
||
# 取消其他默认账号
|
||
GmailCredential.objects.filter(
|
||
user=request.user,
|
||
is_default=True
|
||
).update(is_default=False)
|
||
|
||
# 设置当前账号为默认
|
||
credential.is_default = True
|
||
credential.save()
|
||
|
||
return Response({
|
||
'code': 200,
|
||
'message': f'已将 {credential.email} 设为默认Gmail账号',
|
||
'data': None
|
||
})
|
||
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)
|
||
|
||
@action(detail=False, methods=['post'])
|
||
def upload_client_secret(self, request):
|
||
"""
|
||
通过上传客户端密钥JSON文件来创建Gmail凭证
|
||
|
||
请求参数:
|
||
- client_secret_file: Google Cloud项目的客户端密钥JSON文件
|
||
- auth_code: [可选] 授权码,用于完成OAuth流程
|
||
"""
|
||
try:
|
||
# 检查是否上传了文件
|
||
if 'client_secret_file' not in request.FILES:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '未找到客户端密钥文件,请使用client_secret_file字段上传',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 读取上传的JSON文件内容
|
||
client_secret_file = request.FILES['client_secret_file']
|
||
client_secret_json = json.loads(client_secret_file.read().decode('utf-8'))
|
||
|
||
# 获取可选的授权码
|
||
auth_code = request.data.get('auth_code', '')
|
||
|
||
if not auth_code: # 初始化OAuth
|
||
# 调用GmailService生成授权URL
|
||
auth_url = GmailService.initiate_authentication(request.user, client_secret_json)
|
||
return Response({
|
||
'code': 200,
|
||
'message': '授权URL生成成功',
|
||
'data': {'auth_url': auth_url}
|
||
})
|
||
else: # 完成OAuth
|
||
# 完成认证并保存凭证
|
||
credential = GmailService.complete_authentication(request.user, auth_code, client_secret_json)
|
||
return Response({
|
||
'code': 201,
|
||
'message': '授权成功',
|
||
'data': {
|
||
'id': credential.id,
|
||
'email': credential.email,
|
||
'is_valid': credential.is_valid
|
||
}
|
||
}, status=status.HTTP_201_CREATED)
|
||
|
||
except json.JSONDecodeError as e:
|
||
logger.error(f"解析客户端密钥JSON文件失败: {str(e)}")
|
||
return Response({
|
||
'code': 400,
|
||
'message': f'无效的JSON文件格式: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
except Exception as e:
|
||
logger.error(f"上传客户端密钥文件失败: {str(e)}")
|
||
logger.error(traceback.format_exc())
|
||
return Response({
|
||
'code': 500,
|
||
'message': f'处理客户端密钥文件失败: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
class GmailConversationView(APIView):
|
||
"""
|
||
API视图,用于获取和保存Gmail对话。
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
authentication_classes = [CustomTokenAuthentication] # 限制访问,仅允许已认证用户
|
||
|
||
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]
|
||
authentication_classes = [CustomTokenAuthentication] # 限制访问,仅允许已认证用户
|
||
|
||
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]
|
||
authentication_classes = [CustomTokenAuthentication] # 限制访问,仅允许已认证用户
|
||
|
||
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]
|
||
authentication_classes = [CustomTokenAuthentication] # 限制访问,仅允许已认证用户
|
||
|
||
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推送通知,
|
||
将推送通知交给Celery任务队列异步处理。
|
||
|
||
Args:
|
||
request: Django REST Framework请求对象,包含Pub/Sub消息。
|
||
|
||
Returns:
|
||
Response: 处理结果的JSON响应。
|
||
"""
|
||
try:
|
||
print("\n" + "="*100)
|
||
print("[Gmail Webhook] 收到推送通知")
|
||
print("="*100)
|
||
|
||
# 打印请求时间和基本信息
|
||
current_time = timezone.now()
|
||
print(f"接收时间: {current_time.strftime('%Y-%m-%d %H:%M:%S.%f')}")
|
||
|
||
# 解析推送消息
|
||
message = request.data.get('message', {})
|
||
data = message.get('data', '')
|
||
message_id = message.get('messageId', '')
|
||
subscription = request.data.get('subscription', '')
|
||
|
||
print(f"消息ID: {message_id}")
|
||
print(f"订阅名称: {subscription}")
|
||
|
||
if not data:
|
||
print("[Gmail Webhook] 错误: 无效的推送消息格式,缺少data字段")
|
||
return Response({
|
||
'code': 400,
|
||
'message': '无效的推送消息格式',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 检查该消息是否已处理过
|
||
from .models import ProcessedPushNotification
|
||
if ProcessedPushNotification.objects.filter(message_id=message_id).exists():
|
||
print(f"[Gmail Webhook] 通知 {message_id} 已处理过,跳过")
|
||
return Response({
|
||
'code': 200,
|
||
'message': '通知已处理过,跳过',
|
||
'data': None
|
||
})
|
||
|
||
# 将消息提交到Celery任务队列进行异步处理
|
||
from .tasks import process_push_notification
|
||
process_push_notification.delay(data, message_id, subscription)
|
||
|
||
print("[Gmail Webhook] 已将推送通知提交到任务队列处理")
|
||
print("="*100 + "\n")
|
||
|
||
# 返回成功响应
|
||
return Response({
|
||
'code': 200,
|
||
'message': '推送通知已接收并提交到任务队列',
|
||
'data': None
|
||
})
|
||
|
||
except Exception as e:
|
||
print(f"[Gmail Webhook] 处理推送通知失败: {str(e)}")
|
||
logger.error(f"处理Gmail推送通知失败: {str(e)}")
|
||
return Response({
|
||
'code': 500,
|
||
'message': f'处理推送通知失败: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
class GmailConversationSummaryView(APIView):
|
||
"""
|
||
API视图,用于获取Gmail对话的总结。
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
authentication_classes = [CustomTokenAuthentication] # 限制访问,仅允许已认证用户
|
||
|
||
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)
|
||
|
||
|
||
class GmailGoalView(APIView):
|
||
"""
|
||
用户目标API - 支持用户为每个对话设置不同的目标
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
authentication_classes = [CustomTokenAuthentication] # 限制访问,仅允许已认证用户
|
||
def get(self, request, conversation_id=None):
|
||
"""
|
||
获取用户的目标
|
||
|
||
如果提供了conversation_id,则获取该对话的活跃目标
|
||
如果没有提供conversation_id,则获取用户的所有活跃目标
|
||
"""
|
||
try:
|
||
if conversation_id:
|
||
# 获取特定对话的活跃目标
|
||
goal = AutoGmailConversationService.get_active_goals_for_conversation(
|
||
request.user,
|
||
conversation_id
|
||
)
|
||
|
||
if not goal:
|
||
return Response({
|
||
'code': 404,
|
||
'message': '未找到该对话的活跃目标',
|
||
'data': None
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
serializer = UserGoalSerializer(goal)
|
||
return Response({
|
||
'code': 200,
|
||
'message': '获取目标成功',
|
||
'data': serializer.data
|
||
})
|
||
else:
|
||
# 获取用户的所有活跃目标
|
||
goals = UserGoal.objects.filter(user=request.user, is_active=True)
|
||
|
||
# 准备响应数据,包括对话信息
|
||
result = []
|
||
for goal in goals:
|
||
goal_data = UserGoalSerializer(goal).data
|
||
result.append(goal_data)
|
||
|
||
return Response({
|
||
'code': 200,
|
||
'message': '获取目标列表成功',
|
||
'data': result
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取用户目标失败: {str(e)}")
|
||
logger.error(traceback.format_exc())
|
||
return Response({
|
||
'code': 500,
|
||
'message': f'获取用户目标失败: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
def post(self, request):
|
||
"""创建对话目标"""
|
||
try:
|
||
# 获取请求参数
|
||
conversation_id = request.data.get('conversation_id')
|
||
goal_description = request.data.get('goal_description')
|
||
|
||
# 验证必要参数
|
||
if not conversation_id or not goal_description:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '缺少必要参数: conversation_id 或 goal_description',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 查找对话
|
||
conversation = GmailConversation.objects.filter(conversation_id=conversation_id).first()
|
||
if not conversation:
|
||
return Response({
|
||
'code': 404,
|
||
'message': '对话不存在',
|
||
'data': None
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
|
||
# 查找现有目标
|
||
existing_goal = UserGoal.objects.filter(
|
||
user=request.user,
|
||
conversation=conversation,
|
||
is_active=True
|
||
).first()
|
||
|
||
if existing_goal:
|
||
# 如果存在活跃目标,直接更新它
|
||
existing_goal.description = goal_description
|
||
existing_goal.status = 'pending' # 重置状态
|
||
existing_goal.last_activity_time = timezone.now()
|
||
existing_goal.metadata = existing_goal.metadata or {}
|
||
existing_goal.metadata.update({
|
||
'updated_at': timezone.now().isoformat(),
|
||
'influencer_email': conversation.influencer_email,
|
||
'user_email': conversation.user_email
|
||
})
|
||
existing_goal.save()
|
||
|
||
logger.info(f"用户 {request.user.name} 更新了对话 {conversation_id} 的目标")
|
||
serializer = UserGoalSerializer(existing_goal)
|
||
return Response({
|
||
'code': 200,
|
||
'message': '目标更新成功',
|
||
'data': serializer.data
|
||
})
|
||
else:
|
||
# 创建新目标
|
||
goal = UserGoal.objects.create(
|
||
user=request.user,
|
||
conversation=conversation,
|
||
description=goal_description,
|
||
is_active=True,
|
||
status='pending',
|
||
metadata={
|
||
'created_at': timezone.now().isoformat(),
|
||
'influencer_email': conversation.influencer_email,
|
||
'user_email': conversation.user_email
|
||
}
|
||
)
|
||
|
||
logger.info(f"用户 {request.user.name} 为对话 {conversation_id} 创建了新目标")
|
||
serializer = UserGoalSerializer(goal)
|
||
return Response({
|
||
'code': 201,
|
||
'message': '目标创建成功',
|
||
'data': serializer.data
|
||
}, status=status.HTTP_201_CREATED)
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建用户目标失败: {str(e)}")
|
||
logger.error(traceback.format_exc())
|
||
return Response({
|
||
'code': 500,
|
||
'message': f'创建用户目标失败: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
def delete(self, request, conversation_id):
|
||
"""停用对话目标"""
|
||
try:
|
||
# 获取该对话的活跃目标
|
||
goal = AutoGmailConversationService.get_active_goals_for_conversation(
|
||
request.user,
|
||
conversation_id
|
||
)
|
||
|
||
if not goal:
|
||
return Response({
|
||
'code': 404,
|
||
'message': '未找到该对话的活跃目标',
|
||
'data': None
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 停用目标
|
||
goal.is_active = False
|
||
goal.save()
|
||
|
||
return Response({
|
||
'code': 200,
|
||
'message': '目标已停用',
|
||
'data': None
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"停用目标失败: {str(e)}")
|
||
logger.error(traceback.format_exc())
|
||
return Response({
|
||
'code': 500,
|
||
'message': f'停用目标失败: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
class SimpleRecommendedReplyView(APIView):
|
||
"""
|
||
通过conversation_id一键获取目标、对话摘要和推荐回复
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
authentication_classes = [CustomTokenAuthentication] # 限制访问,仅允许已认证用户
|
||
def post(self, request):
|
||
"""
|
||
直接通过conversation_id获取推荐回复
|
||
|
||
请求参数:
|
||
- conversation_id: 对话ID (必填)
|
||
|
||
响应:
|
||
- 目标信息
|
||
- 对话摘要
|
||
- 达人最后发送的消息
|
||
- 推荐回复内容
|
||
"""
|
||
try:
|
||
# 获取请求参数
|
||
conversation_id = request.data.get('conversation_id')
|
||
|
||
# 验证必填参数
|
||
if not conversation_id:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '缺少必要参数: conversation_id',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 验证对话存在并且属于当前用户
|
||
# 直接通过conversation_id查找,不进行UUID验证
|
||
# Gmail对话ID可能是形如 gmail_ae51a9e8-ff35-4507-a63b-7d8091327e1e_44a2aaac 的格式
|
||
try:
|
||
conversation = GmailConversation.objects.filter(conversation_id=conversation_id).first()
|
||
if not conversation:
|
||
return Response({
|
||
'code': 404,
|
||
'message': '对话不存在',
|
||
'data': None
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
if conversation.user != request.user:
|
||
return Response({
|
||
'code': 403,
|
||
'message': '无权限访问该对话',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
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)
|
||
|
||
# 1. 获取对话的活跃目标
|
||
goal = UserGoal.objects.filter(
|
||
user=request.user,
|
||
conversation=conversation,
|
||
is_active=True
|
||
).first()
|
||
|
||
if not goal:
|
||
return Response({
|
||
'code': 404,
|
||
'message': '未找到该对话的活跃目标',
|
||
'data': None
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 2. 获取对话摘要
|
||
conversation_summary = get_conversation_summary(conversation_id)
|
||
if not conversation_summary:
|
||
conversation_summary = "无对话摘要"
|
||
|
||
# 3. 获取最后一条达人消息
|
||
last_message = get_last_message(conversation_id)
|
||
if not last_message:
|
||
return Response({
|
||
'code': 400,
|
||
'message': '对话中没有达人消息,无法生成回复',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 4. 生成推荐回复
|
||
reply_content, error = generate_recommended_reply(
|
||
request.user,
|
||
goal.description,
|
||
conversation_summary,
|
||
last_message
|
||
)
|
||
|
||
if error:
|
||
return Response({
|
||
'code': 500,
|
||
'message': f'生成推荐回复失败: {error}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
# 返回完整数据
|
||
return Response({
|
||
'code': 200,
|
||
'message': '获取成功',
|
||
'data': {
|
||
'goal': {
|
||
'id': str(goal.id),
|
||
'description': goal.description,
|
||
'status': goal.status
|
||
},
|
||
'conversation_summary': conversation_summary,
|
||
'last_message': last_message,
|
||
'recommended_reply': reply_content
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"一键获取推荐回复失败: {str(e)}")
|
||
logger.error(traceback.format_exc())
|
||
return Response({
|
||
'code': 500,
|
||
'message': f'服务器错误: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
class GmailExportView(APIView):
|
||
"""
|
||
API视图,用于导出已回复的达人Gmail列表为Excel文件。
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
authentication_classes = [CustomTokenAuthentication] # 限制访问,仅允许已认证用户
|
||
|
||
def get(self, request, format=None):
|
||
"""
|
||
处理GET请求,导出已回复的达人Gmail列表。
|
||
|
||
Query参数:
|
||
- format: 导出格式,支持 'xlsx' 或 'csv',默认为 'xlsx'
|
||
|
||
Returns:
|
||
HttpResponse: 包含Excel文件的响应(成功时),或错误消息的JSON响应(失败时)。
|
||
"""
|
||
try:
|
||
# 获取导出格式参数
|
||
export_format = request.query_params.get('format', 'xlsx')
|
||
if export_format not in ['xlsx', 'csv']:
|
||
return Response({
|
||
'code': 400,
|
||
'message': f'不支持的导出格式: {export_format},支持的格式有: xlsx, csv',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
logger.info(f"用户 {request.user.name} 请求导出已回复达人列表,格式: {export_format}")
|
||
|
||
# 直接从具体文件导入而不依赖__init__.py
|
||
from .services.export_service import GmailExportService
|
||
file_path, error = GmailExportService.export_replied_influencers(request.user, format=export_format)
|
||
|
||
if error:
|
||
logger.error(f"导出失败: {error}")
|
||
return Response({
|
||
'code': 500,
|
||
'message': error,
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
if not file_path or not os.path.exists(file_path):
|
||
logger.error(f"导出文件不存在: {file_path}")
|
||
return Response({
|
||
'code': 500,
|
||
'message': '导出文件生成失败',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
# 读取生成的文件
|
||
with open(file_path, 'rb') as f:
|
||
file_data = f.read()
|
||
|
||
# 设置响应头,启用文件下载
|
||
response = HttpResponse(
|
||
file_data,
|
||
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' if export_format == 'xlsx' else 'text/csv'
|
||
)
|
||
filename = os.path.basename(file_path)
|
||
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
||
|
||
logger.info(f"成功导出文件: {filename}")
|
||
|
||
return response
|
||
|
||
except Exception as e:
|
||
logger.error(f"导出过程中发生错误: {str(e)}")
|
||
error_details = traceback.format_exc()
|
||
return Response({
|
||
'code': 500,
|
||
'message': f'导出失败: {str(e)}',
|
||
'data': {
|
||
'details': error_details[:500]
|
||
}
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|