使用 C++ 和 minizip 实现 ZIP 压缩解压工具
在软件开发中,文件压缩和解压是常见的需求。今天我们来介绍一个基于 minizip 库的 C++ ZIP 工具类 - ZipUtility
,它可以轻松地处理 ZIP 文件的创建和解压。
这篇文章使用AI辅助编写。
核心功能
1. 压缩功能
ZipUtility::zipArchive
方法可以将多个文件打包成一个 ZIP 压缩包:
std::vector<std::string> files = {"document.txt", "image.jpg"};
ZipUtility::zipArchive(files, "archive.zip");
支持的特性:
- 多种压缩方法:存储(不压缩)、DEFLATE、BZIP2
- 压缩级别控制:从最快速度到最佳压缩比
- 密码保护:使用密码加密压缩文件
- ZIP64 支持:处理大文件(超过 4GB)
- 全局注释:为 ZIP 文件添加描述信息
2. 解压功能
ZipUtility::unzipArchive
方法可以解压 ZIP 文件:
ZipUtility::unzipArchive("archive.zip", "output_folder");
支持的特性:
- 目录结构恢复:可选择是否保留原始目录结构
- 密码解压:支持加密 ZIP 文件的解压
- 自动目录创建:递归创建所需的目录结构
设计亮点
跨平台兼容
代码通过条件编译实现了 Windows 和 Unix-like 系统的兼容:
#ifdef _WIN32
#define SEPARATOR '\\'
#else
#define SEPARATOR '/'
#endif
完善的错误处理
定义了详细的错误码枚举,涵盖各种可能的问题场景:
enum class ZipResult
{
OK, // 操作成功
ERR_OPEN_ZIP, // 打开ZIP文件失败
ERR_CREATE_ZIP, // 创建ZIP文件失败
ERR_OPEN_FILE, // 打开源文件失败
// ... 更多错误码
};
递归目录创建
createDirectory
方法能够递归创建多级目录,确保解压路径的正确性。
使用示例
基本压缩
std::vector<std::string> files = {"file1.txt", "file2.jpg"};
auto result = ZipUtility::zipArchive(
files,
"backup.zip",
ZipUtility::CompressionMethod::DEFLATE,
ZipUtility::CompressionLevel::BEST_COMPRESSION
);
加密压缩
auto result = ZipUtility::zipArchive(
files, "secure.zip",
ZipUtility::CompressionMethod::DEFLATE,
ZipUtility::CompressionLevel::DEFAULT,
"mypassword" // 设置密码
);
解压文件
// 保留目录结构
auto result = ZipUtility::unzipArchive("archive.zip", "extracted", nullptr, true);
// 仅提取文件(不保留目录结构)
auto result = ZipUtility::unzipArchive("archive.zip", "extracted", nullptr, false);
总结
这个 ZipUtility
类提供了一个简洁而强大的接口来处理 ZIP 文件操作。它的设计考虑了实际使用中的各种需求,包括跨平台兼容性、错误处理和功能灵活性。无论是用于文件备份、数据分发还是应用程序的资源管理,都是一个很好的工具选择。
代码结构清晰,错误处理完善,非常适合集成到现有的 C++ 项目中。如果你正在寻找一个轻量级的 ZIP 处理解决方案,这个工具类值得一试!
完整代码
ZipUtility.h
#include <string>
#include <vector>
class ZipUtility
{
public:
// 操作结果枚举
enum class ZipResult
{
OK, // 操作成功
ERR_OPEN_ZIP, // 打开ZIP文件失败
ERR_CREATE_ZIP, // 创建ZIP文件失败
ERR_OPEN_FILE, // 打开源文件失败
ERR_READ_FILE, // 读取文件失败
ERR_WRITE_FILE, // 写入文件失败
ERR_CLOSE_ZIP, // 关闭ZIP文件失败
ERR_CREATE_DIR, // 创建目录失败
ERR_INVALID_ZIP, // 无效的ZIP文件
ERR_INVALID_PASSWORD, // 密码错误
ERR_CRC, // CRC校验失败
ERR_UNSUPPORTED, // 不支持的格式
ERR_UNKNOWN // 未知错误
};
// 压缩方法枚举
enum class CompressionMethod
{
STORE = 0, // 不压缩
DEFLATE = 8, // Z_DEFLATED DEFLATE压缩
BZIP2 = 12 // Z_BZIP2ED BZIP2压缩(如果编译支持)
};
// 压缩级别枚举
enum class CompressionLevel
{
DEFAULT = -1, // Z_DEFAULT_COMPRESSION,
BEST_SPEED = 1, // Z_BEST_SPEED,
BEST_COMPRESSION = 9, // Z_BEST_COMPRESSION,
NO_COMPRESSION = 0 // Z_NO_COMPRESSION
};
/**
* 创建 ZIP 压缩文件
*
* @param filelist 需要压缩的文件路径列表
* @param zipPath 输出的 ZIP 文件路径
* @param method 压缩方法 (默认为DEFLATE)
* @param level 压缩级别 (默认为DEFAULT)
* @param password 加密密码 (默认为空)
* @param zip64 是否启用ZIP64格式(大文件支持) (默认为true)
* @param globalComment ZIP文件的全局注释 (默认为空)
* @return 操作状态
*/
static ZipResult zipArchive(const std::vector<std::string>& filelist,
const std::string& zipPath,
CompressionMethod method = CompressionMethod::DEFLATE,
CompressionLevel level = CompressionLevel::DEFAULT,
const char* password = nullptr, bool zip64 = true,
const char* globalComment = nullptr);
/**
* 解压 ZIP 文件
*
* @param zipPath 输入的 ZIP 文件路径
* @param outDirectory 解压输出目录
* @param password 解压密码 (默认为空)
* @param restorePath 是否保留目录结构 (默认为true)
* @return 操作状态
*/
static ZipResult unzipArchive(const std::string& zipPath, const std::string& outDirectory,
const char* password = nullptr, bool restorePath = true);
private:
// 创建目录(递归)
static ZipResult createDirectory(const std::string& path);
// 转换ZIP操作错误码
static ZipResult convertZipError(int err);
// 转换UNZIP操作错误码
static ZipResult convertUnzipError(int err);
};
ZipUtility.cpp
#include "ZipUtility.h"
#include "minizip/ioapi.h"
#include "minizip/unzip.h"
#include "minizip/zip.h"
#include <algorithm>
#include <cstring>
#include <ctime>
#include <string>
#include <sys/stat.h>
#include <vector>
#ifdef _WIN32
# include <direct.h>
# include <windows.h>
# define mkdir(path, mode) _mkdir(path)
# define SEPARATOR '\\'
#else
# include <dirent.h>
# include <unistd.h>
# define SEPARATOR '/'
#endif
ZipUtility::ZipResult ZipUtility::zipArchive(const std::vector<std::string>& filelist,
const std::string& zipPath, CompressionMethod method,
CompressionLevel level, const char* password,
bool zip64, const char* globalComment)
{
// 打开 ZIP 文件
zipFile zf = zipOpen(zipPath.c_str(), APPEND_STATUS_CREATE);
if (!zf) {
return ZipResult::ERR_CREATE_ZIP;
}
int err = ZIP_OK;
char buf[8192];
for (const auto& file : filelist) {
// 打开本地文件
FILE* fin = fopen(file.c_str(), "rb");
if (!fin) {
zipClose(zf, globalComment);
return ZipResult::ERR_OPEN_FILE;
}
// 获取文件信息
zip_fileinfo zi;
memset(&zi, 0, sizeof(zi));
time_t current = time(nullptr);
struct tm* tm = localtime(¤t);
zi.tmz_date.tm_sec = tm->tm_sec;
zi.tmz_date.tm_min = tm->tm_min;
zi.tmz_date.tm_hour = tm->tm_hour;
zi.tmz_date.tm_mday = tm->tm_mday;
zi.tmz_date.tm_mon = tm->tm_mon;
zi.tmz_date.tm_year = tm->tm_year + 1900;
zi.dosDate = static_cast<uLong>(current);
// 提取文件名(不含路径)
std::string filename = file;
size_t pos = file.find_last_of("\\/");
if (pos != std::string::npos) {
filename = file.substr(pos + 1);
}
// 在ZIP中打开新文件
int zip64flag = zip64 ? 1 : 0;
err = zipOpenNewFileInZip3_64(zf,
filename.c_str(),
&zi,
nullptr,
0, // extrafield_local
nullptr,
0, // extrafield_global
nullptr, // comment
static_cast<int>(method),
static_cast<int>(level),
0, // raw
-MAX_WBITS, // windowBits
DEF_MEM_LEVEL, // memLevel
Z_DEFAULT_STRATEGY,
password,
0, // crcForCrypting
zip64flag);
if (err != ZIP_OK) {
fclose(fin);
zipClose(zf, globalComment);
return convertZipError(err);
}
// 读取并写入文件内容
size_t read = 0;
while ((read = fread(buf, 1, sizeof(buf), fin)) > 0) {
err = zipWriteInFileInZip(zf, buf, static_cast<unsigned>(read));
if (err != ZIP_OK) {
fclose(fin);
zipCloseFileInZip(zf);
zipClose(zf, globalComment);
return convertZipError(err);
}
}
// 关闭ZIP中的文件
err = zipCloseFileInZip(zf);
if (err != ZIP_OK) {
fclose(fin);
zipClose(zf, globalComment);
return convertZipError(err);
}
fclose(fin);
}
// 关闭ZIP文件
err = zipClose(zf, globalComment);
return convertZipError(err);
}
ZipUtility::ZipResult ZipUtility::unzipArchive(const std::string& zipPath,
const std::string& outDirectory,
const char* password, bool restorePath)
{
// 打开 ZIP 文件
unzFile uf = unzOpen(zipPath.c_str());
if (!uf) return ZipResult::ERR_OPEN_ZIP;
// 创建输出目录
createDirectory(outDirectory);
// 获取ZIP文件信息
unz_global_info64 gi;
if (unzGetGlobalInfo64(uf, &gi) != UNZ_OK) {
unzClose(uf);
return ZipResult::ERR_INVALID_ZIP;
}
int err = UNZ_OK;
char filename_inzip[256];
char buf[8192];
// 遍历ZIP内所有文件
for (uLong i = 0; i < gi.number_entry; i++) {
unz_file_info64 file_info;
err = unzGetCurrentFileInfo64(
uf, &file_info, filename_inzip, sizeof(filename_inzip), nullptr, 0, nullptr, 0);
if (err != UNZ_OK) {
unzClose(uf);
return convertUnzipError(err);
}
// 处理文件路径
std::string fullPath = outDirectory;
if (restorePath) {
fullPath += SEPARATOR + std::string(filename_inzip);
}
else {
// 仅提取文件名
std::string fileName(filename_inzip);
size_t pos = fileName.find_last_of("\\/");
if (pos != std::string::npos) {
fileName = fileName.substr(pos + 1);
}
fullPath += SEPARATOR + fileName;
}
// 创建目录
size_t pos = fullPath.find_last_of("\\/");
if (pos != std::string::npos) {
ZipResult dirResult = createDirectory(fullPath.substr(0, pos));
if (dirResult != ZipResult::OK) {
unzClose(uf);
return dirResult;
}
}
// 打开ZIP中的文件
if (password) {
err = unzOpenCurrentFilePassword(uf, password);
}
else {
err = unzOpenCurrentFile(uf);
}
if (err != UNZ_OK) {
unzClose(uf);
return convertUnzipError(err);
}
// 创建输出文件
FILE* fout = fopen(fullPath.c_str(), "wb");
if (!fout) {
unzCloseCurrentFile(uf);
unzClose(uf);
return ZipResult::ERR_WRITE_FILE;
}
// 解压文件内容
do {
err = unzReadCurrentFile(uf, buf, sizeof(buf));
if (err < 0) break;
if (err > 0) {
if (fwrite(buf, 1, err, fout) != static_cast<size_t>(err)) {
err = UNZ_ERRNO;
break;
}
}
} while (err > 0);
fclose(fout);
int closeErr = unzCloseCurrentFile(uf);
// 处理CRC错误
if (err == UNZ_CRCERROR || closeErr == UNZ_CRCERROR) {
remove(fullPath.c_str());
unzClose(uf);
return ZipResult::ERR_CRC;
}
if (err != UNZ_OK && err != UNZ_EOF) {
unzClose(uf);
return convertUnzipError(err);
}
// 移动到下一个文件
if (i < gi.number_entry - 1) {
err = unzGoToNextFile(uf);
if (err != UNZ_OK) {
unzClose(uf);
return convertUnzipError(err);
}
}
}
unzClose(uf);
return ZipResult::OK;
}
ZipUtility::ZipResult ZipUtility::createDirectory(const std::string& path)
{
if (path.empty()) return ZipResult::OK;
#ifdef _WIN32
if (CreateDirectoryA(path.c_str(), NULL) || GetLastError() == ERROR_ALREADY_EXISTS) {
return ZipResult::OK;
}
#else
struct stat st;
if (stat(path.c_str(), &st) == 0) {
if (S_ISDIR(st.st_mode)) return ZipResult::OK;
return ZipResult::ERR_CREATE_DIR;
}
size_t pos = 0;
do {
pos = path.find_first_of("\\/", pos + 1);
std::string subdir = path.substr(0, pos);
if (mkdir(subdir.c_str(), 0755) != 0 && errno != EEXIST) {
return ZipResult::ERR_CREATE_DIR;
}
} while (pos != std::string::npos);
#endif
return ZipResult::OK;
}
ZipUtility::ZipResult ZipUtility::convertZipError(int err)
{
switch (err) {
case ZIP_OK: return ZipResult::OK;
case ZIP_ERRNO: return ZipResult::ERR_OPEN_FILE;
case ZIP_PARAMERROR: return ZipResult::ERR_INVALID_ZIP;
case ZIP_BADZIPFILE: return ZipResult::ERR_INVALID_ZIP;
case ZIP_INTERNALERROR: return ZipResult::ERR_WRITE_FILE;
default: return ZipResult::ERR_UNKNOWN;
}
}
ZipUtility::ZipResult ZipUtility::convertUnzipError(int err)
{
switch (err) {
case UNZ_OK: return ZipResult::OK;
case UNZ_ERRNO: return ZipResult::ERR_READ_FILE;
case UNZ_PARAMERROR: return ZipResult::ERR_INVALID_ZIP;
case UNZ_BADZIPFILE: return ZipResult::ERR_INVALID_ZIP;
case UNZ_INTERNALERROR: return ZipResult::ERR_UNKNOWN;
case UNZ_CRCERROR: return ZipResult::ERR_CRC;
// case UNZ_BADPASSWORD: return ZipResult::ERR_INVALID_PASSWORD;
default: return ZipResult::ERR_UNKNOWN;
}
}