#include "processthread.h" #include #include #include #include "api/configapi.h" #include "appevent.h" #include "processmodel.h" #include "qjsonarray.h" #include "qjsonobject.h" #include #include #include "api/processapi.h" #include "qjsonvalue.h" #include "qlist.h" #include "qmessagebox.h" #include "qnamespace.h" #include "basemainTr.h" CWF::SqlDatabaseStorage storage("QSQLITE", "localhost", "postgres", "postgres", "1234", 5432); // 将 FILETIME 转换为 Unix 时间戳 static qint64 fileTimeToUnixTimestamp(FILETIME ft) { // FILETIME 是自 1601 年以来的 100 毫微秒数 ULARGE_INTEGER ull; ull.LowPart = ft.dwLowDateTime; ull.HighPart = ft.dwHighDateTime; // FILETIME 和 时间戳的 基础时间不一样 // 转换为秒,并减去 1601 到 1970 年之间的秒数 if (ull.QuadPart / 10000000LL > 11644473600LL) { qint64 timestamp = ull.QuadPart / 10000000LL - 11644473600LL; return timestamp; } return ull.QuadPart / 10000000LL; } QString convertSecondsToTimeFormat(int totalSeconds) { int days = totalSeconds / (24 * 3600); // 计算天数 int hours = (totalSeconds % (24 * 3600)) / 3600; // 计算小时 int minutes = (totalSeconds % 3600) / 60; // 计算分钟 int seconds = totalSeconds % 60; // 计算秒数 // 返回格式化后的字符串,并使用 tr() 标记可翻译的文本 return QString(Tr::tr("%1days%2hours%3minutes%4seconds")) .arg(days) .arg(hours) .arg(minutes) .arg(seconds); } /// /// \brief 转换到分钟 /// \param totalSeconds /// \return /// QString convertSecondsToMinutesTimeFormat(int totalSeconds) { // 只计算分钟 int minutes = totalSeconds / 60; return QString::number(minutes); } static QVariant config() { QSettings settings(QSettings::NativeFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName()); settings.beginGroup("config"); QVariant value; if (settings.contains("serverConfig")) { value = settings.value("serverConfig"); } else { value = R"Raw([ { "uuid": "3d8a0525-76fa-45bb-b0db-ed7260e31bdc", "name": "sldworks.exe", "chinese_name": "SolidWorks", "pid_type": "学习", "is_confirmed": 1, "is_prompt": 2, "prompt_type": "", "prompt_time": 2400, "path": "", "notes": "", "c_time": 1734346737, "u_time": 0 }, { "uuid": "daa98539-fb91-4eb6-a59f-0fd4509e926c", "name": "sldworks_fs.exe", "chinese_name": "SolidWorks", "pid_type": "学习", "is_confirmed": 2, "is_prompt": 1, "prompt_type": "", "prompt_time": 120, "path": "", "notes": "", "c_time": 1733906424, "u_time": 0 }, { "uuid": "d09df0b1-2386-4527-afa7-3f1fb37b6171", "name": "SLDWORKS.exe", "chinese_name": "SolidWorks", "pid_type": "学习", "is_confirmed": 1, "is_prompt": 2, "prompt_type": "", "prompt_time": 2400, "path": "", "notes": "", "c_time": 1733652641, "u_time": 0 }, { "uuid": "c4de744f-0afe-4e0f-a39e-7ee59f46c042", "name": "WeChat.exe", "chinese_name": "微信", "pid_type": "日常", "is_confirmed": 1, "is_prompt": 2, "prompt_type": "", "prompt_time": 15, "path": "2", "notes": "测试", "c_time": 1733455089, "u_time": 0 } ])Raw"; } settings.endGroup(); return value; } const QJsonArray processNameArray() { TC::ProcessNameApi processNameApi; QJsonArray serverArray = processNameApi.get(); if (serverArray.isEmpty()) { // 读取本地配置 转 QJsonArray const QString configText = config().toString(); QJsonDocument jsonDoc = QJsonDocument::fromJson(configText.toUtf8()); if (jsonDoc.isArray()) { serverArray = jsonDoc.array(); } else if (jsonDoc.isObject()) { // If the config is a single object, we can wrap it in an array serverArray.append(jsonDoc.object()); } qDebug() << "use Local config"; } else { // 写入本地配置 QSettings settings(QSettings::NativeFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName()); settings.beginGroup("config"); settings.setValue("serverArray", QString(QJsonDocument(serverArray).toJson(QJsonDocument::Compact))); settings.endGroup(); qDebug() << "use server config"; } return serverArray; } void ProcessThread::sendExitTime(qint64 updataTime) { serverConfig(); QJsonArray serverArray = processNameArray(); // 获取过滤名单 QSet filter; QSet gameFilter; for (const QJsonValue &item : serverArray) { if (!item.isObject()) { return; } const QJsonObject object = item.toObject(); const QString name = object["name"].toString(); if (object.contains("is_confirmed")) { if (object["is_confirmed"].toInt() == 2) { filter.insert(name); } } } // 发送数据到服务器 sendToServer(filter, updataTime); ProcessModel processModel{storage}; // 获取服务器 对应的时间信息 for (const QJsonValue &item : serverArray) { if (!item.isObject()) { return; } const QJsonObject object = item.toObject(); const QString name = object["name"].toString(); const QString zhName = object["chinese_name"].toString(); const QString type = object["pid_type"].toString(); // 2 提示 1 不提示 0 未知 if (object.contains("is_prompt")) { // 提示 if (object["is_prompt"].toInt() == 2) { if (type == "游戏" || type == "game") { } else { // const QString name = object["name"].toString(); // const QString zhName = object["chinese_name"].toString(); const int time = object["prompt_time"].toInt(); if (time > 0) { const QString whereTime = QString("(updataTime - lastAlertTime) > %1") .arg(time); const QString whereProcessName = QString("processName = '%1'").arg(name); const QString whereUpdataTime = QString("updataTime = '%1'").arg(updataTime); CWF::SqlQueryManager qry(storage); qry.select("*", processModel.getTableName()) .where(QString("%1 AND %2 AND %3") .arg(whereTime) .arg(whereProcessName) .arg(whereUpdataTime)); qry.prepare(); QJsonObject jsonObject = qry.exec(); QJsonArray jsonArray = qry.toJson(); qDebug() << jsonArray; for (const auto &value : jsonArray) { const QJsonObject object = value.toObject(); const QString processName = object["processName"].toString(); const qint64 runTime = object["exitTime"].toVariant().toLongLong() - object["creationTime"].toVariant().toLongLong(); const QString text = QString( tr("Program %1 has been used for %2 minutes%3") .arg(zhName) .arg(convertSecondsToMinutesTimeFormat(runTime)) .arg(messageText.toString())); emit messageBox(text, messageBoxPointX, messageBoxPointY, messageTitle); // 更新 lastAlertTime ProcessModel processModel{storage}; processModel.buildFromJson(object); // 更新 消息时间 processModel.setLastAlertTime(updataTime); processModel.save(); } } } } } if (object.contains("pid_type")) { if (type == "游戏" || type == "game") { const QString whereTime = QString("(updataTime - lastAlertTime) > %1").arg(1); const QString whereProcessName = QString("processName = '%1'").arg(name); const QString whereUpdataTime = QString("updataTime = '%1'").arg(updataTime); CWF::SqlQueryManager qry(storage); qry.select("*", processModel.getTableName()) .where(QString("%1 AND %2 AND %3") .arg(whereTime) .arg(whereProcessName) .arg(whereUpdataTime)); qry.prepare(); QJsonObject jsonObject = qry.exec(); QJsonArray jsonArray = qry.toJson(); for (const auto &value : jsonArray) { const QJsonObject object = value.toObject(); const QString processName = object["processName"].toString(); const qint64 runTime = object["exitTime"].toVariant().toLongLong() - object["creationTime"].toVariant().toLongLong(); const QString text = QString(tr("Program %1 has been used for %2 minutes%3") .arg(zhName) .arg(convertSecondsToMinutesTimeFormat(runTime)) .arg(messageText.toString())); QTemporaryFile tempFile("tempfile_XXXXXX.png"); tempFile.setAutoRemove(true); tempFile.open(); AppEvent::captureDesktop(&tempFile, "PNG"); // qDebug() << "发送截图" << tempFile.fileName(); TC::ProcessImageApi processImageApi; qDebug() << "发送截图" << processImageApi.post({tempFile.fileName()}); tempFile.close(); qDebug() << "发送截图" << tempFile.remove() << tempFile.errorString(); const QString message = gameMessageText.toString().isEmpty() ? text : gameMessageText.toString(); emit messageBox(message, messageBoxPointX, messageBoxPointY, messageTitle); QProcess p; p.startDetached("taskkill", {"/im", name, "/f"}); p.waitForFinished(3000); p.close(); // 更新 lastAlertTime ProcessModel processModel{storage}; processModel.buildFromJson(object); // 更新 消息时间 processModel.setLastAlertTime(updataTime); processModel.save(); } } } } } void ProcessThread::sendToServer(const QSet &filter, qint64 updataTime) { ProcessModel processModel{storage}; // 在数据库 获取 不是这个更新日期的数据 CWF::SqlQueryManager qry(storage); qry.select("*", processModel.getTableName()) .where(QString("updataTime != '%1' OR updataTime IS NULL").arg(updataTime)); qry.prepare(); QJsonObject jsonObject = qry.exec(); QJsonArray jsonArray = qry.toJson(); QJsonArray sendJsonArray; if (jsonArray.size() > 0) { for (const auto &json : jsonArray) { const QJsonObject object = json.toObject(); const QString name = object["processName"].toString(); QJsonObject sendObject; sendObject.insert("pid", object["pid"]); sendObject.insert("pid_name", object["processName"]); sendObject.insert("begin_time", object["creationTime"]); sendObject.insert("end_time", object["exitTime"]); sendObject.insert("last_check_time", object["updataTime"]); sendObject.insert("status", 1); sendObject.insert("notes", ""); // 存在过滤里面的 不发送数据 if (!filter.contains(name)) { sendJsonArray.append(sendObject); } } } // 上传 后 删除 if (sendJsonArray.size() > 0) { TC::ProcessApi processApi(sendJsonArray); bool isSendok = processApi.post(); if (isSendok) { // 上传 成功会 推送 并清理本地数据 // 移除发送到服务器的本地数据 CWF::SqlQueryManager qry(storage); qry.remove(processModel.getTableName(), QString("updataTime != '%1' OR updataTime IS NULL").arg(updataTime)); qry.prepare(); QJsonObject jsonObject = qry.exec(); QJsonArray jsonArray = qry.toJson(); } } } bool ProcessThread::upDataProcessSql() { std::vector> timeProcessVector = processMonitor.checkProcesses(); // 退出时间默认当前时间 然后更新 auto exitTimestamp = QDateTime::currentSecsSinceEpoch(); auto updataTimestamp = QDateTime::currentSecsSinceEpoch(); QStringList sqlValues; for (auto timeProcess : timeProcessVector) { qint64 timestamp = fileTimeToUnixTimestamp(timeProcess->creationTime); ProcessModel processModel{storage}; CWF::SqlQueryManager qry(storage); qry.select("*", processModel.getTableName()) .where(QString("pid == '%1' AND processName == '%2'") .arg(timeProcess->pid) .arg(timeProcess->processName.c_str())); qry.prepare(); QJsonObject jsonObject = qry.exec(); if (jsonObject["success"].toBool()) { // 查询或者替换 ? QJsonArray array = qry.toJson(); if (array.size() > 0) { // 替换 for (const QJsonValue &info : array) { const QJsonObject object = info.toObject(); //数据还原到 结构体 processModel.buildFromJson(object); // 更新 结束时间 processModel.setExitTime(exitTimestamp); processModel.setUpdataTime(updataTimestamp); processModel.save(); } } else { // 插入数据 processModel.setProcessName(timeProcess->processName.c_str()); processModel.setPid(timeProcess->pid); processModel.setCreationTime(timestamp); processModel.setExitTime(exitTimestamp); processModel.setUpdataTime(updataTimestamp); processModel.setLastAlertTime(updataTimestamp); processModel.save(); } } } // 发送更新后的数据 sendExitTime(updataTimestamp); return false; } ProcessThread::ProcessThread(QObject *parent) : QThread{parent} , messageBoxPointX(QVariant()) , messageBoxPointY(QVariant()) , messageText(QVariant("")) , gameMessageText(QVariant("")) , messageTitle(QVariant(Tr::tr("Tips"))) {} bool ProcessThread::serverConfig() { QSettings settings(QSettings::NativeFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName()); settings.beginGroup("config"); // 获取配置 // QVariant messageBoxPointX = QVariant(); // QVariant messageBoxPointY = QVariant(); // QVariant messageText = QVariant(""); // QVariant gameMessageText = QVariant(""); // QVariant messageTitle = QVariant(Tr::tr("Tips")); { TC::ConfigApi configApi("messageBoxPointX"); std::optional value = configApi.get(); if (value.has_value()) { messageBoxPointX = value.value(); settings.setValue("messageBoxPointX", messageBoxPointX); } else { if (settings.contains("messageBoxPointX")) { messageBoxPointX = settings.value("messageBoxPointX"); } } } { TC::ConfigApi configApi("messageBoxPointY"); std::optional value = configApi.get(); if (value.has_value()) { messageBoxPointY = value.value(); settings.setValue("messageBoxPointY", messageBoxPointY); } else { if (settings.contains("messageBoxPointX")) { messageBoxPointY = settings.value("messageBoxPointY"); } } } { TC::ConfigApi configApi("messageText"); std::optional value = configApi.get(); if (value.has_value()) { messageText = value.value(); settings.setValue("messageText", messageText); } else { if (settings.contains("messageText")) { messageText = settings.value("messageText"); } else { messageText = ", 请运动一下"; } } } { TC::ConfigApi configApi("messageTitle"); std::optional value = configApi.get(); if (value.has_value()) { messageTitle = value.value(); settings.setValue("messageTitle", messageTitle); } else { if (settings.contains("messageTitle")) { messageTitle = settings.value("messageTitle"); } else { messageTitle = "提示"; } } } { TC::ConfigApi configApi("gameMessageText"); std::optional value = configApi.get(); if (value.has_value()) { gameMessageText = value.value(); settings.setValue("gameMessageText", gameMessageText); } else { if (settings.contains("gameMessageText")) { gameMessageText = settings.value("gameMessageText"); } else { gameMessageText = "请不要玩游戏"; } } } settings.endGroup(); return true; }; void ProcessThread::run() { ProcessModel processModel{storage}; processModel.updateDB(); { const QString query = QString("CREATE UNIQUE INDEX %2_%3_unique ON %1 (%2, %3);") .arg(processModel.getTableName()) .arg("pid") .arg("processName"); CWF::SqlQuery qry(storage); qry.exec(query); } QElapsedTimer timer; // 创建高精度计时器 timer.start(); // 启动计时器 upDataProcessSql(); while (true) { // 校验网络 if (timer.elapsed() >= 10 * 1000) { // 检查是否经过1分钟 timer.restart(); // 重新启动计时器 upDataProcessSql(); } msleep(1000); // 休息1秒 } }