processthread.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  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. } else {
  146. // 写入本地配置
  147. QSettings settings(QSettings::NativeFormat,
  148. QSettings::UserScope,
  149. QCoreApplication::organizationName(),
  150. QCoreApplication::applicationName());
  151. settings.beginGroup("config");
  152. settings.setValue("serverArray",
  153. QString(QJsonDocument(serverArray).toJson(QJsonDocument::Compact)));
  154. settings.endGroup();
  155. }
  156. return serverArray;
  157. }
  158. void ProcessThread::sendExitTime(qint64 updataTime)
  159. {
  160. serverConfig();
  161. QJsonArray serverArray = processNameArray();
  162. // 获取过滤名单
  163. QSet<QString> filter;
  164. QSet<QString> gameFilter;
  165. for (const QJsonValue &item : serverArray) {
  166. if (!item.isObject()) {
  167. return;
  168. }
  169. const QJsonObject object = item.toObject();
  170. const QString name = object["name"].toString();
  171. if (object.contains("is_confirmed")) {
  172. if (object["is_confirmed"].toInt() == 2) {
  173. filter.insert(name);
  174. }
  175. }
  176. }
  177. // 发送数据到服务器
  178. sendToServer(filter, updataTime);
  179. ProcessModel processModel{storage};
  180. // 获取服务器 对应的时间信息
  181. for (const QJsonValue &item : serverArray) {
  182. if (!item.isObject()) {
  183. return;
  184. }
  185. const QJsonObject object = item.toObject();
  186. const QString name = object["name"].toString();
  187. const QString zhName = object["chinese_name"].toString();
  188. const QString type = object["pid_type"].toString();
  189. // 2 提示 1 不提示 0 未知
  190. if (object.contains("is_prompt")) { // 提示
  191. if (object["is_prompt"].toInt() == 2) {
  192. if (type == "游戏" || type == "game") {
  193. } else {
  194. // const QString name = object["name"].toString();
  195. // const QString zhName = object["chinese_name"].toString();
  196. const int time = object["prompt_time"].toInt();
  197. if (time > 0) {
  198. const QString whereTime = QString("(updataTime - lastAlertTime) > %1")
  199. .arg(time);
  200. const QString whereProcessName = QString("processName = '%1'").arg(name);
  201. const QString whereUpdataTime = QString("updataTime = '%1'").arg(updataTime);
  202. CWF::SqlQueryManager qry(storage);
  203. qry.select("*", processModel.getTableName())
  204. .where(QString("%1 AND %2 AND %3")
  205. .arg(whereTime)
  206. .arg(whereProcessName)
  207. .arg(whereUpdataTime));
  208. qry.prepare();
  209. QJsonObject jsonObject = qry.exec();
  210. QJsonArray jsonArray = qry.toJson();
  211. qDebug() << jsonArray;
  212. for (const auto &value : jsonArray) {
  213. const QJsonObject object = value.toObject();
  214. const QString processName = object["processName"].toString();
  215. const qint64 runTime = object["exitTime"].toVariant().toLongLong()
  216. - object["creationTime"].toVariant().toLongLong();
  217. const QString text = QString(
  218. tr("Program %1 has been used for %2 minutes%3")
  219. .arg(zhName)
  220. .arg(convertSecondsToMinutesTimeFormat(runTime))
  221. .arg(messageText.toString()));
  222. emit messageBox(text, messageBoxPointX, messageBoxPointY, messageTitle);
  223. // 更新 lastAlertTime
  224. ProcessModel processModel{storage};
  225. processModel.buildFromJson(object);
  226. // 更新 消息时间
  227. processModel.setLastAlertTime(updataTime);
  228. processModel.save();
  229. }
  230. }
  231. }
  232. }
  233. }
  234. if (object.contains("pid_type")) {
  235. if (type == "游戏" || type == "game") {
  236. const QString whereTime = QString("(updataTime - lastAlertTime) > %1").arg(1);
  237. const QString whereProcessName = QString("processName = '%1'").arg(name);
  238. const QString whereUpdataTime = QString("updataTime = '%1'").arg(updataTime);
  239. CWF::SqlQueryManager qry(storage);
  240. qry.select("*", processModel.getTableName())
  241. .where(QString("%1 AND %2 AND %3")
  242. .arg(whereTime)
  243. .arg(whereProcessName)
  244. .arg(whereUpdataTime));
  245. qry.prepare();
  246. QJsonObject jsonObject = qry.exec();
  247. QJsonArray jsonArray = qry.toJson();
  248. for (const auto &value : jsonArray) {
  249. const QJsonObject object = value.toObject();
  250. const QString processName = object["processName"].toString();
  251. const qint64 runTime = object["exitTime"].toVariant().toLongLong()
  252. - object["creationTime"].toVariant().toLongLong();
  253. const QString text = QString(tr("Program %1 has been used for %2 minutes%3")
  254. .arg(zhName)
  255. .arg(convertSecondsToMinutesTimeFormat(runTime))
  256. .arg(messageText.toString()));
  257. QTemporaryFile tempFile("tempfile_XXXXXX.png");
  258. tempFile.setAutoRemove(true);
  259. tempFile.open();
  260. AppEvent::captureDesktop(&tempFile, "PNG");
  261. // qDebug() << "发送截图" << tempFile.fileName();
  262. TC::ProcessImageApi processImageApi;
  263. qDebug() << "发送截图" << processImageApi.post({tempFile.fileName()});
  264. tempFile.close();
  265. qDebug() << "发送截图" << tempFile.remove() << tempFile.errorString();
  266. const QString message = gameMessageText.toString().isEmpty()
  267. ? text
  268. : gameMessageText.toString();
  269. emit messageBox(message, messageBoxPointX, messageBoxPointY, messageTitle);
  270. QProcess p;
  271. p.startDetached("taskkill", {"/im", name, "/f"});
  272. p.waitForFinished(3000);
  273. p.close();
  274. // 更新 lastAlertTime
  275. ProcessModel processModel{storage};
  276. processModel.buildFromJson(object);
  277. // 更新 消息时间
  278. processModel.setLastAlertTime(updataTime);
  279. processModel.save();
  280. }
  281. }
  282. }
  283. }
  284. }
  285. void ProcessThread::sendToServer(const QSet<QString> &filter, qint64 updataTime)
  286. {
  287. ProcessModel processModel{storage};
  288. // 在数据库 获取 不是这个更新日期的数据
  289. CWF::SqlQueryManager qry(storage);
  290. qry.select("*", processModel.getTableName())
  291. .where(QString("updataTime != '%1' OR updataTime IS NULL").arg(updataTime));
  292. qry.prepare();
  293. QJsonObject jsonObject = qry.exec();
  294. QJsonArray jsonArray = qry.toJson();
  295. QJsonArray sendJsonArray;
  296. if (jsonArray.size() > 0) {
  297. for (const auto &json : jsonArray) {
  298. const QJsonObject object = json.toObject();
  299. const QString name = object["processName"].toString();
  300. QJsonObject sendObject;
  301. sendObject.insert("pid", object["pid"]);
  302. sendObject.insert("pid_name", object["processName"]);
  303. sendObject.insert("begin_time", object["creationTime"]);
  304. sendObject.insert("end_time", object["exitTime"]);
  305. sendObject.insert("last_check_time", object["updataTime"]);
  306. sendObject.insert("status", 1);
  307. sendObject.insert("notes", "");
  308. // 存在过滤里面的 不发送数据
  309. if (!filter.contains(name)) {
  310. sendJsonArray.append(sendObject);
  311. }
  312. }
  313. }
  314. // 上传 后 删除
  315. if (sendJsonArray.size() > 0) {
  316. TC::ProcessApi processApi(sendJsonArray);
  317. bool isSendok = processApi.post();
  318. if (isSendok) {
  319. // 上传 成功会 推送 并清理本地数据
  320. // 移除发送到服务器的本地数据
  321. CWF::SqlQueryManager qry(storage);
  322. qry.remove(processModel.getTableName(),
  323. QString("updataTime != '%1' OR updataTime IS NULL").arg(updataTime));
  324. qry.prepare();
  325. QJsonObject jsonObject = qry.exec();
  326. QJsonArray jsonArray = qry.toJson();
  327. }
  328. }
  329. }
  330. bool ProcessThread::upDataProcessSql()
  331. {
  332. std::vector<std::shared_ptr<ProcessMonitor::ProcessInfo>> timeProcessVector
  333. = processMonitor.checkProcesses();
  334. // 退出时间默认当前时间 然后更新
  335. auto exitTimestamp = QDateTime::currentSecsSinceEpoch();
  336. auto updataTimestamp = QDateTime::currentSecsSinceEpoch();
  337. QStringList sqlValues;
  338. for (auto timeProcess : timeProcessVector) {
  339. qint64 timestamp = fileTimeToUnixTimestamp(timeProcess->creationTime);
  340. ProcessModel processModel{storage};
  341. CWF::SqlQueryManager qry(storage);
  342. qry.select("*", processModel.getTableName())
  343. .where(QString("pid == '%1' AND processName == '%2'")
  344. .arg(timeProcess->pid)
  345. .arg(timeProcess->processName.c_str()));
  346. qry.prepare();
  347. QJsonObject jsonObject = qry.exec();
  348. if (jsonObject["success"].toBool()) {
  349. // 查询或者替换 ?
  350. QJsonArray array = qry.toJson();
  351. if (array.size() > 0) {
  352. // 替换
  353. for (const QJsonValue &info : array) {
  354. const QJsonObject object = info.toObject();
  355. //数据还原到 结构体
  356. processModel.buildFromJson(object);
  357. // 更新 结束时间
  358. processModel.setExitTime(exitTimestamp);
  359. processModel.setUpdataTime(updataTimestamp);
  360. processModel.save();
  361. }
  362. } else {
  363. // 插入数据
  364. processModel.setProcessName(timeProcess->processName.c_str());
  365. processModel.setPid(timeProcess->pid);
  366. processModel.setCreationTime(timestamp);
  367. processModel.setExitTime(exitTimestamp);
  368. processModel.setUpdataTime(updataTimestamp);
  369. processModel.setLastAlertTime(updataTimestamp);
  370. processModel.save();
  371. }
  372. }
  373. }
  374. // 发送更新后的数据
  375. sendExitTime(updataTimestamp);
  376. return false;
  377. }
  378. ProcessThread::ProcessThread(QObject *parent)
  379. : QThread{parent}
  380. , messageBoxPointX(QVariant())
  381. , messageBoxPointY(QVariant())
  382. , messageText(QVariant(""))
  383. , gameMessageText(QVariant(""))
  384. , messageTitle(QVariant(Tr::tr("Tips")))
  385. {}
  386. bool ProcessThread::serverConfig()
  387. {
  388. QSettings settings(QSettings::NativeFormat,
  389. QSettings::UserScope,
  390. QCoreApplication::organizationName(),
  391. QCoreApplication::applicationName());
  392. settings.beginGroup("config");
  393. // 获取配置
  394. // QVariant messageBoxPointX = QVariant();
  395. // QVariant messageBoxPointY = QVariant();
  396. // QVariant messageText = QVariant("");
  397. // QVariant gameMessageText = QVariant("");
  398. // QVariant messageTitle = QVariant(Tr::tr("Tips"));
  399. {
  400. TC::ConfigApi configApi("messageBoxPointX");
  401. std::optional<QVariant> value = configApi.get();
  402. if (value.has_value()) {
  403. messageBoxPointX = value.value();
  404. settings.setValue("messageBoxPointX", messageBoxPointX);
  405. } else {
  406. if (settings.contains("messageBoxPointX")) {
  407. messageBoxPointX = settings.value("messageBoxPointX");
  408. }
  409. }
  410. }
  411. {
  412. TC::ConfigApi configApi("messageBoxPointY");
  413. std::optional<QVariant> value = configApi.get();
  414. if (value.has_value()) {
  415. messageBoxPointY = value.value();
  416. settings.setValue("messageBoxPointY", messageBoxPointY);
  417. } else {
  418. if (settings.contains("messageBoxPointX")) {
  419. messageBoxPointY = settings.value("messageBoxPointY");
  420. }
  421. }
  422. }
  423. {
  424. TC::ConfigApi configApi("messageText");
  425. std::optional<QVariant> value = configApi.get();
  426. if (value.has_value()) {
  427. messageText = value.value();
  428. settings.setValue("messageText", messageText);
  429. } else {
  430. if (settings.contains("messageText")) {
  431. messageText = settings.value("messageText");
  432. } else {
  433. messageText = ", 请运动一下";
  434. }
  435. }
  436. }
  437. {
  438. TC::ConfigApi configApi("messageTitle");
  439. std::optional<QVariant> value = configApi.get();
  440. if (value.has_value()) {
  441. messageTitle = value.value();
  442. settings.setValue("messageTitle", messageTitle);
  443. } else {
  444. if (settings.contains("messageTitle")) {
  445. messageTitle = settings.value("messageTitle");
  446. } else {
  447. messageTitle = "提示";
  448. }
  449. }
  450. }
  451. {
  452. TC::ConfigApi configApi("gameMessageText");
  453. std::optional<QVariant> value = configApi.get();
  454. if (value.has_value()) {
  455. gameMessageText = value.value();
  456. settings.setValue("gameMessageText", gameMessageText);
  457. } else {
  458. if (settings.contains("gameMessageText")) {
  459. gameMessageText = settings.value("gameMessageText");
  460. } else {
  461. gameMessageText = "请不要玩游戏";
  462. }
  463. }
  464. }
  465. settings.endGroup();
  466. return true;
  467. };
  468. void ProcessThread::run()
  469. {
  470. ProcessModel processModel{storage};
  471. processModel.updateDB();
  472. {
  473. const QString query = QString("CREATE UNIQUE INDEX %2_%3_unique ON %1 (%2, %3);")
  474. .arg(processModel.getTableName())
  475. .arg("pid")
  476. .arg("processName");
  477. CWF::SqlQuery qry(storage);
  478. qry.exec(query);
  479. }
  480. QElapsedTimer timer; // 创建高精度计时器
  481. timer.start(); // 启动计时器
  482. upDataProcessSql();
  483. while (true) {
  484. // 校验网络
  485. if (timer.elapsed() >= 10 * 1000) { // 检查是否经过1分钟
  486. timer.restart(); // 重新启动计时器
  487. upDataProcessSql();
  488. }
  489. msleep(1000); // 休息1秒
  490. }
  491. }