Files
TFSImport/README.md
T
chenshoulin 8a6f9d55bc refactor: 移除不再使用的配置文件与代码,优化用户界面
- 删除 config.json 和 requirements.txt 文件,移除 Python 版相关代码,简化项目结构。
- 更新 README.md,调整工具说明,强调仅保留 C# WinForms 版本。
- 在 MainForm.cs 中添加输入框的占位提示,提升用户体验。
- 更新 .gitignore,添加 debug.log 文件以记录运行时日志。
- 移除与 Python 版相关的示例 CSV 文件,确保数据源一致性。
2026-11-01 21:44:31 +08:00

157 lines
12 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 桌面版),数据源为 `.xlsx`(默认模板随程序发布),图形界面预览后导入。
通信走 TFS Client OM(VS/Team Explorer 同款内部 SOAP):该 TFS 2013 实例只有 `/_apis/projects` 通,`wit/fields`、`wit/classificationnodes`、`wit/wiql` 全部 404,REST 路线走不通。原先另有一套 Python CLI(REST `/_apis/wit/*`)实现,因上述限制无法在本环境工作,已移除。
## 目录结构
```
TFSImport/
├── data/
│ └── work_items.csv # 本地工时台账(含人员/项目,不入库)
├── 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 # 桌面版配置
```
## 快速开始
### 桌面版
```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、项目、工作项类型与账号密码(认证固定 NTLM,用户名可写 `user` 或 `DOMAIN\user`),点「测试连接」——右上状态灯变绿即连通。
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」的普通输出。
## 配置说明
桌面版的配置落在 exe 同目录的 `config.json`(界面改动后自动写回);exe 目录不可写时回退到 `%AppData%\TfsExcelImporter\config.json`。
配置的语义、留空字段如何解析见 [Config.cs](TfsExcelImporter/Config.cs),关键字段:
| 键 | 说明 |
| --- | --- |
| `TfsUrl` / `Project` / `WorkItemType` | 目标集合、项目、工作项类型(`Task` 或 `Product Backlog Item`) |
| `IterationPath` | 可只写 `2026年Sprint\2026年Sprint10`,程序自动补项目名前缀 |
| `AreaPath` | 区域路径;界面「高级工具」栏也可改 |
| `Username` / `Password` | 认证固定 NTLM,用户名可写 `user` 或 `DOMAIN\user` |
| `FinancialProjectRef` / `CompletedRef` / `LogDateRef` / `OriginalEstimateRef` | 自定义字段引用名;留空则按显示名在服务器字段表里查(支持前缀模糊匹配,如 `Completed` → `Completed Work`) |
| `ParentPbiId` | 所有 Task 默认挂接的父 PBI Id;Excel 每行 `PBI Id` 列可覆盖 |
| `SkipExistingTitles` | 同项目/同类型/同迭代下按标题去重 |
| `DefaultState` | Excel `State` 列留空时的默认值 |
| `LastExcelPath` | 上次选中的数据文件,界面回填用 |
> 配置里含明文密码,请勿提交到共享仓库;`config.json` 保持空值,凭据本地填写。
## Excel 列
桌面版 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` 不再写入、保持为空。
真实台账含人员与项目信息,不入库,放在 `data\work_items.csv`(已在 `.gitignore` 里)。
- 表头按名匹配,顺序不限,`AssignedTo` / `Assigned To` 等价;缺列按模板顺序兜底,但该列已被别的表头占用时不套用位置(旧版模板文件不会串列)。
- 空 `Title` 的行直接跳过;`State` 为空时用 `DefaultState`;未识别的列(如 `Group`)忽略。
- 日期支持 `yyyy-MM-dd[ / ][ HH:mm[:ss]]` 等格式,Excel 日期序列号(如 `46300`)会被还原成日期。
## 环境坑点
这些是实测结论,改代码前先看:
- **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` 置空强制直连,界面不提供代理开关(本机若只能经代理上网,程序会直连失败)。
- **TF400276 / 关闭状态只读**:改已关闭工作项的 Area 会被拒;`FixAreaAsync` 走「重新打开 → 改 Area → 还原状态」的状态循环,中途失败回滚状态。
- **`System.CreatedDate` 写不进去**:OM 14.x 没有 `SaveFlags.BypassRules`(15.x 才加),该字段服务器托管;`Microsoft.VSTS.Common.ClosedDate` 在 `State=Done` 时按普通字段可尝试写入。模板已去掉日期列,所以导入不再走这条路——若某模板要求 `Done` 必填 Closed Date,需另加列或改回写日期。
- **字段校验不可绕过**:OM 14.x 没有 `SaveFlags.BypassRules`,旧界面上那个 `bypassRules` 勾选从未真正生效,已移除。校验失败时日志与 report CSV 直接给出「字段(原因) → 怎么改」,列表字段还会列出允许值,按提示改 Excel 对应列后重跑即可。
- **孤立 Task 在积压工作页不可见**:TFS 2013 的 backlog/任务板按「父项 → 子任务」组织,没挂父链接的 Task 只能在查询里看到,所以导入必须带父项(界面填父 PBI Id,或 Excel 里加 `PBI Id` 列)。
- **DNS 解析失败 / 超时**:多为 VPN 或网络断开,不是代码问题;桌面版会对这类异常附加排查提示。
## 验证
桌面版改完,先用只含几行的 Excel 跑一次小批量导入(先删服务器已有同标题项或勾「跳过已存在标题」),再在积压工作页展开父项确认子任务可见;改动涉及界面布局或图标时,跑 `python tools/win_screenshot.py` 抓图核对。确认无误后再跑全量。