processthread.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. #include "processthread.h"
  2. #include <QProcess>
  3. #include <QSettings>
  4. #include <QTemporaryFile>
  5. #include "api/configapi.h"
  6. #include "appevent.h"
  7. #include "processmodel.h"
  8. #include "qjsonarray.h"
  9. #include "qjsonobject.h"
  10. #include <cwf/sqldatabasestorage.h>
  11. #include <optional>
  12. #include "api/processapi.h"
  13. #include "qjsonvalue.h"
  14. #include "qlist.h"
  15. #include "qmessagebox.h"
  16. #include "qnamespace.h"
  17. #include "basemainTr.h"
  18. CWF::SqlDatabaseStorage storage("QSQLITE", "localhost", "postgres", "postgres", "1234", 5432);
  19. // 将 FILETIME 转换为 Unix 时间戳
  20. static qint64 fileTimeToUnixTimestamp(FILETIME ft)
  21. {
  22. // FILETIME 是自 1601 年以来的 100 毫微秒数
  23. ULARGE_INTEGER ull;
  24. ull.LowPart = ft.dwLowDateTime;
  25. ull.HighPart = ft.dwHighDateTime;
  26. // FILETIME 和 时间戳的 基础时间不一样
  27. // 转换为秒,并减去 1601 到 1970 年之间的秒数
  28. if (ull.QuadPart / 10000000LL > 11644473600LL) {
  29. qint64 timestamp = ull.QuadPart / 10000000LL - 11644473600LL;
  30. return timestamp;
  31. }
  32. return ull.QuadPart / 10000000LL;
  33. }
  34. QString convertSecondsToTimeFormat(int totalSeconds)
  35. {
  36. int days = totalSeconds / (24 * 3600); // 计算天数
  37. int hours = (totalSeconds % (24 * 3600)) / 3600; // 计算小时
  38. int minutes = (totalSeconds % 3600) / 60; // 计算分钟
  39. int seconds = totalSeconds % 60; // 计算秒数
  40. // 返回格式化后的字符串,并使用 tr() 标记可翻译的文本
  41. return QString(Tr::tr("%1days%2hours%3minutes%4seconds"))
  42. .arg(days)
  43. .arg(hours)
  44. .arg(minutes)
  45. .arg(seconds);
  46. }
  47. ///
  48. /// \brief 转换到分钟
  49. /// \param totalSeconds
  50. /// \return
  51. ///
  52. QString convertSecondsToMinutesTimeFormat(int totalSeconds)
  53. {
  54. // 只计算分钟
  55. int minutes = totalSeconds / 60;
  56. return QString::number(minutes);
  57. }
  58. static QVariant config()
  59. {
  60. QSettings settings(QSettings::NativeFormat,
  61. QSettings::UserScope,
  62. QCoreApplication::organizationName(),
  63. QCoreApplication::applicationName());
  64. settings.beginGroup("config");
  65. QVariant value;
  66. if (settings.contains("serverConfig")) {
  67. value = settings.value("serverConfig");
  68. } else {
  69. value = R"Raw([
  70. {
  71. "uuid": "3d8a0525-76fa-45bb-b0db-ed7260e31bdc",
  72. "name": "sldworks.exe",
  73. "chinese_name": "SolidWorks",
  74. "pid_type": "学习",
  75. "is_confirmed": 1,
  76. "is_prompt": 2,
  77. "prompt_type": "",
  78. "prompt_time": 2400,
  79. "path": "",
  80. "notes": "",
  81. "c_time": 1734346737,
  82. "u_time": 0
  83. },
  84. {
  85. "uuid": "daa98539-fb91-4eb6-a59f-0fd4509e926c",
  86. "name": "sldworks_fs.exe",
  87. "chinese_name": "SolidWorks",
  88. "pid_type": "学习",
  89. "is_confirmed": 2,
  90. "is_prompt": 1,
  91. "prompt_type": "",
  92. "prompt_time": 120,
  93. "path": "",
  94. "notes": "",
  95. "c_time": 1733906424,
  96. "u_time": 0
  97. },
  98. {
  99. "uuid": "d09df0b1-2386-4527-afa7-3f1fb37b6171",
  100. "name": "SLDWORKS.exe",
  101. "chinese_name": "SolidWorks",
  102. "pid_type": "学习",
  103. "is_confirmed": 1,
  104. "is_prompt": 2,
  105. "prompt_type": "",
  106. "prompt_time": 2400,
  107. "path": "",
  108. "notes": "",
  109. "c_time": 1733652641,
  110. "u_time": 0
  111. },
  112. {
  113. "uuid": "c4de744f-0afe-4e0f-a39e-7ee59f46c042",
  114. "name": "WeChat.exe",
  115. "chinese_name": "微信",
  116. "pid_type": "日常",
  117. "is_confirmed": 1,
  118. "is_prompt": 2,
  119. "prompt_type": "",
  120. "prompt_time": 15,
  121. "path": "2",
  122. "notes": "测试",
  123. "c_time": 1733455089,
  124. "u_time": 0
  125. }
  126. ])Raw";
  127. }
  128. settings.endGroup();
  129. return value;
  130. }
  131. const QJsonArray processNameArray()
  132. {
  133. TC::ProcessNameApi processNameApi;
  134. QJsonArray serverArray = processNameApi.get();
  135. if (serverArray.isEmpty()) {
  136. // 读取本地配置 转 QJsonArray
  137. const QString configText = config().toString();
  138. QJsonDocument jsonDoc = QJsonDocument::fromJson(configText.toUtf8());
  139. if (jsonDoc.isArray()) {
  140. serverArray = jsonDoc.array();
  141. } else if (jsonDoc.isObject()) {
  142. // If the config is a single object, we can wrap it in an array
  143. serverArray.append(jsonDoc.object());
  144. }
  145. qDebug() << "use Local config";
  146. } else {
  147. // 写入本地配置
  148. QSettings settings(QSettings::NativeFormat,
  149. QSettings::UserScope,
  150. QCoreApplication::organizationName(),
  151. QCoreApplication::applicationName());
  152. settings.beginGroup("config");
  153. settings.setValue("serverArray",
  154. QString(QJsonDocument(serverArray).toJson(QJsonDocument::Compact)));
  155. settings.endGroup();
  156. qDebug() << "use server config";
  157. }
  158. return serverArray;
  159. }
  160. void ProcessThread::sendExitTime(qint64 updataTime)
  161. {
  162. serverConfig();
  163. QJsonArray serverArray = processNameArray();
  164. // 获取过滤名单
  165. QSet<QString> filter;
  166. QSet<QString> gameFilter;
  167. for (const QJsonValue &item : serverArray) {
  168. if (!item.isObject()) {
  169. return;
  170. }
  171. const QJsonObject object = item.toObject();
  172. const QString name = object["name"].toString();
  173. if (object.contains("is_confirmed")) {
  174. if (object["is_confirmed"].toInt() == 2) {
  175. filter.insert(name);
  176. }
  177. }
  178. }
  179. // 发送数据到服务器
  180. sendToServer(filter, updataTime);
  181. ProcessModel processModel{storage};
  182. // 获取服务器 对应的时间信息
  183. for (const QJsonValue &item : serverArray) {
  184. if (!item.isObject()) {
  185. return;
  186. }
  187. const QJsonObject object = item.toObject();
  188. const QString name = object["name"].toString();
  189. const QString zhName = object["chinese_name"].toString();
  190. const QString type = object["pid_type"].toString();
  191. // 2 提示 1 不提示 0 未知
  192. if (object.contains("is_prompt")) { // 提示
  193. if (object["is_prompt"].toInt() == 2) {
  194. if (type == "游戏" || type == "game") {
  195. } else {
  196. // const QString name = object["name"].toString();
  197. // const QString zhName = object["chinese_name"].toString();
  198. const int time = object["prompt_time"].toInt();
  199. if (time > 0) {
  200. const QString whereTime = QString("(updataTime - lastAlertTime) > %1")
  201. .arg(time);
  202. const QString whereProcessName = QString("processName = '%1'").arg(name);
  203. const QString whereUpdataTime = QString("updataTime = '%1'").arg(updataTime);
  204. CWF::SqlQueryManager qry(storage);
  205. qry.select("*", processModel.getTableName())
  206. .where(QString("%1 AND %2 AND %3")
  207. .arg(whereTime)
  208. .arg(whereProcessName)
  209. .arg(whereUpdataTime));
  210. qry.prepare();
  211. QJsonObject jsonObject = qry.exec();
  212. QJsonArray jsonArray = qry.toJson();
  213. qDebug() << jsonArray;
  214. for (const auto &value : jsonArray) {
  215. const QJsonObject object = value.toObject();
  216. const QString processName = object["processName"].toString();
  217. const qint64 runTime = object["exitTime"].toVariant().toLongLong()
  218. - object["creationTime"].toVariant().toLongLong();
  219. const QString text = QString(
  220. tr("Program %1 has been used for %2 minutes%3")
  221. .arg(zhName)
  222. .arg(convertSecondsToMinutesTimeFormat(runTime))
  223. .arg(messageText.toString()));
  224. emit messageBox(text, messageBoxPointX, messageBoxPointY, messageTitle);
  225. // 更新 lastAlertTime
  226. ProcessModel processModel{storage};
  227. processModel.buildFromJson(object);
  228. // 更新 消息时间
  229. processModel.setLastAlertTime(updataTime);
  230. processModel.save();
  231. }
  232. }
  233. }
  234. }
  235. }
  236. if (object.contains("pid_type")) {
  237. if (type == "游戏" || type == "game") {
  238. const QString whereTime = QString("(updataTime - lastAlertTime) > %1").arg(1);
  239. const QString whereProcessName = QString("processName = '%1'").arg(name);
  240. const QString whereUpdataTime = QString("updataTime = '%1'").arg(updataTime);
  241. CWF::SqlQueryManager qry(storage);
  242. qry.select("*", processModel.getTableName())
  243. .where(QString("%1 AND %2 AND %3")
  244. .arg(whereTime)
  245. .arg(whereProcessName)
  246. .arg(whereUpdataTime));
  247. qry.prepare();
  248. QJsonObject jsonObject = qry.exec();
  249. QJsonArray jsonArray = qry.toJson();
  250. for (const auto &value : jsonArray) {
  251. const QJsonObject object = value.toObject();
  252. const QString processName = object["processName"].toString();
  253. const qint64 runTime = object["exitTime"].toVariant().toLongLong()
  254. - object["creationTime"].toVariant().toLongLong();
  255. const QString text = QString(tr("Program %1 has been used for %2 minutes%3")
  256. .arg(zhName)
  257. .arg(convertSecondsToMinutesTimeFormat(runTime))
  258. .arg(messageText.toString()));
  259. QTemporaryFile tempFile("tempfile_XXXXXX.png");
  260. tempFile.setAutoRemove(true);
  261. tempFile.open();
  262. AppEvent::captureDesktop(&tempFile, "PNG");
  263. // qDebug() << "发送截图" << tempFile.fileName();
  264. TC::ProcessImageApi processImageApi;
  265. qDebug() << "发送截图" << processImageApi.post({tempFile.fileName()});
  266. tempFile.close();
  267. qDebug() << "发送截图" << tempFile.remove() << tempFile.errorString();
  268. const QString message = gameMessageText.toString().isEmpty()
  269. ? text
  270. : gameMessageText.toString();
  271. emit messageBox(message, messageBoxPointX, messageBoxPointY, messageTitle);
  272. QProcess p;
  273. p.startDetached("taskkill", {"/im", name, "/f"});
  274. p.waitForFinished(3000);
  275. p.close();
  276. // 更新 lastAlertTime
  277. ProcessModel processModel{storage};
  278. processModel.buildFromJson(object);
  279. // 更新 消息时间
  280. processModel.setLastAlertTime(updataTime);
  281. processModel.save();
  282. }
  283. }
  284. }
  285. }
  286. }
  287. void ProcessThread::sendToServer(const QSet<QString> &filter, qint64 updataTime)
  288. {
  289. ProcessModel processModel{storage};
  290. // 在数据库 获取 不是这个更新日期的数据
  291. CWF::SqlQueryManager qry(storage);
  292. qry.select("*", processModel.getTableName())
  293. .where(QString("updataTime != '%1' OR updataTime IS NULL").arg(updataTime));
  294. qry.prepare();
  295. QJsonObject jsonObject = qry.exec();
  296. QJsonArray jsonArray = qry.toJson();
  297. QJsonArray sendJsonArray;
  298. if (jsonArray.size() > 0) {
  299. for (const auto &json : jsonArray) {
  300. const QJsonObject object = json.toObject();
  301. const QString name = object["processName"].toString();
  302. QJsonObject sendObject;
  303. sendObject.insert("pid", object["pid"]);
  304. sendObject.insert("pid_name", object["processName"]);
  305. sendObject.insert("begin_time", object["creationTime"]);
  306. sendObject.insert("end_time", object["exitTime"]);
  307. sendObject.insert("last_check_time", object["updataTime"]);
  308. sendObject.insert("status", 1);
  309. sendObject.insert("notes", "");
  310. // 存在过滤里面的 不发送数据
  311. if (!filter.contains(name)) {
  312. sendJsonArray.append(sendObject);
  313. }
  314. }
  315. }
  316. // 上传 后 删除
  317. if (sendJsonArray.size() > 0) {
  318. TC::ProcessApi processApi(sendJsonArray);
  319. bool isSendok = processApi.post();
  320. if (isSendok) {
  321. // 上传 成功会 推送 并清理本地数据
  322. // 移除发送到服务器的本地数据
  323. CWF::SqlQueryManager qry(storage);
  324. qry.remove(processModel.getTableName(),
  325. QString("updataTime != '%1' OR updataTime IS NULL").arg(updataTime));
  326. qry.prepare();
  327. QJsonObject jsonObject = qry.exec();
  328. QJsonArray jsonArray = qry.toJson();
  329. }
  330. }
  331. }
  332. bool ProcessThread::upDataProcessSql()
  333. {
  334. std::vector<std::shared_ptr<ProcessMonitor::ProcessInfo>> timeProcessVector
  335. = processMonitor.checkProcesses();
  336. // 退出时间默认当前时间 然后更新
  337. auto exitTimestamp = QDateTime::currentSecsSinceEpoch();
  338. auto updataTimestamp = QDateTime::currentSecsSinceEpoch();
  339. QStringList sqlValues;
  340. for (auto timeProcess : timeProcessVector) {
  341. qint64 timestamp = fileTimeToUnixTimestamp(timeProcess->creationTime);
  342. ProcessModel processModel{storage};
  343. CWF::SqlQueryManager qry(storage);
  344. qry.select("*", processModel.getTableName())
  345. .where(QString("pid == '%1' AND processName == '%2'")
  346. .arg(timeProcess->pid)
  347. .arg(timeProcess->processName.c_str()));
  348. qry.prepare();
  349. QJsonObject jsonObject = qry.exec();
  350. if (jsonObject["success"].toBool()) {
  351. // 查询或者替换 ?
  352. QJsonArray array = qry.toJson();
  353. if (array.size() > 0) {
  354. // 替换
  355. for (const QJsonValue &info : array) {
  356. const QJsonObject object = info.toObject();
  357. //数据还原到 结构体
  358. processModel.buildFromJson(object);
  359. // 更新 结束时间
  360. processModel.setExitTime(exitTimestamp);
  361. processModel.setUpdataTime(updataTimestamp);
  362. processModel.save();
  363. }
  364. } else {
  365. // 插入数据
  366. processModel.setProcessName(timeProcess->processName.c_str());
  367. processModel.setPid(timeProcess->pid);
  368. processModel.setCreationTime(timestamp);
  369. processModel.setExitTime(exitTimestamp);
  370. processModel.setUpdataTime(updataTimestamp);
  371. processModel.setLastAlertTime(updataTimestamp);
  372. processModel.save();
  373. }
  374. }
  375. }
  376. // 发送更新后的数据
  377. sendExitTime(updataTimestamp);
  378. return false;
  379. }
  380. ProcessThread::ProcessThread(QObject *parent)
  381. : QThread{parent}
  382. , messageBoxPointX(QVariant())
  383. , messageBoxPointY(QVariant())
  384. , messageText(QVariant(""))
  385. , gameMessageText(QVariant(""))
  386. , messageTitle(QVariant(Tr::tr("Tips")))
  387. {}
  388. bool ProcessThread::serverConfig()
  389. {
  390. QSettings settings(QSettings::NativeFormat,
  391. QSettings::UserScope,
  392. QCoreApplication::organizationName(),
  393. QCoreApplication::applicationName());
  394. settings.beginGroup("config");
  395. // 获取配置
  396. // QVariant messageBoxPointX = QVariant();
  397. // QVariant messageBoxPointY = QVariant();
  398. // QVariant messageText = QVariant("");
  399. // QVariant gameMessageText = QVariant("");
  400. // QVariant messageTitle = QVariant(Tr::tr("Tips"));
  401. {
  402. TC::ConfigApi configApi("messageBoxPointX");
  403. std::optional<QVariant> value = configApi.get();
  404. if (value.has_value()) {
  405. messageBoxPointX = value.value();
  406. settings.setValue("messageBoxPointX", messageBoxPointX);
  407. } else {
  408. if (settings.contains("messageBoxPointX")) {
  409. messageBoxPointX = settings.value("messageBoxPointX");
  410. }
  411. }
  412. }
  413. {
  414. TC::ConfigApi configApi("messageBoxPointY");
  415. std::optional<QVariant> value = configApi.get();
  416. if (value.has_value()) {
  417. messageBoxPointY = value.value();
  418. settings.setValue("messageBoxPointY", messageBoxPointY);
  419. } else {
  420. if (settings.contains("messageBoxPointX")) {
  421. messageBoxPointY = settings.value("messageBoxPointY");
  422. }
  423. }
  424. }
  425. {
  426. TC::ConfigApi configApi("messageText");
  427. std::optional<QVariant> value = configApi.get();
  428. if (value.has_value()) {
  429. messageText = value.value();
  430. settings.setValue("messageText", messageText);
  431. } else {
  432. if (settings.contains("messageText")) {
  433. messageText = settings.value("messageText");
  434. } else {
  435. messageText = ", 请运动一下";
  436. }
  437. }
  438. }
  439. {
  440. TC::ConfigApi configApi("messageTitle");
  441. std::optional<QVariant> value = configApi.get();
  442. if (value.has_value()) {
  443. messageTitle = value.value();
  444. settings.setValue("messageTitle", messageTitle);
  445. } else {
  446. if (settings.contains("messageTitle")) {
  447. messageTitle = settings.value("messageTitle");
  448. } else {
  449. messageTitle = "提示";
  450. }
  451. }
  452. }
  453. {
  454. TC::ConfigApi configApi("gameMessageText");
  455. std::optional<QVariant> value = configApi.get();
  456. if (value.has_value()) {
  457. gameMessageText = value.value();
  458. settings.setValue("gameMessageText", gameMessageText);
  459. } else {
  460. if (settings.contains("gameMessageText")) {
  461. gameMessageText = settings.value("gameMessageText");
  462. } else {
  463. gameMessageText = "请不要玩游戏";
  464. }
  465. }
  466. }
  467. settings.endGroup();
  468. return true;
  469. };
  470. void ProcessThread::run()
  471. {
  472. ProcessModel processModel{storage};
  473. processModel.updateDB();
  474. {
  475. const QString query = QString("CREATE UNIQUE INDEX %2_%3_unique ON %1 (%2, %3);")
  476. .arg(processModel.getTableName())
  477. .arg("pid")
  478. .arg("processName");
  479. CWF::SqlQuery qry(storage);
  480. qry.exec(query);
  481. }
  482. QElapsedTimer timer; // 创建高精度计时器
  483. timer.start(); // 启动计时器
  484. upDataProcessSql();
  485. while (true) {
  486. // 校验网络
  487. if (timer.elapsed() >= 10 * 1000) { // 检查是否经过1分钟
  488. timer.restart(); // 重新启动计时器
  489. upDataProcessSql();
  490. }
  491. msleep(1000); // 休息1秒
  492. }
  493. }