将达人已回复导出到excel

This commit is contained in:
wanjia 2025-05-21 11:14:08 +08:00
parent 82e418b684
commit 487a1a5960
5 changed files with 675 additions and 4 deletions

468
apps/gmail/README_GMAIL.md Normal file
View File

@ -0,0 +1,468 @@
# Gmail模块API接口文档 - Apifox测试版
## 1. 认证管理
### 1.1 启动OAuth授权
- **接口URL**: `/api/gmail/auth/initiate/`
- **请求方式**: `POST`
- **接口描述**: 生成Google OAuth授权URL供用户访问并获取授权码
#### 请求参数
```json
{
"client_secret_json": "String (必填,二选一): Google OAuth客户端凭证(JSON格式字符串)",
"client_secret_file": "File (必填,二选一): 上传的客户端凭证JSON文件"
}
```
#### 响应参数
```json
{
"auth_url": "String: 用户需访问的授权URL",
"status": "String: 接口调用状态"
}
```
#### 接口作用
生成Google OAuth授权链接用户通过访问此链接授予应用访问Gmail账户的权限并获取授权码用于后续步骤。
---
### 1.2 完成OAuth授权
- **接口URL**: `/api/gmail/auth/complete/`
- **请求方式**: `POST`
- **接口描述**: 使用授权码完成OAuth流程获取并保存Gmail访问凭证
#### 请求参数
```json
{
"client_secret_json": "String (必填): Google OAuth客户端凭证(JSON格式)",
"auth_code": "String (必填): 用户从授权URL获得的授权码"
}
```
#### 响应参数
```json
{
"id": "Integer: 凭证ID",
"email": "String: 关联的Gmail邮箱地址",
"is_default": "Boolean: 是否为默认账户",
"created_at": "DateTime: 创建时间",
"status": "String: 操作状态"
}
```
#### 接口作用
完成OAuth2.0授权流程将用户的授权码转换为访问令牌并将凭证保存到系统中以便后续API调用。
---
### 1.3 获取Gmail凭证列表
- **接口URL**: `/api/gmail/credentials/`
- **请求方式**: `GET`
- **接口描述**: 获取系统中所有已授权的Gmail账户凭证列表
#### 响应参数
```json
{
"results": [
{
"id": "Integer: 凭证ID",
"email": "String: Gmail邮箱地址",
"is_default": "Boolean: 是否为默认账户",
"created_at": "DateTime: 创建时间"
}
],
"count": "Integer: 总记录数"
}
```
#### 接口作用
列出系统中所有已授权的Gmail账户便于用户查看和管理多个Gmail账户。
---
### 1.4 设置默认Gmail账户
- **接口URL**: `/api/gmail/credentials/{id}/set_default/`
- **请求方式**: `POST`
- **接口描述**: 将指定ID的Gmail账户设置为默认账户
#### 路径参数
```json
{
"id": "Integer (必填): 凭证ID"
}
```
#### 响应参数
```json
{
"status": "String: 操作状态",
"message": "String: 操作结果信息"
}
```
#### 接口作用
设置系统默认使用的Gmail账户当未指定账户时系统操作将使用此默认账户。
---
## 2. 对话管理
### 2.1 创建Gmail对话
- **接口URL**: `/api/gmail/conversations/`
- **请求方式**: `POST`
- **接口描述**: 创建并保存用户与特定联系人的邮件对话
#### 请求参数
```json
{
"user_email": "String (必填): 用户Gmail邮箱",
"influencer_email": "String (必填): 对话方Gmail邮箱",
"kb_id": "Integer (可选): 关联的知识库ID"
}
```
#### 响应参数
```json
{
"conversation_id": "Integer: 创建的会话ID",
"user_email": "String: 用户Gmail邮箱",
"influencer_email": "String: 对话方Gmail邮箱",
"created_at": "DateTime: 创建时间",
"status": "String: 操作状态"
}
```
#### 接口作用
创建并保存特定邮箱之间的对话关系,为后续邮件往来、自动回复和内容分析建立基础。
---
### 2.2 获取对话列表
- **接口URL**: `/api/gmail/conversations/`
- **请求方式**: `GET`
- **接口描述**: 获取所有保存的Gmail邮件对话
#### 查询参数
```json
{
"user_email": "String (可选): 筛选特定用户的对话",
"influencer_email": "String (可选): 筛选与特定联系人的对话"
}
```
#### 响应参数
```json
{
"results": [
{
"id": "Integer: 对话ID",
"user_email": "String: 用户Gmail邮箱",
"influencer_email": "String: 对话方Gmail邮箱",
"last_message_time": "DateTime: 最后消息时间",
"message_count": "Integer: 消息数量"
}
],
"count": "Integer: 总记录数"
}
```
#### 接口作用
查询系统中保存的所有Gmail对话可通过筛选参数查找特定用户或联系人的对话记录。
---
### 2.3 获取对话摘要
- **接口URL**: `/api/gmail/conversations/summary/{conversation_id}/`
- **请求方式**: `GET`
- **接口描述**: 获取特定对话的AI生成摘要信息
#### 路径参数
```json
{
"conversation_id": "Integer (必填): 对话ID"
}
```
#### 响应参数
```json
{
"conversation_id": "Integer: 对话ID",
"summary": "String: AI生成的对话摘要",
"key_points": ["String: 关键点1", "String: 关键点2"],
"sentiment": "String: 情感分析结果",
"last_updated": "DateTime: 最后更新时间"
}
```
#### 接口作用
使用AI技术自动分析对话内容生成对话摘要、提取关键点并进行情感分析帮助用户快速了解对话内容和进展。
---
## 3. 邮件管理
### 3.1 发送邮件
- **接口URL**: `/api/gmail/send/`
- **请求方式**: `POST`
- **接口描述**: 通过指定Gmail账户发送邮件
#### 请求参数
```json
{
"email": "String (必填): 发件人Gmail邮箱",
"to": "String (必填): 收件人邮箱,多个收件人用逗号分隔",
"subject": "String (必填): 邮件主题",
"body": "String (必填): 邮件正文内容(支持HTML)",
"cc": "String (可选): 抄送邮箱,多个用逗号分隔",
"bcc": "String (可选): 密送邮箱,多个用逗号分隔",
"attachments": ["String: 附件ID1", "String: 附件ID2"]
}
```
#### 响应参数
```json
{
"message_id": "String: 发送的邮件ID",
"thread_id": "String: Gmail线程ID",
"status": "String: 发送状态",
"timestamp": "DateTime: 发送时间戳"
}
```
#### 接口作用
使用已授权的Gmail账户发送邮件支持HTML内容、抄送、密送和附件返回发送状态和邮件ID。
---
### 3.2 获取附件列表
- **接口URL**: `/api/gmail/attachments/{conversation_id}/`
- **请求方式**: `GET`
- **接口描述**: 获取特定对话中的所有附件
#### 路径参数
```json
{
"conversation_id": "Integer (必填): 对话ID"
}
```
#### 响应参数
```json
{
"results": [
{
"id": "String: 附件ID",
"filename": "String: 文件名",
"size": "Integer: 文件大小(字节)",
"mime_type": "String: MIME类型",
"message_id": "String: 所属邮件ID",
"created_at": "DateTime: 创建时间"
}
],
"count": "Integer: 总记录数"
}
```
#### 接口作用
检索特定对话中的所有邮件附件信息,包括文件名、大小和类型,便于管理和下载附件。
---
## 4. 推送通知
### 4.1 设置Gmail推送通知
- **接口URL**: `/api/gmail/notifications/setup/`
- **请求方式**: `POST`
- **接口描述**: 为指定Gmail账户设置实时邮件推送通知
#### 请求参数
```json
{
"email": "String (必填): 要监听的Gmail邮箱地址",
"topic_name": "String (可选): 自定义Pub/Sub主题名称"
}
```
#### 响应参数
```json
{
"status": "String: 设置状态",
"topic": "String: Pub/Sub主题名称",
"expiration": "DateTime: 通知过期时间",
"message": "String: 操作结果信息"
}
```
#### 接口作用
使用Google Pub/Sub为Gmail账户设置实时通知当有新邮件时系统会接收到推送通知从而实现邮件的实时处理。
---
### 4.2 接收Gmail推送通知
- **接口URL**: `/api/gmail/webhook/`
- **请求方式**: `POST`
- **接口描述**: 接收Google Pub/Sub推送的Gmail新邮件通知
#### 请求参数
```json
{
"message": {
"data": "String (必填): Base64编码的通知数据",
"attributes": "Object (可选): 消息属性"
},
"subscription": "String (必填): 订阅名称"
}
```
#### 响应参数
```json
{
"status": "String: 处理状态",
"received": "Boolean: 是否成功接收"
}
```
#### 接口作用
作为Webhook端点接收Google Pub/Sub推送的Gmail新邮件通知解析通知内容并触发相应的处理逻辑如自动回复和更新对话。
---
## 5. 自动回复功能
### 5.1 设置对话目标
- **接口URL**: `/api/gmail/goals/`
- **请求方式**: `POST`
- **接口描述**: 为特定对话设置AI自动回复的目标
#### 请求参数
```json
{
"conversation_id": "Integer (必填): 对话ID",
"goal_description": "String (必填): 目标描述文本",
"priority": "Integer (可选): 优先级(1-5)",
"auto_reply": "Boolean (可选): 是否启用自动回复"
}
```
#### 响应参数
```json
{
"id": "Integer: 目标ID",
"conversation_id": "Integer: 对话ID",
"goal_description": "String: 目标描述",
"priority": "Integer: 优先级",
"auto_reply": "Boolean: 是否启用自动回复",
"created_at": "DateTime: 创建时间",
"status": "String: 操作状态"
}
```
#### 接口作用
为指定的Gmail对话设置AI处理目标系统会根据目标生成推荐回复或自动回复邮件帮助用户实现特定的沟通目标。
---
### 5.2 获取对话目标列表
- **接口URL**: `/api/gmail/goals/`
- **请求方式**: `GET`
- **接口描述**: 获取所有设置的对话目标
#### 查询参数
```json
{
"conversation_id": "Integer (可选): 筛选特定对话的目标",
"active": "Boolean (可选): 筛选活跃/非活跃目标"
}
```
#### 响应参数
```json
{
"results": [
{
"id": "Integer: 目标ID",
"conversation_id": "Integer: 对话ID",
"goal_description": "String: 目标描述",
"priority": "Integer: 优先级",
"auto_reply": "Boolean: 是否启用自动回复",
"created_at": "DateTime: 创建时间"
}
],
"count": "Integer: 总记录数"
}
```
#### 接口作用
获取系统中设置的所有对话目标,可通过参数筛选特定对话的目标或活跃/非活跃目标。
---
### 5.3 获取推荐回复
- **接口URL**: `/api/gmail/recommended-reply/`
- **请求方式**: `POST`
- **接口描述**: 获取针对特定对话的AI推荐回复内容
#### 请求参数
```json
{
"conversation_id": "Integer (必填): 对话ID",
"last_message_id": "String (可选): 最后接收的邮件ID",
"custom_instructions": "String (可选): 自定义回复指示"
}
```
#### 响应参数
```json
{
"goal": {
"id": "Integer: 目标ID",
"description": "String: 目标描述"
},
"conversation_summary": "String: 对话摘要",
"last_message": {
"sender": "String: 发送者",
"subject": "String: 主题",
"content": "String: 内容摘要"
},
"recommended_reply": "String: AI推荐的回复内容",
"alternative_replies": ["String: 备选回复1", "String: 备选回复2"]
}
```
#### 接口作用
基于对话历史、设定目标和最新邮件内容使用AI生成推荐回复帮助用户快速回应邮件并实现沟通目标。
---
## 使用说明
1. **授权流程**:
- 先调用"启动OAuth授权"接口获取授权URL
- 用户访问URL并获取授权码
- 调用"完成OAuth授权"接口保存Gmail账户凭证
2. **实时通知设置**:
- 授权成功后调用"设置Gmail推送通知"接口
- 确保webhook端点可从公网访问
3. **自动回复流程**:
- 创建对话并设置目标
- 系统接收新邮件推送通知
- 自动分析内容并生成回复
- 根据设置决定是推荐回复还是自动回复

View File

@ -1,3 +0,0 @@

View File

@ -0,0 +1,128 @@
import os
import logging
import pandas as pd
from datetime import datetime
from django.db.models import Q, F
from django.utils import timezone
from apps.gmail.models import GmailConversation, GmailAttachment, ConversationSummary
from apps.chat.models import ChatHistory
logger = logging.getLogger(__name__)
class GmailExportService:
"""
Gmail导出服务提供将达人回复导出为Excel的功能
"""
@staticmethod
def export_replied_influencers(user, format='xlsx'):
"""
导出已回复的达人Gmail列表
Args:
user: 用户对象
format: 导出格式, 默认为xlsx
Returns:
tuple: (文件路径, 错误信息)
"""
try:
# 获取用户所有的Gmail对话
conversations = GmailConversation.objects.filter(
user=user,
is_active=True
)
if not conversations.exists():
return None, "未找到任何Gmail对话"
# 准备导出数据
export_data = []
for conversation in conversations:
# 查询达人是否有回复
chat_history = ChatHistory.objects.filter(
user=user,
conversation_id=conversation.conversation_id,
metadata__contains={"from": conversation.influencer_email}
).order_by('created_at')
# 如果达人有回复,则添加到导出列表
if chat_history.exists():
# 获取第一条回复的时间
first_reply = chat_history.first()
first_reply_time = first_reply.created_at.replace(tzinfo=None) if first_reply else None
# 获取最后一条回复的时间
last_reply = chat_history.last()
last_reply_time = last_reply.created_at.replace(tzinfo=None) if last_reply else None
# 获取回复次数
reply_count = chat_history.count()
# 直接从数据库中获取对话摘要不调用外部API
summary = ""
try:
# 先从ConversationSummary模型中查找
conversation_summary = ConversationSummary.objects.filter(
conversation=conversation
).first()
if conversation_summary:
summary = conversation_summary.content
else:
# 如果没有摘要,尝试获取最新的几条消息内容
recent_messages = ChatHistory.objects.filter(
user=user,
conversation_id=conversation.conversation_id
).order_by('-created_at')[:5]
if recent_messages:
summary = "最近消息: " + " | ".join([msg.content[:100] + ("..." if len(msg.content) > 100 else "") for msg in recent_messages])
except Exception as e:
logger.error(f"获取对话摘要失败: {str(e)}")
summary = '无法获取摘要'
# 构建导出记录 - 注意移除时区信息
export_record = {
'用户邮箱': conversation.user_email,
'达人邮箱': conversation.influencer_email,
'对话标题': conversation.title,
'对话开始时间': conversation.created_at.replace(tzinfo=None),
'首次回复时间': first_reply_time,
'最近回复时间': last_reply_time,
'回复次数': reply_count,
'对话摘要': summary,
'对话ID': conversation.conversation_id
}
export_data.append(export_record)
if not export_data:
return None, "没有找到已回复的达人"
# 创建DataFrame
df = pd.DataFrame(export_data)
# 生成文件名和路径
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
file_name = f"influencer_replies_{timestamp}.{format}"
export_dir = os.path.join('media', 'exports')
os.makedirs(export_dir, exist_ok=True)
file_path = os.path.join(export_dir, file_name)
# 根据格式导出
if format == 'xlsx':
df.to_excel(file_path, index=False, engine='openpyxl')
elif format == 'csv':
df.to_csv(file_path, index=False, encoding='utf-8-sig')
else:
return None, f"不支持的导出格式: {format}"
return file_path, None
except Exception as e:
logger.error(f"导出已回复达人失败: {str(e)}")
import traceback
logger.error(traceback.format_exc())
return None, f"导出失败: {str(e)}"

View File

@ -11,7 +11,8 @@ from .views import (
GmailConversationSummaryView,
GmailGoalView,
SimpleRecommendedReplyView,
GmailCredentialViewSet
GmailCredentialViewSet,
GmailExportView
)
app_name = 'gmail'
@ -34,6 +35,7 @@ urlpatterns = [
path('conversations/summary/<str:conversation_id>/', GmailConversationSummaryView.as_view(), name='conversation_summary_detail'),
path('goals/', GmailGoalView.as_view(), name='user_goals'),
path('recommended-reply/', SimpleRecommendedReplyView.as_view(), name='recommended_reply'),
path('export/', GmailExportView.as_view(), name='export_replied_influencers'),
# 包含Router生成的URL
path('', include(router.urls)),
]

View File

@ -1393,3 +1393,79 @@ class SimpleRecommendedReplyView(APIView):
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class GmailExportView(APIView):
"""
API视图用于导出已回复的达人Gmail列表为Excel文件
"""
permission_classes = [IsAuthenticated] # 限制访问,仅允许已认证用户
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.username} 请求导出已回复达人列表,格式: {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)