Files
chenshoulin 58850314cf 初始提交:QuickTeam 工单接口导入工具
用「WCF + 通用存储过程网关」接口替代 Selenium(D://Learning//Python//工单自动化)导入工单:
单 Sprint 12 个项目 175s 跑完(旧方案 25~35 分钟),失败可断点续跑且不重复建单。

- 分层:transport(HTTP/协议)/ api(存储过程语义)/ domain(纯规则)/
  workflow(阶段编排)/ store(SQLite 台账)/ report / CLI
- 幂等:台账驱动断点续跑;dry-run 与跳过行不污染进度;--rollback-on-fail 清理半成品
- 命令:plan / apply / report / status,真实写入需 --yes
- tools:只读探针、存储过程源码导出(sp_helptext)、导入诊断
- docs:调研与设计文档(含联调踩到的 5 个坑)+ 6 个关键存储过程源码

实测:Sprint 2026 Sprint 10 的 12 个项目全部完成建单→导入→确认→推送 TFS,
工单 225134–225145,35 个功能模块,重跑 plan 全部 skip-pushed。69 个单测通过。
2026-11-05 19:14:25 +08:00

59 lines
2.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""异常与错误分类。
WCF 的错误是 HTML 页面,真正的信息藏在 "The exception message is '...'" 里。
分类的目的是决定「重试 / 跳过 / 整批中止」,而不是把所有失败都当网络问题重试。
"""
from __future__ import annotations
from enum import StrEnum
class Category(StrEnum):
NET = "NET" # 网络层:可重试
SP_MISSING = "SP_MISSING" # 存储过程不存在:配置/版本问题,不重试
PARAM = "PARAM" # 参数错误/类型转换:数据问题,不重试
EXCEL = "EXCEL" # Excel 解析失败(Column1 != 0)
TFS = "TFS" # 推送 TFS 失败
AUTH = "AUTH" # 登录失败
UNKNOWN = "UNKNOWN"
class QtError(RuntimeError):
"""带分类的接口错误。"""
def __init__(self, message: str, category: Category = Category.UNKNOWN, *, retryable: bool = False):
super().__init__(message)
self.message = message
self.category = category
self.retryable = retryable
def __str__(self) -> str: # pragma: no cover - 直通
return f"[{self.category}] {self.message}"
@classmethod
def from_http(cls, status: int, body: str) -> "QtError":
"""把 WCF 的 HTML 错误页转成可读错误。"""
message = extract_exception_message(body) or f"HTTP {status}"
lowered = message.lower()
if "could not find stored procedure" in lowered:
return cls(message, Category.SP_MISSING)
if "missing parameter" in lowered or "conversion" in lowered or "procedure or function" in lowered:
return cls(message, Category.PARAM)
if status >= 500:
return cls(message, Category.NET, retryable=True)
return cls(message, Category.UNKNOWN)
def extract_exception_message(body: str) -> str:
"""从 WCF 错误页里抠出真正的异常文本。"""
import html
import re
match = re.search(r"exception message is '(.*?)'\s*\.?\s*See server logs", body, re.S)
if match:
return html.unescape(match.group(1)).strip()
plain = re.sub(r"<[^>]+>", " ", body)
plain = html.unescape(re.sub(r"\s+", " ", plain)).strip()
return plain[:400]