前言
最近在做项目时,客户提出了一个新需求"审计日志导出",即需要给程序加上导出记录的操作日志的功能,导出为一个本地excel文件。
此时想到以前学习过一个第三方开源库QXlsx,可以用来导出excel文件的操作,遂把QXlsx库的源码直接添加到项目中,然后处理可以正常导出excel文件;
因为我是在Windows环境进行开发的,但实际程序运行环境是在ARM架构统信UOS系统中(以下简称:目标系统);
当将开发好的代码移植到目标系统编译时,报错了,报了一个QT私有模块gui-private的错误,还有一个包含 <private/qzipreader_p.h> 的错误;
为什么呢?
经过排查,得出结果:QXlsx源码使用到了QT的私有模块,然而我在目标系统中安装的QT是使用在线命令进行安装的(QT没有提供ARM架构的安装包;要么使用命令安装QT,要么下载源码进行编译安装),安装后是没有包含到QT的源码模块,然后QT的私有模块却是在源码中的,所以程序在编译时没有找到该模块就报错了。
既然知道了原因,那该如何解决呢?
QXlsx源码使用到了QT的私有模块中ZipReader、QZipWriter模块,即压缩和解压缩模块,因为excel(.xlsx文件)本质上也是一个zip压缩包;就可以使用网络上开源的压缩模块去替换QXlsx源码中的ZipReader、QZipWriter模块,这样就可以解决问题了。
这时,想到以前也学习过在QT中引入Quazip的源码去使用,刚好可以将以前学习过的知识都联动起来了。
QT 引入Quazip和Zlib源码工程到项目中,无需编译成库,跨平台,加密压缩,带有压缩进度
1.搭建测试项目
新建QT项目,并且在项目main.cpp路径,将QXlsx源码和Quazip源码复制到此;
(源码请在上面给出的两个博客链接中去下载,都是开源出来了的,免费下载的)
然后在项目的.pro文件中包含进来:
cpp
# 包含QXlsx源码到项目中
include($$PWD/3rdparty/qtxlsx/xlsx/qtxlsx.pri)
# 包含Quazip源码到项目中(Quazip源码中包含了zlib源码)
DEFINES += QUAZIP_STATIC
include($$PWD/3rdparty/quazip/3rdparty/zlib.pri)
include($$PWD/3rdparty/quazip/quazip.pri)
include($$PWD/3rdparty/quazip/zipop/zipop.pri)

最后在main函数中输入如下代码进行测试:
main.cpp(官方案例)
cpp
#include "widget.h"
#include <QApplication>
#include <QtCore>
#include "xlsxdocument.h"
#include "xlsxchartsheet.h"
#include "xlsxcellrange.h"
#include "xlsxchart.h"
#include "xlsxrichstring.h"
#include "xlsxworkbook.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
{
QXlsx::Document xlsx;
xlsx.setColumnWidth(1, 20); // 设置第一列列宽
QXlsx::Format format1;
format1.setFontColor(QColor(Qt::red));
format1.setFontSize(15);
format1.setHorizontalAlignment(QXlsx::Format::AlignHCenter);
format1.setBorderStyle(QXlsx::Format::BorderDashDotDot);
xlsx.write("A1", "Hello Qt!", format1);
xlsx.write("B3", 12345, format1);
QXlsx::Format format2;
format2.setFontBold(true);
format2.setFontUnderline(QXlsx::Format::FontUnderlineDouble);
format2.setFillPattern(QXlsx::Format::PatternLightUp);
xlsx.write("C5", "=44+33", format2);
xlsx.write("D7", true, format2);
QXlsx::Format format3;
format3.setFontBold(true);
format3.setFontColor(QColor(Qt::blue));
format3.setFontSize(20);
xlsx.write(11, 1, "Hello Row Style");
xlsx.write(11, 6, "Blue Color");
xlsx.setRowFormat(11, 41, format3);
QXlsx::Format format4;
format4.setFontBold(true);
format4.setFontColor(QColor(Qt::magenta));
for (int row=21; row<=40; row++)
for (int col=9; col<16; col++)
xlsx.write(row, col, row+col);
xlsx.setColumnFormat(9, 16, format4);
xlsx.write("A5", QDate(2013, 8, 29));
QXlsx::Format format6;
format6.setPatternBackgroundColor(QColor(Qt::green));
xlsx.write("A6", "Background color: green", format6);
xlsx.saveAs("book1.xlsx");
}
Widget w;
w.show();
return a.exec();
}
编译运行后:

2.QXlsx引入Quazip
上面的案例中,QXlsx使用的还是QT私有模块去处理压缩和解压缩操作,涉及到如下四个文件:
cpp
xlsxzipreader_p.h
xlsxzipreader.cpp
xlsxzipwriter_p
xlsxzipwriter.cpp
打开xlsxzipreader.cpp和xlsxzipwriter.cpp文件查看:

发现就使用到了#include <private/qzipreader_p.h> 和 #include <private/qzipwriter_p.h>
所以就需要修改这四个文件,去除QT的私有模块,改用Quazip;
以下直接给出代码,直接复制粘贴替换原有文件内容;
2.1 xlsxzipreader_p.h
cpp
#ifndef QXLSX_XLSXZIPREADER_P_H
#define QXLSX_XLSXZIPREADER_P_H
#include "xlsxglobal.h"
#include <QScopedPointer>
#include <QStringList>
class QuaZip; // 改用 QuaZip 替换 QZipReader
class QIODevice;
namespace QXlsx {
class XLSX_AUTOTEST_EXPORT ZipReader
{
public:
explicit ZipReader(const QString &fileName);
explicit ZipReader(QIODevice *device);
~ZipReader();
bool exists() const;
QStringList filePaths() const;
QByteArray fileData(const QString &fileName) const;
private:
Q_DISABLE_COPY(ZipReader)
void init();
QScopedPointer<QuaZip> m_reader; // 替换为 QuaZip 指针
QStringList m_filePaths;
};
} // namespace QXlsx
#endif // QXLSX_XLSXZIPREADER_P_H
2.2 xlsxzipreader.cpp
cpp
#include "xlsxzipreader_p.h"
#include <QuaZip.h> // 引入 QuaZip 头文件
#include <QuaZipFile.h> // 引入 QuaZipFile 头文件
#include <QtCore/qvector.h>
#include <QFile>
namespace QXlsx {
// 构造函数:根据文件路径初始化
ZipReader::ZipReader(const QString &filePath)
: m_reader(new QuaZip(filePath))
{
init();
}
// 构造函数:根据 QIODevice 指针初始化
ZipReader::ZipReader(QIODevice *device)
: m_reader(new QuaZip(device))
{
init();
}
// 析构函数:关闭并清理资源
ZipReader::~ZipReader()
{
if (m_reader && m_reader->isOpen())
m_reader->close();
}
// 初始化函数:遍历 ZIP 文件,收集所有有效文件名
void ZipReader::init()
{
if (!m_reader->open(QuaZip::mdUnzip)) {
return;
}
for (bool more = m_reader->goToFirstFile(); more; more = m_reader->goToNextFile()) {
QuaZipFileInfo64 info;
if (m_reader->getCurrentFileInfo(&info)) {
// 只记录文件条目,忽略目录(目录名以 '/' 结尾)
if (!info.name.endsWith('/')) {
m_filePaths.append(info.name);
}
}
}
m_reader->close();
}
// 检查 ZIP 文件是否存在且可读
bool ZipReader::exists() const
{
return QFile::exists(m_reader->getZipName()) || m_reader->isOpen();
}
// 返回 ZIP 包内所有文件路径列表
QStringList ZipReader::filePaths() const
{
return m_filePaths;
}
// 读取 ZIP 包内指定文件的数据
QByteArray ZipReader::fileData(const QString &fileName) const
{
if (!m_reader->open(QuaZip::mdUnzip)) {
return QByteArray();
}
if (!m_reader->setCurrentFile(fileName)) {
m_reader->close();
return QByteArray();
}
QuaZipFile file(m_reader.data());
if (!file.open(QIODevice::ReadOnly)) {
m_reader->close();
return QByteArray();
}
QByteArray data = file.readAll();
file.close();
m_reader->close();
return data;
}
} // namespace QXlsx
2.3 xlsxzipwriter_p.h
cpp
#ifndef QXLSX_ZIPWRITER_H
#define QXLSX_ZIPWRITER_H
#include <QString>
class QIODevice;
class QuaZip; // 前置声明 QuaZip
namespace QXlsx {
class ZipWriter
{
public:
explicit ZipWriter(const QString &filePath);
explicit ZipWriter(QIODevice *device);
~ZipWriter();
void addFile(const QString &filePath, QIODevice *device);
void addFile(const QString &filePath, const QByteArray &data);
bool error() const;
void close();
private:
QuaZip *m_zip; // 改用 QuaZip 指针
bool m_error; // 添加错误标志
};
} // namespace QXlsx
#endif // QXLSX_ZIPWRITER_H
2.4 xlsxzipwriter.cpp
cpp
#include "xlsxzipwriter_p.h"
#include <QDebug>
#include <QuaZip.h>
#include <QuaZipFile.h>
#include <QBuffer>
namespace QXlsx {
ZipWriter::ZipWriter(const QString &filePath)
: m_zip(new QuaZip(filePath)), m_error(false)
{
if (!m_zip->open(QuaZip::mdCreate)) {
m_error = true;
qWarning() << "Failed to create zip file:" << filePath;
}
}
ZipWriter::ZipWriter(QIODevice *device)
: m_zip(new QuaZip(device)), m_error(false)
{
if (!m_zip->open(QuaZip::mdCreate)) {
m_error = true;
qWarning() << "Failed to create zip file from device";
}
}
ZipWriter::~ZipWriter()
{
if (m_zip) {
if (m_zip->isOpen())
m_zip->close();
delete m_zip;
}
}
bool ZipWriter::error() const
{
return m_error || (m_zip && m_zip->getZipError() != UNZ_OK);
}
// 从 QIODevice 添加文件
void ZipWriter::addFile(const QString &filePath, QIODevice *device)
{
if (!device || m_error) return;
// 1. 创建 QuaZipFile 对象,与 Zip 归档关联
QuaZipFile zipFile(m_zip);
// 2. 准备新文件的信息,主要是文件名
QuaZipNewInfo newFileInfo(filePath);
// 3. 以 WriteOnly 模式打开文件,并传入文件信息
if (!zipFile.open(QIODevice::WriteOnly, newFileInfo)) {
m_error = true;
qWarning() << "Failed to open file in zip for writing:" << filePath;
return;
}
// 4. 从传入的 device 读取数据并写入 zip 文件中
QByteArray data = device->readAll();
if (zipFile.write(data) != data.size()) {
m_error = true;
qWarning() << "Failed to write data to zip file:" << filePath;
}
// 5. 关闭文件
zipFile.close();
}
// 从 QByteArray 添加文件
void ZipWriter::addFile(const QString &filePath, const QByteArray &data)
{
if (m_error) return;
// 1. 创建 QuaZipFile 对象
QuaZipFile zipFile(m_zip);
// 2. 准备新文件的信息
QuaZipNewInfo newFileInfo(filePath);
// 3. 打开文件
if (!zipFile.open(QIODevice::WriteOnly, newFileInfo)) {
m_error = true;
qWarning() << "Failed to open file in zip for writing:" << filePath;
return;
}
// 4. 直接写入 QByteArray 数据
if (zipFile.write(data) != data.size()) {
m_error = true;
qWarning() << "Failed to write data to zip file:" << filePath;
}
// 5. 关闭文件
zipFile.close();
}
void ZipWriter::close()
{
if (m_zip && m_zip->isOpen()) {
m_zip->close();
}
}
} // namespace QXlsx
把这四个文件内容依次替换,即可调用Quazip去处理excel,而不是使用QT私有库去处理!
这样,在没有下载安装QT源码的电脑上,QT也可以正常编译运行了!
Quazip已经完全替换了QZipReader 和 QZipWriter。
重新编译运行程序。
至此,问题完美解决!