前阵子接了个改造任务,一个跑了好多年的JSP老项目要往信创环境迁移。业务方提的需求倒是不复杂:在网页上点一个按钮,把本地文件夹一次性传上去,别让我一个个选文件。听起来就是典型的“文件夹上传”,但真正动手之后我发现,这个功能在信创环境下的坑,比想象中要多不少。如果你也在做类似的Java Web迁移,或者正在JSP项目里处理目录批量上传,这篇文章应该能帮你少走点弯路。
1. 为什么文件夹上传在信创环境里会变成一个专门的课题
1.1 老式JSP开发者的上传习惯,在目录场景下失灵了
早年的JSP项目做上传,最常用的套路是 <input type="file"> 加 commons-fileupload,前端再用隐藏iframe模拟异步提交,后端用 DiskFileItemFactory 解析。单个文件、多个文件都能应付,唯独“文件夹”这种对象不是 file input 的标准能力,压根无法直接选中。
后来HTML5普及,webkitdirectory 属性可以让 input 选择整个目录,但很多老项目的代码库还停留在十年前,没人去动这部分。真正到了信创环境改造时,业务方拿着新需求上门,才发现旧代码连“目录选择”这层基础能力都没有。
还有一个更麻烦的旧方案依赖:早些年有些系统用Flash组件实现多选和文件夹选择,或者用ActiveX插件调本地接口。这些技术在信创环境下基本不可用,浏览器默认禁用、安全策略拦截、操作系统不兼容,现实逼着你只能往标准HTML5能力上靠。
1.2 信创环境不只是换了个操作系统,而是换了整条技术链
很多团队一听到“信创环境”就以为只是把服务器从Windows换成Linux,把浏览器从IE换成国产浏览器,其实远不止这些。文件夹上传这条链路,涉及的是前端浏览器、后端中间件、文件系统三个层面。
传统环境里,开发机用Windows,浏览器用Chrome,中间件用Tomcat,问题排查路径比较熟悉。信创环境通常是这样一套组合:
| 环节 | 传统环境 | 信创环境常见组合 |
|---|---|---|
| 操作系统 | Windows | 麒麟、统信UOS |
| 客户端浏览器 | Chrome、Edge | 奇安信、360安全浏览器、红莲花、系统自带浏览器 |
| 中间件 | Tomcat | 东方通TongWeb、金蝶天燕 |
| 后端技术 | Servlet、Spring MVC | 不变,还是Java |
| 页面渲染 | JSP | 不变,还是JSP |
这中间的差异不是语法层面的,而是“浏览器对标准的支持程度”和“中间件对Servlet规范的完整度”。目录上传依赖的 webkitdirectory 属性,恰好处于浏览器能力边界;后端解析multipart又依赖Servlet 3.0+的 Part 接口,恰好也是老中间件容易出问题的地方。
1.3 所以目录上传在信创环境下的真正难点是什么
一句话总结:前端不能依赖IE时代的插件,后端不能依赖裸写 getParameter 惯性思维,中间件配置也不能照搬Tomcat经验。文件夹上传需要自己把目录遍历、相对路径传递、服务端落盘三层打通,任何一层漏了,功能都是残废的。
我当时给项目定的目标是:浏览器选择目录后,能把整个目录层级原样保存到服务器,支持中文文件名,上传过程有进度反馈,用户能直观看到传了哪些文件。下面的方案就是围绕这套目标展开的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 文件夹上传方案选型:哪些能跑,哪些是坑
2.1 方案一:H5原生目录上传,最贴合场景
现代浏览器支持给 input[type=file] 加 webkitdirectory 属性,用户选择文件夹后,input.files 里不是文件夹对象,而是文件夹下所有文件的平铺列表。每个文件对象带一个 webkitRelativePath 属性,比如选择 项目资料 文件夹,里面有 docs/readme.txt,文件的 webkitRelativePath 就是 项目资料/docs/readme.txt。
这个方案不需要任何插件,后端只需要正常接收multipart文件,再把相对路径存下来。优点是依赖少、可控性强,理论上只要是Chromium内核浏览器就能跑。缺点是空文件夹无法上传,因为浏览器只返回文件对象,不返回目录条目;另外如果目录文件数量极大,前端遍历也会有性能压力。
2.2 方案二:成熟上传组件二次封装,容易栽在版本兼容上
网上有不少上传组件,比如Web Uploader、Plupload,印象里它们都号称支持文件夹上传。实际上这类组件走的是“大而全”路子:Flash、HTML5、Silverlight全都要兼容,信创浏览器默认禁用Flash后,组件会自动降级到HTML5模式,这时它的行为本质上还是 webkitdirectory。
问题出在组件本身:老版本组件年久失修,内置的 webkitdirectory 探测逻辑可能失效;有些组件强依赖jQuery版本,和JSP老项目里的旧jQuery冲突;还有的组件上传时丢掉了 webkitRelativePath,只发文件名,服务器端拿到的就是一堆平铺文件。我见过不止一次“选定文件夹传上去全散成一堆”的情况,最后追到组件源码才发现它压根没传路径。
所以如果你不是有大量历史代码非要复用组件,我建议直接放弃组件,原生写代码反而更干净。
2.3 方案三:压缩包上传、服务端解压,作为兜底
如果浏览器版本太老,连 webkitdirectory 都没有,那就只能退一步:让用户先把本地文件夹打包成ZIP,再用普通文件上传把ZIP传上来,服务端解压后还原目录结构。这个方案绝对兼容所有支持普通文件上传的浏览器。
代价是交互变重,普通用户不一定知道怎么压缩,也不一定愿意用第三方工具。而且大压缩包解压时极易把服务器内存打满,还要应对Zip Slip这类解压路径穿越漏洞。我在扩展部分会专门展开,这里不赘述。
2.4 我的选型结论
我最终选的是“H5原生目录上传 + 后端Servlet自收自存”,不引第三方组件,不上前端压缩。理由有二:
第一,信创环境里浏览器内核其实高度统一。市面上主流的信创浏览器大多基于Chromium内核,版本也普遍较新,对 webkitdirectory 的支持基本没问题,没必要为了少数老浏览器牺牲主路径的稳定性。
第二,JSP老项目最怕引入复杂依赖。第三方组件通常自带样式、自带回调、自带资源文件,部署时多一堆静态资源,出问题反而难排查。原生写法就是 input + FormData + XMLHttpRequest,逻辑全在自己手里,中间件怎么换都能定位。
下面是方案对比,方便你做决策:
| 对比维度 | H5原生目录上传 | 第三方组件 | ZIP上传+服务端解压 |
|---|---|---|---|
| 目录结构保留 | 可以 | 看组件是否传relativePath | 可以 |
| 浏览器要求 | Chromium内核 | 老组件可能失效 | 普通文件上传即可 |
| 服务端改造量 | 低 | 低到中 | 中(需要解压) |
| 前端开发量 | 中 | 低 | 低 |
| 大目录性能 | 中(逐文件或小并发) | 看实现 | 高(但解压耗资源) |
| 踩坑风险 | 低 | 中 | 中 |
| 推荐程度 | 推荐主力 | 不推荐 | 兜底 |
3. 前端核心实现:把文件夹变成可上传的文件列表
3.1 目录选择输入框的正确写法与能力探测
HTML代码很简单:
html复制<input type="
