同步失败消息推送

This commit is contained in:
zelong 2026-01-09 19:01:34 +08:00
parent 7cdd3e9390
commit f91dadffd4
8 changed files with 213 additions and 588 deletions

1
.gitignore vendored
View File

@ -132,6 +132,7 @@ cython_debug/
# Custom
logs/
logs2/
.claude/
# Project documentation

View File

@ -10,4 +10,10 @@ docid = dcOsT3czWy0YEDg38vlDqwVCTjv0kzwC_GU2XmT9wSZctQ0ZJQUAV7vMQ3ljZx-n_NqxzEEY
[Schedule]
# 同步频率(分钟)
sync_interval = 15
sync_interval = 1
[wework]
# 企业微信应用ID
agentid = 1000615
# 接收人列表用户ID多个用|分隔,@all表示全部成员
receivers = 046364

View File

@ -1,4 +1,4 @@
{
"access_token": "OadV3tQWg6mWhvIU4fO5d56EkPyHxcygjX31jTd222norNXqKA9WRp7IzCiAMKmYfAp00N3esAoSx6SR3V0ywXU95xr2yy984mYxxYA1t5_GRQwnYZtMXz9h5U6W6Sk9vDz_aHVLEk69e8W4BZ0i_8XRrOWKci4Kox1IDWDVWcEEicyYu_v5-FmNBWIPaj0_ZdQD7B0wn_PcopGHEvsaJ9r5Y3gb9Ix4wQuAISzHMNM",
"fetch_time": 1767859873.4756753
"access_token": "LsTV1Z6Ye4h2c77s1oKx4zasisAn4QiTbqXmZRUYchZRzp6Q_igpVWp22oEhv8rTHuYyB1LKfqhLjQ9qvgwU13fZns6t-aNBDMw00BBf76V2XqoKKXPHNQZGKQly8RH9NgFMoqfYiKeDnoCdbYnvRjgn8oYoc0AvnCu_qU9TdP8XJgLO01-inoLGSEdTLZnWXf90uf4RztU8xH1a7nuM1p31w58AT7iV3h8Uv9aPOe8",
"fetch_time": 1767954012.568217
}

View File

@ -1,583 +0,0 @@
{
"records": [],
{
"api_type": "test",
"operation": "task2/setup_test",
"timestamp": "2026-01-07 20:14:18",
"success": true,
"request": {
"test": "验证脚本测试"
},
"response": {
"status": "success"
}
} ,
{
"api_type": "tapd",
"operation": "stories",
"timestamp": "2026-01-07 20:48:56",
"success": true,
"request": {
"url": "https://tapd-api.bilibili.co/tapd/stories",
"method": "GET",
"params": {
"workspace_id": "58335167",
"id": "1158335167004800796"
},
"auth_user": "g41_tapd"
},
"response": {
"status": 1,
"data": [
{
"Story": {
"id": "1158335167004800796",
"workitem_type_id": "1158335167001002451",
"app_id": "1",
"name": "12月版本性能优化",
"description": "<p><span style=\"color: #3f4a56;\">【设计文档链接】</span><br /></p><p><br /></p><p>【需求文档链接】</p>",
"workspace_id": "58335167",
"creator": "星渊",
"created": "2025-12-19 15:24:53",
"modified": "2025-12-19 18:04:08",
"status": "status_13",
"step": "",
"owner": "星渊;",
"cc": "",
"begin": null,
"due": null,
"size": null,
"priority": "4",
"developer": "星渊;",
"iteration_id": "0",
"test_focus": "",
"type": "",
"source": "",
"module": "",
"version": "",
"completed": null,
"category_id": "1158335167001017101",
"path": "1158335167004800796:",
"parent_id": "0",
"children_id": "||1158335167004800798|1158335167004800799|1158335167004800802|1158335167004800828",
"ancestor_id": "1158335167004800796",
"level": "0",
"business_value": null,
"effort": null,
"effort_completed": "0",
"exceed": "0",
"remain": "0",
"release_id": "0",
"bug_id": "0",
"templated_id": "1158335167001006075",
"created_from": null,
"feature": "",
"label": "",
"progress": "100",
"is_archived": "0",
"tech_risk": null,
"flows": null,
"custom_field_one": "星渊;",
"custom_field_two": "",
"custom_field_three": "",
"custom_field_four": "",
"custom_field_five": "",
"custom_field_six": "",
"custom_field_seven": "",
"custom_field_eight": "",
"secret_root_id": "0",
"progress_manual": "0",
"custom_field_9": "",
"custom_field_10": "",
"custom_field_11": "",
"custom_field_12": "",
"custom_field_13": "",
"custom_field_14": "",
"custom_field_15": "",
"custom_field_16": "",
"custom_field_17": "",
"custom_field_18": "",
"custom_field_19": "",
"custom_field_20": "",
"custom_field_21": "",
"custom_field_22": "",
"custom_field_23": "",
"custom_field_24": "",
"custom_field_25": "",
"custom_field_26": "",
"custom_field_27": "",
"custom_field_28": "",
"custom_field_29": "",
"custom_field_30": "",
"custom_field_31": "",
"custom_field_32": "",
"custom_field_33": "",
"custom_field_34": "",
"custom_field_35": "",
"custom_field_36": "",
"custom_field_37": "",
"custom_field_38": "",
"custom_field_39": "",
"custom_field_40": "",
"custom_field_41": "",
"custom_field_42": "",
"custom_field_43": "",
"custom_field_44": "",
"custom_field_45": "",
"custom_field_46": "",
"custom_field_47": "",
"custom_field_48": "",
"custom_field_49": "",
"custom_field_50": "",
"custom_field_51": "",
"custom_field_52": "",
"custom_field_53": "",
"custom_field_54": "",
"custom_field_55": "",
"custom_field_56": "",
"custom_field_57": "",
"custom_field_58": "",
"custom_field_59": "",
"custom_field_60": "",
"custom_field_61": "",
"custom_field_62": "",
"custom_field_63": "",
"custom_field_64": "",
"custom_field_65": "",
"custom_field_66": "",
"custom_field_67": "",
"custom_field_68": "",
"custom_field_69": "",
"custom_field_70": "",
"custom_field_71": "",
"custom_field_72": "",
"custom_field_73": "",
"custom_field_74": "",
"custom_field_75": "",
"custom_field_76": "",
"custom_field_77": "",
"custom_field_78": "",
"custom_field_79": "",
"custom_field_80": "",
"custom_field_81": "",
"custom_field_82": "",
"custom_field_83": "",
"custom_field_84": "",
"custom_field_85": "",
"custom_field_86": "",
"custom_field_87": "",
"custom_field_88": "",
"custom_field_89": "",
"custom_field_90": "",
"custom_field_91": "",
"custom_field_92": "",
"custom_field_93": "",
"custom_field_94": "",
"custom_field_95": "",
"custom_field_96": "",
"custom_field_97": "",
"custom_field_98": "",
"custom_field_99": "",
"custom_field_100": "",
"custom_field_101": "",
"custom_field_102": "",
"custom_field_103": "",
"custom_field_104": "",
"custom_field_105": "",
"custom_field_106": "",
"custom_field_107": "",
"custom_field_108": "",
"custom_field_109": "",
"custom_field_110": "",
"custom_field_111": "",
"custom_field_112": "",
"custom_field_113": "",
"custom_field_114": "",
"custom_field_115": "",
"custom_field_116": "",
"custom_field_117": "",
"custom_field_118": "",
"custom_field_119": "",
"custom_field_120": "",
"custom_field_121": "",
"custom_field_122": "",
"custom_field_123": "",
"custom_field_124": "",
"custom_field_125": "",
"custom_field_126": "",
"custom_field_127": "",
"custom_field_128": "",
"custom_field_129": "",
"custom_field_130": "",
"custom_field_131": "",
"custom_field_132": "",
"custom_field_133": "",
"custom_field_134": "",
"custom_field_135": "",
"custom_field_136": "",
"custom_field_137": "",
"custom_field_138": "",
"custom_field_139": "",
"custom_field_140": "",
"custom_field_141": "",
"custom_field_142": "",
"custom_field_143": "",
"custom_field_144": "",
"custom_field_145": "",
"custom_field_146": "",
"custom_field_147": "",
"custom_field_148": "",
"custom_field_149": "",
"custom_field_150": "",
"custom_field_151": "",
"custom_field_152": "",
"custom_field_153": "",
"custom_field_154": "",
"custom_field_155": "",
"custom_field_156": "",
"custom_field_157": "",
"custom_field_158": "",
"custom_field_159": "",
"custom_field_160": "",
"custom_field_161": "",
"custom_field_162": "",
"custom_field_163": "",
"custom_field_164": "",
"custom_field_165": "",
"custom_field_166": "",
"custom_field_167": "",
"custom_field_168": "",
"custom_field_169": "",
"custom_field_170": "",
"custom_field_171": "",
"custom_field_172": "",
"custom_field_173": "",
"custom_field_174": "",
"custom_field_175": "",
"custom_field_176": "",
"custom_field_177": "",
"custom_field_178": "",
"custom_field_179": "",
"custom_field_180": "",
"custom_field_181": "",
"custom_field_182": "",
"custom_field_183": "",
"custom_field_184": "",
"custom_field_185": "",
"custom_field_186": "",
"custom_field_187": "",
"custom_field_188": "",
"custom_field_189": "",
"custom_field_190": "",
"custom_field_191": "",
"custom_field_192": "",
"custom_field_193": "",
"custom_field_194": "",
"custom_field_195": "",
"custom_field_196": "",
"custom_field_197": "",
"custom_field_198": "",
"custom_field_199": "",
"custom_field_200": "",
"custom_plan_field_1": "1158335167001033687",
"custom_plan_field_2": "0",
"custom_plan_field_3": "0",
"custom_plan_field_4": "0",
"custom_plan_field_5": "0",
"custom_plan_field_6": "0",
"custom_plan_field_7": "0",
"custom_plan_field_8": "0",
"custom_plan_field_9": "0",
"custom_plan_field_10": "0",
"priority_label": "High"
}
}
],
"info": "success"
}
} ,
{
"api_type": "tapd",
"operation": "stories",
"timestamp": "2026-01-07 20:50:30",
"success": true,
"request": {
"url": "https://tapd-api.bilibili.co/tapd/stories",
"method": "GET",
"params": {
"workspace_id": "58335167",
"id": "1158335167004774712"
},
"auth_user": "g41_tapd"
},
"response": {
"status": 1,
"data": [
{
"Story": {
"id": "1158335167004774712",
"workitem_type_id": "1158335167001002456",
"app_id": "1",
"name": "破绽-通用特效",
"description": "<p><a href=\"https://doc.weixin.qq.com/sheet/e3_AfIAXgaSAKsCNYIUEmQnvQT6pAabt?scode=ANYAEAdoABEiTVm8UVASEAsgY6AKM&amp;tab=4x5x07\" target=\"_blank\" rel=\"noopener\">https://doc.weixin.qq.com/sheet/e3_AfIAXgaSAKsCNYIUEmQnvQT6pAabt?scode=ANYAEAdoABEiTVm8UVASEAsgY6AKM&amp;tab=4x5x07</a></p>",
"workspace_id": "58335167",
"creator": "Haoo",
"created": "2025-11-24 20:46:53",
"modified": "2025-12-08 14:26:37",
"status": "status_7",
"step": "",
"owner": "parkerluo;",
"cc": "",
"begin": null,
"due": "2025-12-31",
"size": null,
"priority": "2",
"developer": "",
"iteration_id": "0",
"test_focus": "",
"type": "",
"source": "",
"module": "",
"version": "",
"completed": null,
"category_id": "1158335167001017699",
"path": "1158335167004782406::1158335167004788137::1158335167004774712:",
"parent_id": "1158335167004788137",
"children_id": "|",
"ancestor_id": "1158335167004782406",
"level": "2",
"business_value": null,
"effort": null,
"effort_completed": "0",
"exceed": "0",
"remain": "0",
"release_id": "0",
"bug_id": "0",
"templated_id": "1158335167001006073",
"created_from": null,
"feature": "",
"label": "",
"progress": "0",
"is_archived": "0",
"tech_risk": null,
"flows": null,
"custom_field_one": "",
"custom_field_two": "",
"custom_field_three": "",
"custom_field_four": "",
"custom_field_five": "",
"custom_field_six": "",
"custom_field_seven": "",
"custom_field_eight": "",
"secret_root_id": "0",
"progress_manual": "0",
"custom_field_9": "",
"custom_field_10": "",
"custom_field_11": "",
"custom_field_12": "",
"custom_field_13": "",
"custom_field_14": "",
"custom_field_15": "",
"custom_field_16": "",
"custom_field_17": "",
"custom_field_18": "",
"custom_field_19": "",
"custom_field_20": "",
"custom_field_21": "",
"custom_field_22": "",
"custom_field_23": "",
"custom_field_24": "",
"custom_field_25": "",
"custom_field_26": "",
"custom_field_27": "",
"custom_field_28": "",
"custom_field_29": "",
"custom_field_30": "",
"custom_field_31": "",
"custom_field_32": "",
"custom_field_33": "",
"custom_field_34": "",
"custom_field_35": "",
"custom_field_36": "",
"custom_field_37": "",
"custom_field_38": "",
"custom_field_39": "",
"custom_field_40": "",
"custom_field_41": "",
"custom_field_42": "",
"custom_field_43": "",
"custom_field_44": "",
"custom_field_45": "",
"custom_field_46": "",
"custom_field_47": "",
"custom_field_48": "",
"custom_field_49": "",
"custom_field_50": "",
"custom_field_51": "",
"custom_field_52": "",
"custom_field_53": "",
"custom_field_54": "",
"custom_field_55": "",
"custom_field_56": "",
"custom_field_57": "",
"custom_field_58": "",
"custom_field_59": "",
"custom_field_60": "",
"custom_field_61": "",
"custom_field_62": "",
"custom_field_63": "",
"custom_field_64": "",
"custom_field_65": "",
"custom_field_66": "",
"custom_field_67": "",
"custom_field_68": "",
"custom_field_69": "",
"custom_field_70": "",
"custom_field_71": "",
"custom_field_72": "",
"custom_field_73": "",
"custom_field_74": "",
"custom_field_75": "",
"custom_field_76": "",
"custom_field_77": "",
"custom_field_78": "",
"custom_field_79": "",
"custom_field_80": "",
"custom_field_81": "",
"custom_field_82": "",
"custom_field_83": "",
"custom_field_84": "",
"custom_field_85": "",
"custom_field_86": "",
"custom_field_87": "",
"custom_field_88": "",
"custom_field_89": "",
"custom_field_90": "",
"custom_field_91": "",
"custom_field_92": "",
"custom_field_93": "",
"custom_field_94": "",
"custom_field_95": "",
"custom_field_96": "",
"custom_field_97": "",
"custom_field_98": "",
"custom_field_99": "",
"custom_field_100": "",
"custom_field_101": "",
"custom_field_102": "",
"custom_field_103": "",
"custom_field_104": "",
"custom_field_105": "",
"custom_field_106": "",
"custom_field_107": "",
"custom_field_108": "",
"custom_field_109": "",
"custom_field_110": "",
"custom_field_111": "",
"custom_field_112": "",
"custom_field_113": "",
"custom_field_114": "",
"custom_field_115": "",
"custom_field_116": "",
"custom_field_117": "",
"custom_field_118": "",
"custom_field_119": "",
"custom_field_120": "",
"custom_field_121": "",
"custom_field_122": "",
"custom_field_123": "",
"custom_field_124": "",
"custom_field_125": "",
"custom_field_126": "",
"custom_field_127": "",
"custom_field_128": "",
"custom_field_129": "",
"custom_field_130": "",
"custom_field_131": "",
"custom_field_132": "",
"custom_field_133": "",
"custom_field_134": "",
"custom_field_135": "",
"custom_field_136": "",
"custom_field_137": "",
"custom_field_138": "",
"custom_field_139": "",
"custom_field_140": "",
"custom_field_141": "",
"custom_field_142": "",
"custom_field_143": "",
"custom_field_144": "",
"custom_field_145": "",
"custom_field_146": "",
"custom_field_147": "",
"custom_field_148": "",
"custom_field_149": "",
"custom_field_150": "",
"custom_field_151": "",
"custom_field_152": "",
"custom_field_153": "",
"custom_field_154": "",
"custom_field_155": "",
"custom_field_156": "",
"custom_field_157": "",
"custom_field_158": "",
"custom_field_159": "",
"custom_field_160": "",
"custom_field_161": "",
"custom_field_162": "",
"custom_field_163": "",
"custom_field_164": "",
"custom_field_165": "",
"custom_field_166": "",
"custom_field_167": "",
"custom_field_168": "",
"custom_field_169": "",
"custom_field_170": "",
"custom_field_171": "",
"custom_field_172": "",
"custom_field_173": "",
"custom_field_174": "",
"custom_field_175": "",
"custom_field_176": "",
"custom_field_177": "",
"custom_field_178": "",
"custom_field_179": "",
"custom_field_180": "",
"custom_field_181": "",
"custom_field_182": "",
"custom_field_183": "",
"custom_field_184": "",
"custom_field_185": "",
"custom_field_186": "",
"custom_field_187": "",
"custom_field_188": "",
"custom_field_189": "",
"custom_field_190": "",
"custom_field_191": "",
"custom_field_192": "",
"custom_field_193": "",
"custom_field_194": "",
"custom_field_195": "",
"custom_field_196": "",
"custom_field_197": "",
"custom_field_198": "",
"custom_field_199": "",
"custom_field_200": "",
"custom_plan_field_1": "1158335167001033687",
"custom_plan_field_2": "0",
"custom_plan_field_3": "0",
"custom_plan_field_4": "0",
"custom_plan_field_5": "0",
"custom_plan_field_6": "0",
"custom_plan_field_7": "0",
"custom_plan_field_8": "0",
"custom_plan_field_9": "0",
"custom_plan_field_10": "0",
"priority_label": "Low"
}
}
],
"info": "success"
}
}
]}

View File

@ -60,6 +60,7 @@ class SmartSheetAPI:
print("=" * 80)
print(f"请求方法: {method}")
print(f"请求URL: {self.BASE_URL}/{endpoint}")
print(f"完整URL含参数: {url}") # 显示完整URL
if data:
import json
print(f"请求数据:")

View File

@ -76,12 +76,35 @@ class Task2ConfigManager(BaseConfigManager):
return {'sync_interval': sync_interval}
def get_wework_config(self):
"""
获取企业微信推送配置
Returns:
dict: 包含agentid和receivers的字典如果配置不存在则返回None
"""
if not self.config.has_section('wework'):
return None
agentid = self.config.get('wework', 'agentid', fallback='').strip()
receivers = self.config.get('wework', 'receivers', fallback='').strip()
# 如果配置不完整返回None
if not agentid or not receivers:
return None
return {
'agentid': agentid,
'receivers': receivers
}
def get_all_config(self):
"""获取所有配置"""
return {
'tapd': self.get_tapd_config(),
'smartsheet': self.get_smartsheet_config(),
'schedule': self.get_schedule_config()
'schedule': self.get_schedule_config(),
'wework': self.get_wework_config()
}
def print_config(self):
@ -108,6 +131,14 @@ class Task2ConfigManager(BaseConfigManager):
except ValueError as e:
print(f"[Schedule] 配置错误: {e}")
wework_config = self.get_wework_config()
if wework_config:
print(f"[wework]")
print(f" agentid: {wework_config['agentid']}")
print(f" receivers: {wework_config['receivers']}")
else:
print(f"[wework] 未配置(推送功能将被禁用)")
print("======================\n")

106
src2/notifier.py Normal file
View File

@ -0,0 +1,106 @@
"""
任务二企业微信消息推送模块
用于发送同步失败通知
"""
import sys
from pathlib import Path
from typing import List, Dict
from datetime import datetime
# 将项目根目录添加到 Python 路径
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
from src.wework_notifier import WeWorkNotifier
def send_sync_failure_notification(access_token: str, agentid: str,
receivers: str, failed_records: List[Dict]) -> bool:
"""
发送同步失败通知
Args:
access_token: 企业微信access_token
agentid: 应用ID
receivers: 接收人列表
failed_records: 失败记录列表每条记录包含
- sheet_title: 子表标题
- record_id: 记录ID
- tapd_link: TAPD链接
- error_message: 失败原因
Returns:
bool: 是否发送成功
"""
if not failed_records:
return True
# 构造消息内容
content = _build_sync_failure_message(failed_records)
# 使用任务一的推送器发送消息
notifier = WeWorkNotifier(access_token, agentid, receivers)
return notifier._send_text_message(content)
def _build_sync_failure_message(failed_records: List[Dict]) -> str:
"""
构造同步失败消息内容支持多子表分组
Args:
failed_records: 失败记录列表
Returns:
str: 格式化的消息内容
"""
# 按子表分组
records_by_sheet = {}
for record in failed_records:
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)
# 消息头部
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
total_count = len(failed_records)
sheet_count = len(records_by_sheet)
lines = [
"【autoTAPD 同步失败通知】",
f"时间: {timestamp}",
f"失败数量: {total_count} 条(来自 {sheet_count} 个子表)",
"",
"以下记录同步失败,请检查:",
"=" * 40
]
# 按子表分组显示失败记录
global_idx = 1
for sheet_title, sheet_records in records_by_sheet.items():
lines.append(f"\n【子表:{sheet_title}")
lines.append("")
for record in sheet_records:
record_id = record.get('record_id', '未知')
tapd_link = record.get('tapd_link', '(无链接)')
error_message = record.get('error_message', '未知错误')
lines.append(f"[{global_idx}] 记录ID: {record_id}")
lines.append(f"TAPD链接: {tapd_link}")
lines.append(f"失败原因: {error_message}")
lines.append("")
global_idx += 1
lines.append("=" * 40)
lines.append("系统将在下次同步时自动重试失败记录。")
return "\n".join(lines)
if __name__ == "__main__":
print("=== 任务二推送模块 ===")
print("此模块提供同步失败通知功能")
print("请通过 sync_service 或 main.py 调用")

View File

@ -112,6 +112,15 @@ class SyncService:
result["success"] = True
# 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)
except Exception as e:
result["error_message"] = str(e)
print(f"\n✗ 同步失败: {e}")
@ -148,7 +157,8 @@ class SyncService:
"records_synced": 0,
"records_updated": 0,
"records_failed": 0,
"details": []
"details": [],
"failed_records": [] # 收集失败记录的详细信息
}
try:
@ -188,6 +198,15 @@ class SyncService:
sheet_result["records_failed"] += 1
failed_record_ids.append(record_info["record_id"])
# 收集失败记录的详细信息用于推送
failed_record = {
"sheet_title": sheet_title,
"record_id": record_info["record_id"],
"tapd_link": record_info.get("tapd_link", "(无链接)"),
"error_message": record_result.get("error_message", "未知错误")
}
sheet_result["failed_records"].append(failed_record)
# 4. 批量回写成功记录(业务字段 + 同步状态=成功)
if success_records:
print(f"\n正在回写 {len(success_records)} 条成功记录...")
@ -333,6 +352,50 @@ class SyncService:
return False
def _send_failure_notification(self, failed_records: List[Dict]) -> None:
"""
发送同步失败通知
Args:
failed_records: 失败记录列表
"""
try:
# 获取企业微信配置
wework_config = self.config.get('wework')
if not wework_config:
print(" 未配置企业微信推送,跳过失败通知")
return
agentid = wework_config.get('agentid')
receivers = wework_config.get('receivers')
if not agentid or not receivers:
print(" ⚠ 企业微信推送配置不完整,跳过失败通知")
return
# 发送推送通知
print(f"\n正在发送同步失败通知...")
from src2.notifier import send_sync_failure_notification
success = send_sync_failure_notification(
self.access_token,
agentid,
receivers,
failed_records
)
if success:
print(f" ✓ 失败通知已发送")
else:
print(f" ✗ 失败通知发送失败")
except Exception as e:
print(f" ✗ 发送失败通知时出错: {e}")
if self.test_mode:
import traceback
traceback.print_exc()
def run_once(config_manager: Task2ConfigManager = None,
access_token: str = None,