'phase1-1.1'
This commit is contained in:
commit
b1f1324d02
135
.gitignore
vendored
Normal file
135
.gitignore
vendored
Normal 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
1218
TAPD接口文档.md
Normal file
File diff suppressed because it is too large
Load Diff
9
config/config.ini
Normal file
9
config/config.ini
Normal 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
13
requirements.txt
Normal file
@ -0,0 +1,13 @@
|
||||
# 企业微信智能表格API测试工具依赖包
|
||||
|
||||
# HTTP请求库
|
||||
requests>=2.31.0
|
||||
|
||||
# JSON处理(Python内置,无需安装)
|
||||
# json
|
||||
|
||||
# 日期时间处理(Python内置,无需安装)
|
||||
# datetime
|
||||
|
||||
# 操作系统接口(Python内置,无需安装)
|
||||
# os
|
||||
7
src/__init__.py
Normal file
7
src/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""
|
||||
autoTAPD - Debug阶段自动开单工具
|
||||
从腾讯智能表格读取bug信息,自动在TAPD创建bug单
|
||||
"""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
__author__ = "autoTAPD Team"
|
||||
372
src/api_test.py
Normal file
372
src/api_test.py
Normal 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
143
src/config.py
Normal 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
149
src/main.py
Normal 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
638
开发路线.md
Normal 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_id(TAPD项目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+
|
||||
- **核心库:**
|
||||
- requests:HTTP请求
|
||||
- configparser:配置文件解析
|
||||
- argparse:命令行参数解析
|
||||
- json:JSON文件处理
|
||||
- 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
4222
智能表格接口文档.md
Normal file
File diff suppressed because it is too large
Load Diff
55
需求文档.md
Normal file
55
需求文档.md
Normal file
@ -0,0 +1,55 @@
|
||||
# **Debug阶段自动开单工具需求**
|
||||
|
||||
## **一、功能概述**
|
||||
|
||||
策划在腾讯智能表格填写bug信息,工具定时扫描并自动在TAPD创建bug单,实现表格到TAPD的单向同步。
|
||||
|
||||
|
||||
|
||||
## **二、表格字段设计**
|
||||
|
||||
| **列** | **说明** | **备注** |
|
||||
| -------- | -------- | ---------------- |
|
||||
| 标题 | 必填 | 同步为TAPD标题 |
|
||||
| 详细描述 | 必填 | |
|
||||
| 优先级 | 必填 | 单选 |
|
||||
| 严重程度 | 必填 | 单选 |
|
||||
| 处理人 | 必填 | |
|
||||
| 验证人 | 必填 | |
|
||||
| 发现版本 | 必填 | |
|
||||
| 模块 | 必填 | |
|
||||
| 开单状态 | 工具回写 | ✅成功 / ❌失败 |
|
||||
| TAPD单号 | 工具回写 | 可点击跳转 |
|
||||
| bug状态 | 工具回写 | 同步TAPD当前状态 |
|
||||
|
||||
##
|
||||
|
||||
## **三、处理逻辑**
|
||||
|
||||
1. 定时扫描"开单状态"为空的行
|
||||
|
||||
2. 校验必填项是否完整
|
||||
|
||||
3. 调用TAPD API创建bug单
|
||||
|
||||
4. 回写结果到表格
|
||||
|
||||
##
|
||||
|
||||
## **四、异常处理**
|
||||
|
||||
● 开单成功:开单状态列打✅,回写单号和状态
|
||||
|
||||
● 开单失败:开单状态列打❌,企业微信推送报错信息给相关人员
|
||||
|
||||
##
|
||||
|
||||
## **五、运行机制**
|
||||
|
||||
● 轮询频率:建议5分钟一次(可配置)
|
||||
|
||||
● bug状态同步频率:建议15分钟一次(可配置)
|
||||
|
||||
#
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user