'修复任务2单次同步记录过多触发429限流,会暂停1分钟'

This commit is contained in:
zelong 2026-02-28 17:16:30 +08:00
parent 2d7fa8ea7a
commit 0769cf4755
8 changed files with 670 additions and 63 deletions

View File

@ -9,8 +9,8 @@ workspace_id = 58335167
# 支持配置多个docid用逗号分隔例如
# docid = doc1,doc2,doc3
# 同步时会依次对所有表格进行同步
#docid = dcOsT3czWy0YEDg38vlDqwVCTjv0kzwC_GU2XmT9wSZctQ0ZJQUAV7vMQ3ljZx-n_NqxzEEYG2DiLAvNdNsHJwgQ,dcHWzWyaHpZNQwUkZzgH5Kfyx9cMvQzVjZIapajGDuXqjS4nEe0LQqOojBL8s3rlwghw4deOgVnbOqHLoxcKzaHg
docid =dc2Q5Kb0T4zerbo4_ag0MMcXHCusIaFJX5fO6_8n-l_yV-bn5brZSi1kNw3kjme-qIs0LvPKbC5GDEEPaZ1BGlvA
docid = dcOsT3czWy0YEDg38vlDqwVCTjv0kzwC_GU2XmT9wSZctQ0ZJQUAV7vMQ3ljZx-n_NqxzEEYG2DiLAvNdNsHJwgQ,dcHWzWyaHpZNQwUkZzgH5Kfyx9cMvQzVjZIapajGDuXqjS4nEe0LQqOojBL8s3rlwghw4deOgVnbOqHLoxcKzaHg
#docid =dc2Q5Kb0T4zerbo4_ag0MMcXHCusIaFJX5fO6_8n-l_yV-bn5brZSi1kNw3kjme-qIs0LvPKbC5GDEEPaZ1BGlvA
[Schedule]
# 同步频率(分钟)
@ -20,4 +20,5 @@ sync_interval = 30
# 企业微信应用ID
agentid = 1000615
# 接收人列表用户ID多个用|分隔,@all表示全部成员
receivers = 046364
# receivers = 046364
receivers = 040005

View File

@ -1,4 +1,4 @@
{
"access_token": "2LMUjFN9nR32QOSk-RYWTfpdYRL4CLtdgk10JWam9o4mvbfTnsLt8a0ODt-S_eu7MFJ4CFnC6fjLMbAUmfOufPXZrwa2sndigq7xoTWnUqeGqsu_2YcRW3VwilGMJuMG1_6_SYJgNwizMoS8BapKpGW1b37i1ITlQERhRR-iCyO-4DOezbQzb_07y_G1XNM1T3uyo09BNvfIIRx1yGAbmyv_63koAjW4LG3x_AbZnPE",
"fetch_time": 1770177536.5388677
"access_token": "GQKX61Nh9c5A4Mp0FDOGy4nZgnFok2gefTA0_X4Y5PEL7NkpD9UHWwji3lWkZLsHMJf3dpbJ_l-NdichZ5qSZuPhF7kJNU47Blf2yLQRqFctmXMU6m1cWU80iLiY0vrX2EPzvldaHMR-al3HgKK6PUSU9T2a5Xp-lCjh9StPzEnQJUnwickV4PiPegLLGcH5F6jcM-9pHztkJ6pSV6bfP5QFFATAldmj-71Occib9V0",
"fetch_time": 1772269143.6670134
}

14
core/__init__.py Normal file
View File

@ -0,0 +1,14 @@
"""全局核心模块导出"""
from core.global_log_system import (
GlobalLogSystem,
create_task1_log_system,
create_task2_log_system,
)
__all__ = [
"GlobalLogSystem",
"create_task1_log_system",
"create_task2_log_system",
]

210
core/global_log_system.py Normal file
View File

@ -0,0 +1,210 @@
"""
全局日志系统内核
职责
1. 按天写入 jsonl 日志
2. 统一记录 API 调用事件
3. 记录同步开始/结束事件与统计
4. token 等敏感字段做脱敏
"""
from __future__ import annotations
import json
import re
import uuid
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, Optional
class GlobalLogSystem:
"""全局日志记录器"""
ALLOWED_MODULES = {"smartsheet", "tapd", "wework"}
SENSITIVE_KEYS = {
"access_token",
"token",
"authorization",
"corpsecret",
"api_password",
"password",
"secret",
"auth",
}
TOKEN_PATTERNS = [
re.compile(r"(access_token=)([^&\s]+)", re.IGNORECASE),
re.compile(r"(corpsecret=)([^&\s]+)", re.IGNORECASE),
re.compile(r"(token=)([^&\s]+)", re.IGNORECASE),
]
def __init__(self, task_name: str, log_dir: str | Path):
self.task_name = task_name
self.log_dir = Path(log_dir)
self.log_dir.mkdir(parents=True, exist_ok=True)
self._active_sync_id: Optional[str] = None
def start_sync(self,
trigger: str,
metadata: Optional[Dict[str, Any]] = None,
sync_id: Optional[str] = None) -> str:
"""记录一次同步开始事件并返回 sync_id"""
if sync_id is None:
sync_id = self._generate_sync_id()
event = {
"event_type": "start_sync",
"timestamp": self._now_string(),
"task": self.task_name,
"sync_id": sync_id,
"trigger": trigger,
"metadata": self._sanitize(metadata or {}),
}
self._active_sync_id = sync_id
self._write_event_safely(event)
return sync_id
def log_api(self,
module: str,
operation: str,
request_data: Optional[Dict[str, Any]],
response_data: Optional[Dict[str, Any]],
success: bool,
error_message: Optional[str] = None,
duration_ms: Optional[int] = None,
sync_id: Optional[str] = None,
extra: Optional[Dict[str, Any]] = None) -> None:
"""记录单次 API 调用事件"""
if module not in self.ALLOWED_MODULES:
raise ValueError(
f"module 不合法: {module},允许值: {sorted(self.ALLOWED_MODULES)}"
)
resolved_sync_id = sync_id or self._active_sync_id
event = {
"event_type": "api_call",
"timestamp": self._now_string(),
"task": self.task_name,
"sync_id": resolved_sync_id,
"module": module,
"operation": operation,
"success": success,
"request": self._sanitize(request_data or {}),
"response": self._sanitize(response_data or {}),
"error_message": self._sanitize_text(error_message),
"duration_ms": duration_ms,
"extra": self._sanitize(extra or {}),
}
self._write_event_safely(event)
def end_sync_with_stats(self,
stats: Dict[str, Any],
success: bool,
error_message: Optional[str] = None,
sync_id: Optional[str] = None,
extra: Optional[Dict[str, Any]] = None) -> None:
"""记录一次同步结束事件"""
resolved_sync_id = sync_id or self._active_sync_id
event = {
"event_type": "end_sync",
"timestamp": self._now_string(),
"task": self.task_name,
"sync_id": resolved_sync_id,
"success": success,
"stats": self._sanitize(stats),
"error_message": self._sanitize_text(error_message),
"extra": self._sanitize(extra or {}),
}
self._write_event_safely(event)
if resolved_sync_id == self._active_sync_id:
self._active_sync_id = None
def _write_event_safely(self, event: Dict[str, Any]) -> None:
"""安全写入:日志失败不影响主流程"""
try:
self._write_event(event)
except Exception as exc:
print(f"⚠️ 全局日志写入失败: {exc}")
def _write_event(self, event: Dict[str, Any]) -> None:
log_file = self._get_today_log_file()
with open(log_file, "a", encoding="utf-8") as handle:
handle.write(json.dumps(event, ensure_ascii=False) + "\n")
def _get_today_log_file(self) -> Path:
today = datetime.now().strftime("%Y-%m-%d")
return self.log_dir / f"api_log_{today}.jsonl"
def _generate_sync_id(self) -> str:
time_part = datetime.now().strftime("%Y%m%d_%H%M%S")
random_part = uuid.uuid4().hex[:8]
return f"{self.task_name}_{time_part}_{random_part}"
def _now_string(self) -> str:
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def _sanitize(self, value: Any, parent_key: str = "") -> Any:
if isinstance(value, dict):
sanitized: Dict[str, Any] = {}
for key, item in value.items():
key_lower = str(key).lower()
if key_lower in self.SENSITIVE_KEYS:
sanitized[key] = self._mask_sensitive_value(item)
else:
sanitized[key] = self._sanitize(item, parent_key=key_lower)
return sanitized
if isinstance(value, list):
return [self._sanitize(item, parent_key=parent_key) for item in value]
if isinstance(value, tuple):
return tuple(self._sanitize(item, parent_key=parent_key) for item in value)
if isinstance(value, str):
if parent_key in self.SENSITIVE_KEYS:
return self._mask_sensitive_value(value)
return self._sanitize_text(value)
return value
def _sanitize_text(self, text: Optional[str]) -> Optional[str]:
if text is None:
return None
masked = text
for pattern in self.TOKEN_PATTERNS:
masked = pattern.sub(r"\1***", masked)
return masked
def _mask_sensitive_value(self, value: Any) -> str:
text = "" if value is None else str(value)
if not text:
return "***"
if text.lower().startswith("bearer "):
return "Bearer ***"
if len(text) <= 6:
return "***"
return f"{text[:3]}***{text[-2:]}"
def create_task1_log_system(project_root: Optional[Path] = None) -> GlobalLogSystem:
"""创建任务一日志系统(固定 logs/"""
root = project_root or Path(__file__).parent.parent
return GlobalLogSystem(task_name="task1", log_dir=root / "logs")
def create_task2_log_system(project_root: Optional[Path] = None) -> GlobalLogSystem:
"""创建任务二日志系统(固定 logs2/"""
root = project_root or Path(__file__).parent.parent
return GlobalLogSystem(task_name="task2", log_dir=root / "logs2")

111
docs/全局框架文档.md Normal file
View File

@ -0,0 +1,111 @@
# 全局框架文档
> 用途:统一维护项目模块、脚本职责与接口定义,避免调用不存在接口与跨任务耦合失控。
> 范围:`src/``src2/`,以及新增的全局模块(如后续 `core/`)。
---
## 1. 日志系统总览(重构目标态)
### 1.1 目标架构
- **全局日志内核**:位于任务目录外,提供统一记录能力。
- **任务一适配层**:写入 `logs/`
- **任务二适配层**:写入 `logs2/`
- **同步边界管理**:每次同步使用唯一 `sync_id` 分隔,结束时写统计。
### 1.2 统一日志字段(约定)
- `event_type``start_sync` / `api_call` / `end_sync`
- `timestamp`:事件时间
- `task``task1` / `task2`
- `sync_id`:单次同步唯一标识
- `module``smartsheet` / `tapd` / `wework`
- `operation`:接口操作名
- `request`:请求快照(含脱敏)
- `response`:响应快照(含脱敏)
- `success`:调用是否成功
- `error_message`:失败原因
- `duration_ms`:调用耗时(毫秒)
- `stats`:同步完成统计(仅 `end_sync`
---
## 2. 模块清单与职责(当前状态)
## 2.0 全局核心(`core/`
- `core/global_log_system.py`
- **职责**提供统一日志内核jsonl 写入、同步分隔、API 事件、统计事件、脱敏)。
- **接口(对内)**`start_sync``log_api``end_sync_with_stats`
- **创建器**`create_task1_log_system`(固定 `logs/`)、`create_task2_log_system`(固定 `logs2/`)。
- `core/__init__.py`
- **职责**:导出全局日志内核与创建器。
## 2.1 任务一(`src/`
- `src/scheduler.py`
- **职责**:任务一调度入口,定时触发单次同步。
- **关键依赖**`src/main.py`
- `src/main.py`
- **职责**:任务一主流程编排(扫描、校验、开单、回写、通知)。
- **接口(对内)**`run_once(...)`
- `src/smartsheet.py`
- **职责**:智能表格 API 封装。
- **接口(对内)**`get_sheet_list``get_fields``get_records``update_records` 等。
- `src/tapd_api.py`
- **职责**TAPD Bug API 封装(创建、查询、附件上传)。
- **接口(对内)**`create_bug``get_bug``upload_attachment` 等。
- `src/token_manager.py`
- **职责**:企业微信 `access_token` 获取与缓存。
- **接口(对内)**`get_token()`
- `src/wework_notifier.py`
- **职责**:企业微信消息通知。
- **接口(对内)**`send_validation_failure_notification(...)`
- `src/api_logger.py`
- **职责**:现有日志记录器(后续将作为兼容层)。
## 2.2 任务二(`src2/`
- `src2/scheduler.py`
- **职责**:任务二调度入口,定时触发同步。
- **关键依赖**`src2/sync_service.py`
- `src2/sync_service.py`
- **职责**:任务二同步编排(读取、解析链接、查询 TAPD、回写、通知
- **接口(对内)**`sync_once()`
- `src2/smartsheet.py`
- **职责**:任务二智能表格 API 适配层。
- **关键点**:应固定写入 `logs2/`
- `src2/smartsheet_sync.py`
- **职责**:任务二表格字段检查、记录提取与回写构造。
- `src2/tapd_api.py`
- **职责**:任务二 TAPD Story 查询与状态映射。
- `src2/notifier.py`
- **职责**:任务二失败通知封装(当前复用任务一通知器,存在串目录风险)。
- `src2/logger.py`
- **职责**:任务二日志实例入口(后续接入统一内核)。
---
## 3. 现存问题与待改造点
- **串目录问题**:任务二复用 `TokenManager``WeWorkNotifier` 可能写入 `logs/`
- **双记录矛盾**:同一次请求在部分分支可能出现先成功后失败两条记录。
- **写入稳定性问题**:现有按 JSON 数组拼接的策略会造成结构损坏风险。
- **同步边界缺失**:缺少标准化 `start_sync` / `end_sync` 分隔与统计记录。
---
## 4. 模块接口演进计划(摘要)
- 阶段1新增全局日志内核模块定义统一接口。已完成
- 阶段2`src/api_logger.py` 改造成兼容层,保证旧调用可用。
- 阶段3`src2/logger.py` 与任务二编排层切换到统一内核,修复串目录。
- 阶段4更新查看工具与文档支持 `jsonl + sync_id`
---
## 5. 变更记录
### 2026-02-28
- 新建本框架文档。
- 写入日志系统重构目标态、模块职责清单、问题清单与演进路线。
### 2026-02-28更新
- 新增 `core/global_log_system.py``core/__init__.py` 模块说明。
- 标记阶段1完成统一日志内核已落地待阶段2/3接线。

105
docs/全局迭代日志.md Normal file
View File

@ -0,0 +1,105 @@
# 全局迭代日志
> 用途:跨任务(`src` / `src2`)记录每个阶段的增改内容、验收结果与回滚点。
> 约束:每次阶段验收通过后,必须追加一条日志记录。
---
## 记录模板
### 阶段:
- **阶段名称**
- **日期**
- **目标**
### 变更清单
- **新增文件**
- **修改文件**
- **删除文件**
### 关键改动说明
- **日志结构变更**
- **接口/调用链变更**
- **兼容性说明**
### 验收结果
- **通过项**
- **未通过项**
- **遗留风险**
### 回滚与追踪
- **可回滚点**
- **关联文档**
- **备注**
---
## 阶段日志
## 阶段0文档与约定先行
- **阶段名称**:日志系统重构 - 阶段0
- **日期**2026-02-28
- **目标**:建立重构方案与全局文档骨架,为后续代码改造提供统一约束。
### 变更清单
- **新增文件**
- `docs/日志系统重构实施方案.md`
- `docs/全局迭代日志.md`
- `docs/全局框架文档.md`
- **修改文件**:无
- **删除文件**:无
### 关键改动说明
- **日志结构变更**:确定后续采用 `jsonl`,并以 `sync_id` 分隔每次同步。
- **接口/调用链变更**:明确生产链路以 `src/scheduler.py``src2/scheduler.py` 为准。
- **兼容性说明**阶段0仅文档不影响现网行为。
### 验收结果
- **通过项**
- 分阶段路线、边界条件、验收清单已落文档。
- 已建立全局迭代日志与框架文档容器。
- **未通过项**:无
- **遗留风险**
- 任务二存在复用模块导致串目录风险待阶段3修复
- 现有日志写入策略存在双记录与结构损坏风险待阶段1/2修复
### 回滚与追踪
- **可回滚点**:当前为纯文档提交,可直接整提交回滚。
- **关联文档**`docs/日志系统重构实施方案.md`
- **备注**阶段1开始前需再次确认日志字段最终版。
## 阶段1实现全局日志内核
- **阶段名称**:日志系统重构 - 阶段1
- **日期**2026-02-28
- **负责人**Codex
- **目标**:在任务目录外提供可复用的统一日志内核,支持 jsonl、sync_id、token 脱敏。
### 变更清单
- **新增文件**
- `core/global_log_system.py`
- `core/__init__.py`
- **修改文件**
- `docs/全局迭代日志.md`
- `docs/全局框架文档.md`
- **删除文件**:无
### 关键改动说明
- **日志结构变更**:新增 `start_sync` / `api_call` / `end_sync` 三类事件模型。
- **接口/调用链变更**:提供 `start_sync``log_api``end_sync_with_stats` 三个核心接口。
- **兼容性说明**阶段1仅新增内核未接入任务一/任务二业务调用,不影响现网逻辑。
### 验收结果
- **通过项**
- 已支持按天写入 `api_log_YYYY-MM-DD.jsonl`
- 已支持 `sync_id` 生命周期记录。
- 已支持 token/secret 脱敏。
- 已提供任务一、任务二创建器(固定目录)。
- **未通过项**
- 尚未接入 `src` / `src2`,串目录与双记录矛盾仍待后续阶段修复。
- **遗留风险**
- 阶段2/3接线时若沿用旧 logger 分支逻辑,可能再次引入双记录。
### 回滚与追踪
- **可回滚点**`core/` 新增为独立改动,可单独回滚。
- **关联文档**`docs/日志系统重构实施方案.md`
- **备注**:下一阶段优先完成任务一接入并验证“单次调用单条记录”。

View File

@ -0,0 +1,119 @@
# 日志系统重构实施方案(任务一 + 任务二)
## 1. 目标与边界
### 1.1 重构目标
- 在任务一和任务二之外,提供统一的全局日志记录系统。
- 覆盖生产链路:`src/scheduler.py``src2/scheduler.py` 触发的同步流程。
- 在每一个发起 API 调用的地方记录请求与结果(成功/失败都记录)。
- 任务一日志落在 `logs/`,任务二日志落在 `logs2/`
- 每条日志必须包含 `module` 字段,允许值:`smartsheet` / `tapd` / `wework`
- 通过 `sync_id` 分隔每次同步,并在同步完成后写入当次统计信息。
### 1.2 明确要解决的问题
- 解决“串目录”问题:任务二不得写入 `logs/`,任务一不得写入 `logs2/`
- 解决“双记录矛盾”问题:同一次 API 调用只能有一条最终记录(不能先 success 再 failure
- 日志格式改为 `jsonl`(每行一条完整 JSON 记录)。
- 对 token 做脱敏,不做响应内容截断。
### 1.3 非目标
- 本次不改业务流程语义(只改日志系统与接线方式)。
- 本次不对历史 JSON 文件做离线迁移,仅保证新写入生效。
---
## 2. 总体设计
### 2.1 新增全局日志内核(任务外)
- 新增独立模块(建议路径:`core/global_log_system.py`)。
- 提供统一接口:
- `start_sync(...)`:开始一次同步,生成 `sync_id`
- `log_api(...)`:记录单次 API 调用结果(成功/失败统一出口)。
- `end_sync_with_stats(...)`:写入同步统计并结束。
- 通过构造参数确定任务上下文:`task_name``log_dir`、默认 `module`
### 2.2 日志存储格式
- 文件命名:`api_log_YYYY-MM-DD.jsonl`
- 存储目录:任务一 `logs/`,任务二 `logs2/`
- 记录模型:
- 通用字段:`event_type``timestamp``task``sync_id``module`
- API 事件字段:`operation``request``response``success``error_message``duration_ms`
- 同步边界字段:`start_sync` / `end_sync` 事件及统计 `stats`
### 2.3 脱敏规则
- `request/response` 中出现 token 字段统一脱敏(如 `access_token``Authorization``corpsecret``api_password`)。
- 脱敏策略:保留前后少量字符,中间替换为 `***`
---
## 3. 分阶段实施路线(小步走)
## 阶段0文档与约定先行
### 工作内容
- 新建并维护两份全局文档:
- `docs/全局迭代日志.md`
- `docs/全局框架文档.md`
- 在文档中建立日志重构专属章节、字段定义、阶段验收标准。
### 验收标准
- 文档结构可用于后续持续更新。
- 明确字段与职责边界(尤其是 `sync_id``task``module`)。
## 阶段1实现全局日志内核
### 工作内容
- 新增 `core/global_log_system.py`,实现 `jsonl` 写入、脱敏、同步分隔和统计写入。
- 新增兼容层,避免一次性改动过大。
### 验收标准
- 能独立写入 `logs/` / `logs2/`
- `start_sync -> 多条 log_api -> end_sync_with_stats` 链路完整。
## 阶段2接入任务一src
### 工作内容
- 改造 `src/api_logger.py` 为新内核兼容封装。
- 在任务一同步主流程增加 `sync_id` 生命周期。
- 覆盖所有 API 调用记录点,统一单次调用单条记录。
### 验收标准
- 任务一生产链路日志全部位于 `logs/`
- 无“双记录矛盾”。
## 阶段3接入任务二src2
### 工作内容
- 改造 `src2/logger.py` 接入同一内核并固定 `logs2/`
- 在任务二同步主流程增加 `sync_id` 生命周期。
- 修复复用模块导致的串目录问题(含 token 获取、wework 通知链路)。
### 验收标准
- 任务二生产链路日志全部位于 `logs2/`
- 与任务一日志完全隔离。
## 阶段4查看工具与收尾
### 工作内容
- 更新 `src/log_viewer.py` 支持读取 `jsonl`
- 更新 `logs/README.md`(新增 jsonl 规范与排障说明)。
### 验收标准
- 可按日期与 `sync_id` 追踪一次完整同步。
- 文档、代码、日志格式三者一致。
---
## 4. 核心风险与规避
- 风险1兼容层改造影响原调用。
- 规避:先保留 `log_api_call(...)` 旧接口,再逐步替换调用。
- 风险2任务二通过复用模块写错目录。
- 规避:统一使用可注入 logger/context禁止隐式全局默认目录。
- 风险3单次请求出现多状态记录。
- 规避:采用“单出口记录”,业务错误也只写一次最终状态。
---
## 5. 验收清单(最终)
- [ ] 生产链路(两个 scheduler所有 API 调用均有日志。
- [ ] 每次同步都有 `start_sync``end_sync`,并可通过 `sync_id` 聚合。
- [ ] 任务一只写 `logs/`,任务二只写 `logs2/`
- [ ] 无同一次 API 调用 success/failure 双写冲突。
- [ ] token 已脱敏。
- [ ] `jsonl` 可被查看工具读取。

View File

@ -9,6 +9,7 @@ TAPD API调用模块任务二专用
import os
import requests
import time
from typing import Dict, Optional, Any
from requests.auth import HTTPBasicAuth
@ -105,7 +106,7 @@ class TAPDStoryApi:
def _make_request(self, endpoint: str, params: Optional[Dict] = None) -> Dict:
"""
发起TAPD API GET请求的通用方法
发起TAPD API GET请求的通用方法支持429错误重试
Args:
endpoint: API端点 "stories"
@ -138,6 +139,11 @@ class TAPDStoryApi:
for key, value in params.items():
print(f" {key}: {value}")
# 429错误重试逻辑最多重试1次
max_retries = 1
retry_count = 0
while retry_count <= max_retries:
try:
response = self.session.get(
url,
@ -158,6 +164,42 @@ class TAPDStoryApi:
print(f"响应内容: {response.text[:500]}")
print("=" * 60)
# 检查是否是429错误在raise_for_status之前检查
if response.status_code == 429:
if retry_count < max_retries:
retry_count += 1
wait_seconds = 60
print(f"\n⚠️ 触发TAPD API限流 (429 Too Many Requests)")
print(f" [开始等待] 等待 {wait_seconds} 秒后重试... (第 {retry_count}/{max_retries} 次重试)")
print(f" [重要] 在等待期间,代码会阻塞在这里,不会继续处理其他记录")
# 记录429错误日志
self.logger.log_api_call(
api_type="tapd",
operation=endpoint,
request_data=log_request_data,
response_data={"status_code": 429, "retry_count": retry_count},
success=False,
error_message=f"429 Too Many Requests, 等待{wait_seconds}秒后重试"
)
time.sleep(wait_seconds)
print(f" [等待结束] 开始重试请求...")
continue # 重试
else:
# 已达到最大重试次数,抛出异常
error_msg = "TAPD API限流 (429 Too Many Requests),重试后仍然失败"
self.logger.log_api_call(
api_type="tapd",
operation=endpoint,
request_data=log_request_data,
response_data={"status_code": 429, "retry_count": retry_count},
success=False,
error_message=error_msg
)
raise RuntimeError(error_msg)
# 对于非429错误调用raise_for_status检查HTTP状态
response.raise_for_status()
result = response.json()
@ -198,6 +240,8 @@ class TAPDStoryApi:
raise RuntimeError(error_msg)
except requests.exceptions.RequestException as e:
# 如果是HTTPError且状态码是429由上面的status_code检查处理
# 这里不应该到达因为429在response.status_code检查时已处理
error_msg = f"TAPD API请求失败: {e}"
self.logger.log_api_call(
api_type="tapd",
@ -209,6 +253,9 @@ class TAPDStoryApi:
)
raise RuntimeError(error_msg)
# 理论上不会到达这里
raise RuntimeError("TAPD API请求失败未知错误")
def get_story(self, story_id: str) -> Dict:
"""
获取需求详情