*creator.user*
*_qmlcache.qrc
+
+build_release/
+build_debug/
testConfig["expectedVer"] = "";
testConfig["bluetoothAddress"] = "";
testConfig["bluetoothRSSI"] = "";
+ testConfig["cat1MinSignal"] = "";
defaultConfig["testConfig"] = testConfig;
// 默认测试项checkbox配置(默认全部选中)
--- /dev/null
+@echo off
+setlocal EnableDelayedExpansion
+set "ROOT=%~1"
+set "APP=%~2"
+set "WD=%~3"
+set "EXTRA=%~4"
+set "QTBIN=%~5"
+set "OSSL_TOOLS=%~6"
+set "SSL_ARCH=%~7"
+set "PROJ_LIBS=%~8"
+if "%EXTRA%"=="__NO_EXTRA__" set "EXTRA="
+if "%SSL_ARCH%"=="" set "SSL_ARCH=x64"
+echo [deploy_qt] args: EXE_ROOT=%ROOT% TARGET=%APP% SSL_ARCH=%SSL_ARCH% PROJ_LIBS=%PROJ_LIBS%
+if "%WD%"=="" (
+ echo [deploy_qt] windeployqt path empty, skip.
+ exit /b 0
+)
+
+set "EXE_PATH="
+set "EXE_DIR="
+set "WDFLAG="
+
+if exist "%ROOT%\release\%APP%.exe" (
+ set "EXE_PATH=%ROOT%\release\%APP%.exe"
+ set "EXE_DIR=%ROOT%\release"
+ set "WDFLAG=--release"
+ goto :do_deploy
+)
+if exist "%ROOT%\debug\%APP%.exe" (
+ set "EXE_PATH=%ROOT%\debug\%APP%.exe"
+ set "EXE_DIR=%ROOT%\debug"
+ set "WDFLAG=--debug"
+ goto :do_deploy
+)
+if exist "%ROOT%\%APP%.exe" (
+ set "EXE_PATH=%ROOT%\%APP%.exe"
+ set "EXE_DIR=%ROOT%"
+ set "WDFLAG=--release"
+ echo %ROOT% | findstr /i "build_debug" >nul
+ if not errorlevel 1 set "WDFLAG=--debug"
+ goto :do_deploy
+)
+
+echo [deploy_qt] skip: %APP%.exe not found under "%ROOT%"
+exit /b 0
+
+:do_deploy
+echo [deploy_qt] windeployqt "%EXE_PATH%"
+echo [deploy_qt] OpenSSL 部署位数: %SSL_ARCH% (与当前编译目标 QT_ARCH 一致)
+"%WD%" %EXTRA% %WDFLAG% --no-translations "%EXE_PATH%"
+if errorlevel 1 (
+ echo [deploy_qt] windeployqt returned error
+)
+
+REM OpenSSL:先 Qt bin,再 Tools\OpenSSL(路径已由 qmake 按 i386/x86_64 选 Win_x86 或 Win_x64)
+call :copy_openssl "%QTBIN%" "%EXE_DIR%" "Qt bin" "%SSL_ARCH%"
+if not "%OSSL_TOOLS%"=="" (
+ call :copy_openssl "%OSSL_TOOLS%" "%EXE_DIR%" "Tools\\OpenSSL" "%SSL_ARCH%"
+)
+REM 可选:环境变量 OPENSSL_ROOT 指向 OpenSSL-Win32/Win64 安装根目录(含 bin\libssl*.dll)
+if defined OPENSSL_ROOT if exist "%OPENSSL_ROOT%\bin\." (
+ call :copy_openssl "%OPENSSL_ROOT%\bin" "%EXE_DIR%" "OPENSSL_ROOT" "%SSL_ARCH%"
+)
+REM 工程 libs 下全部文件(含子目录)复制到 exe 输出目录;后执行,可覆盖前述 OpenSSL 同名文件
+if not "%PROJ_LIBS%"=="" if exist "%PROJ_LIBS%\." (
+ call :copy_proj_libs_tree "%PROJ_LIBS%" "%EXE_DIR%"
+)
+exit /b 0
+
+:copy_openssl
+set "SRC=%~1"
+set "DST=%~2"
+set "LABEL=%~3"
+set "SSL_ARCH=%~4"
+if "%SSL_ARCH%"=="" set "SSL_ARCH=x64"
+if "%SRC%"=="" exit /b 0
+if not exist "%SRC%\." exit /b 0
+
+set "FOUND=0"
+for %%F in ("%SRC%\libssl*.dll") do if exist "%%F" set "FOUND=1"
+for %%F in ("%SRC%\libcrypto*.dll") do if exist "%%F" set "FOUND=1"
+if "!FOUND!"=="0" (
+ echo [deploy_qt] OpenSSL: %LABEL% 在 "%SRC%" 无 libssl*.dll 且无 libcrypto*.dll,跳过
+ exit /b 0
+)
+
+echo [deploy_qt] OpenSSL: 从 %LABEL% 复制到 "%DST%" (仅 %SSL_ARCH%)
+for %%F in ("%SRC%\libssl*.dll") do call :copy_ssl_if_arch "%%F" "%DST%" "%SSL_ARCH%"
+for %%F in ("%SRC%\libcrypto*.dll") do call :copy_ssl_if_arch "%%F" "%DST%" "%SSL_ARCH%"
+exit /b 0
+
+REM 将工程 libs 整棵目录复制到输出目录(robocopy 退出码 0~7 视为成功)
+:copy_proj_libs_tree
+set "LSRC=%~1"
+set "LDST=%~2"
+if "%LSRC%"=="" exit /b 0
+if not exist "%LSRC%\." exit /b 0
+echo [deploy_qt] 复制 project\libs 全部内容到 "%LDST%"
+robocopy "%LSRC%" "%LDST%" /E /IS /R:1 /W:1 /NFL /NDL /NJH /NJS /NC /NS /NP
+set "RC=!ERRORLEVEL!"
+if !RC! GEQ 8 (
+ echo [deploy_qt] robocopy libs 警告,退出码 !RC!
+)
+exit /b 0
+
+REM x86:文件名不得含 x64;x64:文件名须含 x64(与官方 Windows OpenSSL 命名一致)
+:copy_ssl_if_arch
+set "F=%~1"
+set "DST=%~2"
+set "A=%~3"
+echo %~nx1 | findstr /i "x64" >nul
+if "%A%"=="x86" (
+ if errorlevel 1 (
+ echo + %~nx1
+ copy /y "%F" "%DST%\" >nul
+ )
+ exit /b 0
+)
+if "%A%"=="x64" (
+ if not errorlevel 1 (
+ echo + %~nx1
+ copy /y "%F" "%DST%\" >nul
+ )
+)
+exit /b 0
--- /dev/null
+#include "dmp_log_uploader.h"
+
+#include <QCoreApplication>
+#include <QDateTime>
+#include <QDebug>
+#include <QDir>
+#include <QFile>
+#include <QFileInfo>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QNetworkRequest>
+#include <QRegularExpression>
+#include <QSslSocket>
+#include <QUrl>
+#include <QtGlobal>
+
+namespace {
+
+const char *const kDmpUploadUrl =
+ "https://dmp-dev-global.raisound.com/dmp-api/log/log-add"
+ "?dmpSecretKey=df4d7e24-31b6-4888-a5a6-ae8b1612c8e5";
+
+void appendLineToLogFile(const QString &absolutePath, const QString &line)
+{
+ if (absolutePath.isEmpty())
+ return;
+ QDir().mkpath(QFileInfo(absolutePath).absolutePath());
+
+ QFile f(absolutePath);
+ if (!f.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
+ qDebug() << "DMP 上传日志写入失败:" << absolutePath << f.errorString();
+ return;
+ }
+ const QString row = QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd hh:mm:ss.zzz"))
+ + QLatin1Char(' ') + line + QLatin1Char('\n');
+ f.write(row.toUtf8());
+}
+
+} // namespace
+
+QStringList DmpLogUploader::csvColumnNames()
+{
+ return QStringList{
+ QStringLiteral("开始时间"),
+ QStringLiteral("结束时间"),
+ QStringLiteral("测试耗时"),
+ QStringLiteral("软件版本"),
+ QStringLiteral("IMEI"),
+ QStringLiteral("CGSN_IMEI"),
+ QStringLiteral("ICCID"),
+ QStringLiteral("Modem版本"),
+ QStringLiteral("PYVER版本"),
+ QStringLiteral("LCD测试"),
+ QStringLiteral("马达测试"),
+ QStringLiteral("按键测试"),
+ QStringLiteral("录音测试"),
+ QStringLiteral("电池测试"),
+ QStringLiteral("G-sensor测试"),
+ QStringLiteral("蓝牙测试"),
+ QStringLiteral("4G信号"),
+ QStringLiteral("Ping测试"),
+ QStringLiteral("4G平均信号强度"),
+ QStringLiteral("录音MIC采样依据"),
+ QStringLiteral("整体结果"),
+ };
+}
+
+QString DmpLogUploader::resolveDevid(const QString &imei, const QString &cgsnImei, const QString &iccid)
+{
+ const QRegularExpression digits15(QStringLiteral("^\\d{15}$"));
+ if (digits15.match(cgsnImei.trimmed()).hasMatch())
+ return cgsnImei.trimmed();
+ if (digits15.match(imei.trimmed()).hasMatch())
+ return imei.trimmed();
+ const QString ic = iccid.trimmed();
+ if (!ic.isEmpty())
+ return ic;
+ if (!cgsnImei.isEmpty())
+ return cgsnImei.trimmed();
+ if (!imei.isEmpty())
+ return imei.trimmed();
+ return QStringLiteral("unknown");
+}
+
+QString DmpLogUploader::logFieldFromOverall(const QString &overallText)
+{
+ const QString t = overallText.trimmed();
+ if (t == QStringLiteral("通过"))
+ return QStringLiteral("success");
+ if (t.contains(QStringLiteral("未通过")) || t.contains(QStringLiteral("失败")))
+ return QStringLiteral("fail");
+ return t;
+}
+
+void DmpLogUploader::appendUploadLog(const QString &line)
+{
+ appendLineToLogFile(m_uploadLogFilePath, line);
+}
+
+DmpLogUploader::DmpLogUploader(QObject *parent)
+ : QObject(parent)
+ , m_nam(new QNetworkAccessManager(this))
+{
+ if (!QSslSocket::supportsSsl()) {
+ qWarning() << "[DMP] supportsSsl==false,HTTPS 可能失败;请将与本程序位数一致的 libssl/libcrypto 置于 exe 同目录"
+ << (QT_POINTER_SIZE == 4 ? QStringLiteral("(32 位为 libssl-1_1.dll 等,非 x64 后缀)")
+ : QStringLiteral("(64 位常见为 *-x64.dll)"));
+ } else {
+ qDebug() << "[DMP] TLS 已就绪,SSL 构建版本:" << QSslSocket::sslLibraryBuildVersionString()
+ << "运行时:" << QSslSocket::sslLibraryVersionString();
+ }
+}
+
+void DmpLogUploader::uploadProductionRecord(const QStringList &fields,
+ const QString &imei,
+ const QString &cgsnImei,
+ const QString &iccid,
+ const QString &logFilePath)
+{
+ m_uploadLogFilePath = logFilePath;
+
+ const QStringList cols = csvColumnNames();
+ if (fields.size() != cols.size()) {
+ const QString msg = QStringLiteral("uploadProductionRecord: 字段数量不匹配 expect=%1 got=%2")
+ .arg(cols.size())
+ .arg(fields.size());
+ qDebug() << msg;
+ appendUploadLog(msg);
+ return;
+ }
+
+ QJsonObject root;
+ root.insert(QStringLiteral("product"), QStringLiteral("ln"));
+
+ const QString devid = resolveDevid(imei, cgsnImei, iccid);
+ root.insert(QStringLiteral("devid"), devid);
+
+ const QString overall = fields.value(cols.size() - 1);
+ root.insert(QStringLiteral("log"), logFieldFromOverall(overall));
+
+ for (int i = 0; i < cols.size(); ++i)
+ root.insert(cols.at(i), fields.at(i));
+
+ const QJsonDocument doc(root);
+ const QByteArray body = doc.toJson(QJsonDocument::Compact);
+
+ const QUrl url(QString::fromUtf8(kDmpUploadUrl));
+ QNetworkRequest req(url);
+ req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
+ req.setRawHeader("Accept", "*/*");
+
+ const QString bodyStr = QString::fromUtf8(body);
+ appendUploadLog(QStringLiteral("--- DMP POST ---"));
+ appendUploadLog(QStringLiteral("Header Content-Type: application/json"));
+ appendUploadLog(QStringLiteral("Header Accept: */*"));
+ appendUploadLog(QStringLiteral("Body(JSON): %1").arg(bodyStr));
+ qDebug() << "[DMP upload] Body:" << bodyStr;
+
+ const QString logPathForReply = m_uploadLogFilePath;
+ QNetworkReply *reply = m_nam->post(req, body);
+ connect(reply, &QNetworkReply::finished, this, [this, reply, logPathForReply]() {
+ const QVariant codeVar = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
+ const int httpCode = codeVar.isValid() ? codeVar.toInt() : -1;
+ const QString err = reply->error() != QNetworkReply::NoError ? reply->errorString() : QString();
+ const QByteArray resp = reply->readAll();
+ const QString respStr = QString::fromUtf8(resp);
+ const QString line = QStringLiteral("Response HTTP=%1 error=%2 body=%3")
+ .arg(httpCode)
+ .arg(err.isEmpty() ? QStringLiteral("(none)") : err)
+ .arg(respStr);
+ appendLineToLogFile(logPathForReply, line);
+ qDebug() << "[DMP upload] Response HTTP:" << httpCode
+ << "error:" << (err.isEmpty() ? QStringLiteral("none") : err)
+ << "body:" << respStr;
+ reply->deleteLater();
+ });
+}
--- /dev/null
+#ifndef DMP_LOG_UPLOADER_H
+#define DMP_LOG_UPLOADER_H
+
+#include <QObject>
+#include <QStringList>
+
+class QNetworkAccessManager;
+
+/** DMP 产测记录 HTTPS POST;上传过程写入产测记录/logs 下与主会话日志同名的 *_dmp_upload.log */
+class DmpLogUploader : public QObject
+{
+ Q_OBJECT
+public:
+ explicit DmpLogUploader(QObject *parent = nullptr);
+
+ /** 与 saveTestRecord 中 CSV 表头列名、顺序一致(21 列) */
+ static QStringList csvColumnNames();
+
+ /**
+ * @param fields 与 csvColumnNames() 顺序一致的一行数据
+ * @param imei / cgsnImei / iccid 用于生成 devid(优先 15 位 IMEI)
+ * @param logFilePath 本次上传对应的本地日志绝对路径(如 .../logs/yyyyMMdd_hhmmss_IMEI_ICCID_dmp_upload.log)
+ */
+ void uploadProductionRecord(const QStringList &fields,
+ const QString &imei,
+ const QString &cgsnImei,
+ const QString &iccid,
+ const QString &logFilePath);
+
+private:
+ void appendUploadLog(const QString &line);
+ static QString resolveDevid(const QString &imei, const QString &cgsnImei, const QString &iccid);
+ static QString logFieldFromOverall(const QString &overallText);
+
+ QNetworkAccessManager *m_nam;
+ /** 当前一次 upload 写文件用;与主会话 log 同目录命名 */
+ QString m_uploadLogFilePath;
+};
+
+#endif // DMP_LOG_UPLOADER_H
-QT += core gui serialport
+QT += core gui serialport network
+# HTTPS 与 HTTP 同属 Qt Network,无需额外 QT 模块。
+# Windows 若 TLS initialization failed:将 libssl/libcrypto(与 Qt 同位数)复制到 exe 同目录,或对 exe 运行 windeployqt;Qt6 可部署 plugins/tls/qschannelbackend.dll 使用系统 Schannel。
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = LeiNiaoFactoryTool
TEMPLATE = app
+# 统一输出根目录 build_debug / build_release,其下仍使用与 Qt 默认一致的 debug、release、moc、ui 命名
+CONFIG(debug, debug|release) {
+ BUILD_SUBDIR = $$_PRO_FILE_PWD_/build_debug
+ DESTDIR = $$BUILD_SUBDIR/debug
+ OBJECTS_DIR = $$BUILD_SUBDIR/debug
+ MOC_DIR = $$BUILD_SUBDIR/debug/moc
+ UI_DIR = $$BUILD_SUBDIR/debug/ui
+} else {
+ BUILD_SUBDIR = $$_PRO_FILE_PWD_/build_release
+ DESTDIR = $$BUILD_SUBDIR/release
+ OBJECTS_DIR = $$BUILD_SUBDIR/release
+ MOC_DIR = $$BUILD_SUBDIR/release/moc
+ UI_DIR = $$BUILD_SUBDIR/release/ui
+}
+
# 设置编码为UTF-8
QMAKE_CXXFLAGS += -finput-charset=UTF-8 -fexec-charset=UTF-8
CONFIG += utf8_source
SOURCES += \
main.cpp \
mainwindow.cpp \
+ dmp_log_uploader.cpp \
app_debug_log.cpp \
config_manager.cpp \
qrcode_barcode_generator.cpp \
# 头文件列表
HEADERS += \
mainwindow.h \
+ dmp_log_uploader.h \
app_debug_log.h \
config_manager.h \
qrcode_barcode_generator.h \
FORMS += \
mainwindow.ui
-# 确保 MOC 正确处理 Qt 元对象系统
-MOC_DIR += moc
-UI_DIR += ui # 指定 UI 生成文件的输出目录
+# ---------- 编译后自动部署 Qt 依赖(Windows:windeployqt + OpenSSL + 工程 libs 全量复制)----------
+# deploy_qt.bat:windeployqt;再从 Qt bin、Tools\\OpenSSL 复制 SSL;最后将工程 libs\\ 下全部文件拷到 exe 同目录。
+win32:equals(TEMPLATE, app) {
+ PRO_DIR = $$_PRO_FILE_PWD_
+ PROJECT_LIBS_SSL = $$PRO_DIR/libs
+ WINDEPLOYQT = $$[QT_INSTALL_BINS]/windeployqt.exe
+ QT_BIN_DIR = $$[QT_INSTALL_BINS]
+ equals(QT_ARCH, i386) {
+ QT_TOOLS_OPENSSL = $$[QT_INSTALL_PREFIX]/../../Tools/OpenSSL/Win_x86/bin
+ SSL_DEPLOY_ARCH = x86
+ } else {
+ QT_TOOLS_OPENSSL = $$[QT_INSTALL_PREFIX]/../../Tools/OpenSSL/Win_x64/bin
+ SSL_DEPLOY_ARCH = x64
+ }
+ msvc {
+ WIN_DEPLOY_EXTRA = --compiler-runtime
+ } else {
+ # MinGW 等:勿留空,否则 nmake/mingw32-make 下 QMAKE_POST_LINK 参数易错位,导致 SSL_ARCH 未传到 deploy_qt.bat
+ WIN_DEPLOY_EXTRA = __NO_EXTRA__
+ }
+ QMAKE_POST_LINK += $$quote($$shell_path($$PRO_DIR/deploy_qt.bat)) $$quote($$shell_path($$BUILD_SUBDIR)) $$quote($$TARGET) $$quote($$shell_path($$WINDEPLOYQT)) $$quote($$WIN_DEPLOY_EXTRA) $$quote($$shell_path($$QT_BIN_DIR)) $$quote($$shell_path($$QT_TOOLS_OPENSSL)) $$quote($$SSL_DEPLOY_ARCH) $$quote($$shell_path($$PROJECT_LIBS_SSL)) $$escape_expand(\n\t)
+}
#include "app_debug_log.h"
#include <QApplication>
+#include <QDebug>
#include <QTextCodec>
+#include <QtGlobal>
+#include <QSslSocket>
+#ifdef Q_OS_WIN
+#include <QLibrary>
+#include <QCoreApplication>
+#include <QDir>
+#include <QFile>
+#include <QFileInfo>
+#include <string>
+#endif
#ifdef _WIN32
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#endif
+#ifdef Q_OS_WIN
+static QString tlsFormatWin32Message(DWORD err)
+{
+ if (err == 0)
+ return QString();
+ wchar_t *buf = nullptr;
+ const DWORD n = FormatMessageW(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ reinterpret_cast<LPWSTR>(&buf), 0, nullptr);
+ QString msg;
+ if (n && buf) {
+ msg = QString::fromWCharArray(buf).trimmed();
+ LocalFree(buf);
+ }
+ return msg;
+}
+
+static void printTlsWin32Error(const char *context, DWORD err)
+{
+ const QString sysMsg = tlsFormatWin32Message(err);
+ if (err == 0)
+ qWarning() << "[TLS]" << context << "GetLastError: 0(可能被 Qt 内部调用覆盖,以下 LoadLibraryW 探测为准)";
+ else
+ qWarning() << "[TLS]" << context << "GetLastError:" << err << sysMsg;
+}
+
+/** 列出 applicationDirPath 下 Qt 能枚举到的 *.dll,用于核对“是否真在同一目录、文件名是否一致”。 */
+static void printTlsExeDirDllInventory(const QString &appDir)
+{
+ const QString exeDir = QDir::cleanPath(appDir);
+ QDir d(exeDir);
+ qWarning() << "[TLS] applicationDirPath:" << exeDir << "QDir.exists:" << d.exists();
+ const QStringList names = d.entryList(QStringList() << QStringLiteral("*.dll"), QDir::Files, QDir::Name);
+ if (names.isEmpty())
+ qWarning() << "[TLS] 本目录下 Qt 枚举 *.dll: 无。若资源管理器里有:请确认是否**同一文件夹**、是否**云盘仅联机占位**未落盘、是否改的是别的 build 目录下的 debug。";
+ else
+ qWarning() << "[TLS] 本目录下 *.dll 共" << names.size() << "个:" << names.join(QStringLiteral(", "));
+ for (const QString &n : names) {
+ if (n.contains(QLatin1String("ssl"), Qt::CaseInsensitive)
+ || n.contains(QLatin1String("crypto"), Qt::CaseInsensitive)) {
+ const QFileInfo x(d.absoluteFilePath(n));
+ qWarning() << "[TLS] (含 ssl/crypto)" << n << "size:" << x.size();
+ }
+ }
+}
+
+static void printTlsDllLoadSearchPaths(const QString &dllBaseName)
+{
+ const QString dllFile = dllBaseName + QStringLiteral(".dll");
+ qWarning() << "[TLS] LoadLibrary(" << dllFile << ") 失败时的典型搜索顺序与每个目录下的完整路径:";
+
+ QStringList paths;
+ paths << QCoreApplication::applicationDirPath();
+ paths << QDir::currentPath();
+
+ wchar_t wbuf[MAX_PATH];
+ if (GetSystemDirectoryW(wbuf, MAX_PATH))
+ paths << QString::fromWCharArray(wbuf);
+ if (GetWindowsDirectoryW(wbuf, MAX_PATH))
+ paths << QString::fromWCharArray(wbuf);
+ if (GetDllDirectoryW(MAX_PATH, wbuf) > 0u)
+ paths << QString::fromWCharArray(wbuf);
+
+ const QString pathEnv = qEnvironmentVariable("PATH");
+ const QStringList pathParts = pathEnv.split(QLatin1Char(';'), QString::SkipEmptyParts);
+ for (const QString &p : pathParts)
+ paths << p.trimmed();
+
+ qWarning() << "[TLS] exe:" << QCoreApplication::applicationFilePath();
+ qWarning() << "[TLS] PATH 环境变量(原始):" << pathEnv;
+
+ int idx = 1;
+ QStringList seen;
+ for (const QString &dir : paths) {
+ if (dir.isEmpty())
+ continue;
+ const QString norm = QDir::cleanPath(dir);
+ if (seen.contains(norm))
+ continue;
+ seen << norm;
+ const QString full = QDir::cleanPath(norm + QLatin1Char('/') + dllFile);
+ const bool exists = QFileInfo::exists(full);
+ qWarning() << "[TLS] " << idx++ << norm << "->" << full << (exists ? QLatin1String("[存在]") : QLatin1String("[无]"));
+ }
+}
+
+/** exe 同目录下若已有该 DLL,用完整路径 LoadLibrary,区分“文件缺失”与“子依赖缺失”(126 常为后者)。 */
+static void printTlsLoadDllProbe(const QString &appDir, const QString &dllBaseName)
+{
+ const QString dllFile = dllBaseName + QStringLiteral(".dll");
+ const QString fullPath = QDir::cleanPath(appDir + QLatin1Char('/') + dllFile);
+ const QFileInfo fi(fullPath);
+ const QString nativeFull = QDir::toNativeSeparators(fullPath);
+ qWarning() << "[TLS] 探测" << dllFile << "完整路径:" << nativeFull << "QFileInfo::exists:" << fi.exists()
+ << "QFile::exists:" << QFile::exists(fullPath);
+ if (!fi.exists()) {
+ const std::wstring wnative = nativeFull.toStdWString();
+ const DWORD attr = GetFileAttributesW(wnative.c_str());
+ if (attr == INVALID_FILE_ATTRIBUTES) {
+ qWarning() << "[TLS] GetFileAttributesW 失败 LastError:" << GetLastError()
+ << "(若仍为“文件在的”,多为路径不是 exe 同目录,或云盘/权限导致 API 不可见)";
+ } else {
+ qWarning() << "[TLS] GetFileAttributesW 成功 attrs=0x" << QString::number(attr, 16)
+ << "(与 QFileInfo 不一致时请反馈 Qt/路径问题)";
+ }
+ return;
+ }
+
+ const std::wstring wpath = QDir::toNativeSeparators(fullPath).toStdWString();
+ HMODULE h = LoadLibraryW(wpath.c_str());
+ if (h) {
+ qWarning() << "[TLS] LoadLibraryW(完整路径) 成功;若 QLibrary(短名) 仍失败,多为搜索路径/工作目录与 exe 目录不一致。";
+ FreeLibrary(h);
+ return;
+ }
+ const DWORD err = GetLastError();
+ printTlsWin32Error("LoadLibraryW(完整路径)", err);
+ if (err == 126u)
+ qWarning() << "[TLS] 错误 126:多为该 DLL 的“子依赖”未找到(不是主文件名错)。"
+ "MinGW 构建的 OpenSSL 常需同目录另有:libgcc_s_seh-1.dll、libwinpthread-1.dll、"
+ "libstdc++-6.dll(从本机 Qt 的 Tools\\mingw730_64\\bin 或 mingw73_64\\bin 复制);"
+ "MSVC 构建的 OpenSSL 则需 VCRUNTIME140.dll、api-ms-win-crt-*.dll(装 VC++ 运行库)。"
+ "可用 dumpbin /dependents xxx.dll 或 Dependencies 工具查看完整依赖树。";
+}
+#endif
+
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
+ // HTTPS 依赖 Qt Network 在运行时加载 TLS 后端;Windows 下若缺 OpenSSL DLL,会出现 TLS initialization failed
+ qDebug() << "[TLS] supportsSsl:" << QSslSocket::supportsSsl()
+ << "build:" << QSslSocket::sslLibraryBuildVersionString()
+ << "runtime:" << QSslSocket::sslLibraryVersionString();
+#ifdef Q_OS_WIN
+ if (!QSslSocket::supportsSsl()) {
+ const QString appDir = QCoreApplication::applicationDirPath();
+ printTlsExeDirDllInventory(appDir);
+ const bool is32 = (QT_POINTER_SIZE == 4);
+ const QString cryptoName = is32 ? QStringLiteral("libcrypto-1_1") : QStringLiteral("libcrypto-1_1-x64");
+ const QString sslName = is32 ? QStringLiteral("libssl-1_1") : QStringLiteral("libssl-1_1-x64");
+ QLibrary libCrypto(cryptoName);
+ SetLastError(0);
+ const bool okCrypto = libCrypto.load();
+ if (!okCrypto) {
+ qWarning() << "[TLS] QLibrary 加载" << cryptoName << "失败:" << libCrypto.errorString();
+ printTlsWin32Error("QLibrary(libcrypto)", GetLastError());
+ printTlsDllLoadSearchPaths(cryptoName);
+ printTlsLoadDllProbe(appDir, cryptoName);
+ }
+ QLibrary libSsl(sslName);
+ SetLastError(0);
+ const bool okSsl = libSsl.load();
+ if (!okSsl) {
+ qWarning() << "[TLS] QLibrary 加载" << sslName << "失败:" << libSsl.errorString();
+ printTlsWin32Error("QLibrary(libssl)", GetLastError());
+ printTlsDllLoadSearchPaths(sslName);
+ printTlsLoadDllProbe(appDir, sslName);
+ }
+ if (okCrypto && okSsl)
+ qWarning() << "[TLS] 注:QLibrary 能加载但 QSslSocket 仍 false,多为 Qt 与 OpenSSL 版本/符号不匹配,请使用与 Qt 同套件提供的 OpenSSL。";
+
+ const QString dllHint = is32
+ ? QStringLiteral("本程序为 32 位:仅需 libssl-1_1.dll、libcrypto-1_1.dll(必须同为 32 位 PE,勿用 *-x64.dll)。同目录混放 64 位文件无效。")
+ : QStringLiteral("本程序为 64 位:需 libssl-1_1-x64.dll、libcrypto-1_1-x64.dll(勿用无 x64 的 32 位库)。");
+ qWarning() << "[TLS] exe 目录:" << appDir << dllHint
+ << "若提示“不是有效的 Win32 应用程序”,说明 DLL 位数与 exe 不一致。";
+ }
+#endif
+
AppDebugLog::install();
#ifdef _WIN32
#include <QStandardPaths>
#include <QDir>
#include <QFile>
+#include <QFileInfo>
#include <QDesktopServices>
#include <QUrl>
#include <QCoreApplication>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
+ , m_dmpUploader(new DmpLogUploader(this))
{
ui->setupUi(this);
setWindowTitle("雷鸟产测工具 V1.9");
ui->overallStatusText->setStyleSheet(textStyle);
}
+QString MainWindow::csvFieldForTestItem(const QString& key, const QString& uiText) const
+{
+ if (!isTestItemEnabled(key))
+ return QStringLiteral("没测");
+ QString t = uiText.trimmed();
+ if (t == QStringLiteral("未测试"))
+ return QStringLiteral("没测");
+ return t;
+}
+
+QString MainWindow::csvFieldForBattery() const
+{
+ if (!isTestItemEnabled(QStringLiteral("charge")) && !isTestItemEnabled(QStringLiteral("ntc")))
+ return QStringLiteral("没测");
+ QString t = ui->batteryStatusText->text().trimmed();
+ if (t == QStringLiteral("未测试"))
+ return QStringLiteral("没测");
+ return t;
+}
+
+QString MainWindow::csvFieldFor4gSignal() const
+{
+ if (!isTestItemEnabled(QStringLiteral("4g")))
+ return QStringLiteral("没测");
+ QString t = ui->fourgSignalStatusText->text().trimmed();
+ if (t == QStringLiteral("未测试"))
+ t = QStringLiteral("没测");
+ return t;
+}
+
+QString MainWindow::csvField4gAverageSignalDetail() const
+{
+ if (!isTestItemEnabled(QStringLiteral("4g")))
+ return QStringLiteral("没测");
+ const int valid = cat1SignalReceivedCount - cat1Signal99Count;
+ if (valid > 0)
+ return QString::number(double(cat1SignalTotalValue) / valid, 'f', 1);
+ return QStringLiteral("无有效采样");
+}
+
+QString MainWindow::csvFieldForPing() const
+{
+ if (!isTestItemEnabled(QStringLiteral("4g")))
+ return QStringLiteral("没测");
+ QString t = ui->PingStatusText->text().trimmed();
+ if (t == QStringLiteral("未测试"))
+ return QStringLiteral("没测");
+ return t;
+}
+
+QString MainWindow::csvFieldForAudio() const
+{
+ if (!isTestItemEnabled(QStringLiteral("audio")))
+ return QStringLiteral("没测");
+ QString t = ui->audioStatusText->text().trimmed();
+ if (t == QStringLiteral("未测试"))
+ t = QStringLiteral("没测");
+ return t;
+}
+
+QString MainWindow::csvFieldAudioMicSampleDetail() const
+{
+ if (!isTestItemEnabled(QStringLiteral("audio")))
+ return QStringLiteral("没测");
+ if (!audioMicLevelsHistory.isEmpty())
+ return audioMicLevelsHistory;
+ return QStringLiteral("无采样 门槛>=%1").arg(audioLevelStandard);
+}
+
+namespace {
+
+/** 旧版 19 列(Ping 后为整体结果)→ 补「4G平均信号强度」「录音MIC采样依据」两列,表头对齐 kCsvHeader */
+QStringList migrateProdTestCsvIfOld19Columns(const QStringList &existingLines, const QString &kCsvHeader)
+{
+ if (existingLines.isEmpty())
+ return existingLines;
+ const QString header0 = existingLines[0];
+ if (header0.contains(QStringLiteral("4G平均信号强度")))
+ return existingLines;
+
+ qDebug() << "[产测CSV] 检测到旧表头,自动补全两列依据字段(历史行填「历史无此列」)";
+ QStringList out;
+ out << kCsvHeader;
+ for (int i = 1; i < existingLines.size(); ++i) {
+ const QString line = existingLines[i];
+ if (line.trimmed().isEmpty()) {
+ out << line;
+ continue;
+ }
+ QStringList f = line.split(QStringLiteral(","));
+ if (f.size() == 19) {
+ QStringList nf;
+ nf.reserve(21);
+ for (int j = 0; j < 18; ++j)
+ nf << f[j];
+ nf << QStringLiteral("历史无此列") << QStringLiteral("历史无此列") << f[18];
+ out << nf.join(QStringLiteral(","));
+ } else {
+ qDebug() << "[产测CSV] 跳过迁移:行字段数非19,保持原样,行号" << (i + 1);
+ out << line;
+ }
+ }
+ return out;
+}
+
+} // namespace
+
// 保存测试记录到CSV文件(可用Excel打开)
QString MainWindow::saveTestRecord()
{
+ const QString kCsvHeader = QStringLiteral(
+ "开始时间,结束时间,测试耗时,软件版本,IMEI,CGSN_IMEI,ICCID,Modem版本,PYVER版本,"
+ "LCD测试,马达测试,按键测试,录音测试,电池测试,G-sensor测试,蓝牙测试,4G信号,Ping测试,"
+ "4G平均信号强度,录音MIC采样依据,整体结果");
+
QDateTime endTime = QDateTime::currentDateTime();
QString startTimeStr = testStartTime.toString("yyyy-MM-dd hh:mm:ss");
QString endTimeStr = endTime.toString("yyyy-MM-dd hh:mm:ss");
qDebug() << " ICCID:" << iccid;
qDebug() << " Modem:" << (modem.isEmpty() ? "空" : modem);
qDebug() << " PYVER:" << (pyver.isEmpty() ? "空" : pyver);
- // 获取各项测试结果
- QString lcdResult = ui->lcdStatusText->text();
- QString motorResult = ui->motorStatusText->text();
- QString keyResult = ui->keyStatusText->text();
- QString audioResult = ui->audioStatusText->text();
- QString batteryResult = ui->batteryStatusText->text();
- QString gsensorResult = ui->gsensorStatusText->text();
- QString bluetoothResult = ui->bluetoothStatusText->text();
- QString fourgSignalResult = ui->fourgSignalStatusText->text();
- const QString pingResult = ui->PingStatusText->text();
QString overallResult = ui->overallStatusText->text();
-
- qDebug() << " Ping结果:" << pingResult;
// 创建保存目录(在源码同一路径下)
QString appPath = QCoreApplication::applicationDirPath();
dir.mkpath(saveDir);
}
- // CSV文件路径(统一命名)
- QString csvFileName = "产测记录.csv";
- QString csvFilePath = saveDir + "/" + csvFileName;
+ // CSV:按保存时刻的日期命名(产测记录_yyyyMMdd.csv),每天一个文件;每次写入前重新计算
+ const QDateTime csvFileStamp = QDateTime::currentDateTime();
+ const QString csvFileName = QStringLiteral("产测记录_%1.csv").arg(csvFileStamp.toString(QStringLiteral("yyyyMMdd")));
+ const QString csvFilePath = saveDir + QLatin1Char('/') + csvFileName;
// 检查是否是新文件(需要写入表头)
bool isNewFile = !QFile::exists(csvFilePath);
<< (iccid.isEmpty() ? "未获取" : iccid)
<< (modem.isEmpty() ? "未获取" : modem)
<< (pyver.isEmpty() ? "未获取" : pyver)
- << lcdResult
- << motorResult
- << keyResult
- << audioResult
- << batteryResult
- << gsensorResult
- << bluetoothResult
- << fourgSignalResult
- << pingResult
+ << csvFieldForTestItem(QStringLiteral("lcd"), ui->lcdStatusText->text())
+ << csvFieldForTestItem(QStringLiteral("motor"), ui->motorStatusText->text())
+ << csvFieldForTestItem(QStringLiteral("key"), ui->keyStatusText->text())
+ << csvFieldForAudio()
+ << csvFieldForBattery()
+ << csvFieldForTestItem(QStringLiteral("gsensor"), ui->gsensorStatusText->text())
+ << csvFieldForTestItem(QStringLiteral("bluetooth"), ui->bluetoothStatusText->text())
+ << csvFieldFor4gSignal()
+ << csvFieldForPing()
+ << csvField4gAverageSignalDetail()
+ << csvFieldAudioMicSampleDetail()
<< overallResult;
QString newDataLine = dataFields.join(",") + "\n";
if (isNewFile) {
// 新文件,写入表头和数据
- finalContent += "开始时间,结束时间,测试耗时,软件版本,IMEI,CGSN_IMEI,ICCID,Modem版本,PYVER版本,LCD测试,马达测试,按键测试,录音测试,电池测试,G-sensor测试,蓝牙测试,4G信号,Ping测试,整体结果\n";
+ finalContent += kCsvHeader + QLatin1Char('\n');
finalContent += newDataLine;
qDebug() << "创建新的CSV文件:" << csvFilePath;
} else {
existingLines << readStream.readLine();
}
existingFile.close();
+
+ const QStringList workingLines = migrateProdTestCsvIfOld19Columns(existingLines, kCsvHeader);
- // 构建最终内容
- if (!existingLines.isEmpty()) {
- finalContent = existingLines[0] + "\n"; // 表头
-
- bool iccidFound = false;
-
- // 检查每一行数据,查找相同ICCID
- for (int i = 1; i < existingLines.size(); i++) {
- QString line = existingLines[i];
- if (line.trimmed().isEmpty()) continue;
-
- // 分割CSV行,获取ICCID字段(第7列,索引为6)
- QStringList fields = line.split(",");
- if (fields.size() >= 7) {
- QString existingIccid = fields[6].trimmed();
-
- if (existingIccid == iccid) {
- // 找到相同ICCID,替换为新记录
- finalContent += newDataLine;
- iccidFound = true;
- qDebug() << "找到相同ICCID,替换记录:" << iccid;
- } else {
- // 保留不同ICCID的记录
- finalContent += line + "\n";
- }
- } else {
- // 格式不正确的行,保留
- finalContent += line + "\n";
- }
- }
-
- // 如果没有找到相同ICCID,追加新记录
- if (!iccidFound) {
- finalContent += newDataLine;
- qDebug() << "未找到相同ICCID,追加新记录:" << iccid;
+ // 构建最终内容:保留全部历史行,同一硬件多次测试**追加**新行(不再按 ICCID 覆盖)
+ if (!workingLines.isEmpty()) {
+ finalContent = workingLines[0] + QLatin1Char('\n'); // 表头(可能已由迁移更新)
+ for (int i = 1; i < workingLines.size(); i++) {
+ const QString line = workingLines[i];
+ if (line.trimmed().isEmpty())
+ continue;
+ finalContent += line + QLatin1Char('\n');
}
+ finalContent += newDataLine;
+ qDebug() << "产测 CSV 追加一行(同一 ICCID 可多条)ICCID:" << iccid;
} else {
// 文件为空,重建
- finalContent = "开始时间,结束时间,测试耗时,软件版本,IMEI,CGSN_IMEI,ICCID,Modem版本,PYVER版本,LCD测试,马达测试,按键测试,录音测试,电池测试,G-sensor测试,蓝牙测试,4G信号,Ping测试,整体结果\n";
+ finalContent = kCsvHeader + QLatin1Char('\n');
finalContent += newDataLine;
}
}
qDebug() << "测试记录已保存到:" << csvFilePath;
qDebug() << "记录内容: 开始时间=" << startTimeStr << ", 结束时间=" << endTimeStr << ", 耗时=" << durationStr
<< ", Ver=" << ver << ", IMEI=" << imei << ", CGSN=" << cgsnImei << ", ICCID=" << iccid << ", Modem=" << modem
- << ", PYVER=" << pyver << ", LCD=" << lcdResult << ", 马达=" << motorResult
- << ", 按键=" << keyResult << ", 4G信号=" << fourgSignalResult << ", Ping=" << pingResult
+ << ", PYVER=" << pyver << ", LCD=" << csvFieldForTestItem(QStringLiteral("lcd"), ui->lcdStatusText->text())
+ << ", 录音=" << csvFieldForAudio()
+ << ", 录音MIC依据=" << csvFieldAudioMicSampleDetail()
+ << ", 4G=" << csvFieldFor4gSignal() << ", 4G平均=" << csvField4gAverageSignalDetail()
+ << ", Ping=" << csvFieldForPing()
<< ", 整体=" << overallResult;
// 显示保存成功消息
// 用最终设备信息修正会话日志和UI日志的文件名与文件头(可能之前是TMP占位)
renameLogFileWithDeviceInfo();
renameUiLogFileWithDeviceInfo();
+
+ // DMP 上传日志:与产测记录/logs 下主会话日志同名,尾缀 _dmp_upload.log
+ {
+ QString dmpLogPath;
+ if (!testLogFilePath.isEmpty()) {
+ const QFileInfo fi(testLogFilePath);
+ dmpLogPath = fi.absolutePath() + QLatin1Char('/') + fi.completeBaseName() + QStringLiteral("_dmp_upload.log");
+ } else {
+ const QString logsDir = appPath + QStringLiteral("/产测记录/logs");
+ QDir().mkpath(logsDir);
+ const QDateTime now = QDateTime::currentDateTime();
+ const QString im = imei.isEmpty() ? QStringLiteral("NA") : imei;
+ const QString ic = iccid.isEmpty() ? QStringLiteral("NA") : iccid;
+ dmpLogPath = QStringLiteral("%1/%2_%3_%4_%5_dmp_upload.log")
+ .arg(logsDir)
+ .arg(now.toString(QStringLiteral("yyyyMMdd")))
+ .arg(now.toString(QStringLiteral("hhmmss")))
+ .arg(im)
+ .arg(ic);
+ }
+ m_dmpUploader->uploadProductionRecord(dataFields, imei, cgsnImei, iccid, dmpLogPath);
+ }
+
// 测试结束立即关闭日志文件,后续不再写入;下一轮开始会重新创建
closeTestLogFile();
closeUiLogFile();
keyTestStarted = false;
audioTestPopupShown = false;
audioPlaybackPopupShown = false;
+ audioMicLevelsHistory.clear();
// 重置状态指示器
updateTestStatus("lcd", STATUS_UNTESTED);
audioMicMiddleConsecutiveCount = 0;
audioMicTotalPassCount = 0;
audioTestFinished = false;
+ audioMicLevelsHistory.clear();
// 获取音频测试通过标准(从UI中读取)
audioLevelStandard = "中"; // 默认值
qDebug() << "无法解析音频等级,跳过";
return;
}
+
+ if (!audioMicLevelsHistory.isEmpty())
+ audioMicLevelsHistory += QLatin1Char('|');
+ audioMicLevelsHistory += detectedLevel;
+ if (audioMicLevelsHistory.size() > 400)
+ audioMicLevelsHistory = audioMicLevelsHistory.right(400);
qDebug() << QString("解析到音频等级: %1 (要求: >= %2)").arg(detectedLevel).arg(audioLevelStandard);
// 保存蓝牙测试参数
configManager.setTestConfig("bluetoothAddress", ui->bluetoothAddressLineEdit->text());
configManager.setTestConfig("bluetoothRSSI", ui->bluetoothRSSILineEdit->text());
+ // 4G CAT1 信号门槛(空则运行时用默认 25)
+ configManager.setTestConfig("cat1MinSignal", ui->cat1RSSILineEdit->text());
// 保存测试项checkbox状态
configManager.setTestCheckbox("lcd", ui->checkBox_lcd->isChecked());
// 恢复蓝牙测试参数
ui->bluetoothAddressLineEdit->setText(configManager.getTestConfig("bluetoothAddress", ""));
ui->bluetoothRSSILineEdit->setText(configManager.getTestConfig("bluetoothRSSI", ""));
+ ui->cat1RSSILineEdit->setText(configManager.getTestConfig("cat1MinSignal", ""));
// 恢复测试项checkbox状态
ui->checkBox_lcd->setChecked(configManager.getTestCheckbox("lcd", true));
dir.mkpath(logsDir);
}
- // 生成临时文件名:yyyymmdd_hhmmss_TMP_TMP.log
- // 说明:测试过程中数据要安全落盘;文件名关键信息在获取到IMEI/ICCID后再重命名补齐
- QString fileName = QString("%1_%2_%3_%4.log")
+ // 临时文件名:yyyymmdd_hhmmss_mmm_TMP_TMP.log(含毫秒,避免同秒两次产测重命名撞车覆盖)
+ // 获取到 IMEI/ICCID 后重命名为 日期_时间_毫秒_IMEI_ICCID.log
+ const QString msecPart = QStringLiteral("%1").arg(now.time().msec(), 3, 10, QChar('0'));
+ QString fileName = QString("%1_%2_%3_%4_%5.log")
.arg(now.toString("yyyyMMdd"))
.arg(now.toString("hhmmss"))
+ .arg(msecPart)
.arg(imei)
.arg(iccid);
dir.mkpath(logsDir);
}
- // 生成临时文件名:yyyymmdd_hhmmss_TMP_TMP_UI.log
- QString fileName = QString("%1_%2_%3_%4_UI.log")
+ const QString msecPartUi = QStringLiteral("%1").arg(now.time().msec(), 3, 10, QChar('0'));
+ QString fileName = QString("%1_%2_%3_%4_%5_UI.log")
.arg(now.toString("yyyyMMdd"))
.arg(now.toString("hhmmss"))
+ .arg(msecPartUi)
.arg(imei)
.arg(iccid);
return;
}
- // 从当前文件名中提取时间部分
QFileInfo fileInfo(testLogFilePath);
QString baseName = fileInfo.baseName();
- QStringList parts = baseName.split("_");
+ QStringList parts = baseName.split(QStringLiteral("_"));
const QString oldMainPathForMirror = testLogFilePath;
- if (parts.size() >= 2) {
- QString datePart = parts[0]; // yyyymmdd
- QString timePart = parts[1]; // hhmmss
-
- // 生成新文件名
- QString newFileName = QString("%1_%2_%3_%4.log")
- .arg(datePart)
- .arg(timePart)
- .arg(imei)
- .arg(iccid);
-
- QString newFilePath = fileInfo.absolutePath() + "/" + newFileName;
-
- // 如果新文件名与当前文件名不同,重命名并修正文件头中的IMEI/ICCID
- if (newFilePath != testLogFilePath) {
+ QString datePart;
+ QString timePart;
+ QString msecPart;
+ // 新:yyyyMMdd_hhmmss_mmm_TMP_TMP;旧:yyyyMMdd_hhmmss_TMP_TMP(无毫秒)
+ if (parts.size() >= 5 && parts[3] == QStringLiteral("TMP") && parts[4] == QStringLiteral("TMP")) {
+ datePart = parts[0];
+ timePart = parts[1];
+ msecPart = parts[2];
+ } else if (parts.size() >= 4 && parts[2] == QStringLiteral("TMP") && parts[3] == QStringLiteral("TMP")) {
+ datePart = parts[0];
+ timePart = parts[1];
+ msecPart = QStringLiteral("000");
+ } else {
+ qDebug() << "renameLogFileWithDeviceInfo: 无法解析会话日志基名,跳过:" << baseName;
+ return;
+ }
+
+ // 生成新文件名(保留毫秒段,同一硬件多次测试各有一份文件)
+ const QString newFileName = QString("%1_%2_%3_%4_%5.log")
+ .arg(datePart)
+ .arg(timePart)
+ .arg(msecPart)
+ .arg(imei)
+ .arg(iccid);
+
+ const QString newFilePath = fileInfo.absolutePath() + QLatin1Char('/') + newFileName;
+
+ // 如果新文件名与当前文件名不同,重命名并修正文件头中的IMEI/ICCID
+ if (newFilePath != testLogFilePath) {
// 确保当前文件关闭
if (testLogFile->isOpen()) {
testLogFile->close();
}
}
}
- }
}
// 使用设备信息重命名UI日志文件(与会话测试日志同步)
}
QFileInfo fileInfo(uiLogFilePath);
- QString baseName = fileInfo.baseName();
- QStringList parts = baseName.split("_");
-
- if (parts.size() >= 2) {
- QString datePart = parts[0]; // yyyymmdd
- QString timePart = parts[1]; // hhmmss
+ const QString baseName = fileInfo.baseName();
+ QStringList parts = baseName.split(QStringLiteral("_"));
+ if (parts.isEmpty() || parts.last() != QStringLiteral("UI")) {
+ qDebug() << "renameUiLogFileWithDeviceInfo: 基名不以 _UI 结尾,跳过:" << baseName;
+ return;
+ }
+ parts.removeLast();
+
+ QString datePart;
+ QString timePart;
+ QString msecPart;
+ if (parts.size() >= 5 && parts[3] == QStringLiteral("TMP") && parts[4] == QStringLiteral("TMP")) {
+ datePart = parts[0];
+ timePart = parts[1];
+ msecPart = parts[2];
+ } else if (parts.size() >= 4 && parts[2] == QStringLiteral("TMP") && parts[3] == QStringLiteral("TMP")) {
+ datePart = parts[0];
+ timePart = parts[1];
+ msecPart = QStringLiteral("000");
+ } else {
+ qDebug() << "renameUiLogFileWithDeviceInfo: 无法解析 UI 日志基名,跳过:" << baseName;
+ return;
+ }
- // 生成新文件名:与旧方案类似,后缀标记为UI
- QString newFileName = QString("%1_%2_%3_%4_UI.log")
- .arg(datePart)
- .arg(timePart)
- .arg(imei)
- .arg(iccid);
+ const QString newFileName = QString("%1_%2_%3_%4_%5_UI.log")
+ .arg(datePart)
+ .arg(timePart)
+ .arg(msecPart)
+ .arg(imei)
+ .arg(iccid);
- QString newFilePath = fileInfo.absolutePath() + "/" + newFileName;
+ const QString newFilePath = fileInfo.absolutePath() + QLatin1Char('/') + newFileName;
- if (newFilePath != uiLogFilePath) {
+ if (newFilePath != uiLogFilePath) {
// 确保当前文件关闭
if (uiLogFile->isOpen()) {
uiLogFile->close();
}
}
}
- }
}
// ICCID输入框文本改变时触发
#include <QFile>
#include <QTextStream>
#include "config_manager.h"
+#include "dmp_log_uploader.h"
#include "qrcode_barcode_generator.h"
QT_BEGIN_NAMESPACE
int audioMicTotalPassCount; // 累计检测到满足要求的Level的次数(包括非连续的)
QString audioLevelStandard; // 音频测试通过标准("中"或"低"等)
bool audioTestFinished; // 录音测试是否已完成(成功或失败)
+ QString audioMicLevelsHistory; // fac:mic level 采样序列(低|高|静音…),供 CSV/DMP
QSet<QString> bluetoothAddresses; // 蓝牙测试检测到的不同蓝牙地址集合
bool bluetoothTestFinished; // 蓝牙测试是否已完成(成功或失败)
QString detectedBluetoothAddress; // 检测到的指定蓝牙地址
// 配置管理
ConfigManager configManager; // 配置管理器(不使用指针,直接作为成员)
+ DmpLogUploader *m_dmpUploader; // DMP 产测记录 HTTPS 上传(独立日志)
// 开机检测相关变量
bool bootDetected; // 是否已检测到开机
// 测试项checkbox检查辅助函数
bool isTestItemEnabled(const QString& testName) const;
void skipToNextEnabledTest(); // 跳过当前测试项,推进到下一个启用的测试项
+ /** 产测 CSV/DMP:未勾选项写「没测」;录音/4G 等附加明细 */
+ QString csvFieldForTestItem(const QString& key, const QString& uiText) const;
+ QString csvFieldForBattery() const;
+ QString csvFieldFor4gSignal() const;
+ QString csvFieldForPing() const;
+ QString csvFieldForAudio() const;
+ /** 独立列:CAT1 有效采样平均 RSSI;无有效值时“无有效采样”或未测“没测” */
+ QString csvField4gAverageSignalDetail() const;
+ /** 独立列:fac:mic 档位序列(| 分隔)或 无采样/没测 */
+ QString csvFieldAudioMicSampleDetail() const;
// 二维码和条形码生成相关函数
void onICCIDLineEditTextChanged(const QString& text); // ICCID输入框文本改变时触发