'phase1-1.1'

This commit is contained in:
zelong 2025-12-15 18:52:51 +08:00
commit b1f1324d02
11 changed files with 6961 additions and 0 deletions

135
.gitignore vendored Normal file
View File

@ -0,0 +1,135 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# PEP 582; __pypackages__
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyderworkspace
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# VSCode
.vscode/
# Custom
logs/
.claude/

1218
TAPD接口文档.md Normal file

File diff suppressed because it is too large Load Diff

9
config/config.ini Normal file
View File

@ -0,0 +1,9 @@
# autoTAPD 配置文件
[TAPD]
# TAPD项目ID
workspace_id = 58335167
[SmartSheet]
# 智能表格文档ID
docid = dcRybSHojZR9-b5ePgDp33yr29bQy6BtQiVJ-nSGUM-ot6FSpq-TGW9jEn_f7ORLcFWRj9zvxtB1PP_TE29qPoAw

13
requirements.txt Normal file
View File

@ -0,0 +1,13 @@
# 企业微信智能表格API测试工具依赖包
# HTTP请求库
requests>=2.31.0
# JSON处理Python内置无需安装
# json
# 日期时间处理Python内置无需安装
# datetime
# 操作系统接口Python内置无需安装
# os

7
src/__init__.py Normal file
View File

@ -0,0 +1,7 @@
"""
autoTAPD - Debug阶段自动开单工具
从腾讯智能表格读取bug信息自动在TAPD创建bug单
"""
__version__ = "0.1.0"
__author__ = "autoTAPD Team"

372
src/api_test.py Normal file
View File

@ -0,0 +1,372 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
企业微信智能表格API测试工具
用于测试和熟悉企业微信文档API接口
"""
import requests
import json
import os
from datetime import datetime
class WeWorkAPITester:
"""企业微信API测试类"""
def __init__(self):
self.access_token = None
self.log_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'logs', 'api_test_log.json')
self.base_url = "https://qyapi.weixin.qq.com/cgi-bin"
# 确保日志文件存在
self._init_log_file()
def _init_log_file(self):
"""初始化日志文件"""
if not os.path.exists(self.log_file):
with open(self.log_file, 'w', encoding='utf-8') as f:
json.dump({"records": []}, f, ensure_ascii=False, indent=2)
def _log_api_call(self, operation, request_data, response_data):
"""记录API调用到JSON文件"""
try:
# 读取现有记录
with open(self.log_file, 'r', encoding='utf-8') as f:
log_data = json.load(f)
# 添加新记录
record = {
"operation": operation,
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"request": request_data,
"response": response_data
}
log_data["records"].append(record)
# 写回文件
with open(self.log_file, 'w', encoding='utf-8') as f:
json.dump(log_data, f, ensure_ascii=False, indent=2)
print(f"✓ API调用已记录到日志文件")
except Exception as e:
print(f"✗ 记录日志失败: {str(e)}")
def get_access_token(self, corpid, corpsecret):
"""
获取access_token
Args:
corpid: 企业ID
corpsecret: 应用的凭证密钥
Returns:
bool: 是否成功获取token
"""
print("\n=== 获取access_token ===")
url = f"{self.base_url}/gettoken"
params = {
"corpid": corpid,
"corpsecret": corpsecret
}
try:
response = requests.get(url, params=params, timeout=10)
response_data = response.json()
# 记录API调用
request_data = {
"url": url,
"params": {
"corpid": corpid,
"corpsecret": "***" # 隐藏敏感信息
}
}
self._log_api_call("get_access_token", request_data, response_data)
# 检查返回结果
if response_data.get("errcode") == 0:
self.access_token = response_data.get("access_token")
expires_in = response_data.get("expires_in")
print(f"✓ 成功获取access_token")
print(f" Token: {self.access_token[:20]}...")
print(f" 有效期: {expires_in}秒 ({expires_in//60}分钟)")
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 create_doc(self, doc_name, doc_type=10, spaceid=None, fatherid=None):
"""
新建文档
Args:
doc_name: 文档名称
doc_type: 文档类型 (3:文档 4:表格 10:智能表格)
spaceid: 空间ID (可选)
fatherid: 父目录ID (可选)
Returns:
dict: 包含docid和url的字典失败返回None
"""
print("\n=== 新建文档 ===")
if not self.access_token:
print("✗ 请先获取access_token")
return None
url = f"{self.base_url}/wedoc/create_doc"
params = {"access_token": self.access_token}
# 构造请求体
data = {
"doc_type": doc_type,
"doc_name": doc_name
}
if spaceid:
data["spaceid"] = spaceid
if fatherid:
data["fatherid"] = fatherid
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("create_doc", request_data, response_data)
# 检查返回结果
if response_data.get("errcode") == 0:
docid = response_data.get("docid")
doc_url = response_data.get("url")
print(f"✓ 文档创建成功")
print(f" 文档名称: {doc_name}")
print(f" 文档ID: {docid}")
print(f" 访问链接: {doc_url}")
return {"docid": docid, "url": doc_url}
else:
print(f"✗ 创建失败")
print(f" 错误码: {response_data.get('errcode')}")
print(f" 错误信息: {response_data.get('errmsg')}")
return None
except Exception as e:
print(f"✗ 请求异常: {str(e)}")
return None
def rename_doc(self, docid, new_name):
"""
重命名文档
Args:
docid: 文档ID
new_name: 新的文档名称
Returns:
bool: 是否成功
"""
print("\n=== 重命名文档 ===")
if not self.access_token:
print("✗ 请先获取access_token")
return False
url = f"{self.base_url}/wedoc/rename_doc"
params = {"access_token": self.access_token}
data = {
"docid": docid,
"new_name": new_name
}
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("rename_doc", request_data, response_data)
# 检查返回结果
if response_data.get("errcode") == 0:
print(f"✓ 重命名成功")
print(f" 文档ID: {docid}")
print(f" 新名称: {new_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 delete_doc(self, docid):
"""
删除文档
Args:
docid: 文档ID
Returns:
bool: 是否成功
"""
print("\n=== 删除文档 ===")
if not self.access_token:
print("✗ 请先获取access_token")
return False
url = f"{self.base_url}/wedoc/del_doc"
params = {"access_token": self.access_token}
data = {"docid": docid}
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("delete_doc", request_data, response_data)
# 检查返回结果
if response_data.get("errcode") == 0:
print(f"✓ 删除成功")
print(f" 文档ID: {docid}")
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 print_menu():
"""打印菜单"""
print("\n" + "="*50)
print("企业微信智能表格API测试工具")
print("="*50)
print("1. 获取access_token")
print("2. 新建文档")
print("3. 重命名文档")
print("4. 删除文档")
print("5. 查看日志文件")
print("0. 退出")
print("="*50)
def main():
"""主函数"""
tester = WeWorkAPITester()
while True:
print_menu()
choice = input("\n请选择操作 (0-5): ").strip()
if choice == "0":
print("\n感谢使用,再见!")
break
elif choice == "1":
print("\n请输入企业微信认证信息:")
corpid = input("企业ID (corpid): ").strip()
corpsecret = input("应用密钥 (corpsecret): ").strip()
if corpid and corpsecret:
tester.get_access_token(corpid, corpsecret)
else:
print("✗ corpid和corpsecret不能为空")
elif choice == "2":
doc_name = input("\n请输入文档名称: ").strip()
if not doc_name:
print("✗ 文档名称不能为空")
continue
print("\n文档类型:")
print(" 3 - 文档")
print(" 4 - 表格")
print(" 10 - 智能表格 (默认)")
doc_type_input = input("请选择文档类型 (直接回车默认为10): ").strip()
doc_type = int(doc_type_input) if doc_type_input else 10
tester.create_doc(doc_name, doc_type)
elif choice == "3":
docid = input("\n请输入文档ID: ").strip()
new_name = input("请输入新的文档名称: ").strip()
if docid and new_name:
tester.rename_doc(docid, new_name)
else:
print("✗ 文档ID和新名称不能为空")
elif choice == "4":
docid = input("\n请输入要删除的文档ID: ").strip()
if docid:
confirm = input(f"确认要删除文档 {docid} 吗? (y/n): ").strip().lower()
if confirm == 'y':
tester.delete_doc(docid)
else:
print("已取消删除操作")
else:
print("✗ 文档ID不能为空")
elif choice == "5":
print("\n=== 查看日志文件 ===")
try:
with open(tester.log_file, 'r', encoding='utf-8') as f:
log_data = json.load(f)
records = log_data.get("records", [])
if not records:
print("日志文件为空")
else:
print(f"\n共有 {len(records)} 条记录\n")
for i, record in enumerate(records[-10:], 1): # 只显示最近10条
print(f"记录 {i}:")
print(f" 操作: {record.get('operation')}")
print(f" 时间: {record.get('timestamp')}")
print(f" 响应: errcode={record.get('response', {}).get('errcode')}, "
f"errmsg={record.get('response', {}).get('errmsg')}")
print()
if len(records) > 10:
print(f"(仅显示最近10条完整日志请查看: {tester.log_file})")
except Exception as e:
print(f"✗ 读取日志文件失败: {str(e)}")
else:
print("✗ 无效的选择,请重新输入")
input("\n按回车键继续...")
if __name__ == "__main__":
main()

143
src/config.py Normal file
View File

@ -0,0 +1,143 @@
"""
配置管理模块
负责读取和管理config.ini配置文件
"""
import os
import configparser
from pathlib import Path
class ConfigManager:
"""配置管理器"""
def __init__(self, config_path=None):
"""
初始化配置管理器
Args:
config_path: 配置文件路径如果为None则使用默认路径
"""
self.config = configparser.ConfigParser()
# 确定配置文件路径
if config_path is None:
# 默认路径:项目根目录/config/config.ini
project_root = Path(__file__).parent.parent
config_path = project_root / "config" / "config.ini"
self.config_path = Path(config_path)
# 读取配置文件
self._load_config()
def _load_config(self):
"""加载配置文件"""
# 检查配置文件是否存在
if not self.config_path.exists():
raise FileNotFoundError(
f"配置文件不存在: {self.config_path}\n"
f"请确保配置文件存在于正确的位置。"
)
# 读取配置文件
try:
self.config.read(self.config_path, encoding='utf-8')
print(f"成功加载配置文件: {self.config_path}")
except Exception as e:
raise RuntimeError(f"读取配置文件失败: {e}")
def get_tapd_config(self):
"""
获取TAPD配置
Returns:
dict: 包含workspace_id的字典
Raises:
ValueError: 配置项缺失时抛出
"""
if not self.config.has_section('TAPD'):
raise ValueError("配置文件缺少[TAPD]节")
if not self.config.has_option('TAPD', 'workspace_id'):
raise ValueError("配置文件[TAPD]节缺少workspace_id配置项")
workspace_id = self.config.get('TAPD', 'workspace_id').strip()
if not workspace_id:
raise ValueError("workspace_id配置项不能为空")
return {
'workspace_id': workspace_id
}
def get_smartsheet_config(self):
"""
获取智能表格配置
Returns:
dict: 包含docid的字典
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 = self.config.get('SmartSheet', 'docid').strip()
if not docid:
raise ValueError("docid配置项不能为空")
return {
'docid': docid
}
def get_all_config(self):
"""
获取所有配置
Returns:
dict: 包含所有配置的字典
"""
return {
'tapd': self.get_tapd_config(),
'smartsheet': self.get_smartsheet_config()
}
def print_config(self):
"""打印当前配置信息(用于调试)"""
print("\n=== 当前配置信息 ===")
try:
tapd_config = self.get_tapd_config()
print(f"[TAPD]")
print(f" workspace_id: {tapd_config['workspace_id']}")
except ValueError as e:
print(f"[TAPD] 配置错误: {e}")
try:
smartsheet_config = self.get_smartsheet_config()
print(f"[SmartSheet]")
print(f" docid: {smartsheet_config['docid']}")
except ValueError as e:
print(f"[SmartSheet] 配置错误: {e}")
print("==================\n")
if __name__ == "__main__":
# 测试代码
try:
config_manager = ConfigManager()
config_manager.print_config()
# 测试获取所有配置
all_config = config_manager.get_all_config()
print("所有配置获取成功:")
print(f" TAPD workspace_id: {all_config['tapd']['workspace_id']}")
print(f" SmartSheet docid: {all_config['smartsheet']['docid']}")
except Exception as e:
print(f"错误: {e}")

149
src/main.py Normal file
View File

@ -0,0 +1,149 @@
"""
autoTAPD 主程序
Debug阶段自动开单工具 - 第一阶段1.1版本
"""
import sys
import argparse
from pathlib import Path
# 添加项目根目录到Python路径
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
from src.config import ConfigManager
def parse_arguments():
"""
解析命令行参数
Returns:
argparse.Namespace: 解析后的参数对象
"""
parser = argparse.ArgumentParser(
description='autoTAPD - Debug阶段自动开单工具',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例用法:
python main.py --access-token YOUR_ACCESS_TOKEN
python main.py -t YOUR_ACCESS_TOKEN --config /path/to/config.ini
"""
)
# 必需参数
parser.add_argument(
'-t', '--access-token',
required=True,
help='企业微信access_token必填'
)
# 可选参数
parser.add_argument(
'-c', '--config',
default=None,
help='配置文件路径(默认: config/config.ini'
)
parser.add_argument(
'-v', '--verbose',
action='store_true',
help='显示详细输出信息'
)
return parser.parse_args()
def validate_access_token(access_token):
"""
验证access_token是否有效
Args:
access_token: 待验证的token
Raises:
ValueError: token无效时抛出
"""
if not access_token or not access_token.strip():
raise ValueError("access_token不能为空")
# 基本格式检查企业微信的access_token通常较长
if len(access_token.strip()) < 20:
raise ValueError("access_token格式可能不正确长度过短")
def main():
"""主函数"""
print("=" * 60)
print("autoTAPD - Debug阶段自动开单工具")
print("版本: 0.1.0 (第一阶段1.1)")
print("=" * 60)
print()
try:
# 1. 解析命令行参数
print("[1/3] 解析命令行参数...")
args = parse_arguments()
if args.verbose:
print(f" - access_token: {args.access_token[:10]}...(已隐藏)")
print(f" - config: {args.config or '使用默认路径'}")
print(f" - verbose: {args.verbose}")
# 2. 验证access_token
print("[2/3] 验证access_token...")
validate_access_token(args.access_token)
print(" ✓ access_token格式验证通过")
# 3. 加载配置文件
print("[3/3] 加载配置文件...")
config_manager = ConfigManager(config_path=args.config)
# 获取并显示配置信息
all_config = config_manager.get_all_config()
print(" ✓ 配置文件加载成功")
print()
# 显示配置摘要
print("=" * 60)
print("配置摘要:")
print("-" * 60)
print(f"TAPD workspace_id: {all_config['tapd']['workspace_id']}")
print(f"SmartSheet docid: {all_config['smartsheet']['docid'][:20]}...")
print(f"Access Token: {args.access_token[:10]}...(已隐藏)")
print("=" * 60)
print()
print("✓ 所有初始化步骤完成!")
print()
print("提示: 第一阶段1.1已完成,后续将实现智能表格数据读取功能。")
return 0
except FileNotFoundError as e:
print(f"\n✗ 错误: {e}")
print("\n解决方案:")
print(" 1. 检查配置文件是否存在")
print(" 2. 使用 --config 参数指定正确的配置文件路径")
return 1
except ValueError as e:
print(f"\n✗ 错误: {e}")
print("\n解决方案:")
print(" 1. 检查配置文件中的配置项是否完整")
print(" 2. 确保所有必填项都已填写")
print(" 3. 检查access_token是否正确")
return 1
except Exception as e:
print(f"\n✗ 未预期的错误: {e}")
print(f"错误类型: {type(e).__name__}")
if args.verbose:
import traceback
print("\n详细错误信息:")
traceback.print_exc()
return 1
if __name__ == "__main__":
sys.exit(main())

638
开发路线.md Normal file
View File

@ -0,0 +1,638 @@
# Debug阶段自动开单工具 - 开发路线
## 一、项目概述
本项目旨在实现一个自动化工具从腾讯智能表格读取bug信息自动在TAPD创建bug单实现表格到TAPD的单向同步。
## 二、开发说明
### 2.1 access_token管理策略
- **第五阶段前:** 所有需要access_token的操作由开发者手动获取token并传入程序
- **第五阶段:** 实现access_token的自动获取与缓存机制
- **第五阶段后:** 程序自动管理access_token的获取、缓存和刷新
### 2.2 日志记录策略
- **第八阶段前:** 使用简单的print输出关键信息
- **第八阶段:** 实现完整的日志记录系统
## 三、字段映射关系
| 智能表格列名 | TAPD Bug字段 | 字段类型 | 说明 |
|------------|-------------|---------|------|
| 标题 | title | string | 必填同步为TAPD标题 |
| 详细描述 | description | string | 必填 |
| 优先级 | priority_label | string | 必填,单选 |
| 严重程度 | severity | string | 必填,单选 |
| 处理人 | current_owner | string | 必填 |
| 验证人 | confirmer | string | 必填 |
| 发现版本 | version_report | string | 必填 |
| 模块 | module | string | 必填 |
| 开单状态 | - | - | 工具回写,✅成功 / ❌失败 |
| TAPD单号 | - | - | 工具回写,可点击跳转 |
| bug状态 | - | - | 工具回写同步TAPD当前状态 |
**字段说明:**
- `priority_label`:推荐使用的优先级字段(兼容自定义优先级)
- `severity` 可选值fatal(致命)、serious(严重)、normal(一般)、prompt(提示)、advice(建议)
- 必填字段共8个需在第一阶段校验
## 四、开发阶段规划
### 前期准备API测试工具开发
**目标:** 创建测试脚本熟悉智能表格API为后续开发做准备
**任务清单:**
1. 创建API测试脚本api_test.py
2. 实现获取access_token功能
- 手动输入corpid和corpsecret
- 调用企业微信API获取access_token
- 将token保存到变量中供后续使用
3. 实现新建文档功能
- 调用创建智能表格接口
- 传入必要参数doc_name、doc_type等
4. 实现重命名文档功能
- 调用重命名文档接口
- 传入docid和新名称
5. 实现删除文档功能
- 调用删除文档接口
- 传入docid
6. 实现执行记录功能
- 记录每次API调用的请求内容
- 记录请求发起时间
- 记录返回结果
- 将所有记录保存到JSON文件api_test_log.json
7. 实现简单的命令行交互
- 选择要执行的操作
- 输入必要参数
- 显示执行结果
**验收标准:**
- [x] 能成功获取access_token
- [x] 能成功创建智能表格文档
- [x] 能成功重命名文档
- [x] 能成功删除文档
- [x] 所有API调用都有完整的记录保存到JSON文件
- [x] JSON文件格式清晰包含请求和响应的完整信息
- [x] 脚本有基本的错误处理
**技术要点:**
- 使用requests库调用企业微信API
- JSON文件的读写操作
- 时间戳的格式化
- 基本的命令行交互
**JSON记录格式示例**
```json
{
"records": [
{
"operation": "get_access_token",
"timestamp": "2025-12-15 10:30:00",
"request": {
"url": "https://qyapi.weixin.qq.com/cgi-bin/gettoken",
"params": {
"corpid": "xxx",
"corpsecret": "***"
}
},
"response": {
"errcode": 0,
"errmsg": "ok",
"access_token": "xxx",
"expires_in": 7200
}
}
]
}
```
**潜在问题记录:**
- access_token的有效期管理本阶段暂不处理手动重新获取即可
- API调用失败时的重试机制本阶段暂不实现
---
### 第一阶段:基础框架搭建与数据读取
#### 阶段1.1:建立基础框架
**目标:** 搭建项目基础结构,实现配置管理
**任务清单:**
1. 创建项目目录结构
2. 实现ini配置文件读取模块
- 配置项workspace_idTAPD项目ID
- 配置项docid智能表格文档ID
3. 实现命令行参数解析
- 接收access_token参数
- 接收其他必要参数
4. 实现基本的错误处理
- 配置文件不存在
- 配置项缺失
- access_token未提供
**验收标准:**
- [ ] 能成功读取ini配置文件中的所有配置项
- [ ] 能通过命令行参数接收access_token
- [ ] 配置缺失时有清晰的错误提示
- [ ] 使用print输出关键信息
**技术要点:**
- 使用configparser读取ini文件
- 使用argparse解析命令行参数
- 基本的异常处理
**潜在问题记录:**
- 配置文件路径的处理(相对路径 vs 绝对路径)
---
#### 阶段1.2:实现智能表格数据读取与校验
**目标:** 扫描智能表格,获取"开单状态"为空的行,并校验必填项
**任务清单:**
1. 实现智能表格API调用模块
- 接收命令行传入的access_token
- 获取子表信息get_sheet接口
- 获取字段信息get_fields接口
- 查询记录get_records接口
2. 实现字段ID映射
- 根据字段标题获取字段ID
- 建立字段名称到字段ID的映射关系
3. 实现数据过滤逻辑
- 筛选"开单状态"字段为空的记录
- 使用filter_spec参数实现服务端过滤
4. 实现必填项校验
- 校验8个必填字段是否为空
- 记录校验失败的行和具体缺失字段
5. 输出扫描结果
- 使用print打印待开单的记录数量
- 使用print打印校验通过和失败的记录详情
**验收标准:**
- [ ] 能成功连接智能表格并获取子表信息
- [ ] 能正确获取所有字段的ID和名称
- [ ] 能准确筛选出"开单状态"为空的记录
- [ ] 能正确校验所有必填字段
- [ ] 校验失败时能明确指出缺失的字段
- [ ] 使用print输出关键信息
- [ ] 异常情况(如网络错误、字段不存在)有明确的错误提示
**技术要点:**
- 使用企业微信文档API的smartsheet相关接口
- 使用filter_spec实现条件查询OPERATOR_IS_EMPTY
- 字段类型判断:文本、单选、人员等
- 数据结构设计:记录对象的表示方式
**潜在问题记录:**
- 字段值的合法性校验(如优先级、严重程度的可选值)暂不实现,后续优化
- 人员字段的userid格式校验暂不实现
- 大量数据的分页处理limit和offset参数
---
### 第二阶段TAPD API集成与开单功能
**目标:** 根据获取到的需要开单的行调用TAPD API创建bug单
**任务清单:**
1. 实现TAPD API认证模块
- 从环境变量读取TAPD API User和Password
- 实现Basic Auth认证
2. 实现字段映射转换
- 将智能表格字段值转换为TAPD API参数
- 处理人员字段的userid映射
- 处理单选字段的值映射
3. 实现TAPD创建bug接口调用
- 构造请求参数
- 调用POST /bugs接口
- 解析返回结果
4. 实现错误处理
- API调用失败重试机制最多3次
- 记录失败原因
5. 批量处理逻辑
- 逐条创建bug单
- 记录成功和失败的数量
**验收标准:**
- [ ] 能成功从环境变量读取TAPD认证信息
- [ ] 能正确将智能表格数据转换为TAPD API参数
- [ ] 能成功调用TAPD API创建bug单
- [ ] 创建成功后能获取到bug ID
- [ ] API调用失败时有重试机制
- [ ] 所有操作都有详细的日志记录
- [ ] 能处理TAPD API返回的各种错误码
**技术要点:**
- 使用requests库的auth参数实现Basic Auth
- TAPD API的workspace_id参数必填
- 返回结果中的Bug.id即为TAPD单号
- 错误码处理:认证失败、参数错误、权限不足等
**潜在问题记录:**
- TAPD API的频率限制需要注意
- 字段值的格式转换(如日期格式)
- 人员字段的userid在TAPD和企业微信中可能不一致需要确认映射关系
---
### 第三阶段:回写结果到智能表格
**目标:** 将开单结果回写到智能表格,更新"开单状态"、"TAPD单号"、"bug状态"字段
**任务清单:**
1. 实现智能表格更新记录接口
- 调用update_records接口
- 构造更新参数
2. 实现开单成功的回写逻辑
- 更新"开单状态"为✅
- 更新"TAPD单号"为可点击的链接
- 更新"bug状态"为TAPD当前状态
3. 实现开单失败的回写逻辑
- 更新"开单状态"为❌
- 记录失败原因(可选,后续优化)
4. 实现批量回写
- 收集所有需要更新的记录
- 批量调用更新接口
5. 实现回写失败的处理
- 记录回写失败的记录
- 重试机制
**验收标准:**
- [ ] 开单成功后能正确回写✅到"开单状态"字段
- [ ] 能正确回写TAPD单号并生成可点击的链接
- [ ] 能正确回写bug状态
- [ ] 开单失败后能正确回写❌到"开单状态"字段
- [ ] 回写失败时有重试机制
- [ ] 所有操作都有详细的日志记录
**技术要点:**
- 智能表格的链接字段格式CellUrlValue类型
- TAPD单号链接格式https://tapd-api.bilibili.co/tapd/workspace_id/bugtrace/bugs/view/bug_id
- 批量更新时注意API的请求限制
- 字段类型:文本、链接
**潜在问题记录:**
- 回写失败时是否需要回滚TAPD的bug单暂不实现
- 链接字段的格式需要确认
- bug状态的同步频率需求文档建议15分钟一次
---
### 第四阶段:定时任务与服务化
**目标:** 将工具部署为服务,实现定时扫描和状态同步
**任务清单:**
1. 实现定时任务调度
- 使用schedule库或APScheduler
- 配置开单扫描频率默认5分钟
- 配置状态同步频率默认15分钟
2. 实现bug状态同步功能
- 查询已开单的记录
- 调用TAPD API获取bug最新状态
- 更新智能表格的"bug状态"字段
3. 实现服务启动和停止
- 命令行参数解析
- 优雅退出机制
4. 实现配置文件扩展
- 添加轮询频率配置
- 添加状态同步频率配置
5. 完善日志和监控
- 添加运行统计信息
- 添加性能监控
**验收标准:**
- [ ] 服务能正常启动和停止
- [ ] 能按配置的频率执行开单扫描
- [ ] 能按配置的频率执行状态同步
- [ ] 状态同步功能正常工作
- [ ] 服务异常时能自动恢复
- [ ] 有完善的运行日志和统计信息
**技术要点:**
- 定时任务的线程安全
- 信号处理SIGINT、SIGTERM
- 异常捕获和恢复
- 配置热加载(可选)
**潜在问题记录:**
- 长时间运行的内存泄漏问题
- access_token过期的自动刷新
- 并发执行的冲突处理
---
### 第五阶段access_token自动获取与缓存
**目标:** 实现access_token的自动获取与缓存机制不再需要手动传入token
**任务清单:**
1. 实现企业微信认证模块
- 从环境变量读取corpid和corpsecret
- 调用企业微信API获取access_token
- 处理认证失败的情况
2. 设计token缓存结构
- 存储token值
- 存储过期时间
- 存储获取时间
3. 实现token缓存逻辑
- 首次获取时缓存
- 使用前检查是否过期
- 过期前自动刷新建议提前5分钟
4. 实现持久化存储(可选)
- 使用文件存储token
- 服务重启后能恢复token
5. 实现token失效处理
- 检测token失效API返回42001错误码
- 自动重新获取
6. 重构现有代码
- 移除命令行传入access_token的逻辑
- 所有API调用自动使用缓存的token
**验收标准:**
- [ ] 能从环境变量读取corpid和corpsecret
- [ ] 能自动获取access_token
- [ ] token能正确缓存和复用
- [ ] token过期前能自动刷新
- [ ] token失效时能自动重新获取
- [ ] 不再需要手动传入access_token
- [ ] 减少了获取token的API调用次数
- [ ] 环境变量未设置时有清晰的错误提示
**技术要点:**
- token有效期为7200秒2小时
- 建议在过期前5分钟刷新
- 使用os.environ读取环境变量
- 线程安全的缓存实现
- 异常处理:网络错误、认证失败等
**潜在问题记录:**
- 多实例部署时的token共享问题
- 时钟不同步导致的过期判断错误
- 环境变量的安全性问题
---
### 第六阶段:企业微信推送功能
**目标:** 实现开单失败时的企业微信推送通知
**任务清单:**
1. 实现企业微信消息推送API
- 调用发送应用消息接口
- 构造消息内容
2. 实现推送对象配置
- 配置接收推送的人员列表
- 支持按部门推送
3. 实现推送内容设计
- 失败原因
- 失败的记录信息
- 智能表格链接
4. 实现推送频率控制
- 避免频繁推送
- 合并相同类型的错误
5. 实现推送失败处理
- 记录推送失败的日志
- 重试机制
**验收标准:**
- [ ] 开单失败时能正确推送消息
- [ ] 推送内容清晰明确
- [ ] 推送对象配置灵活
- [ ] 推送频率合理
- [ ] 推送失败有日志记录
**技术要点:**
- 企业微信消息推送API
- 消息格式文本、markdown等
- 推送对象userid、部门ID
- 频率控制:时间窗口、消息合并
**潜在问题记录:**
- 推送频率限制
- 消息长度限制
- 用户未关注应用时的处理
---
### 第七阶段:字段合法性校验与优化
**目标:** 完善字段值的合法性校验,提高数据质量
**任务清单:**
1. 实现优先级字段校验
- 获取TAPD项目的优先级配置
- 校验智能表格中的值是否合法
2. 实现严重程度字段校验
- 校验可选值fatal、serious、normal、prompt、advice
3. 实现人员字段校验
- 校验userid格式
- 校验用户是否存在
4. 实现模块字段校验
- 获取TAPD项目的模块配置
- 校验模块是否存在
5. 实现版本字段校验
- 获取TAPD项目的版本配置
- 校验版本是否存在
6. 完善错误提示
- 明确指出不合法的字段和原因
- 提供修正建议
**验收标准:**
- [ ] 能正确校验所有字段的合法性
- [ ] 不合法的值有明确的错误提示
- [ ] 校验失败时不创建TAPD单
- [ ] 校验结果能回写到智能表格(可选)
**技术要点:**
- TAPD API获取字段配置接口
- 智能表格单选字段的option_id
- 数据缓存:避免频繁查询配置
- 错误信息的友好展示
**潜在问题记录:**
- TAPD配置变更时的同步问题
- 自定义字段的校验规则
---
### 第八阶段:日志系统与性能优化
**目标:** 实现完整的日志记录系统,优化系统性能,添加监控和告警
**任务清单:**
1. 实现日志记录模块
- 使用Python logging模块
- 日志级别DEBUG、INFO、WARNING、ERROR
- 日志输出:控制台 + 文件
- 日志轮转:按日期或大小分割
2. 重构现有代码
- 将所有print语句替换为日志记录
- 添加详细的日志信息
- 统一日志格式
3. 实现批量处理优化
- 批量创建TAPD bug
- 批量更新智能表格
4. 实现并发处理
- 使用线程池或协程
- 控制并发数量
5. 实现性能监控
- 记录每次扫描的耗时
- 记录API调用的耗时
- 记录成功率
6. 实现告警机制
- 失败率过高时告警
- 服务异常时告警
7. 实现数据统计
- 每日开单数量
- 失败原因统计
- 性能趋势分析
**验收标准:**
- [ ] 日志模块能正常输出到控制台和文件
- [ ] 日志格式统一,信息完整
- [ ] 日志文件能自动轮转
- [ ] 所有关键操作都有日志记录
- [ ] 批量处理性能提升明显
- [ ] 并发处理稳定可靠
- [ ] 有完善的性能监控数据
- [ ] 异常情况能及时告警
- [ ] 有详细的统计报表
**技术要点:**
- logging模块的配置和使用
- RotatingFileHandler或TimedRotatingFileHandler
- 线程池concurrent.futures.ThreadPoolExecutor
- 协程asyncio、aiohttp
- 监控Prometheus、Grafana可选
- 告警:企业微信、邮件等
**潜在问题记录:**
- 并发时的API频率限制
- 数据一致性问题
- 监控数据的存储和查询
- 日志文件的磁盘占用
---
## 五、技术栈
- **编程语言:** Python 3.8+
- **核心库:**
- requestsHTTP请求
- configparser配置文件解析
- argparse命令行参数解析
- jsonJSON文件处理
- datetime时间处理
- logging日志记录第八阶段
- schedule/APScheduler定时任务第四阶段
- **API**
- 企业微信文档API
- TAPD Open API
## 六、配置文件示例
### config.ini
```ini
[TAPD]
workspace_id = 10158231
[SmartSheet]
docid = your_doc_id
[Schedule]
# 第四阶段添加
scan_interval = 5
sync_interval = 15
```
### 环境变量(第五阶段开始使用)
```bash
# 企业微信(第五阶段)
WEWORK_CORPID=your_corpid
WEWORK_CORPSECRET=your_corpsecret
# TAPD第二阶段
TAPD_API_USER=your_api_user
TAPD_API_PASSWORD=your_api_password
```
## 七、目录结构(建议)
```
autoTAPD/
├── config/
│ └── config.ini # 配置文件
├── src/
│ ├── __init__.py
│ ├── api_test.py # 前期准备API测试工具
│ ├── config.py # 配置管理
│ ├── wework_api.py # 企业微信API第五阶段完善
│ ├── smartsheet.py # 智能表格操作
│ ├── tapd_api.py # TAPD API
│ ├── validator.py # 数据校验
│ ├── mapper.py # 字段映射
│ ├── token_cache.py # token缓存第五阶段
│ ├── scheduler.py # 定时任务(第四阶段)
│ ├── logger.py # 日志模块(第八阶段)
│ └── main.py # 主程序
├── logs/ # 日志目录(第八阶段)
│ └── api_test_log.json # API测试记录
├── tests/ # 测试代码
├── requirements.txt # 依赖包
└── README.md # 项目说明
```
## 八、开发原则
1. **小步走原则:** 每个阶段完成一个独立功能,验收通过后再进入下一阶段
2. **先实现后优化:** 前期以完成功能为主,不过度设计,后期再优化性能和潜在问题
3. **异常处理:** 所有外部调用都要有异常处理,关键操作需要重试机制
4. **配置驱动:** 尽量通过配置文件控制行为,避免硬编码
5. **代码复用:** 相同逻辑封装为函数或类,避免重复代码
6. **阶段性验收:** 每个阶段完成后必须通过验收标准才能进入下一阶段
## 九、注意事项
1. **API频率限制** 注意企业微信和TAPD的API调用频率限制
2. **数据安全:** 敏感信息如token、密码不要硬编码使用环境变量
3. **错误恢复:** 服务异常时要能自动恢复,不影响后续执行
4. **向后兼容:** 配置文件和数据结构的变更要考虑向后兼容
5. **文档更新:** 每个阶段完成后更新相关文档
6. **access_token管理** 第五阶段前手动传入,第五阶段后自动管理
7. **日志策略:** 第八阶段前使用print第八阶段后使用logging模块
## 十、阶段依赖关系
```
前期准备API测试工具
第一阶段(基础框架 + 数据读取)
第二阶段TAPD开单
第三阶段(回写结果)
第四阶段(定时任务)
第五阶段access_token自动化
第六阶段(企业微信推送)
第七阶段(字段合法性校验)
第八阶段(日志系统 + 性能优化)
```
## 十一、后续优化方向
1. 支持多个智能表格和TAPD项目
2. 支持自定义字段映射配置
3. 支持webhook触发实时开单
4. 支持双向同步TAPD更新同步到智能表格
5. 提供Web管理界面
6. 支持数据统计和报表
7. 支持更多的字段类型和复杂映射规则

4222
智能表格接口文档.md Normal file

File diff suppressed because it is too large Load Diff

55
需求文档.md Normal file
View File

@ -0,0 +1,55 @@
# **Debug阶段自动开单工具需求**
## **一、功能概述**
策划在腾讯智能表格填写bug信息工具定时扫描并自动在TAPD创建bug单实现表格到TAPD的单向同步。
## **二、表格字段设计**
| **列** | **说明** | **备注** |
| -------- | -------- | ---------------- |
| 标题 | 必填 | 同步为TAPD标题 |
| 详细描述 | 必填 | |
| 优先级 | 必填 | 单选 |
| 严重程度 | 必填 | 单选 |
| 处理人 | 必填 | |
| 验证人 | 必填 | |
| 发现版本 | 必填 | |
| 模块 | 必填 | |
| 开单状态 | 工具回写 | ✅成功 / ❌失败 |
| TAPD单号 | 工具回写 | 可点击跳转 |
| bug状态 | 工具回写 | 同步TAPD当前状态 |
##
## **三、处理逻辑**
1. 定时扫描"开单状态"为空的行
2. 校验必填项是否完整
3. 调用TAPD API创建bug单
4. 回写结果到表格
##
## **四、异常处理**
● 开单成功:开单状态列打✅,回写单号和状态
● 开单失败:开单状态列打❌,企业微信推送报错信息给相关人员
##
## **五、运行机制**
● 轮询频率建议5分钟一次可配置
● bug状态同步频率建议15分钟一次可配置
#