@api_view(['POST']) @permission_classes([]) def gmail_webhook(request): """Gmail推送通知webhook""" try: # 导入需要的模块 import logging import traceback from django.utils import timezone from django.contrib.auth import get_user_model from rest_framework import status from rest_framework.response import Response # 获取用户模型 User = get_user_model() # 导入Gmail集成相关的模块 from .models import GmailCredential from .gmail_integration import GmailIntegration, GmailServiceManager logger = logging.getLogger(__name__) # 添加更详细的日志 logger.info(f"接收到Gmail webhook请求: 路径={request.path}, 方法={request.method}") logger.info(f"请求头: {dict(request.headers)}") logger.info(f"请求数据: {request.data}") # 验证请求来源(可以添加额外的安全校验) data = request.data if not data: return Response({ 'code': 400, 'message': '无效的请求数据', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 处理数据 email_address = None history_id = None # 处理Google Pub/Sub消息格式 if isinstance(data, dict) and 'message' in data and 'data' in data['message']: try: import base64 import json logger.info("检测到Google Pub/Sub消息格式") # Base64解码data字段 encoded_data = data['message']['data'] decoded_data = base64.b64decode(encoded_data).decode('utf-8') logger.info(f"解码后的数据: {decoded_data}") # 解析JSON获取email和historyId json_data = json.loads(decoded_data) email_address = json_data.get('emailAddress') history_id = json_data.get('historyId') logger.info(f"从Pub/Sub消息中提取: email={email_address}, historyId={history_id}") except Exception as decode_error: logger.error(f"解析Pub/Sub消息失败: {str(decode_error)}") logger.error(traceback.format_exc()) # 处理其他格式的数据 elif isinstance(data, dict): # 直接使用JSON格式数据 logger.info("接收到JSON格式数据") email_address = data.get('emailAddress') history_id = data.get('historyId') elif hasattr(data, 'decode'): # 尝试解析原始数据 logger.info("接收到原始数据格式,尝试解析") try: import json json_data = json.loads(data.decode('utf-8')) email_address = json_data.get('emailAddress') history_id = json_data.get('historyId') except Exception as parse_error: logger.error(f"解析请求数据失败: {str(parse_error)}") email_address = None history_id = None else: # 尝试从请求参数获取 logger.info("尝试从请求参数获取数据") email_address = request.GET.get('emailAddress') or request.POST.get('emailAddress') history_id = request.GET.get('historyId') or request.POST.get('historyId') logger.info(f"提取的邮箱: {email_address}, 历史ID: {history_id}") if not email_address or not history_id: return Response({ 'code': 400, 'message': '缺少必要的参数', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 查找用户和认证信息 - 优化的查找逻辑 user = None credential = None # 1. 首先尝试直接通过Gmail凭证表查找 credential = GmailCredential.objects.filter( gmail_email=email_address, is_active=True ).select_related('user').order_by('-is_default', '-updated_at').first() if credential: user = credential.user logger.info(f"通过gmail_email直接找到用户和凭证: 用户={user.email}, 凭证ID={credential.id}") else: # 2. 如果没找到,尝试通过用户邮箱查找 user = User.objects.filter(email=email_address).first() if user: logger.info(f"通过用户邮箱找到用户: {user.email}") # 为该用户查找任何有效的Gmail凭证 credential = GmailCredential.objects.filter( user=user, is_active=True ).order_by('-is_default', '-updated_at').first() if credential: logger.info(f"为用户 {user.email} 找到有效的Gmail凭证: {credential.id}") else: logger.error(f"无法找到与{email_address}关联的用户或凭证") return Response({ 'code': 404, 'message': f'找不到与 {email_address} 关联的用户', 'data': None }, status=status.HTTP_404_NOT_FOUND) if not credential: logger.error(f"用户 {user.email} 没有有效的Gmail凭证") return Response({ 'code': 404, 'message': f'找不到用户 {user.email} 的Gmail凭证', 'data': None }, status=status.HTTP_404_NOT_FOUND) # 更新history_id,无论如何都记录这次历史ID credential.last_history_id = history_id credential.save() # 清除可能存在的缓存实例,确保使用最新凭证 GmailServiceManager.clear_instance(user, str(credential.id)) # 检查凭证是否需要重新授权 notification_queued = False if credential.needs_reauth or not credential.credentials: logger.warning(f"Gmail凭证需要重新授权,将通知保存到队列: {email_address}") # 保存到通知队列 from .models import GmailNotificationQueue import json # 将通知数据序列化 try: notification_json = json.dumps(data) except: notification_json = f'{{"emailAddress": "{email_address}", "historyId": "{history_id}"}}' # 创建队列记录 GmailNotificationQueue.objects.create( user=user, gmail_credential=credential, email=email_address, history_id=str(history_id), notification_data=notification_json, processed=False ) logger.info(f"Gmail通知已保存到队列,等待用户重新授权: {email_address}") notification_queued = True # 直接返回成功,但记录需要用户重新授权 return Response({ 'code': 202, # Accepted 'message': '通知已保存到队列,等待用户重新授权', 'data': { 'user_id': str(user.id), 'history_id': history_id, 'needs_reauth': True } }) # 如果请求中包含达人邮箱,直接处理特定达人的邮件 talent_email = data.get('talent_email') or request.GET.get('talent_email') if talent_email and user: logger.info(f"检测到特定达人邮箱: {talent_email},将直接处理其最近邮件") try: # 创建Gmail集成实例 - 使用明确的凭证ID integration = GmailIntegration(user=user, gmail_credential_id=str(credential.id)) if integration.authenticate(): # 获取达人最近的邮件 recent_emails = integration.get_recent_emails( from_email=talent_email, max_results=5 # 限制获取最近5封 ) if recent_emails: logger.info(f"找到 {len(recent_emails)} 封来自 {talent_email} 的最近邮件") # 创建或获取知识库 knowledge_base, created = integration.create_talent_knowledge_base(talent_email) # 保存对话 result = integration.save_conversations_to_knowledge_base(recent_emails, knowledge_base) logger.info(f"已处理达人 {talent_email} 的最近邮件: {result}") else: logger.info(f"没有找到来自 {talent_email} 的最近邮件") else: logger.error("Gmail认证失败,无法处理特定达人邮件") # 如果还没有保存到队列,保存通知数据 if not notification_queued: # 保存到通知队列 from .models import GmailNotificationQueue import json try: notification_json = json.dumps(data) except: notification_json = f'{{"emailAddress": "{email_address}", "historyId": "{history_id}", "talent_email": "{talent_email}"}}' GmailNotificationQueue.objects.create( user=user, gmail_credential=credential, email=email_address, history_id=str(history_id), notification_data=notification_json, processed=False ) logger.info(f"Gmail通知(含达人邮箱)已保存到队列: {email_address}, 达人: {talent_email}") except Exception as talent_error: logger.error(f"处理达人邮件失败: {str(talent_error)}") logger.error(traceback.format_exc()) # 处理普通通知 try: # 创建Gmail集成实例 - 明确使用找到的凭证ID integration = GmailIntegration(user=user, gmail_credential_id=str(credential.id)) # 记录详细的凭证信息,帮助排查问题 logger.info(f"处理普通通知: 用户ID={user.id}, 凭证ID={credential.id}, Gmail邮箱={credential.gmail_email}") auth_success = integration.authenticate() if auth_success: logger.info(f"Gmail认证成功,开始处理通知: {email_address}") # 强制设置最小历史ID差值,确保能获取新消息 try: # 从凭证中获取历史ID,并确保作为整数比较 last_history_id = int(credential.last_history_id or 0) current_history_id = int(history_id) # 如果历史ID没有变化,设置一个小的偏移量确保获取最近消息 if current_history_id <= last_history_id: # 设置较小的历史ID以确保获取最近的消息 adjusted_history_id = max(1, last_history_id - 10) logger.info(f"调整历史ID: {last_history_id} -> {adjusted_history_id},以确保能获取最近的消息") # 修改请求中的历史ID if isinstance(data, dict) and 'message' in data and 'data' in data['message']: # 对于Pub/Sub格式,修改解码后的JSON try: import base64 import json decoded_data = base64.b64decode(data['message']['data']).decode('utf-8') json_data = json.loads(decoded_data) json_data['historyId'] = str(adjusted_history_id) data['message']['data'] = base64.b64encode(json.dumps(json_data).encode('utf-8')).decode('utf-8') logger.info(f"已调整Pub/Sub消息中的历史ID为: {adjusted_history_id}") except Exception as adjust_error: logger.error(f"调整历史ID失败: {str(adjust_error)}") else: # 直接修改data中的historyId if isinstance(data, dict) and 'historyId' in data: data['historyId'] = str(adjusted_history_id) logger.info(f"已调整请求中的历史ID为: {adjusted_history_id}") except Exception as history_adjust_error: logger.error(f"历史ID调整失败: {str(history_adjust_error)}") result = integration.process_notification(data) # 日志记录处理结果 if result: logger.info(f"Gmail通知处理成功,检测到新消息: {email_address}") else: logger.warning(f"Gmail通知处理完成,但未检测到新消息: {email_address}") # 如果处理成功,尝试通过WebSocket发送通知 if result: try: from channels.layers import get_channel_layer from asgiref.sync import async_to_sync # 获取Channel Layer channel_layer = get_channel_layer() if channel_layer: # 发送WebSocket消息 async_to_sync(channel_layer.group_send)( f"notification_user_{user.id}", { "type": "notification", "data": { "message_type": "gmail_update", "message": "您的Gmail有新消息,已自动处理", "history_id": history_id, "timestamp": timezone.now().isoformat() } } ) logger.info(f"发送WebSocket通知成功: user_id={user.id}") except Exception as ws_error: logger.error(f"发送WebSocket通知失败: {str(ws_error)}") logger.info(f"Gmail通知处理成功: {email_address}") return Response({ 'code': 200, 'message': '通知已处理', 'data': { 'user_id': str(user.id), 'history_id': history_id, 'success': True, 'new_messages': result } }) else: # 认证失败,保存通知到队列 logger.error(f"Gmail认证失败: {email_address}, 用户ID={user.id}, 凭证ID={credential.id}") # 尝试获取详细的认证失败原因 try: # 尝试刷新令牌 refresh_result = integration.refresh_token() if refresh_result: logger.info(f"令牌刷新成功,将重新尝试处理") result = integration.process_notification(data) if result: logger.info(f"刷新令牌后处理成功!") return Response({ 'code': 200, 'message': '通知已处理(令牌刷新后)', 'data': { 'user_id': str(user.id), 'history_id': history_id, 'success': True } }) except Exception as refresh_error: logger.error(f"尝试刷新令牌失败: {str(refresh_error)}") # 如果还没有保存到队列,保存通知数据 if not notification_queued: # 保存到通知队列 from .models import GmailNotificationQueue import json try: notification_json = json.dumps(data) except: notification_json = f'{{"emailAddress": "{email_address}", "historyId": "{history_id}"}}' # 标记凭证需要重新授权 credential.needs_reauth = True credential.save() logger.info(f"已标记凭证 {credential.id} 需要重新授权") GmailNotificationQueue.objects.create( user=user, gmail_credential=credential, email=email_address, history_id=str(history_id), notification_data=notification_json, processed=False ) logger.info(f"Gmail通知已保存到队列: {email_address}") # 返回处理成功,但告知需要重新授权 return Response({ 'code': 202, # Accepted 'message': '通知已保存到队列,等待重新获取授权', 'data': { 'user_id': str(user.id), 'history_id': history_id, 'needs_reauth': True } }) except Exception as process_error: logger.error(f"处理Gmail通知失败: {str(process_error)}") logger.error(traceback.format_exc()) # 保存到通知队列 if not notification_queued: try: from .models import GmailNotificationQueue import json try: notification_json = json.dumps(data) except: notification_json = f'{{"emailAddress": "{email_address}", "historyId": "{history_id}"}}' # 标记凭证需要重新授权 - 可能是令牌问题导致的错误 error_msg = str(process_error).lower() if "invalid_grant" in error_msg or "token" in error_msg or "auth" in error_msg or "认证" in error_msg: credential.needs_reauth = True credential.save() logger.info(f"根据错误信息标记凭证 {credential.id} 需要重新授权") GmailNotificationQueue.objects.create( user=user, gmail_credential=credential, email=email_address, history_id=str(history_id), notification_data=notification_json, processed=False, error_message=str(process_error)[:255] ) logger.info(f"由于处理错误,Gmail通知已保存到队列: {email_address}") except Exception as queue_error: logger.error(f"保存通知到队列失败: {str(queue_error)}") # 仍然返回成功,防止Google重试导致重复通知 return Response({ 'code': 202, 'message': '通知已保存,稍后处理', 'data': { 'user_id': str(user.id), 'history_id': history_id, 'error': str(process_error)[:100] # 截断错误信息 } }) except Exception as e: logger.error(f"处理Gmail webhook失败: {str(e)}") logger.error(traceback.format_exc()) # 尝试更安全的响应,尽可能提供有用信息 try: # 如果已经提取了邮箱和历史ID等信息,记录在响应中 response_data = { 'error': str(e)[:200] } if 'email_address' in locals() and email_address: response_data['email_address'] = email_address if 'history_id' in locals() and history_id: response_data['history_id'] = history_id if 'user' in locals() and user: response_data['user_id'] = str(user.id) return Response({ 'code': 500, 'message': '处理通知失败', 'data': response_data }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) except: # 最后的备用方案 return Response({ 'code': 500, 'message': '处理通知失败', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)