云端数据服务对接,上传接口写完,等待他们写完接口。

This commit is contained in:
DESKTOP-450PEFP\mainc 2026-07-02 11:25:08 +08:00
parent adc4e3c131
commit 8b487ed28f
5 changed files with 392 additions and 3 deletions

View File

@ -188,3 +188,33 @@ void ApiClient::getBinary(const QString &path, BinarySuccessCB onSuccess, Failed
})
.exec();
}
void ApiClient::postBinary(const QString &path, const QByteArray &body, SuccessCB onSuccess, FailedCB onFailed)
{
postBinary(path, body, QJsonObject(), onSuccess, onFailed);
}
void ApiClient::postBinary(const QString &path, const QByteArray &body, const QJsonObject &headers,
SuccessCB onSuccess, FailedCB onFailed)
{
qDebug() << "ApiClient::postBinary " << path << " bytes=" << body.size();
QString url = joinUrl(m_baseUrl, path);
auto req = m_httpClient->post(url).body(body);
req.header("Content-Type", "application/octet-stream");
if (!m_token.isEmpty()) req.header("Authorization", m_token);
// 添加自定义请求头metadata 信息)
for (auto it = headers.begin(); it != headers.end(); ++it)
{
req.header("X-Metadata-" + it.key(), it.value().toVariant());
}
req.onSuccess([this, onSuccess, onFailed](const QJsonObject &resp) {
this->handleResponse(resp, onSuccess, onFailed);
})
.onFailed([onFailed](const QString &err) {
if (onFailed) onFailed(err);
})
.exec();
}

View File

@ -72,6 +72,13 @@ public:
// 获取二进制原始数据(不走 JSON 解析,直接返回 QByteArray
void getBinary(const QString &path, BinarySuccessCB onSuccess, FailedCB onFailed);
// 上传二进制原始数据body 为 QByteArrayContent-Type: application/octet-stream
void postBinary(const QString &path, const QByteArray &body, SuccessCB onSuccess, FailedCB onFailed);
// 上传二进制原始数据 + 自定义请求头metadata 信息放在 header 里,一次发送)
void postBinary(const QString &path, const QByteArray &body, const QJsonObject &headers,
SuccessCB onSuccess, FailedCB onFailed);
private:
void handleResponse(const QJsonObject &resp, SuccessCB onSuccess, FailedCB onFailed);

View File

@ -156,6 +156,7 @@ void CloudDataDlg::initTreeWells(QJsonObject jObj)
// 井
QTreeWidgetItem *itemJing = new QTreeWidgetItem();
itemJing->setText(0, obj["wellName"].toString());
itemJing->setData(0, Qt::UserRole, "WELL"); // 类型
itemJing->setData(0, Qt::UserRole + 1, obj["wellId"].toString()); // 存储额外数据,项目名
//
QIcon icon;
@ -177,6 +178,7 @@ void CloudDataDlg::initTreeWells(QJsonObject jObj)
QTreeWidgetItem *itemJingCi = new QTreeWidgetItem();
itemJingCi->setText(0, staObj["stageName"].toString());
itemJingCi->setData(0, Qt::UserRole, "STAGE"); // 类型
itemJingCi->setData(0, Qt::UserRole + 1, staObj["stageId"].toString()); // 存储额外数据,项目名
//
QIcon iconci;
@ -245,9 +247,11 @@ bool CloudDataDlg::initTreeData(QTreeWidgetItem* itemJingCi, QJsonObject& jObjCi
itemCurveLog->setText(0, sName);
itemCurveLog->setData(0, Qt::UserRole, sTypeObj); // 存储额外数据如ID
itemCurveLog->setData(0, Qt::UserRole + 1, tmpItem["metadataId"].toString()); // 存储额外数据,井次文件路径
itemCurveLog->setData(0, Qt::UserRole + 2, itemJingCi->data(0, Qt::UserRole + 1)); // 井次id
itemCurveLog->setData(0, Qt::UserRole + 3, itemJingCi->parent()->data(0, Qt::UserRole + 1)); // 井id
//
QIcon iconLog;
iconLog.addPixmap(QPixmap(GetImagePath() + strIcon1), QIcon::Selected);
iconLog.addPixmap(QPixmap(GetImagePath() + strIcon2), QIcon::Selected);
iconLog.addPixmap(QPixmap(GetImagePath() + strIcon2), QIcon::Normal);
itemCurveLog->setIcon(0, iconLog);
itemCurve->addChild(itemCurveLog);
@ -275,6 +279,7 @@ void CloudDataDlg::getList()
void CloudDataDlg::getWells(QString projectId)
{
m_strProjectId = projectId;
QString sapi = "/core/project/tree?projectId=" + projectId;
ApiClient::getInstance()->get(sapi,
[this](const QJsonObject& response)
@ -394,6 +399,282 @@ void CloudDataDlg::on_btn_import_clicked()
);
}
void CloudDataDlg::on_btn_export_clicked()
{
// 1. 获取云端目标位置wellId, stageId
QString strWellId = "";
QString strStageId = "";
foreach(QTreeWidgetItem *pItem, ui->treeWidget->selectedItems())
{
getWellStageId(pItem, strWellId, strStageId);
if (strWellId.length() > 0 && strStageId.length() > 0)
{
break;
}
}
if (strWellId.isEmpty() || strStageId.isEmpty())
{
QMessageBox::warning(this, "提示", "请先在树图中选择导出到的目标井次!");
return;
}
// 2. 获取本地 SLF 文件路径和选中的曲线名
if (!m_projectWidgets)
return;
QString strSlfName = "";
QString strWellName = "";
QString strLeft = m_projectWidgets->getLeftTreeString();
if (strLeft.length() > 0)
{
QStringList list = strLeft.split("#@@#");
if (list.size() > 3)
{
strSlfName = list[0];
strWellName = list[1];
}
}
if (strSlfName.isEmpty())
{
QMessageBox::warning(this, "提示", "请先在左侧树图中选中要导出的井次!");
return;
}
// 3. 从 m_mapShowObject 获取选中的对象名
QStringList curveNames;
if (m_projectWidgets->m_mapShowObject.contains(strSlfName))
{
curveNames = m_projectWidgets->m_mapShowObject[strSlfName];
}
if (curveNames.isEmpty())
{
QMessageBox::warning(this, "提示", "请先在左侧树图中选中要导出的曲线/波列!");
return;
}
qDebug() << "导出到云端目标位置:" << strWellId << strStageId
<< "SLF:" << strSlfName << "曲线:" << curveNames;
// 4. 从 SLF 文件读取数据
QVector<CloudExportItem> items = readLocalData(strSlfName, curveNames);
if (items.isEmpty())
{
QMessageBox::warning(this, "提示", "未从SLF文件中读取到有效数据!");
return;
}
// 5. 禁用按钮,防止重复点击
ui->btn_export->setEnabled(false);
ui->btn_export->setText("导出中...");
// 6. 批量异步上传
exportCloudData(m_strProjectId, strWellId, strStageId, items,
[this](const QVector<CloudExportItem>& results)
{
this->ui->btn_export->setEnabled(true);
this->ui->btn_export->setText("导出");
int successCount = 0;
int failCount = 0;
for (const auto& r : results)
{
if (r.metadataId.isEmpty())
failCount++;
else
successCount++;
}
qDebug() << "===== 云端导出完成 =====";
qDebug() << " 成功:" << successCount << "失败:" << failCount;
QString msg = QString("导出完成!成功 %1 条,失败 %2 条").arg(successCount).arg(failCount);
QMessageBox::information(this, "提示", msg);
}
);
}
// 从本地 SLF 文件读取曲线/波列数据
QVector<CloudExportItem> CloudDataDlg::readLocalData(const QString& strSlfName, const QStringList& curveNames)
{
QVector<CloudExportItem> items;
CMemRdWt logio;
if (!logio.Open(strSlfName.toStdString().c_str(), CMemRdWt::modeRead))
{
qDebug() << "readLocalData: SLF文件打开失败:" << strSlfName;
return items;
}
for (const QString& curveName : curveNames)
{
QByteArray nameBytes = curveName.toLocal8Bit();
// 先尝试作为曲线打开
int nCurveIdx = logio.OpenCurve(nameBytes.data());
if (nCurveIdx >= 0)
{
Slf_CURVE curveInfo;
logio.GetCurveInfo(nCurveIdx, &curveInfo);
DWORD sampleCount = (DWORD)((curveInfo.EndDepth - curveInfo.StartDepth) / curveInfo.DepLevel + 1.5);
float* buffer = new float[sampleCount];
DWORD actual = logio.ReadCurveToFloatBuf(nCurveIdx, curveInfo.StartDepth, sampleCount, buffer);
CloudExportItem item;
item.curveName = curveName;
item.dataType = "curveObject";
item.startDepth = curveInfo.StartDepth;
item.endDepth = curveInfo.EndDepth;
item.depLevel = curveInfo.DepLevel;
item.unit = QString::fromLocal8Bit(curveInfo.Unit);
item.data.resize(actual);
memcpy(item.data.data(), buffer, actual * sizeof(float));
delete[] buffer;
logio.CloseCurve(nCurveIdx);
items.append(item);
qDebug() << "读取曲线:" << curveName << "深度:" << item.startDepth << "-" << item.endDepth
<< "采样数:" << actual;
continue;
}
// 再尝试作为波列打开
int nWaveIdx = logio.OpenWave(nameBytes.data());
if (nWaveIdx >= 0)
{
Slf_WAVE waveInfo;
logio.GetWaveInfo(nWaveIdx, &waveInfo);
DWORD sampleCount = (DWORD)((waveInfo.EndDepth - waveInfo.StartDepth) / waveInfo.DepLevel + 1.5);
// 波列数据量 = 采样点数 × 时间采样数 × 阵列数
DWORD totalFloats = sampleCount * waveInfo.TimeSamples * waveInfo.ArrayNum;
float* buffer = new float[totalFloats];
DWORD actual = logio.ReadWaveToFloatBuf(nWaveIdx, waveInfo.StartDepth, sampleCount, buffer);
CloudExportItem item;
item.curveName = curveName;
item.dataType = "waveObject";
item.startDepth = waveInfo.StartDepth;
item.endDepth = waveInfo.EndDepth;
item.depLevel = waveInfo.DepLevel;
item.unit = QString::fromLocal8Bit(waveInfo.Unit);
item.data.resize(actual);
memcpy(item.data.data(), buffer, actual * sizeof(float));
delete[] buffer;
logio.CloseWave(nWaveIdx);
items.append(item);
qDebug() << "读取波列:" << curveName << "深度:" << item.startDepth << "-" << item.endDepth
<< "采样数:" << actual;
continue;
}
qDebug() << "未找到对象:" << curveName;
}
logio.Close();
return items;
}
// 异步上传单个数据项到云端metadata 放请求头,一次发送)
void CloudDataDlg::uploadCloudData(const QString& proId, const QString& wellId, const QString& stageId,
const CloudExportItem& item,
std::function<void(const CloudExportItem&)> onSuccess,
std::function<void(const QString&)> onFailed)
{
// 构建 metadata 请求头
QJsonObject headers;
headers["ProjectId"] = proId; // 项目id
headers["WellId"] = wellId; // 井id
headers["StageId"] = stageId; // 井次id
headers["DataType"] = item.dataType; // 类型 曲线or波列
headers["CurveName"] = item.curveName; // 名称
headers["StartDepth"] = (double)item.startDepth; // 顶深
headers["EndDepth"] = (double)item.endDepth; // 底深
headers["DepLevel"] = (double)item.depLevel; // 采样间隔
headers["Unit"] = item.unit; // 单位
// 二进制数据
QByteArray binaryData;
binaryData.resize(item.data.size() * sizeof(float));
memcpy(binaryData.data(), item.data.constData(), item.data.size() * sizeof(float));
// 根据数据类型确定上传路径
QString dataPath = (item.dataType == "waveObject")
? "/core/array-data/upload"
: "/core/curve-data/upload";
ApiClient::getInstance()->postBinary(dataPath, binaryData, headers,
[item, onSuccess](const QJsonObject& resp)
{
qDebug() << "上传数据成功:" << item.curveName;
CloudExportItem result = item;
result.metadataId = resp.value("metadataId").toString(resp.value("id").toString());
if (onSuccess) onSuccess(result);
},
[item, onFailed](const QString& error)
{
qDebug() << "上传数据失败:" << item.curveName << error;
if (onFailed) onFailed(error);
}
);
}
// 批量异步导出
void CloudDataDlg::exportCloudData(const QString& proId, const QString& wellId, const QString& stageId,
const QVector<CloudExportItem>& items,
std::function<void(const QVector<CloudExportItem>&)> onAllDone)
{
if (items.isEmpty())
{
if (onAllDone) onAllDone(QVector<CloudExportItem>());
return;
}
int totalCount = items.size();
QVector<CloudExportItem>* results = new QVector<CloudExportItem>();
int* failCount = new int(0);
for (int i = 0; i < items.size(); i++)
{
const CloudExportItem& item = items[i];
uploadCloudData(proId, wellId, stageId, item,
[results, failCount, totalCount, onAllDone](const CloudExportItem& result)
{
results->append(result);
if (results->size() + (*failCount) >= totalCount)
{
QVector<CloudExportItem> finalResults = *results;
delete results;
delete failCount;
if (onAllDone) onAllDone(finalResults);
}
},
[results, failCount, totalCount, onAllDone, item](const QString& error)
{
(*failCount)++;
// 失败的也加入结果metadataId 为空表示失败)
CloudExportItem failed = item;
failed.metadataId = "";
results->append(failed);
qDebug() << "导出失败:" << item.curveName << error;
if (results->size() + (*failCount) >= totalCount)
{
QVector<CloudExportItem> finalResults = *results;
delete results;
delete failCount;
if (onAllDone) onAllDone(finalResults);
}
}
);
}
}
// 异步获取单个 metadataId 的数据
void CloudDataDlg::fetchCloudData(const QString& metadataId, const QString& dataType, const QString& dataName,
std::function<void(const CloudImportResult&)> onSuccess,
@ -562,3 +843,25 @@ void CloudDataDlg::paintEvent(QPaintEvent *pevt)
{
QDialog::paintEvent(pevt);
}
void CloudDataDlg::getWellStageId(QTreeWidgetItem* pItem, QString& strWellId, QString& strStageId)
{
QString strtype = pItem->data(0, Qt::UserRole).toString();
if ("WELL" == strtype)
{
strWellId = pItem->data(0, Qt::UserRole + 1).toString();
}
else if ("STAGE" == strtype)
{
strStageId = pItem->data(0, Qt::UserRole + 1).toString();
}
if (strWellId == "" || strStageId == "")
{
QTreeWidgetItem *parentItem = pItem->parent(); // 上两层目录是井次
if (parentItem)
{
getWellStageId(parentItem, strWellId, strStageId);
}
}
}

View File

@ -18,6 +18,19 @@ struct CloudImportResult
QVector<float> data; // float二进制数组
};
// 云端导出数据项从本地SLF读取的曲线/波列数据)
struct CloudExportItem
{
QString curveName; // 曲线/波列名
QString dataType; // curveObject / waveObject
float startDepth; // 顶深
float endDepth; // 底深
float depLevel; // 采样间隔
QString unit; // 单位
QString metadataId; // 上传后服务端返回的ID
QVector<float> data; // float二进制数组
};
// 云端导入对话框
class CloudDataDlg : public QDialog
{
@ -46,12 +59,30 @@ public:
void importCloudData(const QVector<QString>& vecId, const QMap<QString, QVector<QString>>& mapSelect,
std::function<void(const QVector<CloudImportResult>&)> onAllDone = nullptr);
// ===== 云端数据导出接口 =====
// 从本地 SLF 文件读取曲线/波列数据
QVector<CloudExportItem> readLocalData(const QString& strSlfName, const QStringList& curveNames);
// 异步上传单个数据项到云端先创建metadata再上传二进制数据
void uploadCloudData(const QString& proId, const QString& wellId, const QString& stageId, const CloudExportItem& item,
std::function<void(const CloudExportItem&)> onSuccess,
std::function<void(const QString&)> onFailed);
// 批量异步导出,所有上传完成后回调
void exportCloudData(const QString& proId, const QString& wellId, const QString& stageId,
const QVector<CloudExportItem>& items,
std::function<void(const QVector<CloudExportItem>&)> onAllDone = nullptr);
protected:
virtual void paintEvent(QPaintEvent *pevt);
void getWellStageId(QTreeWidgetItem* pItem, QString& strWellId, QString& strStageId);
private slots:
void on_btn_back_clicked();
void on_btn_import_clicked();
void on_btn_export_clicked();
void onItemDoubleClicked(QTreeWidgetItem* item, int index);//鼠标双击tree菜单项
@ -63,7 +94,9 @@ private:
QtProjectWidgets *m_projectWidgets = NULL;
QString m_strProjectId = "";
QString m_strWellId = "";
QString m_strStageId = "";
};
#endif // CloudDataDlg_H

View File

@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>边框线形设置</string>
<string>云端数据服务</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0">
<item>
@ -163,6 +163,22 @@
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_export">
<property name="minimumSize">
<size>
<width>0</width>
<height>40</height>
</size>
</property>
<property name="text">
<string>数据导入到云端</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_2" native="true"/>
</item>
<item>
<widget class="QPushButton" name="btn_import">
<property name="minimumSize">