'task2多docid'

This commit is contained in:
zelong 2026-01-13 15:59:40 +08:00
parent 7db5d87a94
commit 9e809ddf88
5 changed files with 227 additions and 74 deletions

View File

@ -6,7 +6,10 @@ workspace_id = 58335167
[SmartSheet]
# 智能表格文档ID任务二专用
docid = dcOsT3czWy0YEDg38vlDqwVCTjv0kzwC_GU2XmT9wSZctQ0ZJQUAV7vMQ3ljZx-n_NqxzEEYG2DiLAvNdNsHJwgQ
# 支持配置多个docid用逗号分隔例如
# docid = doc1,doc2,doc3
# 同步时会依次对所有表格进行同步
docid = dcOsT3czWy0YEDg38vlDqwVCTjv0kzwC_GU2XmT9wSZctQ0ZJQUAV7vMQ3ljZx-n_NqxzEEYG2DiLAvNdNsHJwgQ,dcHWzWyaHpZNQwUkZzgH5Kfyx9cMvQzVjZIapajGDuXqjS4nEe0LQqOojBL8s3rlwghw4deOgVnbOqHLoxcKzaHg
[Schedule]
# 同步频率(分钟)

View File

@ -1,4 +1,4 @@
{
"access_token": "9-OFdr2nbGo-1sSAYBujyvTtP5v88W0MjLSy3BzHayMEw0tX0tQ8qsbYXshNdHURsLqjXh4dVaRs2HIxvy2gXbWglBg90YtJU_3Yxfz-EXkCUT6Yyt1U1Z6ojUzWkwG6esUX9rWndHWK7hdXusZj5vpHBqVyQpY1Pi3eVKpB28puC_7IXsgbUaW5-JenDP3C7AnAs_x7jbsECXD8Y2XjZymhBKEwf_jZH-iK1Ojnvxk",
"fetch_time": 1768214302.441097
"access_token": "zM0aHsLt58v2YLVMa5YUiLys9JxCUbcS9bFV-gBAi1DrD6b3sG80ym5XPEJRqQYIffqy5XwrwbTmCbfd9OZtlhGS99D6pRtWl0xrX2QPlpV4DsPTrt4XoDHej47dqd59Z0CcpuZR2Ue8tsH-Vfe3b6f0a6JzKAAeK1mIA0PiAeCT9kSJIruNSlmnQHBYK_6GW0CsR9swct36YGTy5pp7sCIz7iYH91fHCDalobsaoF0",
"fetch_time": 1768286609.6865482
}

View File

@ -11,6 +11,7 @@ project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
from src.config import ConfigManager as BaseConfigManager
from typing import List
class Task2ConfigManager(BaseConfigManager):
@ -50,6 +51,46 @@ class Task2ConfigManager(BaseConfigManager):
'workspace_id': workspace_id
}
def get_smartsheet_config(self):
"""
获取智能表格配置任务二版本支持多个docid
Returns:
dict: 包含docid_list的字典
Raises:
ValueError: 配置项缺失时抛出
"""
if not self.config.has_section('SmartSheet'):
raise ValueError("配置文件缺少[SmartSheet]节")
if not self.config.has_option('SmartSheet', 'docid'):
raise ValueError("配置文件[SmartSheet]节缺少docid配置项")
docid_raw = self.config.get('SmartSheet', 'docid').strip()
if not docid_raw:
raise ValueError("docid配置项不能为空")
# 解析逗号分隔的多个docid
docid_list = [d.strip() for d in docid_raw.split(',') if d.strip()]
if not docid_list:
raise ValueError("docid配置项解析后为空")
return {
'docid': docid_list[0], # 保持向后兼容返回第一个docid
'docid_list': docid_list # 新增返回所有docid列表
}
def get_docid_list(self) -> List[str]:
"""
获取所有配置的docid列表
Returns:
List[str]: docid列表
"""
return self.get_smartsheet_config()['docid_list']
def get_schedule_config(self):
"""
获取调度配置任务二版本只需要sync_interval
@ -120,7 +161,12 @@ class Task2ConfigManager(BaseConfigManager):
try:
smartsheet_config = self.get_smartsheet_config()
print(f"[SmartSheet]")
print(f" docid: {smartsheet_config['docid']}")
docid_list = smartsheet_config['docid_list']
print(f" docid数量: {len(docid_list)}")
for i, docid in enumerate(docid_list, 1):
# 显示docid的前20个字符便于识别
display_id = docid[:20] + "..." if len(docid) > 20 else docid
print(f" docid_{i}: {display_id}")
except ValueError as e:
print(f"[SmartSheet] 配置错误: {e}")

View File

@ -46,39 +46,69 @@ def send_sync_failure_notification(access_token: str, agentid: str,
def _build_sync_failure_message(failed_records: List[Dict]) -> str:
"""
构造同步失败消息内容支持多子表分组
构造同步失败消息内容支持多表格子表分组
Args:
failed_records: 失败记录列表
failed_records: 失败记录列表每条记录可包含
- doc_index: 表格序号可选
- docid_short: 表格ID简写可选
- sheet_title: 子表标题
- record_id: 记录ID
- tapd_link: TAPD链接
- error_message: 失败原因
Returns:
str: 格式化的消息内容
"""
# 按子表分组
records_by_sheet = {}
# 按表格和子表分组
records_by_doc = {}
for record in failed_records:
doc_index = record.get('doc_index', 1)
docid_short = record.get('docid_short', '')
doc_key = (doc_index, docid_short)
if doc_key not in records_by_doc:
records_by_doc[doc_key] = {}
sheet_title = record.get('sheet_title', '未知子表')
if sheet_title not in records_by_sheet:
records_by_sheet[sheet_title] = []
records_by_sheet[sheet_title].append(record)
if sheet_title not in records_by_doc[doc_key]:
records_by_doc[doc_key][sheet_title] = []
records_by_doc[doc_key][sheet_title].append(record)
# 消息头部
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
total_count = len(failed_records)
sheet_count = len(records_by_sheet)
doc_count = len(records_by_doc)
lines = [
"【autoTAPD 同步失败通知】",
f"时间: {timestamp}",
f"失败数量: {total_count} 条(来自 {sheet_count} 个子表)",
f"失败数量: {total_count}",
]
# 如果有多个表格,显示表格数量
if doc_count > 1:
lines.append(f"涉及表格: {doc_count}")
lines.extend([
"",
"以下记录同步失败,请检查:",
"=" * 40
]
])
# 按子表分组显示失败记录
# 按表格和子表分组显示失败记录
global_idx = 1
for sheet_title, sheet_records in records_by_sheet.items():
for (doc_index, docid_short), sheets in sorted(records_by_doc.items()):
# 如果有多个表格,显示表格标识
if doc_count > 1:
doc_label = f"表格{doc_index}"
if docid_short:
doc_label += f" ({docid_short})"
lines.append(f"\n{'#'*20}")
lines.append(f"# {doc_label}")
lines.append(f"{'#'*20}")
for sheet_title, sheet_records in sheets.items():
lines.append(f"\n【子表:{sheet_title}")
lines.append("")

View File

@ -25,7 +25,7 @@ from src2.smartsheet_sync import SmartSheetSync, REQUIRED_FIELDS
class SyncService:
"""TAPD状态同步服务"""
"""TAPD状态同步服务(支持多表格同步)"""
def __init__(self, config_manager: Task2ConfigManager = None,
access_token: str = None, test_mode: bool = False):
@ -49,7 +49,10 @@ class SyncService:
# 获取配置
self.config = self.config_manager.get_all_config()
self.workspace_id = self.config['tapd']['workspace_id']
self.docid = self.config['smartsheet']['docid']
# 获取所有docid列表
self.docid_list = self.config['smartsheet']['docid_list']
print(f" 配置了 {len(self.docid_list)} 个智能表格")
# 获取access_token
if access_token is None:
@ -58,11 +61,10 @@ class SyncService:
else:
self.access_token = access_token
# 初始化API模块
# 初始化TAPD API所有表格共用
self.tapd_api = TAPDStoryApi(self.workspace_id, test_mode=test_mode)
self.smartsheet = SmartSheetSync(self.access_token, self.docid, test_mode=test_mode)
# 获取计划字段映射(每次同步时实时获取
# 获取计划字段映射(每次同步时实时获取,所有表格共用
print(f" 正在获取计划字段映射...")
try:
self.plan_mapping = self.tapd_api.get_plan_mapping()
@ -75,7 +77,7 @@ class SyncService:
def sync_once(self) -> Dict[str, Any]:
"""
执行一次完整的同步流程
执行一次完整的同步流程遍历所有配置的智能表格
Returns:
Dict: 同步结果统计
@ -84,6 +86,9 @@ class SyncService:
"success": False,
"start_time": datetime.now().isoformat(),
"end_time": None,
"docs_total": len(self.docid_list),
"docs_success": 0,
"docs_failed": 0,
"sheets_processed": 0,
"sheets_skipped": 0,
"total_records": 0,
@ -92,68 +97,137 @@ class SyncService:
"records_updated": 0,
"records_failed": 0,
"error_message": None,
"sheet_results": []
"doc_results": [] # 每个表格的详细结果
}
all_failed_records = [] # 汇总所有表格的失败记录
# 遍历所有配置的智能表格
for doc_index, docid in enumerate(self.docid_list, 1):
doc_result = self._sync_single_doc(docid, doc_index, len(self.docid_list))
result["doc_results"].append(doc_result)
if doc_result["success"]:
result["docs_success"] += 1
# 累加统计数据
result["sheets_processed"] += doc_result["sheets_processed"]
result["sheets_skipped"] += doc_result["sheets_skipped"]
result["total_records"] += doc_result["total_records"]
result["records_with_link"] += doc_result["records_with_link"]
result["records_synced"] += doc_result["records_synced"]
result["records_updated"] += doc_result["records_updated"]
result["records_failed"] += doc_result["records_failed"]
# 收集失败记录
all_failed_records.extend(doc_result.get("all_failed_records", []))
else:
result["docs_failed"] += 1
# 判断整体是否成功(至少有一个表格成功)
result["success"] = result["docs_success"] > 0
# 发送失败通知(汇总所有表格的失败记录)
if all_failed_records:
self._send_failure_notification(all_failed_records)
result["end_time"] = datetime.now().isoformat()
return result
def _sync_single_doc(self, docid: str, doc_index: int, total_docs: int) -> Dict[str, Any]:
"""
同步单个智能表格
Args:
docid: 智能表格文档ID
doc_index: 当前表格序号从1开始
total_docs: 表格总数
Returns:
Dict: 单个表格的同步结果
"""
# 显示docid的前16个字符便于识别
display_id = docid[:16] + "..." if len(docid) > 16 else docid
print(f"\n{'#'*70}")
print(f"# [表格 {doc_index}/{total_docs}] docid: {display_id}")
print(f"{'#'*70}")
doc_result = {
"docid": docid,
"doc_index": doc_index,
"success": False,
"sheets_processed": 0,
"sheets_skipped": 0,
"total_records": 0,
"records_with_link": 0,
"records_synced": 0,
"records_updated": 0,
"records_failed": 0,
"error_message": None,
"sheet_results": [],
"all_failed_records": []
}
try:
# 1. 获取所有子表
# 为当前表格创建SmartSheetSync实例
smartsheet = SmartSheetSync(self.access_token, docid, test_mode=self.test_mode)
self.current_smartsheet = smartsheet # 供_process_sheet等方法使用
# 获取所有子表
print("\n正在获取子表列表...")
sheets = self.smartsheet.api.get_sheet_list()
sheets = smartsheet.api.get_sheet_list()
print(f" ✓ 找到 {len(sheets)} 个子表")
# 2. 处理每个子表
# 处理每个子表
for sheet in sheets:
sheet_id = sheet.get('sheet_id', '')
sheet_title = sheet.get('title', '未命名')
sheet_result = self._process_sheet(sheet_id, sheet_title)
result["sheet_results"].append(sheet_result)
sheet_result = self._process_sheet(sheet_id, sheet_title, doc_index)
doc_result["sheet_results"].append(sheet_result)
if sheet_result["skipped"]:
result["sheets_skipped"] += 1
doc_result["sheets_skipped"] += 1
else:
result["sheets_processed"] += 1
result["total_records"] += sheet_result["total_records"]
result["records_with_link"] += sheet_result["records_with_link"]
result["records_synced"] += sheet_result["records_synced"]
result["records_updated"] += sheet_result["records_updated"]
result["records_failed"] += sheet_result["records_failed"]
doc_result["sheets_processed"] += 1
doc_result["total_records"] += sheet_result["total_records"]
doc_result["records_with_link"] += sheet_result["records_with_link"]
doc_result["records_synced"] += sheet_result["records_synced"]
doc_result["records_updated"] += sheet_result["records_updated"]
doc_result["records_failed"] += sheet_result["records_failed"]
result["success"] = True
# 收集失败记录(添加表格标识)
for failed in sheet_result.get("failed_records", []):
failed["doc_index"] = doc_index
failed["docid_short"] = display_id
doc_result["all_failed_records"].append(failed)
# 3. 汇总所有失败记录并发送推送通知
all_failed_records = []
for sheet_result in result["sheet_results"]:
all_failed_records.extend(sheet_result.get("failed_records", []))
# 如果有失败记录,尝试发送推送通知
if all_failed_records:
self._send_failure_notification(all_failed_records)
doc_result["success"] = True
print(f"\n✓ [表格 {doc_index}/{total_docs}] 同步完成")
except Exception as e:
result["error_message"] = str(e)
print(f"\n✗ 同步失败: {e}")
doc_result["error_message"] = str(e)
print(f"\n✗ [表格 {doc_index}/{total_docs}] 同步失败: {e}")
if self.test_mode:
import traceback
traceback.print_exc()
result["end_time"] = datetime.now().isoformat()
return result
return doc_result
def _process_sheet(self, sheet_id: str, sheet_title: str) -> Dict[str, Any]:
def _process_sheet(self, sheet_id: str, sheet_title: str, doc_index: int = 1) -> Dict[str, Any]:
"""
处理单个子表的同步
Args:
sheet_id: 子表ID
sheet_title: 子表标题
doc_index: 表格序号用于日志显示
Returns:
Dict: 子表处理结果
"""
print(f"\n{'='*60}")
print(f"处理子表: {sheet_title}")
print(f"处理子表: {sheet_title} (表格{doc_index})")
print(f"{'='*60}")
sheet_result = {
@ -172,8 +246,8 @@ class SyncService:
try:
# 1. 获取字段信息并检查必要字段
fields = self.smartsheet.api.get_fields(sheet_id)
all_present, missing_fields, field_mapping = self.smartsheet.check_required_fields(fields)
fields = self.current_smartsheet.api.get_fields(sheet_id)
all_present, missing_fields, field_mapping = self.current_smartsheet.check_required_fields(fields)
if not all_present:
sheet_result["skipped"] = True
@ -183,10 +257,10 @@ class SyncService:
# 2. 获取所有记录(只获取一次,供新记录同步和持续同步共用)
print(f"正在获取所有记录...")
all_records = self.smartsheet.get_all_records(sheet_id)
all_records = self.current_smartsheet.get_all_records(sheet_id)
# 3. 获取包含TAPD链接的新记录同步状态为空
records_with_link = self.smartsheet.get_records_with_tapd_link(
records_with_link = self.current_smartsheet.get_records_with_tapd_link(
sheet_id, all_records=all_records
)
sheet_result["records_with_link"] = len(records_with_link)
@ -224,7 +298,7 @@ class SyncService:
if success_records:
print(f"\n正在回写 {len(success_records)} 条成功记录...")
try:
self.smartsheet.batch_update_records(sheet_id, success_records)
self.current_smartsheet.batch_update_records(sheet_id, success_records)
print(f" ✓ 成功记录回写完成")
except Exception as e:
print(f" ✗ 成功记录回写失败: {e}")
@ -234,13 +308,13 @@ class SyncService:
print(f"\n正在回写 {len(failed_record_ids)} 条失败记录的状态...")
try:
failed_updates = [
self.smartsheet.build_update_record(
self.current_smartsheet.build_update_record(
record_id=record_id,
sync_status="失败"
)
for record_id in failed_record_ids
]
self.smartsheet.batch_update_records(sheet_id, failed_updates)
self.current_smartsheet.batch_update_records(sheet_id, failed_updates)
print(f" ✓ 失败记录状态回写完成")
except Exception as e:
print(f" ✗ 失败记录状态回写失败: {e}")
@ -303,7 +377,7 @@ class SyncService:
plan_name = self.tapd_api.map_plan_id_to_name(plan_id)
# 获取当前字段值,判断是否需要更新
current_values = self.smartsheet.get_current_field_values(record_info["record"])
current_values = self.current_smartsheet.get_current_field_values(record_info["record"])
needs_update = self._check_needs_update(
current_values, status, owner, begin_date, due_date, plan_name
@ -311,7 +385,7 @@ class SyncService:
# 构造更新记录(包含业务字段 + 同步状态=成功)
# 即使业务字段没有变化,也要写入同步状态
update_record = self.smartsheet.build_update_record(
update_record = self.current_smartsheet.build_update_record(
record_id=record_info["record_id"],
status=status,
owner=owner,
@ -401,7 +475,7 @@ class SyncService:
}
# 获取需要持续同步的记录
records = self.smartsheet.get_synced_records_for_update(
records = self.current_smartsheet.get_synced_records_for_update(
sheet_id, TERMINAL_STATUSES, all_records=all_records
)
@ -426,13 +500,13 @@ class SyncService:
new_plan = self.tapd_api.map_plan_id_to_name(plan_id)
# 获取当前值并比较
current = self.smartsheet.get_current_field_values(record_info["record"])
current = self.current_smartsheet.get_current_field_values(record_info["record"])
needs_update = self._check_needs_update(
current, new_status, new_owner, new_begin, new_due, new_plan
)
if needs_update:
update_record = self.smartsheet.build_update_record(
update_record = self.current_smartsheet.build_update_record(
record_id=record_info["record_id"],
status=new_status,
owner=new_owner,
@ -450,7 +524,7 @@ class SyncService:
# 批量更新
if updates:
print(f" 正在更新 {len(updates)} 条记录...")
self.smartsheet.batch_update_records(sheet_id, updates)
self.current_smartsheet.batch_update_records(sheet_id, updates)
result["updated"] = len(updates)
print(f" ✓ 持续同步更新完成")
else: