2025-04-17 16:14:00 +08:00
|
|
|
|
import logging
|
|
|
|
|
import traceback
|
2025-04-29 10:22:57 +08:00
|
|
|
|
import os
|
|
|
|
|
import pandas as pd
|
|
|
|
|
from django.http import FileResponse, HttpResponse
|
|
|
|
|
from django.conf import settings
|
2025-04-17 16:14:00 +08:00
|
|
|
|
from rest_framework import status
|
|
|
|
|
from rest_framework.decorators import api_view, permission_classes
|
|
|
|
|
from rest_framework.permissions import IsAuthenticated
|
|
|
|
|
from rest_framework.response import Response
|
|
|
|
|
|
|
|
|
|
from user_management.models import (
|
|
|
|
|
GmailTalentMapping, ChatHistory, ConversationSummary
|
|
|
|
|
)
|
|
|
|
|
from user_management.gmail_integration import GmailIntegration
|
|
|
|
|
|
|
|
|
|
from feishu.feishu_ai_chat import (
|
|
|
|
|
fetch_table_records, find_duplicate_email_creators,
|
|
|
|
|
process_duplicate_emails, auto_chat_session,
|
2025-04-29 10:22:57 +08:00
|
|
|
|
check_goal_achieved, export_feishu_creators_to_excel,
|
|
|
|
|
export_matching_emails_to_excel
|
2025-04-17 16:14:00 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
@api_view(['POST'])
|
|
|
|
|
@permission_classes([IsAuthenticated])
|
|
|
|
|
def process_feishu_table(request):
|
|
|
|
|
"""
|
|
|
|
|
从飞书多维表格读取数据,处理重复邮箱
|
|
|
|
|
|
|
|
|
|
请求参数:
|
|
|
|
|
table_id: 表格ID
|
|
|
|
|
view_id: 视图ID
|
|
|
|
|
app_token: 飞书应用TOKEN (可选)
|
|
|
|
|
access_token: 用户访问令牌 (可选)
|
|
|
|
|
app_id: 应用ID (可选)
|
|
|
|
|
app_secret: 应用密钥 (可选)
|
|
|
|
|
goal_template: 目标内容模板 (可选)
|
|
|
|
|
auto_chat: 是否自动执行AI对话 (可选)
|
|
|
|
|
turns: 自动对话轮次 (可选)
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# 检查用户权限 - 只允许组长使用
|
|
|
|
|
if request.user.role != 'leader':
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": "只有组长角色的用户可以使用此功能"},
|
|
|
|
|
status=status.HTTP_403_FORBIDDEN
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 获取参数
|
|
|
|
|
table_id = request.data.get("table_id", "tbl3oikG3F8YYtVA") # 默认表格ID
|
|
|
|
|
view_id = request.data.get("view_id", "vewSOIsmxc") # 默认视图ID
|
|
|
|
|
app_token = request.data.get("app_token", "XYE6bMQUOaZ5y5svj4vcWohGnmg")
|
|
|
|
|
access_token = request.data.get("access_token", "u-fK0HvbXVte.G2xzYs5oxV6k1nHu1glvFgG00l0Ma24VD")
|
|
|
|
|
app_id = request.data.get("app_id", "cli_a5c97daacb9e500d")
|
|
|
|
|
app_secret = request.data.get("app_secret", "fdVeOCLXmuIHZVmSV0VbJh9wd0Kq1o5y")
|
|
|
|
|
goal_template = request.data.get(
|
|
|
|
|
"goal_template",
|
|
|
|
|
"与达人{handle}(邮箱:{email})建立联系并了解其账号情况,评估合作潜力,处理合作需求,最终目标是达成合作并签约。"
|
|
|
|
|
)
|
|
|
|
|
auto_chat = request.data.get("auto_chat", False)
|
|
|
|
|
turns = request.data.get("turns", 5)
|
|
|
|
|
|
|
|
|
|
logger.info(f"处理飞书表格数据: table_id={table_id}, view_id={view_id}, app_id={app_id}")
|
|
|
|
|
|
|
|
|
|
# 从飞书表格获取记录
|
|
|
|
|
records = fetch_table_records(
|
|
|
|
|
app_token,
|
|
|
|
|
table_id,
|
|
|
|
|
view_id,
|
|
|
|
|
access_token,
|
|
|
|
|
app_id,
|
|
|
|
|
app_secret
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not records:
|
|
|
|
|
logger.warning("未获取到任何记录,可能是表格ID或视图ID不正确,或无权限访问")
|
|
|
|
|
|
|
|
|
|
# 尝试使用SDK中的search方法直接获取
|
|
|
|
|
try:
|
|
|
|
|
import lark_oapi as lark
|
|
|
|
|
from lark_oapi.api.bitable.v1 import (
|
|
|
|
|
SearchAppTableRecordRequest,
|
|
|
|
|
SearchAppTableRecordRequestBody
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 创建client
|
|
|
|
|
client = lark.Client.builder() \
|
|
|
|
|
.enable_set_token(True) \
|
|
|
|
|
.log_level(lark.LogLevel.DEBUG) \
|
|
|
|
|
.build()
|
|
|
|
|
|
|
|
|
|
# 构造请求对象
|
|
|
|
|
request = SearchAppTableRecordRequest.builder() \
|
|
|
|
|
.app_token(app_token) \
|
|
|
|
|
.table_id(table_id) \
|
|
|
|
|
.page_size(20) \
|
|
|
|
|
.request_body(SearchAppTableRecordRequestBody.builder().build()) \
|
|
|
|
|
.build()
|
|
|
|
|
|
|
|
|
|
# 发起请求
|
|
|
|
|
option = lark.RequestOption.builder().user_access_token(access_token).build()
|
|
|
|
|
response = client.bitable.v1.app_table_record.search(request, option)
|
|
|
|
|
|
|
|
|
|
if not response.success():
|
|
|
|
|
logger.error(f"直接搜索请求失败: {response.code}, {response.msg}")
|
|
|
|
|
return Response(
|
|
|
|
|
{"message": f"未获取到任何记录,错误: {response.msg}"},
|
|
|
|
|
status=status.HTTP_404_NOT_FOUND
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 获取记录
|
|
|
|
|
records = response.data.items
|
|
|
|
|
if not records:
|
|
|
|
|
return Response(
|
|
|
|
|
{"message": "未获取到任何记录"},
|
|
|
|
|
status=status.HTTP_404_NOT_FOUND
|
|
|
|
|
)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"尝试直接搜索时出错: {str(e)}")
|
|
|
|
|
logger.error(traceback.format_exc())
|
|
|
|
|
return Response(
|
|
|
|
|
{"message": f"未获取到任何记录,错误: {str(e)}"},
|
|
|
|
|
status=status.HTTP_404_NOT_FOUND
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 查找重复邮箱的创作者
|
|
|
|
|
duplicate_emails = find_duplicate_email_creators(records)
|
|
|
|
|
|
|
|
|
|
if not duplicate_emails:
|
|
|
|
|
return Response(
|
2025-04-29 10:22:57 +08:00
|
|
|
|
{"message": "未找到与系统中已有creator匹配的邮箱"},
|
2025-04-17 16:14:00 +08:00
|
|
|
|
status=status.HTTP_200_OK
|
|
|
|
|
)
|
|
|
|
|
|
2025-04-29 10:22:57 +08:00
|
|
|
|
# 处理匹配的邮箱记录
|
2025-04-17 16:14:00 +08:00
|
|
|
|
results = process_duplicate_emails(duplicate_emails, goal_template)
|
|
|
|
|
|
|
|
|
|
# 如果需要自动对话
|
|
|
|
|
chat_results = []
|
|
|
|
|
if auto_chat and results['success'] > 0:
|
|
|
|
|
# 为每个成功创建的记录执行自动对话
|
|
|
|
|
for detail in results['details']:
|
|
|
|
|
if detail['status'] == 'success':
|
|
|
|
|
email = detail['email']
|
|
|
|
|
chat_result = auto_chat_session(request.user, email, max_turns=turns)
|
|
|
|
|
chat_results.append({
|
|
|
|
|
'email': email,
|
|
|
|
|
'result': chat_result
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# 返回处理结果
|
|
|
|
|
return Response({
|
|
|
|
|
'status': 'success',
|
|
|
|
|
'records_count': len(records),
|
|
|
|
|
'duplicate_emails_count': len(duplicate_emails),
|
|
|
|
|
'processing_results': results,
|
|
|
|
|
'chat_results': chat_results
|
|
|
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"处理飞书表格时出错: {str(e)}")
|
|
|
|
|
logger.error(traceback.format_exc())
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": str(e)},
|
|
|
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@api_view(['POST'])
|
|
|
|
|
@permission_classes([IsAuthenticated])
|
|
|
|
|
def run_auto_chat(request):
|
|
|
|
|
"""
|
|
|
|
|
为指定邮箱执行自动对话
|
|
|
|
|
|
|
|
|
|
请求参数:
|
|
|
|
|
email: 达人邮箱
|
|
|
|
|
force_send: 是否强制发送新邮件(即使没有新回复)(可选)
|
|
|
|
|
subject: 邮件主题(可选,仅当force_send=true时使用)
|
|
|
|
|
content: 邮件内容(可选,仅当force_send=true时使用)
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# 检查用户权限 - 只允许组长使用
|
|
|
|
|
if request.user.role != 'leader':
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": "只有组长角色的用户可以使用此功能"},
|
|
|
|
|
status=status.HTTP_403_FORBIDDEN
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 获取参数
|
|
|
|
|
email = request.data.get("email")
|
|
|
|
|
force_send = request.data.get("force_send", False)
|
|
|
|
|
subject = request.data.get("subject")
|
|
|
|
|
content = request.data.get("content")
|
|
|
|
|
|
|
|
|
|
# 验证必要参数
|
|
|
|
|
if not email:
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": "缺少参数email"},
|
|
|
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 如果强制发送且没有提供内容
|
|
|
|
|
if force_send and not content:
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": "当force_send=true时,必须提供content参数"},
|
|
|
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 首先尝试同步最新邮件
|
|
|
|
|
try:
|
|
|
|
|
# 创建Gmail集成实例
|
|
|
|
|
gmail_integration = GmailIntegration(request.user)
|
|
|
|
|
|
|
|
|
|
# 同步最新邮件
|
|
|
|
|
logger.info(f"正在同步与 {email} 的最新邮件...")
|
|
|
|
|
sync_result = gmail_integration.sync_talent_emails(email)
|
|
|
|
|
|
|
|
|
|
if sync_result.get('status') == 'success':
|
|
|
|
|
logger.info(f"成功同步邮件: {sync_result.get('message', 'No message')}")
|
|
|
|
|
else:
|
|
|
|
|
logger.warning(f"同步邮件警告: {sync_result.get('message', 'Unknown warning')}")
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"同步邮件出错: {str(e)}")
|
|
|
|
|
logger.error(traceback.format_exc())
|
|
|
|
|
# 仅记录错误,不中断流程
|
|
|
|
|
|
|
|
|
|
# 如果是强制发送模式
|
|
|
|
|
if force_send:
|
|
|
|
|
try:
|
2025-04-29 10:22:57 +08:00
|
|
|
|
# 创建Gmail集成实例
|
|
|
|
|
gmail_integration = GmailIntegration(request.user)
|
|
|
|
|
|
|
|
|
|
# 检查Gmail服务是否已正确初始化
|
|
|
|
|
if not hasattr(gmail_integration, 'gmail_service') or gmail_integration.gmail_service is None:
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": "Gmail服务未正确初始化,请检查Gmail API配置"},
|
|
|
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
|
|
|
)
|
|
|
|
|
|
2025-04-17 16:14:00 +08:00
|
|
|
|
# 获取知识库映射
|
|
|
|
|
mapping = GmailTalentMapping.objects.filter(
|
|
|
|
|
user=request.user,
|
|
|
|
|
talent_email=email,
|
|
|
|
|
is_active=True
|
|
|
|
|
).first()
|
|
|
|
|
|
|
|
|
|
if not mapping:
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": f"找不到与邮箱 {email} 的映射关系"},
|
|
|
|
|
status=status.HTTP_404_NOT_FOUND
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 直接发送邮件
|
|
|
|
|
mail_subject = subject if subject else "关于合作的洽谈"
|
|
|
|
|
mail_result = gmail_integration.send_email(
|
|
|
|
|
to_email=email,
|
|
|
|
|
subject=mail_subject,
|
|
|
|
|
body=content,
|
|
|
|
|
conversation_id=mapping.conversation_id
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if mail_result['status'] != 'success':
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": f"邮件发送失败: {mail_result.get('message', 'Unknown error')}"},
|
|
|
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 保存发送的内容到对话历史
|
|
|
|
|
ChatHistory.objects.create(
|
|
|
|
|
user=request.user,
|
|
|
|
|
knowledge_base=mapping.knowledge_base,
|
|
|
|
|
conversation_id=mapping.conversation_id,
|
|
|
|
|
role='assistant',
|
|
|
|
|
content=content
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return Response({
|
|
|
|
|
'status': 'success',
|
|
|
|
|
'message': f"已强制发送邮件到 {email}",
|
|
|
|
|
'email_sent': True,
|
|
|
|
|
'conversation_id': mapping.conversation_id
|
|
|
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"强制发送邮件时出错: {str(e)}")
|
|
|
|
|
logger.error(traceback.format_exc())
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": str(e)},
|
|
|
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 执行自动对话
|
|
|
|
|
result = auto_chat_session(request.user, email)
|
|
|
|
|
|
|
|
|
|
# 返回结果
|
|
|
|
|
return Response(result, status=status.HTTP_200_OK)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"执行自动对话时出错: {str(e)}")
|
|
|
|
|
logger.error(traceback.format_exc())
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": str(e)},
|
|
|
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@api_view(['GET', 'POST'])
|
|
|
|
|
@permission_classes([IsAuthenticated])
|
|
|
|
|
def feishu_user_goal(request):
|
|
|
|
|
"""
|
|
|
|
|
设置或获取用户总目标
|
|
|
|
|
|
|
|
|
|
GET 请求:
|
|
|
|
|
获取当前用户总目标
|
|
|
|
|
|
|
|
|
|
POST 请求参数:
|
|
|
|
|
email: 达人邮箱
|
|
|
|
|
goal: 目标内容
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# 检查用户权限 - 只允许组长使用
|
|
|
|
|
if request.user.role != 'leader':
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": "只有组长角色的用户可以使用此功能"},
|
|
|
|
|
status=status.HTTP_403_FORBIDDEN
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if request.method == 'GET':
|
|
|
|
|
# 创建Gmail集成实例
|
|
|
|
|
gmail_integration = GmailIntegration(request.user)
|
|
|
|
|
# 获取总目标
|
|
|
|
|
result = gmail_integration.manage_user_goal()
|
|
|
|
|
return Response(result, status=status.HTTP_200_OK)
|
|
|
|
|
|
|
|
|
|
elif request.method == 'POST':
|
|
|
|
|
# 获取参数
|
|
|
|
|
email = request.data.get("email")
|
|
|
|
|
goal = request.data.get("goal")
|
|
|
|
|
|
|
|
|
|
# 验证必要参数
|
|
|
|
|
if not email:
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": "缺少参数email"},
|
|
|
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
|
|
|
)
|
|
|
|
|
if not goal:
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": "缺少参数goal"},
|
|
|
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 设置用户总目标
|
|
|
|
|
gmail_integration = GmailIntegration(request.user)
|
|
|
|
|
result = gmail_integration.manage_user_goal(goal)
|
|
|
|
|
|
|
|
|
|
return Response(result, status=status.HTTP_200_OK)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"管理用户总目标时出错: {str(e)}")
|
|
|
|
|
logger.error(traceback.format_exc())
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": str(e)},
|
|
|
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@api_view(['GET'])
|
|
|
|
|
@permission_classes([IsAuthenticated])
|
|
|
|
|
def check_goal_status(request):
|
|
|
|
|
"""
|
|
|
|
|
检查目标完成状态
|
|
|
|
|
|
|
|
|
|
请求参数:
|
|
|
|
|
email: 达人邮箱
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# 检查用户权限 - 只允许组长使用
|
|
|
|
|
if request.user.role != 'leader':
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": "只有组长角色的用户可以使用此功能"},
|
|
|
|
|
status=status.HTTP_403_FORBIDDEN
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 获取参数
|
|
|
|
|
email = request.query_params.get("email")
|
|
|
|
|
|
|
|
|
|
# 验证必要参数
|
|
|
|
|
if not email:
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": "缺少参数email"},
|
|
|
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 查找Gmail映射关系
|
|
|
|
|
mapping = GmailTalentMapping.objects.filter(
|
|
|
|
|
user=request.user,
|
|
|
|
|
talent_email=email,
|
|
|
|
|
is_active=True
|
|
|
|
|
).first()
|
|
|
|
|
|
|
|
|
|
if not mapping:
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": f"找不到与邮箱 {email} 的映射关系"},
|
|
|
|
|
status=status.HTTP_404_NOT_FOUND
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 获取对话历史中最后的AI回复
|
|
|
|
|
last_ai_message = ChatHistory.objects.filter(
|
|
|
|
|
user=request.user,
|
|
|
|
|
knowledge_base=mapping.knowledge_base,
|
|
|
|
|
conversation_id=mapping.conversation_id,
|
|
|
|
|
role='assistant',
|
|
|
|
|
is_deleted=False
|
|
|
|
|
).order_by('-created_at').first()
|
|
|
|
|
|
|
|
|
|
if not last_ai_message:
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": f"找不到与邮箱 {email} 的对话历史"},
|
|
|
|
|
status=status.HTTP_404_NOT_FOUND
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 检查目标是否已达成
|
|
|
|
|
goal_achieved = check_goal_achieved(last_ai_message.content)
|
|
|
|
|
|
|
|
|
|
# 获取对话总结
|
|
|
|
|
summary = ConversationSummary.objects.filter(
|
|
|
|
|
user=request.user,
|
|
|
|
|
talent_email=email,
|
|
|
|
|
is_active=True
|
|
|
|
|
).order_by('-updated_at').first()
|
|
|
|
|
|
|
|
|
|
result = {
|
|
|
|
|
'status': 'success',
|
|
|
|
|
'email': email,
|
|
|
|
|
'goal_achieved': goal_achieved,
|
|
|
|
|
'last_message_time': last_ai_message.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
|
|
|
|
'last_message': last_ai_message.content,
|
|
|
|
|
'summary': summary.summary if summary else None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Response(result, status=status.HTTP_200_OK)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"检查目标状态时出错: {str(e)}")
|
|
|
|
|
logger.error(traceback.format_exc())
|
2025-04-29 10:22:57 +08:00
|
|
|
|
return Response(
|
|
|
|
|
{"error": str(e)},
|
|
|
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@api_view(['POST'])
|
|
|
|
|
@permission_classes([IsAuthenticated])
|
|
|
|
|
def export_creators_data(request):
|
|
|
|
|
"""
|
|
|
|
|
导出匹配的FeishuCreator数据到Excel文件
|
|
|
|
|
|
|
|
|
|
请求参数:
|
|
|
|
|
table_id: 表格ID
|
|
|
|
|
view_id: 视图ID
|
|
|
|
|
app_token: 飞书应用TOKEN (可选)
|
|
|
|
|
access_token: 用户访问令牌 (可选)
|
|
|
|
|
app_id: 应用ID (可选)
|
|
|
|
|
app_secret: 应用密钥 (可选)
|
|
|
|
|
export_type: 导出类型,'creators'或'feishu',默认为'creators'
|
|
|
|
|
format: 格式,支持'excel'或'csv',默认为'excel'
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# 检查用户权限 - 只允许组长使用
|
|
|
|
|
if request.user.role != 'leader':
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": "只有组长角色的用户可以使用此功能"},
|
|
|
|
|
status=status.HTTP_403_FORBIDDEN
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 获取参数
|
|
|
|
|
table_id = request.data.get("table_id", "tbl3oikG3F8YYtVA") # 默认表格ID
|
|
|
|
|
view_id = request.data.get("view_id", "vewSOIsmxc") # 默认视图ID
|
|
|
|
|
app_token = request.data.get("app_token", "XYE6bMQUOaZ5y5svj4vcWohGnmg")
|
|
|
|
|
access_token = request.data.get("access_token", "u-fK0HvbXVte.G2xzYs5oxV6k1nHu1glvFgG00l0Ma24VD")
|
|
|
|
|
app_id = request.data.get("app_id", "cli_a5c97daacb9e500d")
|
|
|
|
|
app_secret = request.data.get("app_secret", "fdVeOCLXmuIHZVmSV0VbJh9wd0Kq1o5y")
|
|
|
|
|
export_type = request.data.get("export_type", "creators") # 导出类型:creators或feishu
|
|
|
|
|
export_format = request.data.get("format", "excel") # 导出格式:excel或csv
|
|
|
|
|
|
|
|
|
|
if export_format not in ["excel", "csv"]:
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": "当前支持的格式有: excel, csv"},
|
|
|
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
logger.info(f"导出飞书数据: table_id={table_id}, view_id={view_id}, type={export_type}, format={export_format}")
|
|
|
|
|
|
|
|
|
|
# 从飞书表格获取记录
|
|
|
|
|
records = fetch_table_records(
|
|
|
|
|
app_token,
|
|
|
|
|
table_id,
|
|
|
|
|
view_id,
|
|
|
|
|
access_token,
|
|
|
|
|
app_id,
|
|
|
|
|
app_secret
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not records:
|
|
|
|
|
logger.warning("未获取到任何记录,可能是表格ID或视图ID不正确,或无权限访问")
|
|
|
|
|
return Response(
|
|
|
|
|
{"message": "未获取到任何记录"},
|
|
|
|
|
status=status.HTTP_404_NOT_FOUND
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 查找重复邮箱的创作者
|
|
|
|
|
duplicate_emails = find_duplicate_email_creators(records)
|
|
|
|
|
|
|
|
|
|
if not duplicate_emails:
|
|
|
|
|
return Response(
|
|
|
|
|
{"message": "未找到与系统中已有creator匹配的邮箱"},
|
|
|
|
|
status=status.HTTP_404_NOT_FOUND
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 创建存储导出文件的目录
|
|
|
|
|
export_dir = os.path.join(settings.MEDIA_ROOT, 'exports')
|
|
|
|
|
os.makedirs(export_dir, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
# 根据导出类型和格式选择输出文件名
|
|
|
|
|
if export_type == "creators":
|
|
|
|
|
file_prefix = "feishu_creators"
|
|
|
|
|
else:
|
|
|
|
|
file_prefix = "feishu_data"
|
|
|
|
|
|
|
|
|
|
if export_format == "excel":
|
|
|
|
|
file_ext = ".xlsx"
|
|
|
|
|
else:
|
|
|
|
|
file_ext = ".csv"
|
|
|
|
|
|
|
|
|
|
output_filename = f"{file_prefix}_{request.user.id}{file_ext}"
|
|
|
|
|
output_path = os.path.join(export_dir, output_filename)
|
|
|
|
|
|
|
|
|
|
# 根据导出类型选择导出函数
|
|
|
|
|
if export_type == "creators":
|
|
|
|
|
# 导出FeishuCreator数据
|
|
|
|
|
if export_format == "excel":
|
|
|
|
|
excel_path = export_feishu_creators_to_excel(duplicate_emails, output_path)
|
|
|
|
|
|
|
|
|
|
if not excel_path:
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": "导出FeishuCreator数据失败"},
|
|
|
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
# CSV格式导出直接处理
|
|
|
|
|
try:
|
|
|
|
|
# 获取所有匹配的邮箱
|
|
|
|
|
emails = list(duplicate_emails.keys())
|
|
|
|
|
|
|
|
|
|
# 从数据库获取FeishuCreator记录
|
|
|
|
|
from user_management.models import FeishuCreator
|
|
|
|
|
creators = FeishuCreator.objects.filter(email__in=emails)
|
|
|
|
|
|
|
|
|
|
if not creators.exists():
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": "没有找到匹配的FeishuCreator记录"},
|
|
|
|
|
status=status.HTTP_404_NOT_FOUND
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 创建数据列表
|
|
|
|
|
data = []
|
|
|
|
|
for creator in creators:
|
|
|
|
|
# 处理datetime字段,移除时区信息
|
|
|
|
|
created_at = creator.created_at
|
|
|
|
|
if hasattr(created_at, 'tzinfo') and created_at.tzinfo is not None:
|
|
|
|
|
created_at = created_at.replace(tzinfo=None)
|
|
|
|
|
|
|
|
|
|
updated_at = creator.updated_at
|
|
|
|
|
if hasattr(updated_at, 'tzinfo') and updated_at.tzinfo is not None:
|
|
|
|
|
updated_at = updated_at.replace(tzinfo=None)
|
|
|
|
|
|
|
|
|
|
row = {
|
|
|
|
|
'id': str(creator.id),
|
|
|
|
|
'handle': creator.handle,
|
|
|
|
|
'email': creator.email,
|
|
|
|
|
'phone': creator.phone,
|
|
|
|
|
'created_at': created_at,
|
|
|
|
|
'updated_at': updated_at,
|
|
|
|
|
# 其他需要的字段...
|
|
|
|
|
}
|
|
|
|
|
data.append(row)
|
|
|
|
|
|
|
|
|
|
# 创建DataFrame并导出到CSV
|
|
|
|
|
df = pd.DataFrame(data)
|
|
|
|
|
df.to_csv(output_path, index=False, encoding='utf-8-sig') # 使用BOM标记以支持中文
|
|
|
|
|
excel_path = output_path
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"导出CSV时出错: {str(e)}")
|
|
|
|
|
logger.error(traceback.format_exc())
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": f"导出CSV失败: {str(e)}"},
|
|
|
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
elif export_type == "feishu":
|
|
|
|
|
# 导出飞书原始数据
|
|
|
|
|
if export_format == "excel":
|
|
|
|
|
excel_path = export_matching_emails_to_excel(duplicate_emails, records, output_path)
|
|
|
|
|
|
|
|
|
|
if not excel_path:
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": "导出飞书数据失败"},
|
|
|
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
# CSV格式导出
|
|
|
|
|
try:
|
|
|
|
|
# 创建数据列表
|
|
|
|
|
data = []
|
|
|
|
|
for email, email_records in duplicate_emails.items():
|
|
|
|
|
for record in email_records:
|
|
|
|
|
fields = record.fields
|
|
|
|
|
row = {
|
|
|
|
|
'Email': email,
|
|
|
|
|
'RecordID': record.record_id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 提取所有字段
|
|
|
|
|
for field_name, field_value in fields.items():
|
|
|
|
|
row[field_name] = extract_field_value(field_value)
|
|
|
|
|
|
|
|
|
|
data.append(row)
|
|
|
|
|
|
|
|
|
|
# 创建DataFrame并导出到CSV
|
|
|
|
|
df = pd.DataFrame(data)
|
|
|
|
|
df.to_csv(output_path, index=False, encoding='utf-8-sig')
|
|
|
|
|
excel_path = output_path
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"导出CSV时出错: {str(e)}")
|
|
|
|
|
logger.error(traceback.format_exc())
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": f"导出CSV失败: {str(e)}"},
|
|
|
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": f"不支持的导出类型: {export_type},可选值为'creators'或'feishu'"},
|
|
|
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 获取服务器域名,考虑各种情况
|
|
|
|
|
domain = request.build_absolute_uri('/').rstrip('/')
|
|
|
|
|
# 如果是本地开发环境,使用127.0.0.1:8000
|
|
|
|
|
if 'localhost' in domain or '127.0.0.1' in domain:
|
|
|
|
|
# 从请求头获取Host
|
|
|
|
|
host = request.META.get('HTTP_HOST', '127.0.0.1:8000')
|
|
|
|
|
if ':' not in host:
|
|
|
|
|
host = f"{host}:8000" # 添加默认端口
|
|
|
|
|
domain = f"http://{host}"
|
|
|
|
|
|
|
|
|
|
# 构建下载URL
|
|
|
|
|
file_url = f"{domain}/api/feishu/download/{output_filename}"
|
|
|
|
|
|
|
|
|
|
return Response({
|
|
|
|
|
"status": "success",
|
|
|
|
|
"message": f"成功导出{export_type}数据,格式为{export_format}",
|
|
|
|
|
"matched_emails": len(duplicate_emails),
|
|
|
|
|
"file_url": file_url,
|
|
|
|
|
"file_name": output_filename
|
|
|
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"导出数据时出错: {str(e)}")
|
|
|
|
|
logger.error(traceback.format_exc())
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": str(e)},
|
|
|
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@api_view(['GET'])
|
|
|
|
|
@permission_classes([IsAuthenticated])
|
|
|
|
|
def download_exported_file(request, filename):
|
|
|
|
|
"""
|
|
|
|
|
下载已导出的Excel文件
|
|
|
|
|
|
|
|
|
|
URL参数:
|
|
|
|
|
filename: 文件名
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# 检查用户权限 - 只允许组长使用
|
|
|
|
|
if request.user.role != 'leader':
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": "只有组长角色的用户可以使用此功能"},
|
|
|
|
|
status=status.HTTP_403_FORBIDDEN
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 构建文件路径
|
|
|
|
|
file_path = os.path.join(settings.MEDIA_ROOT, 'exports', filename)
|
|
|
|
|
|
|
|
|
|
# 检查文件是否存在
|
|
|
|
|
if not os.path.exists(file_path):
|
|
|
|
|
return Response(
|
|
|
|
|
{"error": f"文件不存在: {filename}"},
|
|
|
|
|
status=status.HTTP_404_NOT_FOUND
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 返回文件
|
|
|
|
|
response = FileResponse(open(file_path, 'rb'))
|
|
|
|
|
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"下载文件时出错: {str(e)}")
|
|
|
|
|
logger.error(traceback.format_exc())
|
2025-04-17 16:14:00 +08:00
|
|
|
|
return Response(
|
|
|
|
|
{"error": str(e)},
|
|
|
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
|
|
|
)
|