Skip to content

修复 Windows 本地驱动挂载并完善可复现的一键打包流程 - #858

Merged
chivehao merged 6 commits into
ikaros-dev:mainfrom
wjz2001:main
Jul 30, 2026
Merged

修复 Windows 本地驱动挂载并完善可复现的一键打包流程#858
chivehao merged 6 commits into
ikaros-dev:mainfrom
wjz2001:main

Conversation

@wjz2001

@wjz2001 wjz2001 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

背景

在 Windows 环境中,LOCAL/DISK 附件驱动无法正常挂载 ikaros.work-dir 之外的本地目录。例如服务端工作目录位于 D: 盘时,配置 E:/收藏 作为远端目录虽然可以成功保存并启用驱动,但附件根目录只显示 CoversDownloads,目标目录内容不会被挂载。

进一步排查还发现,当前发布流程依赖未显式锁定的 Node.js、pnpm、主题子模块和预先生成的控制台资源。仅执行 bootJar 虽然能够生成 JAR,但可能缺少 templates/simple/index.htmlconsole/index.html,最终在启动网页界面时出现 Thymeleaf 模板解析异常。

本 PR 统一处理本地驱动挂载、路径安全、前端工具链复现和完整 JAR 打包四项问题。

改动概览

序号 改动 结果
1 修复本地驱动挂载根目录语义 LOCAL/DISK 驱动可以挂载 ikaros.work-dir 之外的 Windows 本地目录
2 完善本地路径安全与 Windows 路径格式校验 阻止目录穿越、符号链接或 Junction 越界,并明确支持和拒绝的分隔符形式
3 锁定前端构建工具链 Gradle 固定使用 Node.js 22.22.1 和 pnpm 10.32.1,不再依赖运行环境中的动态最新版
4 bootJar 自动生成完整 JAR 单独执行 ./gradlew bootJar 即可自动构建前端并打包主题与控制台资源

详细改动

1. 修复本地驱动挂载根目录语义

  • 普通附件仍以 ikaros.work-dir 为可信根目录,原有访问范围保持不变。
  • LOCAL/DISK 驱动改为使用自身的 remote_path 作为可信根目录,不再错误地受 ikaros.work-dir 限制。
  • 驱动启用时注册真实根目录,驱动禁用时移除对应注册信息。
  • 服务启动后自动恢复数据库中已经启用的本地驱动。
  • 已启用驱动缺少挂载根附件时自动补建,避免驱动显示为启用但附件树中没有挂载入口。
  • 动态静态资源映射与附件扫描统一使用经过校验的真实路径。

2. 完善路径安全与 Windows 路径格式校验

新增统一的本地附件路径校验器,所有本地驱动文件访问均经过以下校验:

  1. 使用 toRealPath() 获取驱动根目录和目标文件的真实路径。
  2. 使用真实路径的 startsWith 判断目标是否仍位于驱动根目录中。
  3. 在目录扫描中对每个子项重新校验真实路径,避免通过符号链接或 Junction 跳出挂载目录。
  4. 视频流读取只在请求开始时校验一次,不在数据流传输过程中重复解析路径。
  5. 动态静态资源解析同样校验真实路径,避免通过资源 URL 访问挂载目录之外的文件。

Windows 盘符路径支持以下形式:

E:/1/2
E://1//2
E:\1\2
E:\/1/2/3

以下形式会被拒绝:

E:\/1\/2

盘符后的根分隔符序列允许混用;后续路径段之间可以使用正斜杠或反斜杠,也可以重复使用同一种分隔符,但同一个后续分隔符序列中不能同时混用 \/

3. 锁定前端构建工具链

  • Gradle Node 插件固定下载并使用 Node.js 22.22.1
  • Gradle 固定下载并使用 pnpm 10.32.1
  • console/package.json 同步声明精确的 packageManagerengines 版本。
  • .npmrc 启用 Node.js 引擎和 pnpm 版本严格检查,版本不匹配时立即失败。
  • .nvmrc 与前端开发文档同步为 Node.js 22.22.1
  • Gradle 前端任务由通过 npx 间接调用 pnpm,改为直接使用 PnpmTask
  • 清理 CI 和 lint-staged 中残留的 npm 调用,统一使用 pnpm。
  • 所有构建前端的 CI 工作流不再执行 npm install -g pnpm,避免随着时间安装到不兼容的新主版本。

锁定 Node.js 22.22.1 的原因是当前锁文件中的 lint-staged 17.0.5 明确要求 Node.js 不低于该版本。此前 Node.js 20 能够完成构建,是因为没有启用严格引擎校验,并不代表该版本满足依赖声明。

4. 让 bootJar 自动生成完整 JAR

  • bootJar 显式依赖 buildFrontend
  • 强制任务顺序为 buildFrontend → processResources → bootJar,确保前端产物生成后才处理服务端资源。
  • JAR 打包前检查以下必需资源:
    • templates/simple/index.html
    • console/index.html
  • 任一资源缺失时构建直接失败,不再生成启动后才暴露问题的不完整 JAR。
  • 发布和容器 CI 删除独立的重复前端构建步骤,由 buildbootJar 自动触发前端构建。
  • 专门用于验证前端的 checkConsole CI 任务保持不变。

现在完整打包只需要:

.\gradlew.bat bootJar

Linux 或 macOS 环境使用:

./gradlew bootJar

主题 templates/simple 仍是 Git 子模块。全新检出源码时需要正常初始化子模块;如果主题资源不存在,bootJar 会给出明确错误并停止,不会修改 Git 工作区或在构建期间隐式执行网络检出。

安全性说明

  • 本 PR 没有放宽普通附件对 ikaros.work-dir 的限制。
  • 只有明确配置并启用的 LOCAL/DISK 驱动可以访问自身 remote_path 下的文件。
  • 每个驱动具有独立的可信根目录,不能借助另一个驱动扩大访问范围。
  • ..、符号链接和 Windows Junction 最终都以真实路径进行边界判断。
  • 动态资源访问和附件业务访问共用相同的真实路径安全原则。
  • 不自动运行 git submodule update,避免一次普通构建隐式修改源码仓库或触发网络访问。

性能影响

运行时

  • 驱动根目录只在启用或恢复时解析并缓存真实路径。
  • 普通目录访问会在开始扫描时校验目录,每个扫描结果在读取元数据前校验一次。
  • 视频流只在请求开始时校验一次,后续数据传输不会重复执行路径解析。
  • 因此不会对视频持续读取速度产生可感知影响。

构建时

  • bootJar 现在会自动执行完整前端构建,首次执行时间会比原先只打包后端更长。
  • 这是生成可用网页界面所必需的工作,原发布流水线本来也需要执行。
  • CI 中已删除重复的独立前端构建步骤,避免在同一发布流程中重复构建控制台。

验证结果

自动化测试

  • LocalAttachmentPathValidatorTest:通过。
  • AttachmentDriverEnableListenerTest:通过。
  • DynamicDirectoryResolverTest:通过。
  • AttachmentServiceImplTest 相关路径用例:通过。
  • Windows 路径分隔符 4 个合法样例和 1 个非法样例:通过。
  • 主代码和测试代码 Checkstyle:执行成功;仅保留项目原有的主代码 4 条、测试代码 17 条警告,本次修改文件没有新增违规。

构建验证

以下任务均已使用 JDK 21 验证成功:

buildFrontend
build -x test
bootJar

单独执行 bootJar 的任务图已确认自动包含前端依赖安装、共享包构建、API 客户端构建、控制台构建、资源处理和 JAR 打包。

JAR 内容验证

  • 默认主题入口 BOOT-INF/classes/templates/simple/index.html:存在。
  • 默认主题资源:66 项,与作者发布 JAR 一致。
  • 控制台入口 BOOT-INF/classes/console/index.html:存在。
  • 控制台资源:38 项,与作者发布 JAR 一致。
  • 本地路径安全校验类:存在。

建议审查重点

  1. LOCAL/DISK 驱动以自身 remote_path 为安全根目录是否符合驱动设计预期。
  2. toRealPath()startsWith 的组合是否覆盖项目支持平台上的符号链接和 Junction 场景。
  3. Windows 连续分隔符规则是否符合接口希望接受的输入范围。
  4. bootJar 自动依赖前端构建是否符合发布和本地开发流程预期。
  5. Node.js 22.22.1 与 pnpm 10.32.1 是否适合作为当前分支的固定构建基线。

额外内容

优化附件删除交互与工具栏布局

  • 系统内置的 Covers、Downloads 目录不再显示右键删除入口。
  • 批量选择包含内置目录时,批量删除按钮会变灰并禁用。
  • 同时调整工具栏按钮顺序,使操作按钮保持单行排列,搜索输入框根据剩余空间自动调整宽度。
  • 相关前端检查及完整 JAR 构建均已通过。

附件驱动仅处理变化文件

  • 本次提交将本地附件驱动的目录刷新改为增量扫描:首次扫描会计算全部文件的 SHA-1 并保存文件修改时间,后续刷新仅对新增或大小、修改时间发生变化的文件重新计算 SHA-1,未变化文件直接跳过,已删除文件同步标记删除。
  • 合并同一目录的并发刷新请求, 避免重复扫描和数据库写入。
  • 应用启动、驱动启用及非递归扫描行为保持不变。

@chivehao

Copy link
Copy Markdown
Member

感谢PR,我下班回去时就看下,目前在上班QAQ。

@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 48.68421% with 78 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.53%. Comparing base (633ca04) to head (68d052e).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...nt/extension/LocalDiskAttachmentDriverFetcher.java 5.08% 56 Missing ⚠️
...ikaros/server/config/DynamicDirectoryResolver.java 68.18% 4 Missing and 3 partials ⚠️
...chment/extension/LocalAttachmentPathValidator.java 87.50% 1 Missing and 5 partials ⚠️
...hment/listener/AttachmentDriverEnableListener.java 71.42% 4 Missing ⚠️
...attachment/service/impl/AttachmentServiceImpl.java 40.00% 1 Missing and 2 partials ⚠️
...ment/listener/AttachmentDriverDisableListener.java 50.00% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main     #858      +/-   ##
============================================
+ Coverage     57.20%   57.53%   +0.33%     
- Complexity     1702     1731      +29     
============================================
  Files           269      270       +1     
  Lines         11241    11313      +72     
  Branches        594      608      +14     
============================================
+ Hits           6430     6509      +79     
+ Misses         4577     4562      -15     
- Partials        234      242       +8     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

chivehao
chivehao previously approved these changes Jul 30, 2026
@chivehao

Copy link
Copy Markdown
Member

还需要commit吗?没有commit的话我合并了?

@wjz2001

wjz2001 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

还需要commit吗?没有commit的话我合并了?

1df6381
这个最新的别合并,其他都测试正常

@chivehao

Copy link
Copy Markdown
Member

你直接本地revert comit,然后push就行。

@chivehao

Copy link
Copy Markdown
Member

那个commit,单开个PR如何?先把OK的功能合并了?

@wjz2001

wjz2001 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

那个commit,单开个PR如何?先把OK的功能合并了?

没问题

@chivehao

Copy link
Copy Markdown
Member

这波CI跑完通过后,这个PR我就合并了。

@chivehao
chivehao merged commit 30303bf into ikaros-dev:main Jul 30, 2026
3 checks passed
@chivehao

Copy link
Copy Markdown
Member

额,突然想起来,CHAGELOG没写,你可以加上这个新功能的更新日志。
还有一点就是个人比较建议 fork 后,checkout -b 新分支提PR, 比如 feat/xxx, docs/xxx, bug/xxx 之类的,这样fork的main分支能随时从ikaros-dev/ikaros 同步最新的合并,不用每次都需要删除仓库重新fork。

@wjz2001

wjz2001 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

额,突然想起来,CHAGELOG没写,你可以加上这个新功能的更新日志。 还有一点就是个人比较建议 fork 后,checkout -b 新分支提PR, 比如 feat/xxx, docs/xxx, bug/xxx 之类的,这样fork的main分支能随时从ikaros-dev/ikaros 同步最新的合并,不用每次都需要删除仓库重新fork。

更新日志就是我上面发的一堆。没搞分支是因为我本来打算弄完就删库的🤣

@chivehao

chivehao commented Jul 30, 2026

Copy link
Copy Markdown
Member

我后面加也行,我主要是怕后面忘记了,这段时间工作有点太忙了。
之所以每次更新都要写CHANGELOG.md,是因为CHANGELOG.md能在发版的时候自动发布在对应的releases的内容里,挺方便的。

@wjz2001

wjz2001 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

我后面加也行,我主要是怕后面忘记了,这段时间工作有点太忙了。 之所以每次更新都要写CHANGELOG.md,是因为CHANGELOG.md能在发版的时候自动发布在对应的releases的内容里,挺方便的。

那一会儿吧,最后一个新功能测试完毕我再写
话说你那边能把我最新的两次提交(一次是最新提交一次撤销该提交)删掉么,这俩没啥用,没必要留

@chivehao

Copy link
Copy Markdown
Member

我后面加也行,我主要是怕后面忘记了,这段时间工作有点太忙了。 之所以每次更新都要写CHANGELOG.md,是因为CHANGELOG.md能在发版的时候自动发布在对应的releases的内容里,挺方便的。

那一会儿吧,最后一个新功能测试完毕我再写 话说你那边能把我最新的两次提交(一次是最新提交一次撤销该提交)删掉么,这俩没啥用,没必要留

主仓库一个PR就是一个commit

image

@wjz2001

wjz2001 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

我后面加也行,我主要是怕后面忘记了,这段时间工作有点太忙了。 之所以每次更新都要写CHANGELOG.md,是因为CHANGELOG.md能在发版的时候自动发布在对应的releases的内容里,挺方便的。

那一会儿吧,最后一个新功能测试完毕我再写 话说你那边能把我最新的两次提交(一次是最新提交一次撤销该提交)删掉么,这俩没啥用,没必要留

主仓库一个PR就是一个commit

image

😅我应该先删了再发

@chivehao

Copy link
Copy Markdown
Member

是指pr里面的commit?这个好像没找到哪压缩,合并后的pr相当于是归档了的。
PR里多几个commit其实也没啥影响。

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.

2 participants