初始提交 - Gmail集成功能优化

This commit is contained in:
wanjia 2025-04-10 18:25:59 +08:00
parent 083971a94a
commit 45d49d4b4a
21 changed files with 3815 additions and 86 deletions

View File

@ -1 +1 @@
{"installed":{"client_id":"240872828479-570luspoc31259l1faab6kmjmpcsa9n9.apps.googleusercontent.com","project_id":"first-renderer-454910-c1","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"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","redirect_uris":["http://localhost"]}}
{"installed":{"client_id":"266164728215-v84lngbp3vgr4ulql01sqkg5vaigf4a5.apps.googleusercontent.com","project_id":"knowledge-454905","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"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","redirect_uris":["http://localhost"]}}

View File

@ -34,8 +34,88 @@
"filename": "test2.txt",
"mimeType": "text/plain",
"size": 996,
"attachmentId": "ANGjdJ-f7-vJBk-5aJnh1zPE0rwbH0PXFzJHsXv9XL5LavJ0r4yQAayw1dw1pQ0AUhgwWyTP_hxcROnZO2C5W3MhBGGFmrcVpjwWE-yFr5BYA-I1-6sn24oEPFqX8Hem4dZxcYd2h6RgSfUfq3BqOe0AtZru0GWvUxRK7kadSCjpP2kigFkVhxvl18MiNmeR5B9aLkkjB6kwSCsTekw8uNTZxh01ZCkjOB1tRiCJnchmh4-TPfOrcZBfV0WNFcbend0DfK9tMRmcs3Gv5aWzFe0mF6eXE5dTAACMEkLwwOrvP-aXRq526DN43RFVasGMvq7-E81pjtcZN_8xPWNs"
"attachmentId": "ANGjdJ8O1uGve4uPqFSC2C4sMeC5jXJ3DGilhB1By705ZLGbOF30m6uITRtJHWsnB7yREKVslhYRdu4GKKvrrkWw-63ogqaZPGgi0WuSoB0OxIWXwbQSjaayUOPvc3P8y1g9A2mMAm5k0DkjH_LP0QMmulhXMg8ZgUcm4CZR7-HTBYztZSfWOoUeYkNugdzV5_2ax3GT34P5uaGHh3i4Ge6y-XDN-TDq3i1w9u_eTjZowoyVzjTj48uaQmzmFU36TQxg8ncovHk0sNZEygzCE1gbHbS1uM9N1tUHOOa6FWEpajHgz2aQwLh65SsMRqKe-LognFES-J_082IHddWs"
}
]
},
{
"id": "1961a1981e915eb3",
"subject": "",
"from": "crush wds <ardonisierni@gmail.com>",
"date": "2025-04-09 18:29:51",
"body": "测试角色1\r\n",
"attachments": []
},
{
"id": "1961a19cc997f933",
"subject": "",
"from": "wds crush <crushwds@gmail.com>",
"date": "2025-04-09 18:30:21",
"body": "测试角色2\r\n",
"attachments": []
},
{
"id": "1961de790e0b39e6",
"subject": "",
"from": "crushwds@gmail.com",
"date": "2025-04-10 00:13:57",
"body": "您好,关于我们之前讨论的产品,我想确认一些细节...",
"attachments": []
},
{
"id": "1961dee46652dc86",
"subject": "",
"from": "crushwds@gmail.com",
"date": "2025-04-10 00:21:17",
"body": "您好,关于我们之前讨论的产品,我想确认一些细节...",
"attachments": []
},
{
"id": "1961dfd7d36bf92c",
"subject": "",
"from": "crushwds@gmail.com",
"date": "2025-04-10 00:37:54",
"body": "您好,关于我们之前讨论的产品,我想确认一些细节...",
"attachments": []
},
{
"id": "1961e39ad02da5f0",
"subject": "",
"from": "crushwds@gmail.com",
"date": "2025-04-10 01:43:38",
"body": "您好,关于我们之前讨论的产品,我想确认一些细节...",
"attachments": []
},
{
"id": "1961df3bd554a4af",
"subject": "",
"from": "crush wds <ardonisierni@gmail.com>",
"date": "2025-04-10 12:27:03",
"body": "yes\r\n",
"attachments": []
},
{
"id": "1961e3944c8aab04",
"subject": "",
"from": "crush wds <ardonisierni@gmail.com>",
"date": "2025-04-10 13:42:58",
"body": "1\r\n",
"attachments": []
},
{
"id": "1961e39660feebd9",
"subject": "",
"from": "crush wds <ardonisierni@gmail.com>",
"date": "2025-04-10 13:43:07",
"body": "2\r\n",
"attachments": []
},
{
"id": "1961ec2028c16037",
"subject": "",
"from": "crush wds <ardonisierni@gmail.com>",
"date": "2025-04-10 16:12:20",
"body": "你好\r\n",
"attachments": []
}
]

View File

@ -1,5 +1,5 @@
==================================================
记录时间: 2025-04-07 14:24:38
记录时间: 2025-04-10 16:52:49
==================================================
时间: 2025-03-27 12:04:29
@ -8,7 +8,6 @@
内容:
你好呀
--------------------------------------------------
时间: 2025-03-27 12:05:54
发件人: wds crush <crushwds@gmail.com>
@ -16,7 +15,6 @@
内容:
你那里天气怎么样
--------------------------------------------------
时间: 2025-03-27 13:13:03
发件人: wds crush <crushwds@gmail.com>
@ -24,7 +22,6 @@
内容:
吃饭了吗
--------------------------------------------------
时间: 2025-04-07 14:24:28
发件人: wds crush <crushwds@gmail.com>
@ -33,19 +30,78 @@
测试附件内容
crush wds <ardonisierni@gmail.com> 于2025年3月27日周四 12:04写道
> 你好呀
>
附件:
- test2.txt (text/plain, 996 字节)
--------------------------------------------------
时间: 2025-04-09 18:29:51
发件人: crush wds <ardonisierni@gmail.com>
主题:
内容:
测试角色1
--------------------------------------------------
时间: 2025-04-09 18:30:21
发件人: wds crush <crushwds@gmail.com>
主题:
内容:
测试角色2
--------------------------------------------------
时间: 2025-04-10 00:13:57
发件人: crushwds@gmail.com
主题:
内容:
您好,关于我们之前讨论的产品,我想确认一些细节...
--------------------------------------------------
时间: 2025-04-10 00:21:17
发件人: crushwds@gmail.com
主题:
内容:
您好,关于我们之前讨论的产品,我想确认一些细节...
--------------------------------------------------
时间: 2025-04-10 00:37:54
发件人: crushwds@gmail.com
主题:
内容:
您好,关于我们之前讨论的产品,我想确认一些细节...
--------------------------------------------------
时间: 2025-04-10 01:43:38
发件人: crushwds@gmail.com
主题:
内容:
您好,关于我们之前讨论的产品,我想确认一些细节...
--------------------------------------------------
时间: 2025-04-10 12:27:03
发件人: crush wds <ardonisierni@gmail.com>
主题:
内容:
yes
--------------------------------------------------
时间: 2025-04-10 13:42:58
发件人: crush wds <ardonisierni@gmail.com>
主题:
内容:
1
--------------------------------------------------
时间: 2025-04-10 13:43:07
发件人: crush wds <ardonisierni@gmail.com>
主题:
内容:
2
--------------------------------------------------
时间: 2025-04-10 16:12:20
发件人: crush wds <ardonisierni@gmail.com>
主题:
内容:
你好
--------------------------------------------------

View File

@ -13,7 +13,7 @@ os.environ['HTTP_PROXY'] = 'http://127.0.0.1:7890'
os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890'
# Gmail API 认证
SCOPES = 'https://www.googleapis.com/auth/gmail.readonly'
SCOPES = ['https://mail.google.com/']
store = file.Storage('storage.json')
creds = store.get()
if not creds or creds.invalid:

View File

@ -1 +1 @@
{"access_token": "ya29.a0AZYkNZjEUAU0zL5oOFtnyDomfMZSzeAVMn5tvmHziV8CaMb4LaK3FAwZ_YC3itk2XlUQlIX0jZ4LQbqZZvdgk7fuwlimFzOHIrfkLt2nd6TPt-L3OqJbmYbYsJRgZ6twrlkX4-nvDsvK0br-WByC5WZj4Ih2FevlMb_-n6J-aCgYKAUUSARISFQHGX2MiYlBZMAAMwlcMNHHm-HnGiA0175", "client_id": "240872828479-570luspoc31259l1faab6kmjmpcsa9n9.apps.googleusercontent.com", "client_secret": "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", "refresh_token": "1//0e45xAmjtJtxpCgYIARAAGA4SNwF-L9Ir7GKiW_h2fGY6auAsSxOtmpocidE68QpFfuUDtwPhSJYHhS_1ZfYDNu8pRcD85X1Kh_Y", "token_expiry": "2025-04-07T07:20:36Z", "token_uri": "https://oauth2.googleapis.com/token", "user_agent": null, "revoke_uri": "https://oauth2.googleapis.com/revoke", "id_token": null, "id_token_jwt": null, "token_response": {"access_token": "ya29.a0AZYkNZjEUAU0zL5oOFtnyDomfMZSzeAVMn5tvmHziV8CaMb4LaK3FAwZ_YC3itk2XlUQlIX0jZ4LQbqZZvdgk7fuwlimFzOHIrfkLt2nd6TPt-L3OqJbmYbYsJRgZ6twrlkX4-nvDsvK0br-WByC5WZj4Ih2FevlMb_-n6J-aCgYKAUUSARISFQHGX2MiYlBZMAAMwlcMNHHm-HnGiA0175", "expires_in": 3599, "refresh_token": "1//0e45xAmjtJtxpCgYIARAAGA4SNwF-L9Ir7GKiW_h2fGY6auAsSxOtmpocidE68QpFfuUDtwPhSJYHhS_1ZfYDNu8pRcD85X1Kh_Y", "scope": "https://www.googleapis.com/auth/gmail.readonly", "token_type": "Bearer", "refresh_token_expires_in": 604799}, "scopes": ["https://www.googleapis.com/auth/gmail.readonly"], "token_info_uri": "https://oauth2.googleapis.com/tokeninfo", "invalid": false, "_class": "OAuth2Credentials", "_module": "oauth2client.client"}
{"access_token": "ya29.a0AZYkNZga-tjDnp1lsXRohu1Tji-eVV88RaLnPjxr3HpYuBDW_6boys1aqnRnete1pT-E7ygZ5drpb0Hhbt9o15ryqbfeaKqS4HTDG_iIVvFn3npNNLSqIdvsf98burhBOnR-Nf6ty7xCsPLyFaO15bG2LybRgGL1mubVNMXSaCgYKAdQSARISFQHGX2MicVi2eoShd196_WeptFDUZg0175", "client_id": "266164728215-v84lngbp3vgr4ulql01sqkg5vaigf4a5.apps.googleusercontent.com", "client_secret": "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", "refresh_token": "1//0eAXpVapw8WjjCgYIARAAGA4SNwF-L9Irm0iHkQzqzM7Hn39nctE-DOWKTsm89Ge3nG0bfdfqloRvLMiN4YWHEKcDpLdPIuZel0Q", "token_expiry": "2025-04-10T09:51:34Z", "token_uri": "https://oauth2.googleapis.com/token", "user_agent": null, "revoke_uri": "https://oauth2.googleapis.com/revoke", "id_token": null, "id_token_jwt": null, "token_response": {"access_token": "ya29.a0AZYkNZga-tjDnp1lsXRohu1Tji-eVV88RaLnPjxr3HpYuBDW_6boys1aqnRnete1pT-E7ygZ5drpb0Hhbt9o15ryqbfeaKqS4HTDG_iIVvFn3npNNLSqIdvsf98burhBOnR-Nf6ty7xCsPLyFaO15bG2LybRgGL1mubVNMXSaCgYKAdQSARISFQHGX2MicVi2eoShd196_WeptFDUZg0175", "expires_in": 3599, "refresh_token": "1//0eAXpVapw8WjjCgYIARAAGA4SNwF-L9Irm0iHkQzqzM7Hn39nctE-DOWKTsm89Ge3nG0bfdfqloRvLMiN4YWHEKcDpLdPIuZel0Q", "scope": "https://mail.google.com/", "token_type": "Bearer", "refresh_token_expires_in": 604799}, "scopes": ["https://mail.google.com/"], "token_info_uri": "https://oauth2.googleapis.com/tokeninfo", "invalid": false, "_class": "OAuth2Credentials", "_module": "oauth2client.client"}

View File

@ -1 +0,0 @@
{"token": "ya29.a0AeXRPp5MvarP9U4JZ3ZlV_xpIeHNL7IK1HEQLX6qhY-thS5IOFsdHrcNwbdNqkYrClFm4yGiMCXzWO-IilVbdSrDfEDw-Qfs7V9oTsZS2RYjbgcSR9rLUtZlL8ObnaljQie5VVzQZysHPZcMSdWiqabcQq2UsU5TNuOWo4J7_waCgYKAQESARISFQHGX2MiAR1O_HNYd0F6z0Rj-vSybA0177", "refresh_token": "1//0e1Usc30XxlXRCgYIARAAGA4SNwF-L9IrOOjEN7EkOSatDMHzTy0KaNdp_jf1XtLn7a5pFSljo3IKnrDdvVnxTO_FX5Asj_UDiAw", "token_uri": "https://oauth2.googleapis.com/token", "client_id": "240872828479-570luspoc31259l1faab6kmjmpcsa9n9.apps.googleusercontent.com", "client_secret": "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", "scopes": ["https://www.googleapis.com/auth/gmail.readonly"], "universe_domain": "googleapis.com", "account": "", "expiry": "2025-03-27T04:39:31.274041Z"}

View File

@ -0,0 +1,10 @@
本期节目内容简介
在参加各类比赛时,肯定会遇到竞争对手。英语单词 rival、opponent、competitor 和 contestant 的含义相似,都可以用来指“与他人之间存在竞争关系的人或团队”。在本集《你问我答》节目中,我们将通过和体育比赛有关的实例来为大家阐释这四个近义词之间的区别和用法。
欢迎你加入并和我们一起讨论英语学习的方方面面。请通过微博“BBC英语教学”或邮件与我们取得联系。我们的邮箱地址是 questions.chinaelt@bbc.co.uk。
文字稿
(关于台词的备注: 请注意这不是广播节目的逐字稿件。本文稿可能没有体现录制、编辑过程中对节目做出的改变。)
Feifei
大家好,欢迎收听 BBC 英语教学的《你问我答》节目,我是冯菲菲。每集节目中,我们会回答大家在英语学习时遇到的一个问题。本集的问题来自 Adela。我们来听一下她的问题。

View File

@ -0,0 +1 @@
{"access_token": "ya29.a0AZYkNZga-tjDnp1lsXRohu1Tji-eVV88RaLnPjxr3HpYuBDW_6boys1aqnRnete1pT-E7ygZ5drpb0Hhbt9o15ryqbfeaKqS4HTDG_iIVvFn3npNNLSqIdvsf98burhBOnR-Nf6ty7xCsPLyFaO15bG2LybRgGL1mubVNMXSaCgYKAdQSARISFQHGX2MicVi2eoShd196_WeptFDUZg0175", "client_id": "266164728215-v84lngbp3vgr4ulql01sqkg5vaigf4a5.apps.googleusercontent.com", "client_secret": "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", "refresh_token": "1//0eAXpVapw8WjjCgYIARAAGA4SNwF-L9Irm0iHkQzqzM7Hn39nctE-DOWKTsm89Ge3nG0bfdfqloRvLMiN4YWHEKcDpLdPIuZel0Q", "token_expiry": "2025-04-10T09:51:34Z", "token_uri": "https://oauth2.googleapis.com/token", "user_agent": null, "revoke_uri": "https://oauth2.googleapis.com/revoke", "id_token": null, "id_token_jwt": null, "token_response": {"access_token": "ya29.a0AZYkNZga-tjDnp1lsXRohu1Tji-eVV88RaLnPjxr3HpYuBDW_6boys1aqnRnete1pT-E7ygZ5drpb0Hhbt9o15ryqbfeaKqS4HTDG_iIVvFn3npNNLSqIdvsf98burhBOnR-Nf6ty7xCsPLyFaO15bG2LybRgGL1mubVNMXSaCgYKAdQSARISFQHGX2MicVi2eoShd196_WeptFDUZg0175", "expires_in": 3599, "refresh_token": "1//0eAXpVapw8WjjCgYIARAAGA4SNwF-L9Irm0iHkQzqzM7Hn39nctE-DOWKTsm89Ge3nG0bfdfqloRvLMiN4YWHEKcDpLdPIuZel0Q", "scope": "https://mail.google.com/", "token_type": "Bearer", "refresh_token_expires_in": 604799}, "scopes": ["https://mail.google.com/"], "token_info_uri": "https://oauth2.googleapis.com/tokeninfo", "invalid": false, "_class": "OAuth2Credentials", "_module": "oauth2client.client"}

View File

@ -142,7 +142,7 @@ TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_TZ = False
USE_TZ = True # 将此项设置为True以启用时区支持
@ -294,3 +294,26 @@ REST_FRAMEWORK = {
'rest_framework.parsers.MultiPartParser'
],
}
# Gmail API配置
GOOGLE_CLOUD_PROJECT = 'knowledge-454905' # 更新为当前使用的项目ID
GMAIL_API_SCOPES = ['https://mail.google.com/']
GMAIL_TOPIC_NAME = 'gmail-watch-topic'
# Gmail webhook地址 (开发环境使用本机内网穿透地址)
GMAIL_WEBHOOK_URL = 'https://a7a4-116-227-35-74.ngrok-free.app/api/user/gmail/webhook/'
# 如果在生产环境,使用以下固定地址
# GMAIL_WEBHOOK_URL = 'https://你的域名/api/user/gmail/webhook/'
# 媒体文件目录
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
# 时区设置
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = True # 将此项设置为True以启用时区支持

View File

@ -18,6 +18,7 @@ from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from user_management.views import gmail_webhook # 直接导入视图函数
urlpatterns = [
# 管理后台
@ -26,6 +27,10 @@ urlpatterns = [
# API路由
path('api/', include('user_management.urls')),
# 专用Gmail Webhook路由 - 直接匹配根路径
path('api/user/gmail/webhook/', gmail_webhook, name='root_gmail_webhook'), # 修改为正确路径
path('gmail/webhook/', gmail_webhook, name='alt_gmail_webhook'), # 添加备用路径
# 媒体文件服务
*static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT),

1
temp_client_secret.json Normal file
View File

@ -0,0 +1 @@
{"installed": {"client_id": "240872828479-570luspoc31259l1faab6kmjmpcsa9n9.apps.googleusercontent.com", "project_id": "first-renderer-454910-c1", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_secret": "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", "redirect_uris": ["http://localhost"]}}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
# 管理命令包

View File

@ -0,0 +1 @@
# Gmail管理命令

View File

@ -0,0 +1,80 @@
from django.core.management.base import BaseCommand, CommandError
from user_management.models import GmailCredential, User
from user_management.gmail_integration import GmailIntegration
import logging
import pickle
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = '更新Gmail凭证中的邮箱信息'
def add_arguments(self, parser):
parser.add_argument('--email', type=str, help='指定用户邮箱')
parser.add_argument('--gmail', type=str, help='指定要设置的Gmail邮箱')
parser.add_argument('--all', action='store_true', help='更新所有凭证')
def handle(self, *args, **options):
email = options.get('email')
gmail = options.get('gmail')
update_all = options.get('all')
if update_all:
# 更新所有凭证
credentials = GmailCredential.objects.filter(is_active=True)
self.stdout.write(f"找到 {credentials.count()} 个活跃的Gmail凭证")
for credential in credentials:
self._update_credential(credential, gmail)
elif email:
# 更新指定用户的凭证
try:
user = User.objects.get(email=email)
credentials = GmailCredential.objects.filter(user=user, is_active=True)
if not credentials.exists():
raise CommandError(f"未找到用户 {email} 的Gmail凭证")
for credential in credentials:
self._update_credential(credential, gmail)
except User.DoesNotExist:
raise CommandError(f"未找到用户 {email}")
else:
self.stdout.write("请提供--email参数或--all参数")
def _update_credential(self, credential, gmail=None):
"""更新单个凭证"""
user = credential.user
self.stdout.write(f"正在更新用户 {user.email} 的Gmail凭证...")
if gmail:
# 如果指定了Gmail邮箱直接使用
credential.gmail_email = gmail
credential.save()
self.stdout.write(self.style.SUCCESS(f"已手动设置Gmail邮箱为: {gmail}"))
return
# 尝试使用API获取Gmail邮箱
try:
# 从凭证数据中恢复服务
creds = pickle.loads(credential.credentials)
if creds and not creds.invalid:
# 创建Gmail集成实例
integration = GmailIntegration(user=user)
integration.credentials = creds
# 尝试调用API获取用户资料
profile = integration.gmail_service.users().getProfile(userId='me').execute()
gmail_email = profile.get('emailAddress')
if gmail_email:
credential.gmail_email = gmail_email
credential.save()
self.stdout.write(self.style.SUCCESS(f"已更新Gmail邮箱为: {gmail_email}"))
else:
self.stdout.write(self.style.WARNING("无法从API获取Gmail邮箱"))
else:
self.stdout.write(self.style.ERROR("凭证无效,请重新授权"))
except Exception as e:
self.stdout.write(self.style.ERROR(f"更新失败: {str(e)}"))

View File

@ -0,0 +1,71 @@
# Generated by Django 5.1.5 on 2025-04-09 15:16
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user_management', '0004_knowledgebasedocument'),
]
operations = [
migrations.CreateModel(
name='GmailAttachment',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('gmail_message_id', models.CharField(max_length=100, verbose_name='Gmail消息ID')),
('filename', models.CharField(max_length=255, verbose_name='文件名')),
('filepath', models.CharField(max_length=500, verbose_name='文件路径')),
('mimetype', models.CharField(max_length=100, verbose_name='MIME类型')),
('filesize', models.IntegerField(default=0, verbose_name='文件大小')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('chat_message', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='gmail_attachments', to='user_management.chathistory')),
],
options={
'verbose_name': 'Gmail附件',
'verbose_name_plural': 'Gmail附件',
'db_table': 'gmail_attachments',
},
),
migrations.CreateModel(
name='GmailCredential',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('token_path', models.CharField(max_length=255, verbose_name='Token存储路径')),
('last_history_id', models.CharField(blank=True, max_length=100, null=True, verbose_name='上次同步历史ID')),
('watch_expiration', models.DateTimeField(blank=True, null=True, verbose_name='监听过期时间')),
('is_active', models.BooleanField(default=True, verbose_name='是否激活')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='gmail_credentials', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Gmail认证凭据',
'verbose_name_plural': 'Gmail认证凭据',
'db_table': 'gmail_credentials',
},
),
migrations.CreateModel(
name='GmailTalentMapping',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('talent_email', models.EmailField(max_length=254, verbose_name='达人邮箱')),
('conversation_id', models.CharField(max_length=100, verbose_name='对话ID')),
('is_active', models.BooleanField(default=True, verbose_name='是否激活')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('knowledge_base', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='gmail_mappings', to='user_management.knowledgebase')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='gmail_talent_mappings', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Gmail达人映射',
'verbose_name_plural': 'Gmail达人映射',
'db_table': 'gmail_talent_mappings',
'unique_together': {('user', 'talent_email')},
},
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.1.5 on 2025-04-09 16:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user_management', '0005_gmailattachment_gmailcredential_gmailtalentmapping'),
]
operations = [
migrations.AddField(
model_name='gmailcredential',
name='credentials',
field=models.BinaryField(blank=True, null=True, verbose_name='序列化的凭证数据'),
),
]

View File

@ -0,0 +1,57 @@
# Generated by Django 5.1.5 on 2025-04-10 09:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user_management', '0006_gmailcredential_credentials'),
]
operations = [
migrations.AlterModelOptions(
name='gmailcredential',
options={},
),
migrations.AlterField(
model_name='gmailcredential',
name='created_at',
field=models.DateTimeField(auto_now_add=True),
),
migrations.AlterField(
model_name='gmailcredential',
name='credentials',
field=models.BinaryField(default=b''),
preserve_default=False,
),
migrations.AlterField(
model_name='gmailcredential',
name='is_active',
field=models.BooleanField(default=True),
),
migrations.AlterField(
model_name='gmailcredential',
name='last_history_id',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AlterField(
model_name='gmailcredential',
name='token_path',
field=models.CharField(max_length=255),
),
migrations.AlterField(
model_name='gmailcredential',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
migrations.AlterField(
model_name='gmailcredential',
name='watch_expiration',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AlterModelTable(
name='gmailcredential',
table='gmail_credential',
),
]

View File

@ -707,3 +707,61 @@ class KnowledgeBaseDocument(models.Model):
def __str__(self):
return f"{self.knowledge_base.name} - {self.document_name}"
class GmailCredential(models.Model):
"""Gmail认证信息"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='gmail_credentials')
gmail_email = models.EmailField(max_length=255, null=True, blank=True, help_text="实际授权的Gmail账号可能与user.email不同")
credentials = models.BinaryField() # 序列化的凭证对象
token_path = models.CharField(max_length=255) # token存储路径
last_history_id = models.CharField(max_length=255, null=True, blank=True) # 最后处理的historyId
watch_expiration = models.DateTimeField(null=True, blank=True) # 监听过期时间
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
is_active = models.BooleanField(default=True)
class Meta:
db_table = 'gmail_credential'
def __str__(self):
return f"{self.user.username}的Gmail认证"
class GmailTalentMapping(models.Model):
"""Gmail达人映射关系模型"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='gmail_talent_mappings')
talent_email = models.EmailField(verbose_name='达人邮箱')
knowledge_base = models.ForeignKey(KnowledgeBase, on_delete=models.CASCADE, related_name='gmail_mappings')
conversation_id = models.CharField(max_length=100, verbose_name='对话ID')
is_active = models.BooleanField(default=True, verbose_name='是否激活')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'gmail_talent_mappings'
unique_together = ['user', 'talent_email']
verbose_name = 'Gmail达人映射'
verbose_name_plural = 'Gmail达人映射'
def __str__(self):
return f"{self.user.username} - {self.talent_email}"
class GmailAttachment(models.Model):
"""Gmail附件模型"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
chat_message = models.ForeignKey(ChatHistory, on_delete=models.CASCADE, related_name='gmail_attachments')
gmail_message_id = models.CharField(max_length=100, verbose_name='Gmail消息ID')
filename = models.CharField(max_length=255, verbose_name='文件名')
filepath = models.CharField(max_length=500, verbose_name='文件路径')
mimetype = models.CharField(max_length=100, verbose_name='MIME类型')
filesize = models.IntegerField(default=0, verbose_name='文件大小')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
class Meta:
db_table = 'gmail_attachments'
verbose_name = 'Gmail附件'
verbose_name_plural = 'Gmail附件'
def __str__(self):
return f"{self.filename} ({self.gmail_message_id})"

View File

@ -13,7 +13,19 @@ from .views import (
RegisterView,
LoginView,
LogoutView,
ChatHistoryViewSet
ChatHistoryViewSet,
user_profile,
user_register,
setup_gmail_integration,
send_gmail_message,
gmail_webhook,
get_gmail_attachments,
download_gmail_attachment,
get_gmail_talents,
refresh_gmail_watch,
check_gmail_auth,
import_gmail_from_sender,
sync_talent_emails
)
# 创建路由器
@ -42,4 +54,16 @@ urlpatterns = [
path('users/<str:pk>/', user_detail, name='user-detail'),
path('users/<str:pk>/update/', user_update, name='user-update'),
path('users/<str:pk>/delete/', user_delete, name='user-delete'),
# Gmail集成API
path('gmail/setup/', setup_gmail_integration, name='setup_gmail_integration'),
path('gmail/send/', send_gmail_message, name='send_gmail_message'),
path('gmail/webhook/', gmail_webhook, name='gmail_webhook'),
path('gmail/attachments/', get_gmail_attachments, name='get_gmail_attachments'),
path('gmail/download/', download_gmail_attachment, name='download_gmail_attachment'),
path('gmail/talents/', get_gmail_talents, name='get_gmail_talents'),
path('gmail/refresh-watch/', refresh_gmail_watch, name='refresh_gmail_watch'),
path('gmail/check-auth/', check_gmail_auth, name='check_gmail_auth'),
path('gmail/import-from-sender/', import_gmail_from_sender, name='import_gmail_from_sender'),
path('gmail/sync-talent/', sync_talent_emails, name='sync_talent_emails'),
]

File diff suppressed because it is too large Load Diff