fix: 优化客户端错误提示和校验诊断信息

This commit is contained in:
2026-07-21 03:02:42 +00:00
parent 1c8ec37d2b
commit d9e32c6cea
14 changed files with 1739 additions and 627 deletions
+76 -40
View File
@@ -3,9 +3,10 @@
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QElapsedTimer>
#include <QFile>
#include <QMessageBox>
#include <QElapsedTimer>
#include <QFile>
#include <QFileInfo>
#include <QMessageBox>
#include <QProcess>
#include <QProgressDialog>
#include <QSaveFile>
@@ -164,14 +165,19 @@ int main(int argc, char* argv[])
if (offlinePackagePath.isEmpty())
logic.reportResult(deviceId, fromVersion, targetVersion, success);
};
const auto fail = [&](const QString& title, const QString& message,
const QString& errorCode = QString("update_failed")) {
transaction.markFailed(errorCode, message);
reportUpdateResult(false);
progress.close();
QMessageBox::critical(nullptr, title, message);
return -1;
};
const auto fail = [&](const QString& title, const QString& message,
const QString& errorCode = QString("update_failed")) {
transaction.markFailed(errorCode, message);
reportUpdateResult(false);
progress.close();
QMessageBox::critical(nullptr, title, message);
return -1;
};
const auto withDetails = [](const QString& message, const QString& details) {
return details.trimmed().isEmpty()
? message
: message + QCoreApplication::translate("Updater", "\n\nDetails:\n%1").arg(details);
};
const auto configuredName = [&](const QString& key, const QString& fallback) {
return ConfigHelper::executableNameForCurrentPlatform(
config.getValue("Runtime", key), fallback);
@@ -182,25 +188,44 @@ int main(int argc, char* argv[])
bool timeoutOk = false;
int healthCheckTimeoutMs = config.getValue("Runtime", "health_check_timeout_ms").toInt(&timeoutOk);
if (!timeoutOk || healthCheckTimeoutMs < 1000) healthCheckTimeoutMs = 15000;
const QString mainAppPath = QDir(runtimeDir).filePath(mainExecutable);
const QString updaterPath = QDir(runtimeDir).filePath(updaterExecutable);
const QString bootstrapPath = QDir(runtimeDir).filePath(bootstrapExecutable);
const QString launchToken = config.getValue("App", "launch_token");
const auto launchMainApp = [&](const QString& healthFile = QString()) {
QString ticketPath;
QString ticketError;
const QString launchVersion = config.getValue("App", "current_version");
if (!TicketHelper::createTicket(appId, deviceId, launchVersion, launchToken,
&ticketPath, &ticketError)) {
qDebug() << "Cannot create launch ticket:" << ticketError;
return false;
}
QStringList args{QString("--ticket-file=%1").arg(ticketPath)};
if (!healthFile.isEmpty()) args.append(QString("--health-file=%1").arg(healthFile));
const bool started = QProcess::startDetached(mainAppPath, args);
if (!started) QFile::remove(ticketPath);
return started;
};
const QString mainAppPath = QDir(runtimeDir).filePath(mainExecutable);
const QString updaterPath = QDir(runtimeDir).filePath(updaterExecutable);
const QString bootstrapPath = QDir(runtimeDir).filePath(bootstrapExecutable);
const QString launchToken = config.getValue("App", "launch_token");
QString mainStartupError;
const auto launchMainApp = [&](const QString& healthFile = QString()) {
mainStartupError.clear();
if (!QFileInfo::exists(mainAppPath)) {
mainStartupError = QCoreApplication::translate(
"Updater",
"Cannot start the main application because the executable file does not exist.\nExecutable: %1\nCheck main_executable and install_root in the generated client configuration.")
.arg(mainAppPath);
return false;
}
QString ticketPath;
QString ticketError;
const QString launchVersion = config.getValue("App", "current_version");
if (!TicketHelper::createTicket(appId, deviceId, launchVersion, launchToken,
&ticketPath, &ticketError)) {
qDebug() << "Cannot create launch ticket:" << ticketError;
mainStartupError = QCoreApplication::translate(
"Updater",
"Cannot start the main application because the one-time launch ticket could not be created.\nExecutable: %1\nDetails: %2")
.arg(mainAppPath, ticketError);
return false;
}
QStringList args{QString("--ticket-file=%1").arg(ticketPath)};
if (!healthFile.isEmpty()) args.append(QString("--health-file=%1").arg(healthFile));
const bool started = QProcess::startDetached(mainAppPath, args);
if (!started) {
QFile::remove(ticketPath);
mainStartupError = QCoreApplication::translate(
"Updater",
"Cannot start the main application process.\nExecutable: %1\nTicket file: %2\nHealth file: %3\nCheck file permissions, dependent DLLs/shared libraries, and whether the executable can run independently.")
.arg(mainAppPath, ticketPath, healthFile.isEmpty() ? QCoreApplication::translate("Updater", "<not used>") : healthFile);
}
return started;
};
const auto bootstrapPlanFile = [&]() {
return QDir(updateDir).filePath("bootstrap_plan_" + transaction.transactionId() + ".txt");
};
@@ -282,16 +307,19 @@ int main(int argc, char* argv[])
if (resumingFromBootstrap) {
if (!logic.loadManifestCache(manifestCacheDir, targetVersion))
return delegateRollback(QCoreApplication::translate("Updater", "Manifest Cache Failed"),
QCoreApplication::translate("Updater", "Cannot read the signed manifest cache after Bootstrap installation."));
withDetails(QCoreApplication::translate("Updater", "Cannot read the signed manifest cache after Bootstrap installation."),
logic.errorString()));
} else if (offlinePackagePath.isEmpty()) {
logic.getManifest(appId, channel, targetVersion, targetVersionId);
}
if (!logic.verifyManifestSignature()) {
if (resumingFromBootstrap)
return delegateRollback(QCoreApplication::translate("Updater", "Security Verification Failed"),
QCoreApplication::translate("Updater", "Cannot reverify the manifest signature after Bootstrap installation."));
withDetails(QCoreApplication::translate("Updater", "Cannot reverify the manifest signature after Bootstrap installation."),
logic.errorString()));
return fail(QCoreApplication::translate("Updater", "Security Verification Failed"),
QCoreApplication::translate("Updater", "The version manifest signature is invalid. The update has stopped. Please contact the administrator."),
withDetails(QCoreApplication::translate("Updater", "The version manifest signature is invalid. The update has stopped. Please contact the administrator."),
logic.errorString()),
"manifest_signature_invalid");
}
QStringList obsoletePaths;
@@ -309,9 +337,11 @@ int main(int argc, char* argv[])
if (!logic.saveManifestCache(manifestCacheDir)) {
if (resumingFromBootstrap)
return delegateRollback(QCoreApplication::translate("Updater", "Manifest Cache Failed"),
QCoreApplication::translate("Updater", "Cannot save the new version manifest cache."));
withDetails(QCoreApplication::translate("Updater", "Cannot save the new version manifest cache."),
logic.errorString()));
return fail(QCoreApplication::translate("Updater", "Manifest Cache Failed"),
QCoreApplication::translate("Updater", "Cannot save the new version manifest cache. The update has stopped."),
withDetails(QCoreApplication::translate("Updater", "Cannot save the new version manifest cache. The update has stopped."),
logic.errorString()),
"manifest_cache_failed");
}
@@ -324,7 +354,8 @@ int main(int argc, char* argv[])
logic.getDownloadUrl(appId, channel, targetVersion, targetVersionId);
if (logic.getFileList().isEmpty())
return fail(QCoreApplication::translate("Updater", "No Files to Update"),
QCoreApplication::translate("Updater", "The server did not return any version files. The update has stopped."),
withDetails(QCoreApplication::translate("Updater", "The server did not return any version files. The update has stopped."),
logic.errorString()),
"empty_file_list");
QStorageInfo storage(updateDir);
@@ -353,7 +384,8 @@ int main(int argc, char* argv[])
if (!logic.downloadAllFiles(stagingDir, targetDir)) {
logic.reportDownloadResult(appId, channel, targetVersion, false);
return fail(QCoreApplication::translate("Updater", "Download Failed"),
QCoreApplication::translate("Updater", "Some files failed to download or failed SHA-256 verification. Please check the network and try again."),
withDetails(QCoreApplication::translate("Updater", "Some files failed to download or failed SHA-256 verification. Please check the network, update cache, or server release files and try again."),
logic.errorString()),
"download_failed");
}
logic.reportDownloadResult(appId, channel, targetVersion, true);
@@ -362,7 +394,8 @@ int main(int argc, char* argv[])
setProgress(58, QCoreApplication::translate("Updater", "Verifying complete version files..."));
if (!logic.validateLocalFiles(stagingDir, targetDir))
return fail(QCoreApplication::translate("Updater", "File Verification Failed"),
QCoreApplication::translate("Updater", "The staged files do not match the version manifest. The update has stopped."),
withDetails(QCoreApplication::translate("Updater", "The downloaded/staged files do not match the target version manifest. The update has stopped before replacing installed files."),
logic.errorString()),
"staging_verify_failed");
QStringList changedPaths;
@@ -441,7 +474,8 @@ int main(int argc, char* argv[])
setProgress(82, QCoreApplication::translate("Updater", "Verifying Bootstrap installation result..."));
if (!transaction.markPostVerify() || !logic.validateLocalFiles(targetDir))
return delegateRollback(QCoreApplication::translate("Updater", "Installation Verification Failed"),
QCoreApplication::translate("Updater", "New version files failed verification after installation."));
withDetails(QCoreApplication::translate("Updater", "New version files failed verification after installation. The updater will roll back to the previous version."),
logic.errorString()));
for (const QString& path : transaction.obsoletePaths()) {
if (QFile::exists(QDir(targetDir).filePath(path)))
return delegateRollback(QCoreApplication::translate("Updater", "Obsolete File Cleanup Failed"),
@@ -459,7 +493,9 @@ int main(int argc, char* argv[])
setProgress(94, QCoreApplication::translate("Updater", "Starting the new version and waiting for health confirmation..."));
if (!launchMainApp(healthFile))
return delegateRollback(QCoreApplication::translate("Updater", "Startup Failed"),
QCoreApplication::translate("Updater", "%1 cannot be started.").arg(mainExecutable));
mainStartupError.isEmpty()
? QCoreApplication::translate("Updater", "%1 cannot be started.").arg(mainExecutable)
: mainStartupError);
QElapsedTimer healthTimer;
healthTimer.start();