From 5cb32ec4f160b8bde7801671ffa49c644e18f08e Mon Sep 17 00:00:00 2001 From: slava Date: Thu, 22 May 2025 21:33:25 +0700 Subject: [PATCH] Refactor and enhance device and event handling logic Refactored `DeviceScreener`, `NetworkService`, and event-related logic for improved maintainability and flexibility. Introduced `EventHandler` for thread management and made changes to include configurable URLs via `config.ini`. Modified database schema to support device-level event linking. --- CMakeLists.txt | 3 + config.ini | 14 +++- database/app_data.db | Bin 2621440 -> 2621440 bytes database/dao/DeviceDAO.cpp | 22 ++++++ database/dao/DeviceDAO.h | 2 + database/dao/EventDAO.cpp | 37 +++++++-- database/dao/EventDAO.h | 2 + main.cpp | 35 +++++---- migrations.sql | 2 + models/db/EventInfo.h | 1 + services/DeviceScreener.cpp | 20 +++-- services/DeviceScreener.h | 3 + services/EventHandler.cpp | 130 ++++++++++++++++++++++++++++++++ services/EventHandler.h | 36 +++++++++ services/net/NetworkService.cpp | 60 ++++++++------- services/net/NetworkService.h | 9 +++ 16 files changed, 322 insertions(+), 54 deletions(-) create mode 100644 services/EventHandler.cpp create mode 100644 services/EventHandler.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c5f75de..d8a708a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) +add_compile_definitions(QT_MESSAGELOGCONTEXT) + if (WIN32) # Укажи путь к Qt6 set(CMAKE_PREFIX_PATH "C:/Qt/6.9.0/msvc2022_arm64") @@ -75,6 +77,7 @@ if (WIN32) file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/database/app_data.db" DESTINATION "${CMAKE_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/images" DESTINATION "${CMAKE_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/rus.traineddata" DESTINATION "${CMAKE_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/config.ini" DESTINATION "${CMAKE_BINARY_DIR}") elseif (APPLE) target_link_libraries(ARCDesktopProject diff --git a/config.ini b/config.ini index e2efd37..551c814 100644 --- a/config.ini +++ b/config.ini @@ -1,6 +1,16 @@ +[common] +varsion=0.0.1 + [apps] list=rshb [rshb] -name=Рос Сельхоз Банк -android_package_name=ru.rshb.dbo \ No newline at end of file +name=Росcельхозбанк +android_package_name=ru.rshb.dbo + +[net] +appInfoUpdate="http://localhost:9999/api/v1/app/update" +accountUpdate=http://localhost:9999/api/v1/account/update +paymentsGet=http://localhost:9999/api/v1/payments +transactionUpdate=http://localhost:9999/api/v1/transaction/update +transactionInsert=http://localhost:9999/api/v1/transaction/insert \ No newline at end of file diff --git a/database/app_data.db b/database/app_data.db index ba7d920d5e94d50a06f2250a820cb7bcd21b7025..7f386179000f6bb20ae1c6eff4712498cdbc79a2 100644 GIT binary patch delta 1501 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

Hop%CeAK!gH?>xHk~ z4#nfI{?@-0w*{L`dmlKev=XPJ*v<(t+1Bm^X+&lO?HlIW@5lQyhHinahU%IA|6%7 z!894;9m>dh1U_=p*HM8clZLQGI}eT3#z&pPJdyo#8W6vWjZE(b2@06P%mCHCjzB$2 zf>!yBrb+MF?5pd!F4I-N-A{y!_VR7R_)kPPxNioimK||?HPhEE^$$FCDIb<324`O> zt3xvovpZ7~Hd00`VBF|;Ztho^6p*IR1pf&M(fNI4n4aMUN*?bX)1pg=end>gSqUz7 z?rG;UFzNluY0+!EDs&@7w;=iAd`~b$^3~dB(V0Ib%gwDnypmF7-CIMVZj>{0CfU?W zx&1#D$hv&9c&wfly0*48@XiKyK_{m*RztqI`a?AYDWXe5Guns_)qv8F=`RgXI;++w z^P|l+qKD=QD`V;0>ofkoWQiP^B1XsYHOrX_D|dHaiN#EkQlpJ?QQEE zONI9A6G12we9LMKeUPqvAV7QepfKAR`f=!)am1uR(RAt>3y}INmzC#Y3q|KhxwYY3 z5i9?>e@7lV2RzS>5*xf1B`(_2CR~qq5A)6pI#POBGg?;ImU#`*Khhoc`-brO-^QQ= z!Q7*Zb!>Y_(Ijw-5gsk~))!jJ?zJ+0*mPH-bvreT-O*lSBw_LmXF#ZRBi?|Y zRj2CL%G=a?il3SC94jvnlS$7V(TqW&2qYsELL~M2Zzt!Qje*WK13?KrqVu9xOBs;^ zk7O4bp;tlwO7#Q8g+KewAd;&@2)#F&BPJr;Hcq~sg4cQ$^2NcgGbRx-u>9c$)kf6K z1fA=VymlAQg$J%5q}t3t)lxslidX}thS4yS0-{itc?!DRN+Kn>NPS8{_N(ZN8v{#k z44=~of4G41gOgd0?{Y^EyW>fg*@E!xGt%vuiBM91Q+3m$npwSI zpKsJL&zGfypiuh*q=&HP>pVboxxJ-GJv&p|k= z*-tnh;I_+%5+c;LF^`bp0XPJ+g@-T0$F?W0?&5i?muY0)+BzFUDKwkxPxmcbeuk>S+&P=+OaYdKs(0I>U9C<$lC6h z=)mi{J#AviCX4#jAGEH~tEunSwb>8dD@lqokdXzBUV|aOEJ_MCDFHgQs-sT zgVxt8IO>abuP^!}o})f5quBfU*$#*5MwIwU{EXqz5-`1wH9@}<#LFTRb<1*_E#6J2 z4e2J#-=p4xiY{*gS)QZsDr7hO#W7Doig1yyY5R{7l)(5aR}P*+t4H;Vq>X9qO@smY z91&#g56?h5MqOskTbj4F^e*glZk>~8`G?wpIwYSroX_viU5HbT<-v^jTKx3dQ3IIt z9TJ3ktXV+nf-#ASbXf_y4a~qD_m6%G_72~?v!=8Ws0wUWoE56Uz|>2btQ_5g_zmDc zxTB+fY-PKU?kJSjZ9FDFQAJy5isLVA&O~YLv?sA1<34*?0aS4Mm+8FEx-^ znL7Uh5H^&bEr!pytAWBYQJLLe?~;}Rs^N@CcUISUK_x}0Vxmqf!plHf+)c>8Mo|A= zW^{x_iVn~XCvkW~&#osnm*9fro?338X;8i|U%E@ItPF($^z3*6Kaqs;IWe)gXOY(+ zO2WTr1{Q0e+YVqp>Cnz}-VwkTwgD2GXq!=71W8X5-!OA)M>;#}CTyhD(Dto08W%rv zk<^W--tAuGS`T6cSxxd1;s(v(GfZsj+0UEzQNgCjG0&IhKG@Vu@WhG#02~%)Y>kLU zc(aZL<_XFL)XQ(<2d-)=dXcYMt_{*DuZhs<K} zUmaclzD8kT;32`cjfN71Xc>>@zMm5vdvp4WI=dPga)71+TTdCc<-WYoY#DL&7LUy1 z9rp0J`a?Lzzv|W+Cm>?9TlsPp#zm!jno#fB< z46(S*Hh-cG4js8zLlXjQwVcC2!zjXpoy?s0Dkpvs{1Xpry5>h7v?&+8k!fa^b#Ziy zJn_%)5xT@Y0u&c`+&#?oY>~*+X?p5#WF5IQE!prxNwTqGXY|f*o|MZ)!ggM>DUj~7 zFgWfQcYOA7+xDbDj|5Kc0pGK9V9OCaheyQCz!YBcvP%#<&_bM>nT-G9MD>UmS+e-- z6Ui)e`_7KB-N+7={~boIH%zvVLDxdINw7!~3$0l>O3Pc^xMPlZzm`Q=9i3i@8n02(VPnH92 zmG8~TGk_*pnQbcUmh0yU;d<}7bdv-Lhp`dSf^4msuiL4kpth7~U4694eQ2Q%ul*WNw4 zyWOL&4%XrR>q@O;rj|UyrVEEAd)0=(m$)jvcV@7K#%y+G} z*?eJl==kL64ydezlLI~Rcmbgl>$QzW-0uOGw%?6vqFo{oXJ}R>6jMCkE53n_ zgED)&hh@`ySF$-SK-I1{jR~>n7DfIP?)i2JDqx|)nj*x7_2GMG8EqHWUk43Ls?m&lf2nI` zaGr5!X&G_r>b^gur_CCAEm-ZI_G6yO3aj;OM)(Wht#S*`e653DzMjjGuA4khB*%Gv zHs9qN*4me449>~p{xvV$ezS>**y_VBvLj1$w_)P$0f78r(La~wxwUdNm_Kog z$RYY1Wf!J;=5lq;Mwb0zu@GGlzIur(bAtX&@A=}B@mz9I#g1&K)-gjY zf+(8N)rF|l?#aGvZm+6pn>Kk333X`R8`}d|t{`&Q@NhDVr+N!PGQRCPN;_qGsjBV6 zjQWnEZ;CCf1U`TM)?Y}Q_@*I*4B!>IcR4n#Iq<7rk53n3(c6>TPIC7o68&8Fl`p(f z;AMGW5%Qb3M!D2ZAa>!jK?bq4;9&9h5Y4pSr-8Xx?GGV-TsLY_0@6fr%4HjDDZWjB(u1-aX%=)8fPXoilLGHexvY z#}2^_MossEt~)nHmG|_#ov^EO0Jx;Ln&PIVv)@SLPq2Ua40Xu2_*y z;XTjj5gKjHG~o(3E)s8K1c~Cuz`ZYA&O|R1CG^sm_YQ{Yd2o=^ER`bAzZwkJ6yJ9^ znk2BLzJt3#&uz{mDLc)WJ1I;_aPQc#`nv6?^BJ%b5cv})10;mgQ4@Om|KROCz@m88 zHSr-UAP5XODp@2ai6bgma?U6Qf+WeY5d|bCaU|!QC5+?@0!j{&ljJzW0fzD4{LVRh z?>W2ozxVEQcORab?kQ@jtE;=KzwmzVw|_9veKAcx!&4|?$XWX>LYbkmW*c8oC#Fx1 z<$W7|p46v7OXL|EDz&)#2NHNvEaf^@vsqTk{?2vN^)Pn8NM-l!I+nWEc8?V>Qa6a= zVD2yj+A-8fAk@u{`}j)A)J|!K%9ZQ2;fLH@La&+2ajbdG+Hg+Dv#1$Q6DM)U0*A1N2I)3tj-bmyod<;g1PCxJ(4R_|- zz$&$v#VPS9aNJDSdKKI9raU;E0E4&DOpRneLDSWgO!D8AdvTaSDqO!5LJ&+jkM=q! zT@QpUrVwRr3DSrX@$x7~tx;_=wAdl|LT1SomyDRdI_|84JW&>3)svFWf{%}i7G!oT zW}K$87M?uWBl`gSnpIh}Bv-({(F3in&6f7*NA#sv`loZN0m{{!?7ErHL`6pt%c>kZ8@Ur*prU33sl3YW4Z2>t3GIO~C|L*{S zsG3(za&*Xrj(HWe<+~9ow%_rD6`<|4W(UO039i6`(`Zre78B8T%50Qfmg*+h{A6!3 zO^J1HZDe%z>Eg>jz$KC4=>?VoWpRO_@IR0Q5Lb(VSv(w8-yxMoXNW*sottwl)967s(-1xOU+1B1B!aop*se5a2P=4IyK=` zzsYUg4E|oOlokFiajBemQw zj*->TM%D~tYxgPvFZueo!wMR_xzJawn{m!y&@aRw)wAl<2L-G|e>66T5^ciSZRQ%8 zCa7)!=geO#SVM?^F6SKy&3b~weyn-8JVbQ(g?0%J5AkgUIm$I=(w0}S;B-Q?k$9*t z_fpG_+4C21oS%s<JOBG1TbB3^n$t`B z33sBFJx_mt_H`p@2#(CVE=v@}sWD3DU{Wt!=in^R^-@iNYT?${nrh;6$9-0nAG((j z21Q!xJ0w0m7|4)6m#6w=hb$);!aB_FTjgxGu1s|m^_Bl8JrA)E_hMAqr$Hd3g#--D zC~mKvr$4Aozk+AY2si4_yT;q|b$!{3O^j0Jj5lM41{_(0rB_9Fq(^#3Cj5a!phcM~ zeO{9V(!X<)Y53SXF!J1W7ONMlDk8;-Q}$h+&5-qleQr=ijEQk)%oFy6T)!d+eQNKk z2aJYs-f_0$f73;-^%1zw2Cg9@-5W}e^K9Jilv=VZsL;sBqBqyvbViw29LeHQ>#j{D zuhVu<$&!@8fct>OYSE*kLR|yRTJHfn-DPxtqr!T? z{C3;Nr+$6{p|-zjRS%kgBZmG8l!mvuLV5m`dQ%&*AISa*7hV<#B{Oz2zPcJn$h0-6 z)hD+_e4ZFlyY)exY|l(LA=2=G=r)w1N3_rPh6IZ7U?=uHs1fa@_FOe6n#H8(Q{~Dw z4*STCZX?VP!U?oT-L)yTadYOF?ddZ_YU#)s;#L^+JvLS*y#5`#i0quftpiSsQb@<( zCAI7R6-QLV9a=yAoVx0@s3i9^kf?>;_BD4$n#eh|XwG>}Q_~!;k4f3&QROTcdem(V zecmeWRiG-X1R&D)rr$Hx295u4Mq4DYU@uXF@O5OE^%qC)6!rg^)JeTj%(9OX%zg*8(MjNH7?-; z0qm1tPYr%h5$qi|c%wg5h}HdZ`c{qD>RNQ^F08{v!iUduf6&IgrfDH{|L}jB9t1c& z(>REn2q+%*b#L2PR`7Q*P*LW3^62q6hpPYHubN=`Pq->=lDQdsSq4YlC^i6TbvML%-s+~9|VyMdm3g8AEv*V z4J7@b3V^2QItpk_RQNtyv1e_gWA835S&)DB4AS4vWBIy*Dd&O^<+vig+g653vN_(z zBN(dwk#Onr?zB+BJ8C#OU;2ddrwN&Q;8|Kz6obB46q$MUBtmCbgU;9Rpu7X+J9yh} z!r0tfQ6&3^V-|5G8qd=)6z151Z4;%V3RI4}<TdmkL`}AeUj$7Qda#f0 z{+RR-VrU$#^RU{};3%Gql z3`9UH+2=&oXaq{^aLy;$XY5mSYnDMLRG-1kVMlhY@MfgTW|Ig1n!JxgLb>6#eyE$32i|JcVU@{uM*0o8w(@T8twsXS8^C-^ zl(O+)uhKccf~E1^OlEw_QVcpjKi5s?M^1G?^U82$nwpe;Nq4)V?2d`4xcW_w%g7zi_4gJU9ZqbJ_vYi06~#=}!dqO=xtH$@D$h|2 zE0NgP>113xls*G7$cH#$cyBFkHv&0mse`W5x)RgUnuH<;wySlA@7WC$t(3#&rZ-|z zCf6o+?U3~rq0QoOrHYhvQX6s+Es*R^VClDPcEu^vNpEOb!QXQNpt@ApbLud$eXJ?;S;bbA)&nG@1>nn)H~p?3j#Pa zUxBw5@ZKkP79MJwg%hM_#1^D{g%()azWhlzOOB~`%CbG4t0@WSd4uW_w9{c zli|{PLTjOu!JT96V|pOifW&{;WPnXmuJB^=$3o?L54Y@Hlzg?BJ|Jv1JgzlYKS=rn zD7a+KKR7AR&GKtCm>4Z6YEzfd7%tF{x|>vZE0G3laKUADZm?!xd{KOQjx;exAv!LwmA{norX4+-lY<|I#zIUa~UGjF&v&=FBI$9#m&BP_=L+m}cH6$1;?T z`_(H>2t+c@8o>Hwfbf;LiUwsAxkEl?CD1hB@iex=;Uw;vrBp15bjR*tz*~tP)=d}$ zRCi#~tl4Zjp2d1iNXmh6en+fJ-sYX((VyU~G^Tru16Zt%FQwUSlX>`sM>9V!+ilZK zd3ZD9*+|M@n*1m~CtA4_%=bAnHs$c%kMg#>7pUd~ECj%wwS1iC^US?Q8VK47hE-esq|W79s6e-<;gS^Do;p2zPUcieRL{rd5|?szD7} z6r@)%hbMP|)<{90`)O~sZFzGYF0(j<)5}<9F`~PxHL~{{$1YN$sv0+1^e!+VRV4Df zq9pQ?O�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 A@c;k- diff --git a/database/dao/DeviceDAO.cpp b/database/dao/DeviceDAO.cpp index 69cdbe8..ba54022 100644 --- a/database/dao/DeviceDAO.cpp +++ b/database/dao/DeviceDAO.cpp @@ -45,6 +45,28 @@ QList DeviceDAO::getAllDevices(const bool online) { return devices; } +QList DeviceDAO::getOnlineDeviceCodes() { + QList codes; + QSqlQuery query(DatabaseManager::instance().database()); + QString sql = R"( + SELECT id + FROM devices + WHERE image IS NOT NULL AND status = 'device' + )"; + query.prepare(sql); + + if (!query.exec()) { + qWarning() << "Ошибка при получении списка устройств:" << query.lastError().text(); + return {}; + } + + while (query.next()) { + codes.append(query.value("id").toString()); + } + + return codes; +} + DeviceInfo DeviceDAO::getDeviceById(const QString &deviceId) { QSqlQuery query(DatabaseManager::instance().database()); diff --git a/database/dao/DeviceDAO.h b/database/dao/DeviceDAO.h index 2987c92..ce60436 100644 --- a/database/dao/DeviceDAO.h +++ b/database/dao/DeviceDAO.h @@ -13,4 +13,6 @@ public: [[nodiscard]] static bool updateImage(const QString &deviceId, const QByteArray &imageData); [[nodiscard]] static QList getAllDevices(bool online); + + [[nodiscard]] static QList getOnlineDeviceCodes(); }; diff --git a/database/dao/EventDAO.cpp b/database/dao/EventDAO.cpp index 82c2540..a3c7d28 100644 --- a/database/dao/EventDAO.cpp +++ b/database/dao/EventDAO.cpp @@ -12,6 +12,7 @@ EventInfo parseEvent(const QSqlQuery &query) { event.status = eventStatusFromString(query.value("status").toString()); event.externalId = query.value("external_id").toInt(); event.accountId = query.value("account_id").toInt(); + event.deviceId = query.value("device_id").toString(); event.amount = query.value("amount").toDouble(); event.phone = query.value("phone").toString(); event.bankName = query.value("bank_name").toString(); @@ -26,22 +27,23 @@ int EventDAO::insertEvent(const EventInfo &event) { query.prepare(R"( INSERT INTO events ( - status, external_id, account_id, amount, phone, bank_name, timestamp + status, external_id, account_id, amount, phone, bank_name, timestamp, device_id ) VALUES ( - :status, :external_id, :account_id, :amount, :phone, :bank_name, :timestamp + :status, :external_id, :account_id, :amount, :phone, :bank_name, :timestamp, :device_id ) )"); query.bindValue(":status", eventStatusToString(event.status)); query.bindValue(":external_id", event.externalId); query.bindValue(":account_id", event.accountId); + query.bindValue(":device_id", event.deviceId); query.bindValue(":amount", event.amount); query.bindValue(":phone", event.phone); query.bindValue(":bank_name", event.bankName); query.bindValue(":timestamp", DateUtils::toUtcString(DateUtils::getUtcNow())); if (!query.exec()) { - qWarning() << "Ошибка при вставке event:" << query.lastError().text(); + qCritical() << "Ошибка при вставке event:" << query.lastError().text(); return -1; } @@ -78,7 +80,7 @@ bool EventDAO::updateEvent( } if (!query.exec()) { - qWarning() << "Ошибка при обновлении event:" << query.lastError().text(); + qCritical() << "Ошибка при обновлении event:" << query.lastError().text(); return false; } return true; @@ -89,14 +91,14 @@ QList EventDAO::getAllEvents(const EventStatus &status) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( - SELECT id, account_id, external_id, amount, phone, bank_name, status, update_time, complete_time, timestamp + SELECT id, account_id, device_id, external_id, amount, phone, bank_name, status, update_time, complete_time, timestamp FROM events WHERE status = :status )"); query.bindValue(":status", eventStatusToString(status)); if (!query.exec()) { - qWarning() << "Ошибка при получении списка устройств:" << query.lastError().text(); + qWarning() << "Ошибка при получении getAllEvents:" << query.lastError().text(); return events; } @@ -106,3 +108,26 @@ QList EventDAO::getAllEvents(const EventStatus &status) { return events; } + +EventInfo EventDAO::getFirstWaitEventByDeviceId(const QString &key) { + QSqlQuery query(DatabaseManager::instance().database()); + query.prepare(R"( + SELECT id, account_id, device_id, external_id, amount, phone, bank_name, status, update_time, complete_time, timestamp + FROM events + WHERE status = :status AND device_id = :device_id + ORDER BY timestamp ASC + LIMIT 1 + )"); + query.bindValue(":status", eventStatusToString(EventStatus::Wait)); + query.bindValue(":device_id", key); + + if (!query.exec()) { + qCritical() << "Ошибка при получении getFirstWaitEventByDeviceId:" << query.lastError().text(); + return {}; + } + + while (query.next()) { + return parseEvent(query); + } + return {}; +} diff --git a/database/dao/EventDAO.h b/database/dao/EventDAO.h index 9e4982b..3572a5d 100644 --- a/database/dao/EventDAO.h +++ b/database/dao/EventDAO.h @@ -12,4 +12,6 @@ public: ); [[nodiscard]] static QList getAllEvents(const EventStatus &status); + + [[nodiscard]] static EventInfo getFirstWaitEventByDeviceId(const QString &key); }; diff --git a/main.cpp b/main.cpp index 82388d9..edbb7c9 100644 --- a/main.cpp +++ b/main.cpp @@ -16,6 +16,7 @@ #include #include +#include "EventHandler.h" #include "net/NetworkService.h" #include "rshb/GetLastDaysHistoryScript.h" #include "rshb/HomeScreenScript.h" @@ -145,7 +146,7 @@ static QFile logFile; static QMutex logMutex; void messageHandler( - QtMsgType type, + const QtMsgType type, const QMessageLogContext &context, const QString &msg ) { @@ -166,8 +167,9 @@ void messageHandler( break; } - const QString line = QString("%1 [%2] %3\n").arg(timestamp, level, msg); + const QString line = QString("%1 [%2] [%3:%4] %5\n") + .arg(timestamp, level, context.function, QString::number(context.line), msg); // Записываем в файл (потокобезопасно) { QMutexLocker locker(&logMutex); @@ -178,7 +180,7 @@ void messageHandler( } // По желанию — дублировать в консоль - QByteArray localMsg = line.toLocal8Bit(); + const QByteArray localMsg = line.toLocal8Bit(); fprintf(stderr, "%s", localMsg.constData()); if (type == QtFatalMsg) { @@ -196,21 +198,28 @@ int main(int argc, char *argv[]) { // Устанавливаем свой обработчик qInstallMessageHandler(messageHandler); - auto *thread = new QThread; - auto *worker = new NetworkService; - - // FIXME доставить десктоп ид из настроеек - worker->setDeviceData("DT88bokcwQ", ""); + auto *worker = new EventHandler; worker->moveToThread(thread); - QObject::connect(thread, &QThread::started, worker, &NetworkService::getPayments); - QObject::connect(worker, &NetworkService::finished, thread, &QThread::quit); - QObject::connect(worker, &NetworkService::finished, worker, &QObject::deleteLater); + QObject::connect(thread, &QThread::started, worker, &EventHandler::start); + QObject::connect(worker, &EventHandler::finished, thread, &QThread::quit); + QObject::connect(worker, &EventHandler::finished, worker, &QObject::deleteLater); QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater); thread->start(); - // setupScriptAutoPaymentAndThread(app); - // setupWorkerAndThread(app); + auto *paymentThread = new QThread; + auto *networkService = new NetworkService; + + // FIXME доставить десктоп ид из настроеек + networkService->setDeviceData("DT88bokcwQ", ""); + networkService->moveToThread(paymentThread); + QObject::connect(paymentThread, &QThread::started, networkService, &NetworkService::start); + QObject::connect(networkService, &NetworkService::finished, paymentThread, &QThread::quit); + QObject::connect(networkService, &NetworkService::finished, networkService, &QObject::deleteLater); + QObject::connect(paymentThread, &QThread::finished, paymentThread, &QThread::deleteLater); + paymentThread->start(); + + setupWorkerAndThread(app); MainWindow window; window.show(); diff --git a/migrations.sql b/migrations.sql index 987561f..d995f42 100644 --- a/migrations.sql +++ b/migrations.sql @@ -74,6 +74,7 @@ CREATE TABLE IF NOT EXISTS events id INTEGER PRIMARY KEY AUTOINCREMENT, account_id INTEGER, external_id INTEGER, + device_id TEXT, amount REAL, phone TEXT, @@ -85,5 +86,6 @@ CREATE TABLE IF NOT EXISTS events complete_time DATETIME, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE, + FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE, UNIQUE (external_id) ); \ No newline at end of file diff --git a/models/db/EventInfo.h b/models/db/EventInfo.h index 6973f25..95ce7d9 100644 --- a/models/db/EventInfo.h +++ b/models/db/EventInfo.h @@ -31,6 +31,7 @@ struct EventInfo { int id = -1; int externalId = -1; int accountId = -1; + QString deviceId; double amount = 0; QString phone; diff --git a/services/DeviceScreener.cpp b/services/DeviceScreener.cpp index a3051ea..39ae889 100644 --- a/services/DeviceScreener.cpp +++ b/services/DeviceScreener.cpp @@ -1,6 +1,6 @@ #include "DeviceScreener.h" -#include +#include #include #include #include @@ -16,11 +16,13 @@ #include DeviceScreener::DeviceScreener(QObject *parent) : QObject(parent) { + const QSettings settings("config.ini", QSettings::IniFormat); + m_urlAppUpdate = settings.value("net/appInfoUpdate").toString(); } DeviceScreener::~DeviceScreener() = default; -void postDevicesData(const QList &devices) { +void DeviceScreener::postDevicesData(const QList &devices) { auto *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); QHttpPart jsonPart; @@ -60,7 +62,7 @@ void postDevicesData(const QList &devices) { multiPart->append(uniquePart); QNetworkAccessManager manager; // создаём внутри потока - QUrl url("http://localhost:9999/api/v1/app/update"); + QUrl url(m_urlAppUpdate); QNetworkRequest request(url); // делаем запрос @@ -82,10 +84,12 @@ void postDevicesData(const QList &devices) { QJsonDocument doc = QJsonDocument::fromJson(raw, &err); if (err.error == QJsonParseError::NoError && doc.isObject()) { QJsonObject root = doc.object(); - qDebug() << "Успешный ответ: " << root.value("success").toBool(); + if (!root.value("success").toBool()) { + qWarning() << root; + } } } else { - qWarning() << "Ошибка:" << reply->errorString(); + qWarning() << "Response error:" << reply->errorString() << m_urlAppUpdate; } reply->deleteLater(); } @@ -116,12 +120,14 @@ void DeviceScreener::start() { } } - postDevicesData(deviceInfos); + if (!deviceInfos.empty()) { + postDevicesData(deviceInfos); + } if (!DeviceDAO::markDevicesOfflineExcept(onlineIds)) { qDebug() << "Cant mark devices offline"; } - QThread::sleep(3); + QThread::msleep(1000); } emit finished(); } diff --git a/services/DeviceScreener.h b/services/DeviceScreener.h index e004ca4..b26688a 100644 --- a/services/DeviceScreener.h +++ b/services/DeviceScreener.h @@ -21,10 +21,13 @@ signals: private: bool m_running = true; + QString m_urlAppUpdate; static QList > readAdbDevices(); static QByteArray takeScreenshot(const QString &deviceId); static DeviceInfo readDeviceInfo(const QString &deviceId); + + void postDevicesData(const QList &devices); }; diff --git a/services/EventHandler.cpp b/services/EventHandler.cpp new file mode 100644 index 0000000..5c709b7 --- /dev/null +++ b/services/EventHandler.cpp @@ -0,0 +1,130 @@ +#include "EventHandler.h" + +#include +#include +#include + +#include "dao/AccountDAO.h" +#include "dao/DeviceDAO.h" +#include "dao/EventDAO.h" +#include "rshb/CommonScript.h" +#include "rshb/PayByPhoneScript.h" + +EventHandler::EventHandler(QObject *parent) : QObject(parent) { + m_checkTimer = new QTimer(this); + m_checkTimer->setInterval(1000); + connect(m_checkTimer, &QTimer::timeout, this, &EventHandler::checkAndRestart); +} + +EventHandler::~EventHandler() = default; + +void EventHandler::start() { + m_devices = DeviceDAO::getOnlineDeviceCodes(); + + clearAll(); + // Запустим сразу потоки для всех ключей + for (auto &key: m_devices) { + startThreadForKey(key); + } + + m_checkTimer->start(); +} + +void EventHandler::checkAndRestart() { + if (!m_running) { + return; + } + m_devices = DeviceDAO::getOnlineDeviceCodes(); + + for (const auto &key: m_devices) { + const QThread *thr = m_threads.value(key, nullptr); + if (!thr || !thr->isRunning()) { + startThreadForKey(key); + } + } +} + +void EventHandler::startThreadForKey(const QString &key) { + const EventInfo event = EventDAO::getFirstWaitEventByDeviceId(key); + if (event.id == -1) { + return; + } + if (!EventDAO::updateEvent(event.id, EventStatus::InProgress)) { + qCritical() << "Cant update event: " << event.id; + return; + } + + qDebug() << "Thread for key" << key << "running"; + // FIXME пока это только оплата, + const AccountInfo account = AccountDAO::getAccountById(event.accountId); + const auto thr = new QThread(this); + auto *worker = new PayByPhoneScript(account, event.phone, event.bankName, event.amount); + + worker->moveToThread(thr); + connect(thr, &QThread::started, worker, &PayByPhoneScript::start); + connect(worker, &PayByPhoneScript::finished, thr, &QThread::quit); + connect(worker, &PayByPhoneScript::finished, worker, &QObject::deleteLater); + connect(thr, &QThread::finished, thr, &QObject::deleteLater); + + // При нормальном завершении — перезапустить новый поток + connect(worker, &PayByPhoneScript::finished, this, + [this, key]() { + // qDebug() << "Thread finished for key" << key << "- restarting."; + if (const auto tm = m_timers.take(key)) { + tm->stop(); + tm->deleteLater(); + } + m_threads.remove(key); + }); + + // Таймер на 5 минут — если не успел finish(), убить и перезапустить + const auto timer = new QTimer(this); + timer->setSingleShot(true); + connect(timer, &QTimer::timeout, this, [this, key]() { + qWarning() << "Timeout for key" << key << "- killing stuck thread."; + if (QThread *thread = m_threads.value(key); thread && thread->isRunning()) { + thread->requestInterruption(); // мягкое прерывание + if (!thread->wait(500)) { + qWarning() << "Hard terminate for key" << key; + thread->terminate(); + thread->wait(); + } + } + if (const auto tm = m_timers.take(key)) { + tm->deleteLater(); + } + m_threads.remove(key); + }); + + // Сохраняем и запускаем + m_threads[key] = thr; + m_timers[key] = timer; + + timer->start(5 * 60 * 1000); + thr->start(); +} + +void EventHandler::clearAll() { + // Останавливаем и удаляем старые + for (QThread *thread: m_threads) { + if (thread->isRunning()) { + thread->requestInterruption(); + thread->terminate(); + thread->wait(); + } + thread->deleteLater(); + } + m_threads.clear(); + + for (QTimer *timer: m_timers) { + timer->stop(); + timer->deleteLater(); + } + m_timers.clear(); +} + +void EventHandler::stop() { + m_running = false; + m_checkTimer->stop(); + clearAll(); +} diff --git a/services/EventHandler.h b/services/EventHandler.h new file mode 100644 index 0000000..c6f1380 --- /dev/null +++ b/services/EventHandler.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include +#include + +class EventHandler final : public QObject { + Q_OBJECT + +public: + explicit EventHandler(QObject *parent = nullptr); + + ~EventHandler() override; + +public slots: + void start(); + + void stop(); + +signals: + void finished(); + +private slots: + void checkAndRestart(); // вызывается по таймеру + +private: + bool m_running = true; + QMap m_threads; + QMap m_timers; + QTimer *m_checkTimer = nullptr; + + QStringList m_devices; + + void clearAll(); + + void startThreadForKey(const QString &key); +}; diff --git a/services/net/NetworkService.cpp b/services/net/NetworkService.cpp index 0239fc2..e850f84 100644 --- a/services/net/NetworkService.cpp +++ b/services/net/NetworkService.cpp @@ -1,11 +1,13 @@ #include "NetworkService.h" -#include #include #include #include #include #include +#include +#include +#include #include "dao/AccountDAO.h" #include "dao/EventDAO.h" @@ -13,6 +15,12 @@ #include "db/EventInfo.h" NetworkService::NetworkService(QObject *parent) : QObject(parent) { + const QSettings settings("config.ini", QSettings::IniFormat); + m_urlAccountUpdate = settings.value("net/accountUpdate").toString(); + m_urlPaymentsGet = settings.value("net/paymentsGet").toString(); + m_urlTransactionUpdate = settings.value("net/transactionUpdate").toString(); + m_urlTransactionInsert = settings.value("net/transactionInsert").toString(); + m_manager = new QNetworkAccessManager(this); } NetworkService::~NetworkService() = default; @@ -40,11 +48,10 @@ void addFormMetadata(QHttpMultiPart *multiPart, const QByteArray &fieldValue) { } QJsonValue NetworkService::post(QHttpMultiPart *multiPart, const QString &path) { - QNetworkAccessManager manager; // создаём внутри потока const QUrl url(path); const QNetworkRequest request(url); - QNetworkReply *reply = manager.post(request, multiPart); + QNetworkReply *reply = m_manager->post(request, multiPart); multiPart->setParent(reply); QEventLoop loop; @@ -64,20 +71,17 @@ QJsonValue NetworkService::post(QHttpMultiPart *multiPart, const QString &path) if (root.value("success").toBool()) { result = root.value("data"); } else { - qWarning() << "Ошибка:" << root; + qCritical() << "Ошибка:" << root; } } } else { - qWarning() << "Ошибка:" << reply->errorString(); + qCritical() << "Ошибка:" << reply->errorString() << path; } reply->deleteLater(); return result; } - QJsonArray NetworkService::get(const QString &path, const QMap ¶ms) { - QNetworkAccessManager manager; // создаём внутри потока - QUrl url(path); QUrlQuery query; for (auto it = params.constBegin(); it != params.constEnd(); ++it) { @@ -86,7 +90,8 @@ QJsonArray NetworkService::get(const QString &path, const QMap url.setQuery(query); const QNetworkRequest request(url); - QNetworkReply *reply = manager.get(request); + QNetworkReply *reply = m_manager->get(request); + QScopedPointer guard(reply); QEventLoop loop; connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); @@ -121,9 +126,9 @@ void NetworkService::postAccountsData() { addFormMetadata(multiPart, m_json); addFormField(multiPart, "desktopUniqueName", m_desktopId); addFormField(multiPart, "androidUniqueName", m_deviceId); - const bool isSuccess = post(multiPart, "http://localhost:9999/api/v1/account/update").toBool(); + const bool isSuccess = post(multiPart, m_urlAccountUpdate).toBool(); if (!isSuccess) { - qDebug() << "Cant post accounts data"; + qCritical() << "Cant post accounts data"; } emit finished(); @@ -132,7 +137,7 @@ void NetworkService::postAccountsData() { void NetworkService::getPayments() { QMap params; params["desktopUniqueName"] = m_desktopId; - const QJsonArray data = get("http://localhost:9999/api/v1/payments", params); + const QJsonArray data = get(m_urlPaymentsGet, params); for (QJsonValue item: data) { QJsonObject obj = item.toObject(); @@ -149,20 +154,16 @@ void NetworkService::getPayments() { AccountInfo account = AccountDAO::findAppAccount(deviceId, appName, lastNumbers); eventInfo.accountId = account.id; + eventInfo.deviceId = account.deviceId; - - int id = EventDAO::insertEvent(eventInfo); - if (id == -1) { - qDebug() << "Cant insert event"; - } else { - if (!EventDAO::updateEvent(id, EventStatus::Wait)) { - qDebug() << "Cant update event"; - } + if (int id = EventDAO::insertEvent(eventInfo); id == -1) { + qCritical() << "Cant insert event: " << eventInfo.externalId; } } - - emit finished(); + if (!m_running) { + emit finished(); + } } @@ -174,9 +175,9 @@ void NetworkService::updateTransactionData() { addFormField(multiPart, "androidUniqueName", m_deviceId); addFormField(multiPart, "lastNumbers", m_extraData["lastNumbers"].toString()); - const bool isSuccess = post(multiPart, "http://localhost:9999/api/v1/transaction/update").toBool(); + const bool isSuccess = post(multiPart, m_urlTransactionUpdate).toBool(); if (!isSuccess) { - qDebug() << "Cant update transaction"; + qCritical() << "Cant update transaction: updateTransactionData"; } emit finished(); @@ -190,16 +191,23 @@ void NetworkService::insertTransactionData() { addFormField(multiPart, "androidUniqueName", m_deviceId); addFormField(multiPart, "lastNumbers", m_extraData["lastNumbers"].toString()); - const int id = post(multiPart, "http://localhost:9999/api/v1/transaction/insert").toInt(); + const int id = post(multiPart, m_urlTransactionInsert).toInt(); if (id > 0) { const int transactionId = m_extraData["transactionId"].toInt(); if (!TransactionDAO::updateExternalTransactionId(transactionId, id)) { - qDebug() << "Cant update transaction id"; + qCritical() << "Cant update transaction id: " << id; } } emit finished(); } +void NetworkService::start() { + QTimer *poller = new QTimer(this); + poller->setInterval(1000); + connect(poller, &QTimer::timeout, this, &NetworkService::getPayments); + poller->start(); +} + void NetworkService::stop() { m_running = false; } diff --git a/services/net/NetworkService.h b/services/net/NetworkService.h index 41f35bd..717c3d6 100644 --- a/services/net/NetworkService.h +++ b/services/net/NetworkService.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include @@ -29,6 +30,7 @@ public slots: void updateTransactionData(); + void start(); void stop(); signals: @@ -41,6 +43,13 @@ private: QMap m_extraData = {}; bool m_running = true; + QString m_urlAccountUpdate; + QString m_urlPaymentsGet; + QString m_urlTransactionUpdate; + QString m_urlTransactionInsert; + + QNetworkAccessManager *m_manager; + QJsonValue post(QHttpMultiPart *multiPart, const QString &path);