Files
TFSImport/README.md
T
chenshoulin d055b415fa feat: 增强便携版支持与配置管理
- 更新 .gitignore 文件,添加便携版分发目录和 Fody 生成的配置文件。
- 修改 README.md,详细说明便携版的构建与分发流程,强调单文件分发的优势。
- 在 Config.cs 中实现便携配置管理,优先使用 exe 同目录的 config.json,提升用户体验。
- 更新 ExcelService.cs,优化默认模板的导出逻辑,支持现场替换模板文件。
- 在 MainForm.cs 中加载应用图标,提升界面一致性。
- 调整 Program.cs,确保在单文件分发时正确加载 native DLL。
2026-11-01 20:17:56 +08:00

190 lines
13 KiB
Markdown
Raw 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.
# TFSImport
TFS 2013 工作项批量导入工具集,用于把 Excel/CSV 里的工时台账(标题、状态、指派人、完成量、日期等)批量写成团队项目里的工作项,并挂到指定的父项(Product Backlog Item / 占位 Backlog Item)下,使其在「积压工作」页和任务板可见。
目标环境:
| 项 | 值 |
| --- | --- |
| 服务器 | `http://pdtfs01.dev.goldenstand.cn:8080/tfs/DefaultCollection` |
| 团队项目 | `Tools_Dev` |
| 团队 | `DataTechnologies` |
| 迭代 | `2026年Sprint\2026年Sprint10` |
| 认证 | 域账号 NTLM(内网直连,不走代理) |
## 两套工具,先用哪个
| | [TfsExcelImporter](TfsExcelImporter)(C# WinForms) | [tfs_import.py](tfs_import.py)(Python CLI) |
| --- | --- | --- |
| 通信协议 | TFS Client OM(VS/Team Explorer 同款内部 SOAP) | REST `/_apis/wit/*` |
| 适用 | **本环境可用**。该 TFS 2013 实例只有 `/_apis/projects` 通,`wit/fields`、`wit/classificationnodes`、`wit/wiql` 全部 404 | 服务器开放了 wit REST 路由时使用 |
| 数据源 | `.xlsx`(默认模板随程序发布) | `data/work_items.csv`(GBK/UTF-8 均可,需自备) |
| 界面 | 图形界面,预览后导入 | 命令行,`--dry-run` 先校验 |
Python 版在本环境会因 REST 路由 404 而失败,保留用于其它 TFS 实例或接口可用时的批量脚本化导入。
## 目录结构
```
TFSImport/
├── tfs_import.py # Python 命令行导入器(REST)
├── config.json # Python 版配置
├── requirements.txt # requests / requests-ntlm
├── data/
│ └── work_items.example.csv # 列结构与示例行(UTF-8 BOM);真实台账不入库
├── templates/
│ └── TFS导入模板.xlsx # 默认模板:构建时嵌入 exe,「导出Excel模板」原样导出
├── tools/
│ ├── make_icon.py # 生成 app.ico(9 个尺寸帧,纯标准库 SDF 栅格化 + 手写 PNG/ICO)
│ ├── pe_icon.py # 解析 exe 的 PE 图标资源,导出某帧 PNG(换图标后核对用)
│ └── win_screenshot.py # 冒烟截图:拉起 exe、PrintWindow 抓窗、导出标题栏图标(ctypes)
└── TfsExcelImporter/ # C# WinForms 桌面版(主用)
├── TfsExcelImporter.csproj # net48 + ClosedXML + TFS ExtendedClient 14.*
├── FodyWeavers.xml # Costura 单文件打包配置(便携版)
├── NativeLibraryLoader.cs # 释放并预加载内嵌的 TFS native DLL
├── AppIcon.cs # 从内嵌 app.ico 取 Form.Icon(窗口/任务栏)
├── app.ico # 应用图标(16~256 共 9 档,tools/make_icon.py 生成)
├── MainForm.cs # 导入 / 修 Area / 补挂父项流程
├── MainFormUi.cs # 界面外壳:步骤卡片布局、手风琴折叠、状态呈现
├── Theme.cs # 设计 token(颜色/字号/间距/缩放)与自绘控件
├── app.manifest # System DPI 感知,像素尺寸按缩放换算
├── TfsClient.cs # TFS Client OM 封装
├── ExcelService.cs # 读 Excel、导出默认模板、时间/数值归一
├── Config.cs # exe 同目录 config.json 读写
└── config.json # 桌面版配置
```
## 快速开始
### C# 桌面版(主用)
```powershell
dotnet build TfsExcelImporter.sln -c Release
# 或用 Visual Studio 打开 TfsExcelImporter.sln(目标框架 net48,无需装 VS 也能 dotnet build)
TfsExcelImporter\bin\Release\net48\TfsExcelImporter.exe
```
「导出Excel模板」优先用 exe 同目录的 `TFS导入模板.xlsx`(现场可替换),没有则从 exe 内嵌资源导出——**分发时只带一个 exe 就够,不用再附带模板文件**。
应用图标是 `TfsExcelImporter/app.ico`(蓝色渐变圆角底 + 白色上升箭头),csproj 里 `ApplicationIcon` 写进 exe 的 Win32 图标资源,同时以托管资源再嵌一份给 `Form.Icon` 用(多尺寸帧,高 DPI 下不糊)。要改设计就改 `tools/make_icon.py` 后重跑,覆盖 `app.ico` 再编译:
```powershell
python tools/make_icon.py # 重新生成 app.ico,并出预览图 tools/icon_preview.png
python tools/win_screenshot.py TfsExcelImporter\bin\Release\net48\TfsExcelImporter.exe tools\_win.png
```
界面按 4 步排:顶部状态条常驻显示服务器/项目/类型与连接状态,四张卡片一次只展开一张(展开一张会把其它收成一行摘要),预览表格永远占满剩余高度。
1. **步骤 1 连接**:填 URL、项目、工作项类型、认证方式与域账号,点「测试连接」——右上状态灯变绿即连通;内网保持「使用系统代理」**不勾选**。
2. **步骤 2 选数据**:点「加载迭代列表」选目标迭代(也可手填完整路径);点「导出 Excel 模板」拿默认模板,或「浏览...」选已有 `.xlsx`。需要挂父项时填「父 PBI Id」(默认全部挂同一个;Excel 里加 `PBI Id` 列可逐行指定不同父项)。
3. **步骤 3 预览核对**:选完文件自动读表,表格里逐行核对;缺迭代/文件路径无效时红字直接标在对应输入框下方,不再弹框。
4. **步骤 4 导入**:底部操作条点「开始导入 TFS」,进度条 + `n/m` + 成功/跳过/失败三个计数实时更新,日志抽屉逐行输出 `OK/SKIP/FAIL` 与工作项 ID;跑完点「打开报告 CSV」看逐行结果。
5. 历史数据 Area 不对、或漏挂父项时,展开底部「高级工具」用「按迭代修 Area」「补挂父项」批量纠正;Area 路径也在这一栏里填(以前只能手改 config.json)。
桌面版按 System DPI 感知实现,界面尺寸随 Windows 显示缩放换算;**改了显示缩放比例要重启程序**才生效。
### 便携版(单 exe,免安装)
构建产物只有一个文件:`TfsExcelImporter\bin\Release\net48\TfsExcelImporter.exe`(约 9.3 MB)。依赖和资源全部内嵌:
| 内容 | 打包方式 |
| --- | --- |
| 58 个托管 DLL(ClosedXML / OpenXml / TFS Client OM …) | `Costura.Fody` 内嵌,运行时经 `AssemblyResolve` 从内存加载,不落盘 |
| `Microsoft.WITDataStore32/64.dll`(纯 win32,TFS OM 用) | 独立内嵌,启动时按进程位数释放到 `%TEMP%\TfsExcelImporter\native\{内容hash}` 并 `LoadLibrary` 预加载 |
| 默认模板 `TFS导入模板.xlsx` | 内嵌资源,「导出Excel模板」时释放;exe 同目录放同名文件则优先用它 |
| `config.json` | 首次保存时写到 exe 同目录(便携);目录只读时回退 `%AppData%\TfsExcelImporter\config.json` |
分发:
```powershell
mkdir dist
copy TfsExcelImporter\bin\Release\net48\TfsExcelImporter.exe dist\
```
把 `dist\TfsExcelImporter.exe` 拷到任意目录双击即用。想连配置一起带走,就把 exe 同目录的 `config.json` 一起拷。
前提:目标机需有 .NET Framework 4.8(Win10 1903+ / Win11 系统自带,一般无需额外安装)。`FodyWeavers.xml` 是 Costura 的打包配置,删掉它或用 `-p:FodyDisable=true` 构建即回到「exe + 一堆 DLL」的普通输出。
### Python 版
```powershell
pip install -r requirements.txt
python tfs_import.py --dry-run # 只校验数据与字段映射,不写入
python tfs_import.py --list-fields # 列出服务器全部字段(显示名/引用名)
python tfs_import.py # 正式导入
python tfs_import.py --link-existing # 已导入的项补挂父链接(不新建)
python tfs_import.py --skip-existing-titles # 跳过服务器已有的同标题项
python tfs_import.py --input other.csv --limit 5
```
每次运行在 `data/import_report_<时间戳>.csv` 生成逐行结果报告(行号 / Title / 结果 / WorkItemId / 错误)。
## 配置说明
桌面版的配置落在 exe 同目录的 `config.json`(界面改动后自动写回);exe 目录不可写时回退到 `%AppData%\TfsExcelImporter\config.json`。
配置的语义、留空字段如何解析见 [tfs_import.py:49](tfs_import.py#L49) 与 [Config.cs](TfsExcelImporter/Config.cs),关键字段:
| 键 | 说明 |
| --- | --- |
| `tfs_url` / `project` / `work_item_type` | 目标集合、项目、工作项类型(`Task` 或 `Product Backlog Item`) |
| `iteration_path` | 可只写 `2026年Sprint\2026年Sprint10`,程序自动补项目名前缀 |
| `auth.mode` | `ntlm`(域账号,典型)/ `basic` / `default`(当前 Windows 凭据,仅桌面版) |
| `field_refs` | 语义名 → 字段引用名;自定义字段留空时按 `resolve_fields_by_display_name` 的显示名在服务器字段表里查(支持前缀模糊匹配,如 `Completed` → `Completed Work`) |
| `parent_title` / `parent_id` | 父工作项,二选一;按标题匹配不到会直接报错并提示改用 `parent_id` |
| `bypass_rules` | 是否请求绕过字段校验规则;桌面版走 Client OM(14.x 无 `SaveFlags.BypassRules`),实际由服务器规则决定 |
| `skip_existing_titles` | 同项目/同类型/同迭代下按标题去重 |
> 配置里含明文密码,请勿提交到共享仓库;`config.json` 保持空值,凭据本地填写。
## Excel / CSV 列
桌面版 Excel 模板 = 仓库里的 `templates\TFS导入模板.xlsx`,界面「导出Excel模板」直接复制它:
`Title`、`State`、`Assigned To`、`FinancialProject`、`Completed`、`LogDate`、`PBI Id`、`Description`
写入 TFS 的对应关系:
| 模板列 | 目标字段 |
| --- | --- |
| `Description` | `System.Description` |
| `Completed` | 显示名 `Completed`(模糊匹配到 `Completed Work`)解析出的字段 |
| —(无列) | `Original Estimate` 取该行 `Completed` 的值;引用名留空则按显示名 `Original Estimate` 解析,可写在 `OriginalEstimateRef` 里固定 |
系统日期列(`Created Date` / `Closed Date`)已从模板移除:`System.CreatedDate` 由服务器按导入时间填写,`Microsoft.VSTS.Common.ClosedDate` 不再写入、保持为空。
Python 版仍读 `data/work_items.example.csv` 的旧列(含 `CreatedDate`、`ClosedDate`、`Notes`),未随模板调整。
真实台账含人员与项目信息,不入库;本地准备数据文件:
```powershell
copy data\work_items.example.csv data\work_items.csv # Python 版默认读这个路径,已在 .gitignore 里
```
- 表头按名匹配,顺序不限,`AssignedTo` / `Assigned To` 等价;缺列按模板顺序兜底,但该列已被别的表头占用时不套用位置(旧版模板文件不会串列)。
- 空 `Title` 的行直接跳过;`State` 为空时用 `default_state`;未识别的列(如 `Group`)忽略。
- 日期支持 `yyyy-MM-dd[ / ][ HH:mm[:ss]]` 等格式,Excel 日期序列号(如 `46300`)会被还原成日期。
- CSV 从 Excel 另存时注意编码,脚本按 `utf-8-sig` → `gbk` 依次尝试。
## 环境坑点
这些是实测结论,改代码前先看:
- **wit REST 路由全 404**:TFS 2013 RTM/低 Update 不带 `/_apis/wit/*`,`Classification.asmx?WSDL` 也被禁 → 桌面版整体走 Client OM(NuGet `Microsoft.TeamFoundationServer.ExtendedClient` 钉 `14.*`,对应 VS2015 客户端代际;15.x 不兼容 TFS 2013)。
- **502 Bad Gateway**:TFS OM 内部用 `HttpWebRequest`,默认跟随 IE 系统代理,代理会拦内网 TFS。桌面版默认把 `WebRequest.DefaultWebProxy` 置空直连;确实要走代理时勾「使用系统代理」并带上 Windows 凭据过代理认证。
- **TF400276 / 关闭状态只读**:改已关闭工作项的 Area 会被拒;`FixAreaAsync` 走「重新打开 → 改 Area → 还原状态」的状态循环,中途失败回滚状态。
- **`System.CreatedDate` 写不进去**:OM 14.x 没有 `SaveFlags.BypassRules`(15.x 才加),该字段服务器托管;`Microsoft.VSTS.Common.ClosedDate` 在 `State=Done` 时按普通字段可尝试写入。模板已去掉日期列,所以导入不再走这条路——若某模板要求 `Done` 必填 Closed Date,需另加列或改回写日期。
- **孤立 Task 在积压工作页不可见**:TFS 2013 的 backlog/任务板按「父项 → 子任务」组织,没挂父链接的 Task 只能在查询里看到,所以导入必须带 `parent_title`/`parent_id`。
- **DNS 解析失败 / 超时**:多为 VPN 或网络断开,不是代码问题;桌面版会对这类异常附加排查提示。
## 验证
改动 Python 版后至少跑:
```powershell
python -m py_compile tfs_import.py
python tfs_import.py --dry-run
```
桌面版改完跑一次 `--limit` 级别的小批量导入(先删服务器已有同标题项或开启跳过),并在积压工作页展开父项确认子任务可见。