使用 Qt+cpp+qml 的 http multipart/form-data 完整上传下载示例
·
Qt/C++/QML HTTP Multipart/Form-Data 上传下载示例
下面我将为您提供一个完整的示例,展示如何使用Qt+C++和QML实现HTTP multipart/form-data格式的文件上传和下载功能。
项目结构
HttpDemo/
├── HttpDemo.pro
├── main.cpp
├── FileUploadDownloader.h
├── FileUploadDownloader.cpp
└── qml/
└── main.qml
项目文件
首先创建项目文件:
QT += quick network
CONFIG += c++11
SOURCES += \
main.cpp \
FileUploadDownloader.cpp
HEADERS += \
FileUploadDownloader.h
RESOURCES += qml.qrc
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
# 添加资源文件
qml.files = qml/main.qml
qml.prefix = /
RESOURCES += qml
C++ 头文件
#ifndef FILEUPLOADDOWNLOADER_H
#define FILEUPLOADDOWNLOADER_H
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QFile>
#include <QUrl>
#include <QHttpMultiPart>
class FileUploadDownloader : public QObject
{
Q_OBJECT
Q_PROPERTY(bool uploading READ isUploading NOTIFY uploadingChanged)
Q_PROPERTY(bool downloading READ isDownloading NOTIFY downloadingChanged)
Q_PROPERTY(double uploadProgress READ uploadProgress NOTIFY uploadProgressChanged)
Q_PROPERTY(double downloadProgress READ downloadProgress NOTIFY downloadProgressChanged)
public:
explicit FileUploadDownloader(QObject *parent = nullptr);
~FileUploadDownloader();
Q_INVOKABLE bool uploadFile(const QString &filePath, const QString &url, const QString &fieldName = "file");
Q_INVOKABLE bool downloadFile(const QString &url, const QString &savePath);
Q_INVOKABLE void cancelUpload();
Q_INVOKABLE void cancelDownload();
bool isUploading() const;
bool isDownloading() const;
double uploadProgress() const;
double downloadProgress() const;
signals:
void uploadingChanged();
void downloadingChanged();
void uploadProgressChanged();
void downloadProgressChanged();
void uploadFinished(bool success, const QString &message);
void downloadFinished(bool success, const QString &message, const QString &filePath);
private slots:
void onUploadProgress(qint64 bytesSent, qint64 bytesTotal);
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void onUploadFinished();
void onDownloadFinished();
private:
QNetworkAccessManager *m_networkManager;
QNetworkReply *m_uploadReply;
QNetworkReply *m_downloadReply;
QFile *m_downloadFile;
QHttpMultiPart *m_multiPart;
bool m_uploading;
bool m_downloading;
double m_uploadProgress;
double m_downloadProgress;
QString m_downloadPath;
};
#endif // FILEUPLOADDOWNLOADER_H
C++ 实现文件
#include "FileUploadDownloader.h"
#include <QFileInfo>
#include <QHttpPart>
#include <QMimeDatabase>
FileUploadDownloader::FileUploadDownloader(QObject *parent)
: QObject(parent)
, m_networkManager(new QNetworkAccessManager(this))
, m_uploadReply(nullptr)
, m_downloadReply(nullptr)
, m_downloadFile(nullptr)
, m_multiPart(nullptr)
, m_uploading(false)
, m_downloading(false)
, m_uploadProgress(0.0)
, m_downloadProgress(0.0)
{
}
FileUploadDownloader::~FileUploadDownloader()
{
cancelUpload();
cancelDownload();
}
bool FileUploadDownloader::uploadFile(const QString &filePath, const QString &url, const QString &fieldName)
{
// 检查文件是否存在
QFileInfo fileInfo(filePath);
if (!fileInfo.exists() || !fileInfo.isFile()) {
emit uploadFinished(false, "文件不存在");
return false;
}
// 检查是否已经在上传
if (m_uploading) {
emit uploadFinished(false, "已有上传任务正在进行");
return false;
}
// 打开文件
QFile *file = new QFile(filePath);
if (!file->open(QIODevice::ReadOnly)) {
delete file;
emit uploadFinished(false, "无法打开文件: " + file->errorString());
return false;
}
// 设置HTTP multipart
m_multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
// 添加文件部分
QHttpPart filePart;
// 设置Content-Disposition头
QString contentDisposition = QString("form-data; name=\"%1\"; filename=\"%2\"")
.arg(fieldName)
.arg(fileInfo.fileName());
filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(contentDisposition));
// 设置Content-Type头
QMimeDatabase db;
QMimeType mime = db.mimeTypeForFile(filePath);
filePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(mime.name()));
// 设置文件内容
filePart.setBodyDevice(file);
file->setParent(m_multiPart); // 让multiPart来管理文件的生命周期
m_multiPart->append(filePart);
// 创建请求
QNetworkRequest request(QUrl(url));
// 发送POST请求
m_uploadReply = m_networkManager->post(request, m_multiPart);
m_multiPart->setParent(m_uploadReply); // 让reply来管理multiPart的生命周期
// 连接信号
connect(m_uploadReply, &QNetworkReply::uploadProgress, this, &FileUploadDownloader::onUploadProgress);
connect(m_uploadReply, &QNetworkReply::finished, this, &FileUploadDownloader::onUploadFinished);
// 更新状态
m_uploading = true;
emit uploadingChanged();
return true;
}
bool FileUploadDownloader::downloadFile(const QString &url, const QString &savePath)
{
// 检查是否已经在下载
if (m_downloading) {
emit downloadFinished(false, "已有下载任务正在进行", "");
return false;
}
// 创建文件
m_downloadFile = new QFile(savePath);
if (!m_downloadFile->open(QIODevice::WriteOnly)) {
QString errorMsg = m_downloadFile->errorString();
delete m_downloadFile;
m_downloadFile = nullptr;
emit downloadFinished(false, "无法创建文件: " + errorMsg, "");
return false;
}
// 创建请求
QNetworkRequest request(QUrl(url));
// 发送GET请求
m_downloadReply = m_networkManager->get(request);
// 连接信号
connect(m_downloadReply, &QNetworkReply::downloadProgress, this, &FileUploadDownloader::onDownloadProgress);
connect(m_downloadReply, &QNetworkReply::finished, this, &FileUploadDownloader::onDownloadFinished);
connect(m_downloadReply, &QNetworkReply::readyRead, this, [this]() {
if (m_downloadFile && m_downloadReply) {
m_downloadFile->write(m_downloadReply->readAll());
}
});
// 保存下载路径
m_downloadPath = savePath;
// 更新状态
m_downloading = true;
emit downloadingChanged();
return true;
}
void FileUploadDownloader::cancelUpload()
{
if (m_uploadReply) {
m_uploadReply->abort();
m_uploadReply->deleteLater();
m_uploadReply = nullptr;
}
if (m_multiPart) {
m_multiPart->deleteLater();
m_multiPart = nullptr;
}
if (m_uploading) {
m_uploading = false;
emit uploadingChanged();
emit uploadFinished(false, "上传已取消");
}
m_uploadProgress = 0.0;
emit uploadProgressChanged();
}
void FileUploadDownloader::cancelDownload()
{
if (m_downloadReply) {
m_downloadReply->abort();
m_downloadReply->deleteLater();
m_downloadReply = nullptr;
}
if (m_downloadFile) {
m_downloadFile->close();
m_downloadFile->remove(); // 删除未完成的文件
delete m_downloadFile;
m_downloadFile = nullptr;
}
if (m_downloading) {
m_downloading = false;
emit downloadingChanged();
emit downloadFinished(false, "下载已取消", "");
}
m_downloadProgress = 0.0;
emit downloadProgressChanged();
}
bool FileUploadDownloader::isUploading() const
{
return m_uploading;
}
bool FileUploadDownloader::isDownloading() const
{
return m_downloading;
}
double FileUploadDownloader::uploadProgress() const
{
return m_uploadProgress;
}
double FileUploadDownloader::downloadProgress() const
{
return m_downloadProgress;
}
void FileUploadDownloader::onUploadProgress(qint64 bytesSent, qint64 bytesTotal)
{
if (bytesTotal > 0) {
m_uploadProgress = static_cast<double>(bytesSent) / bytesTotal;
} else {
m_uploadProgress = 0;
}
emit uploadProgressChanged();
}
void FileUploadDownloader::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
if (bytesTotal > 0) {
m_downloadProgress = static_cast<double>(bytesReceived) / bytesTotal;
} else {
m_downloadProgress = 0;
}
emit downloadProgressChanged();
}
void FileUploadDownloader::onUploadFinished()
{
bool success = false;
QString message;
if (m_uploadReply->error() == QNetworkReply::NoError) {
success = true;
message = "上传成功";
} else {
message = "上传失败: " + m_uploadReply->errorString();
}
// 清理资源
m_uploadReply->deleteLater();
m_uploadReply = nullptr;
// 更新状态
m_uploading = false;
emit uploadingChanged();
// 通知结果
emit uploadFinished(success, message);
}
void FileUploadDownloader::onDownloadFinished()
{
bool success = false;
QString message;
QString filePath = "";
if (m_downloadReply->error() == QNetworkReply::NoError) {
if (m_downloadFile) {
// 写入剩余的数据
m_downloadFile->write(m_downloadReply->readAll());
m_downloadFile->flush();
m_downloadFile->close();
success = true;
message = "下载成功";
filePath = m_downloadPath;
} else {
message = "下载失败: 文件句柄无效";
}
} else {
message = "下载失败: " + m_downloadReply->errorString();
}
// 清理资源
if (m_downloadFile) {
delete m_downloadFile;
m_downloadFile = nullptr;
}
m_downloadReply->deleteLater();
m_downloadReply = nullptr;
// 更新状态
m_downloading = false;
emit downloadingChanged();
// 通知结果
emit downloadFinished(success, message, filePath);
}
主函数
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "FileUploadDownloader.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
// 创建上传下载处理器
FileUploadDownloader uploader;
QQmlApplicationEngine engine;
// 将C++对象暴露给QML
engine.rootContext()->setContextProperty("uploader", &uploader);
// 加载QML
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
QML用户界面
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtQuick.Dialogs 1.3
import QtQuick.Layouts 1.12
Window {
visible: true
width: 600
height: 400
title: "HTTP上传下载示例"
FileDialog {
id: uploadFileDialog
title: "选择要上传的文件"
folder: shortcuts.home
nameFilters: ["所有文件 (*.*)"]
onAccepted: {
selectedFileTextField.text = fileUrl.toString().replace("file:///", "")
}
}
FileDialog {
id: saveFileDialog
title: "选择保存位置"
folder: shortcuts.home
selectExisting: false
onAccepted: {
let path = fileUrl.toString().replace("file:///", "")
uploader.downloadFile(downloadUrlTextField.text, path)
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 10
GroupBox {
title: "文件上传"
Layout.fillWidth: true
GridLayout {
anchors.fill: parent
columns: 3
rowSpacing: 10
columnSpacing: 10
Label { text: "服务器URL:" }
TextField {
id: uploadUrlTextField
Layout.fillWidth: true
placeholderText: "例如:http://example.com/upload"
text: "http://httpbin.org/post"
}
Button {
text: "上传"
enabled: !uploader.uploading && selectedFileTextField.text != ""
onClicked: {
uploader.uploadFile(selectedFileTextField.text, uploadUrlTextField.text, "file")
}
}
Label { text: "选择文件:" }
TextField {
id: selectedFileTextField
Layout.fillWidth: true
readOnly: true
placeholderText: "点击右侧按钮选择文件"
}
Button {
text: "浏览..."
onClicked: uploadFileDialog.open()
}
Label { text: "上传进度:" }
ProgressBar {
id: uploadProgressBar
Layout.fillWidth: true
Layout.columnSpan: 2
from: 0
to: 1
value: uploader.uploadProgress
}
Label {
id: uploadStatusLabel
Layout.columnSpan: 3
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
}
}
GroupBox {
title: "文件下载"
Layout.fillWidth: true
GridLayout {
anchors.fill: parent
columns: 3
rowSpacing: 10
columnSpacing: 10
Label { text: "文件URL:" }
TextField {
id: downloadUrlTextField
Layout.fillWidth: true
placeholderText: "例如:http://example.com/file.zip"
text: "https://speed.hetzner.de/100MB.bin"
}
Button {
text: "下载"
enabled: !uploader.downloading && downloadUrlTextField.text != ""
onClicked: saveFileDialog.open()
}
Label { text: "下载进度:" }
ProgressBar {
id: downloadProgressBar
Layout.fillWidth: true
Layout.columnSpan: 2
from: 0
to: 1
value: uploader.downloadProgress
}
Label {
id: downloadStatusLabel
Layout.columnSpan: 3
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
}
}
Item { Layout.fillHeight: true }
RowLayout {
Layout.fillWidth: true
Button {
text: "取消上传"
enabled: uploader.uploading
onClicked: uploader.cancelUpload()
}
Button {
text: "取消下载"
enabled: uploader.downloading
onClicked: uploader.cancelDownload()
}
Item { Layout.fillWidth: true }
Button {
text: "退出程序"
onClicked: Qt.quit()
}
}
}
// 连接信号
Connections {
target: uploader
onUploadFinished: {
uploadStatusLabel.text = message
}
onDownloadFinished: {
downloadStatusLabel.text = message + (success ? ": " + filePath : "")
}
}
}
资源文件
创建qml.qrc文件:
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource prefix="/">
<file>qml/main.qml</file>
</qresource>
</RCC>
使用说明
-
这个示例程序提供了文件上传和下载的功能:
- 上传部分使用multipart/form-data格式发送文件
- 下载部分进行普通的HTTP GET请求
-
上传示例中使用了http://httpbin.org/post作为测试服务器,这是一个测试HTTP请求的服务,会将你上传的数据以JSON格式返回
-
下载示例使用了https://speed.hetzner.de/100MB.bin作为测试文件
-
界面中提供了进度条显示上传/下载进度,以及取消按钮
-
程序会显示上传/下载的状态信息
备注
- 实际使用时,你需要将上传URL替换为你自己的服务器地址
- 如果需要添加额外的表单字段,可以在
uploadFile方法中添加更多的QHttpPart - 错误处理可以根据实际需求进一步完善
- 对于大文件,你可能需要实现断点续传功能
更多推荐



所有评论(0)