简介:面向 Cocos Creator 开发者的 ZIP 文件处理示例工程,围绕引入 JSZip 库、加载二进制数据、解压读取文件、创建并导出压缩包这条主线,完整呈现出在 JavaScript 与原生层之间处理 ZIP 的代码组织方式。资源直接响应资源增量更新、扩展内容下载、存档打包等高频需求,适合需要在游戏内集成压缩包读写能力的初中高级开发者对照学习。压缩包共 25 个文件,体量仅 43KB,其中既有项目配置(fire、json)、JavaScript 业务脚本(js、meta),也有用于绑定原生能力的 C++ 源码(cpp、hpp)和示例数据包(zip),结构清晰,便于快速定位与移植。示例中尤其包含 JSZip 与原生扩展的衔接实现,并指出了路径不匹配、跨平台文件读写等常见坑位的处理要点。目前已有 1144 人学习下载,直接使用这套代码可大幅缩短在 Cocos Creator 中实现 ZIP 功能的时间,也能为后续资源热更和网络下载功能打下基础。 之前做 Cocos Creator 手游项目,线上版本从服务器拉取一个 zip 资源包,里面放的是下一期活动的新 UI 和关卡配置。本来流程挺顺,结果某天突然报“导入资源包失败 caused by: invalid zip archive: could not find eocd”,群里差点炸锅。后来定位到大半天,才发现是运维上传的压缩包下载到一半被网关截断了,文件本身缺了结尾记录,代码怎么重试都没用。像这种 zip 文件处理的问题,在 Cocos Creator 项目里其实特别常见:远程资源更新、热更包、美术交付、打 APK 后的文件读取,每一个环节都可能跟压缩包打交道,而且一碰就出一堆莫名其妙的报错。这篇就把我在 Cocos Creator 里做 zip 文件处理的完整思路、踩过的坑和可用的工程套路整理出来,从选型到解压,从密码包到 EOCD 报错排查,再到打包和 Git 仓库里的周边雷区,一次性说清楚。
1. 在 Cocos Creator 项目里,zip 资源通常卡在哪几道坎
1.1 从“下载 zip”到“加载到场景”的完整链路
很多人以为 zip 文件处理就是“下载下来然后解压”,但在游戏项目里,这条链路远比想象中长。以我当时的项目为例,完整链路是这样的:
- 运维或策划把活动资源压缩成一个 zip 包上传到 CDN 或 OSS
- 客户端启动后,通过 HTTP 请求拉取 zip 文件到本地
- 解压后,把里面的图片、音频、Json 配置等文件读进引擎
- 再用
resources.load或assetManager加载成 SpritFrame、AudioClip,或者直接解析 JSON 数据
任何一个环节出问题,表现都不一样。网络层可能给你一个下载不完整的文件;解压层可能因为密码、编码或分卷而挂掉;引擎加载层又可能因为文件路径带中文或者文件层级不对而报错。所以不要把“zip 文件处理”理解成一句“解压一下完事”,它是一整条链路。
1.2 开发期、预览期、构建期的用法差异很大
同样一个 zip 包,使用场景不同,处理方式也完全不同,我自己就吃过这种哑巴亏。
开发期,美术和策划经常丢过来一个 zip,里面是散落的原图和文本配置。这种包需要的只是本地解压、看到内容,然后自行拷贝到项目目录,我不建议直接扔进 Cocos Creator 资源目录里,否则 Creator 会试图导入压缩包里的所有资源,出问题很难追溯。
预览期,项目在浏览器里调试,zw 包往往是通过跨域请求下载的,本地开发服务器要做 CORS 允许配置,否则fetch或XMLHttpRequest直接失败,根本轮不到解压。
构建期,最典型的就是打 Android APK。构建过程会把resources目录下的资源压缩或加密到包里,你再从外部下载一个 zip 去覆盖或补充资源时,路径选择和权限申请全都变了,代码里写死的绝对路径往往在真机上拿不到文件。
1.3 不要拿系统自带压缩工具当最终交付标准
这是个非常容易忽略的细节。Windows 和 macOS 自带的右键压缩,生成的 zip 元数据和第三方工具不完全一样。最常见的是 macOS 右键压缩会混入__MACOSX目录和.DS_Store文件,解压出来一堆莫名其妙的东西。而且不同压缩工具对中文文件名的编码不一样,有的用 GBK,有的用 UTF-8,Cocos Creator 的 Android 端一旦遇到文件名编码不一致,解压出来的文件名就乱码,资源加载直接失败。
所以,凡是给到项目的 zip 包,我一律建议统一用命令行或同一个压缩工具出品,并且在交付前跑一遍验证。后面我会专门说这个问题。
2. 解压方案选型:为什么我更推荐 JSZip
2.1 浏览器端和原生端都能跑的纯 JS 解压库
Cocos Creator 项目同时要跑在浏览器、iOS、Android 等多个平台,最怕那种“浏览器能跑、原生端歇菜”的方案。早期我用过一个基于原生插件封装的解压能力,iOS 和 Android 要各写一套桥接代码,维护成本很高。后来换成 JSZip,浏览器和原生 JavaScript 环境都能跑,代码统一,问题少一半。
市面上还有几个可选方案,比如fflate、pako、zip.js。pako解决的是 gzip 解压,不是完整 zip 容器解析;zip.js的 API 设计和流式处理做得不错,但生态和资料没有 JSZip 多;fflate性能好,适合大包场景。我用下来的个人感受是:中小型资源包直接用 JSZip,API 直观、文档全、遇到问题搜得到答案,这对团队协作来说比极限性能更重要。
安装很简单,在项目根目录执行npm install jszip,然后在代码里 import 就行。Cocos Creator 3.x 对 npm 包支持还不错,JSZip 本身没有强依赖 DOM API,在原生环境也能跑,这是关键优势。
2.2 稳定的解压模板:包含进度、密码、二进制读取
下面给一套我压箱底的解压模板,适用于 Cocos Creator 3.x 的 TypeScript 项目。先封装一个处理 zip 的模块:
import JSZip from 'jszip'; import { sys } from 'cc'; export class ZipUtils { static async loadZipFromUrl(url: string, password?: string): Promise<JSZip> { const response = await fetch(url); if (!response.ok) { throw new Error(`下载 zip 失败: ${response.status} ${response.statusText}`); } const blob = await response.blob(); const arrayBuffer = await blob.arrayBuffer(); try { const zip = await JSZip.loadAsync(arrayBuffer, { password: password }); return zip; } catch (e) { console.error('zip 解压失败:', e); throw e; } } static async loadTextFromZip(zip: JSZip, filePath: string): Promise<string> { const file = zip.file(filePath); if (!file) { throw new Error(`zip 内找不到文件: ${filePath}`); } return await file.async('text'); } static async getBlobFromZip(zip: JSZip, filePath: string, mimeType: string): Promise<Blob> { const file = zip.file(filePath); if (!file) { throw new Error(`zip 内找不到文件: ${filePath}`); } const blob = await file.async('blob'); return new Blob([blob], { type: mimeType }); } }使用的时候,先下载 zip 拿到 JSZip 实例,再按文件名读取文本或二进制内容:
const zip = await ZipUtils.loadZipFromUrl('https://example.com/res/activity.zip'); const configText = await ZipUtils.loadTextFromZip(zip, 'config/activity.json'); const levelConfig = JSON.parse(configText);这里有个特别要注意的坑:fetch拿到的响应,必须完整转成ArrayBuffer再交给 JSZip。有人图方便直接response.text(),一旦 zip 里包含二进制图片或音频,文本转换会破坏数据,解压出来的文件全部损坏。二进制的东西必须走arrayBuffer,这是硬性规定。
2.3 内存与释放:解压之后老老实实回收 Blob
JSZip 本身就是异步解压,它会把整个压缩包的索引先解析到内存,然后你按需读取每个文件。问题在于,如果你从远端下载一个大 zip,再把它整个交给 JSZip,那内存里会同时驻留一份原始 ArrayBuffer 和一份解压后的文件数据,峰值可能很吓人。
我在项目里就遇到过大 zip 导致低端 Android 设备闪退的情况。后来的解决办法是:第一,下载完成后尽快读取需要的文件,读完后立刻把 ArrayBuffer 引用置空,让浏览器或 V8 的 GC 可以回收;第二,不需要立刻使用的文件不要提前读取,等真正要用时再调file.async();第三,zip 网络下载阶段用流式处理,虽然 JSZip 对超大文件支持一般,但至少不要一次性把几十 MB 全读进内存再解压。
Cocos Creator 在 Web 平台上跑的时候,还要注意引擎自身的资源缓存,解压出来的纹理如果直接交给引擎加载,引擎会再缓存一份。这块内存要想清楚,不然上线后内存报表会很难看。
3. 密码压缩包的合规处理,不做“暴力碰运气”
3.1 用 JSZip 处理带口令的 zip 包,先校验后解压
游戏项目里不是所有 zip 都是公共资源,有些内部工具包、配置表离线包,会给 zip 加密码,防止被无关人员直接解开。这种需求在 Cocos Creator 里同样用 JSZip 就能处理。
JSZip 的loadAsync支持password参数,密文格式是基于传统 ZipCrypto 或 AES 的。用法就是我在上面代码里写的:
const zip = await JSZip.loadAsync(arrayBuffer, { password: 'your_password' });如果密码错误,JSZip 会抛异常。我见过不少开发直接不处理这个异常,导致用户一脸懵。在实际项目里,我更建议加一层错误提示,比如“资源包密码错误,请重新下载”,然后上报日志,方便排查是版本不匹配还是网络劫持。
还有一个更容易翻车的情况:拿到的 zip 是带密码的,但代码里没传,JSZip 一样能读取到文件列表,只是读具体文件内容或者整个包解压时会失败。所以不要只看“能不能打开”,一定要把所有关键文件的读取结果都验证一遍,否则试用阶段一切顺利,上线后个别资源包无法加载。
3.2 密码恢复工具与“免密解压”的真实情况
“zip 密码忘记怎么解压”是搜索量很高的一个问题,我也被策划问过很多次。客观说,zip 的经典 ZipCrypto 加密方式在密码很短或口令简单时,理论上存在被弱口令爆破或已知明文攻击的风险,网上也确实有各种密码恢复工具。但如果你想着还有个工具能“无视密码直接解压”,那就是想多了。现代压缩工具大多已经支持 AES-256,这类加密在当前算力下,想盲目恢复密码几乎是不可能的。
这里要明确一个态度:如果你面对的压缩包不是自己创建的,或者没有取得授权,不要去破解别人的密码。这是基本底线。团队内部如果真的把密码忘了,正确流程是按内部资产管理制度找到密钥备份,或让有权限的同事重新压缩一份。盲目下载来路不明的“免密解压工具”,不仅大概率沒用,还容易把木马和广告插件打包成 zip 塞到你电脑上,这种因为图省事把开发机弄中毒的案例我见过太多了。
3.3 给资源包加密的另一种可行姿势
如果目的是防止玩家或竞品直接解包拿到美术资源和代码配置,不建议只靠 zip 密码。传统 zip 密码对懂技术的人来说保护强度有限,而且一旦密码被提取到客户端安装包里,逆向者用调试器一搜就能找到密钥。
更稳妥的做法是:zip 只承载传输,真正加密用更庄重的算法,客户端拿到密文后再解密,也可以直接把资源映射到 Cocos Creator 的 Asset Bundle 机制,结合引擎资源加密能力来保护敏感内容。这里的顺序是:先保证传输安全,再保证存储安全,最后才是应用层安全。很多人一上来就搞一套复杂的解密逻辑,却忘了 zip 本身是可以被直接拖进工具里列举文件名的,这属于舍本逐末。
4. invalid zip archive: could not find eocd 排查实录
4.1 先搞懂 EOCD 是干什么的,报错才容易解读
刚入门的时候看到could not find eocd这个报错完全懵圈,不知道它到底在说什么。简单说,一个 zip 文件的结构,末尾一定有一个叫 End Of Central Directory Record 的块,里面记录了这个压缩包有多少个文件、中央目录从哪里开始等关键信息。
解压工具拿到 zip 后,第一步不是读文件内容,而是从文件末尾找 EOCD。找不到,就意味着这个文件大概率不是完整的 zip,或者根本不是一个 zip。“导入资源包失败 caused by: invalid zip archive: could not find eocd”在 Cocos Creator 的“导入资源包”功能里特别常见,但很多用户连“为什么找 EOCD”都没搞清楚,自然无从排查。
4.2 项目里的完整定位过程:从网络层到本地文件系统
那次报错,我按照从外到内的顺序排查,逻辑大概是这样的:
第一步,先用浏览器或 curl 直接下载文件,对比本地文件大小和服务器 Content-Length 是否一致。如果本地文件比服务器文件小,说明下载被截断了。这里头的截断不只是网络问题,也可能被网关、防病毒软件、代理缓存拦截过。
第二步,检查本地文件是不是以PK开头。所有标准 zip 文件的开头两个字节都是 “PK”(0x50 0x4B)。如果下载下来的文件开头根本不是这个,多半是服务器返回了一个 HTML 错误页面、登录跳转页面或者空白文件,只是被伪装成 zip 后缀。
第三步,用命令行工具真正验证一下封装完整性。Windows 下可以直接tar -tf xxx.zip,macOS/Linux 下用unzip -t xxx.zip,如果 zip 尾部记录有问题,这些工具会直接报错。Cocos Creator 的导入器只是用了相对严格的解析,底层兼容性问题会暴露出来。
第四步,如果本地验证没问题,再回到编辑器里看报错的具体时间点。如果是在导入刚开始就报 EOCD,那么文件本身就有问题;如果导入到一半才报,可能是 zip 内某个文件块损坏,这时要重点看打印日志里有没有提到具体的源文件或偏移位置。
那次我们最终的原因,是运维平台对超过一定大小的文件做了 CDN 分片传输,分片拼接时出了问题,本该有的末尾记录丢失了。处理方式也很简单,换用对象存储直链,绕过平台的分片逻辑,问题直接消失。
4.3 遇到 z01 分卷和“假 zip”扩展名的情况怎么处理
搜索热词里还有一个很典型的问题:“z01文件没有zip怎么办”。很多非技术同学收到一个分卷压缩包,里面有一堆.z01、.z02,最后才是.zip,于是他们只把主文件拿去导入,结果一头雾水。
分卷压缩包的规则是:第一个分卷有可能是.zip,也有可能是.z01,后面的分卷按.z01、.z02顺序排,最后一个通常是.zip。你必须把全部分卷放在同一个目录里,然后用 7-Zip、Bandizip 等软件合并解压,单独拿任何一个分卷都没有意义。Cocos Creator 本身不支持直接导入分卷 zip,先合并解压再重新压成单文件 zip,是最省事的路径。
至于“假 zip”扩展名,我也遇到过。就是某个文件实际上是一个可执行程序或压缩的 tar 包,但后缀被人为改成 .zip。这种情况拿到 Cocos Creator 里导入,必然报错。怎么看?用十六进制编辑器看文件头部标识,或者直接用file命令识别真实类型:
file unknown.zip输出如果显示Zip archive data,才是真 zip。其他一概不认。
5. APK 打包、Git 仓库与命令行工具的周边坑
5.1 打 APK 后 zip 读取路径要注意 native 差异
在浏览器里调试 zip 加载一切正常,真机上却死活读不到文件,这种问题十有八九出在路径上。
Cocos Creator 打包 Android APK 后,JavaScript 运行环境里的相对路径和本地文件系统路径并不完全等价。网络下载的 zip 文件通常被保存到应用私有目录,比如/data/data/包名/files/下面,如果你在代码里写死一个/sdcard/Download/xxx.zip,在部分国产 ROM 上会因为没有存储权限或目录不存在而抛FileNotFoundException。
这类异常在搜索里也出现过,即“未经处理的异常: system.io.filenotfoundexception”。在 Cocos Creator 项目里,我建议统一通过jsb.fileUtils或sys.localStorage相关的接口来管理下载目录,获取当前平台可写路径后拼接文件名,不要写死任何绝对路径。代码示例:
import { sys, native } from 'cc'; function getDownloadDir(): string { if (sys.isNative) { return native.fileUtils.getWritablePath() + 'download/'; } return ''; }拿到目录后,先创建目录再保存 zip,解压时同样从这个路径读取文件。只要你坚持“运行期可用路径一律由引擎或系统 API 获取”这一条原则,路径类问题能少掉八成。
5.2 .gitignore 不写 zip 的代价,以及 GitHub 下载的 zip 项目关联远程库
另一个很坑的点是版本仓库管理。资源文件动不动就几十 MB,美术同事习惯把最新资源包 zip 直接丢进项目目录,如果.gitignore没有及时把*.zip排除掉,很快仓库就会膨胀到无法忍受。每次克隆项目,开发者的电脑都要下载几百 MB 的无关资源,构建流水线也被拖慢。
Git 本身不是干这个的。资源包应该走对象存储或 CDN,代码仓库里只留一个配置文件记录版本号和下载地址。我有一次迫不及防,忘记排查一个大 zip 进了主干,历史提交里删掉了文件但 git 对象还在,最后只能重写历史,团队全员都要重新拉代码,教训相当深刻。
还有一个关联问题,有人在 GitHub 下载了项目 zip 包,解压到本地后想git push到自己的远程仓库失败,觉得很奇怪。原因在于,GitHub 提供的 zip 下载不包含.git目录,下载下来的只是一份当前快照的源码。正确处理是:
git init git remote add origin 你的远端仓库地址 git add . git commit -m "init from zip" git push -u origin main不能直接认为 git 项目就是可以“到处都能变基到远程仓库”,没有.git目录就没有历史,也谈不上冲突处理,得先把本地仓库初始化出来。
5.3 命令行压缩/解压:打包机上的规范化流程
不管在开发机还是 CI 打包机上,用命令行处理 zip 都比鼠标右键更可靠,原因很简单:命令行是幂等的,结果可复现,还能写进自动化脚本。
Linux/macOS 上我常用的几条命令:
# 解压并保留文件权限 unzip xxx.zip -d output_dir # 只列出内容不真正解压 unzip -l xxx.zip # 验证压缩包完整性 unzip -t xxx.zipWindows 环境下用 PowerShell 也很方便:
Expand-Archive -Path xxx.zip -DestinationPath output_dir Compress-Archive -Path ./res -DestinationPath res.zip在 CI 流水线里,我的习惯是:先用unzip -t验证产物完整性,再放入“待发布”目录,任何一步校验不通过直接终止构建。这样就能在源头拦截掉一部分 EOCD 报错,而不是等客户端跑起来再报警。
最后说几句个人经验
做 Cocos Creator 的 zip 文件处理,大部分人的第一反应是找现成插件,但插件只是工具,真正决定项目稳不稳的是你对这条链路有没有敬畏心。我自己经历了从“能用就行”到“每一步都校验”的转变之后,现在只要涉及压缩包,必做三件事:下载后先验证文件大小和头部标识,解压完成后遍历关键文件做读取测试,所有临时文件用完后及时清理。三件事看起来简单,但真的能规避掉开发中最常见的九成问题。
还有一点小建议,如果你是个人开发或小团队,项目里一定要把“网络下载 zip 失败”的容错做好,给用户明确的错误提示和重试入口,而不是一个红色的报错弹窗。用户不会理解 EOCD 是什么意思,但“下载不完整,请检查网络后重试”这句话,能让你的客服工作轻松很多。踩过的坑写下来,是给项目团队最好的交接文档。
本文还有配套的精品资源,点击获取