'fix:task2-多条记录填入同一tapd链接时从缓存读取已拉取内容'

This commit is contained in:
zelong 2026-02-04 14:14:03 +08:00
parent 01d768b16a
commit 2d7fa8ea7a
2 changed files with 123 additions and 6 deletions

View File

@ -1,4 +1,4 @@
{ {
"access_token": "y4R56yl4blcYc8QUl5Dz04NUWBXZ-siYcApes3uEYuiz3EQpK0V3H6BCYvMNJliIFVFpMzGAQqhc9FRklpojrNSF-5p2x1sPrNy4MaZ85dZLhWP7kr0PRJPHMio-7xCvzL9aoI06HBvIqSum8OBxLueDGDlxHHMpmIH1HKj0bM30Arj4toE36mNck7ThF1ELklzzMtKlwnpVsVgQ3Wro6MuQS9_B0wosfDjFglxluLc", "access_token": "2LMUjFN9nR32QOSk-RYWTfpdYRL4CLtdgk10JWam9o4mvbfTnsLt8a0ODt-S_eu7MFJ4CFnC6fjLMbAUmfOufPXZrwa2sndigq7xoTWnUqeGqsu_2YcRW3VwilGMJuMG1_6_SYJgNwizMoS8BapKpGW1b37i1ITlQERhRR-iCyO-4DOezbQzb_07y_G1XNM1T3uyo09BNvfIIRx1yGAbmyv_63koAjW4LG3x_AbZnPE",
"fetch_time": 1769071472.3016503 "fetch_time": 1770177536.5388677
} }

View File

@ -24,6 +24,16 @@ from src2.tapd_api import TAPDStoryApi, TERMINAL_STATUSES, StoryNotFoundExceptio
from src2.smartsheet_sync import SmartSheetSync, REQUIRED_FIELDS from src2.smartsheet_sync import SmartSheetSync, REQUIRED_FIELDS
class CacheEntry:
"""TAPD查询缓存条目"""
def __init__(self, success: bool, data: Optional[Dict] = None,
error: Optional[Exception] = None):
self.success = success
self.data = data
self.error = error
self.timestamp = datetime.now()
class SyncService: class SyncService:
"""TAPD状态同步服务支持多表格同步""" """TAPD状态同步服务支持多表格同步"""
@ -73,6 +83,16 @@ class SyncService:
print(f" ⚠ 计划字段映射获取失败: {e},将使用空映射") print(f" ⚠ 计划字段映射获取失败: {e},将使用空映射")
self.plan_mapping = {} self.plan_mapping = {}
# TAPD查询缓存在sync_once中初始化
self._story_cache: Dict[str, CacheEntry] = {}
self._cache_stats = {
"total_queries": 0,
"cache_hits": 0,
"cache_misses": 0,
"api_calls": 0,
"cached_failures": 0
}
print(f" ✓ 同步服务初始化完成") print(f" ✓ 同步服务初始化完成")
def _get_beijing_time_str(self) -> str: def _get_beijing_time_str(self) -> str:
@ -94,6 +114,16 @@ class SyncService:
Returns: Returns:
Dict: 同步结果统计 Dict: 同步结果统计
""" """
# 初始化本次同步的缓存
self._story_cache = {}
self._cache_stats = {
"total_queries": 0,
"cache_hits": 0,
"cache_misses": 0,
"api_calls": 0,
"cached_failures": 0
}
result = { result = {
"success": False, "success": False,
"start_time": datetime.now().isoformat(), "start_time": datetime.now().isoformat(),
@ -141,6 +171,10 @@ class SyncService:
if all_failed_records: if all_failed_records:
self._send_failure_notification(all_failed_records) self._send_failure_notification(all_failed_records)
# 记录缓存统计
self._log_cache_statistics()
result["cache_stats"] = self._cache_stats.copy()
result["end_time"] = datetime.now().isoformat() result["end_time"] = datetime.now().isoformat()
return result return result
@ -352,6 +386,59 @@ class SyncService:
return sheet_result return sheet_result
def _get_story_with_cache(self, story_id: str) -> Dict:
"""
带缓存的获取需求详情
Args:
story_id: 需求ID
Returns:
Dict: 需求详细信息
Raises:
StoryNotFoundException: 需求不存在
Exception: 其他API错误
"""
self._cache_stats["total_queries"] += 1
# 检查缓存
if story_id in self._story_cache:
cache_entry = self._story_cache[story_id]
self._cache_stats["cache_hits"] += 1
if cache_entry.success:
# 缓存命中 - 成功记录
if self.test_mode:
print(f" [缓存命中] story_id={story_id}")
return cache_entry.data
else:
# 缓存命中 - 失败记录
self._cache_stats["cached_failures"] += 1
if self.test_mode:
print(f" [缓存命中-失败] story_id={story_id}")
raise cache_entry.error
# 缓存未命中调用API
self._cache_stats["cache_misses"] += 1
self._cache_stats["api_calls"] += 1
if self.test_mode:
print(f" [API调用] story_id={story_id}")
try:
story_info = self.tapd_api.get_story(story_id)
# 缓存成功结果
self._story_cache[story_id] = CacheEntry(success=True, data=story_info)
return story_info
except StoryNotFoundException as e:
# 缓存确定性失败(单号无效)
self._story_cache[story_id] = CacheEntry(success=False, error=e)
raise
except Exception as e:
# 不缓存非确定性失败网络错误、API限流等
raise
def _process_record(self, record_info: Dict) -> Dict[str, Any]: def _process_record(self, record_info: Dict) -> Dict[str, Any]:
""" """
处理单条记录的同步 处理单条记录的同步
@ -393,8 +480,8 @@ class SyncService:
story_id = record_info["story_id"] story_id = record_info["story_id"]
try: try:
# 查询TAPD获取需求信息 # 查询TAPD获取需求信息(使用缓存)
story_info = self.tapd_api.get_story(story_id) story_info = self._get_story_with_cache(story_id)
record_result["story_info"] = story_info record_result["story_info"] = story_info
# 提取需要同步的字段None 转为空字符串) # 提取需要同步的字段None 转为空字符串)
@ -554,8 +641,8 @@ class SyncService:
for record_info in records: for record_info in records:
try: try:
# 查询TAPD最新状态 # 查询TAPD最新状态(使用缓存)
story_info = self.tapd_api.get_story(record_info["story_id"]) story_info = self._get_story_with_cache(record_info["story_id"])
# 提取最新字段值None 转为空字符串) # 提取最新字段值None 转为空字符串)
new_status = story_info.get('status') or '' new_status = story_info.get('status') or ''
@ -622,6 +709,36 @@ class SyncService:
return result return result
def _log_cache_statistics(self):
"""记录缓存统计信息到日志"""
stats = self._cache_stats
if stats["total_queries"] == 0:
return
hit_rate = (stats["cache_hits"] / stats["total_queries"]) * 100
print(f"\n{'='*60}")
print(f"TAPD查询缓存统计")
print(f"{'='*60}")
print(f" 总查询次数: {stats['total_queries']}")
print(f" 缓存命中: {stats['cache_hits']} ({hit_rate:.1f}%)")
print(f" 缓存未命中: {stats['cache_misses']}")
print(f" 实际API调用: {stats['api_calls']}")
print(f" 缓存失败记录命中: {stats['cached_failures']}")
print(f" 缓存条目数: {len(self._story_cache)}")
print(f"{'='*60}\n")
# 记录到日志文件
self.logger.log_api_call(
api_type="task2",
operation="cache_statistics",
request_data={},
response_data=stats,
success=True,
error_message=None
)
def _send_failure_notification(self, failed_records: List[Dict]) -> None: def _send_failure_notification(self, failed_records: List[Dict]) -> None:
""" """
发送同步失败通知 发送同步失败通知