使用 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(&current);
        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;
    }
}

posted @ 2025-10-11 14:50  乌合之众  阅读(19)  评论(0)    收藏  举报
clear