operations_project/apps/gmail/models.py
2025-05-21 12:32:20 +08:00

236 lines
11 KiB
Python
Raw Permalink 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 django.db import models
from apps.accounts.models import User
import json
import os
from django.utils import timezone
import uuid
class GmailCredential(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='gmail_credentials')
email = models.EmailField(unique=True, help_text="Gmail email address")
credentials = models.TextField(help_text="Serialized OAuth2 credentials (JSON)")
is_default = models.BooleanField(default=False, help_text="Default Gmail account for user")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
is_valid = models.BooleanField(default=True, help_text="Whether the credential is valid")
last_history_id = models.CharField(max_length=50, blank=True, null=True, help_text="Last processed Gmail history ID")
class Meta:
unique_together = ('user', 'email')
def set_credentials(self, credentials):
self.credentials = json.dumps({
'token': credentials.token,
'refresh_token': credentials.refresh_token,
'token_uri': credentials.token_uri,
'client_id': credentials.client_id,
'client_secret': credentials.client_secret,
'scopes': credentials.scopes
})
self.is_valid = True
def get_credentials(self):
from google.oauth2.credentials import Credentials
creds_data = json.loads(self.credentials)
return Credentials(
token=creds_data['token'],
refresh_token=creds_data['refresh_token'],
token_uri=creds_data['token_uri'],
client_id=creds_data['client_id'],
client_secret=creds_data['client_secret'],
scopes=creds_data['scopes']
)
def __str__(self):
return f"{self.user.username} - {self.email}"
class GmailConversation(models.Model):
"""Gmail对话记录跟踪用户和达人之间的邮件交互"""
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='gmail_conversations')
conversation_id = models.CharField(max_length=100, unique=True, help_text="Unique conversation identifier")
user_email = models.EmailField(help_text="User's Gmail address")
influencer_email = models.EmailField(help_text="Influencer's email address")
title = models.CharField(max_length=255, help_text="Conversation title")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
last_sync_time = models.DateTimeField(null=True, blank=True, help_text="Last time conversation was synced with Gmail")
is_active = models.BooleanField(default=True, help_text="Whether this conversation is active")
has_sent_greeting = models.BooleanField(default=False, help_text="Whether a greeting message has been sent to this conversation")
metadata = models.JSONField(null=True, blank=True, help_text="Additional metadata for the conversation")
def __str__(self):
return f"{self.user.username}: {self.user_email} - {self.influencer_email}"
class Meta:
ordering = ['-updated_at']
unique_together = ('user', 'user_email', 'influencer_email')
class GmailAttachment(models.Model):
"""Gmail附件记录"""
conversation = models.ForeignKey(GmailConversation, on_delete=models.CASCADE, related_name='attachments')
email_message_id = models.CharField(max_length=100, help_text="Gmail邮件ID")
attachment_id = models.TextField(help_text="Gmail附件的唯一标识符可能很长")
filename = models.CharField(max_length=255, help_text="原始文件名")
file_path = models.CharField(max_length=255, help_text="保存在服务器上的路径")
content_type = models.CharField(max_length=100, help_text="MIME类型")
size = models.IntegerField(default=0, help_text="文件大小(字节)")
sender_email = models.EmailField(help_text="发送者邮箱")
chat_message_id = models.CharField(max_length=100, blank=True, null=True, help_text="关联到ChatHistory的消息ID")
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.filename} ({self.size} bytes)"
def get_file_extension(self):
"""获取文件扩展名"""
_, ext = os.path.splitext(self.filename)
return ext.lower()
def get_absolute_url(self):
"""获取文件URL"""
return f"/media/gmail_attachments/{os.path.basename(self.file_path)}"
class Meta:
ordering = ['-created_at']
class ConversationSummary(models.Model):
"""Gmail对话摘要模型用于持久化存储对话摘要"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
conversation = models.OneToOneField('GmailConversation', on_delete=models.CASCADE, related_name='summary')
content = models.TextField(verbose_name='摘要内容')
last_message_id = models.CharField(max_length=255, verbose_name='最后处理的消息ID或ChatHistory的ID', null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'gmail_conversation_summaries'
verbose_name = 'Gmail对话摘要'
verbose_name_plural = 'Gmail对话摘要'
def __str__(self):
return f"对话 {self.conversation.id} 摘要"
class UserGoal(models.Model):
"""用户目标模型 - 存储用户设定的沟通或销售目标"""
STATUS_CHOICES = [
('pending', '待处理'),
('in_progress', '进行中'),
('completed', '已完成'),
('failed', '失败')
]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='goals')
conversation = models.ForeignKey(GmailConversation, on_delete=models.CASCADE, related_name='goals', null=True)
description = models.TextField(verbose_name='目标描述')
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending', verbose_name='目标状态')
is_active = models.BooleanField(default=True, verbose_name='是否激活')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
completion_time = models.DateTimeField(null=True, blank=True, verbose_name='完成时间')
last_activity_time = models.DateTimeField(null=True, blank=True, verbose_name='最后活动时间')
metadata = models.JSONField(default=dict, blank=True, null=True, help_text="存储额外信息")
class Meta:
verbose_name = '用户目标'
verbose_name_plural = '用户目标'
ordering = ['-updated_at']
def __str__(self):
return f"{self.user.username}的目标 - {self.description[:20]}..."
def get_conversation_id(self):
"""获取对话ID"""
if self.conversation:
return self.conversation.conversation_id
return None
class AutoReplyConfig(models.Model):
"""
自动回复配置模型 - 设置特定用户Gmail与达人Gmail的自动回复规则
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='auto_reply_configs')
user_email = models.EmailField(help_text="用户Gmail邮箱")
influencer_email = models.EmailField(help_text="达人Gmail邮箱")
is_enabled = models.BooleanField(default=True, help_text="是否启用自动回复")
goal_description = models.TextField(verbose_name='自动回复的目标描述', help_text="AI回复时参考的目标")
reply_template = models.TextField(blank=True, null=True, help_text="回复模板可选为空则由AI生成")
max_replies = models.IntegerField(default=5, help_text="最大自动回复次数")
current_replies = models.IntegerField(default=0, help_text="当前已自动回复次数")
last_reply_time = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
metadata = models.JSONField(default=dict, blank=True, null=True, help_text="存储额外信息如已处理的消息ID等")
class Meta:
db_table = 'gmail_auto_reply_configs'
verbose_name = 'Gmail自动回复配置'
verbose_name_plural = 'Gmail自动回复配置'
unique_together = ('user', 'user_email', 'influencer_email')
ordering = ['-updated_at']
def __str__(self):
return f"{self.user.username}: {self.user_email}{self.influencer_email}"
def can_reply(self):
"""检查是否可以继续自动回复"""
return self.is_enabled and self.current_replies < self.max_replies
def increment_reply_count(self):
"""增加回复计数并更新时间"""
self.current_replies += 1
self.last_reply_time = timezone.now()
self.save(update_fields=['current_replies', 'last_reply_time', 'updated_at'])
class ProcessedPushNotification(models.Model):
"""
已处理的推送通知记录,用于去重防止重复处理同一个通知
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
message_id = models.CharField(max_length=255, unique=True, help_text="Pub/Sub消息ID")
email_address = models.EmailField(help_text="通知关联的Gmail邮箱")
history_id = models.CharField(max_length=100, help_text="Gmail历史ID")
processed_at = models.DateTimeField(auto_now_add=True, help_text="处理时间")
is_successful = models.BooleanField(default=True, help_text="处理是否成功")
metadata = models.JSONField(default=dict, blank=True, help_text="额外信息")
class Meta:
db_table = 'gmail_processed_push_notifications'
verbose_name = '已处理推送通知'
verbose_name_plural = '已处理推送通知'
ordering = ['-processed_at']
indexes = [
models.Index(fields=['message_id']),
models.Index(fields=['email_address', 'history_id']),
]
def __str__(self):
return f"{self.email_address} - {self.history_id} ({self.message_id})"
class UnmatchedEmail(models.Model):
"""
记录未匹配到对话的邮件,避免重复处理
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
message_id = models.CharField(max_length=255, unique=True, help_text="Gmail邮件ID")
user_id = models.CharField(max_length=36, help_text="用户ID (UUID字符串形式)")
user_email = models.EmailField(help_text="用户Gmail邮箱")
from_email = models.EmailField(help_text="发件人邮箱")
to_email = models.EmailField(help_text="收件人邮箱")
subject = models.CharField(max_length=500, blank=True, help_text="邮件主题")
processed_at = models.DateTimeField(auto_now_add=True, help_text="处理时间")
class Meta:
db_table = 'gmail_unmatched_emails'
verbose_name = '未匹配邮件'
verbose_name_plural = '未匹配邮件'
ordering = ['-processed_at']
indexes = [
models.Index(fields=['message_id']),
models.Index(fields=['user_id']),
models.Index(fields=['user_email', 'from_email']),
]
def __str__(self):
return f"{self.user_email} - {self.from_email} - {self.subject}"