修复任务一429时不推送企微报错
This commit is contained in:
parent
3dc4815e26
commit
a81e6961b4
@ -1,4 +1,4 @@
|
||||
{
|
||||
"access_token": "MzN3pnuz4rhX4CUzHCEgLONP_tzovqGquHfGlKRli6uZmZfdMUlnx6q4FqK6jTOF_xFfE0HSz4H2OhKI8FPS1eo8-EQJgjxLycRMhOFKwqKtEE9oMwyPfi9mwMaeXJUoZfBhlXQOPfOvCN0sL6LD6atthEdmj0VVSyDU_fPJ1XpSkqzWv8cosuG8tbrssX8EZgw90vKT46N0ySi5Mnhe45yY2_V-PH0TtuSHd-D6EgM",
|
||||
"fetch_time": 1775029801.2061243
|
||||
"access_token": "uUoS08xX0tunEpZHP5HD2bDQgSeozq4bf8fuyuNNWKqBXY5-Ygjglv6dqeyYDkPigiuhAQaRpEnWa625ImTxNQYzETWip57YpmI2YRWZW2b6fNSxc7GtcpcgfZO8ACETZjVz6iO0E9szj5CgCqf5M6V_ndJGkZU4rjtcmCt9Hn4jAPfhc00T2VPNQCtToZkLv62RFELABgqQ61PJaKp9dEG8yexEkaYoiS9W6O387qU",
|
||||
"fetch_time": 1777019153.1190348
|
||||
}
|
||||
38
docs/全局任务列表.md
Normal file
38
docs/全局任务列表.md
Normal file
@ -0,0 +1,38 @@
|
||||
# 全局任务列表
|
||||
|
||||
> 用途:按层级维护项目任务、阶段状态与新增需求,避免任务漂移。
|
||||
|
||||
---
|
||||
|
||||
## 任务一:智能表格自动开 TAPD Bug 单
|
||||
|
||||
- [x] 扫描智能表格待开单记录
|
||||
- [x] 校验必填字段并回写开单状态
|
||||
- [x] 创建 TAPD Bug 并回写 TAPD 单号、Bug 状态
|
||||
- [x] 校验失败企微通知
|
||||
- [x] 处理 TAPD 429 限速导致的通知缺失
|
||||
- [x] 识别 TAPD HTTP 429 与业务限速错误
|
||||
- [x] TAPD 开单限速时等待2分钟重试当前记录
|
||||
- [x] 重试仍限速时停止本轮继续开单,保留未处理记录等待下次调度
|
||||
- [x] TAPD 开单失败与限速事件纳入企微推送
|
||||
|
||||
## 任务一:TAPD Bug 状态同步
|
||||
|
||||
- [x] 扫描已开单且非终态记录
|
||||
- [x] 查询 TAPD 最新 Bug 状态
|
||||
- [x] 回写智能表格 Bug 状态
|
||||
- [x] 处理 TAPD 429 限速导致的通知缺失
|
||||
- [x] 单条查询失败不再静默吞掉
|
||||
- [x] TAPD 限速时停止本轮后续状态查询
|
||||
- [x] 查询失败与限速事件纳入企微推送
|
||||
- [x] 调度统计补充 `failed_count` 与 `rate_limited`
|
||||
|
||||
## 后续建议任务
|
||||
|
||||
- [ ] TAPD 请求节流与退避策略
|
||||
- [ ] 配置化单次请求间隔
|
||||
- [ ] 支持按 `Retry-After` 延迟重试
|
||||
- [ ] 支持每轮最大处理数量,避免数百条记录一次性打满 TAPD 限额
|
||||
- [ ] 状态同步批量化优化
|
||||
- [ ] 调研 TAPD Bug 列表接口是否支持批量按 ID 查询
|
||||
- [ ] 减少逐条 `get_bug` 调用
|
||||
@ -45,21 +45,27 @@
|
||||
- **关键依赖**:`src/main.py`。
|
||||
- `src/main.py`
|
||||
- **职责**:任务一主流程编排(扫描、校验、开单、回写、通知)。
|
||||
- **接口(对内)**:`run_once(...)`。
|
||||
- **接口(对内)**:`run_once(...)`、`create_bug_with_rate_limit_retry(...)`。
|
||||
- **关键点**:开单触发 TAPD 429 时等待 120 秒重试当前记录一次;重试仍限速才停止本轮后续开单。
|
||||
- `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` 等。
|
||||
- **接口(对内)**:`RateLimitError`、`create_bug`、`get_bug`、`upload_attachment` 等。
|
||||
- **关键点**:HTTP 429 或业务错误中的限速信息会抛出 `RateLimitError`,上层应停止本轮继续打 TAPD。
|
||||
- `src/token_manager.py`
|
||||
- **职责**:企业微信 `access_token` 获取与缓存。
|
||||
- **接口(对内)**:`__init__(cache_file_path=None, logger=None)`、`get_token()`。
|
||||
- `src/wework_notifier.py`
|
||||
- **职责**:企业微信消息通知。
|
||||
- **接口(对内)**:`__init__(access_token, agentid, receivers, logger=None)`、`send_validation_failure_notification(...)`。
|
||||
- **接口(对内)**:`__init__(access_token, agentid, receivers, logger=None)`、`send_validation_failure_notification(...)`、`send_operation_failure_notification(...)`。
|
||||
- `src/api_logger.py`
|
||||
- **职责**:现有日志记录器(后续将作为兼容层)。
|
||||
- `src/sync_status.py`
|
||||
- **职责**:任务一 Bug 状态同步编排。
|
||||
- **接口(对内)**:`BugStatusSyncer(access_token, docid, workspace_id, test_mode=False, agentid=None, receivers=None)`、`sync_bug_status()`。
|
||||
- **关键点**:TAPD 查询失败会计入 `failed_count`;触发 429 时停止本轮后续查询并推送企微异常通知。
|
||||
|
||||
## 2.2 任务二(`src2/`)
|
||||
- `src2/scheduler.py`
|
||||
@ -146,3 +152,10 @@
|
||||
- 新增任务三(`src3/`)模块清单与职责说明。
|
||||
- 任务三配置改为多群分组推送:按顺序维护“组名-成员子表标题-Webhook”一一对应关系。
|
||||
- 任务三成员配置读取由手填 `sheet_id` 调整为通过子表标题自动解析 `sheet_id`。
|
||||
|
||||
### 2026-06-02(更新5)
|
||||
- `src/tapd_api.py` 新增 `RateLimitError`,用于区分 TAPD 429 限速与普通运行错误。
|
||||
- `src/main.py` 在 TAPD 开单触发限速时等待 120 秒重试当前记录;重试仍失败时停止本轮后续开单,并将限速事件纳入企微异常通知。
|
||||
- `src/sync_status.py` 不再静默吞掉单条 TAPD 查询失败;触发限速时停止本轮状态同步并推送异常通知。
|
||||
- `src/wework_notifier.py` 新增 `send_operation_failure_notification(...)`,用于运行异常类通知。
|
||||
- `src/scheduler.py` 状态同步统计新增 `failed_count` 与 `rate_limited`。
|
||||
|
||||
@ -260,3 +260,52 @@
|
||||
- **可回滚点**:`src3/message_formatter.py` 与 `src3/main.py` 可独立回滚。
|
||||
- **关联文档**:`需求文档.md`(任务三 V2 段落)
|
||||
- **备注**:可在后续补“消息模板配置化”减少文案硬编码。
|
||||
|
||||
## 阶段5:任务一 TAPD 429 限速通知修复
|
||||
- **阶段名称**:任务一迭代 - TAPD 429 限速与企微通知补丁
|
||||
- **日期**:2026-06-02
|
||||
- **负责人**:Codex
|
||||
- **目标**:定位并修复智能表格存在数百条记录时,开单和状态同步触发 TAPD 429 后不会触发企微推送的问题。
|
||||
|
||||
### 根因
|
||||
- **开单链路**:`src/main.py` 只把字段校验失败加入企微通知集合;TAPD 创建失败虽然会写回 `❌`,但不会触发企微推送。
|
||||
- **状态同步链路**:`src/sync_status.py` 对单条 `get_bug` 查询异常使用 `except Exception: continue` 静默吞掉,导致 429 被误判为“状态无变化/同步成功”。
|
||||
- **限速放大**:TAPD 调用层没有区分 429,批量记录触发限速后仍可能继续逐条请求 TAPD;开单链路还缺少用户期望的“等待2分钟后重试当前记录”步骤。
|
||||
|
||||
### 变更清单
|
||||
- **新增文件**:
|
||||
- `docs/全局任务列表.md`
|
||||
- **修改文件**:
|
||||
- `src/tapd_api.py`
|
||||
- `src/main.py`
|
||||
- `src/sync_status.py`
|
||||
- `src/scheduler.py`
|
||||
- `src/wework_notifier.py`
|
||||
- `docs/全局框架文档.md`
|
||||
- `docs/全局迭代日志.md`
|
||||
- **删除文件**:无
|
||||
|
||||
### 关键改动说明
|
||||
- `src/tapd_api.py` 新增 `RateLimitError`,识别 HTTP 429 与业务错误文本中的限速信息。
|
||||
- `src/main.py` 在开单触发限速后等待 120 秒重试当前记录一次;重试仍触发限速时才停止本轮后续 TAPD 开单,未处理记录保持未开单状态,等待下次调度重试。
|
||||
- `src/main.py` 将 TAPD 开单失败与限速事件纳入企微异常通知。
|
||||
- `src/sync_status.py` 将 TAPD 查询失败计入 `failed_count`,触发限速时停止本轮后续查询并推送企微异常通知。
|
||||
- `src/scheduler.py` 状态同步日志统计新增 `failed_count` 与 `rate_limited`。
|
||||
- `src/wework_notifier.py` 新增运行异常通知接口,复用企业微信应用消息发送链路。
|
||||
|
||||
### 验收结果
|
||||
- **通过项**:
|
||||
- 已完成静态链路复查:限速错误从 TAPD API 层到开单/同步编排层都有专用分支。
|
||||
- 已确认开单链路触发 429 后会先等待 120 秒重试当前记录。
|
||||
- 已确认开单链路不再只通知字段校验失败,TAPD 开单异常也会进入企微通知。
|
||||
- 已确认状态同步不再静默吞掉单条 TAPD 查询失败。
|
||||
- **未通过项**:
|
||||
- 尚未执行运行态联调(按项目约束未在命令行执行 Python)。
|
||||
- **遗留风险**:
|
||||
- 当前补丁是“开单限速后固定等待 120 秒重试一次”,尚未实现主动节流、按 `Retry-After` 动态等待或单轮处理上限。
|
||||
- 若 TAPD 限速信息不包含 429、限速、频繁等关键词,仍可能被识别为普通运行错误。
|
||||
|
||||
### 回滚与追踪
|
||||
- **可回滚点**:优先按文件粒度回滚 `src/tapd_api.py`、`src/main.py`、`src/sync_status.py`、`src/wework_notifier.py`、`src/scheduler.py`。
|
||||
- **关联文档**:`docs/全局框架文档.md`、`docs/全局任务列表.md`
|
||||
- **备注**:下一阶段建议做“请求节流 + 单轮最大处理数 + Retry-After 动态退避”,这是从根上降低 429 的方案。
|
||||
|
||||
114
src/api_test.py
114
src/api_test.py
@ -480,6 +480,69 @@ class WeWorkAPITester:
|
||||
print(f"✗ 请求异常: {str(e)}")
|
||||
return None
|
||||
|
||||
def mod_doc_member(self, docid, userid, auth=7):
|
||||
"""
|
||||
修改文档通知范围及权限(添加管理员等)
|
||||
|
||||
Args:
|
||||
docid: 文档ID
|
||||
userid: 企业成员的userid
|
||||
auth: 权限类型 1:只读 2:读写 7:管理员
|
||||
|
||||
Returns:
|
||||
bool: 是否成功
|
||||
"""
|
||||
print("\n=== 修改文档成员权限 ===")
|
||||
|
||||
if not self.access_token:
|
||||
print("✗ 请先获取access_token")
|
||||
return False
|
||||
|
||||
auth_name_map = {1: "只读", 2: "读写", 7: "管理员"}
|
||||
auth_name = auth_name_map.get(auth, f"未知({auth})")
|
||||
|
||||
url = f"{self.base_url}/wedoc/mod_doc_member"
|
||||
params = {"access_token": self.access_token}
|
||||
data = {
|
||||
"docid": docid,
|
||||
"update_file_member_list": [
|
||||
{
|
||||
"type": 1,
|
||||
"auth": auth,
|
||||
"userid": userid
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(url, params=params, json=data, timeout=10)
|
||||
response_data = response.json()
|
||||
|
||||
# 记录API调用
|
||||
request_data = {
|
||||
"url": url,
|
||||
"params": {"access_token": "***"},
|
||||
"body": data
|
||||
}
|
||||
self._log_api_call("mod_doc_member", request_data, response_data)
|
||||
|
||||
# 检查返回结果
|
||||
if response_data.get("errcode") == 0:
|
||||
print(f"✓ 权限修改成功")
|
||||
print(f" 文档ID: {docid}")
|
||||
print(f" 用户ID: {userid}")
|
||||
print(f" 权限: {auth_name}")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ 权限修改失败")
|
||||
print(f" 错误码: {response_data.get('errcode')}")
|
||||
print(f" 错误信息: {response_data.get('errmsg')}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 请求异常: {str(e)}")
|
||||
return False
|
||||
|
||||
def send_message(self, content):
|
||||
"""
|
||||
发送应用消息
|
||||
@ -1290,14 +1353,15 @@ def print_menu():
|
||||
print("4. 删除文档")
|
||||
print("5. 发送应用消息")
|
||||
print("6. 查询智能表格子表列表")
|
||||
print("7. 修改文档成员权限")
|
||||
print("\n【TAPD API】")
|
||||
print("7. 获取缺陷字段配置")
|
||||
print("8. 获取需求字段配置")
|
||||
print("9. 获取需求")
|
||||
print("10. 获取附件列表")
|
||||
print("11. 上传附件")
|
||||
print("8. 获取缺陷字段配置")
|
||||
print("9. 获取需求字段配置")
|
||||
print("10. 获取需求")
|
||||
print("11. 获取附件列表")
|
||||
print("12. 上传附件")
|
||||
print("\n【其他】")
|
||||
print("12. 查看日志文件")
|
||||
print("13. 查看日志文件")
|
||||
print("0. 退出")
|
||||
print("="*50)
|
||||
|
||||
@ -1309,7 +1373,7 @@ def main():
|
||||
|
||||
while True:
|
||||
print_menu()
|
||||
choice = input("\n请选择操作 (0-12): ").strip()
|
||||
choice = input("\n请选择操作 (0-13): ").strip()
|
||||
|
||||
if choice == "0":
|
||||
print("\n感谢使用,再见!")
|
||||
@ -1389,14 +1453,40 @@ def main():
|
||||
print("✗ 文档ID不能为空")
|
||||
|
||||
elif choice == "7":
|
||||
# 修改文档成员权限
|
||||
print("\n=== 修改文档成员权限 ===")
|
||||
docid = input("请输入文档ID (docid): ").strip()
|
||||
if not docid:
|
||||
print("✗ 文档ID不能为空")
|
||||
continue
|
||||
|
||||
userid = input("请输入用户ID (userid): ").strip()
|
||||
if not userid:
|
||||
print("✗ 用户ID不能为空")
|
||||
continue
|
||||
|
||||
print("\n权限类型:")
|
||||
print(" 1 - 只读")
|
||||
print(" 2 - 读写(仅智能表格支持)")
|
||||
print(" 7 - 管理员 (默认)")
|
||||
auth_input = input("请选择权限类型 (直接回车默认为7-管理员): ").strip()
|
||||
auth = int(auth_input) if auth_input else 7
|
||||
|
||||
if auth not in [1, 2, 7]:
|
||||
print("✗ 无效的权限类型,必须是 1/2/7 之一")
|
||||
continue
|
||||
|
||||
wework_tester.mod_doc_member(docid, userid, auth)
|
||||
|
||||
elif choice == "8":
|
||||
# 获取TAPD缺陷字段配置
|
||||
tapd_tester.get_bug_custom_fields()
|
||||
|
||||
elif choice == "8":
|
||||
elif choice == "9":
|
||||
# 获取TAPD需求字段配置
|
||||
tapd_tester.get_story_fields_info()
|
||||
|
||||
elif choice == "9":
|
||||
elif choice == "10":
|
||||
# 获取TAPD需求
|
||||
story_id = input("\n请输入需求ID: ").strip()
|
||||
if not story_id:
|
||||
@ -1404,7 +1494,7 @@ def main():
|
||||
continue
|
||||
tapd_tester.get_story(story_id)
|
||||
|
||||
elif choice == "10":
|
||||
elif choice == "11":
|
||||
# 获取TAPD附件列表
|
||||
print("\n=== 获取附件列表 ===")
|
||||
print("是否需要添加筛选条件?")
|
||||
@ -1442,7 +1532,7 @@ def main():
|
||||
limit=limit
|
||||
)
|
||||
|
||||
elif choice == "11":
|
||||
elif choice == "12":
|
||||
# 上传附件到TAPD
|
||||
print("\n=== 上传附件 ===")
|
||||
file_path = input("请输入文件路径: ").strip()
|
||||
@ -1486,7 +1576,7 @@ def main():
|
||||
overwrite=overwrite
|
||||
)
|
||||
|
||||
elif choice == "12":
|
||||
elif choice == "13":
|
||||
print("\n=== 查看日志文件 ===")
|
||||
try:
|
||||
with open(wework_tester.log_file, 'r', encoding='utf-8') as f:
|
||||
|
||||
117
src/main.py
117
src/main.py
@ -5,6 +5,7 @@ Debug阶段自动开单工具
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
|
||||
@ -15,7 +16,7 @@ sys.path.insert(0, str(project_root))
|
||||
from src.config import ConfigManager
|
||||
from src.smartsheet import SmartSheetAPI
|
||||
from src.validator import RecordValidator
|
||||
from src.tapd_api import TAPDApi
|
||||
from src.tapd_api import RateLimitError, TAPDApi
|
||||
from src.mapper import FieldMapper, BugCreationResult
|
||||
from src.token_manager import TokenManager
|
||||
from src.status_mapper import BugStatusMapper
|
||||
@ -23,6 +24,10 @@ from src.wework_notifier import WeWorkNotifier
|
||||
from src.api_logger import get_logger
|
||||
|
||||
|
||||
TAPD_RATE_LIMIT_RETRY_WAIT_SECONDS = 120
|
||||
TAPD_RATE_LIMIT_MAX_RETRIES = 1
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
"""
|
||||
解析命令行参数
|
||||
@ -296,6 +301,40 @@ def scan_all_sheets(access_token: str, docid: str, verbose: bool = False, test_m
|
||||
return all_results
|
||||
|
||||
|
||||
def create_bug_with_rate_limit_retry(tapd_api: TAPDApi, tapd_data: Dict,
|
||||
wait_seconds: int = TAPD_RATE_LIMIT_RETRY_WAIT_SECONDS,
|
||||
max_retries: int = TAPD_RATE_LIMIT_MAX_RETRIES) -> Dict:
|
||||
"""
|
||||
创建TAPD bug;触发限速时等待后重试当前记录
|
||||
|
||||
Args:
|
||||
tapd_api: TAPD API实例
|
||||
tapd_data: TAPD开单数据
|
||||
wait_seconds: 限速后等待秒数
|
||||
max_retries: 限速后的最大重试次数
|
||||
|
||||
Returns:
|
||||
Dict: 创建成功的bug信息
|
||||
|
||||
Raises:
|
||||
RateLimitError: 等待重试后仍触发限速
|
||||
RuntimeError: TAPD API其他错误
|
||||
"""
|
||||
retry_count = 0
|
||||
|
||||
while True:
|
||||
try:
|
||||
return tapd_api.create_bug(tapd_data)
|
||||
except RateLimitError:
|
||||
if retry_count >= max_retries:
|
||||
raise
|
||||
|
||||
retry_count += 1
|
||||
print(f" ⚠ TAPD触发限速,等待 {wait_seconds} 秒后重试当前记录 ({retry_count}/{max_retries})")
|
||||
time.sleep(wait_seconds)
|
||||
print(f" → 正在重试创建TAPD bug...")
|
||||
|
||||
|
||||
def create_tapd_bugs(valid_records: list, workspace_id: str, reporter: str, test_mode: bool = False) -> Dict:
|
||||
"""
|
||||
批量创建TAPD bug单
|
||||
@ -339,6 +378,7 @@ def create_tapd_bugs(valid_records: list, workspace_id: str, reporter: str, test
|
||||
|
||||
success_results = []
|
||||
failed_results = []
|
||||
rate_limit_info = None
|
||||
|
||||
for idx, record_data in enumerate(valid_records, 1):
|
||||
record_id = record_data.get('record_id', '未知')
|
||||
@ -354,7 +394,7 @@ def create_tapd_bugs(valid_records: list, workspace_id: str, reporter: str, test
|
||||
|
||||
# 创建bug
|
||||
print(f" → 正在创建TAPD bug...")
|
||||
bug_info = tapd_api.create_bug(tapd_data)
|
||||
bug_info = create_bug_with_rate_limit_retry(tapd_api, tapd_data)
|
||||
|
||||
# 提取bug ID
|
||||
bug_id = bug_info.get('id')
|
||||
@ -405,8 +445,35 @@ def create_tapd_bugs(valid_records: list, workspace_id: str, reporter: str, test
|
||||
success=False,
|
||||
error_message=error_msg
|
||||
)
|
||||
result.title = title
|
||||
failed_results.append(result)
|
||||
|
||||
except RateLimitError as e:
|
||||
# 等待2分钟重试后仍限速,继续逐条请求只会扩大失败面。
|
||||
error_msg = f"TAPD限速,已等待2分钟重试仍失败,暂停本轮开单: {e}"
|
||||
remaining_count = len(valid_records) - idx + 1
|
||||
print(f" ✗ {error_msg}")
|
||||
print(f" ⚠ 本轮剩余 {remaining_count} 条记录将保持未开单状态,等待下次调度重试")
|
||||
|
||||
logger = get_logger()
|
||||
logger.log_api_call(
|
||||
api_type="task1",
|
||||
operation="create_bug_rate_limited",
|
||||
request_data={"record_id": record_id, "title": title},
|
||||
response_data={},
|
||||
success=False,
|
||||
error_message=error_msg,
|
||||
extra={"remaining_count": remaining_count}
|
||||
)
|
||||
|
||||
rate_limit_info = {
|
||||
"record_id": record_id,
|
||||
"title": title,
|
||||
"error_message": error_msg,
|
||||
"remaining_count": remaining_count
|
||||
}
|
||||
break
|
||||
|
||||
except RuntimeError as e:
|
||||
# TAPD API调用错误
|
||||
error_msg = f"TAPD API调用失败: {e}"
|
||||
@ -428,6 +495,7 @@ def create_tapd_bugs(valid_records: list, workspace_id: str, reporter: str, test
|
||||
success=False,
|
||||
error_message=error_msg
|
||||
)
|
||||
result.title = title
|
||||
failed_results.append(result)
|
||||
|
||||
except Exception as e:
|
||||
@ -451,6 +519,7 @@ def create_tapd_bugs(valid_records: list, workspace_id: str, reporter: str, test
|
||||
success=False,
|
||||
error_message=error_msg
|
||||
)
|
||||
result.title = title
|
||||
failed_results.append(result)
|
||||
|
||||
# 显示汇总结果
|
||||
@ -473,7 +542,8 @@ def create_tapd_bugs(valid_records: list, workspace_id: str, reporter: str, test
|
||||
|
||||
return {
|
||||
'success_results': success_results,
|
||||
'failed_results': failed_results
|
||||
'failed_results': failed_results,
|
||||
'rate_limit_info': rate_limit_info
|
||||
}
|
||||
|
||||
|
||||
@ -651,7 +721,10 @@ def run_once(config_manager: ConfigManager, access_token: str, verbose: bool = F
|
||||
|
||||
# 2. 遍历每个子表,分别处理
|
||||
all_invalid_records = [] # 收集所有子表的失败记录(用于推送)
|
||||
all_creation_failure_records = [] # 收集TAPD开单失败记录(用于推送)
|
||||
all_rate_limit_records = [] # 收集TAPD限速事件(用于推送)
|
||||
sheet_summaries = [] # 收集每个子表的统计摘要
|
||||
hit_tapd_rate_limit = False
|
||||
|
||||
for sheet_result in all_sheet_results:
|
||||
sheet_id = sheet_result['sheet_id']
|
||||
@ -686,7 +759,9 @@ def run_once(config_manager: ConfigManager, access_token: str, verbose: bool = F
|
||||
creation_success_results = []
|
||||
creation_failed_results = []
|
||||
|
||||
if valid_count > 0:
|
||||
if valid_count > 0 and hit_tapd_rate_limit:
|
||||
print(f"\n[{sheet_title}] 已触发TAPD限速,本轮跳过该子表开单,待下次调度重试")
|
||||
elif valid_count > 0:
|
||||
creation_result = create_tapd_bugs(
|
||||
validation_result['valid_records'],
|
||||
all_config['tapd']['workspace_id'],
|
||||
@ -695,6 +770,19 @@ def run_once(config_manager: ConfigManager, access_token: str, verbose: bool = F
|
||||
)
|
||||
creation_success_results = creation_result['success_results']
|
||||
creation_failed_results = creation_result['failed_results']
|
||||
rate_limit_info = creation_result.get('rate_limit_info')
|
||||
if rate_limit_info:
|
||||
rate_limit_info['sheet_title'] = sheet_title
|
||||
all_rate_limit_records.append(rate_limit_info)
|
||||
hit_tapd_rate_limit = True
|
||||
|
||||
for failed_result in creation_failed_results:
|
||||
all_creation_failure_records.append({
|
||||
"sheet_title": sheet_title,
|
||||
"record_id": failed_result.record_id,
|
||||
"title": getattr(failed_result, "title", "未记录标题"),
|
||||
"error_message": failed_result.error_message
|
||||
})
|
||||
|
||||
# 4. 处理校验失败的记录(转换为BugCreationResult格式)
|
||||
validation_failed_results = []
|
||||
@ -773,6 +861,27 @@ def run_once(config_manager: ConfigManager, access_token: str, verbose: bool = F
|
||||
except Exception as e:
|
||||
print(f" ✗ 企业微信推送失败: {e}")
|
||||
|
||||
if len(all_creation_failure_records) > 0 or len(all_rate_limit_records) > 0:
|
||||
print("\n" + "=" * 60)
|
||||
print("发送TAPD开单异常企业微信推送")
|
||||
print("=" * 60)
|
||||
try:
|
||||
wework_config = all_config.get('wework', {})
|
||||
agentid = wework_config.get('agentid')
|
||||
receivers = wework_config.get('receivers')
|
||||
|
||||
if agentid and receivers:
|
||||
notifier = WeWorkNotifier(access_token, agentid, receivers)
|
||||
failure_records = all_rate_limit_records + all_creation_failure_records
|
||||
notifier.send_operation_failure_notification(
|
||||
"autoTAPD TAPD开单异常通知",
|
||||
failure_records
|
||||
)
|
||||
else:
|
||||
print(" ⚠ 企业微信推送配置不完整,跳过推送")
|
||||
except Exception as e:
|
||||
print(f" ✗ 企业微信推送失败: {e}")
|
||||
|
||||
# 7. 显示最终统计(分表统计 + 总体统计)
|
||||
print("\n" + "=" * 60)
|
||||
print("执行完成 - 分表统计")
|
||||
|
||||
@ -207,11 +207,14 @@ class AutoTAPDScheduler:
|
||||
access_token = self.token_manager.get_token()
|
||||
|
||||
# 创建状态同步器
|
||||
wework_config = self.config.get('wework', {})
|
||||
syncer = BugStatusSyncer(
|
||||
access_token=access_token,
|
||||
docid=self.config['smartsheet']['docid'],
|
||||
workspace_id=self.config['tapd']['workspace_id'],
|
||||
test_mode=False
|
||||
test_mode=False,
|
||||
agentid=wework_config.get('agentid'),
|
||||
receivers=wework_config.get('receivers')
|
||||
)
|
||||
|
||||
# 执行一次状态同步
|
||||
@ -230,6 +233,7 @@ class AutoTAPDScheduler:
|
||||
print("本次同步统计:")
|
||||
print(f" 检查bug: {result['checked_count']} 个")
|
||||
print(f" 更新bug: {result['updated_count']} 个")
|
||||
print(f" 查询失败: {result.get('failed_count', 0)} 个")
|
||||
print("-" * 80)
|
||||
else:
|
||||
self.stats['failed_sync_runs'] += 1
|
||||
@ -242,6 +246,8 @@ class AutoTAPDScheduler:
|
||||
stats={
|
||||
"checked_count": result.get('checked_count', 0),
|
||||
"updated_count": result.get('updated_count', 0),
|
||||
"failed_count": result.get('failed_count', 0),
|
||||
"rate_limited": result.get('rate_limited', False),
|
||||
},
|
||||
success=result.get('success', False),
|
||||
error_message=result.get('error_message'),
|
||||
@ -269,6 +275,8 @@ class AutoTAPDScheduler:
|
||||
stats={
|
||||
"checked_count": 0,
|
||||
"updated_count": 0,
|
||||
"failed_count": 0,
|
||||
"rate_limited": False,
|
||||
},
|
||||
success=False,
|
||||
error_message=f"{type(e).__name__}: {e}",
|
||||
|
||||
@ -12,14 +12,16 @@ project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from src.smartsheet import SmartSheetAPI
|
||||
from src.tapd_api import TAPDApi
|
||||
from src.tapd_api import RateLimitError, TAPDApi
|
||||
from src.status_mapper import BugStatusMapper
|
||||
from src.wework_notifier import WeWorkNotifier
|
||||
|
||||
|
||||
class BugStatusSyncer:
|
||||
"""Bug状态同步器"""
|
||||
|
||||
def __init__(self, access_token: str, docid: str, workspace_id: str, test_mode: bool = False):
|
||||
def __init__(self, access_token: str, docid: str, workspace_id: str, test_mode: bool = False,
|
||||
agentid: str = None, receivers: str = None):
|
||||
"""
|
||||
初始化状态同步器
|
||||
|
||||
@ -33,10 +35,13 @@ class BugStatusSyncer:
|
||||
self.docid = docid
|
||||
self.workspace_id = workspace_id
|
||||
self.test_mode = test_mode
|
||||
self.agentid = agentid
|
||||
self.receivers = receivers
|
||||
|
||||
# 初始化API
|
||||
self.smartsheet_api = None
|
||||
self.tapd_api = None
|
||||
self.failure_records = []
|
||||
|
||||
def _sync_sheet_status(self, sheet_id: str, sheet_title: str) -> Dict:
|
||||
"""
|
||||
@ -47,7 +52,7 @@ class BugStatusSyncer:
|
||||
sheet_title: 子表标题
|
||||
|
||||
Returns:
|
||||
Dict: {'checked': int, 'updated': int}
|
||||
Dict: {'checked': int, 'updated': int, 'failed': int, 'rate_limited': bool}
|
||||
"""
|
||||
# 1. 获取字段信息
|
||||
fields = self.smartsheet_api.get_fields(sheet_id)
|
||||
@ -73,12 +78,15 @@ class BugStatusSyncer:
|
||||
|
||||
if len(records) == 0:
|
||||
print(f" ✓ 没有需要同步状态的记录")
|
||||
return {'checked': 0, 'updated': 0}
|
||||
return {'checked': 0, 'updated': 0, 'failed': 0, 'rate_limited': False}
|
||||
|
||||
print(f" → 检查 {len(records)} 个bug的状态...")
|
||||
|
||||
# 3. 逐个查询TAPD bug状态并对比
|
||||
updates = [] # 需要更新的记录列表
|
||||
failed_count = 0
|
||||
checked_count = 0
|
||||
rate_limited = False
|
||||
|
||||
for record in records:
|
||||
record_id = record.get('record_id', '未知')
|
||||
@ -91,6 +99,7 @@ class BugStatusSyncer:
|
||||
current_status = self.smartsheet_api.get_field_value_by_title(record, 'bug状态')
|
||||
|
||||
try:
|
||||
checked_count += 1
|
||||
# 查询TAPD的最新状态
|
||||
bug_info = self.tapd_api.get_bug(bug_id)
|
||||
latest_status_en = bug_info.get('status', '')
|
||||
@ -106,8 +115,31 @@ class BugStatusSyncer:
|
||||
}
|
||||
updates.append(update_record)
|
||||
|
||||
except Exception:
|
||||
# 单个bug查询失败不影响其他bug的同步
|
||||
except RateLimitError as e:
|
||||
failed_count += 1
|
||||
rate_limited = True
|
||||
error_msg = f"TAPD限速,暂停本轮状态同步: {e}"
|
||||
print(f" ✗ 记录 {record_id} 查询失败: {error_msg}")
|
||||
self.failure_records.append({
|
||||
"sheet_title": sheet_title,
|
||||
"record_id": record_id,
|
||||
"title": f"TAPD单号 {bug_id}",
|
||||
"error_message": error_msg,
|
||||
"remaining_count": len(records) - checked_count + 1
|
||||
})
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
# 单个bug查询失败不影响其他bug的同步,但必须记录,避免失败被误判为状态无变化。
|
||||
failed_count += 1
|
||||
error_msg = f"{type(e).__name__}: {e}"
|
||||
print(f" ✗ 记录 {record_id} 查询失败: {error_msg}")
|
||||
self.failure_records.append({
|
||||
"sheet_title": sheet_title,
|
||||
"record_id": record_id,
|
||||
"title": f"TAPD单号 {bug_id}",
|
||||
"error_message": error_msg
|
||||
})
|
||||
continue
|
||||
|
||||
# 4. 批量更新智能表格
|
||||
@ -117,7 +149,12 @@ class BugStatusSyncer:
|
||||
else:
|
||||
print(f" ✓ 所有bug状态均未变化")
|
||||
|
||||
return {'checked': len(records), 'updated': len(updates)}
|
||||
return {
|
||||
'checked': checked_count,
|
||||
'updated': len(updates),
|
||||
'failed': failed_count,
|
||||
'rate_limited': rate_limited
|
||||
}
|
||||
|
||||
def sync_bug_status(self) -> Dict:
|
||||
"""
|
||||
@ -128,6 +165,7 @@ class BugStatusSyncer:
|
||||
'success': bool,
|
||||
'checked_count': int, # 检查的bug数量
|
||||
'updated_count': int, # 更新的bug数量
|
||||
'failed_count': int, # 查询失败的bug数量
|
||||
'error_message': str
|
||||
}
|
||||
"""
|
||||
@ -135,10 +173,14 @@ class BugStatusSyncer:
|
||||
'success': False,
|
||||
'checked_count': 0,
|
||||
'updated_count': 0,
|
||||
'failed_count': 0,
|
||||
'rate_limited': False,
|
||||
'error_message': None
|
||||
}
|
||||
|
||||
try:
|
||||
self.failure_records = []
|
||||
|
||||
# 1. 初始化API
|
||||
print("\n[1/5] 初始化API...")
|
||||
self.smartsheet_api = SmartSheetAPI(self.access_token, self.docid, test_mode=self.test_mode)
|
||||
@ -158,8 +200,14 @@ class BugStatusSyncer:
|
||||
sheet_summaries = [] # 收集每个子表的统计摘要
|
||||
total_checked = 0
|
||||
total_updated = 0
|
||||
total_failed = 0
|
||||
hit_rate_limit = False
|
||||
|
||||
for idx, sheet in enumerate(sheet_list, 1):
|
||||
if hit_rate_limit:
|
||||
print("\n已触发TAPD限速,停止处理后续子表")
|
||||
break
|
||||
|
||||
sheet_id = sheet['sheet_id']
|
||||
sheet_title = sheet.get('title', '未命名')
|
||||
|
||||
@ -174,12 +222,16 @@ class BugStatusSyncer:
|
||||
'sheet_title': sheet_title,
|
||||
'error': None,
|
||||
'checked': sheet_result['checked'],
|
||||
'updated': sheet_result['updated']
|
||||
'updated': sheet_result['updated'],
|
||||
'failed': sheet_result['failed']
|
||||
})
|
||||
|
||||
# 累加总计
|
||||
total_checked += sheet_result['checked']
|
||||
total_updated += sheet_result['updated']
|
||||
total_failed += sheet_result['failed']
|
||||
if sheet_result.get('rate_limited'):
|
||||
hit_rate_limit = True
|
||||
|
||||
except RuntimeError as e:
|
||||
print(f" ✗ 处理失败: {e}")
|
||||
@ -187,7 +239,8 @@ class BugStatusSyncer:
|
||||
'sheet_title': sheet_title,
|
||||
'error': str(e),
|
||||
'checked': 0,
|
||||
'updated': 0
|
||||
'updated': 0,
|
||||
'failed': 0
|
||||
})
|
||||
except Exception as e:
|
||||
print(f" ✗ 未预期的错误: {type(e).__name__}: {e}")
|
||||
@ -195,12 +248,15 @@ class BugStatusSyncer:
|
||||
'sheet_title': sheet_title,
|
||||
'error': f"{type(e).__name__}: {e}",
|
||||
'checked': 0,
|
||||
'updated': 0
|
||||
'updated': 0,
|
||||
'failed': 0
|
||||
})
|
||||
|
||||
# 4. 显示最终统计(分表统计 + 总体统计)
|
||||
result['checked_count'] = total_checked
|
||||
result['updated_count'] = total_updated
|
||||
result['failed_count'] = total_failed
|
||||
result['rate_limited'] = hit_rate_limit
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("状态同步完成 - 分表统计")
|
||||
@ -217,6 +273,8 @@ class BugStatusSyncer:
|
||||
print(f" ✓ 更新: {summary['updated']} 个bug")
|
||||
else:
|
||||
print(f" ✓ 所有bug状态均未变化")
|
||||
if summary['failed'] > 0:
|
||||
print(f" ⚠ 查询失败: {summary['failed']} 个bug")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("状态同步完成 - 总体统计")
|
||||
@ -236,9 +294,30 @@ class BugStatusSyncer:
|
||||
print(f" ✓ 更新: {total_updated} 个bug")
|
||||
else:
|
||||
print(f" ✓ 所有bug状态均未变化")
|
||||
if total_failed > 0:
|
||||
print(f" ⚠ 查询失败: {total_failed} 个bug")
|
||||
print("=" * 60)
|
||||
|
||||
result['success'] = True
|
||||
if self.failure_records and self.agentid and self.receivers:
|
||||
print("\n" + "=" * 60)
|
||||
print("发送bug状态同步异常企业微信推送")
|
||||
print("=" * 60)
|
||||
try:
|
||||
notifier = WeWorkNotifier(self.access_token, self.agentid, self.receivers)
|
||||
notifier.send_operation_failure_notification(
|
||||
"autoTAPD bug状态同步异常通知",
|
||||
self.failure_records
|
||||
)
|
||||
except Exception as e:
|
||||
print(f" ✗ 企业微信推送失败: {e}")
|
||||
elif self.failure_records:
|
||||
print(" ⚠ 企业微信推送配置不完整,跳过同步异常推送")
|
||||
|
||||
result['success'] = total_failed == 0 and not hit_rate_limit
|
||||
if hit_rate_limit:
|
||||
result['error_message'] = "TAPD触发429限速,本轮状态同步已提前停止"
|
||||
elif total_failed > 0:
|
||||
result['error_message'] = f"状态同步存在 {total_failed} 个TAPD查询失败"
|
||||
return result
|
||||
|
||||
except FileNotFoundError as e:
|
||||
|
||||
@ -10,6 +10,16 @@ from requests.auth import HTTPBasicAuth
|
||||
from src.api_logger import get_logger
|
||||
|
||||
|
||||
class RateLimitError(RuntimeError):
|
||||
"""TAPD 触发限速时抛出的专用异常"""
|
||||
|
||||
def __init__(self, message: str, endpoint: str = "", retry_after: str = None, status_code: int = 429):
|
||||
super().__init__(message)
|
||||
self.endpoint = endpoint
|
||||
self.retry_after = retry_after
|
||||
self.status_code = status_code
|
||||
|
||||
|
||||
class TAPDApi:
|
||||
"""TAPD API封装类"""
|
||||
|
||||
@ -52,6 +62,21 @@ class TAPDApi:
|
||||
if test_mode:
|
||||
print(f" ⚠ 测试模式已启用:将显示所有TAPD API调用的详细信息")
|
||||
|
||||
@staticmethod
|
||||
def _looks_like_rate_limit(error_msg: str) -> bool:
|
||||
"""识别 TAPD 以业务错误形式返回的限速信息"""
|
||||
normalized_msg = str(error_msg or "").lower()
|
||||
rate_limit_keywords = [
|
||||
"429",
|
||||
"too many requests",
|
||||
"rate limit",
|
||||
"ratelimit",
|
||||
"限速",
|
||||
"频繁",
|
||||
"请求过多",
|
||||
]
|
||||
return any(keyword in normalized_msg for keyword in rate_limit_keywords)
|
||||
|
||||
def _make_request(self, endpoint: str, method: str = "POST",
|
||||
data: Optional[Dict] = None, params: Optional[Dict] = None) -> Dict:
|
||||
"""
|
||||
@ -151,6 +176,12 @@ class TAPDApi:
|
||||
success=False,
|
||||
error_message=error_msg
|
||||
)
|
||||
if self._looks_like_rate_limit(error_msg):
|
||||
raise RateLimitError(
|
||||
f"TAPD API触发限速: {error_msg}",
|
||||
endpoint=endpoint,
|
||||
status_code=429
|
||||
)
|
||||
raise RuntimeError(f"TAPD API调用失败: {error_msg}")
|
||||
|
||||
# 记录API调用日志(成功)
|
||||
@ -177,9 +208,42 @@ class TAPDApi:
|
||||
)
|
||||
raise RuntimeError(error_msg)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
status_code = getattr(e.response, 'status_code', None)
|
||||
retry_after = None
|
||||
response_text = ""
|
||||
if e.response is not None:
|
||||
retry_after = e.response.headers.get("Retry-After")
|
||||
response_text = e.response.text[:200]
|
||||
|
||||
if status_code == 429:
|
||||
error_msg = f"TAPD API触发429限速: {endpoint}"
|
||||
if retry_after:
|
||||
error_msg += f",建议等待 {retry_after} 秒后重试"
|
||||
if response_text:
|
||||
error_msg += f"\n响应内容: {response_text}"
|
||||
|
||||
self.logger.log_api_call(
|
||||
api_type="tapd",
|
||||
operation=endpoint,
|
||||
request_data=log_request_data,
|
||||
response_data={
|
||||
"status_code": status_code,
|
||||
"retry_after": retry_after,
|
||||
"body": response_text
|
||||
},
|
||||
success=False,
|
||||
error_message=error_msg
|
||||
)
|
||||
raise RateLimitError(
|
||||
error_msg,
|
||||
endpoint=endpoint,
|
||||
retry_after=retry_after,
|
||||
status_code=status_code
|
||||
)
|
||||
|
||||
error_msg = f"TAPD API HTTP错误: {e}"
|
||||
if hasattr(e.response, 'text'):
|
||||
error_msg += f"\n响应内容: {e.response.text[:200]}"
|
||||
if response_text:
|
||||
error_msg += f"\n响应内容: {response_text}"
|
||||
# 记录API调用日志(失败)
|
||||
self.logger.log_api_call(
|
||||
api_type="tapd",
|
||||
|
||||
@ -51,6 +51,23 @@ class WeWorkNotifier:
|
||||
# 发送消息
|
||||
return self._send_text_message(content)
|
||||
|
||||
def send_operation_failure_notification(self, title: str, failure_records: List[Dict]) -> bool:
|
||||
"""
|
||||
发送运行异常通知
|
||||
|
||||
Args:
|
||||
title: 通知标题
|
||||
failure_records: 异常记录列表,每条记录建议包含 sheet_title、record_id、title、error_message
|
||||
|
||||
Returns:
|
||||
bool: 是否发送成功
|
||||
"""
|
||||
if not failure_records:
|
||||
return True
|
||||
|
||||
content = self._build_operation_failure_message(title, failure_records)
|
||||
return self._send_text_message(content)
|
||||
|
||||
def _build_failure_message(self, invalid_records: List[Dict]) -> str:
|
||||
"""
|
||||
构造校验失败消息内容(支持多子表分组)
|
||||
@ -109,6 +126,54 @@ class WeWorkNotifier:
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def _build_operation_failure_message(self, title: str, failure_records: List[Dict]) -> str:
|
||||
"""
|
||||
构造运行异常消息内容
|
||||
|
||||
Args:
|
||||
title: 通知标题
|
||||
failure_records: 异常记录列表
|
||||
|
||||
Returns:
|
||||
str: 格式化后的消息内容
|
||||
"""
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
max_display_count = 20
|
||||
display_records = failure_records[:max_display_count]
|
||||
|
||||
lines = [
|
||||
f"【{title}】",
|
||||
f"时间: {timestamp}",
|
||||
f"异常数量: {len(failure_records)} 条",
|
||||
"",
|
||||
"以下记录在本轮任务中发生运行异常,请查看日志并等待下次调度或人工处理。",
|
||||
"=" * 40
|
||||
]
|
||||
|
||||
for idx, record in enumerate(display_records, 1):
|
||||
sheet_title = record.get("sheet_title", "未知子表")
|
||||
record_title = record.get("title", "(无标题)")
|
||||
record_id = record.get("record_id", "未知")
|
||||
error_message = record.get("error_message", "未知错误")
|
||||
|
||||
lines.append(f"\n[{idx}] {record_title}")
|
||||
lines.append(f"子表: {sheet_title}")
|
||||
lines.append(f"记录ID: {record_id}")
|
||||
lines.append(f"错误: {error_message}")
|
||||
|
||||
remaining_count = record.get("remaining_count")
|
||||
if remaining_count:
|
||||
lines.append(f"本轮暂停处理数量: {remaining_count} 条")
|
||||
|
||||
if len(failure_records) > max_display_count:
|
||||
lines.append("")
|
||||
lines.append(f"仅展示前 {max_display_count} 条,其余请查看日志。")
|
||||
|
||||
lines.append("=" * 40)
|
||||
lines.append("若开单触发TAPD 429限速,系统会等待2分钟重试当前记录;重试仍失败时才停止本轮后续请求。")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def _send_text_message(self, content: str) -> bool:
|
||||
"""
|
||||
发送文本消息
|
||||
|
||||
69
智能表格接口文档.md
69
智能表格接口文档.md
@ -3893,6 +3893,75 @@ Smartsheet 的某个表格中记录相关的参数:
|
||||
对于全员权限,生效成员即是全部文档成员。
|
||||
对于成员额外权限,可以配置生效的成员范围。
|
||||
|
||||
|
||||
|
||||
## 修改文档成员与权限
|
||||
|
||||
最后更新:2026/03/12
|
||||
|
||||
该接口用于管理文档、表格、智能表格的文档成员,支持增加或删除成员、修改成员的权限。
|
||||
|
||||
**请求方式**:POST(**HTTPS**)
|
||||
**请求地址**: https://qyapi.weixin.qq.com/cgi-bin/wedoc/mod_doc_member?access_token=ACCESS_TOKEN
|
||||
|
||||
**请求包体**
|
||||
|
||||
```json
|
||||
{
|
||||
"docid":"DOCID",
|
||||
"update_file_member_list":[
|
||||
{
|
||||
"type":1,
|
||||
"auth":7,
|
||||
"userid":"USERID1"
|
||||
}
|
||||
],
|
||||
"del_file_member_list":[
|
||||
{
|
||||
"type":1,
|
||||
"userid":"USERID2"
|
||||
},
|
||||
{
|
||||
"type":1,
|
||||
"tmp_external_userid":"TMP_EXTERNAL_USERID2"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**参数说明**
|
||||
|
||||
| 参数 | 类型 | 是否必须 | 说明 |
|
||||
| ----------------------- | ------ | -------- | ----------------------------------------------------------- |
|
||||
| docid | string | 是 | 操作的文档id |
|
||||
| update_file_member_list | obj[] | 否 | 更新文档成员的列表, 批次大小最大100 |
|
||||
| type | uint32 | 是 | 文档成员的类型 1:用户。文档成员仅支持按人配置 |
|
||||
| auth | uint32 | 是 | 文档成员内人员获得的权限 1:只读权限 2:读写权限 7:管理员权限 |
|
||||
| userid | string | 否 | 企业内成员的ID |
|
||||
| tmp_external_userid | string | 否 | 外部用户临时id。同一个用户在不同的文档中返回的该id不一致。 |
|
||||
| del_file_member_list | obj[] | 否 | 删除的文档成员列表,批次大小最大一百 |
|
||||
| type | uint32 | 是 | 文档成员的类型 1:用户。文档成员仅支持按人配置 |
|
||||
| userid | string | 否 | 企业内成员的ID |
|
||||
| tmp_external_userid | string | 否 | 外部用户临时id。同一个用户在不同的文档中返回的该id不一致。 |
|
||||
|
||||
**权限说明**
|
||||
|
||||
- 自建应用需配置到“[可调用应用](https://developer.work.weixin.qq.com/document/path/97795#43883)”列表中的应用secret所获取的accesstoken来调用([accesstoken如何获取?](https://developer.work.weixin.qq.com/document/path/97795#10013/第三步:获取access_token))
|
||||
- 第三方应用需具有“文档”权限
|
||||
- 代开发自建应用需具有“文档”权限
|
||||
- 只能操作该应用创建的文档
|
||||
|
||||
**返回示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": 0,
|
||||
"errmsg": "ok"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 查询智能表格子表权限
|
||||
|
||||
该接口用于查询智能表格子表权限详情
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user