Skip to content

✨ feat(metadata): 统一源树与发布物读取入口 - #65

Open
mrbbbaixue wants to merge 1 commit into
mainfrom
feat/unified-metadata-reader
Open

mrbbbaixue wants to merge 1 commit into
mainfrom
feat/unified-metadata-reader

Conversation

@mrbbbaixue

@mrbbbaixue mrbbbaixue commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

这个 PR 在解决什么

同一份元数据原来有四处各自解析:源树读取(LoadSource)、构建展平(MetadataFlattener)、发布导出(MetadataSourceExporter)、运行时缓存(MetadataClient)。Include 展开、继承、短 ID 补全、Source 归一化各写一遍,行为靠人肉对齐。这个 PR 收成一条路:

  • MetadataReader 读并合成,产出规范化模型和捕获的原始文档 / Include 边;
  • MetadataExporter 只决定输出怎么组织(Flat / Tree / Mixed);
  • MetadataBuilder 变成「校验 + 调 Export + 变量替换 + WebP + 发布 XSD 校验」;
  • MetadataClient 只负责取数,落一份规范化完整快照。

读取(Ra3.BattleNet.Metadata/Reading/)

  • MetadataReader.Read(pathOrUrl, options).Root 是唯一入口,本地源树和已发布展平物走同一条路:Include 递归展开、菱形去重、循环报错,所有路径锁在 SourceRoot 内。
  • 资源 ID 统一生成 scope/local;已有含 /、: 的完整键原样保留并精确匹配,短 ID 只在唯一命中时补全,多义直接失败。
  • Source 相对定义它的文档解释,输出成入口目录相对路径;SourceRoot 只做包含校验,不当地址回退;Image@Url 优先于 Source;HTTP 文档不能引用本地文件。
  • MetadataParser 退回纯 XML 到模型的投影。

导出(Exporting/)

  • MetadataExporter.Export 用一套布局算法产出 Flat / Tree / Mixed;Mixed 用 --inline= 挑哪些边留在外部;--inline-manifests、--resolve-variables 是显式开关。
  • 写盘前检查输出与源树重叠、软链、文件与目录碰撞;失败先清空 Output/ 再抛。
  • MetadataSourceExporter 保留发布物反向导出作者源的能力,仍按短 ID 写。

构建(Building/)

  • Build 现在就是 Reader + Exporter + 原有的 XSD、域模型、变量残留、WebP、发布 XSD 校验;MetadataFlattener 和独立的展平复制管线删除。
  • 删除 MetadataBuilder.Load、LoadSource、Open。

契约变化

  • SchemaVersion 改由作者写在源根 metadata.xml 的根节点,缺失时构建直接失败;构建不再注入版本。
  • 删除构建期 ContentRevision:构建参数、CLI、发布 XSD 必填项、build.sh、deploy.yml 传参一起移除。
  • CLI 去掉 --schema-version=、--id-format,新增 export --layout=flat|tree|mixed、--inline=、--inline-manifests、--resolve-variables。
  • 编辑器判断「这是发布物」的探针从 SchemaVersion 属性改成 Tags/Commit 是否已被替换成时间戳。

缓存(Cache/)

  • 每份取到的 XML 先过 Reader,落一份规范化后的完整快照,不再保留旧布局的兼容读取。
  • URI 版 GetLeafAsync / RefreshLeafAsync / GetCachedLeafAsync 换成按 PackageEntry 的 GetPackageAsync / GetCachedPackageAsync / RefreshPackageAsync。
  • 保持不变:根 304 时子文档仍逐个再校验、子文档失败回退上一份完整快照、离线读不触网、validator 仍绑原始字节。

顺带修的 bug

叶子清单 stub 的 Source 被当成「待内联正文」继续追踪,HTTP 文档会不断拼出新 URL;Linux CI 上表现为单测 13 分钟没有任何进展被取消,Windows 本地因路径长度限制先炸,掩盖了它。

结构与测试

  • 生产代码按 Reading/、Exporting/、Building/、Cache/ 分层,测试目录同构;只覆盖旧入口的 IncludeTests 并入新用例,StageATests 更名 BuildTests;新增 Reader、Export、PathSecurity、CompositionCache 等用例。
  • 文档同步 README.md、ARCHITECTURE.md、library-api.md、data-authoring.md、build-and-release.md;新增 3 篇决策笔记和作者向预览页 .agents/docs/visuals/metadata-authoring.html。

破坏性变更与下游

  • 库读取 API、缓存叶子方法签名、冒号格式 ID、--schema-version / --content-revision 均不兼容。
  • 桌面端配套 PR:RA3BattleNet/Desktop#182。它当前的子模块指针停在 rebase 前的 a237549,需要更新到本 PR 的新提交后,再跑默认引用构建与服务冒烟。

验证

  • verify.yml:文档校验、dotnet test、源数据构建全部通过;Workers 预览构建通过。
  • 本地 Windows dotnet test Metadata.sln:430 通过、0 失败。
  • node scripts/verify-docs.mjs:23 篇笔记、2 份生成物、41 份文档通过。
  • 已 rebase 到 main 最新 ac26057,分支保持单个提交。

@cloudflare-workers-and-pages

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Failed ❌

View logs ↗
25f42b9 2026-11-01T16:32:42.791Z View logs ↗

@mrbbbaixue

Copy link
Copy Markdown
Member Author

第二步:Desktop仓库接入,并且实现Metadata缓存功能

@mrbbbaixue

Copy link
Copy Markdown
Member Author

审查结论

能简化,但建议先修正确性问题,再合并。审查提交:25f42b91c1143444efee84ee537dd98663fbed4f。

需要修复

1. [P1] HTTP 文件去重不应忽略路径大小写

位置:MetadataFlattener.cs:445–446

Processing、Expanded 都使用 OrdinalIgnoreCase,把本地路径的比较规则带到了 HTTP URL。

已复现:入口分别 Include a.xml 和 A.xml,两份文件声明不同 Mod,结果只返回小写文件的 Mod;大写文件没有发请求。嵌套引用也可能被误判成循环。

建议:文件身份比较由源实现决定,不能在展平器里统一忽略大小写。

2. [P1] 60 秒超时没有覆盖正文读取

位置:HttpXmlSource.cs:189–192

Send 使用 ResponseHeadersRead,随后同步读取正文,没有独立超时或取消控制。此时 HttpClient.Timeout 只管到响应头,见 .NET 官方说明。

已启动实际 HTTP 服务验证:立即返回响应头,64 秒后发送 <Metadata/>,MetadataBuilder.Open 在 64.1 秒后成功返回,没有触发配置的 60 秒超时。正文持续停滞时,调用可能长期阻塞。

建议:请求与正文读取共用明确的超时预算,而不是只设置 HttpClient.Timeout。

3. [P2] 入口越界检查发生在读取之后

位置:MetadataBuilder.cs:182–194

OpenWithSource 先 LoadDocument,进入 Flatten 后才检查入口是否在 SourceRoot 内;本地分支也一样。

已复现:指定允许根为 /root/,打开 /outside.xml,程序先发一次 GET,再抛越界异常。这违反了“读取目标前拒绝”的契约,也是 LoadSource 委托新入口后引入的退化。

建议:路径规范化、确定源根和入口边界检查,都放在首次读取之前。

4. [P2] HTTP 入口仍然请求两次

位置:HttpXmlSource.cs:52–85

只有 Exists 写缓存;LoadDocument 自己读取后不写缓存,Exists 也不先查缓存。

已复现:Open 读取远端源树,入口 GET 次数为 2。不仅多一次网络开销,探测和展平还可能读到不同版本,与文档“同一 XML 只发一次 GET”不符。

建议:复用首次读取的入口文档;若保留正文缓存,让两个入口一致使用它。

最值得做的简化

范围 建议
OpenLocal / OpenWithSource 本地分支只负责还原 file:// 和创建源,之后共用同一条读取流程。发布物直接解析已读 XDocument,不要再次 LoadFromFile;源树也复用入口文档。
MetadataValidator / ValidateHard 把引用检查变成一份实现,ValidateHard 复用结果,仅保留文件存在性、显示名等构建专属检查。目前复制了登记索引和字段遍历,连 Trim 行为都已分叉。
EquivalenceBaselineTests 保留整树等价断言。删除已被全文比较覆盖的节点数、ID 集合、引用数量重复断言及辅助方法;保留构建期字段的差异检查。
PerformanceTests / ValidationTests 固定 500ms 的磁盘计时不适合作普通单测门槛。引用测试里的跨文件成功、缺失 Manifest 用例已有综合用例覆盖,可以删减。

次要缺口:新校验器漏查 Mod/Links/Link/Icon。已复现:引用不存在的图片 ID,FindMissingReferences 返回错误数仍为 0。旧 ValidateHard 也漏查,不是回归,但提取公共检查时应一起补齐,并且这条引用应检查图片登记。

另有网络层契约冲突:新 HttpXmlSource 直接创建 HttpClient,绕过仓库规定的 IMetadataTransfer。现有传输接口要求异步落盘,不能机械套用并仍宣称“纯内存不写盘”;需要明确处理这项取舍,避免留下两套互相矛盾的规则。

验证

  • dotnet test Metadata.sln:372 通过,0 失败。
  • 实际构建成功,检查发布物:无 Include / Includes。
  • node scripts/verify-docs.mjs:通过。
  • 上述大小写、越界、重复 GET、正文超时及链接图标遗漏均做了定向复现。
  • 未修改原工作区;独立审查副本和临时产物已移入回收站。

@mrbbbaixue
mrbbbaixue force-pushed the feat/unified-metadata-reader branch from 25f42b9 to dc94f47 Compare November 7, 2026 01:41
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://feat-unified-metadata-reader-metadata.bakneko.workers.dev (commit 54cff02)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://742669fe-metadata.bakneko.workers.dev 54cff02 2026-11-13T13:18:29.581Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://543ad2e9-metadata.bakneko.workers.dev a237549 2026-11-12T02:53:02.778Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://e509648e-metadata.bakneko.workers.dev b175dc1 2026-11-09T10:12:53.677Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://a8888bf2-metadata.bakneko.workers.dev ab6928e 2026-11-09T09:37:09.319Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://212c7fc4-metadata.bakneko.workers.dev 584c730 2026-11-09T08:39:18.215Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://c7a56f37-metadata.bakneko.workers.dev 000d880 2026-11-07T12:13:11.686Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://4bf651f4-metadata.bakneko.workers.dev 1535580 2026-11-07T07:44:33.607Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://69d04b43-metadata.bakneko.workers.dev 7f27be1 2026-11-07T05:07:08.424Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://218c2414-metadata.bakneko.workers.dev dcaa1d5 2026-11-07T03:21:27.580Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://deb68d26-metadata.bakneko.workers.dev 4a7faa6 2026-11-07T03:13:10.861Z Visit the dashboard ↗

View all previews: View all previews ↗

@mrbbbaixue mrbbbaixue changed the title ✨ feat(metadata): 统一源树与发布物读取入口 ♻️ refactor(metadata): 统一文档组合与布局导出核心 Nov 7, 2026
@mrbbbaixue mrbbbaixue changed the title ♻️ refactor(metadata): 统一文档组合与布局导出核心 ♻️ refactor(metadata): 统一单一语义并删除兼容模式 Nov 7, 2026
@mrbbbaixue

Copy link
Copy Markdown
Member Author

审查后修复已推送 000d880:删除LocalId手写百分号格式拒绝,完整键scope:icon%raw与scope/icon%raw可原样匹配;删除OfflineSource无效disk参数及不可达远程raw-cache分支,保留本地Source安全边界。完整Metadata测试428/428通过;实际production根/引用与22/22独立清单、本地RisingStorm完整清单、无磁盘缓存Include组合读取和原仓库CLI Build全部通过。没有修改原始数据/XSD。

@mrbbbaixue
mrbbbaixue force-pushed the feat/unified-metadata-reader branch 2 times, most recently from 584c730 to ab6928e Compare November 9, 2026 09:36
@mrbbbaixue mrbbbaixue changed the title ♻️ refactor(metadata): 统一单一语义并删除兼容模式 ✨ feat(metadata): 统一源树与发布物读取入口 Nov 9, 2026
@mrbbbaixue
mrbbbaixue force-pushed the feat/unified-metadata-reader branch 2 times, most recently from b175dc1 to a237549 Compare November 12, 2026 02:51
- 源树与展平发布物共用 MetadataReader / MetadataClient:指定入口 metadata.xml 即可读取,Include 递归展开、菱形去重、路径限制在源根
- SchemaVersion 改由作者写在源根 metadata.xml,构建不再注入版本,读入口缺失直接失败
- 删除构建期修订标记 ContentRevision:构建参数、CLI、发布 XSD 与 CI 传递一并移除
- 修复叶子存根 Source 被当成待内联正文导致的无限请求链(Linux CI 卡死)
- 保持按完整注册键精确匹配,旧资源地址与本地组合缓存兼容;移除冒号与百分号限制
- 删除兼容模式,统一语义与测试结构
- 文档补充单入口读取说明与可视化预览,新增三篇决策笔记,重生成路径清单与笔记索引
@mrbbbaixue
mrbbbaixue force-pushed the feat/unified-metadata-reader branch from a237549 to 54cff02 Compare November 13, 2026 13:17

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant