commands.rs 218 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034
  1. use crate::steam_auth::{SteamGuardAccount, Confirmation};
  2. use crate::api_client::{ApiConfig, initialize_api_client, get_api_client_for_async, get_http_client};
  3. use crate::app_logger;
  4. use crate::config::AppConfig;
  5. use crate::machine::generate_machine_code;
  6. use crate::storage::{get_storage, UserInfo};
  7. use crate::heartbeat::{
  8. start_heartbeat_service,
  9. stop_heartbeat_service,
  10. get_heartbeat_service_status,
  11. is_heartbeat_running,
  12. execute_heartbeat,
  13. execute_key_sync,
  14. update_heartbeat_config,
  15. get_heartbeat_config,
  16. HeartbeatConfig,
  17. };
  18. use serde::{Deserialize, Serialize};
  19. use serde_json;
  20. use std::collections::HashMap;
  21. use std::fs;
  22. use std::path::PathBuf;
  23. use std::sync::{Mutex, OnceLock};
  24. use std::time::Duration;
  25. use chrono::{Datelike, Utc};
  26. // 导出 reqwest::StatusCode 供子模块使用
  27. pub use reqwest::StatusCode;
  28. // 加密相关导入
  29. use base64::{Engine as _, engine::general_purpose};
  30. use hmac::{Hmac, Mac};
  31. use sha2::Sha256;
  32. use aes::Aes256;
  33. use ctr::Ctr64BE;
  34. use aes::cipher::{KeyIvInit, StreamCipher};
  35. use rsa::{RsaPublicKey, pkcs8::DecodePublicKey, pkcs1v15::Pkcs1v15Encrypt, oaep::Oaep};
  36. use rand;
  37. use rand::rngs::OsRng;
  38. use std::collections::VecDeque;
  39. use std::time::{SystemTime, UNIX_EPOCH, Instant};
  40. // 新的安全通信模块导入
  41. pub mod crypto;
  42. use crate::commands::crypto::{
  43. aes_encrypt,
  44. aes_decrypt,
  45. get_stored_key_version,
  46. get_current_public_key,
  47. initialize_key_manager,
  48. refresh_key_if_needed,
  49. get_key_manager_state,
  50. validate_stored_key,
  51. has_valid_hmac_key,
  52. clear_hmac_key,
  53. store_hmac_key_from_api,
  54. sync_hmac_key as crypto_sync_hmac_key,
  55. generate_hmac_signature,
  56. rsa_encrypt_aes_key as crypto_rsa_encrypt_aes_key,
  57. store_rsa_key_segments,
  58. retrieve_rsa_key_segments,
  59. has_stored_rsa_key as crypto_has_stored_rsa_key,
  60. clear_stored_rsa_key as crypto_clear_stored_rsa_key,
  61. KeyManagerState,
  62. };
  63. use log::{info, error, warn, debug};
  64. mod buff_sweep_decode;
  65. mod buff_sweep_runner;
  66. // ========== 本地 nonce 去重缓存 ==========
  67. // 防止 Rust 端重试时短时间内使用同一个 nonce(已被后端防重放拒绝过),导致雪崩
  68. lazy_static::lazy_static! {
  69. static ref RECENT_NONCES: std::sync::Mutex<VecDeque<(String, Instant)>> = std::sync::Mutex::new(VecDeque::new());
  70. }
  71. const NONCE_CACHE_TTL_SECS: u64 = 10; // 10 秒内相同 nonce 不重发
  72. /// 生成一个唯一的随机 nonce(64位hex = 32字节),并检查本地缓存
  73. fn generate_unique_nonce() -> String {
  74. let nonce: String = (0..32u8).map(|_| rand::random::<u8>()).map(|b| format!("{:02x}", b)).collect();
  75. let mut cache = RECENT_NONCES.lock().unwrap();
  76. // 清理过期条目(超过 TTL 的)
  77. let now = Instant::now();
  78. cache.retain(|(_, instant)| now.duration_since(*instant).as_secs() < NONCE_CACHE_TTL_SECS);
  79. // 如果 nonce 已在缓存中(极罕见),重新生成一次
  80. if cache.iter().any(|(n, _)| n == &nonce) {
  81. let nonce2: String = (0..32u8).map(|_| rand::random::<u8>()).map(|b| format!("{:02x}", b)).collect();
  82. cache.push_back((nonce2.clone(), now));
  83. return nonce2;
  84. }
  85. cache.push_back((nonce.clone(), now));
  86. nonce
  87. }
  88. /// 记录一个 nonce(请求发出后调用,即使失败了也要记录,避免同一 nonce 在 TTL 内重发)
  89. fn record_nonce(nonce: &str) {
  90. if let Ok(mut cache) = RECENT_NONCES.lock() {
  91. let now = Instant::now();
  92. cache.retain(|(_, instant)| now.duration_since(*instant).as_secs() < NONCE_CACHE_TTL_SECS);
  93. cache.push_back((nonce.to_string(), now));
  94. }
  95. }
  96. #[tauri::command]
  97. pub async fn generate_steam_code(account_data: String) -> Result<String, String> {
  98. let account: SteamGuardAccount = serde_json::from_str(&account_data)
  99. .map_err(|e| format!("Failed to parse account data: {}", e))?;
  100. account.generate_code()
  101. .map_err(|e| format!("Failed to generate code: {}", e))
  102. }
  103. #[tauri::command]
  104. pub async fn fetch_confirmations(account_data: String) -> Result<String, String> {
  105. let account: SteamGuardAccount = serde_json::from_str(&account_data)
  106. .map_err(|e| format!("Failed to parse account data: {}", e))?;
  107. let confirmations = account.fetch_confirmations().await
  108. .map_err(|e| format!("Failed to fetch confirmations: {}", e))?;
  109. serde_json::to_string(&confirmations)
  110. .map_err(|e| format!("Failed to serialize confirmations: {}", e))
  111. }
  112. #[tauri::command]
  113. pub async fn accept_confirmation(account_data: String, confirmation_data: String) -> Result<bool, String> {
  114. let account: SteamGuardAccount = serde_json::from_str(&account_data)
  115. .map_err(|e| format!("Failed to parse account data: {}", e))?;
  116. let confirmation: Confirmation = serde_json::from_str(&confirmation_data)
  117. .map_err(|e| format!("Failed to parse confirmation data: {}", e))?;
  118. account.accept_confirmation(&confirmation).await
  119. .map_err(|e| format!("Failed to accept confirmation: {}", e))
  120. }
  121. #[tauri::command]
  122. pub async fn deny_confirmation(account_data: String, confirmation_data: String) -> Result<bool, String> {
  123. let account: SteamGuardAccount = serde_json::from_str(&account_data)
  124. .map_err(|e| format!("Failed to parse account data: {}", e))?;
  125. let confirmation: Confirmation = serde_json::from_str(&confirmation_data)
  126. .map_err(|e| format!("Failed to parse confirmation data: {}", e))?;
  127. account.deny_confirmation(&confirmation).await
  128. .map_err(|e| format!("Failed to deny confirmation: {}", e))
  129. }
  130. #[tauri::command]
  131. pub fn save_steam_account(account_data: String, account_name: String) -> Result<(), String> {
  132. let app_data_dir = dirs::data_dir()
  133. .ok_or("Failed to get app data directory")?
  134. .join("YourAppName")
  135. .join("steam_accounts");
  136. fs::create_dir_all(&app_data_dir)
  137. .map_err(|e| format!("Failed to create directory: {}", e))?;
  138. let file_path = app_data_dir.join(format!("{}.json", account_name));
  139. // 简单的加密(生产环境应该使用更强的加密)
  140. let encrypted_data = simple_encrypt(&account_data);
  141. fs::write(&file_path, encrypted_data)
  142. .map_err(|e| format!("Failed to save account: {}", e))
  143. }
  144. #[tauri::command]
  145. pub fn load_steam_accounts() -> Result<String, String> {
  146. let app_data_dir = dirs::data_dir()
  147. .ok_or("Failed to get app data directory")?
  148. .join("YourAppName")
  149. .join("steam_accounts");
  150. if !app_data_dir.exists() {
  151. return Ok("[]".to_string());
  152. }
  153. let mut accounts = Vec::new();
  154. for entry in fs::read_dir(&app_data_dir)
  155. .map_err(|e| format!("Failed to read directory: {}", e))?
  156. {
  157. let entry = entry.map_err(|e| format!("Failed to read entry: {}", e))?;
  158. let path = entry.path();
  159. if path.extension().and_then(|s| s.to_str()) == Some("json") {
  160. let encrypted_data = fs::read(&path)
  161. .map_err(|e| format!("Failed to read file: {}", e))?;
  162. let account_data = simple_decrypt(&encrypted_data);
  163. accounts.push(account_data);
  164. }
  165. }
  166. Ok(format!("[{}]", accounts.join(",")))
  167. }
  168. // 简单的加密/解密函数(生产环境应该使用更强的加密)
  169. fn simple_encrypt(data: &str) -> Vec<u8> {
  170. data.as_bytes().iter().map(|&b| b.wrapping_add(42)).collect()
  171. }
  172. fn simple_decrypt(data: &[u8]) -> String {
  173. let bytes: Vec<u8> = data.iter().map(|&b| b.wrapping_sub(42)).collect();
  174. String::from_utf8_lossy(&bytes).to_string()
  175. }
  176. // ==================== Project-local steam-msg storage commands ====================
  177. #[tauri::command]
  178. pub async fn save_steam_guard_file(base64Data: String, filename: String) -> Result<String, String> {
  179. use base64::{engine::general_purpose, Engine as _};
  180. // Determine project root (assume current_dir is inside the project, fall back to current_dir)
  181. let cwd = std::env::current_dir().map_err(|e| format!("Failed to get current dir: {}", e))?;
  182. let project_root = cwd; // use current dir; in typical dev setup this is project root
  183. let steam_msg_dir: PathBuf = project_root.join("steam-msg");
  184. fs::create_dir_all(&steam_msg_dir).map_err(|e| format!("Failed to create steam-msg dir: {}", e))?;
  185. let target_path = steam_msg_dir.join(&filename);
  186. let decoded = general_purpose::STANDARD
  187. .decode(&base64Data)
  188. .map_err(|e| format!("Failed to decode base64 data: {}", e))?;
  189. fs::write(&target_path, &decoded).map_err(|e| format!("Failed to write steam guard file: {}", e))?;
  190. Ok(target_path.to_string_lossy().to_string())
  191. }
  192. /// 统一 accounts 文件名(所有账号信息存于一个 JSON)
  193. const ACCOUNTS_JSON: &str = "accounts.json";
  194. /// 获取 steam-msg 目录。
  195. /// 路径为 `AppData\Local\steam-msg\{当前登录用户名}\`,实现多用户数据隔离。
  196. /// 若新目录下无数据而旧扁平目录下有数据,则一次性迁移过来。
  197. fn get_steam_msg_dir() -> Result<PathBuf, String> {
  198. // 获取 AppData\Local\steam-msg 基础路径
  199. let base = dirs::data_local_dir()
  200. .or_else(|| std::env::current_dir().ok())
  201. .ok_or_else(|| "无法获取可写目录".to_string())?;
  202. let base_steam_msg = base.join("steam-msg");
  203. // 获取当前登录用户名,拼入子目录
  204. let username = get_current_username_internal()
  205. .unwrap_or_else(|_| "guest".to_string());
  206. let user_dir = base_steam_msg.join(&username);
  207. // 迁移逻辑:若新目录下尚无任何 .maFile(说明从未迁移过),
  208. // 而旧扁平目录下存在 accounts.json,则一次性迁移所有数据过来
  209. let has_existing_files = user_dir.join(ACCOUNTS_JSON).exists()
  210. || std::fs::read_dir(&user_dir)
  211. .and_then(|mut d| d.next().transpose())
  212. .is_ok();
  213. if !has_existing_files && base_steam_msg.join(ACCOUNTS_JSON).exists() {
  214. if let Err(e) = fs::create_dir_all(&user_dir) {
  215. log::warn!("[get_steam_msg_dir] 创建用户目录失败: {}", e);
  216. } else {
  217. // 复制 accounts.json 和所有 .maFile
  218. if let Ok(entries) = fs::read_dir(&base_steam_msg) {
  219. for entry in entries.flatten() {
  220. let name = entry.file_name();
  221. let name_str = name.to_string_lossy();
  222. if name_str.ends_with(".maFile") || name_str == ACCOUNTS_JSON {
  223. let dest = user_dir.join(&name);
  224. if !dest.exists() {
  225. if let Err(e) = fs::copy(entry.path(), &dest) {
  226. log::warn!("[get_steam_msg_dir] 迁移文件失败 {:?}: {}", entry.path(), e);
  227. }
  228. }
  229. }
  230. }
  231. }
  232. log::info!("[get_steam_msg_dir] 已从旧目录迁移数据到 {}", user_dir.display());
  233. }
  234. }
  235. Ok(user_dir)
  236. }
  237. /// 将用户名中不能作为文件名的字符替换为下划线,避免 Windows 等平台路径错误或崩溃
  238. fn sanitize_username_for_filename(username: &str) -> String {
  239. let mut s = username.to_string();
  240. for c in ['\\', '/', ':', '*', '?', '"', '<', '>', '|'] {
  241. s = s.replace(c, "_");
  242. }
  243. if s.is_empty() {
  244. s = "steam_account".to_string();
  245. }
  246. s
  247. }
  248. #[tauri::command]
  249. pub async fn save_steam_account_to_project(
  250. steamUsername: String,
  251. steamPassword: String,
  252. steamGuardPath: String,
  253. isUsAccount: Option<bool>,
  254. ) -> Result<(), String> {
  255. use serde_json::{json, Value};
  256. let steam_msg_dir = get_steam_msg_dir()?;
  257. fs::create_dir_all(&steam_msg_dir).map_err(|e| format!("创建目录失败: {}", e))?;
  258. let safe_name = sanitize_username_for_filename(&steamUsername);
  259. let mafile_dest = steam_msg_dir.join(format!("{}.maFile", safe_name));
  260. if !steamGuardPath.is_empty() {
  261. let src = PathBuf::from(&steamGuardPath);
  262. if src.exists() {
  263. fs::copy(&src, &mafile_dest).map_err(|e| format!("复制 maFile 失败: {}", e))?;
  264. }
  265. }
  266. let steam_guard_path_str = mafile_dest.to_string_lossy().to_string();
  267. let accounts_file = steam_msg_dir.join(ACCOUNTS_JSON);
  268. let mut root: Value = if accounts_file.exists() {
  269. let text = fs::read_to_string(&accounts_file).map_err(|e| format!("读取账号文件失败: {}", e))?;
  270. serde_json::from_str(&text).unwrap_or_else(|_| json!({ "accounts": {} }))
  271. } else {
  272. json!({ "accounts": {} })
  273. };
  274. let accounts = root.get_mut("accounts").and_then(|a| a.as_object_mut()).ok_or("账号数据结构异常")?;
  275. let is_us = isUsAccount.unwrap_or(true);
  276. let entry = json!({
  277. "steam_account": steamUsername,
  278. "steam_password": steamPassword,
  279. "steam_guard_path": steam_guard_path_str,
  280. "is_us_account": is_us
  281. });
  282. accounts.insert(steamUsername.clone(), entry);
  283. let content = serde_json::to_string_pretty(&root).map_err(|e| format!("序列化失败: {}", e))?;
  284. fs::write(&accounts_file, content).map_err(|e| format!("写入账号文件失败: {}", e))?;
  285. Ok(())
  286. }
  287. #[tauri::command]
  288. pub async fn list_project_steam_accounts() -> Result<String, String> {
  289. let steam_msg_dir = get_steam_msg_dir()?;
  290. let accounts_file = steam_msg_dir.join(ACCOUNTS_JSON);
  291. if !accounts_file.exists() {
  292. return Ok("[]".to_string());
  293. }
  294. let text = fs::read_to_string(&accounts_file).map_err(|e| format!("Read accounts file failed: {}", e))?;
  295. let root: serde_json::Value = serde_json::from_str(&text).unwrap_or(serde_json::json!({ "accounts": {} }));
  296. let accounts_obj = root
  297. .get("accounts")
  298. .and_then(|a| a.as_object())
  299. .map(|m| m.values().cloned().collect::<Vec<_>>())
  300. .unwrap_or_default();
  301. serde_json::to_string(&accounts_obj).map_err(|e| format!("Serialize failed: {}", e))
  302. }
  303. #[tauri::command]
  304. pub async fn delete_project_steam_account(steamUsername: String) -> Result<(), String> {
  305. let steam_msg_dir = get_steam_msg_dir()?;
  306. let accounts_file = steam_msg_dir.join(ACCOUNTS_JSON);
  307. if accounts_file.exists() {
  308. let text = fs::read_to_string(&accounts_file).map_err(|e| format!("Read accounts file failed: {}", e))?;
  309. let mut root: serde_json::Value = serde_json::from_str(&text).unwrap_or(serde_json::json!({ "accounts": {} }));
  310. if let Some(accounts) = root.get_mut("accounts").and_then(|a| a.as_object_mut()) {
  311. accounts.remove(&steamUsername);
  312. }
  313. let content = serde_json::to_string_pretty(&root).map_err(|e| format!("Serialize failed: {}", e))?;
  314. fs::write(&accounts_file, content).map_err(|e| format!("Failed to write accounts json: {}", e))?;
  315. }
  316. let safe_name = sanitize_username_for_filename(&steamUsername);
  317. let mafile_path = steam_msg_dir.join(format!("{}.maFile", safe_name));
  318. if mafile_path.exists() {
  319. let _ = fs::remove_file(&mafile_path);
  320. }
  321. Ok(())
  322. }
  323. /// 更新账号的是否美国账号属性
  324. #[tauri::command]
  325. pub async fn update_account_is_us_account(
  326. steamUsername: String,
  327. isUsAccount: bool,
  328. ) -> Result<(), String> {
  329. let steam_msg_dir = get_steam_msg_dir()?;
  330. let accounts_file = steam_msg_dir.join(ACCOUNTS_JSON);
  331. if !accounts_file.exists() {
  332. return Err("账号文件不存在".to_string());
  333. }
  334. let text = fs::read_to_string(&accounts_file).map_err(|e| format!("读取账号文件失败: {}", e))?;
  335. let mut root: serde_json::Value =
  336. serde_json::from_str(&text).unwrap_or(serde_json::json!({ "accounts": {} }));
  337. let accounts = root
  338. .get_mut("accounts")
  339. .and_then(|a| a.as_object_mut())
  340. .ok_or("账号数据结构异常")?;
  341. let entry = accounts
  342. .get_mut(&steamUsername)
  343. .and_then(|v| v.as_object_mut())
  344. .ok_or_else(|| format!("未找到账号 {}", steamUsername))?;
  345. entry.insert(
  346. "is_us_account".to_string(),
  347. serde_json::json!(isUsAccount),
  348. );
  349. let content =
  350. serde_json::to_string_pretty(&root).map_err(|e| format!("序列化失败: {}", e))?;
  351. fs::write(&accounts_file, content).map_err(|e| format!("写入账号文件失败: {}", e))?;
  352. Ok(())
  353. }
  354. /// 更新账号的充值卡价比例
  355. #[tauri::command]
  356. pub async fn update_account_card_rate(
  357. steamUsername: String,
  358. cardRate: Option<f64>,
  359. ) -> Result<(), String> {
  360. let steam_msg_dir = get_steam_msg_dir()?;
  361. let accounts_file = steam_msg_dir.join(ACCOUNTS_JSON);
  362. if !accounts_file.exists() {
  363. return Err("账号文件不存在".to_string());
  364. }
  365. let text = fs::read_to_string(&accounts_file).map_err(|e| format!("读取账号文件失败: {}", e))?;
  366. let mut root: serde_json::Value =
  367. serde_json::from_str(&text).unwrap_or(serde_json::json!({ "accounts": {} }));
  368. let accounts = root
  369. .get_mut("accounts")
  370. .and_then(|a| a.as_object_mut())
  371. .ok_or("账号数据结构异常")?;
  372. let entry = accounts
  373. .get_mut(&steamUsername)
  374. .and_then(|v| v.as_object_mut())
  375. .ok_or_else(|| format!("未找到账号 {}", steamUsername))?;
  376. match cardRate {
  377. Some(rate) => {
  378. entry.insert(
  379. "card_rate".to_string(),
  380. serde_json::json!(rate),
  381. );
  382. }
  383. None => {
  384. entry.remove("card_rate");
  385. }
  386. }
  387. let content =
  388. serde_json::to_string_pretty(&root).map_err(|e| format!("序列化失败: {}", e))?;
  389. fs::write(&accounts_file, content).map_err(|e| format!("写入账号文件失败: {}", e))?;
  390. Ok(())
  391. }
  392. /// 更新账号的是否参与扫货状态
  393. #[tauri::command]
  394. pub async fn update_account_is_active(
  395. steamUsername: String,
  396. isActive: bool,
  397. ) -> Result<(), String> {
  398. let steam_msg_dir = get_steam_msg_dir()?;
  399. let accounts_file = steam_msg_dir.join(ACCOUNTS_JSON);
  400. if !accounts_file.exists() {
  401. return Err("账号文件不存在".to_string());
  402. }
  403. let text = fs::read_to_string(&accounts_file).map_err(|e| format!("读取账号文件失败: {}", e))?;
  404. let mut root: serde_json::Value =
  405. serde_json::from_str(&text).unwrap_or(serde_json::json!({ "accounts": {} }));
  406. let accounts = root
  407. .get_mut("accounts")
  408. .and_then(|a| a.as_object_mut())
  409. .ok_or("账号数据结构异常")?;
  410. let entry = accounts
  411. .get_mut(&steamUsername)
  412. .and_then(|v| v.as_object_mut())
  413. .ok_or_else(|| format!("未找到账号 {}", steamUsername))?;
  414. entry.insert(
  415. "is_active".to_string(),
  416. serde_json::json!(isActive),
  417. );
  418. let content =
  419. serde_json::to_string_pretty(&root).map_err(|e| format!("序列化失败: {}", e))?;
  420. fs::write(&accounts_file, content).map_err(|e| format!("写入账号文件失败: {}", e))?;
  421. Ok(())
  422. }
  423. /// 将指定账号的 maFile 复制到用户选择的路径(下载 maFile)
  424. #[tauri::command]
  425. pub async fn export_mafile_to_path(steam_username: String, dest_path: String) -> Result<(), String> {
  426. let steam_msg_dir = get_steam_msg_dir()?;
  427. let safe_name = sanitize_username_for_filename(&steam_username);
  428. let src = steam_msg_dir.join(format!("{}.maFile", safe_name));
  429. if !src.exists() {
  430. return Err(format!("未找到账号 {} 的 maFile", steam_username));
  431. }
  432. let dest = PathBuf::from(&dest_path);
  433. fs::copy(&src, &dest).map_err(|e| format!("复制 maFile 失败: {}", e))?;
  434. Ok(())
  435. }
  436. /// 在 `accounts` 对象中解析与后台绑定名、界面传入名一致的存储键(精确匹配 → 忽略大小写 → 匹配条目内 `steam_account`)。
  437. fn resolve_accounts_storage_key(
  438. accounts: &serde_json::Map<String, serde_json::Value>,
  439. hint: &str,
  440. ) -> Option<String> {
  441. let hint = hint.trim();
  442. if hint.is_empty() {
  443. return None;
  444. }
  445. if accounts.contains_key(hint) {
  446. return Some(hint.to_string());
  447. }
  448. for (k, v) in accounts {
  449. if k.trim().eq_ignore_ascii_case(hint) {
  450. return Some(k.clone());
  451. }
  452. if let Some(sa) = v.get("steam_account").and_then(|x| x.as_str()) {
  453. if sa.trim().eq_ignore_ascii_case(hint) {
  454. return Some(k.clone());
  455. }
  456. }
  457. }
  458. None
  459. }
  460. /// 与界面「查询余额」按钮同源:读 `accounts.json`、POST 本地 steamAPI `wallet-balance`(含代理重试)。
  461. pub(super) async fn steam_wallet_balance_fetch(lookup_username: &str) -> Result<String, String> {
  462. use serde_json::Value;
  463. let steam_msg_dir = get_steam_msg_dir()?;
  464. let accounts_file = steam_msg_dir.join(ACCOUNTS_JSON);
  465. if !accounts_file.exists() {
  466. return Err("本地账号文件不存在".to_string());
  467. }
  468. let text = fs::read_to_string(&accounts_file).map_err(|e| format!("读取账号文件失败: {}", e))?;
  469. let root: Value = serde_json::from_str(&text).map_err(|e| format!("账号文件格式错误: {}", e))?;
  470. let accounts = root.get("accounts").and_then(|a| a.as_object()).ok_or("账号文件结构错误")?;
  471. let storage_key = resolve_accounts_storage_key(accounts, lookup_username).ok_or_else(|| {
  472. format!(
  473. "未找到账号 {}(请确认 accounts.json 中键名或 steam_account 与后台绑定一致)",
  474. lookup_username
  475. )
  476. })?;
  477. let obj = accounts
  478. .get(&storage_key)
  479. .ok_or_else(|| format!("未找到账号条目 {}", storage_key))?;
  480. let steam_login_secure = obj
  481. .get("steamLoginSecure")
  482. .and_then(|v| v.as_str())
  483. .filter(|s| !s.is_empty())
  484. .ok_or_else(|| format!("账号 {} 暂无登录凭证,请先进行「账号验证绑定」", lookup_username))?;
  485. let client = get_http_client();
  486. let api_url = "http://localhost:5000/wallet-balance";
  487. let request_body = serde_json::json!({
  488. "SteamLoginSecure": steam_login_secure
  489. });
  490. let (status, body) = post_steam_api_with_proxy_retry(
  491. &client,
  492. api_url,
  493. &request_body,
  494. "wallet-balance",
  495. )
  496. .await?;
  497. if !status.is_success() {
  498. return Err(format!("steamAPI 余额接口返回错误: {} {}", status.as_u16(), body));
  499. }
  500. let json: Value = serde_json::from_str(&body).map_err(|e| format!("响应解析失败: {}", e))?;
  501. let amount = json
  502. .get("amount")
  503. .and_then(|v| v.as_str())
  504. .unwrap_or("0")
  505. .to_string();
  506. let currency = json
  507. .get("currency")
  508. .and_then(|v| v.as_str())
  509. .unwrap_or("USD")
  510. .to_string();
  511. let out = serde_json::json!({ "amount": amount, "currency": currency });
  512. serde_json::to_string(&out).map_err(|e| format!("序列化失败: {}", e))
  513. }
  514. /// 通过本地 steamAPI 获取账号余额(由 steamAPI 统一处理 Steam 出网代理)
  515. #[tauri::command]
  516. pub async fn get_steam_wallet_balance(steam_username: String) -> Result<String, String> {
  517. steam_wallet_balance_fetch(&steam_username).await
  518. }
  519. /// IP 代理配置文件名(与 accounts.json 同目录,仅存本地)
  520. const PROXY_CONFIG_JSON: &str = "proxy_config.json";
  521. static API_PROXY_CACHE: OnceLock<Mutex<Option<String>>> = OnceLock::new();
  522. #[derive(Debug, Clone, Copy, PartialEq, Eq)]
  523. enum ProxyModeKind {
  524. None,
  525. Api,
  526. Account,
  527. }
  528. #[derive(Debug, Clone)]
  529. struct ProxySelection {
  530. mode: ProxyModeKind,
  531. proxy_url: Option<String>,
  532. }
  533. #[derive(Debug, Default, Deserialize)]
  534. struct StoredProxyConfig {
  535. #[serde(rename = "effectiveBrandId")]
  536. effective_brand_id: Option<String>,
  537. #[serde(rename = "effectiveMode")]
  538. effective_mode: Option<String>,
  539. configs: Option<std::collections::HashMap<String, StoredBrandConfig>>,
  540. }
  541. #[derive(Debug, Default, Deserialize, Clone)]
  542. struct StoredBrandConfig {
  543. #[serde(rename = "apiUrl")]
  544. api_url: Option<String>,
  545. #[serde(rename = "serverHost")]
  546. server_host: Option<String>,
  547. #[serde(rename = "serverPort")]
  548. server_port: Option<u16>,
  549. #[serde(rename = "serverUsername")]
  550. server_username: Option<String>,
  551. #[serde(rename = "serverPassword")]
  552. server_password: Option<String>,
  553. }
  554. #[derive(Debug, Deserialize)]
  555. struct ProxyTestRequest {
  556. mode: String,
  557. protocol: String,
  558. #[serde(rename = "apiUrl")]
  559. api_url: Option<String>,
  560. #[serde(rename = "serverHost")]
  561. server_host: Option<String>,
  562. #[serde(rename = "serverPort")]
  563. server_port: Option<u16>,
  564. #[serde(rename = "serverUsername")]
  565. server_username: Option<String>,
  566. #[serde(rename = "serverPassword")]
  567. server_password: Option<String>,
  568. }
  569. #[derive(Debug, Serialize)]
  570. struct ProxyTestResponse {
  571. success: bool,
  572. logs: Vec<String>,
  573. error: Option<String>,
  574. }
  575. fn api_proxy_cache() -> &'static Mutex<Option<String>> {
  576. API_PROXY_CACHE.get_or_init(|| Mutex::new(None))
  577. }
  578. fn clear_api_proxy_cache() {
  579. if let Ok(mut cache) = api_proxy_cache().lock() {
  580. *cache = None;
  581. }
  582. }
  583. /// 扫货多路并行时,每路 worker 使用独立缓存,避免多路共用一个 API 代理出口。
  584. fn clear_sweep_worker_api_proxy_cache(slot: &std::sync::Arc<Mutex<Option<String>>>) {
  585. if let Ok(mut g) = slot.lock() {
  586. *g = None;
  587. }
  588. }
  589. fn is_proxy_related_failure(
  590. status: Option<reqwest::StatusCode>,
  591. err_msg: Option<&str>,
  592. body: Option<&str>,
  593. ) -> bool {
  594. if let Some(s) = status {
  595. if s == reqwest::StatusCode::PROXY_AUTHENTICATION_REQUIRED
  596. || s == reqwest::StatusCode::BAD_GATEWAY
  597. || s == reqwest::StatusCode::SERVICE_UNAVAILABLE
  598. || s == reqwest::StatusCode::GATEWAY_TIMEOUT
  599. || s == reqwest::StatusCode::TOO_MANY_REQUESTS
  600. {
  601. return true;
  602. }
  603. }
  604. let mut text = String::new();
  605. if let Some(msg) = err_msg {
  606. text.push_str(msg);
  607. text.push(' ');
  608. }
  609. if let Some(resp) = body {
  610. text.push_str(resp);
  611. }
  612. let text = text.to_lowercase();
  613. [
  614. "proxy",
  615. "tunnel",
  616. "timed out",
  617. "timeout",
  618. "connection refused",
  619. "connection reset",
  620. "failed to connect",
  621. "unreachable",
  622. "network is unreachable",
  623. "name or service not known",
  624. ]
  625. .iter()
  626. .any(|token| text.contains(token))
  627. }
  628. /// 解析单行代理 URL
  629. fn normalize_proxy_url(raw: &str) -> Option<String> {
  630. let value = raw.trim().trim_matches('"').trim_matches('\'');
  631. if value.is_empty() {
  632. return None;
  633. }
  634. if value.starts_with("http://") || value.starts_with("https://") {
  635. return Some(value.to_string());
  636. }
  637. if value.contains(':') {
  638. return Some(format!("http://{}", value));
  639. }
  640. None
  641. }
  642. /// 解析 API 返回的多行代理列表(每行一个 IP:Port)
  643. fn parse_multi_line_proxies(body: &str) -> Vec<String> {
  644. let trimmed = body.trim();
  645. if trimmed.is_empty() {
  646. return Vec::new();
  647. }
  648. let mut proxies = Vec::new();
  649. for line in trimmed.lines() {
  650. let line = line.trim();
  651. if line.is_empty() {
  652. continue;
  653. }
  654. // 跳过 JSON 响应
  655. if line.starts_with('{') || line.starts_with('[') || line.starts_with('{') {
  656. continue;
  657. }
  658. // 尝试解析 IP:Port 格式
  659. if let Some(proxy) = normalize_proxy_url(line) {
  660. proxies.push(proxy);
  661. }
  662. }
  663. proxies
  664. }
  665. fn normalize_proxy_url_with_protocol(raw: &str, protocol: &str) -> Option<String> {
  666. let value = raw.trim().trim_matches('"').trim_matches('\'');
  667. if value.is_empty() {
  668. return None;
  669. }
  670. if value.starts_with("http://") || value.starts_with("https://") || value.starts_with("socks5://") {
  671. return Some(value.to_string());
  672. }
  673. if !value.contains(':') {
  674. return None;
  675. }
  676. let scheme = if protocol.eq_ignore_ascii_case("socks5") {
  677. "socks5"
  678. } else {
  679. "http"
  680. };
  681. Some(format!("{}://{}", scheme, value))
  682. }
  683. // ============================================================================
  684. // 代理池:支持批量获取代理,自动从 API 拉取并缓存
  685. // ============================================================================
  686. /// 代理池容量阈值:少于此数量时自动从 API 获取新的代理
  687. const PROXY_POOL_LOW_THRESHOLD: usize = 20;
  688. /// 代理池每次从 API 获取的数量
  689. const PROXY_POOL_FETCH_COUNT: usize = 100;
  690. /// 代理池全局状态
  691. static PROXY_POOL: OnceLock<Mutex<ProxyPool>> = OnceLock::new();
  692. /// 保护扫货成功文件写入的全局锁
  693. static SWEEP_SUCCESS_FILE_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
  694. fn get_sweep_success_file_lock() -> &'static Mutex<()> {
  695. SWEEP_SUCCESS_FILE_LOCK.get_or_init(|| Mutex::new(()))
  696. }
  697. /// 获取代理池实例(线程安全)
  698. fn get_proxy_pool() -> &'static Mutex<ProxyPool> {
  699. PROXY_POOL.get_or_init(|| Mutex::new(ProxyPool::new()))
  700. }
  701. /// 代理池结构
  702. struct ProxyPool {
  703. /// 可用的代理列表
  704. proxies: Vec<String>,
  705. /// 正在使用的代理(避免重复使用)
  706. in_use: HashMap<String, usize>, // proxy_url -> 使用计数
  707. }
  708. impl ProxyPool {
  709. fn new() -> Self {
  710. Self {
  711. proxies: Vec::new(),
  712. in_use: HashMap::new(),
  713. }
  714. }
  715. /// 获取一个可用代理(优先获取最少使用的)
  716. fn get_proxy(&mut self) -> Option<String> {
  717. if self.proxies.is_empty() {
  718. return None;
  719. }
  720. // 按使用次数排序,取最少的那个
  721. self.proxies.sort_by(|a, b| {
  722. let count_a = self.in_use.get(a).unwrap_or(&0);
  723. let count_b = self.in_use.get(b).unwrap_or(&0);
  724. count_a.cmp(count_b)
  725. });
  726. if let Some(proxy) = self.proxies.pop() {
  727. *self.in_use.entry(proxy.clone()).or_insert(0) += 1;
  728. return Some(proxy);
  729. }
  730. None
  731. }
  732. /// 归还代理(成功使用后调用,减少使用计数)
  733. fn return_proxy(&mut self, proxy: &str) {
  734. if let Some(count) = self.in_use.get_mut(proxy) {
  735. if *count > 0 {
  736. *count -= 1;
  737. }
  738. }
  739. }
  740. /// 标记代理为失效(被封禁等),从池中移除
  741. fn mark_proxy_invalid(&mut self, proxy: &str) {
  742. self.proxies.retain(|p| p != proxy);
  743. self.in_use.remove(proxy);
  744. }
  745. /// 添加多个代理到池中
  746. fn add_proxies(&mut self, proxies: Vec<String>) {
  747. for proxy in proxies {
  748. if !self.in_use.contains_key(&proxy) && !self.proxies.contains(&proxy) {
  749. self.proxies.push(proxy);
  750. }
  751. }
  752. }
  753. /// 检查是否需要补充代理
  754. fn needs_refill(&self) -> bool {
  755. self.proxies.len() < PROXY_POOL_LOW_THRESHOLD
  756. }
  757. /// 获取当前可用代理数量
  758. fn available_count(&self) -> usize {
  759. self.proxies.len()
  760. }
  761. }
  762. fn parse_proxy_api_response(body: &str) -> Option<String> {
  763. let trimmed = body.trim();
  764. if trimmed.is_empty() {
  765. return None;
  766. }
  767. if let Ok(json) = serde_json::from_str::<serde_json::Value>(trimmed) {
  768. if let Some(s) = json.as_str() {
  769. return normalize_proxy_url(s);
  770. }
  771. if let Some(obj) = json.as_object() {
  772. for key in ["proxyUrl", "proxy_url", "url", "proxy"] {
  773. if let Some(v) = obj.get(key).and_then(|x| x.as_str()) {
  774. if let Some(proxy) = normalize_proxy_url(v) {
  775. return Some(proxy);
  776. }
  777. }
  778. }
  779. let host = obj
  780. .get("ip")
  781. .and_then(|x| x.as_str())
  782. .or_else(|| obj.get("host").and_then(|x| x.as_str()))
  783. .or_else(|| obj.get("server").and_then(|x| x.as_str()));
  784. let port = obj
  785. .get("port")
  786. .and_then(|x| x.as_u64())
  787. .or_else(|| obj.get("serverPort").and_then(|x| x.as_u64()));
  788. if let (Some(h), Some(p)) = (host, port) {
  789. return normalize_proxy_url(&format!("{}:{}", h, p));
  790. }
  791. }
  792. }
  793. for line in trimmed.lines() {
  794. let candidate = line.trim().split(',').next().unwrap_or("").trim();
  795. if let Some(proxy) = normalize_proxy_url(candidate) {
  796. return Some(proxy);
  797. }
  798. }
  799. None
  800. }
  801. fn parse_proxy_api_response_by_protocol(body: &str, protocol: &str) -> Option<String> {
  802. let trimmed = body.trim();
  803. if trimmed.is_empty() {
  804. return None;
  805. }
  806. if let Ok(json) = serde_json::from_str::<serde_json::Value>(trimmed) {
  807. if let Some(s) = json.as_str() {
  808. return normalize_proxy_url_with_protocol(s, protocol);
  809. }
  810. if let Some(obj) = json.as_object() {
  811. for key in ["proxyUrl", "proxy_url", "url", "proxy"] {
  812. if let Some(v) = obj.get(key).and_then(|x| x.as_str()) {
  813. if let Some(proxy) = normalize_proxy_url_with_protocol(v, protocol) {
  814. return Some(proxy);
  815. }
  816. }
  817. }
  818. let host = obj
  819. .get("ip")
  820. .and_then(|x| x.as_str())
  821. .or_else(|| obj.get("host").and_then(|x| x.as_str()))
  822. .or_else(|| obj.get("server").and_then(|x| x.as_str()));
  823. let port = obj
  824. .get("port")
  825. .and_then(|x| x.as_u64())
  826. .or_else(|| obj.get("serverPort").and_then(|x| x.as_u64()));
  827. if let (Some(h), Some(p)) = (host, port) {
  828. return normalize_proxy_url_with_protocol(&format!("{}:{}", h, p), protocol);
  829. }
  830. }
  831. }
  832. for line in trimmed.lines() {
  833. let candidate = line.trim().split(',').next().unwrap_or("").trim();
  834. if let Some(proxy) = normalize_proxy_url_with_protocol(candidate, protocol) {
  835. return Some(proxy);
  836. }
  837. }
  838. None
  839. }
  840. fn clip_text(value: &str, max_chars: usize) -> String {
  841. value.chars().take(max_chars).collect::<String>()
  842. }
  843. async fn fetch_public_ip_via_client(client: &reqwest::Client) -> Result<String, String> {
  844. let ip_services = [
  845. "https://api.ipify.org?format=json",
  846. "https://httpbin.org/ip",
  847. "https://ifconfig.me/ip",
  848. ];
  849. let mut last_error = String::new();
  850. for url in ip_services {
  851. let resp = match client.get(url).send().await {
  852. Ok(v) => v,
  853. Err(e) => {
  854. last_error = format!("请求 {} 失败: {}", url, e);
  855. continue;
  856. }
  857. };
  858. let status = resp.status();
  859. let body = resp
  860. .text()
  861. .await
  862. .map_err(|e| format!("读取 IP 检测响应失败: {}", e))?;
  863. if !status.is_success() {
  864. last_error = format!("请求 {} 返回 {}: {}", url, status.as_u16(), clip_text(&body, 220));
  865. continue;
  866. }
  867. if let Ok(json) = serde_json::from_str::<serde_json::Value>(&body) {
  868. if let Some(ip) = json.get("ip").and_then(|v| v.as_str()) {
  869. return Ok(ip.to_string());
  870. }
  871. if let Some(origin) = json.get("origin").and_then(|v| v.as_str()) {
  872. return Ok(origin.to_string());
  873. }
  874. }
  875. let plain_ip = body.trim().to_string();
  876. if !plain_ip.is_empty() {
  877. return Ok(plain_ip);
  878. }
  879. }
  880. if last_error.is_empty() {
  881. Err("无法从公网 IP 检测服务获取结果".to_string())
  882. } else {
  883. Err(last_error)
  884. }
  885. }
  886. fn build_account_proxy_url(req: &ProxyTestRequest) -> Result<String, String> {
  887. let host = req.server_host.clone().unwrap_or_default().trim().to_string();
  888. let port = req.server_port.unwrap_or_default();
  889. if host.is_empty() || port == 0 {
  890. return Err("账号密码方式配置不完整:请填写代理服务器和端口".to_string());
  891. }
  892. let scheme = if req.protocol.eq_ignore_ascii_case("socks5") {
  893. "socks5"
  894. } else {
  895. "http"
  896. };
  897. let username = req.server_username.clone().unwrap_or_default();
  898. let password = req.server_password.clone().unwrap_or_default();
  899. if username.trim().is_empty() {
  900. Ok(format!("{}://{}:{}", scheme, host, port))
  901. } else {
  902. Ok(format!("{}://{}:{}@{}:{}", scheme, username, password, host, port))
  903. }
  904. }
  905. #[tauri::command]
  906. pub async fn test_proxy_configuration(payload: String) -> Result<String, String> {
  907. let req: ProxyTestRequest =
  908. serde_json::from_str(&payload).map_err(|e| format!("测试参数格式错误: {}", e))?;
  909. let mut logs: Vec<String> = Vec::new();
  910. logs.push(format!(
  911. "开始测试,模式: {},协议: {}",
  912. req.mode,
  913. req.protocol
  914. ));
  915. let test_result: Result<(), String> = async {
  916. let protocol = req.protocol.to_lowercase();
  917. if protocol != "http" && protocol != "socks5" {
  918. return Err("不支持的代理协议,仅支持 http/socks5".to_string());
  919. }
  920. let proxy_url = if req.mode.eq_ignore_ascii_case("api") {
  921. let api_url = req.api_url.clone().unwrap_or_default();
  922. if api_url.trim().is_empty() {
  923. return Err("API 方式配置不完整:请填写 API 地址".to_string());
  924. }
  925. logs.push(format!("步骤1/3:请求代理 API -> {}", api_url));
  926. let client = reqwest::Client::builder()
  927. .timeout(Duration::from_secs(12))
  928. .build()
  929. .map_err(|e| format!("创建请求客户端失败: {}", e))?;
  930. let resp = client
  931. .get(&api_url)
  932. .send()
  933. .await
  934. .map_err(|e| format!("请求代理 API 失败: {}", e))?;
  935. let status = resp.status();
  936. let body = resp
  937. .text()
  938. .await
  939. .map_err(|e| format!("读取代理 API 响应失败: {}", e))?;
  940. logs.push(format!("代理 API 响应状态: {}", status.as_u16()));
  941. logs.push(format!("代理 API 响应内容: {}", clip_text(&body, 500)));
  942. if !status.is_success() {
  943. return Err(format!(
  944. "代理 API 返回错误: {} {}",
  945. status.as_u16(),
  946. clip_text(&body, 220)
  947. ));
  948. }
  949. let parsed = parse_proxy_api_response_by_protocol(&body, &protocol)
  950. .ok_or_else(|| "代理 API 返回内容无法解析为代理地址,请检查接口文档".to_string())?;
  951. logs.push(format!("步骤2/3:解析到代理地址 -> {}", parsed));
  952. parsed
  953. } else if req.mode.eq_ignore_ascii_case("account") {
  954. logs.push("步骤1/3:组装账号密码代理地址".to_string());
  955. let built = build_account_proxy_url(&req)?;
  956. logs.push(format!("步骤2/3:已生成代理地址 -> {}", built));
  957. built
  958. } else {
  959. return Err("不支持的测试模式,仅支持 api/account".to_string());
  960. };
  961. logs.push("步骤3/3:通过代理请求公网 IP 检测服务".to_string());
  962. let proxy = reqwest::Proxy::all(&proxy_url)
  963. .map_err(|e| format!("代理地址格式错误,无法创建客户端: {}", e))?;
  964. let test_client = reqwest::Client::builder()
  965. .proxy(proxy)
  966. .timeout(Duration::from_secs(16))
  967. .build()
  968. .map_err(|e| format!("创建代理测试客户端失败: {}", e))?;
  969. let ip = fetch_public_ip_via_client(&test_client).await?;
  970. logs.push(format!("代理可用,出口 IP: {}", ip));
  971. Ok(())
  972. }
  973. .await;
  974. let response = match test_result {
  975. Ok(()) => ProxyTestResponse {
  976. success: true,
  977. logs,
  978. error: None,
  979. },
  980. Err(err) => {
  981. logs.push(format!("测试失败: {}", err));
  982. ProxyTestResponse {
  983. success: false,
  984. logs,
  985. error: Some(err),
  986. }
  987. }
  988. };
  989. serde_json::to_string(&response).map_err(|e| format!("序列化测试结果失败: {}", e))
  990. }
  991. fn load_stored_proxy_config() -> Result<StoredProxyConfig, String> {
  992. let steam_msg_dir = get_steam_msg_dir()?;
  993. let path = steam_msg_dir.join(PROXY_CONFIG_JSON);
  994. if !path.exists() {
  995. return Ok(StoredProxyConfig::default());
  996. }
  997. let raw = fs::read_to_string(&path).map_err(|e| format!("读取代理配置失败: {}", e))?;
  998. serde_json::from_str::<StoredProxyConfig>(&raw).map_err(|e| format!("代理配置格式错误: {}", e))
  999. }
  1000. /// 获取当前生效的代理 API URL(供 Worker 独立代理池使用)
  1001. pub fn get_effective_proxy_api_url() -> Option<String> {
  1002. let stored = load_stored_proxy_config().ok()?;
  1003. let mode = stored.effective_mode?;
  1004. if mode != "api" {
  1005. return None;
  1006. }
  1007. let brand_id = stored.effective_brand_id?;
  1008. let configs = stored.configs?;
  1009. let brand = configs.get(&brand_id)?;
  1010. let api_url = brand.api_url.clone()?;
  1011. if api_url.trim().is_empty() {
  1012. return None;
  1013. }
  1014. Some(api_url)
  1015. }
  1016. /// WorkerProxyPool 专用的获取代理函数(获取一个代理)
  1017. pub async fn fetch_one_proxy_for_worker() -> Result<String, String> {
  1018. let api_url = get_effective_proxy_api_url()
  1019. .ok_or_else(|| "未配置代理 API".to_string())?;
  1020. let client = reqwest::Client::builder()
  1021. .timeout(Duration::from_secs(12))
  1022. .build()
  1023. .map_err(|e| format!("创建代理API客户端失败: {}", e))?;
  1024. let resp = client
  1025. .get(&api_url)
  1026. .send()
  1027. .await
  1028. .map_err(|e| format!("请求代理API失败: {}", e))?;
  1029. let status = resp.status();
  1030. let body = resp.text().await.map_err(|e| format!("读取代理API响应失败: {}", e))?;
  1031. if !status.is_success() {
  1032. return Err(format!("代理API返回错误: {} {}", status.as_u16(), body));
  1033. }
  1034. let proxies = parse_multi_line_proxies(&body);
  1035. if proxies.is_empty() {
  1036. return Err("代理API返回内容无法解析".to_string());
  1037. }
  1038. // 返回第一个代理
  1039. Ok(proxies.into_iter().next().unwrap())
  1040. }
  1041. async fn fetch_api_proxy_url(api_url: &str) -> Result<String, String> {
  1042. let client = reqwest::Client::builder()
  1043. .timeout(Duration::from_secs(12))
  1044. .build()
  1045. .map_err(|e| format!("创建代理API客户端失败: {}", e))?;
  1046. let resp = client
  1047. .get(api_url)
  1048. .send()
  1049. .await
  1050. .map_err(|e| format!("请求代理API失败: {}", e))?;
  1051. let status = resp.status();
  1052. let body = resp.text().await.map_err(|e| format!("读取代理API响应失败: {}", e))?;
  1053. if !status.is_success() {
  1054. return Err(format!("代理API返回错误: {} {}", status.as_u16(), body));
  1055. }
  1056. // 解析多行代理列表
  1057. let proxies = parse_multi_line_proxies(&body);
  1058. let proxies_count = proxies.len();
  1059. if proxies.is_empty() {
  1060. // 降级:尝试旧解析方式
  1061. return parse_proxy_api_response(&body)
  1062. .ok_or_else(|| "代理API返回内容无法解析为 HTTP 代理".to_string());
  1063. }
  1064. // 添加到代理池
  1065. let pool = get_proxy_pool();
  1066. if let Ok(mut guard) = pool.lock() {
  1067. guard.add_proxies(proxies);
  1068. info!(
  1069. "[proxy-pool] 从 API 获取了 {} 个代理,池中现有 {} 个可用",
  1070. proxies_count,
  1071. guard.available_count()
  1072. );
  1073. }
  1074. // 从池中获取一个代理
  1075. let pool = get_proxy_pool();
  1076. if let Ok(mut guard) = pool.lock() {
  1077. if let Some(proxy) = guard.get_proxy() {
  1078. return Ok(proxy);
  1079. }
  1080. }
  1081. Err("代理池为空,无法获取代理".to_string())
  1082. }
  1083. async fn resolve_local_http_proxy(force_refresh_api: bool) -> Result<ProxySelection, String> {
  1084. resolve_local_http_proxy_for_sweep(force_refresh_api, None).await
  1085. }
  1086. /// `cache_slot`:`Some` 时使用该互斥缓存(扫货每路 worker 独立);`None` 时使用全局 `API_PROXY_CACHE`。
  1087. async fn resolve_local_http_proxy_for_sweep(
  1088. force_refresh_api: bool,
  1089. cache_slot: Option<&std::sync::Arc<Mutex<Option<String>>>>,
  1090. ) -> Result<ProxySelection, String> {
  1091. let stored = load_stored_proxy_config()?;
  1092. let mode_raw = stored.effective_mode.unwrap_or_default();
  1093. let mode = match mode_raw.as_str() {
  1094. "api" => ProxyModeKind::Api,
  1095. "account" => ProxyModeKind::Account,
  1096. _ => ProxyModeKind::None,
  1097. };
  1098. if mode == ProxyModeKind::None {
  1099. return Ok(ProxySelection {
  1100. mode,
  1101. proxy_url: None,
  1102. });
  1103. }
  1104. let brand_id = stored.effective_brand_id.unwrap_or_default();
  1105. let brand = stored
  1106. .configs
  1107. .as_ref()
  1108. .and_then(|map| map.get(&brand_id))
  1109. .cloned()
  1110. .ok_or_else(|| "未找到当前生效品牌的代理配置".to_string())?;
  1111. if mode == ProxyModeKind::Account {
  1112. let host = brand.server_host.unwrap_or_default();
  1113. let port = brand.server_port.unwrap_or_default();
  1114. if host.trim().is_empty() || port == 0 {
  1115. return Err("账号密码代理配置不完整:请检查代理服务器和端口".to_string());
  1116. }
  1117. let user = brand.server_username.unwrap_or_default();
  1118. let pass = brand.server_password.unwrap_or_default();
  1119. let proxy_url = if user.is_empty() {
  1120. format!("http://{}:{}", host, port)
  1121. } else {
  1122. format!("http://{}:{}@{}:{}", user, pass, host, port)
  1123. };
  1124. return Ok(ProxySelection {
  1125. mode,
  1126. proxy_url: Some(proxy_url),
  1127. });
  1128. }
  1129. if !force_refresh_api {
  1130. if let Some(slot) = cache_slot {
  1131. if let Ok(cache) = slot.lock() {
  1132. if let Some(cached) = cache.as_ref() {
  1133. return Ok(ProxySelection {
  1134. mode,
  1135. proxy_url: Some(cached.clone()),
  1136. });
  1137. }
  1138. }
  1139. } else if let Ok(cache) = api_proxy_cache().lock() {
  1140. if let Some(cached) = cache.as_ref() {
  1141. return Ok(ProxySelection {
  1142. mode,
  1143. proxy_url: Some(cached.clone()),
  1144. });
  1145. }
  1146. }
  1147. }
  1148. let api_url = brand.api_url.unwrap_or_default();
  1149. if api_url.trim().is_empty() {
  1150. return Err("API代理配置不完整:请填写 API 地址".to_string());
  1151. }
  1152. // 检查代理池是否需要补充(少于阈值时自动补充)
  1153. let pool = get_proxy_pool();
  1154. let should_refill = if let Ok(guard) = pool.lock() {
  1155. guard.needs_refill()
  1156. } else {
  1157. true
  1158. };
  1159. if should_refill {
  1160. info!("[proxy-pool] 代理池数量不足(<{}),自动从 API 补充", PROXY_POOL_LOW_THRESHOLD);
  1161. // 预先调用 API 补充代理池(异步,带重试机制)
  1162. let api_url_clone = api_url.clone();
  1163. tokio::spawn(async move {
  1164. refill_proxy_pool_with_retry(&api_url_clone, 3).await;
  1165. });
  1166. }
  1167. // 从池中获取代理,如果没有则调用 API
  1168. let proxy_url = get_proxy_from_pool_or_fetch(&api_url).await?;
  1169. if let Some(slot) = cache_slot {
  1170. if let Ok(mut cache) = slot.lock() {
  1171. *cache = Some(proxy_url.clone());
  1172. }
  1173. } else if let Ok(mut cache) = api_proxy_cache().lock() {
  1174. *cache = Some(proxy_url.clone());
  1175. }
  1176. Ok(ProxySelection {
  1177. mode,
  1178. proxy_url: Some(proxy_url),
  1179. })
  1180. }
  1181. /// 带重试机制的代理池补充(异步)
  1182. async fn refill_proxy_pool_with_retry(api_url: &str, max_retries: usize) {
  1183. for attempt in 1..=max_retries {
  1184. match fetch_api_proxy_url(api_url).await {
  1185. Ok(_) => {
  1186. info!("[proxy-pool] 异步补充代理成功");
  1187. return;
  1188. }
  1189. Err(e) => {
  1190. if attempt < max_retries {
  1191. warn!("[proxy-pool] 异步补充代理失败 (尝试 {}/{}): {},2秒后重试",
  1192. attempt, max_retries, e);
  1193. tokio::time::sleep(Duration::from_secs(2)).await;
  1194. } else {
  1195. error!("[proxy-pool] 异步补充代理最终失败 (尝试 {}/{}): {}",
  1196. attempt, max_retries, e);
  1197. }
  1198. }
  1199. }
  1200. }
  1201. }
  1202. /// 从代理池获取代理,如果没有则调用 API 获取
  1203. async fn get_proxy_from_pool_or_fetch(api_url: &str) -> Result<String, String> {
  1204. // 先尝试从池中获取
  1205. {
  1206. let pool = get_proxy_pool();
  1207. if let Ok(mut guard) = pool.lock() {
  1208. if let Some(proxy) = guard.get_proxy() {
  1209. return Ok(proxy);
  1210. }
  1211. }
  1212. }
  1213. // 池为空,直接调用 API 获取
  1214. fetch_api_proxy_url(api_url).await
  1215. }
  1216. async fn post_steam_api_once(
  1217. client: &reqwest::Client,
  1218. api_url: &str,
  1219. base_body: &serde_json::Value,
  1220. _proxy_url: Option<&str>,
  1221. ) -> Result<(reqwest::StatusCode, String), String> {
  1222. // 不再传递代理信息给 localhost:5000,让 steamAPI 服务直连 Steam
  1223. // 代理只用于客户端直接访问 Steam 的接口(如 render、buylisting)
  1224. let resp = client
  1225. .post(api_url)
  1226. .json(base_body)
  1227. .send()
  1228. .await
  1229. .map_err(|e| format!("HTTP 请求失败: {}", e))?;
  1230. let status = resp.status();
  1231. let response_text = resp.text().await.map_err(|e| format!("读取响应失败: {}", e))?;
  1232. Ok((status, response_text))
  1233. }
  1234. async fn post_steam_api_with_proxy_retry(
  1235. client: &reqwest::Client,
  1236. api_url: &str,
  1237. base_body: &serde_json::Value,
  1238. label: &str,
  1239. ) -> Result<(reqwest::StatusCode, String), String> {
  1240. // 调用 localhost:5000 接口,不使用代理(直连模式)
  1241. // 代理仅用于客户端直接访问 Steam 的接口(如 render、buylisting)
  1242. log::info!(
  1243. "[api:{}] 直连 localhost:5000(不使用代理)",
  1244. label
  1245. );
  1246. let resp = client
  1247. .post(api_url)
  1248. .json(base_body)
  1249. .send()
  1250. .await
  1251. .map_err(|e| {
  1252. log::error!("[api:{}] HTTP 请求失败: {}", label, e);
  1253. format!("HTTP 请求失败: {}", e)
  1254. })?;
  1255. let status = resp.status();
  1256. let response_text = resp.text().await.map_err(|e| {
  1257. log::error!("[api:{}] 读取响应失败: {}", label, e);
  1258. format!("读取响应失败: {}", e)
  1259. })?;
  1260. Ok((status, response_text))
  1261. }
  1262. /// 读取本地 IP 代理配置(存于 steam-msg 目录,不上传服务器)
  1263. #[tauri::command]
  1264. pub async fn read_proxy_config() -> Result<String, String> {
  1265. let steam_msg_dir = get_steam_msg_dir()?;
  1266. let path = steam_msg_dir.join(PROXY_CONFIG_JSON);
  1267. if !path.exists() {
  1268. return Ok("{}".to_string());
  1269. }
  1270. fs::read_to_string(&path).map_err(|e| format!("读取代理配置失败: {}", e))
  1271. }
  1272. /// 写入本地 IP 代理配置(存于 steam-msg 目录,不上传服务器)
  1273. #[tauri::command]
  1274. pub async fn write_proxy_config(json: String) -> Result<(), String> {
  1275. let steam_msg_dir = get_steam_msg_dir()?;
  1276. fs::create_dir_all(&steam_msg_dir).map_err(|e| format!("创建目录失败: {}", e))?;
  1277. let path = steam_msg_dir.join(PROXY_CONFIG_JSON);
  1278. fs::write(&path, json).map_err(|e| format!("写入代理配置失败: {}", e))?;
  1279. clear_api_proxy_cache();
  1280. Ok(())
  1281. }
  1282. /// 标记代理为失效(被封禁等),会从代理池中移除
  1283. #[tauri::command]
  1284. pub fn invalidate_proxy(proxy_url: String) -> Result<(), String> {
  1285. let pool = get_proxy_pool();
  1286. if let Ok(mut guard) = pool.lock() {
  1287. guard.mark_proxy_invalid(&proxy_url);
  1288. info!("[proxy-pool] 标记代理失效: {}", proxy_url);
  1289. Ok(())
  1290. } else {
  1291. Err("代理池锁定失败".to_string())
  1292. }
  1293. }
  1294. /// 获取代理池状态(用于调试)
  1295. #[tauri::command]
  1296. pub fn get_proxy_pool_status() -> String {
  1297. let pool = get_proxy_pool();
  1298. if let Ok(guard) = pool.lock() {
  1299. let count = guard.available_count();
  1300. let response = serde_json::json!({
  1301. "code": 200,
  1302. "msg": "ok",
  1303. "data": {
  1304. "available_count": count,
  1305. "low_threshold": PROXY_POOL_LOW_THRESHOLD,
  1306. "needs_refill": guard.needs_refill()
  1307. }
  1308. });
  1309. serde_json::to_string(&response).unwrap_or_else(|_| r#"{"code":500,"msg":"序列化失败"}"#.to_string())
  1310. } else {
  1311. r#"{"code":500,"msg":"代理池锁定失败"}"#.to_string()
  1312. }
  1313. }
  1314. /// 手动触发代理池补充(用于调试或主动刷新)
  1315. #[tauri::command]
  1316. pub async fn refill_proxy_pool() -> Result<(), String> {
  1317. let stored = load_stored_proxy_config()?;
  1318. let mode_raw = stored.effective_mode.unwrap_or_default();
  1319. if mode_raw != "api" {
  1320. return Err("当前代理模式不是 API 模式,无法手动补充".to_string());
  1321. }
  1322. let brand_id = stored.effective_brand_id.unwrap_or_default();
  1323. let brand = stored
  1324. .configs
  1325. .as_ref()
  1326. .and_then(|map| map.get(&brand_id))
  1327. .cloned()
  1328. .ok_or_else(|| "未找到当前生效品牌的代理配置".to_string())?;
  1329. let api_url = brand.api_url.unwrap_or_default();
  1330. if api_url.trim().is_empty() {
  1331. return Err("API代理配置不完整".to_string());
  1332. }
  1333. fetch_api_proxy_url(&api_url).await?;
  1334. info!("[proxy-pool] 手动触发代理池补充完成");
  1335. Ok(())
  1336. }
  1337. /// 调用本地 steamAPI `/login` 刷新 session 并写回 `accounts.json`(与 `verify_steam_login` 同源逻辑)。
  1338. /// 供扫货启动时对已绑定账号预热 token,避免购买阶段因过期凭证失败。
  1339. pub(super) async fn steam_account_relogin_persist(steam_username: &str) -> Result<String, String> {
  1340. use serde_json::Value;
  1341. log::info!("[steam_relogin] 开始: {}", steam_username);
  1342. let steam_msg_dir = get_steam_msg_dir()?;
  1343. let accounts_file = steam_msg_dir.join(ACCOUNTS_JSON);
  1344. if !accounts_file.exists() {
  1345. log::error!(
  1346. "[steam_relogin] 账号文件不存在: {}",
  1347. accounts_file.to_string_lossy()
  1348. );
  1349. return Err(format!(
  1350. "Accounts file not found: {}",
  1351. accounts_file.to_string_lossy()
  1352. ));
  1353. }
  1354. let text =
  1355. fs::read_to_string(&accounts_file).map_err(|e| format!("Failed to read accounts file: {}", e))?;
  1356. let mut root: Value = serde_json::from_str(&text).map_err(|e| format!("Invalid accounts json: {}", e))?;
  1357. let storage_key = {
  1358. let accounts_ro = root
  1359. .get("accounts")
  1360. .and_then(|a| a.as_object())
  1361. .ok_or("Invalid accounts structure")?;
  1362. resolve_accounts_storage_key(accounts_ro, steam_username).ok_or_else(|| {
  1363. format!(
  1364. "Account {} not found in local storage(accounts 键或 steam_account 字段)",
  1365. steam_username
  1366. )
  1367. })?
  1368. };
  1369. let accounts = root
  1370. .get_mut("accounts")
  1371. .and_then(|a| a.as_object_mut())
  1372. .ok_or("Invalid accounts structure")?;
  1373. let mut obj: Value = accounts
  1374. .get(&storage_key)
  1375. .cloned()
  1376. .ok_or_else(|| format!("账号条目缺失 {}", storage_key))?;
  1377. let login_username = obj
  1378. .get("steam_account")
  1379. .and_then(|v| v.as_str())
  1380. .map(|s| s.trim())
  1381. .filter(|s| !s.is_empty())
  1382. .unwrap_or(storage_key.as_str())
  1383. .to_string();
  1384. let steam_password = obj
  1385. .get("steam_password")
  1386. .and_then(|v| v.as_str())
  1387. .unwrap_or("")
  1388. .to_string();
  1389. let steam_guard_path = obj
  1390. .get("steam_guard_path")
  1391. .and_then(|v| v.as_str())
  1392. .unwrap_or("")
  1393. .to_string();
  1394. log::info!(
  1395. "[steam_relogin] lookup={} storage_key={} login_username={} maFile 路径长度: {}",
  1396. steam_username,
  1397. storage_key,
  1398. login_username,
  1399. steam_guard_path.len()
  1400. );
  1401. let client = get_http_client();
  1402. let api_url = "http://localhost:5000/login";
  1403. let request_body = serde_json::json!({
  1404. "Username": login_username,
  1405. "Password": steam_password,
  1406. "MaFilePath": steam_guard_path
  1407. });
  1408. log::info!("[steam_relogin] POST {}", api_url);
  1409. let (status, resp_body) = match post_steam_api_with_proxy_retry(&client, api_url, &request_body, "login").await {
  1410. Ok(result) => result,
  1411. Err(e) => {
  1412. log::error!("[steam_relogin] HTTP 失败: {}", e);
  1413. obj["status"] = serde_json::Value::String("fail".to_string());
  1414. if let Some(acc) = root.get_mut("accounts").and_then(|a| a.as_object_mut()) {
  1415. acc.insert(storage_key.clone(), obj);
  1416. }
  1417. let _ = fs::write(&accounts_file, serde_json::to_string_pretty(&root).unwrap_or_default());
  1418. return Err(format!("HTTP request failed: {}", e));
  1419. }
  1420. };
  1421. log::info!("[steam_relogin] 状态码: {}", status);
  1422. if !status.is_success() {
  1423. let err_body = resp_body;
  1424. log::error!(
  1425. "[steam_relogin] 非成功, 响应(前500): {}",
  1426. &err_body.chars().take(500).collect::<String>()
  1427. );
  1428. obj["status"] = serde_json::Value::String("fail".to_string());
  1429. if let Some(acc) = root.get_mut("accounts").and_then(|a| a.as_object_mut()) {
  1430. acc.insert(storage_key.clone(), obj);
  1431. }
  1432. let _ = fs::write(&accounts_file, serde_json::to_string_pretty(&root).unwrap_or_default());
  1433. return Err(format!(
  1434. "Steam 登录接口返回错误: {} - {}",
  1435. status,
  1436. if err_body.len() > 200 {
  1437. format!("{}...", &err_body[..200])
  1438. } else {
  1439. err_body
  1440. }
  1441. ));
  1442. }
  1443. log::info!(
  1444. "[steam_relogin] 响应长度: {}, 前200: {}",
  1445. resp_body.len(),
  1446. &resp_body.chars().take(200).collect::<String>()
  1447. );
  1448. let resp_json: Value = serde_json::from_str(&resp_body).map_err(|e| {
  1449. log::error!(
  1450. "[steam_relogin] JSON 解析失败: {}, 原始(前300): {}",
  1451. e,
  1452. &resp_body.chars().take(300).collect::<String>()
  1453. );
  1454. obj["status"] = serde_json::Value::String("fail".to_string());
  1455. if let Some(acc) = root.get_mut("accounts").and_then(|a| a.as_object_mut()) {
  1456. acc.insert(storage_key.clone(), obj.clone());
  1457. }
  1458. let _ = fs::write(&accounts_file, serde_json::to_string_pretty(&root).unwrap_or_default());
  1459. format!("Failed to parse response JSON: {}", e)
  1460. })?;
  1461. if resp_json.get("encrypted").is_some() && resp_json.get("session").is_none() {
  1462. log::error!("[steam_relogin] 加密响应且无 session");
  1463. return Err(
  1464. "Steam 登录接口返回了加密数据,当前仅支持明文 session 格式,请检查 localhost:5000 服务配置"
  1465. .to_string(),
  1466. );
  1467. }
  1468. if let Some(session) = resp_json.get("session") {
  1469. if let Some(session_id) = session.get("sessionId").and_then(|v| v.as_str()) {
  1470. obj["sessionId"] = serde_json::Value::String(session_id.to_string());
  1471. }
  1472. if let Some(access_token) = session.get("accessToken").and_then(|v| v.as_str()) {
  1473. obj["accessToken"] = serde_json::Value::String(access_token.to_string());
  1474. }
  1475. if let Some(refresh_token) = session.get("refreshToken").and_then(|v| v.as_str()) {
  1476. obj["refreshToken"] = serde_json::Value::String(refresh_token.to_string());
  1477. }
  1478. if let Some(steam_id) = session.get("steamId").and_then(|v| v.as_i64()) {
  1479. obj["steamId"] = serde_json::Value::Number(serde_json::Number::from(steam_id));
  1480. } else if let Some(steam_id_s) = session.get("steamId").and_then(|v| v.as_str()) {
  1481. if let Ok(n) = steam_id_s.parse::<i64>() {
  1482. obj["steamId"] = serde_json::Value::Number(serde_json::Number::from(n));
  1483. } else {
  1484. obj["steamId"] = serde_json::Value::String(steam_id_s.to_string());
  1485. }
  1486. }
  1487. }
  1488. if let Some(cookies) = resp_json.get("cookies").and_then(|v| v.as_array()) {
  1489. for c in cookies {
  1490. if let Some(name) = c.get("name").and_then(|n| n.as_str()) {
  1491. if name == "steamLoginSecure" {
  1492. if let Some(value) = c.get("value").and_then(|v| v.as_str()) {
  1493. obj["steamLoginSecure"] = serde_json::Value::String(value.to_string());
  1494. }
  1495. }
  1496. if name == "sessionid" {
  1497. if let Some(value) = c.get("value").and_then(|v| v.as_str()) {
  1498. obj["sessionId"] = serde_json::Value::String(value.to_string());
  1499. }
  1500. }
  1501. }
  1502. }
  1503. }
  1504. obj["status"] = serde_json::Value::String("success".to_string());
  1505. let steam_id = obj
  1506. .get("steamId")
  1507. .and_then(|v| match v {
  1508. serde_json::Value::Number(n) => n.as_i64().map(|i| i.to_string()),
  1509. serde_json::Value::String(s) => Some(s.clone()),
  1510. _ => None,
  1511. })
  1512. .unwrap_or_default();
  1513. if let Some(acc) = root.get_mut("accounts").and_then(|a| a.as_object_mut()) {
  1514. acc.insert(storage_key.clone(), obj);
  1515. }
  1516. let content = serde_json::to_string_pretty(&root).map_err(|e| format!("Serialize failed: {}", e))?;
  1517. fs::write(&accounts_file, &content).map_err(|e| {
  1518. log::error!("[steam_relogin] 写入 accounts 失败: {}", e);
  1519. format!("Failed to write accounts json: {}", e)
  1520. })?;
  1521. log::info!(
  1522. "[steam_relogin] 已写回 accounts: {}",
  1523. accounts_file.to_string_lossy()
  1524. );
  1525. Ok(steam_id)
  1526. }
  1527. /// 扫货启动时 Steam 会话准备:与界面「查询余额 → 失败再重新登录 → 再查余额」一致,减少无效 /login 耗时。
  1528. pub(super) async fn steam_account_sweep_startup_auth(lookup_username: &str) -> Result<(), String> {
  1529. match steam_wallet_balance_fetch(lookup_username).await {
  1530. Ok(_) => {
  1531. log::info!(
  1532. "[sweep_steam_auth] {} 余额探活成功(同界面「查询余额」),会话有效,跳过 /login",
  1533. lookup_username
  1534. );
  1535. Ok(())
  1536. }
  1537. Err(e_first) => {
  1538. log::info!(
  1539. "[sweep_steam_auth] {} 余额探活失败,将执行与「重新登录」相同的 /login: {}",
  1540. lookup_username,
  1541. e_first
  1542. );
  1543. steam_account_relogin_persist(lookup_username).await?;
  1544. if let Err(e2) = steam_wallet_balance_fetch(lookup_username).await {
  1545. log::warn!(
  1546. "[sweep_steam_auth] {} 已写回登录态,但登录后余额探活仍失败(可继续扫货,与界面一致仅提示): {}",
  1547. lookup_username,
  1548. e2
  1549. );
  1550. }
  1551. Ok(())
  1552. }
  1553. }
  1554. }
  1555. #[tauri::command]
  1556. pub async fn verify_steam_login(steamUsername: String) -> Result<String, String> {
  1557. log::info!("[verify_steam_login] 开始验证账号: {}", steamUsername);
  1558. let steam_id = steam_account_relogin_persist(&steamUsername).await?;
  1559. if let Err(e) = steam_wallet_balance_fetch(&steamUsername).await {
  1560. log::warn!("[verify_steam_login] 与界面一致:登录后余额探活失败(仍视为登录成功): {}", e);
  1561. }
  1562. if steam_id.is_empty() {
  1563. log::warn!("[verify_steam_login] 未能解析 steamId,请检查 session.steamId");
  1564. } else {
  1565. log::info!("[verify_steam_login] steam_id: {}", steam_id);
  1566. }
  1567. let result = serde_json::json!({ "success": true, "steam_id": steam_id });
  1568. Ok(result.to_string())
  1569. }
  1570. // ==================== 后端API调用命令 ====================
  1571. #[tauri::command]
  1572. pub async fn initialize_backend_api(base_url: String, software_code: String) -> Result<(), String> {
  1573. let config = ApiConfig {
  1574. base_url,
  1575. timeout: 30,
  1576. software_code,
  1577. };
  1578. initialize_api_client(config)?;
  1579. Ok(())
  1580. }
  1581. #[tauri::command]
  1582. pub async fn user_register(username: String, password: String, email: String, card_code: String, _machine_code: Option<String>) -> Result<String, String> {
  1583. let mut client = get_api_client_for_async()?;
  1584. // 注册时不传递机器码,让其为None
  1585. let result = client.register(&username, &password, &email, &card_code, "").await?;
  1586. serde_json::to_string(&result)
  1587. .map_err(|e| format!("序列化响应失败: {}", e))
  1588. }
  1589. #[tauri::command]
  1590. pub async fn user_login(username: String, password: String, machine_code: String) -> Result<String, String> {
  1591. let mut client = get_api_client_for_async()?;
  1592. let result = client.login(&username, &password, &machine_code).await?;
  1593. // 登录成功后,启动心跳服务
  1594. if let Some(ref data) = result.data {
  1595. let api_url = get_api_base_url()?;
  1596. let session_id = data.session_id.as_deref().unwrap_or("").to_string();
  1597. if !session_id.is_empty() {
  1598. info!("[Login] 登录成功,启动心跳服务");
  1599. // 启动心跳服务(包括密钥同步定时任务)
  1600. start_heartbeat_service(api_url, session_id, machine_code).await
  1601. .map_err(|e| format!("启动心跳服务失败: {}", e))?;
  1602. }
  1603. }
  1604. serde_json::to_string(&result)
  1605. .map_err(|e| format!("序列化响应失败: {}", e))
  1606. }
  1607. #[tauri::command]
  1608. pub async fn user_unbind(username: String, password: String) -> Result<String, String> {
  1609. let mut client = get_api_client_for_async()?;
  1610. let result = client.unbind(&username, &password).await?;
  1611. serde_json::to_string(&result)
  1612. .map_err(|e| format!("序列化响应失败: {}", e))
  1613. }
  1614. #[tauri::command]
  1615. pub async fn user_recharge(username: String, password: String, card_code: String) -> Result<String, String> {
  1616. let mut client = get_api_client_for_async()?;
  1617. let result = client.recharge(&username, &password, &card_code).await?;
  1618. serde_json::to_string(&result)
  1619. .map_err(|e| format!("序列化响应失败: {}", e))
  1620. }
  1621. #[tauri::command]
  1622. pub async fn user_heartbeat(user_id: i32) -> Result<String, String> {
  1623. let mut client = get_api_client_for_async()?;
  1624. let result = client.heartbeat(user_id).await?;
  1625. serde_json::to_string(&result)
  1626. .map_err(|e| format!("序列化响应失败: {}", e))
  1627. }
  1628. #[tauri::command]
  1629. pub async fn user_logout(user_id: i32, session_id: String) -> Result<String, String> {
  1630. // 扫货远程停止由前端在调用本接口前通过 `stop_all_processes` 完成。
  1631. // 1. 停止心跳服务
  1632. info!("[Logout] 停止心跳服务");
  1633. stop_heartbeat_service().await;
  1634. let mut client = get_api_client_for_async()?;
  1635. let result = client.logout(user_id, &session_id).await?;
  1636. serde_json::to_string(&result)
  1637. .map_err(|e| format!("序列化响应失败: {}", e))
  1638. }
  1639. #[tauri::command]
  1640. #[allow(dead_code)]
  1641. pub async fn validate_session(session_id: String) -> Result<String, String> {
  1642. let mut client = get_api_client_for_async()?;
  1643. let result = client.validate_session(&session_id).await?;
  1644. serde_json::to_string(&result)
  1645. .map_err(|e| format!("序列化响应失败: {}", e))
  1646. }
  1647. #[tauri::command]
  1648. pub async fn activate_card(user_id: i32, card_code: String) -> Result<String, String> {
  1649. let mut client = get_api_client_for_async()?;
  1650. let result = client.activate_card(user_id, &card_code).await?;
  1651. serde_json::to_string(&result)
  1652. .map_err(|e| format!("序列化响应失败: {}", e))
  1653. }
  1654. #[tauri::command]
  1655. pub async fn get_steam_account_limit(user_id: i32) -> Result<String, String> {
  1656. let mut client = get_api_client_for_async()?;
  1657. let result = client.get_steam_account_limit(user_id).await?;
  1658. serde_json::to_string(&result).map_err(|e| format!("序列化响应失败: {}", e))
  1659. }
  1660. #[tauri::command]
  1661. pub async fn get_steam_accounts(user_id: i32) -> Result<String, String> {
  1662. let mut client = get_api_client_for_async()?;
  1663. let result = client.get_steam_accounts(user_id).await?;
  1664. serde_json::to_string(&result)
  1665. .map_err(|e| format!("序列化响应失败: {}", e))
  1666. }
  1667. #[tauri::command]
  1668. pub async fn create_steam_account(user_id: i32, steam_username: String, steam_id: Option<String>) -> Result<String, String> {
  1669. let mut client = get_api_client_for_async()?;
  1670. let result = client.create_steam_account(user_id, &steam_username, steam_id.as_deref()).await?;
  1671. serde_json::to_string(&result)
  1672. .map_err(|e| format!("序列化响应失败: {}", e))
  1673. }
  1674. #[tauri::command]
  1675. pub async fn bind_steam_account(user_id: i32, steam_username: String, steam_id: String) -> Result<String, String> {
  1676. if steam_id.trim().is_empty() {
  1677. log::warn!("[bind_steam_account] steam_id 为空,跳过后端绑定");
  1678. return Err("steam_id 为空,请先完成 Steam 登录验证".to_string());
  1679. }
  1680. log::info!("[bind_steam_account] 开始: user_id={}, steam_username={}, steam_id={}", user_id, steam_username, steam_id);
  1681. let mut client = get_api_client_for_async().map_err(|e| {
  1682. log::error!("[bind_steam_account] 获取 API 客户端失败: {:?}", e);
  1683. format!("获取 API 客户端失败: {:?}", e)
  1684. })?;
  1685. let result = client.bind_steam_account(user_id, &steam_username, &steam_id).await.map_err(|e| {
  1686. log::error!("[bind_steam_account] 请求失败: {:?}", e);
  1687. let msg = match &e {
  1688. crate::error::ClientError::ApiBusinessError { message, .. } => message.clone(),
  1689. crate::error::ClientError::ServerError { message, .. } => message.clone(),
  1690. _ => format!("{:?}", e),
  1691. };
  1692. msg
  1693. })?;
  1694. let json = serde_json::to_string(&result).map_err(|e| {
  1695. log::error!("[bind_steam_account] 序列化响应失败: {}", e);
  1696. format!("序列化响应失败: {}", e)
  1697. })?;
  1698. log::info!("[bind_steam_account] 成功");
  1699. Ok(json)
  1700. }
  1701. #[tauri::command]
  1702. pub async fn update_steam_account(user_id: i32, account_id: i32, updates: String) -> Result<String, String> {
  1703. let mut client = get_api_client_for_async()?;
  1704. let updates_json: serde_json::Value = serde_json::from_str(&updates)
  1705. .map_err(|e| format!("无效的更新JSON: {}", e))?;
  1706. let result = client.update_steam_account(user_id, account_id, updates_json).await?;
  1707. serde_json::to_string(&result)
  1708. .map_err(|e| format!("序列化响应失败: {}", e))
  1709. }
  1710. #[tauri::command]
  1711. pub async fn delete_steam_account(user_id: i32, account_id: i32) -> Result<String, String> {
  1712. let mut client = get_api_client_for_async()?;
  1713. let result = client.delete_steam_account(user_id, account_id).await?;
  1714. serde_json::to_string(&result)
  1715. .map_err(|e| format!("序列化响应失败: {}", e))
  1716. }
  1717. #[tauri::command]
  1718. pub async fn get_announcements(page: i32, page_size: i32, keyword: Option<String>) -> Result<String, String> {
  1719. let mut client = get_api_client_for_async()?;
  1720. let result = client.get_announcements(page, page_size, keyword.as_deref()).await?;
  1721. serde_json::to_string(&result)
  1722. .map_err(|e| format!("序列化响应失败: {}", e))
  1723. }
  1724. #[tauri::command]
  1725. pub async fn health_check() -> Result<String, String> {
  1726. let client = get_api_client_for_async()?;
  1727. let result = client.health_check().await?;
  1728. serde_json::to_string(&result)
  1729. .map_err(|e| format!("序列化响应失败: {}", e))
  1730. }
  1731. #[tauri::command]
  1732. pub async fn get_public_key() -> Result<String, String> {
  1733. let client = get_api_client_for_async()?;
  1734. let result = client.get_public_key().await?;
  1735. serde_json::to_string(&result)
  1736. .map_err(|e| format!("序列化响应失败: {}", e))
  1737. }
  1738. #[tauri::command]
  1739. pub async fn get_client_configs() -> Result<String, String> {
  1740. let mut client = get_api_client_for_async()?;
  1741. let result = client.get_client_configs().await?;
  1742. serde_json::to_string(&result)
  1743. .map_err(|e| format!("序列化响应失败: {}", e))
  1744. }
  1745. #[tauri::command]
  1746. #[allow(dead_code)]
  1747. pub fn clear_api_cache() -> Result<(), String> {
  1748. // 清除API客户端的缓存
  1749. // 这里需要根据实际的API客户端实现来清除缓存
  1750. // 目前暂时返回成功,具体实现需要根据业务逻辑调整
  1751. println!("清除API缓存的命令已调用");
  1752. Ok(())
  1753. }
  1754. // ==================== 配置管理命令 ====================
  1755. #[tauri::command]
  1756. pub fn get_app_config() -> Result<String, String> {
  1757. let config = AppConfig::load()
  1758. .map_err(|e| format!("Failed to load config: {}", e))?;
  1759. serde_json::to_string(&config)
  1760. .map_err(|e| format!("Failed to serialize config: {}", e))
  1761. }
  1762. #[tauri::command]
  1763. pub fn update_api_url(new_url: String) -> Result<(), String> {
  1764. let mut config = AppConfig::load()
  1765. .map_err(|e| format!("Failed to load config: {}", e))?;
  1766. config.update_api_url(new_url)
  1767. .map_err(|e| format!("Failed to update API URL: {}", e))?;
  1768. // 重新初始化API客户端
  1769. let api_config = ApiConfig {
  1770. base_url: config.api.base_url.clone(),
  1771. timeout: config.api.timeout,
  1772. software_code: config.api.software_code.clone(),
  1773. };
  1774. initialize_api_client(api_config)
  1775. .map_err(|e| format!("Failed to reinitialize API client: {}", e))
  1776. }
  1777. #[tauri::command]
  1778. pub fn get_api_base_url() -> Result<String, String> {
  1779. let config = AppConfig::load()
  1780. .map_err(|e| format!("Failed to load config: {}", e))?;
  1781. Ok(config.api.base_url)
  1782. }
  1783. // ==================== 机器码和存储功能 ====================
  1784. #[tauri::command]
  1785. pub fn get_machine_code() -> Result<String, String> {
  1786. generate_machine_code()
  1787. .map_err(|e| format!("生成机器码失败: {}", e))
  1788. }
  1789. #[tauri::command]
  1790. pub fn store_user_info(user_info: String) -> Result<(), String> {
  1791. let user_info: UserInfo = serde_json::from_str(&user_info)
  1792. .map_err(|e| format!("解析用户信息失败: {}", e))?;
  1793. let storage = get_storage()
  1794. .map_err(|e| format!("获取存储失败: {}", e))?;
  1795. storage.set_user_info(&user_info)
  1796. .map_err(|e| format!("保存用户信息失败: {}", e))
  1797. }
  1798. #[tauri::command]
  1799. pub fn get_stored_user_info() -> Result<String, String> {
  1800. let storage = get_storage()
  1801. .map_err(|e| format!("获取存储失败: {}", e))?;
  1802. let user_info = storage.get_user_info()
  1803. .map_err(|e| format!("读取用户信息失败: {}", e))?;
  1804. serde_json::to_string(&user_info)
  1805. .map_err(|e| format!("序列化用户信息失败: {}", e))
  1806. }
  1807. /// 获取当前登录用户名
  1808. #[tauri::command]
  1809. pub fn get_current_username() -> Result<String, String> {
  1810. let storage = get_storage()
  1811. .map_err(|e| format!("获取存储失败: {}", e))?;
  1812. match storage.get_user_info() {
  1813. Ok(user_info) => Ok(user_info.username),
  1814. Err(_) => Ok("guest".to_string()),
  1815. }
  1816. }
  1817. /// 从后端获取用户信息(安全加密调用,符合技术方案)
  1818. #[tauri::command]
  1819. pub async fn get_user_profile(
  1820. session_id: String,
  1821. machine_code: String,
  1822. ) -> Result<serde_json::Value, String> {
  1823. let api_url = get_api_base_url()?;
  1824. secure_get_user_profile_call(&api_url, &session_id, &machine_code).await
  1825. }
  1826. #[tauri::command]
  1827. pub fn get_system_username() -> Result<String, String> {
  1828. // 尝试读取环境变量(Unix/Windows常用变量)
  1829. if let Ok(u) = std::env::var("USER") {
  1830. if !u.is_empty() {
  1831. return Ok(u)
  1832. }
  1833. }
  1834. if let Ok(u) = std::env::var("USERNAME") {
  1835. if !u.is_empty() {
  1836. return Ok(u)
  1837. }
  1838. }
  1839. // 环境变量中没有找到用户名时返回 unknown
  1840. Ok("unknown".to_string())
  1841. }
  1842. #[tauri::command]
  1843. pub fn clear_stored_user_info() -> Result<(), String> {
  1844. let storage = get_storage()
  1845. .map_err(|e| format!("获取存储失败: {}", e))?;
  1846. storage.remove("user_info")
  1847. .map_err(|e| format!("清除用户信息失败: {}", e))
  1848. }
  1849. #[tauri::command]
  1850. pub fn minimize_window() -> Result<(), String> {
  1851. // 在Tauri中最小化窗口
  1852. // 这里需要使用 Tauri's window API,但目前我们返回成功
  1853. Ok(())
  1854. }
  1855. #[tauri::command]
  1856. pub fn close_window() -> Result<(), String> {
  1857. // 在Tauri中关闭窗口
  1858. // 这里需要使用 Tauri's window API,但目前我们返回成功
  1859. Ok(())
  1860. }
  1861. /// 登出 / 认证失效 / 关窗等场景:取消本地扫货 tokio 任务,并在有会话时尽力通知后端 `selection-stop` 与 `sweep-stop`。
  1862. #[derive(Debug, Deserialize)]
  1863. #[serde(rename_all = "camelCase")]
  1864. pub struct StopAllProcessesArgs {
  1865. pub session_id: Option<String>,
  1866. pub machine_code: Option<String>,
  1867. #[serde(default)]
  1868. pub trace_id: Option<String>,
  1869. }
  1870. #[tauri::command]
  1871. pub async fn stop_all_processes(args: StopAllProcessesArgs) -> Result<(), String> {
  1872. // 本地扫货为多 worker 并行,统一用全局 SWEEP_CANCEL;无需逐任务 abort。
  1873. info!("[stop_all_processes] 取消本地扫货并尽力通知后端停止选品/扫货");
  1874. buff_sweep_runner::request_cancel();
  1875. if let (Some(sid), Some(mid)) = (args.session_id, args.machine_code) {
  1876. let s = sid.trim();
  1877. let m = mid.trim();
  1878. if !s.is_empty() && !m.is_empty() {
  1879. match buff_secure_selection_stop_decrypt(s, m).await {
  1880. Ok(_) => info!("[stop_all_processes] 已通知后端 selection-stop"),
  1881. Err(e) => warn!("[stop_all_processes] 通知后端 selection-stop 失败(可忽略): {}", e),
  1882. }
  1883. match buff_secure_sweep_stop_decrypt(s, m).await {
  1884. Ok(_) => info!("[stop_all_processes] 已通知后端 sweep-stop"),
  1885. Err(e) => warn!("[stop_all_processes] 通知后端 sweep-stop 失败(可忽略): {}", e),
  1886. }
  1887. }
  1888. }
  1889. Ok(())
  1890. }
  1891. #[derive(serde::Serialize, serde::Deserialize)]
  1892. pub struct LoginRequest {
  1893. pub username: String,
  1894. pub password: String,
  1895. pub machine_code: String,
  1896. }
  1897. #[derive(serde::Serialize, serde::Deserialize)]
  1898. pub struct LoginResponse {
  1899. pub user_id: i32,
  1900. pub username: String,
  1901. pub expire_time: String,
  1902. pub days_remaining: i32,
  1903. pub email: Option<String>,
  1904. pub session_id: Option<String>,
  1905. }
  1906. /// 校验服务端响应签名(response_signature)
  1907. ///
  1908. /// 按文档约定:
  1909. /// sign_string = "{response_timestamp}:{response_nonce}:{encrypted_data}:{key_version}"
  1910. fn verify_response_signature(
  1911. response_json: &serde_json::Value,
  1912. encrypted_data: &str,
  1913. key_version: i32,
  1914. ) -> Result<(), String> {
  1915. use std::time::{SystemTime, UNIX_EPOCH};
  1916. let response_timestamp = response_json
  1917. .get("response_timestamp")
  1918. .and_then(|v| v.as_i64())
  1919. .ok_or_else(|| "响应缺少 response_timestamp 字段".to_string())?;
  1920. let response_nonce = response_json
  1921. .get("response_nonce")
  1922. .and_then(|v| v.as_str())
  1923. .ok_or_else(|| "响应缺少 response_nonce 字段".to_string())?;
  1924. let response_signature = response_json
  1925. .get("response_signature")
  1926. .and_then(|v| v.as_str())
  1927. .ok_or_else(|| "响应缺少 response_signature 字段".to_string())?;
  1928. // 时间戳容忍范围 ±5 分钟
  1929. let now = SystemTime::now()
  1930. .duration_since(UNIX_EPOCH)
  1931. .map_err(|e| format!("Time error: {}", e))?
  1932. .as_secs() as i64;
  1933. let diff = (now - response_timestamp).abs();
  1934. if diff > 300 {
  1935. return Err("响应时间戳超出允许范围,请重试登录".to_string());
  1936. }
  1937. // 构建签名字符串,服务端用 SERVER_SECRET 签名,客户端用相同密钥验签
  1938. let sign_string = format!(
  1939. "{}:{}:{}:{}",
  1940. response_timestamp, response_nonce, encrypted_data, key_version
  1941. );
  1942. let server_secret = get_server_secret_key();
  1943. let expected_signature = generate_hmac_signature(&sign_string, server_secret.as_str())
  1944. .map_err(|e| format!("生成响应签名失败: {}", e))?;
  1945. if expected_signature != response_signature {
  1946. return Err("响应签名验证失败,可能存在中间人攻击风险".to_string());
  1947. }
  1948. Ok(())
  1949. }
  1950. // AES 加解密使用 crypto 模块的 aes_encrypt / aes_decrypt
  1951. #[tauri::command]
  1952. pub async fn secure_user_login(request: LoginRequest) -> Result<LoginResponse, String> {
  1953. use base64::{Engine as _, engine::general_purpose};
  1954. use serde_json;
  1955. use std::time::{SystemTime, UNIX_EPOCH};
  1956. // 1. 从Rust后端分段存储中重组RSA公钥
  1957. let public_key_pem = retrieve_rsa_key_segments()?;
  1958. // 2. 生成随机AES密钥
  1959. let aes_key = (0..32).map(|_| rand::random::<u8>()).collect::<Vec<u8>>();
  1960. // 3. 生成时间戳和 nonce(需在加密请求体前生成,供 request_timestamp 与签名使用)
  1961. let timestamp = SystemTime::now()
  1962. .duration_since(UNIX_EPOCH)
  1963. .map_err(|e| format!("Time error: {}", e))?
  1964. .as_secs() as i64;
  1965. let nonce: String = (0..32u8).map(|_| rand::random::<u8>()).map(|b| format!("{:02x}", b)).collect();
  1966. // 4. AES加密请求参数(包含原始机器特征 + 业务时间戳 + 软件标识,供多软件共用用户表)
  1967. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  1968. let request_body_plain = serde_json::json!({
  1969. "username": request.username,
  1970. "password": request.password,
  1971. "raw_machine_info": request.machine_code,
  1972. "request_timestamp": timestamp,
  1973. "software_code": config.api.software_code,
  1974. });
  1975. let request_json = request_body_plain.to_string();
  1976. let encrypted_params = aes_encrypt(request_json.as_bytes(), &aes_key)?;
  1977. let encrypted_params_base64 = general_purpose::STANDARD.encode(&encrypted_params);
  1978. // 5. RSA加密AES密钥
  1979. let public_key_parsed = RsaPublicKey::from_public_key_pem(&public_key_pem)
  1980. .map_err(|e| format!("Failed to parse public key: {}", e))?;
  1981. let mut rng = OsRng;
  1982. // 使用OAEP填充,与后端保持一致
  1983. let padding = Oaep::new::<sha2::Sha256>();
  1984. let encrypted_aes_key = public_key_parsed.encrypt(&mut rng, padding, &aes_key)
  1985. .map_err(|e| format!("Failed to encrypt AES key: {}", e))?;
  1986. let encrypted_aes_key_base64 = general_purpose::STANDARD.encode(&encrypted_aes_key);
  1987. // 6. 获取RSA密钥版本
  1988. let key_version = get_stored_key_version().ok_or_else(|| "RSA公钥版本不存在".to_string())?;
  1989. // 7. 生成签名
  1990. let signature_data = format!("{}:{}:{}:{}:{}", timestamp, nonce, encrypted_params_base64, encrypted_aes_key_base64, key_version);
  1991. type HmacSha256 = Hmac<Sha256>;
  1992. let mut mac = HmacSha256::new_from_slice(get_secret_key().as_bytes())
  1993. .map_err(|e| format!("HMAC init failed: {}", e))?;
  1994. mac.update(signature_data.as_bytes());
  1995. let signature_bytes = mac.finalize().into_bytes();
  1996. let signature = general_purpose::STANDARD.encode(&signature_bytes);
  1997. // 8. 发送HTTP请求
  1998. let client = get_http_client();
  1999. let api_url = format!("{}/api/v1/auth/login", get_api_base_url()?);
  2000. let request_body = serde_json::json!({
  2001. "encrypted_params": encrypted_params_base64,
  2002. "aes_key": encrypted_aes_key_base64,
  2003. "key_version": key_version,
  2004. "timestamp": timestamp,
  2005. "nonce": nonce,
  2006. "signature": signature
  2007. });
  2008. let response = client
  2009. .post(api_url)
  2010. .json(&request_body)
  2011. .send()
  2012. .await
  2013. .map_err(|e| format!("HTTP request failed: {}", e))?;
  2014. if !response.status().is_success() {
  2015. return Err(format!("HTTP request failed with status: {}", response.status()));
  2016. }
  2017. let response_json: serde_json::Value = response.json()
  2018. .await
  2019. .map_err(|e| format!("Failed to parse response: {}", e))?;
  2020. // 检查响应状态
  2021. if let Some(code) = response_json.get("code") {
  2022. if code != 200 {
  2023. let msg = response_json.get("msg")
  2024. .and_then(|m| m.as_str())
  2025. .unwrap_or("Unknown error");
  2026. return Err(format!("API error: {}", msg));
  2027. }
  2028. }
  2029. // 8. 解密前先校验响应签名,然后解密响应数据
  2030. if let Some(encrypted_data) = response_json.get("encrypted_data").and_then(|d| d.as_str()) {
  2031. // 先验证响应签名
  2032. verify_response_signature(&response_json, encrypted_data, key_version)?;
  2033. let encrypted_bytes = general_purpose::STANDARD
  2034. .decode(encrypted_data)
  2035. .map_err(|e| format!("Failed to decode encrypted data: {}", e))?;
  2036. // 从响应数据中提取IV(前16字节,与公告API保持一致)
  2037. let response_iv = &encrypted_bytes[0..16];
  2038. let encrypted_payload = &encrypted_bytes[16..];
  2039. // AES解密payload部分
  2040. let mut cipher = Ctr64BE::<Aes256>::new_from_slices(&aes_key, response_iv)
  2041. .map_err(|e| format!("AES cipher init failed: {}", e))?;
  2042. let mut decrypted_data = encrypted_payload.to_vec();
  2043. cipher.apply_keystream(&mut decrypted_data);
  2044. let decrypted_json: serde_json::Value = serde_json::from_slice(&decrypted_data)
  2045. .map_err(|e| format!("Failed to parse decrypted data: {}", e))?;
  2046. // 检查响应状态码
  2047. let code = decrypted_json.get("code")
  2048. .and_then(|c| c.as_i64())
  2049. .unwrap_or(500) as i32;
  2050. if code != 200 {
  2051. let msg = decrypted_json.get("msg")
  2052. .and_then(|m| m.as_str())
  2053. .unwrap_or("Unknown error");
  2054. // 401/403 直接返回后端友好提示,便于登录页展示「卡密已过期」等
  2055. if code == 401 || code == 403 {
  2056. return Err(msg.to_string());
  2057. }
  2058. return Err(format!("API error {}: {}", code, msg));
  2059. }
  2060. // 提取用户信息:兼容后端在 root 或 data 子对象中返回用户字段
  2061. let payload = if let Some(d) = decrypted_json.get("data") {
  2062. d
  2063. } else {
  2064. &decrypted_json
  2065. };
  2066. let user_id = payload.get("user_id")
  2067. .and_then(|id| id.as_i64())
  2068. .unwrap_or(0) as i32;
  2069. let username = payload.get("username")
  2070. .and_then(|u| u.as_str())
  2071. .unwrap_or("")
  2072. .to_string();
  2073. let expire_time = payload.get("expire_time")
  2074. .and_then(|et| et.as_str())
  2075. .unwrap_or("")
  2076. .to_string();
  2077. let days_remaining = payload.get("days_remaining")
  2078. .and_then(|d| d.as_i64())
  2079. .unwrap_or(0) as i32;
  2080. let email = payload.get("email")
  2081. .and_then(|e| e.as_str())
  2082. .map(|s| s.to_string());
  2083. let session_id = payload.get("session_id")
  2084. .and_then(|s| s.as_str())
  2085. .map(|s| s.to_string());
  2086. Ok(LoginResponse {
  2087. user_id,
  2088. username,
  2089. expire_time,
  2090. days_remaining,
  2091. email,
  2092. session_id,
  2093. })
  2094. } else {
  2095. Err("No encrypted_data in response".to_string())
  2096. }
  2097. }
  2098. #[derive(serde::Serialize, serde::Deserialize)]
  2099. pub struct RegisterRequest {
  2100. pub username: String,
  2101. pub password: String,
  2102. pub email: String,
  2103. pub card_code: String,
  2104. }
  2105. #[derive(serde::Serialize, serde::Deserialize)]
  2106. pub struct RegisterResponse {
  2107. pub user_id: i32,
  2108. pub username: String,
  2109. pub message: String,
  2110. }
  2111. #[tauri::command]
  2112. pub async fn secure_user_register(request: RegisterRequest) -> Result<RegisterResponse, String> {
  2113. use base64::{Engine as _, engine::general_purpose};
  2114. use serde_json;
  2115. use std::time::{SystemTime, UNIX_EPOCH};
  2116. // 1. 从Rust后端分段存储中重组RSA公钥
  2117. let public_key_pem = retrieve_rsa_key_segments()?;
  2118. // 2. 生成随机AES密钥
  2119. let aes_key = (0..32).map(|_| rand::random::<u8>()).collect::<Vec<u8>>();
  2120. // 3. AES加密请求参数(含 software_code,供多软件共用用户表)
  2121. let app_config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  2122. let request_data = serde_json::json!({
  2123. "username": request.username,
  2124. "password": request.password,
  2125. "email": request.email,
  2126. "card_code": request.card_code,
  2127. "software_code": app_config.api.software_code,
  2128. });
  2129. let request_json = request_data.to_string();
  2130. let encrypted_params = aes_encrypt(&request_json.as_bytes(), &aes_key)?;
  2131. let encrypted_params_base64 = general_purpose::STANDARD.encode(&encrypted_params);
  2132. // 4. RSA加密AES密钥
  2133. let public_key_parsed = RsaPublicKey::from_public_key_pem(&public_key_pem)
  2134. .map_err(|e| format!("Failed to parse public key: {}", e))?;
  2135. let mut rng = OsRng;
  2136. // 使用OAEP填充,与后端保持一致
  2137. let padding = Oaep::new::<sha2::Sha256>();
  2138. let encrypted_aes_key = public_key_parsed.encrypt(&mut rng, padding, &aes_key)
  2139. .map_err(|e| format!("Failed to encrypt AES key: {}", e))?;
  2140. let encrypted_aes_key_base64 = general_purpose::STANDARD.encode(&encrypted_aes_key);
  2141. // 5. 生成时间戳和nonce
  2142. let timestamp = SystemTime::now()
  2143. .duration_since(UNIX_EPOCH)
  2144. .map_err(|e| format!("Time error: {}", e))?
  2145. .as_secs() as i64;
  2146. let nonce: String = (0..32u8).map(|_| rand::random::<u8>()).map(|b| format!("{:02x}", b)).collect();
  2147. // 6. 获取RSA密钥版本
  2148. let key_version = get_stored_key_version().ok_or_else(|| "RSA公钥版本不存在".to_string())?;
  2149. // 7. 生成签名
  2150. let signature_data = format!("{}:{}:{}:{}:{}", timestamp, nonce, encrypted_params_base64, encrypted_aes_key_base64, key_version);
  2151. type HmacSha256 = Hmac<Sha256>;
  2152. let mut mac = HmacSha256::new_from_slice(get_secret_key().as_bytes())
  2153. .map_err(|e| format!("HMAC init failed: {}", e))?;
  2154. mac.update(signature_data.as_bytes());
  2155. let signature_bytes = mac.finalize().into_bytes();
  2156. let signature = general_purpose::STANDARD.encode(&signature_bytes);
  2157. // 8. 发送HTTP请求
  2158. let client = get_http_client();
  2159. let api_url = format!("{}/api/v1/auth/register", get_api_base_url()?);
  2160. let request_body = serde_json::json!({
  2161. "encrypted_params": encrypted_params_base64,
  2162. "aes_key": encrypted_aes_key_base64,
  2163. "key_version": key_version,
  2164. "timestamp": timestamp,
  2165. "nonce": nonce,
  2166. "signature": signature
  2167. });
  2168. let response = client
  2169. .post(api_url)
  2170. .json(&request_body)
  2171. .send()
  2172. .await
  2173. .map_err(|e| format!("HTTP request failed: {}", e))?;
  2174. if !response.status().is_success() {
  2175. return Err(format!("HTTP request failed with status: {}", response.status()));
  2176. }
  2177. let response_json: serde_json::Value = response.json()
  2178. .await
  2179. .map_err(|e| format!("Failed to parse response: {}", e))?;
  2180. // 检查响应状态
  2181. if let Some(code) = response_json.get("code") {
  2182. if code != 200 {
  2183. let msg = response_json.get("msg")
  2184. .and_then(|m| m.as_str())
  2185. .unwrap_or("Unknown error");
  2186. return Err(format!("API error: {}", msg));
  2187. }
  2188. }
  2189. // 8. 解密前先校验响应签名,然后解密响应数据
  2190. if let Some(encrypted_data) = response_json.get("encrypted_data").and_then(|d| d.as_str()) {
  2191. verify_response_signature(&response_json, encrypted_data, key_version)?;
  2192. let encrypted_bytes = general_purpose::STANDARD
  2193. .decode(encrypted_data)
  2194. .map_err(|e| format!("Failed to decode encrypted data: {}", e))?;
  2195. // 从响应数据中提取IV(前16字节,与公告API保持一致)
  2196. let response_iv = &encrypted_bytes[0..16];
  2197. let encrypted_payload = &encrypted_bytes[16..];
  2198. // AES解密payload部分(与后端 IV + AES-CTR 约定一致)
  2199. let mut cipher = Ctr64BE::<Aes256>::new_from_slices(&aes_key, response_iv)
  2200. .map_err(|e| format!("AES cipher init failed: {}", e))?;
  2201. let mut decrypted_data = encrypted_payload.to_vec();
  2202. cipher.apply_keystream(&mut decrypted_data);
  2203. let decrypted_json: serde_json::Value = serde_json::from_slice(&decrypted_data)
  2204. .map_err(|e| format!("Failed to parse decrypted data: {}", e))?;
  2205. // 检查响应状态码
  2206. let code = decrypted_json.get("code")
  2207. .and_then(|c| c.as_i64())
  2208. .unwrap_or(500) as i32;
  2209. if code != 200 {
  2210. let msg = decrypted_json.get("msg")
  2211. .and_then(|m| m.as_str())
  2212. .unwrap_or("Unknown error");
  2213. return Err(format!("API error {}: {}", code, msg));
  2214. }
  2215. // 提取注册结果(后端返回格式为 { "code", "msg", "data": { "user_id", "username", ... } })
  2216. let data_obj = decrypted_json.get("data");
  2217. let user_id = data_obj
  2218. .and_then(|d| d.get("user_id").and_then(|id| id.as_i64()))
  2219. .or_else(|| decrypted_json.get("user_id").and_then(|id| id.as_i64()))
  2220. .unwrap_or(0) as i32;
  2221. let username = data_obj
  2222. .and_then(|d| d.get("username").and_then(|u| u.as_str()))
  2223. .or_else(|| decrypted_json.get("username").and_then(|u| u.as_str()))
  2224. .unwrap_or("")
  2225. .to_string();
  2226. let message = decrypted_json.get("msg")
  2227. .and_then(|m| m.as_str())
  2228. .or_else(|| decrypted_json.get("message").and_then(|m| m.as_str()))
  2229. .unwrap_or("注册成功")
  2230. .to_string();
  2231. Ok(RegisterResponse {
  2232. user_id,
  2233. username,
  2234. message,
  2235. })
  2236. } else {
  2237. Err("No encrypted_data in response".to_string())
  2238. }
  2239. }
  2240. /// 找回密码请求(明文 POST,仅用户名和邮箱)
  2241. #[derive(serde::Serialize, serde::Deserialize)]
  2242. pub struct ForgotPasswordRequest {
  2243. pub username: String,
  2244. pub email: String,
  2245. }
  2246. #[tauri::command]
  2247. pub async fn forgot_password_request(username: String, email: String) -> Result<String, String> {
  2248. log::info!("[Rust forgot_password] Step 1 - 收到请求: username='{}', email='{}'", username, email);
  2249. let api_url = format!("{}/api/v1/auth/forgot-password", get_api_base_url()?);
  2250. let client = get_http_client();
  2251. let body = serde_json::json!({ "username": username.trim(), "email": email.trim() });
  2252. log::info!("[Rust forgot_password] Step 2 - 发送请求体: {}", body);
  2253. let response = client
  2254. .post(&api_url)
  2255. .json(&body)
  2256. .send()
  2257. .await
  2258. .map_err(|e| {
  2259. log::error!("[Rust forgot_password] Step 3 - 请求失败: {}", e);
  2260. format!("请求失败: {}", e)
  2261. })?;
  2262. log::info!("[Rust forgot_password] Step 4 - 收到响应状态: {}", response.status());
  2263. let status = response.status();
  2264. let text = response.text().await.map_err(|e| format!("读取响应失败: {}", e))?;
  2265. log::info!("[Rust forgot_password] Step 5 - 响应内容: {}", text);
  2266. if !status.is_success() {
  2267. return Err(format!("请求失败: {}", text));
  2268. }
  2269. let json: serde_json::Value = serde_json::from_str(&text).unwrap_or(serde_json::json!({}));
  2270. let msg = json.get("msg").and_then(|m| m.as_str()).unwrap_or("提交成功,请查收邮箱");
  2271. log::info!("[Rust forgot_password] Step 6 - 返回消息: {}", msg);
  2272. Ok(msg.to_string())
  2273. }
  2274. #[derive(serde::Serialize, serde::Deserialize)]
  2275. pub struct UnbindRequest {
  2276. pub username: String,
  2277. pub password: String,
  2278. /// 与登录时一致的 raw 机器信息(前端从 authStore.machineCode 传入,保证与后端存储的 machine_code 一致)
  2279. #[serde(default)]
  2280. pub machine_code: Option<String>,
  2281. }
  2282. #[derive(serde::Serialize, serde::Deserialize)]
  2283. pub struct UnbindResponse {
  2284. pub message: String,
  2285. pub success: bool,
  2286. }
  2287. #[tauri::command]
  2288. pub async fn secure_user_unbind(request: UnbindRequest) -> Result<UnbindResponse, String> {
  2289. use base64::{Engine as _, engine::general_purpose};
  2290. use serde_json;
  2291. use std::time::{SystemTime, UNIX_EPOCH};
  2292. // 1. 从Rust后端分段存储中重组RSA公钥
  2293. let public_key_pem = retrieve_rsa_key_segments()?;
  2294. // 2. 生成随机AES密钥
  2295. let aes_key = (0..32).map(|_| rand::random::<u8>()).collect::<Vec<u8>>();
  2296. // 3. 使用与登录时一致的 raw_machine_info:优先用前端传入的 machine_code(来自 authStore),否则本机实时生成
  2297. let raw_machine_info = request
  2298. .machine_code
  2299. .filter(|s| !s.is_empty())
  2300. .unwrap_or_else(|| generate_machine_code().unwrap_or_else(|_| String::new()));
  2301. // 4. AES加密请求参数(含 username、password、raw_machine_info)
  2302. let request_data = serde_json::json!({
  2303. "username": request.username,
  2304. "password": request.password,
  2305. "raw_machine_info": raw_machine_info
  2306. });
  2307. let request_json = request_data.to_string();
  2308. let encrypted_params = aes_encrypt(&request_json.as_bytes(), &aes_key)?;
  2309. let encrypted_params_base64 = general_purpose::STANDARD.encode(&encrypted_params);
  2310. // 4. RSA加密AES密钥
  2311. let public_key_parsed = RsaPublicKey::from_public_key_pem(&public_key_pem)
  2312. .map_err(|e| format!("Failed to parse public key: {}", e))?;
  2313. let mut rng = OsRng;
  2314. // 使用OAEP填充,与后端保持一致
  2315. let padding = Oaep::new::<sha2::Sha256>();
  2316. let encrypted_aes_key = public_key_parsed.encrypt(&mut rng, padding, &aes_key)
  2317. .map_err(|e| format!("Failed to encrypt AES key: {}", e))?;
  2318. let encrypted_aes_key_base64 = general_purpose::STANDARD.encode(&encrypted_aes_key);
  2319. // 5. 生成时间戳和nonce
  2320. let timestamp = SystemTime::now()
  2321. .duration_since(UNIX_EPOCH)
  2322. .map_err(|e| format!("Time error: {}", e))?
  2323. .as_secs() as i64;
  2324. let nonce: String = (0..32u8).map(|_| rand::random::<u8>()).map(|b| format!("{:02x}", b)).collect();
  2325. // 6. 获取RSA密钥版本
  2326. let key_version = get_stored_key_version().ok_or_else(|| "RSA公钥版本不存在".to_string())?;
  2327. // 7. 生成签名
  2328. let signature_data = format!("{}:{}:{}:{}:{}", timestamp, nonce, encrypted_params_base64, encrypted_aes_key_base64, key_version);
  2329. type HmacSha256 = Hmac<Sha256>;
  2330. let mut mac = HmacSha256::new_from_slice(get_secret_key().as_bytes())
  2331. .map_err(|e| format!("HMAC init failed: {}", e))?;
  2332. mac.update(signature_data.as_bytes());
  2333. let signature_bytes = mac.finalize().into_bytes();
  2334. let signature = general_purpose::STANDARD.encode(&signature_bytes);
  2335. // 8. 发送HTTP请求
  2336. let client = get_http_client();
  2337. let api_url = format!("{}/api/v1/auth/unbind", get_api_base_url()?);
  2338. let request_body = serde_json::json!({
  2339. "encrypted_params": encrypted_params_base64,
  2340. "aes_key": encrypted_aes_key_base64,
  2341. "key_version": key_version,
  2342. "timestamp": timestamp,
  2343. "nonce": nonce,
  2344. "signature": signature
  2345. });
  2346. let response = client
  2347. .post(api_url)
  2348. .json(&request_body)
  2349. .send()
  2350. .await
  2351. .map_err(|e| format!("HTTP request failed: {}", e))?;
  2352. if !response.status().is_success() {
  2353. return Err(format!("HTTP request failed with status: {}", response.status()));
  2354. }
  2355. let response_json: serde_json::Value = response.json()
  2356. .await
  2357. .map_err(|e| format!("Failed to parse response: {}", e))?;
  2358. // 检查响应状态
  2359. if let Some(code) = response_json.get("code") {
  2360. if code != 200 {
  2361. let msg = response_json.get("msg")
  2362. .and_then(|m| m.as_str())
  2363. .unwrap_or("Unknown error");
  2364. return Err(format!("API error: {}", msg));
  2365. }
  2366. }
  2367. // 8. 解密前先校验响应签名,然后解密响应数据
  2368. if let Some(encrypted_data) = response_json.get("encrypted_data").and_then(|d| d.as_str()) {
  2369. verify_response_signature(&response_json, encrypted_data, key_version)?;
  2370. let encrypted_bytes = general_purpose::STANDARD
  2371. .decode(encrypted_data)
  2372. .map_err(|e| format!("Failed to decode encrypted data: {}", e))?;
  2373. if encrypted_bytes.len() < 16 {
  2374. return Err("Encrypted data too short".to_string());
  2375. }
  2376. // 提取前16字节作为IV,后面的作为加密数据(与后端create_secure_response保持一致)
  2377. let response_iv = &encrypted_bytes[0..16];
  2378. let encrypted_payload = &encrypted_bytes[16..];
  2379. // AES解密响应数据
  2380. let mut cipher = Ctr64BE::<Aes256>::new_from_slices(&aes_key, response_iv)
  2381. .map_err(|e| format!("AES cipher init failed: {}", e))?;
  2382. let mut decrypted_data = encrypted_payload.to_vec();
  2383. cipher.apply_keystream(&mut decrypted_data);
  2384. let decrypted_json: serde_json::Value = serde_json::from_slice(&decrypted_data)
  2385. .map_err(|e| format!("Failed to parse decrypted data: {}", e))?;
  2386. // 检查响应状态码
  2387. let code = decrypted_json.get("code")
  2388. .and_then(|c| c.as_i64())
  2389. .unwrap_or(500) as i32;
  2390. if code != 200 {
  2391. let msg = decrypted_json.get("msg")
  2392. .and_then(|m| m.as_str())
  2393. .unwrap_or("Unknown error");
  2394. return Err(format!("API error {}: {}", code, msg));
  2395. }
  2396. // 提取解绑结果
  2397. let message = decrypted_json.get("message")
  2398. .and_then(|m| m.as_str())
  2399. .unwrap_or("解绑成功")
  2400. .to_string();
  2401. let success = decrypted_json.get("success")
  2402. .and_then(|s| s.as_bool())
  2403. .unwrap_or(true);
  2404. Ok(UnbindResponse {
  2405. message,
  2406. success,
  2407. })
  2408. } else {
  2409. Err("No encrypted_data in response".to_string())
  2410. }
  2411. }
  2412. #[derive(serde::Serialize, serde::Deserialize)]
  2413. pub struct ChangePasswordRequest {
  2414. pub username: String,
  2415. pub old_password: String,
  2416. pub new_password: String,
  2417. }
  2418. #[derive(serde::Serialize, serde::Deserialize)]
  2419. pub struct ChangePasswordResponse {
  2420. pub message: String,
  2421. pub success: bool,
  2422. }
  2423. #[tauri::command]
  2424. pub async fn secure_user_change_password(request: ChangePasswordRequest) -> Result<ChangePasswordResponse, String> {
  2425. use base64::{Engine as _, engine::general_purpose};
  2426. use serde_json;
  2427. use std::time::{SystemTime, UNIX_EPOCH};
  2428. let public_key_pem = retrieve_rsa_key_segments()?;
  2429. let aes_key = (0..32).map(|_| rand::random::<u8>()).collect::<Vec<u8>>();
  2430. let request_data = serde_json::json!({
  2431. "username": request.username,
  2432. "old_password": request.old_password,
  2433. "new_password": request.new_password
  2434. });
  2435. let request_json = request_data.to_string();
  2436. let encrypted_params = aes_encrypt(&request_json.as_bytes(), &aes_key)?;
  2437. let encrypted_params_base64 = general_purpose::STANDARD.encode(&encrypted_params);
  2438. let public_key_parsed = RsaPublicKey::from_public_key_pem(&public_key_pem)
  2439. .map_err(|e| format!("Failed to parse public key: {}", e))?;
  2440. let mut rng = OsRng;
  2441. let padding = Oaep::new::<sha2::Sha256>();
  2442. let encrypted_aes_key = public_key_parsed.encrypt(&mut rng, padding, &aes_key)
  2443. .map_err(|e| format!("Failed to encrypt AES key: {}", e))?;
  2444. let encrypted_aes_key_base64 = general_purpose::STANDARD.encode(&encrypted_aes_key);
  2445. let timestamp = SystemTime::now()
  2446. .duration_since(UNIX_EPOCH)
  2447. .map_err(|e| format!("Time error: {}", e))?
  2448. .as_secs() as i64;
  2449. let nonce: String = (0..32u8).map(|_| rand::random::<u8>()).map(|b| format!("{:02x}", b)).collect();
  2450. let key_version = get_stored_key_version().ok_or_else(|| "RSA公钥版本不存在".to_string())?;
  2451. let signature_data = format!("{}:{}:{}:{}:{}", timestamp, nonce, encrypted_params_base64, encrypted_aes_key_base64, key_version);
  2452. type HmacSha256 = Hmac<Sha256>;
  2453. let mut mac = HmacSha256::new_from_slice(get_secret_key().as_bytes())
  2454. .map_err(|e| format!("HMAC init failed: {}", e))?;
  2455. mac.update(signature_data.as_bytes());
  2456. let signature_bytes = mac.finalize().into_bytes();
  2457. let signature = general_purpose::STANDARD.encode(&signature_bytes);
  2458. let client = get_http_client();
  2459. let api_url = format!("{}/api/v1/auth/change-password", get_api_base_url()?);
  2460. let request_body = serde_json::json!({
  2461. "encrypted_params": encrypted_params_base64,
  2462. "aes_key": encrypted_aes_key_base64,
  2463. "key_version": key_version,
  2464. "timestamp": timestamp,
  2465. "nonce": nonce,
  2466. "signature": signature
  2467. });
  2468. let response = client
  2469. .post(api_url)
  2470. .json(&request_body)
  2471. .send()
  2472. .await
  2473. .map_err(|e| format!("HTTP request failed: {}", e))?;
  2474. if !response.status().is_success() {
  2475. return Err(format!("HTTP request failed with status: {}", response.status()));
  2476. }
  2477. let response_json: serde_json::Value = response.json()
  2478. .await
  2479. .map_err(|e| format!("Failed to parse response: {}", e))?;
  2480. if let Some(code) = response_json.get("code") {
  2481. if code != 200 {
  2482. let msg = response_json.get("msg")
  2483. .and_then(|m| m.as_str())
  2484. .unwrap_or("Unknown error");
  2485. return Err(format!("API error: {}", msg));
  2486. }
  2487. }
  2488. if let Some(encrypted_data) = response_json.get("encrypted_data").and_then(|d| d.as_str()) {
  2489. verify_response_signature(&response_json, encrypted_data, key_version)?;
  2490. let encrypted_bytes = general_purpose::STANDARD
  2491. .decode(encrypted_data)
  2492. .map_err(|e| format!("Failed to decode encrypted data: {}", e))?;
  2493. if encrypted_bytes.len() < 16 {
  2494. return Err("Encrypted data too short".to_string());
  2495. }
  2496. let response_iv = &encrypted_bytes[0..16];
  2497. let encrypted_payload = &encrypted_bytes[16..];
  2498. let mut cipher = Ctr64BE::<Aes256>::new_from_slices(&aes_key, response_iv)
  2499. .map_err(|e| format!("AES cipher init failed: {}", e))?;
  2500. let mut decrypted_data = encrypted_payload.to_vec();
  2501. cipher.apply_keystream(&mut decrypted_data);
  2502. let decrypted_json: serde_json::Value = serde_json::from_slice(&decrypted_data)
  2503. .map_err(|e| format!("Failed to parse decrypted data: {}", e))?;
  2504. let code = decrypted_json.get("code")
  2505. .and_then(|c| c.as_i64())
  2506. .unwrap_or(500) as i32;
  2507. if code != 200 {
  2508. let msg = decrypted_json.get("msg")
  2509. .and_then(|m| m.as_str())
  2510. .unwrap_or("Unknown error");
  2511. return Err(format!("API error {}: {}", code, msg));
  2512. }
  2513. let message = decrypted_json.get("msg")
  2514. .and_then(|m| m.as_str())
  2515. .unwrap_or("密码修改成功")
  2516. .to_string();
  2517. let success = code == 200;
  2518. Ok(ChangePasswordResponse {
  2519. message,
  2520. success,
  2521. })
  2522. } else {
  2523. Err("No encrypted_data in response".to_string())
  2524. }
  2525. }
  2526. #[derive(serde::Serialize, serde::Deserialize)]
  2527. pub struct RechargeRequest {
  2528. pub username: String,
  2529. pub password: String,
  2530. pub card_code: String,
  2531. }
  2532. #[derive(serde::Serialize, serde::Deserialize)]
  2533. pub struct RechargeResponse {
  2534. pub message: String,
  2535. pub success: bool,
  2536. }
  2537. #[tauri::command]
  2538. pub async fn secure_user_recharge(request: RechargeRequest) -> Result<RechargeResponse, String> {
  2539. use base64::{Engine as _, engine::general_purpose};
  2540. use serde_json;
  2541. use std::time::{SystemTime, UNIX_EPOCH};
  2542. // 1. 从Rust后端分段存储中重组RSA公钥
  2543. let public_key_pem = retrieve_rsa_key_segments()?;
  2544. // 2. 生成随机AES密钥
  2545. let aes_key = (0..32).map(|_| rand::random::<u8>()).collect::<Vec<u8>>();
  2546. // 3. AES加密请求参数(含 software_code,供多软件共用用户表)
  2547. let app_config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  2548. let request_data = serde_json::json!({
  2549. "username": request.username,
  2550. "password": request.password,
  2551. "card_code": request.card_code,
  2552. "software_code": app_config.api.software_code,
  2553. });
  2554. let request_json = request_data.to_string();
  2555. let encrypted_params = aes_encrypt(&request_json.as_bytes(), &aes_key)?;
  2556. let encrypted_params_base64 = general_purpose::STANDARD.encode(&encrypted_params);
  2557. // 4. RSA加密AES密钥
  2558. let public_key_parsed = RsaPublicKey::from_public_key_pem(&public_key_pem)
  2559. .map_err(|e| format!("Failed to parse public key: {}", e))?;
  2560. let mut rng = OsRng;
  2561. // 使用OAEP填充,与后端保持一致
  2562. let padding = Oaep::new::<sha2::Sha256>();
  2563. let encrypted_aes_key = public_key_parsed.encrypt(&mut rng, padding, &aes_key)
  2564. .map_err(|e| format!("Failed to encrypt AES key: {}", e))?;
  2565. let encrypted_aes_key_base64 = general_purpose::STANDARD.encode(&encrypted_aes_key);
  2566. // 5. 生成时间戳和nonce
  2567. let timestamp = SystemTime::now()
  2568. .duration_since(UNIX_EPOCH)
  2569. .map_err(|e| format!("Time error: {}", e))?
  2570. .as_secs() as i64;
  2571. let nonce: String = (0..32u8).map(|_| rand::random::<u8>()).map(|b| format!("{:02x}", b)).collect();
  2572. // 6. 获取RSA密钥版本
  2573. let key_version = get_stored_key_version().ok_or_else(|| "RSA公钥版本不存在".to_string())?;
  2574. // 7. 生成签名
  2575. let signature_data = format!("{}:{}:{}:{}:{}", timestamp, nonce, encrypted_params_base64, encrypted_aes_key_base64, key_version);
  2576. type HmacSha256 = Hmac<Sha256>;
  2577. let mut mac = HmacSha256::new_from_slice(get_secret_key().as_bytes())
  2578. .map_err(|e| format!("HMAC init failed: {}", e))?;
  2579. mac.update(signature_data.as_bytes());
  2580. let signature_bytes = mac.finalize().into_bytes();
  2581. let signature = general_purpose::STANDARD.encode(&signature_bytes);
  2582. // 8. 发送HTTP请求
  2583. let client = get_http_client();
  2584. let api_url = format!("{}/api/v1/auth/recharge", get_api_base_url()?);
  2585. let request_body = serde_json::json!({
  2586. "encrypted_params": encrypted_params_base64,
  2587. "aes_key": encrypted_aes_key_base64,
  2588. "key_version": key_version,
  2589. "timestamp": timestamp,
  2590. "nonce": nonce,
  2591. "signature": signature
  2592. });
  2593. let response = client
  2594. .post(api_url)
  2595. .json(&request_body)
  2596. .send()
  2597. .await
  2598. .map_err(|e| format!("HTTP request failed: {}", e))?;
  2599. if !response.status().is_success() {
  2600. return Err(format!("HTTP request failed with status: {}", response.status()));
  2601. }
  2602. let response_json: serde_json::Value = response.json()
  2603. .await
  2604. .map_err(|e| format!("Failed to parse response: {}", e))?;
  2605. // 检查响应状态
  2606. if let Some(code) = response_json.get("code") {
  2607. if code != 200 {
  2608. let msg = response_json.get("msg")
  2609. .and_then(|m| m.as_str())
  2610. .unwrap_or("Unknown error");
  2611. return Err(format!("API error: {}", msg));
  2612. }
  2613. }
  2614. // 8. 解密前先校验响应签名,然后解密响应数据
  2615. if let Some(encrypted_data) = response_json.get("encrypted_data").and_then(|d| d.as_str()) {
  2616. verify_response_signature(&response_json, encrypted_data, key_version)?;
  2617. let encrypted_bytes = general_purpose::STANDARD
  2618. .decode(encrypted_data)
  2619. .map_err(|e| format!("Failed to decode encrypted data: {}", e))?;
  2620. if encrypted_bytes.len() < 16 {
  2621. return Err("Invalid encrypted data length".to_string());
  2622. }
  2623. // 提取前16字节作为IV,后面的作为加密数据(与后端create_secure_response保持一致)
  2624. let response_iv = &encrypted_bytes[0..16];
  2625. let encrypted_payload = &encrypted_bytes[16..];
  2626. // AES解密响应数据
  2627. let mut cipher = Ctr64BE::<Aes256>::new_from_slices(&aes_key, response_iv)
  2628. .map_err(|e| format!("AES cipher init failed: {}", e))?;
  2629. let mut decrypted_data = encrypted_payload.to_vec();
  2630. cipher.apply_keystream(&mut decrypted_data);
  2631. let decrypted_json: serde_json::Value = serde_json::from_slice(&decrypted_data)
  2632. .map_err(|e| format!("Failed to parse decrypted data: {}", e))?;
  2633. // 检查响应状态码
  2634. let code = decrypted_json.get("code")
  2635. .and_then(|c| c.as_i64())
  2636. .unwrap_or(500) as i32;
  2637. if code != 200 {
  2638. let msg = decrypted_json.get("msg")
  2639. .and_then(|m| m.as_str())
  2640. .unwrap_or("Unknown error");
  2641. return Err(format!("API error {}: {}", code, msg));
  2642. }
  2643. // 提取充值结果
  2644. let message = decrypted_json.get("message")
  2645. .and_then(|m| m.as_str())
  2646. .unwrap_or("充值成功")
  2647. .to_string();
  2648. let success = decrypted_json.get("success")
  2649. .and_then(|s| s.as_bool())
  2650. .unwrap_or(true);
  2651. Ok(RechargeResponse {
  2652. message,
  2653. success,
  2654. })
  2655. } else {
  2656. Err("No encrypted_data in response".to_string())
  2657. }
  2658. }
  2659. /// 安全获取用户信息(单次):AES+RSA 加密请求,验证并解密响应
  2660. async fn secure_get_user_profile_call_inner(
  2661. api_url: &str,
  2662. session_id: &str,
  2663. machine_code: &str,
  2664. ) -> Result<serde_json::Value, String> {
  2665. use std::time::{SystemTime, UNIX_EPOCH};
  2666. let public_key_pem = retrieve_rsa_key_segments()?;
  2667. let key_version = get_stored_key_version().ok_or_else(|| "RSA公钥版本不存在".to_string())?;
  2668. let aes_key: Vec<u8> = (0..32).map(|_| rand::random::<u8>()).collect();
  2669. let request_data = serde_json::json!({
  2670. "session_id": session_id,
  2671. "machine_code": machine_code
  2672. });
  2673. let request_json = request_data.to_string();
  2674. let encrypted_params_bytes = aes_encrypt(request_json.as_bytes(), &aes_key)
  2675. .map_err(|e| format!("AES加密失败: {}", e))?;
  2676. let encrypted_params_base64 = general_purpose::STANDARD.encode(&encrypted_params_bytes);
  2677. let encrypted_aes_key_base64 = crypto_rsa_encrypt_aes_key(&aes_key, &public_key_pem)
  2678. .map_err(|e| format!("RSA加密AES密钥失败: {}", e))?;
  2679. let timestamp = SystemTime::now()
  2680. .duration_since(UNIX_EPOCH)
  2681. .map_err(|e| format!("Time error: {}", e))?
  2682. .as_secs() as i64;
  2683. let nonce: String = (0..32u8).map(|_| rand::random::<u8>()).map(|b| format!("{:02x}", b)).collect();
  2684. let signature_data = format!(
  2685. "{}:{}:{}:{}:{}",
  2686. timestamp, nonce, encrypted_params_base64, encrypted_aes_key_base64, key_version
  2687. );
  2688. let signature = generate_hmac_signature(&signature_data, &get_secret_key())
  2689. .map_err(|e| format!("HMAC签名失败: {}", e))?;
  2690. let client = get_http_client();
  2691. let url = format!("{}/api/v1/user/profile", api_url.trim_end_matches('/'));
  2692. let request_body = serde_json::json!({
  2693. "encrypted_params": encrypted_params_base64,
  2694. "aes_key": encrypted_aes_key_base64,
  2695. "key_version": key_version,
  2696. "timestamp": timestamp,
  2697. "nonce": nonce,
  2698. "signature": signature
  2699. });
  2700. let response = client
  2701. .post(&url)
  2702. .json(&request_body)
  2703. .send()
  2704. .await
  2705. .map_err(|e| format!("请求失败: {}", e))?;
  2706. if !response.status().is_success() {
  2707. return Err(format!("请求失败: {}", response.status()));
  2708. }
  2709. let response_json: serde_json::Value = response
  2710. .json()
  2711. .await
  2712. .map_err(|e| format!("解析响应失败: {}", e))?;
  2713. if let Some(code) = response_json.get("code") {
  2714. if code != 200 {
  2715. let msg = response_json
  2716. .get("msg")
  2717. .and_then(|m| m.as_str())
  2718. .unwrap_or("未知错误");
  2719. return Err(format!("API error: {}", msg));
  2720. }
  2721. }
  2722. let encrypted_data = response_json
  2723. .get("encrypted_data")
  2724. .and_then(|d| d.as_str())
  2725. .ok_or("响应中缺少 encrypted_data")?;
  2726. verify_response_signature(&response_json, encrypted_data, key_version)?;
  2727. let encrypted_bytes = general_purpose::STANDARD
  2728. .decode(encrypted_data)
  2729. .map_err(|e| format!("Base64解码失败: {}", e))?;
  2730. if encrypted_bytes.len() < 16 {
  2731. return Err("加密数据长度不足".to_string());
  2732. }
  2733. let decrypted_bytes = aes_decrypt(&encrypted_bytes, &aes_key)
  2734. .map_err(|e| format!("AES解密失败: {}", e))?;
  2735. let decrypted_json: serde_json::Value = serde_json::from_slice(&decrypted_bytes)
  2736. .map_err(|e| format!("解析解密数据失败: {}", e))?;
  2737. let code = decrypted_json.get("code").and_then(|c| c.as_i64()).unwrap_or(500) as i32;
  2738. if code != 200 {
  2739. let msg = decrypted_json
  2740. .get("msg")
  2741. .and_then(|m| m.as_str())
  2742. .unwrap_or("未知错误");
  2743. return Err(format!("{}: {}", code, msg));
  2744. }
  2745. Ok(decrypted_json)
  2746. }
  2747. /// 安全获取用户信息:AES+RSA 加密请求,验证并解密响应
  2748. pub async fn secure_get_user_profile_call(
  2749. api_url: &str,
  2750. session_id: &str,
  2751. machine_code: &str,
  2752. ) -> Result<serde_json::Value, String> {
  2753. secure_get_user_profile_call_inner(api_url, session_id, machine_code).await
  2754. }
  2755. /// 安全密钥同步(单次):POST /api/v1/keys/sync(已登录),返回解密后的 public_key、key_version
  2756. async fn secure_keys_sync_call_inner(
  2757. api_url: &str,
  2758. session_id: &str,
  2759. machine_code: &str,
  2760. ) -> Result<serde_json::Value, String> {
  2761. use std::time::{SystemTime, UNIX_EPOCH};
  2762. let public_key_pem = retrieve_rsa_key_segments()?;
  2763. let key_version = get_stored_key_version().ok_or_else(|| "RSA公钥版本不存在".to_string())?;
  2764. let aes_key: Vec<u8> = (0..32).map(|_| rand::random::<u8>()).collect();
  2765. let request_data = serde_json::json!({
  2766. "session_id": session_id,
  2767. "machine_code": machine_code
  2768. });
  2769. let request_json = request_data.to_string();
  2770. let encrypted_params_bytes = aes_encrypt(request_json.as_bytes(), &aes_key)
  2771. .map_err(|e| format!("AES加密失败: {}", e))?;
  2772. let encrypted_params_base64 = general_purpose::STANDARD.encode(&encrypted_params_bytes);
  2773. let encrypted_aes_key_base64 = crypto_rsa_encrypt_aes_key(&aes_key, &public_key_pem)
  2774. .map_err(|e| format!("RSA加密AES密钥失败: {}", e))?;
  2775. let timestamp = SystemTime::now()
  2776. .duration_since(UNIX_EPOCH)
  2777. .map_err(|e| format!("Time error: {}", e))?
  2778. .as_secs() as i64;
  2779. let nonce: String = (0..32u8).map(|_| rand::random::<u8>()).map(|b| format!("{:02x}", b)).collect();
  2780. let signature_data = format!(
  2781. "{}:{}:{}:{}:{}",
  2782. timestamp, nonce, encrypted_params_base64, encrypted_aes_key_base64, key_version
  2783. );
  2784. let signature = generate_hmac_signature(&signature_data, &get_secret_key())
  2785. .map_err(|e| format!("HMAC签名失败: {}", e))?;
  2786. let client = get_http_client();
  2787. let url = format!("{}/api/v1/keys/sync", api_url.trim_end_matches('/'));
  2788. let request_body = serde_json::json!({
  2789. "encrypted_params": encrypted_params_base64,
  2790. "aes_key": encrypted_aes_key_base64,
  2791. "key_version": key_version,
  2792. "timestamp": timestamp,
  2793. "nonce": nonce,
  2794. "signature": signature
  2795. });
  2796. let response = client
  2797. .post(&url)
  2798. .json(&request_body)
  2799. .send()
  2800. .await
  2801. .map_err(|e| format!("密钥同步请求失败: {}", e))?;
  2802. if !response.status().is_success() {
  2803. return Err(format!("密钥同步响应错误: {}", response.status()));
  2804. }
  2805. let response_json: serde_json::Value = response
  2806. .json()
  2807. .await
  2808. .map_err(|e| format!("解析密钥同步响应失败: {}", e))?;
  2809. if let Some(code) = response_json.get("code") {
  2810. if code != 200 {
  2811. let msg = response_json
  2812. .get("msg")
  2813. .and_then(|m| m.as_str())
  2814. .unwrap_or("未知错误");
  2815. return Err(format!("API error: {}", msg));
  2816. }
  2817. }
  2818. let encrypted_data = response_json
  2819. .get("encrypted_data")
  2820. .and_then(|d| d.as_str())
  2821. .ok_or("响应中缺少 encrypted_data")?;
  2822. verify_response_signature(&response_json, encrypted_data, key_version)?;
  2823. let encrypted_bytes = general_purpose::STANDARD
  2824. .decode(encrypted_data)
  2825. .map_err(|e| format!("Base64解码失败: {}", e))?;
  2826. if encrypted_bytes.len() < 16 {
  2827. return Err("加密数据长度不足".to_string());
  2828. }
  2829. let decrypted_bytes = aes_decrypt(&encrypted_bytes, &aes_key)
  2830. .map_err(|e| format!("AES解密失败: {}", e))?;
  2831. let decrypted_json: serde_json::Value = serde_json::from_slice(&decrypted_bytes)
  2832. .map_err(|e| format!("解析解密数据失败: {}", e))?;
  2833. let code = decrypted_json.get("code").and_then(|c| c.as_i64()).unwrap_or(500) as i32;
  2834. if code != 200 {
  2835. let msg = decrypted_json
  2836. .get("msg")
  2837. .and_then(|m| m.as_str())
  2838. .unwrap_or("未知错误");
  2839. return Err(format!("{}: {}", code, msg));
  2840. }
  2841. Ok(decrypted_json)
  2842. }
  2843. /// 安全密钥同步:POST /api/v1/keys/sync(已登录),返回解密后的 public_key、key_version
  2844. pub async fn secure_keys_sync_call(
  2845. api_url: &str,
  2846. session_id: &str,
  2847. machine_code: &str,
  2848. ) -> Result<serde_json::Value, String> {
  2849. secure_keys_sync_call_inner(api_url, session_id, machine_code).await
  2850. }
  2851. /// 安全密钥同步并更新本地存储(供 heartbeat 密钥同步任务调用)
  2852. pub async fn secure_keys_sync_and_store(
  2853. api_url: &str,
  2854. session_id: &str,
  2855. machine_code: &str,
  2856. ) -> Result<bool, String> {
  2857. let decrypted = secure_keys_sync_call(api_url, session_id, machine_code).await?;
  2858. let public_key = decrypted
  2859. .get("public_key")
  2860. .and_then(|v| v.as_str())
  2861. .ok_or("响应缺少 public_key")?;
  2862. let key_version = decrypted
  2863. .get("key_version")
  2864. .and_then(|v| v.as_i64())
  2865. .ok_or("响应缺少 key_version")? as i32;
  2866. store_rsa_key_segments(public_key.to_string(), key_version);
  2867. Ok(true)
  2868. }
  2869. /// 获取客户端版本号
  2870. fn get_client_version() -> String {
  2871. // 从 Cargo.toml 的 package.version 获取
  2872. env!("CARGO_PKG_VERSION").to_string()
  2873. }
  2874. /// 构建并发送单次安全心跳请求,返回原始响应 JSON(解密前的)。
  2875. /// 需要外部保证 nonce 唯一(用 generate_unique_nonce),并在请求前 record_nonce。
  2876. async fn do_secure_heartbeat_request(
  2877. api_url: &str,
  2878. session_id: &str,
  2879. machine_code: &str,
  2880. nonce: &str,
  2881. ) -> Result<serde_json::Value, String> {
  2882. let public_key_pem = retrieve_rsa_key_segments()?;
  2883. let key_version = get_stored_key_version().ok_or_else(|| "RSA公钥版本不存在".to_string())?;
  2884. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  2885. let aes_key: Vec<u8> = (0..32).map(|_| rand::random::<u8>()).collect();
  2886. let request_data = serde_json::json!({
  2887. "session_id": session_id,
  2888. "machine_code": machine_code,
  2889. "software_code": config.api.software_code,
  2890. "client_version": get_client_version(),
  2891. "platform": "windows",
  2892. "channel": "stable",
  2893. });
  2894. let request_json = request_data.to_string();
  2895. let encrypted_params_bytes = aes_encrypt(request_json.as_bytes(), &aes_key)
  2896. .map_err(|e| format!("AES加密失败: {}", e))?;
  2897. let encrypted_params_base64 = general_purpose::STANDARD.encode(&encrypted_params_bytes);
  2898. let encrypted_aes_key_base64 = crypto_rsa_encrypt_aes_key(&aes_key, &public_key_pem)
  2899. .map_err(|e| format!("RSA加密AES密钥失败: {}", e))?;
  2900. let timestamp = std::time::SystemTime::now()
  2901. .duration_since(std::time::UNIX_EPOCH)
  2902. .map_err(|e| format!("Time error: {}", e))?
  2903. .as_secs() as i64;
  2904. let signature_data = format!(
  2905. "{}:{}:{}:{}:{}",
  2906. timestamp, nonce, encrypted_params_base64, encrypted_aes_key_base64, key_version
  2907. );
  2908. let signature = generate_hmac_signature(&signature_data, &get_secret_key())
  2909. .map_err(|e| format!("HMAC签名失败: {}", e))?;
  2910. let client = get_http_client();
  2911. let url = format!("{}/api/v1/heartbeat", api_url.trim_end_matches('/'));
  2912. let request_body = serde_json::json!({
  2913. "encrypted_params": encrypted_params_base64,
  2914. "aes_key": encrypted_aes_key_base64,
  2915. "key_version": key_version,
  2916. "timestamp": timestamp,
  2917. "nonce": nonce,
  2918. "signature": signature
  2919. });
  2920. let response = client
  2921. .post(&url)
  2922. .json(&request_body)
  2923. .send()
  2924. .await
  2925. .map_err(|e| format!("心跳请求失败: {}", e))?;
  2926. let status = response.status();
  2927. // 401/403:认证问题,原样返回给前端
  2928. if status.as_u16() == 401 || status.as_u16() == 403 {
  2929. let body = response.text().await.unwrap_or_else(|_| "{}".to_string());
  2930. let body_json: serde_json::Value = serde_json::from_str(&body).unwrap_or(serde_json::json!({}));
  2931. let code = body_json.get("code").and_then(|c| c.as_i64()).unwrap_or(status.as_u16() as i64) as i32;
  2932. let msg = body_json.get("msg").and_then(|m| m.as_str()).unwrap_or(
  2933. if status.as_u16() == 401 { "会话已过期,请重新登录" } else { "权限不足" }
  2934. ).to_string();
  2935. return Ok(serde_json::json!({ "code": code, "msg": msg, "data": {} }));
  2936. }
  2937. // 400 Bad Request:可能是 nonce 冲突(后端防重放拒绝),需要重试
  2938. if status.as_u16() == 400 {
  2939. return Err("400".to_string());
  2940. }
  2941. if !status.is_success() {
  2942. return Err(format!("心跳响应错误: {}", status));
  2943. }
  2944. let response_json: serde_json::Value = response.json().await
  2945. .map_err(|e| format!("解析心跳响应失败: {}", e))?;
  2946. if let Some(code) = response_json.get("code") {
  2947. if code != 200 {
  2948. let msg = response_json.get("msg").and_then(|m| m.as_str()).unwrap_or("未知错误");
  2949. return Err(format!("API error: {}", msg));
  2950. }
  2951. }
  2952. let encrypted_data = response_json
  2953. .get("encrypted_data")
  2954. .and_then(|d| d.as_str())
  2955. .ok_or("响应中缺少 encrypted_data")?;
  2956. verify_response_signature(&response_json, encrypted_data, key_version)?;
  2957. let encrypted_bytes = general_purpose::STANDARD.decode(encrypted_data)
  2958. .map_err(|e| format!("Base64解码失败: {}", e))?;
  2959. if encrypted_bytes.len() < 16 {
  2960. return Err("加密数据长度不足".to_string());
  2961. }
  2962. let decrypted_bytes = aes_decrypt(&encrypted_bytes, &aes_key)
  2963. .map_err(|e| format!("AES解密失败: {}", e))?;
  2964. let decrypted_json: serde_json::Value = serde_json::from_slice(&decrypted_bytes)
  2965. .map_err(|e| format!("解析解密数据失败: {}", e))?;
  2966. Ok(decrypted_json)
  2967. }
  2968. /// 安全心跳调用(内部重试逻辑):
  2969. /// 1. 生成唯一 nonce
  2970. /// 2. 记录 nonce 后发请求
  2971. /// 3. 收到 400(nonce 冲突)则重试一次(新 nonce + 重新签名)
  2972. async fn secure_heartbeat_call_inner(
  2973. api_url: &str,
  2974. session_id: &str,
  2975. machine_code: &str,
  2976. ) -> Result<serde_json::Value, String> {
  2977. // 第一次尝试
  2978. let nonce1 = generate_unique_nonce();
  2979. record_nonce(&nonce1);
  2980. let result = do_secure_heartbeat_request(api_url, session_id, machine_code, &nonce1).await;
  2981. match result {
  2982. Ok(resp) => Ok(resp),
  2983. Err(e) if e == "400" => {
  2984. // Nonce 冲突,后端拒绝。生成新 nonce,重试一次
  2985. warn!("[Heartbeat] 心跳被后端防重放拒绝(nonce冲突),重试...");
  2986. let nonce2 = generate_unique_nonce();
  2987. record_nonce(&nonce2);
  2988. do_secure_heartbeat_request(api_url, session_id, machine_code, &nonce2).await
  2989. }
  2990. Err(e) => Err(e),
  2991. }
  2992. }
  2993. /// 通用安全 POST:构建加密请求体、发送 POST、验证并解密响应。
  2994. /// path 为相对路径,如 "/api/v1/buff/filter-options";request_data 需包含 session_id、machine_code 及业务字段。
  2995. pub async fn secure_post_decrypt(
  2996. api_url: &str,
  2997. path: &str,
  2998. request_data: &serde_json::Value,
  2999. ) -> Result<serde_json::Value, String> {
  3000. use std::time::{SystemTime, UNIX_EPOCH};
  3001. let nonce1 = generate_unique_nonce();
  3002. record_nonce(&nonce1);
  3003. let result = do_secure_post_request(api_url, path, request_data, &nonce1).await;
  3004. match result {
  3005. Ok(resp) => Ok(resp),
  3006. Err(e) if e == "400" => {
  3007. // Nonce 冲突(防重放拒绝),生成新 nonce 重试一次
  3008. warn!("[SecurePost] 接口 {} 被防重放拒绝,重试...", path);
  3009. let nonce2 = generate_unique_nonce();
  3010. record_nonce(&nonce2);
  3011. do_secure_post_request(api_url, path, request_data, &nonce2).await
  3012. }
  3013. Err(e) => Err(e),
  3014. }
  3015. }
  3016. async fn do_secure_post_request(
  3017. api_url: &str,
  3018. path: &str,
  3019. request_data: &serde_json::Value,
  3020. nonce: &str,
  3021. ) -> Result<serde_json::Value, String> {
  3022. use std::time::{SystemTime, UNIX_EPOCH};
  3023. let public_key_pem = retrieve_rsa_key_segments()?;
  3024. let key_version = get_stored_key_version().ok_or_else(|| "RSA公钥版本不存在".to_string())?;
  3025. let aes_key: Vec<u8> = (0..32).map(|_| rand::random::<u8>()).collect();
  3026. let request_json = request_data.to_string();
  3027. let encrypted_params_bytes = aes_encrypt(request_json.as_bytes(), &aes_key)
  3028. .map_err(|e| format!("AES加密失败: {}", e))?;
  3029. let encrypted_params_base64 = general_purpose::STANDARD.encode(&encrypted_params_bytes);
  3030. let encrypted_aes_key_base64 = crypto_rsa_encrypt_aes_key(&aes_key, &public_key_pem)
  3031. .map_err(|e| format!("RSA加密AES密钥失败: {}", e))?;
  3032. let timestamp = SystemTime::now()
  3033. .duration_since(UNIX_EPOCH)
  3034. .map_err(|e| format!("Time error: {}", e))?
  3035. .as_secs() as i64;
  3036. let signature_data = format!(
  3037. "{}:{}:{}:{}:{}",
  3038. timestamp, nonce, encrypted_params_base64, encrypted_aes_key_base64, key_version
  3039. );
  3040. let signature = generate_hmac_signature(&signature_data, &get_secret_key())
  3041. .map_err(|e| format!("HMAC签名失败: {}", e))?;
  3042. let client = get_http_client();
  3043. let url = format!("{}{}", api_url.trim_end_matches('/'), path);
  3044. let request_body = serde_json::json!({
  3045. "encrypted_params": encrypted_params_base64,
  3046. "aes_key": encrypted_aes_key_base64,
  3047. "key_version": key_version,
  3048. "timestamp": timestamp,
  3049. "nonce": nonce,
  3050. "signature": signature
  3051. });
  3052. let response = client
  3053. .post(&url)
  3054. .json(&request_body)
  3055. .send()
  3056. .await
  3057. .map_err(|e| format!("请求失败: {}", e))?;
  3058. let status = response.status();
  3059. if status.as_u16() == 401 || status.as_u16() == 403 {
  3060. let body = response.text().await.unwrap_or_else(|_| "{}".to_string());
  3061. let body_json: serde_json::Value = serde_json::from_str(&body).unwrap_or(serde_json::json!({}));
  3062. let code = body_json.get("code").and_then(|c| c.as_i64()).unwrap_or(status.as_u16() as i64) as i32;
  3063. let msg = body_json.get("msg").and_then(|m| m.as_str()).unwrap_or(
  3064. if status.as_u16() == 401 { "会话已过期,请重新登录" } else { "权限不足" }
  3065. ).to_string();
  3066. return Ok(serde_json::json!({ "code": code, "msg": msg, "data": serde_json::Value::Null }));
  3067. }
  3068. // 400 Bad Request:可能是 nonce 冲突(后端防重放拒绝)
  3069. if status.as_u16() == 400 {
  3070. return Err("400".to_string());
  3071. }
  3072. if !status.is_success() {
  3073. return Err(format!("响应错误: {}", status));
  3074. }
  3075. let response_json: serde_json::Value = response.json().await
  3076. .map_err(|e| format!("解析响应失败: {}", e))?;
  3077. // 业务错误(如 403 今日次数上限)仍返回 Ok,由前端根据 code 展示 msg
  3078. let encrypted_data = response_json.get("encrypted_data").and_then(|d| d.as_str());
  3079. let decrypted_json = if let Some(encrypted_data) = encrypted_data {
  3080. verify_response_signature(&response_json, encrypted_data, key_version)?;
  3081. let encrypted_bytes = general_purpose::STANDARD
  3082. .decode(encrypted_data)
  3083. .map_err(|e| format!("Base64解码失败: {}", e))?;
  3084. if encrypted_bytes.len() < 16 {
  3085. return Err("加密数据长度不足".to_string());
  3086. }
  3087. let decrypted_bytes = aes_decrypt(&encrypted_bytes, &aes_key)
  3088. .map_err(|e| format!("AES解密失败: {}", e))?;
  3089. serde_json::from_slice::<serde_json::Value>(&decrypted_bytes)
  3090. .map_err(|e| format!("解析解密数据失败: {}", e))?
  3091. } else {
  3092. response_json.clone()
  3093. };
  3094. Ok(decrypted_json)
  3095. }
  3096. /// 安全心跳调用:AES+RSA 加密请求,验证并解密响应(供 heartbeat 模块使用)
  3097. pub async fn secure_heartbeat_call(
  3098. api_url: &str,
  3099. session_id: &str,
  3100. machine_code: &str,
  3101. ) -> Result<serde_json::Value, String> {
  3102. secure_heartbeat_call_inner(api_url, session_id, machine_code).await
  3103. }
  3104. /// 供前端状态栏调用的安全心跳命令(与后台心跳同一套加密流程,避免 fetch_public_key 竞态和解密错误)
  3105. #[tauri::command]
  3106. pub async fn secure_heartbeat(session_id: String, machine_code: String) -> Result<String, String> {
  3107. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  3108. let api_url = config.api.base_url.trim_end_matches('/');
  3109. let decrypted = secure_heartbeat_call_inner(api_url, &session_id, &machine_code).await?;
  3110. // 后端解密后为 { code, msg, config?, user_info?, session_info? },转为前端期望的 { code, msg, data: { config, session_info, user_info } }
  3111. let code = decrypted.get("code").and_then(|c| c.as_i64()).unwrap_or(200) as i32;
  3112. let msg = decrypted
  3113. .get("msg")
  3114. .and_then(|m| m.as_str())
  3115. .unwrap_or("成功")
  3116. .to_string();
  3117. let data = serde_json::json!({
  3118. "config": decrypted.get("config"),
  3119. "session_info": decrypted.get("session_info"),
  3120. "user_info": decrypted.get("user_info"),
  3121. });
  3122. let response = serde_json::json!({ "code": code, "msg": msg, "data": data });
  3123. serde_json::to_string(&response).map_err(|e| format!("序列化心跳响应失败: {}", e))
  3124. }
  3125. // HMAC 签名密钥:按年份写死,与后端一致(CLIENT_SECRET = buff-client-secret-{年})
  3126. fn get_secret_key() -> String {
  3127. format!("buff-client-secret-{}", Utc::now().year())
  3128. }
  3129. fn get_server_secret_key() -> String {
  3130. format!("buff-server-secret-{}", Utc::now().year())
  3131. }
  3132. // ---------- Buff 选品等安全 POST 命令(与后端 @secure_endpoint 对应) ----------
  3133. #[tauri::command]
  3134. pub async fn secure_buff_filter_options(
  3135. session_id: String,
  3136. machine_code: String,
  3137. game: Option<String>,
  3138. ) -> Result<String, String> {
  3139. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  3140. let api_url = config.api.base_url.trim_end_matches('/');
  3141. let request_data = serde_json::json!({
  3142. "session_id": session_id,
  3143. "machine_code": machine_code,
  3144. "game": game.unwrap_or_else(|| "csgo".to_string()),
  3145. });
  3146. let decrypted = secure_post_decrypt(api_url, "/api/v1/buff/filter-options", &request_data).await?;
  3147. serde_json::to_string(&decrypted).map_err(|e| format!("序列化失败: {}", e))
  3148. }
  3149. #[tauri::command]
  3150. pub async fn secure_buff_goods_attr_list(
  3151. session_id: String,
  3152. machine_code: String,
  3153. goods_ids: Vec<i64>,
  3154. ) -> Result<String, String> {
  3155. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  3156. let api_url = config.api.base_url.trim_end_matches('/');
  3157. let request_data = serde_json::json!({
  3158. "session_id": session_id,
  3159. "machine_code": machine_code,
  3160. "goods_ids": goods_ids,
  3161. });
  3162. let decrypted = secure_post_decrypt(api_url, "/api/v1/buff/goods-attr-list", &request_data).await?;
  3163. serde_json::to_string(&decrypted).map_err(|e| format!("序列化失败: {}", e))
  3164. }
  3165. #[tauri::command]
  3166. pub async fn secure_buff_selection_config(
  3167. session_id: String,
  3168. machine_code: String,
  3169. action: String,
  3170. name: Option<String>,
  3171. config_json: Option<String>,
  3172. is_default: Option<bool>,
  3173. ) -> Result<String, String> {
  3174. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  3175. let api_url = config.api.base_url.trim_end_matches('/');
  3176. let mut request_data = serde_json::json!({
  3177. "session_id": session_id,
  3178. "machine_code": machine_code,
  3179. "action": action,
  3180. });
  3181. if let Some(n) = name {
  3182. request_data["name"] = serde_json::Value::String(n);
  3183. }
  3184. if let Some(c) = config_json {
  3185. request_data["config_json"] = serde_json::Value::String(c);
  3186. }
  3187. if let Some(d) = is_default {
  3188. request_data["is_default"] = serde_json::Value::Bool(d);
  3189. }
  3190. let decrypted = secure_post_decrypt(api_url, "/api/v1/buff/secure/selection-config", &request_data).await?;
  3191. serde_json::to_string(&decrypted).map_err(|e| format!("序列化失败: {}", e))
  3192. }
  3193. #[tauri::command]
  3194. pub async fn secure_buff_selection_start(
  3195. session_id: String,
  3196. machine_code: String,
  3197. config_id: Option<i64>,
  3198. ) -> Result<String, String> {
  3199. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  3200. let api_url = config.api.base_url.trim_end_matches('/');
  3201. let mut request_data = serde_json::json!({
  3202. "session_id": session_id,
  3203. "machine_code": machine_code,
  3204. });
  3205. if let Some(id) = config_id {
  3206. request_data["config_id"] = serde_json::json!(id);
  3207. }
  3208. let decrypted = secure_post_decrypt(api_url, "/api/v1/buff/secure/selection-start", &request_data).await?;
  3209. serde_json::to_string(&decrypted).map_err(|e| format!("序列化失败: {}", e))
  3210. }
  3211. #[tauri::command]
  3212. pub async fn secure_buff_selection_log(
  3213. session_id: String,
  3214. machine_code: String,
  3215. ) -> Result<String, String> {
  3216. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  3217. let api_url = config.api.base_url.trim_end_matches('/');
  3218. let request_data = serde_json::json!({
  3219. "session_id": session_id,
  3220. "machine_code": machine_code,
  3221. });
  3222. let decrypted = secure_post_decrypt(api_url, "/api/v1/buff/secure/selection-log", &request_data).await?;
  3223. serde_json::to_string(&decrypted).map_err(|e| format!("序列化失败: {}", e))
  3224. }
  3225. #[tauri::command]
  3226. pub async fn secure_buff_selection_result(
  3227. session_id: String,
  3228. machine_code: String,
  3229. selection_request_id: Option<i64>,
  3230. ) -> Result<String, String> {
  3231. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  3232. let api_url = config.api.base_url.trim_end_matches('/');
  3233. let mut request_data = serde_json::json!({
  3234. "session_id": session_id,
  3235. "machine_code": machine_code,
  3236. });
  3237. if let Some(sid) = selection_request_id {
  3238. if sid > 0 {
  3239. request_data["selection_request_id"] = serde_json::json!(sid);
  3240. }
  3241. }
  3242. let decrypted = secure_post_decrypt(api_url, "/api/v1/buff/secure/selection-result", &request_data).await?;
  3243. serde_json::to_string(&decrypted).map_err(|e| format!("序列化失败: {}", e))
  3244. }
  3245. async fn buff_secure_selection_stop_decrypt(
  3246. session_id: &str,
  3247. machine_code: &str,
  3248. ) -> Result<serde_json::Value, String> {
  3249. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  3250. let api_url = config.api.base_url.trim_end_matches('/');
  3251. let request_data = serde_json::json!({
  3252. "session_id": session_id,
  3253. "machine_code": machine_code,
  3254. });
  3255. secure_post_decrypt(api_url, "/api/v1/buff/secure/selection-stop", &request_data).await
  3256. }
  3257. async fn buff_secure_sweep_stop_decrypt(
  3258. session_id: &str,
  3259. machine_code: &str,
  3260. ) -> Result<serde_json::Value, String> {
  3261. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  3262. let api_url = config.api.base_url.trim_end_matches('/');
  3263. let request_data = serde_json::json!({
  3264. "session_id": session_id,
  3265. "machine_code": machine_code,
  3266. });
  3267. secure_post_decrypt(api_url, "/api/v1/buff/secure/sweep-stop", &request_data).await
  3268. }
  3269. #[tauri::command]
  3270. pub async fn secure_buff_selection_stop(
  3271. session_id: String,
  3272. machine_code: String,
  3273. ) -> Result<String, String> {
  3274. let decrypted = buff_secure_selection_stop_decrypt(&session_id, &machine_code).await?;
  3275. serde_json::to_string(&decrypted).map_err(|e| format!("序列化失败: {}", e))
  3276. }
  3277. #[tauri::command]
  3278. pub async fn secure_keys_hmac(
  3279. session_id: String,
  3280. machine_code: String,
  3281. ) -> Result<String, String> {
  3282. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  3283. let api_url = config.api.base_url.trim_end_matches('/');
  3284. let request_data = serde_json::json!({
  3285. "session_id": session_id,
  3286. "machine_code": machine_code,
  3287. });
  3288. let decrypted = secure_post_decrypt(api_url, "/api/v1/keys/hmac/secure", &request_data).await?;
  3289. if let (Some(200), Some(data)) = (decrypted.get("code").and_then(|c| c.as_i64()), decrypted.get("data")) {
  3290. if let (Some(hmac_key), Some(expires_at_val)) = (data.get("hmac_key").and_then(|v| v.as_str()), data.get("expires_at")) {
  3291. let expires_at_u64: u64 = if let Some(s) = expires_at_val.as_str() {
  3292. chrono::DateTime::parse_from_rfc3339(s)
  3293. .map(|dt| dt.timestamp().max(0) as u64)
  3294. .or_else(|_| {
  3295. chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S")
  3296. .map(|naive| naive.and_utc().timestamp().max(0) as u64)
  3297. })
  3298. .unwrap_or_else(|_| s.parse::<u64>().unwrap_or(0))
  3299. } else if let Some(n) = expires_at_val.as_i64() {
  3300. (n as u64).max(0)
  3301. } else {
  3302. 0
  3303. };
  3304. if !hmac_key.is_empty() && expires_at_u64 > 0 {
  3305. store_hmac_key_from_api(hmac_key.to_string(), expires_at_u64);
  3306. }
  3307. }
  3308. }
  3309. serde_json::to_string(&decrypted).map_err(|e| format!("序列化失败: {}", e))
  3310. }
  3311. #[tauri::command]
  3312. pub async fn store_rsa_public_key(public_key: String, key_version: i32) -> Result<(), String> {
  3313. // 使用 crypto/key_storage 统一存储,与 initialize_key_manager 同一套存储
  3314. store_rsa_key_segments(public_key.clone(), key_version);
  3315. // 调试:验证存储和重组是否正确(生产环境请移除)
  3316. #[cfg(debug_assertions)]
  3317. {
  3318. println!("RSA公钥已安全存储,版本: {}", key_version);
  3319. match retrieve_rsa_key_segments() {
  3320. Ok(retrieved) if retrieved == public_key => {
  3321. println!("✅ RSA公钥存储和重组验证成功");
  3322. }
  3323. Ok(retrieved) => {
  3324. println!("❌ RSA公钥存储验证失败: 内容不匹配");
  3325. println!(" 原始长度: {}, 重组长度: {}", public_key.len(), retrieved.len());
  3326. println!(" 原始前缀: {}", &public_key[..50.min(public_key.len())]);
  3327. println!(" 重组前缀: {}", &retrieved[..50.min(retrieved.len())]);
  3328. }
  3329. Err(e) => {
  3330. println!("❌ RSA公钥存储验证失败: {}", e);
  3331. }
  3332. }
  3333. }
  3334. Ok(())
  3335. }
  3336. #[tauri::command]
  3337. pub async fn has_stored_rsa_key() -> Result<bool, String> {
  3338. Ok(crypto_has_stored_rsa_key())
  3339. }
  3340. #[tauri::command]
  3341. pub async fn clear_stored_rsa_key() -> Result<(), String> {
  3342. crypto_clear_stored_rsa_key();
  3343. Ok(())
  3344. }
  3345. #[tauri::command]
  3346. pub async fn rsa_encrypt_aes_key_by_stored_key(aes_key: String) -> Result<String, String> {
  3347. use base64::{Engine as _, engine::general_purpose};
  3348. use rsa::{RsaPublicKey, pkcs8::DecodePublicKey, pkcs1v15::Pkcs1v15Encrypt, oaep::Oaep};
  3349. use rand::thread_rng;
  3350. // 从Rust后端分段存储中重组RSA公钥
  3351. let public_key_pem = retrieve_rsa_key_segments()?;
  3352. let aes_key_bytes = general_purpose::STANDARD.decode(&aes_key)
  3353. .map_err(|e| format!("Failed to decode AES key: {}", e))?;
  3354. if aes_key_bytes.len() != 32 {
  3355. return Err("Invalid AES key length".to_string());
  3356. }
  3357. let public_key_parsed = RsaPublicKey::from_public_key_pem(&public_key_pem)
  3358. .map_err(|e| format!("Failed to parse public key: {}", e))?;
  3359. let mut rng = thread_rng();
  3360. // 使用OAEP填充,与后端保持一致
  3361. let padding = Oaep::new::<sha2::Sha256>();
  3362. let encrypted_aes_key = public_key_parsed.encrypt(&mut rng, padding, &aes_key_bytes)
  3363. .map_err(|e| format!("Failed to encrypt AES key: {}", e))?;
  3364. Ok(general_purpose::STANDARD.encode(&encrypted_aes_key))
  3365. }
  3366. #[tauri::command]
  3367. pub async fn rsa_encrypt_aes_key(aes_key: String, public_key: String) -> Result<String, String> {
  3368. use base64::{Engine as _, engine::general_purpose};
  3369. // 解码AES密钥
  3370. let aes_key_bytes = general_purpose::STANDARD.decode(&aes_key)
  3371. .map_err(|e| format!("Failed to decode AES key: {}", e))?;
  3372. if aes_key_bytes.len() != 32 {
  3373. return Err("Invalid AES key length".to_string());
  3374. }
  3375. // 解析RSA公钥
  3376. use rsa::{RsaPublicKey, pkcs8::DecodePublicKey};
  3377. let public_key_parsed = RsaPublicKey::from_public_key_pem(&public_key)
  3378. .map_err(|e| format!("Failed to parse public key: {}", e))?;
  3379. // 使用RSA加密AES密钥
  3380. use rsa::pkcs1v15::Pkcs1v15Encrypt;
  3381. let mut rng = rand::thread_rng();
  3382. // 使用OAEP填充,与后端保持一致
  3383. let padding = Oaep::new::<sha2::Sha256>();
  3384. let encrypted_aes_key = public_key_parsed.encrypt(&mut rng, padding, &aes_key_bytes)
  3385. .map_err(|e| format!("Failed to encrypt AES key: {}", e))?;
  3386. // Base64编码
  3387. Ok(general_purpose::STANDARD.encode(&encrypted_aes_key))
  3388. }
  3389. #[derive(serde::Serialize, serde::Deserialize)]
  3390. pub struct AnnouncementRequest {
  3391. pub page: i32,
  3392. pub page_size: i32,
  3393. pub keyword: Option<String>,
  3394. }
  3395. /// Tauri 2 invoke 传参格式为 { request: AnnouncementRequest },需用包装结构体接收
  3396. #[derive(serde::Serialize, serde::Deserialize)]
  3397. pub struct SecureGetAnnouncementsArgs {
  3398. pub request: AnnouncementRequest,
  3399. }
  3400. #[derive(serde::Serialize, serde::Deserialize)]
  3401. pub struct AnnouncementResponse {
  3402. pub data: Vec<serde_json::Value>,
  3403. pub total: i32,
  3404. pub page: i32,
  3405. pub page_size: i32,
  3406. }
  3407. #[tauri::command]
  3408. pub async fn secure_get_announcements(args: SecureGetAnnouncementsArgs) -> Result<AnnouncementResponse, String> {
  3409. let request = args.request;
  3410. // 公告为公开数据,使用 GET /api/v1/announcements 明文接口,避免 POST 安全端点的 422 等问题
  3411. let base = get_api_base_url()?;
  3412. let url = format!(
  3413. "{}/api/v1/announcements?page={}&page_size={}",
  3414. base.trim_end_matches('/'),
  3415. request.page,
  3416. request.page_size
  3417. );
  3418. let client = get_http_client();
  3419. let response = client
  3420. .get(&url)
  3421. .send()
  3422. .await
  3423. .map_err(|e| format!("HTTP request failed: {}", e))?;
  3424. if !response.status().is_success() {
  3425. return Err(format!("HTTP request failed with status: {}", response.status()));
  3426. }
  3427. let response_json: serde_json::Value = response
  3428. .json()
  3429. .await
  3430. .map_err(|e| format!("Failed to parse response: {}", e))?;
  3431. let code = response_json.get("code").and_then(|c| c.as_i64()).unwrap_or(0);
  3432. if code != 200 {
  3433. let msg = response_json
  3434. .get("msg")
  3435. .and_then(|m| m.as_str())
  3436. .unwrap_or("Unknown error");
  3437. return Err(format!("API error: {}", msg));
  3438. }
  3439. // 后端 GET 返回: { "code": 200, "msg": "...", "data": { "announcements": [...], "total", "page", "page_size" } }
  3440. let data_obj = response_json.get("data").and_then(|d| d.as_object());
  3441. let data = data_obj
  3442. .and_then(|o| o.get("announcements"))
  3443. .and_then(|a| a.as_array())
  3444. .cloned()
  3445. .unwrap_or_default();
  3446. let total = data_obj
  3447. .and_then(|o| o.get("total"))
  3448. .and_then(|t| t.as_i64())
  3449. .unwrap_or(0) as i32;
  3450. let page = data_obj
  3451. .and_then(|o| o.get("page"))
  3452. .and_then(|p| p.as_i64())
  3453. .unwrap_or(1) as i32;
  3454. let page_size = data_obj
  3455. .and_then(|o| o.get("page_size"))
  3456. .and_then(|ps| ps.as_i64())
  3457. .unwrap_or(10) as i32;
  3458. Ok(AnnouncementResponse {
  3459. data,
  3460. total,
  3461. page,
  3462. page_size,
  3463. })
  3464. }
  3465. #[tauri::command]
  3466. pub fn get_rsa_key_version() -> Result<i32, String> {
  3467. get_stored_key_version().ok_or_else(|| "RSA公钥版本不存在".to_string())
  3468. }
  3469. // ==================== 新的安全通信命令 ====================
  3470. /// 初始化安全客户端(仅 RSA 公钥;HMAC 使用按年份写死的静态密钥,无需同步)
  3471. #[tauri::command]
  3472. pub async fn initialize_security_client(
  3473. api_url: String,
  3474. _username: Option<String>,
  3475. machine_code: String,
  3476. ) -> Result<bool, String> {
  3477. use crate::commands::crypto::initialize_key_manager;
  3478. info!("[SecurityClient] 初始化安全客户端");
  3479. let key_result = initialize_key_manager(&api_url, false, Some(&machine_code)).await;
  3480. if let Err(e) = key_result {
  3481. error!("[SecurityClient] RSA密钥初始化失败: {}", e);
  3482. return Err(format!("RSA密钥初始化失败: {}", e));
  3483. }
  3484. info!("[SecurityClient] 安全客户端初始化成功");
  3485. Ok(true)
  3486. }
  3487. /// 检查安全客户端是否已初始化
  3488. #[tauri::command]
  3489. pub fn check_security_initialized() -> Result<bool, String> {
  3490. Ok(crate::commands::crypto::has_stored_rsa_key())
  3491. }
  3492. /// 获取安全客户端状态
  3493. #[tauri::command]
  3494. pub fn get_security_status() -> Result<serde_json::Value, String> {
  3495. Ok(serde_json::json!({
  3496. "key_manager": crate::commands::crypto::get_key_manager_status(),
  3497. "hmac_key": crate::commands::crypto::get_hmac_key_status(),
  3498. "timestamp": std::time::SystemTime::now()
  3499. .duration_since(std::time::UNIX_EPOCH)
  3500. .unwrap_or_default()
  3501. .as_secs(),
  3502. }))
  3503. }
  3504. /// 刷新安全密钥(如果需要)
  3505. #[tauri::command]
  3506. pub async fn refresh_security_keys(api_url: String) -> Result<bool, String> {
  3507. use crate::commands::crypto::refresh_key_if_needed;
  3508. info!("[SecurityClient] 刷新安全密钥");
  3509. refresh_key_if_needed(&api_url).await
  3510. }
  3511. /// 重置安全客户端
  3512. #[tauri::command]
  3513. pub fn reset_security_client() -> Result<(), String> {
  3514. use crate::commands::crypto::clear_hmac_key;
  3515. info!("[SecurityClient] 重置安全客户端");
  3516. clear_hmac_key();
  3517. Ok(())
  3518. }
  3519. /// 同步HMAC密钥(调用后端API)
  3520. #[tauri::command]
  3521. pub async fn sync_hmac_key(
  3522. username: String,
  3523. machine_code: String,
  3524. ) -> Result<serde_json::Value, String> {
  3525. use serde_json::Value;
  3526. info!("[SecurityClient] 同步HMAC密钥: {}", username);
  3527. // 获取API基础URL
  3528. let config = AppConfig::load()
  3529. .map_err(|e| format!("加载配置失败: {}", e))?;
  3530. let api_url = config.api.base_url;
  3531. let url = format!("{}/api/v1/keys/hmac", api_url);
  3532. // 发送请求
  3533. let client = get_http_client();
  3534. let response = client
  3535. .post(&url)
  3536. .json(&serde_json::json!({
  3537. "username": username,
  3538. "machine_code": machine_code
  3539. }))
  3540. .send()
  3541. .await
  3542. .map_err(|e| format!("HTTP请求失败: {}", e))?;
  3543. // 解析响应
  3544. let response_json: Value = response
  3545. .json()
  3546. .await
  3547. .map_err(|e| format!("响应解析失败: {}", e))?;
  3548. info!("[SecurityClient] HMAC密钥同步响应: {:?}", response_json);
  3549. Ok(response_json)
  3550. }
  3551. /// 获取密钥管理器状态(供前端/心跳使用)
  3552. #[tauri::command]
  3553. pub fn get_key_manager_status() -> Result<serde_json::Value, String> {
  3554. Ok(crate::commands::crypto::get_key_manager_status())
  3555. }
  3556. /// 获取 HMAC 密钥状态(供前端/心跳使用)
  3557. #[tauri::command]
  3558. pub fn get_hmac_key_status() -> Result<serde_json::Value, String> {
  3559. Ok(crate::commands::crypto::get_hmac_key_status())
  3560. }
  3561. // ============ 心跳管理命令 ============
  3562. /// 获取心跳状态
  3563. #[tauri::command]
  3564. pub fn get_heartbeat_state_command() -> Result<serde_json::Value, String> {
  3565. Ok(get_heartbeat_service_status())
  3566. }
  3567. /// 停止心跳服务
  3568. #[tauri::command]
  3569. pub async fn stop_heartbeat_command() -> Result<(), String> {
  3570. stop_heartbeat_service().await;
  3571. Ok(())
  3572. }
  3573. /// 获取心跳配置
  3574. #[tauri::command]
  3575. pub fn get_heartbeat_config_command() -> Result<serde_json::Value, String> {
  3576. let config = get_heartbeat_config();
  3577. Ok(serde_json::json!({
  3578. "interval_seconds": config.interval_seconds,
  3579. "max_failures": config.max_failures,
  3580. "renewal_threshold": config.renewal_threshold,
  3581. "extension_duration": config.extension_duration,
  3582. }))
  3583. }
  3584. /// 读取用户选择的 txt 文件内容(用于 BUFF 选品排除/指定商品 id)
  3585. #[tauri::command]
  3586. pub fn read_text_file(path: String) -> Result<String, String> {
  3587. fs::read_to_string(&path).map_err(|e| format!("读取文件失败: {}", e))
  3588. }
  3589. // ============ 扫货日志(仅本地 JSONL 文件 + 后端最近一次请求单元数据,不落库日志正文) ============
  3590. #[derive(Debug, Clone, Serialize, Deserialize)]
  3591. struct LocalSweepLogEntry {
  3592. created_at: Option<String>,
  3593. goods_id: String,
  3594. goods_name: String,
  3595. result: String,
  3596. message: String,
  3597. target_price_usd: Option<f64>,
  3598. purchase_price_usd: Option<f64>,
  3599. #[serde(default)]
  3600. purchase_wear: Option<f64>,
  3601. #[serde(default)]
  3602. listing_id: Option<String>,
  3603. #[serde(default)]
  3604. line_color: Option<String>,
  3605. #[serde(default)]
  3606. diag_level: Option<String>,
  3607. // API 详细日志字段
  3608. #[serde(default)]
  3609. api_name: Option<String>,
  3610. #[serde(default)]
  3611. steam_account: Option<String>,
  3612. #[serde(default)]
  3613. method: Option<String>,
  3614. #[serde(default)]
  3615. url: Option<String>,
  3616. #[serde(default)]
  3617. request_headers: Option<serde_json::Value>,
  3618. #[serde(default)]
  3619. request_body: Option<serde_json::Value>,
  3620. #[serde(default)]
  3621. response_status: Option<u16>,
  3622. #[serde(default)]
  3623. response_body: Option<String>,
  3624. #[serde(default)]
  3625. elapsed_ms: Option<u64>,
  3626. #[serde(default)]
  3627. error: Option<String>,
  3628. }
  3629. /// 去掉扫货日志 `【线程N】` 前缀后再做关键词着色,避免影响成功/命中判断。
  3630. fn sweep_log_message_for_color_rules(message: &str) -> &str {
  3631. let s = message.trim();
  3632. if !s.starts_with('【') {
  3633. return s;
  3634. }
  3635. if let Some(i) = s.find('】') {
  3636. let rest = &s[i + '】'.len_utf8()..];
  3637. return rest.trim_start();
  3638. }
  3639. s
  3640. }
  3641. fn sweep_log_line_color_hex(result: &str, message: &str, diag_level: Option<&str>) -> String {
  3642. if result == "diag" {
  3643. return match diag_level {
  3644. Some("warn") => "#fa8c16".to_string(),
  3645. Some("error") => "#ff4d4f".to_string(),
  3646. _ => "#595959".to_string(),
  3647. };
  3648. }
  3649. let msg = sweep_log_message_for_color_rules(message);
  3650. if result == "failed" {
  3651. return "#fa8c16".to_string();
  3652. }
  3653. if result == "success" {
  3654. return "#ff4d4f".to_string();
  3655. }
  3656. if !msg.is_empty() && (msg.contains("购买成功") || msg.contains("扫货成功")) {
  3657. return "#ff4d4f".to_string();
  3658. }
  3659. if result == "matched" {
  3660. return "#52c41a".to_string();
  3661. }
  3662. if result == "scan" {
  3663. return "#8c8c8c".to_string();
  3664. }
  3665. if msg.contains("磨损命中目标区间") {
  3666. return "#52c41a".to_string();
  3667. }
  3668. if msg.contains("价格命中候选") && msg.contains("磨损查询") {
  3669. return "#52c41a".to_string();
  3670. }
  3671. "#000000".to_string()
  3672. }
  3673. fn hydrate_sweep_log_line_colors(logs: &mut [LocalSweepLogEntry]) {
  3674. for e in logs.iter_mut() {
  3675. if e.line_color.as_deref().unwrap_or("").is_empty() {
  3676. e.line_color = Some(sweep_log_line_color_hex(
  3677. &e.result,
  3678. &e.message,
  3679. e.diag_level.as_deref(),
  3680. ));
  3681. }
  3682. }
  3683. }
  3684. fn local_sweep_log_file_path(request_id: i64) -> Result<PathBuf, String> {
  3685. let steam_msg_dir = get_steam_msg_dir()?;
  3686. Ok(steam_msg_dir.join(format!("{}.txt", request_id)))
  3687. }
  3688. fn discover_latest_local_sweep_request_id() -> Option<i64> {
  3689. let dir = match get_steam_msg_dir() {
  3690. Ok(d) => d,
  3691. Err(e) => {
  3692. log::error!("[discover_latest_local_sweep_request_id] 获取目录失败: {}", e);
  3693. return None;
  3694. }
  3695. };
  3696. log::info!("[discover_latest_local_sweep_request_id] 扫描目录: {:?}", dir);
  3697. // 检查目录是否存在
  3698. if !dir.exists() {
  3699. log::error!("[discover_latest_local_sweep_request_id] 目录不存在: {:?}", dir);
  3700. return None;
  3701. }
  3702. // 检查是否是目录
  3703. if !dir.is_dir() {
  3704. log::error!("[discover_latest_local_sweep_request_id] 路径不是目录: {:?}", dir);
  3705. return None;
  3706. }
  3707. let entries = match fs::read_dir(&dir) {
  3708. Ok(e) => e,
  3709. Err(e) => {
  3710. log::error!("[discover_latest_local_sweep_request_id] 读取目录失败: {} (目录: {:?})", e, dir);
  3711. return None;
  3712. }
  3713. };
  3714. let mut max_id: Option<i64> = None;
  3715. for entry in entries.flatten() {
  3716. let path = entry.path();
  3717. if !path.is_file() {
  3718. continue;
  3719. }
  3720. let name = match path.file_name().and_then(|n| n.to_str()) {
  3721. Some(n) => n,
  3722. None => continue,
  3723. };
  3724. let stem = match name.strip_suffix(".txt") {
  3725. Some(s) => s,
  3726. None => continue,
  3727. };
  3728. match stem.parse::<i64>() {
  3729. Ok(id) => {
  3730. log::info!("[discover_latest_local_sweep_request_id] 发现日志文件: {} -> id={}", name, id);
  3731. max_id = Some(max_id.map(|m| m.max(id)).unwrap_or(id));
  3732. }
  3733. Err(_) => {
  3734. // 非数字命名的 txt 文件(如 accounts.json),忽略
  3735. }
  3736. }
  3737. }
  3738. log::info!("[discover_latest_local_sweep_request_id] 最大 request_id: {:?}", max_id);
  3739. max_id
  3740. }
  3741. fn read_local_sweep_meta_started_at(request_id: i64) -> Option<String> {
  3742. let path = local_sweep_log_file_path(request_id).ok()?;
  3743. let content = fs::read_to_string(&path).ok()?;
  3744. let first = content.lines().next()?.trim();
  3745. if !first.starts_with('{') {
  3746. return None;
  3747. }
  3748. let v: serde_json::Value = serde_json::from_str(first).ok()?;
  3749. v.get("meta")?
  3750. .get("started_at")?
  3751. .as_str()
  3752. .map(|s| s.to_string())
  3753. }
  3754. fn read_local_sweep_logs_from_file(request_id: i64) -> Result<Vec<LocalSweepLogEntry>, String> {
  3755. fn extract_field(raw: &str, start_marker: &str, end_marker: Option<&str>) -> Option<String> {
  3756. let start = raw.find(start_marker)? + start_marker.len();
  3757. let tail = &raw[start..];
  3758. let end = match end_marker {
  3759. Some(marker) => tail.find(marker)?,
  3760. None => tail.len(),
  3761. };
  3762. Some(tail[..end].trim().to_string())
  3763. }
  3764. let path = local_sweep_log_file_path(request_id)?;
  3765. if !path.exists() {
  3766. return Ok(Vec::new());
  3767. }
  3768. let content = fs::read_to_string(&path).map_err(|e| format!("读取日志文件失败: {}", e))?;
  3769. let mut out: Vec<LocalSweepLogEntry> = Vec::new();
  3770. for line in content.lines() {
  3771. let raw = line.trim();
  3772. if raw.is_empty() {
  3773. continue;
  3774. }
  3775. if raw.starts_with('{') {
  3776. // 跳过 meta 行
  3777. if let Ok(value) = serde_json::from_str::<serde_json::Value>(raw) {
  3778. if value.get("meta").is_some() {
  3779. continue;
  3780. }
  3781. }
  3782. // 解析为 LocalSweepLogEntry,失败时记录日志
  3783. match serde_json::from_str::<LocalSweepLogEntry>(raw) {
  3784. Ok(mut entry) => {
  3785. if entry.line_color.as_deref().unwrap_or("").is_empty() {
  3786. entry.line_color = Some(sweep_log_line_color_hex(
  3787. &entry.result,
  3788. &entry.message,
  3789. entry.diag_level.as_deref(),
  3790. ));
  3791. }
  3792. out.push(entry);
  3793. }
  3794. Err(e) => {
  3795. log::warn!(
  3796. "[read_local_sweep_logs_from_file] JSON 行解析失败: {} (前50字符: {:.50?})",
  3797. e, raw
  3798. );
  3799. }
  3800. }
  3801. continue;
  3802. }
  3803. if raw.starts_with("扫货日志(本地文件为准)") {
  3804. continue;
  3805. }
  3806. if raw.starts_with("created_at\t") {
  3807. continue;
  3808. }
  3809. if raw.starts_with("时间=") {
  3810. let created_at = extract_field(raw, "时间=", Some(" | 饰品ID="));
  3811. let goods_id = extract_field(raw, " | 饰品ID=", Some(" | 饰品名称=")).unwrap_or_default();
  3812. let goods_name = extract_field(raw, " | 饰品名称=", Some(" | 结果=")).unwrap_or_default();
  3813. let result = extract_field(raw, " | 结果=", Some(" | 目标价USD=")).unwrap_or_default();
  3814. let target_raw = extract_field(raw, " | 目标价USD=", Some(" | 成交价USD=")).unwrap_or_default();
  3815. let purchase_raw = extract_field(raw, " | 成交价USD=", Some(" | 说明=")).unwrap_or_default();
  3816. let message = extract_field(raw, " | 说明=", None).unwrap_or_default();
  3817. let mut entry = LocalSweepLogEntry {
  3818. created_at: created_at.and_then(|v| if v.is_empty() { None } else { Some(v) }),
  3819. goods_id,
  3820. goods_name,
  3821. result,
  3822. message,
  3823. target_price_usd: if target_raw.is_empty() {
  3824. None
  3825. } else {
  3826. target_raw.parse::<f64>().ok()
  3827. },
  3828. purchase_price_usd: if purchase_raw.is_empty() {
  3829. None
  3830. } else {
  3831. purchase_raw.parse::<f64>().ok()
  3832. },
  3833. purchase_wear: None,
  3834. listing_id: None,
  3835. line_color: None,
  3836. diag_level: None,
  3837. api_name: None,
  3838. steam_account: None,
  3839. method: None,
  3840. url: None,
  3841. request_headers: None,
  3842. request_body: None,
  3843. response_status: None,
  3844. response_body: None,
  3845. elapsed_ms: None,
  3846. error: None,
  3847. };
  3848. entry.line_color = Some(sweep_log_line_color_hex(
  3849. &entry.result,
  3850. &entry.message,
  3851. None,
  3852. ));
  3853. out.push(entry);
  3854. continue;
  3855. }
  3856. let cols: Vec<&str> = raw.splitn(7, '\t').collect();
  3857. if cols.len() != 7 {
  3858. continue;
  3859. }
  3860. let target_price_usd = if cols[4].trim().is_empty() {
  3861. None
  3862. } else {
  3863. cols[4].trim().parse::<f64>().ok()
  3864. };
  3865. let purchase_price_usd = if cols[5].trim().is_empty() {
  3866. None
  3867. } else {
  3868. cols[5].trim().parse::<f64>().ok()
  3869. };
  3870. let mut entry = LocalSweepLogEntry {
  3871. created_at: if cols[0].trim().is_empty() {
  3872. None
  3873. } else {
  3874. Some(cols[0].trim().to_string())
  3875. },
  3876. goods_id: cols[1].trim().to_string(),
  3877. goods_name: cols[2].trim().to_string(),
  3878. result: cols[3].trim().to_string(),
  3879. message: cols[6].trim().to_string(),
  3880. target_price_usd,
  3881. purchase_price_usd,
  3882. purchase_wear: None,
  3883. listing_id: None,
  3884. line_color: None,
  3885. diag_level: None,
  3886. api_name: None,
  3887. steam_account: None,
  3888. method: None,
  3889. url: None,
  3890. request_headers: None,
  3891. request_body: None,
  3892. response_status: None,
  3893. response_body: None,
  3894. elapsed_ms: None,
  3895. error: None,
  3896. };
  3897. entry.line_color = Some(sweep_log_line_color_hex(
  3898. &entry.result,
  3899. &entry.message,
  3900. None,
  3901. ));
  3902. out.push(entry);
  3903. }
  3904. hydrate_sweep_log_line_colors(&mut out);
  3905. log::info!("[read_local_sweep_logs_from_file] request_id={} 总行数={} 解析成功={} 解析失败={}",
  3906. request_id, content.lines().count(), out.len(),
  3907. content.lines().filter(|l| !l.trim().is_empty()).count() - out.len());
  3908. Ok(out)
  3909. }
  3910. /// 单笔扫货成功明细立即写入 `buff_sweep_request_item`(`append_only`,不删历史行)。
  3911. /// 本地扫货在购买成功分支里应第一时间 `await` 本函数。
  3912. pub async fn buff_sweep_append_success_item_inner(
  3913. session_id: &str,
  3914. machine_code: &str,
  3915. sweep_request_id: i64,
  3916. selection_request_id: i64,
  3917. item: serde_json::Value,
  3918. ) -> Result<(), String> {
  3919. if session_id.trim().is_empty() || machine_code.trim().is_empty() {
  3920. return Err("会话或机器码为空".to_string());
  3921. }
  3922. if sweep_request_id <= 0 || selection_request_id <= 0 {
  3923. return Err("sweep_request_id 或 selection_request_id 无效".to_string());
  3924. }
  3925. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  3926. let api_url = config.api.base_url.trim_end_matches('/');
  3927. let request_data = serde_json::json!({
  3928. "session_id": session_id,
  3929. "machine_code": machine_code,
  3930. "sweep_request_id": sweep_request_id,
  3931. "selection_request_id": selection_request_id,
  3932. "append_only": true,
  3933. "items": [item],
  3934. });
  3935. let decrypted =
  3936. secure_post_decrypt(api_url, "/api/v1/buff/secure/sweep-item-client-sync", &request_data).await?;
  3937. let code = decrypted.get("code").and_then(|v| v.as_i64()).unwrap_or(500);
  3938. if code != 200 {
  3939. let msg = decrypted
  3940. .get("msg")
  3941. .and_then(|v| v.as_str())
  3942. .unwrap_or("扫货成功明细同步失败");
  3943. return Err(msg.to_string());
  3944. }
  3945. Ok(())
  3946. }
  3947. /// 供前端或其它调用方传入单条成功明细 JSON(字段与后端 `sweep-item-client-sync` 中单条一致)。
  3948. #[tauri::command]
  3949. pub async fn secure_buff_sweep_append_success_item(
  3950. session_id: String,
  3951. machine_code: String,
  3952. sweep_request_id: i64,
  3953. selection_request_id: i64,
  3954. item_json: String,
  3955. ) -> Result<String, String> {
  3956. let item: serde_json::Value =
  3957. serde_json::from_str(&item_json).map_err(|e| format!("item_json 解析失败: {}", e))?;
  3958. buff_sweep_append_success_item_inner(
  3959. &session_id,
  3960. &machine_code,
  3961. sweep_request_id,
  3962. selection_request_id,
  3963. item,
  3964. )
  3965. .await?;
  3966. serde_json::to_string(&serde_json::json!({
  3967. "code": 200,
  3968. "msg": "ok",
  3969. "data": null
  3970. }))
  3971. .map_err(|e| format!("序列化失败: {}", e))
  3972. }
  3973. /// 客户端版本检查(安全端点,登录前可调用)
  3974. #[tauri::command]
  3975. pub async fn secure_client_version_check() -> Result<String, String> {
  3976. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  3977. let api_url = config.api.base_url.trim_end_matches('/');
  3978. let platform = if cfg!(target_os = "windows") {
  3979. "windows"
  3980. } else if cfg!(target_os = "macos") {
  3981. "macos"
  3982. } else {
  3983. "linux"
  3984. };
  3985. let request_data = serde_json::json!({
  3986. "client_version": env!("CARGO_PKG_VERSION"),
  3987. "platform": platform,
  3988. "channel": "stable"
  3989. });
  3990. let decrypted = secure_post_decrypt(api_url, "/api/v1/client/version/check", &request_data).await?;
  3991. serde_json::to_string(&decrypted).map_err(|e| format!("序列化失败: {}", e))
  3992. }
  3993. #[tauri::command]
  3994. pub async fn secure_buff_selection_item_delete(
  3995. session_id: String,
  3996. machine_code: String,
  3997. item_id: i64,
  3998. ) -> Result<String, String> {
  3999. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  4000. let api_url = config.api.base_url.trim_end_matches('/');
  4001. let request_data = serde_json::json!({
  4002. "session_id": session_id,
  4003. "machine_code": machine_code,
  4004. "item_id": item_id,
  4005. });
  4006. let decrypted =
  4007. secure_post_decrypt(api_url, "/api/v1/buff/secure/selection-item-delete", &request_data).await?;
  4008. serde_json::to_string(&decrypted).map_err(|e| format!("序列化失败: {}", e))
  4009. }
  4010. /// 批量导入选品数据(JSON文件方式)。
  4011. ///
  4012. /// 1. 读取本地多个JSON文件,合并内容
  4013. /// 2. 调用后端接口,先删除当前用户所有选品数据,再批量插入新数据
  4014. /// 3. JSON中的价格为CNY,转换为USD:USD = CNY / steam_card_ratio + 0.01(保留2位小数)
  4015. #[tauri::command]
  4016. pub async fn batch_import_buff_selection_items(
  4017. session_id: String,
  4018. machine_code: String,
  4019. file_paths: Vec<String>,
  4020. steam_card_ratio: f64,
  4021. ) -> Result<String, String> {
  4022. use std::collections::HashMap;
  4023. if session_id.trim().is_empty() || machine_code.trim().is_empty() {
  4024. return Err("会话或机器码为空".to_string());
  4025. }
  4026. if file_paths.is_empty() {
  4027. return Err("至少需要选择一个JSON文件".to_string());
  4028. }
  4029. if steam_card_ratio <= 0.0 {
  4030. return Err("Steam卡价比例必须大于0".to_string());
  4031. }
  4032. // 合并多个JSON文件内容
  4033. let mut merged_payload: HashMap<String, serde_json::Value> = HashMap::new();
  4034. for path in &file_paths {
  4035. let content = std::fs::read_to_string(path)
  4036. .map_err(|e| format!("读取文件失败 {}: {}", path, e))?;
  4037. let payload: serde_json::Value = serde_json::from_str(&content)
  4038. .map_err(|e| format!("JSON解析失败 {}: {}", path, e))?;
  4039. if let serde_json::Value::Object(obj) = payload {
  4040. for (k, v) in obj {
  4041. merged_payload.insert(k, v);
  4042. }
  4043. } else {
  4044. return Err(format!("JSON文件格式错误(非对象): {}", path));
  4045. }
  4046. }
  4047. if merged_payload.is_empty() {
  4048. return Err("JSON文件中没有有效数据".to_string());
  4049. }
  4050. let goods_ids: Vec<String> = merged_payload.keys().cloned().collect();
  4051. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  4052. let api_url = config.api.base_url.trim_end_matches('/');
  4053. // 先调用后端接口获取这些 goods_id 对应的元数据(goods_name, icon_url, steam_link)
  4054. let request_data = serde_json::json!({
  4055. "session_id": session_id,
  4056. "machine_code": machine_code,
  4057. "goods_ids": goods_ids,
  4058. });
  4059. let decrypted = secure_post_decrypt(api_url, "/api/v1/buff/goods-attr-list", &request_data).await?;
  4060. // 解析 goods 元数据
  4061. let goods_meta_list = decrypted
  4062. .get("data")
  4063. .and_then(|d| d.as_array())
  4064. .map(|arr| {
  4065. arr.iter()
  4066. .filter_map(|item| {
  4067. let goods_id = item.get("goods_id")?.as_str()?.to_string();
  4068. let name = item.get("name").and_then(|n| n.as_str()).unwrap_or("").to_string();
  4069. let icon_url = item.get("icon_url").and_then(|u| u.as_str()).unwrap_or("").to_string();
  4070. let steam_link = item
  4071. .get("steam_link")
  4072. .and_then(|l| l.as_str())
  4073. .unwrap_or("")
  4074. .to_string();
  4075. Some((goods_id, name, icon_url, steam_link))
  4076. })
  4077. .collect::<Vec<_>>()
  4078. })
  4079. .unwrap_or_default();
  4080. let goods_meta_map: HashMap<String, (String, String, String)> = goods_meta_list
  4081. .into_iter()
  4082. .map(|(id, name, icon, link)| (id, (name, icon, link)))
  4083. .collect();
  4084. // 组装批量导入的数据
  4085. let mut items_to_insert: Vec<serde_json::Value> = Vec::new();
  4086. for (goods_id, value) in &merged_payload {
  4087. let price_str = value
  4088. .get("price")
  4089. .and_then(|p| p.as_str().map(|s| s.to_string()))
  4090. .or_else(|| value.get("price").and_then(|p| p.as_f64().map(|f| format!("{}", f))))
  4091. .ok_or_else(|| format!("goods_id {} 缺少有效的 price 字段", goods_id))?;
  4092. let price_cny = price_str.parse::<f64>()
  4093. .map_err(|_| format!("goods_id {} price 字段不是有效数字: {}", goods_id, price_str))?;
  4094. let min_wear = value
  4095. .get("min_paintwear")
  4096. .and_then(|p| p.as_str().map(|s| s.to_string()))
  4097. .or_else(|| value.get("min_paintwear").and_then(|p| p.as_f64().map(|f| format!("{}", f))))
  4098. .unwrap_or_else(|| "0".to_string());
  4099. let max_wear = value
  4100. .get("max_paintwear")
  4101. .and_then(|p| p.as_str().map(|s| s.to_string()))
  4102. .or_else(|| value.get("max_paintwear").and_then(|p| p.as_f64().map(|f| format!("{}", f))))
  4103. .unwrap_or_else(|| "0".to_string());
  4104. let wear_range = format!("{}-{}", min_wear, max_wear);
  4105. // CNY 保留2位小数
  4106. let price_cny_rounded = (price_cny * 100.0).round() / 100.0;
  4107. // USD = CNY / ratio + 0.01,保留2位小数
  4108. let price_usd = ((price_cny_rounded / steam_card_ratio) * 100.0).round() / 100.0 + 0.01;
  4109. // 优先从 JSON 文件的 name 字段获取饰品名称,如果没有则用后端返回的数据
  4110. let goods_name_from_json = value
  4111. .get("name")
  4112. .and_then(|n| n.as_str())
  4113. .map(|s| s.to_string())
  4114. .unwrap_or_default();
  4115. let (goods_name_from_api, icon_url, steam_link) = goods_meta_map
  4116. .get(goods_id)
  4117. .cloned()
  4118. .unwrap_or_else(|| (String::new(), String::new(), String::new()));
  4119. // 优先使用 JSON 中的名称,其次使用后端接口返回的名称
  4120. let goods_name = if !goods_name_from_json.is_empty() {
  4121. goods_name_from_json
  4122. } else {
  4123. goods_name_from_api
  4124. };
  4125. items_to_insert.push(serde_json::json!({
  4126. "goods_id": goods_id,
  4127. "goods_name": goods_name,
  4128. "icon_url": icon_url,
  4129. "steam_link": steam_link,
  4130. "price_cny": price_cny_rounded,
  4131. "price_usd": price_usd,
  4132. "wear_range": wear_range,
  4133. }));
  4134. }
  4135. // 调用后端批量导入接口
  4136. let import_request_data = serde_json::json!({
  4137. "session_id": session_id,
  4138. "machine_code": machine_code,
  4139. "items": items_to_insert,
  4140. });
  4141. let import_decrypted =
  4142. secure_post_decrypt(api_url, "/api/v1/buff/secure/selection-batch-import", &import_request_data).await?;
  4143. serde_json::to_string(&import_decrypted).map_err(|e| format!("序列化失败: {}", e))
  4144. }
  4145. /// 扫货:获取后台已绑定 Steam 用户名(与本地 accounts.json 取交集后用于扫货)。
  4146. #[tauri::command]
  4147. pub async fn secure_buff_sweep_bound_steam_usernames(
  4148. session_id: String,
  4149. machine_code: String,
  4150. ) -> Result<String, String> {
  4151. if session_id.trim().is_empty() || machine_code.trim().is_empty() {
  4152. return Err("会话或机器码为空".to_string());
  4153. }
  4154. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  4155. let api_url = config.api.base_url.trim_end_matches('/');
  4156. let request_data = serde_json::json!({
  4157. "session_id": session_id,
  4158. "machine_code": machine_code,
  4159. });
  4160. let decrypted = secure_post_decrypt(
  4161. api_url,
  4162. "/api/v1/buff/secure/sweep-bound-steam-usernames",
  4163. &request_data,
  4164. )
  4165. .await?;
  4166. serde_json::to_string(&decrypted).map_err(|e| format!("序列化失败: {}", e))
  4167. }
  4168. #[tauri::command]
  4169. pub async fn secure_buff_sweep_start(session_id: String, machine_code: String) -> Result<String, String> {
  4170. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  4171. let api_url = config.api.base_url.trim_end_matches('/');
  4172. let request_data = serde_json::json!({
  4173. "session_id": session_id,
  4174. "machine_code": machine_code,
  4175. "client_local": true
  4176. });
  4177. let decrypted = secure_post_decrypt(api_url, "/api/v1/buff/secure/sweep-start", &request_data).await?;
  4178. serde_json::to_string(&decrypted).map_err(|e| format!("序列化失败: {}", e))
  4179. }
  4180. /// 启动客户端本地扫货循环(需先调用 `secure_buff_sweep_start` 成功并拿到 request_id / selection_request_id)。
  4181. #[tauri::command]
  4182. pub async fn spawn_buff_sweep_local_runner(
  4183. session_id: String,
  4184. machine_code: String,
  4185. sweep_request_id: i64,
  4186. selection_request_id: i64,
  4187. sweep_worker_count: Option<u32>,
  4188. selected_accounts: Option<Vec<String>>,
  4189. account_card_rate_map: Option<std::collections::HashMap<String, f64>>,
  4190. ) -> Result<(), String> {
  4191. // 扫货启动时,确保用户文件和目录存在
  4192. ensure_sweep_data_files_exist()?;
  4193. buff_sweep_runner::spawn_runner(
  4194. session_id,
  4195. machine_code,
  4196. sweep_request_id,
  4197. selection_request_id,
  4198. sweep_worker_count.unwrap_or(3).max(1),
  4199. selected_accounts.unwrap_or_default(),
  4200. account_card_rate_map.unwrap_or_default(),
  4201. )
  4202. .await
  4203. }
  4204. /// 确保扫货数据文件和目录存在(扫货启动前调用)
  4205. fn ensure_sweep_data_files_exist() -> Result<(), String> {
  4206. let steam_msg_dir = get_steam_msg_dir()?;
  4207. fs::create_dir_all(&steam_msg_dir).map_err(|e| format!("创建 steam-msg 目录失败: {}", e))?;
  4208. // 确保用户文件存在(空文件或空结构)
  4209. let username = get_current_username_internal().unwrap_or_else(|_| "guest".to_string());
  4210. let user_file_path = sweep_success_items_path()?;
  4211. if !user_file_path.exists() {
  4212. let initial_data = LocalSweepSuccessData::default();
  4213. let content = serde_json::to_string_pretty(&initial_data)
  4214. .map_err(|e| format!("序列化失败: {}", e))?;
  4215. fs::write(&user_file_path, content).map_err(|e| format!("创建用户文件失败: {}", e))?;
  4216. log::info!("[ensure] 已创建用户扫货数据文件: {:?}", user_file_path);
  4217. }
  4218. log::info!("[ensure] steam-msg 目录: {:?}", steam_msg_dir);
  4219. log::info!("[ensure] 用户扫货数据文件: {:?}", user_file_path);
  4220. Ok(())
  4221. }
  4222. #[tauri::command]
  4223. pub async fn secure_buff_sweep_stop(session_id: String, machine_code: String) -> Result<String, String> {
  4224. buff_sweep_runner::request_cancel();
  4225. // 等待扫货任务检测到取消并退出(最多等待 3 秒)
  4226. match tokio::time::timeout(
  4227. tokio::time::Duration::from_secs(3),
  4228. buff_sweep_runner::wait_for_stop()
  4229. ).await {
  4230. Ok(_) => info!("[sweep-stop] 扫货任务已停止"),
  4231. Err(_) => warn!("[sweep-stop] 等待扫货任务停止超时,强制继续"),
  4232. }
  4233. let decrypted = buff_secure_sweep_stop_decrypt(&session_id, &machine_code).await?;
  4234. serde_json::to_string(&decrypted).map_err(|e| format!("序列化失败: {}", e))
  4235. }
  4236. #[tauri::command]
  4237. pub async fn secure_buff_sweep_result(
  4238. session_id: String,
  4239. machine_code: String,
  4240. page: Option<i64>,
  4241. page_size: Option<i64>,
  4242. ) -> Result<String, String> {
  4243. if session_id.trim().is_empty() || machine_code.trim().is_empty() {
  4244. let resp = serde_json::json!({
  4245. "code": 400,
  4246. "msg": "请求参数错误,请检查后重试",
  4247. "data": null
  4248. });
  4249. return serde_json::to_string(&resp).map_err(|e| format!("序列化失败: {}", e));
  4250. }
  4251. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  4252. let api_url = config.api.base_url.trim_end_matches('/');
  4253. let mut request_data = serde_json::json!({
  4254. "session_id": session_id,
  4255. "machine_code": machine_code,
  4256. });
  4257. if let Some(p) = page {
  4258. request_data["page"] = serde_json::json!(p);
  4259. }
  4260. if let Some(ps) = page_size {
  4261. request_data["page_size"] = serde_json::json!(ps);
  4262. }
  4263. let decrypted = secure_post_decrypt(api_url, "/api/v1/buff/secure/sweep-result", &request_data).await?;
  4264. serde_json::to_string(&decrypted).map_err(|e| format!("序列化失败: {}", e))
  4265. }
  4266. /// 扫货日志:正文仅来自本地 `steam-msg/{request_id}.txt`(JSONL,可含 `line_color`)。
  4267. /// 扫货进程不再向 `/buff/secure/sweep-log-client-sync` 上传日志正文;元数据仍来自 `/buff/secure/sweep-result`(ID/状态/时间)。
  4268. #[tauri::command]
  4269. pub async fn secure_buff_sweep_log(
  4270. session_id: String,
  4271. machine_code: String,
  4272. last_log_count: Option<i64>,
  4273. limit: Option<i64>,
  4274. ) -> Result<String, String> {
  4275. if session_id.trim().is_empty() || machine_code.trim().is_empty() {
  4276. let resp = serde_json::json!({
  4277. "code": 400,
  4278. "msg": "请求参数错误,请检查后重试",
  4279. "data": null
  4280. });
  4281. return serde_json::to_string(&resp).map_err(|e| format!("序列化失败: {}", e));
  4282. }
  4283. let config = AppConfig::load().map_err(|e| format!("加载配置失败: {}", e))?;
  4284. let api_url = config.api.base_url.trim_end_matches('/');
  4285. let sweep_result_data = {
  4286. let request_data = serde_json::json!({
  4287. "session_id": session_id.clone(),
  4288. "machine_code": machine_code.clone(),
  4289. "page": 1,
  4290. "page_size": 1,
  4291. });
  4292. match secure_post_decrypt(api_url, "/api/v1/buff/secure/sweep-result", &request_data).await {
  4293. Ok(v) => v,
  4294. Err(e) => {
  4295. warn!("[sweep-log] 获取 sweep-result 失败,将尝试仅本地日志: {}", e);
  4296. serde_json::json!({ "code": 500 })
  4297. }
  4298. }
  4299. };
  4300. let mut request_id_opt: Option<i64> = None;
  4301. let mut status_opt: Option<String> = None;
  4302. let mut started_at_opt: Option<String> = None;
  4303. let mut ended_at_opt: Option<String> = None;
  4304. if sweep_result_data.get("code").and_then(|v| v.as_i64()) == Some(200) {
  4305. if let Some(data) = sweep_result_data.get("data") {
  4306. request_id_opt = data.get("request_id").and_then(|v| v.as_i64());
  4307. status_opt = data.get("status").and_then(|v| v.as_str()).map(|s| s.to_string());
  4308. started_at_opt = data.get("started_at").and_then(|v| v.as_str()).map(|s| s.to_string());
  4309. ended_at_opt = data.get("ended_at").and_then(|v| v.as_str()).map(|s| s.to_string());
  4310. }
  4311. }
  4312. if request_id_opt.is_none() {
  4313. request_id_opt = discover_latest_local_sweep_request_id();
  4314. }
  4315. let Some(request_id) = request_id_opt else {
  4316. let resp = serde_json::json!({
  4317. "code": 200,
  4318. "msg": "ok",
  4319. "data": {
  4320. "request_id": null,
  4321. "status": null,
  4322. "started_at": null,
  4323. "ended_at": null,
  4324. "log_count": 0,
  4325. "logs": []
  4326. }
  4327. });
  4328. return serde_json::to_string(&resp).map_err(|e| format!("序列化失败: {}", e));
  4329. };
  4330. if started_at_opt.is_none() {
  4331. started_at_opt = read_local_sweep_meta_started_at(request_id);
  4332. }
  4333. let all_logs = read_local_sweep_logs_from_file(request_id).unwrap_or_default();
  4334. let total_log_count = all_logs.len();
  4335. let cursor = last_log_count.unwrap_or(-1);
  4336. let mut logs = if cursor >= 0 {
  4337. let start = if (cursor as usize) > total_log_count {
  4338. 0
  4339. } else {
  4340. cursor as usize
  4341. };
  4342. all_logs[start..].to_vec()
  4343. } else {
  4344. all_logs
  4345. };
  4346. logs.reverse();
  4347. let effective_limit = limit.unwrap_or(200).clamp(1, 500) as usize;
  4348. if logs.len() > effective_limit {
  4349. logs.truncate(effective_limit);
  4350. }
  4351. let resp = serde_json::json!({
  4352. "code": 200,
  4353. "msg": "ok",
  4354. "data": {
  4355. "request_id": request_id,
  4356. "status": status_opt,
  4357. "started_at": started_at_opt,
  4358. "ended_at": ended_at_opt,
  4359. "log_count": total_log_count as i64,
  4360. "logs": logs
  4361. }
  4362. });
  4363. serde_json::to_string(&resp).map_err(|e| format!("序列化失败: {}", e))
  4364. }
  4365. /// 纯本地读取扫货日志(不再调用后端 API)。
  4366. /// 从 `steam-msg/{sweep_request_id}.txt` 读取日志,返回与 `secure_buff_sweep_log` 相同格式的 JSON。
  4367. #[tauri::command]
  4368. pub async fn read_local_sweep_logs(
  4369. last_log_count: Option<i64>,
  4370. limit: Option<i64>,
  4371. ) -> Result<String, String> {
  4372. // 调试信息(会返回给前端)
  4373. let mut debug_info = serde_json::json!({});
  4374. // 获取 steam-msg 目录路径
  4375. let steam_msg_dir_path = match get_steam_msg_dir() {
  4376. Ok(p) => p,
  4377. Err(e) => {
  4378. log::error!("[read_local_sweep_logs] 获取目录失败: {}", e);
  4379. let resp = serde_json::json!({
  4380. "code": 500,
  4381. "msg": format!("获取目录失败: {}", e),
  4382. "data": {
  4383. "request_id": null,
  4384. "status": null,
  4385. "started_at": null,
  4386. "ended_at": null,
  4387. "log_count": 0,
  4388. "logs": [],
  4389. "debug": {
  4390. "error": format!("get_steam_msg_dir failed: {}", e)
  4391. }
  4392. }
  4393. });
  4394. return serde_json::to_string(&resp).map_err(|e| format!("序列化失败: {}", e));
  4395. }
  4396. };
  4397. debug_info["steam_msg_dir"] = serde_json::json!(steam_msg_dir_path.to_string_lossy().to_string());
  4398. debug_info["steam_msg_dir_exists"] = serde_json::json!(steam_msg_dir_path.exists());
  4399. if steam_msg_dir_path.exists() {
  4400. debug_info["steam_msg_dir_is_dir"] = serde_json::json!(steam_msg_dir_path.is_dir());
  4401. }
  4402. log::info!("[read_local_sweep_logs] steam-msg 目录: {:?}", steam_msg_dir_path);
  4403. let request_id = discover_latest_local_sweep_request_id();
  4404. debug_info["discovered_request_id"] = serde_json::json!(request_id);
  4405. log::info!("[read_local_sweep_logs] 发现的最新 request_id: {:?}", request_id);
  4406. let Some(request_id) = request_id else {
  4407. log::warn!("[read_local_sweep_logs] 未找到扫货日志文件");
  4408. let resp = serde_json::json!({
  4409. "code": 200,
  4410. "msg": "ok",
  4411. "data": {
  4412. "request_id": null,
  4413. "status": null,
  4414. "started_at": null,
  4415. "ended_at": null,
  4416. "log_count": 0,
  4417. "logs": [],
  4418. "debug": debug_info
  4419. }
  4420. });
  4421. return serde_json::to_string(&resp).map_err(|e| format!("序列化失败: {}", e));
  4422. };
  4423. let log_file_path = local_sweep_log_file_path(request_id)?;
  4424. debug_info["log_file_path"] = serde_json::json!(log_file_path.to_string_lossy().to_string());
  4425. debug_info["log_file_exists"] = serde_json::json!(log_file_path.exists());
  4426. log::info!("[read_local_sweep_logs] 日志文件路径: {:?}", log_file_path);
  4427. log::info!("[read_local_sweep_logs] 日志文件是否存在: {}", log_file_path.exists());
  4428. if log_file_path.exists() {
  4429. if let Ok(metadata) = fs::metadata(&log_file_path) {
  4430. debug_info["log_file_size"] = serde_json::json!(metadata.len());
  4431. log::info!("[read_local_sweep_logs] 日志文件大小: {} bytes", metadata.len());
  4432. }
  4433. }
  4434. let started_at = read_local_sweep_meta_started_at(request_id);
  4435. debug_info["started_at"] = serde_json::json!(started_at);
  4436. let all_logs = read_local_sweep_logs_from_file(request_id).unwrap_or_default();
  4437. debug_info["total_logs_parsed"] = serde_json::json!(all_logs.len());
  4438. log::info!("[read_local_sweep_logs] 读取到的日志条数: {}", all_logs.len());
  4439. let total_log_count = all_logs.len();
  4440. let cursor = last_log_count.unwrap_or(-1);
  4441. let mut logs = if cursor >= 0 {
  4442. let start = if (cursor as usize) > total_log_count {
  4443. 0
  4444. } else {
  4445. cursor as usize
  4446. };
  4447. all_logs[start..].to_vec()
  4448. } else {
  4449. all_logs
  4450. };
  4451. logs.reverse();
  4452. let effective_limit = limit.unwrap_or(200).clamp(1, 500) as usize;
  4453. if logs.len() > effective_limit {
  4454. logs.truncate(effective_limit);
  4455. }
  4456. // status: 根据本地日志判断扫货是否进行中
  4457. // 如果日志文件存在但没有 "ended" 相关记录,视为 running
  4458. let has_ended = logs.iter().any(|l| {
  4459. l.result == "failed" && l.message.contains("扫货已结束")
  4460. || l.message.contains("扫货已完成")
  4461. || l.message.contains("扫货已停止")
  4462. });
  4463. let status = if has_ended { "completed" } else { "running" };
  4464. let ended_at_val = if has_ended { started_at.clone() } else { None::<String> };
  4465. log::info!("[read_local_sweep_logs] 返回日志条数: {}", logs.len());
  4466. let resp = serde_json::json!({
  4467. "code": 200,
  4468. "msg": "ok",
  4469. "data": {
  4470. "request_id": request_id,
  4471. "status": status,
  4472. "started_at": started_at,
  4473. "ended_at": ended_at_val,
  4474. "log_count": total_log_count as i64,
  4475. "logs": logs,
  4476. "debug": debug_info
  4477. }
  4478. });
  4479. serde_json::to_string(&resp).map_err(|e| format!("序列化失败: {}", e))
  4480. }
  4481. /// 获取扫货 Worker 日志文件列表
  4482. #[tauri::command]
  4483. pub async fn get_sweep_worker_log_files(
  4484. request_id: Option<i64>,
  4485. ) -> Result<String, String> {
  4486. let request_id = match request_id {
  4487. Some(id) if id > 0 => id,
  4488. _ => discover_latest_local_sweep_request_id()
  4489. .ok_or_else(|| "未找到扫货记录".to_string())?,
  4490. };
  4491. let steam_msg_dir = get_steam_msg_dir()
  4492. .map_err(|e| format!("获取目录失败: {}", e))?;
  4493. // 查找所有匹配的日志文件 {request_id}-{n}.txt
  4494. let pattern = format!("{}-", request_id);
  4495. let mut worker_files: Vec<serde_json::Value> = Vec::new();
  4496. if let Ok(entries) = std::fs::read_dir(&steam_msg_dir) {
  4497. for entry in entries.flatten() {
  4498. if let Some(name) = entry.file_name().to_str() {
  4499. // 主日志文件: {request_id}-{worker_index}.txt
  4500. // 例如: 306-1.txt
  4501. if name.starts_with(&pattern) && name.ends_with(".txt") {
  4502. let file_name_stem = name.trim_end_matches(".txt");
  4503. // 排除备份文件(如果存在旧的备份文件)
  4504. let parts: Vec<&str> = file_name_stem.split('-').collect();
  4505. if parts.len() >= 3 {
  4506. // 检查是否是旧格式的备份文件: {request_id}-{worker_index}-{backup_index}
  4507. if parts[1].parse::<u32>().is_ok() && parts[2].parse::<u32>().is_ok() {
  4508. // 这是旧备份文件,跳过
  4509. continue;
  4510. }
  4511. }
  4512. // 主日志文件: {request_id}-{worker_index}.txt
  4513. if let Some(idx) = file_name_stem.strip_prefix(&pattern)
  4514. .and_then(|s| s.parse::<u32>().ok())
  4515. {
  4516. let path = entry.path();
  4517. let size = std::fs::metadata(&path)
  4518. .map(|m| m.len())
  4519. .unwrap_or(0);
  4520. worker_files.push(serde_json::json!({
  4521. "worker_index": idx,
  4522. "file_name": name,
  4523. "file_size": size,
  4524. }));
  4525. }
  4526. }
  4527. }
  4528. }
  4529. }
  4530. // 按 worker 序号排序
  4531. worker_files.sort_by_key(|v| v.get("worker_index").and_then(|x| x.as_u64()).unwrap_or(0));
  4532. let resp = serde_json::json!({
  4533. "code": 200,
  4534. "msg": "ok",
  4535. "data": {
  4536. "request_id": request_id,
  4537. "worker_count": worker_files.len(),
  4538. "workers": worker_files,
  4539. "rotation_threshold": 1000,
  4540. "note": "每个 Worker 日志文件最多保留 1000 条记录,超出后自动清空",
  4541. }
  4542. });
  4543. serde_json::to_string(&resp).map_err(|e| format!("序列化失败: {}", e))
  4544. }
  4545. /// 读取指定 Worker 的扫货日志
  4546. #[tauri::command]
  4547. pub async fn read_sweep_worker_log(
  4548. request_id: i64,
  4549. worker_index: u32,
  4550. last_log_count: Option<i64>,
  4551. limit: Option<i64>,
  4552. ) -> Result<String, String> {
  4553. let steam_msg_dir = get_steam_msg_dir()
  4554. .map_err(|e| format!("获取目录失败: {}", e))?;
  4555. let file_name = format!("{}-{}.txt", request_id, worker_index);
  4556. let file_path = steam_msg_dir.join(&file_name);
  4557. if !file_path.exists() {
  4558. return Err(format!("Worker {} 日志文件不存在", worker_index));
  4559. }
  4560. // 读取文件内容
  4561. let content = std::fs::read_to_string(&file_path)
  4562. .map_err(|e| format!("读取日志文件失败: {}", e))?;
  4563. // 解析日志条目
  4564. let mut all_logs: Vec<LocalSweepLogEntry> = Vec::new();
  4565. for line in content.lines() {
  4566. let raw = line.trim();
  4567. if raw.is_empty() {
  4568. continue;
  4569. }
  4570. if let Ok(v) = serde_json::from_str::<serde_json::Value>(raw) {
  4571. let entry = LocalSweepLogEntry {
  4572. created_at: v.get("created_at").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4573. goods_id: v.get("goods_id").and_then(|x| x.as_str()).map(|s| s.to_string()).unwrap_or_default(),
  4574. goods_name: v.get("goods_name").and_then(|x| x.as_str()).map(|s| s.to_string()).unwrap_or_default(),
  4575. result: v.get("result").and_then(|x| x.as_str()).map(|s| s.to_string()).unwrap_or_default(),
  4576. message: v.get("message").and_then(|x| x.as_str()).map(|s| s.to_string()).unwrap_or_default(),
  4577. target_price_usd: v.get("target_price_usd").and_then(|x| x.as_f64()),
  4578. purchase_price_usd: v.get("purchase_price_usd").and_then(|x| x.as_f64()),
  4579. purchase_wear: v.get("purchase_wear").and_then(|x| x.as_f64()),
  4580. listing_id: v.get("listing_id").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4581. line_color: v.get("line_color").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4582. diag_level: v.get("diag_level").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4583. api_name: v.get("api_name").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4584. steam_account: v.get("steam_account").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4585. method: v.get("method").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4586. url: v.get("url").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4587. error: v.get("error").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4588. elapsed_ms: v.get("elapsed_ms").and_then(|x| x.as_i64()).map(|x| x as u64),
  4589. request_headers: None,
  4590. request_body: None,
  4591. response_status: v.get("response_status").and_then(|x| x.as_i64()).map(|x| x as u16),
  4592. response_body: None,
  4593. };
  4594. all_logs.push(entry);
  4595. }
  4596. }
  4597. hydrate_sweep_log_line_colors(&mut all_logs);
  4598. let total_log_count = all_logs.len();
  4599. let cursor = last_log_count.unwrap_or(-1);
  4600. let logs: Vec<LocalSweepLogEntry> = if cursor >= 0 {
  4601. let start = if (cursor as usize) > total_log_count {
  4602. 0
  4603. } else {
  4604. cursor as usize
  4605. };
  4606. let lim = limit.unwrap_or(1000) as usize;
  4607. all_logs[start..].iter().take(lim).cloned().collect()
  4608. } else {
  4609. let lim = limit.unwrap_or(1000) as usize;
  4610. all_logs.iter().take(lim).cloned().collect()
  4611. };
  4612. let resp = serde_json::json!({
  4613. "code": 200,
  4614. "msg": "ok",
  4615. "data": {
  4616. "request_id": request_id,
  4617. "worker_index": worker_index,
  4618. "log_count": total_log_count as i64,
  4619. "logs": logs,
  4620. }
  4621. });
  4622. serde_json::to_string(&resp).map_err(|e| format!("序列化失败: {}", e))
  4623. }
  4624. /// 读取指定 Worker 的备份扫货日志
  4625. #[tauri::command]
  4626. pub async fn read_sweep_worker_backup_log(
  4627. request_id: i64,
  4628. worker_index: u32,
  4629. backup_index: u32,
  4630. last_log_count: Option<i64>,
  4631. limit: Option<i64>,
  4632. ) -> Result<String, String> {
  4633. let steam_msg_dir = get_steam_msg_dir()
  4634. .map_err(|e| format!("获取目录失败: {}", e))?;
  4635. let file_name = format!("{}-{}-{}.txt", request_id, worker_index, backup_index);
  4636. let file_path = steam_msg_dir.join(&file_name);
  4637. if !file_path.exists() {
  4638. return Err(format!("Worker {} 备份 {} 日志文件不存在", worker_index, backup_index));
  4639. }
  4640. // 读取文件内容
  4641. let content = std::fs::read_to_string(&file_path)
  4642. .map_err(|e| format!("读取日志文件失败: {}", e))?;
  4643. // 解析日志条目
  4644. let mut all_logs: Vec<LocalSweepLogEntry> = Vec::new();
  4645. for line in content.lines() {
  4646. let raw = line.trim();
  4647. if raw.is_empty() {
  4648. continue;
  4649. }
  4650. if let Ok(v) = serde_json::from_str::<serde_json::Value>(raw) {
  4651. let entry = LocalSweepLogEntry {
  4652. created_at: v.get("created_at").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4653. goods_id: v.get("goods_id").and_then(|x| x.as_str()).map(|s| s.to_string()).unwrap_or_default(),
  4654. goods_name: v.get("goods_name").and_then(|x| x.as_str()).map(|s| s.to_string()).unwrap_or_default(),
  4655. result: v.get("result").and_then(|x| x.as_str()).map(|s| s.to_string()).unwrap_or_default(),
  4656. message: v.get("message").and_then(|x| x.as_str()).map(|s| s.to_string()).unwrap_or_default(),
  4657. target_price_usd: v.get("target_price_usd").and_then(|x| x.as_f64()),
  4658. purchase_price_usd: v.get("purchase_price_usd").and_then(|x| x.as_f64()),
  4659. purchase_wear: v.get("purchase_wear").and_then(|x| x.as_f64()),
  4660. listing_id: v.get("listing_id").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4661. line_color: v.get("line_color").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4662. diag_level: v.get("diag_level").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4663. api_name: v.get("api_name").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4664. steam_account: v.get("steam_account").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4665. method: v.get("method").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4666. url: v.get("url").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4667. error: v.get("error").and_then(|x| x.as_str()).map(|s| s.to_string()),
  4668. elapsed_ms: v.get("elapsed_ms").and_then(|x| x.as_i64()).map(|x| x as u64),
  4669. request_headers: None,
  4670. request_body: None,
  4671. response_status: v.get("response_status").and_then(|x| x.as_i64()).map(|x| x as u16),
  4672. response_body: None,
  4673. };
  4674. all_logs.push(entry);
  4675. }
  4676. }
  4677. hydrate_sweep_log_line_colors(&mut all_logs);
  4678. let total_log_count = all_logs.len();
  4679. let cursor = last_log_count.unwrap_or(-1);
  4680. let logs: Vec<LocalSweepLogEntry> = if cursor >= 0 {
  4681. let start = if (cursor as usize) > total_log_count {
  4682. 0
  4683. } else {
  4684. cursor as usize
  4685. };
  4686. let lim = limit.unwrap_or(1000) as usize;
  4687. all_logs[start..].iter().take(lim).cloned().collect()
  4688. } else {
  4689. let lim = limit.unwrap_or(1000) as usize;
  4690. all_logs.iter().take(lim).cloned().collect()
  4691. };
  4692. let resp = serde_json::json!({
  4693. "code": 200,
  4694. "msg": "ok",
  4695. "data": {
  4696. "request_id": request_id,
  4697. "worker_index": worker_index,
  4698. "backup_index": backup_index,
  4699. "log_count": total_log_count as i64,
  4700. "logs": logs,
  4701. "note": "这是历史备份日志",
  4702. }
  4703. });
  4704. serde_json::to_string(&resp).map_err(|e| format!("序列化失败: {}", e))
  4705. }
  4706. // ============ 本地扫货成功数据存储(不再依赖后端数据库) ============
  4707. // 数据结构:以 listing_id 为 key,便于记录同一饰品的多次购买(每次购买磨损不同)
  4708. /// 扫货成功数据结构(以 listing_id 为 key)
  4709. #[derive(Debug, Clone, Serialize, Deserialize)]
  4710. struct LocalSweepSuccessItem {
  4711. /// 扫货请求ID
  4712. sweep_request_id: i64,
  4713. /// 选品请求ID
  4714. selection_request_id: i64,
  4715. /// 选品记录ID
  4716. selection_item_id: i64,
  4717. /// 商品ID
  4718. goods_id: String,
  4719. /// 商品名称
  4720. goods_name: String,
  4721. /// 图标URL
  4722. icon_url: Option<String>,
  4723. /// 磨损区间
  4724. wear_range: Option<String>,
  4725. /// 购买时的磨损值
  4726. purchase_wear: Option<f64>,
  4727. /// 目标价格(USD)
  4728. target_price_usd: Option<f64>,
  4729. /// 成交价格(USD)
  4730. purchase_price_usd: Option<f64>,
  4731. /// 成交价格(人民币)
  4732. purchase_price_cny: Option<f64>,
  4733. /// 购买账号
  4734. steam_account: Option<String>,
  4735. /// Listing ID(唯一标识一次购买)
  4736. listing_id: Option<String>,
  4737. /// 创建时间
  4738. created_at: String,
  4739. }
  4740. impl LocalSweepSuccessItem {
  4741. fn new(
  4742. sweep_request_id: i64,
  4743. selection_request_id: i64,
  4744. selection_item_id: i64,
  4745. goods_id: String,
  4746. goods_name: String,
  4747. icon_url: Option<String>,
  4748. wear_range: Option<String>,
  4749. purchase_wear: Option<f64>,
  4750. target_price_usd: Option<f64>,
  4751. purchase_price_usd: Option<f64>,
  4752. purchase_price_cny: Option<f64>,
  4753. steam_account: Option<String>,
  4754. listing_id: Option<String>,
  4755. // 购买时间(由调用方在发起购买前生成,确保排序正确)
  4756. created_at: Option<String>,
  4757. ) -> Self {
  4758. Self {
  4759. sweep_request_id,
  4760. selection_request_id,
  4761. selection_item_id,
  4762. goods_id,
  4763. goods_name,
  4764. icon_url,
  4765. wear_range,
  4766. purchase_wear,
  4767. target_price_usd,
  4768. purchase_price_usd,
  4769. purchase_price_cny,
  4770. steam_account,
  4771. listing_id,
  4772. created_at: created_at.unwrap_or_else(|| {
  4773. chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string()
  4774. }),
  4775. }
  4776. }
  4777. }
  4778. /// 本地扫货成功数据外层结构
  4779. #[derive(Debug, Clone, Serialize, Deserialize, Default)]
  4780. struct LocalSweepSuccessData {
  4781. /// 以 listing_id 为 key 的购买记录(同一饰品的多次购买各自独立记录)
  4782. items: HashMap<String, LocalSweepSuccessItem>,
  4783. /// 总数
  4784. total: i64,
  4785. }
  4786. /// 获取扫货成功数据文件路径(按用户名存储)
  4787. fn sweep_success_items_path() -> Result<PathBuf, String> {
  4788. let username = get_current_username_internal().unwrap_or_else(|_| "guest".to_string());
  4789. let steam_msg_dir = get_steam_msg_dir()?;
  4790. let filename = format!("{}_sweep_success_items.json", sanitize_username_for_filename(&username));
  4791. Ok(steam_msg_dir.join(filename))
  4792. }
  4793. /// 内部函数:获取当前用户名(不返回 Result)
  4794. fn get_current_username_internal() -> Result<String, String> {
  4795. let storage = get_storage()
  4796. .map_err(|e| format!("获取存储失败: {}", e))?;
  4797. match storage.get_user_info() {
  4798. Ok(user_info) => Ok(user_info.username),
  4799. Err(_) => Err("未找到用户信息".to_string()),
  4800. }
  4801. }
  4802. /// 读取本地所有扫货成功数据(返回 Vec 格式供兼容性使用)
  4803. fn load_all_local_sweep_success_items() -> Result<Vec<LocalSweepSuccessItem>, String> {
  4804. let path = sweep_success_items_path()?;
  4805. if !path.exists() {
  4806. return Ok(Vec::new());
  4807. }
  4808. let content = fs::read_to_string(&path).map_err(|e| format!("读取扫货成功数据失败: {}", e))?;
  4809. if content.trim().is_empty() {
  4810. return Ok(Vec::new());
  4811. }
  4812. // 尝试解析新格式(items 为数组,按时间倒序存储)
  4813. #[derive(Debug, Deserialize)]
  4814. struct SweepSuccessDataArray {
  4815. items: Vec<LocalSweepSuccessItem>,
  4816. total: i64,
  4817. }
  4818. if let Ok(data) = serde_json::from_str::<SweepSuccessDataArray>(&content) {
  4819. // 新格式:数组已按时间倒序,直接返回
  4820. return Ok(data.items);
  4821. }
  4822. // 尝试解析旧格式(items 为 HashMap)
  4823. if let Ok(data) = serde_json::from_str::<LocalSweepSuccessData>(&content) {
  4824. let mut items: Vec<LocalSweepSuccessItem> = data.items.into_values().collect();
  4825. items.sort_by(|a, b| b.created_at.cmp(&a.created_at));
  4826. return Ok(items);
  4827. }
  4828. // 兼容最旧格式(纯数组)
  4829. let items: Vec<LocalSweepSuccessItem> =
  4830. serde_json::from_str(&content).map_err(|e| format!("解析扫货成功数据失败: {}", e))?;
  4831. Ok(items)
  4832. }
  4833. /// 追加单条扫货成功数据到本地(以 listing_id 为 key,每次购买独立记录)
  4834. fn append_local_sweep_success_item(item: LocalSweepSuccessItem) -> Result<(), String> {
  4835. let path = sweep_success_items_path()?;
  4836. let steam_msg_dir = get_steam_msg_dir()?;
  4837. fs::create_dir_all(&steam_msg_dir).map_err(|e| format!("创建目录失败: {}", e))?;
  4838. app_logger::debug(&format!(
  4839. "[sweep] [DEBUG] 开始写入文件: path={}, listing_id={:?}",
  4840. path.display(),
  4841. item.listing_id
  4842. ));
  4843. // 获取文件锁,防止并发写入导致数据覆盖
  4844. let _guard = get_sweep_success_file_lock()
  4845. .lock()
  4846. .map_err(|e| format!("获取文件锁失败: {}", e))?;
  4847. // 读取现有数据
  4848. let mut data = if path.exists() {
  4849. let content = fs::read_to_string(&path).unwrap_or_default();
  4850. app_logger::debug(&format!(
  4851. "[sweep] [DEBUG] 读取现有文件成功, 内容长度={}",
  4852. content.len()
  4853. ));
  4854. if content.trim().is_empty() {
  4855. LocalSweepSuccessData::default()
  4856. } else if let Ok(d) = serde_json::from_str::<LocalSweepSuccessData>(&content) {
  4857. // 新格式(items 为 HashMap)
  4858. d
  4859. } else {
  4860. // 尝试解析新数组格式 { "items": [...], "total": N }
  4861. #[derive(Deserialize)]
  4862. struct SweepSuccessDataArray {
  4863. items: Vec<LocalSweepSuccessItem>,
  4864. total: i64,
  4865. }
  4866. if let Ok(array_data) = serde_json::from_str::<SweepSuccessDataArray>(&content) {
  4867. let mut map = HashMap::new();
  4868. for i in array_data.items {
  4869. let key = i.listing_id.clone().unwrap_or_else(|| i.goods_id.clone());
  4870. map.insert(key, i);
  4871. }
  4872. LocalSweepSuccessData {
  4873. items: map,
  4874. total: array_data.total,
  4875. }
  4876. } else {
  4877. // 旧格式数据(goods_id 为 key 或纯数组),转换为新格式(listing_id 为 key)
  4878. if let Ok(items) = serde_json::from_str::<Vec<LocalSweepSuccessItem>>(&content) {
  4879. let mut map = HashMap::new();
  4880. for i in items {
  4881. let key = i.listing_id.clone().unwrap_or_else(|| i.goods_id.clone());
  4882. map.insert(key, i);
  4883. }
  4884. let total = map.len() as i64;
  4885. LocalSweepSuccessData {
  4886. items: map,
  4887. total,
  4888. }
  4889. } else if let Ok(data_old) = serde_json::from_str::<LocalSweepSuccessData>(&content) {
  4890. // 旧格式(goods_id 为 key),迁移时用 goods_id 作为 listing_id 兜底
  4891. let mut map = HashMap::new();
  4892. for (k, v) in data_old.items {
  4893. let key = v.listing_id.clone().unwrap_or(k);
  4894. map.insert(key, v);
  4895. }
  4896. let total = map.len() as i64;
  4897. LocalSweepSuccessData {
  4898. items: map,
  4899. total,
  4900. }
  4901. } else {
  4902. LocalSweepSuccessData::default()
  4903. }
  4904. }
  4905. }
  4906. } else {
  4907. app_logger::debug("[sweep] [DEBUG] 文件不存在,将创建新文件");
  4908. LocalSweepSuccessData::default()
  4909. };
  4910. // 使用 listing_id 作为 key 存储/更新(每次购买独立记录)
  4911. let listing_id = item.listing_id.clone().unwrap_or_else(|| item.goods_id.clone());
  4912. let prev_count = data.items.len();
  4913. app_logger::debug(&format!(
  4914. "[sweep] [DEBUG] 插入前: listing_id={}, prev_count={}, key_exists={}",
  4915. listing_id,
  4916. prev_count,
  4917. data.items.contains_key(&listing_id)
  4918. ));
  4919. data.items.insert(listing_id.clone(), item.clone());
  4920. let new_count = data.items.len();
  4921. data.total = data.items.len() as i64;
  4922. // 记录是否新增成功
  4923. if new_count > prev_count {
  4924. info!(
  4925. "[sweep] 新增购买记录: listing_id={}, 当前总数={}",
  4926. listing_id, new_count
  4927. );
  4928. } else {
  4929. warn!(
  4930. "[sweep] 更新购买记录: listing_id={}, 可能存在重复",
  4931. listing_id
  4932. );
  4933. }
  4934. // 按购买时间倒序排列后写入文件(最新在前)
  4935. let content = serialize_sweep_success_data_ordered(&data)
  4936. .map_err(|e| format!("序列化扫货成功数据失败: {}", e))?;
  4937. app_logger::debug(&format!(
  4938. "[sweep] [DEBUG] 序列化后内容长度={}",
  4939. content.len()
  4940. ));
  4941. fs::write(&path, content).map_err(|e| format!("写入扫货成功数据失败: {}", e))?;
  4942. app_logger::debug(&format!(
  4943. "[sweep] [DEBUG] 文件写入成功: {} 条记录",
  4944. new_count
  4945. ));
  4946. // 释放锁(_guard 在此作用域结束时自动释放)
  4947. // 同时写入以 steam_account 为名称的文件
  4948. if let Some(ref account) = item.steam_account {
  4949. if !account.is_empty() {
  4950. if let Err(e) = append_to_steam_account_file(account, &item) {
  4951. error!("[sweep] 写入账号 {} 文件失败: {}", account, e);
  4952. } else {
  4953. info!("[sweep] 写入账号 {} 文件成功", account);
  4954. }
  4955. }
  4956. }
  4957. Ok(())
  4958. }
  4959. /// 获取以 Steam 账号为名称的扫货成功数据文件路径
  4960. fn steam_account_sweep_items_path(account: &str) -> Result<PathBuf, String> {
  4961. let steam_msg_dir = get_steam_msg_dir()?;
  4962. let filename = format!("{}_sweep_items.json", sanitize_username_for_filename(account));
  4963. Ok(steam_msg_dir.join(filename))
  4964. }
  4965. /// 追加数据到以 Steam 账号为名称的文件(以 listing_id 为 key)
  4966. fn append_to_steam_account_file(account: &str, item: &LocalSweepSuccessItem) -> Result<(), String> {
  4967. let path = steam_account_sweep_items_path(account)?;
  4968. let steam_msg_dir = get_steam_msg_dir()?;
  4969. fs::create_dir_all(&steam_msg_dir).map_err(|e| format!("创建目录失败: {}", e))?;
  4970. debug!(
  4971. "[sweep] [account_file] 开始写入: account={}, listing_id={:?}",
  4972. account,
  4973. item.listing_id
  4974. );
  4975. // 读取现有数据
  4976. let mut data = if path.exists() {
  4977. let content = fs::read_to_string(&path).unwrap_or_default();
  4978. debug!(
  4979. "[sweep] [account_file] 读取现有文件成功: {} bytes",
  4980. content.len()
  4981. );
  4982. if content.trim().is_empty() {
  4983. LocalSweepSuccessData::default()
  4984. } else if let Ok(d) = serde_json::from_str::<LocalSweepSuccessData>(&content) {
  4985. // 新格式(items 为 HashMap)
  4986. d
  4987. } else {
  4988. // 尝试解析新数组格式 { "items": [...], "total": N }
  4989. #[derive(Deserialize)]
  4990. struct SweepSuccessDataArray {
  4991. items: Vec<LocalSweepSuccessItem>,
  4992. total: i64,
  4993. }
  4994. if let Ok(array_data) = serde_json::from_str::<SweepSuccessDataArray>(&content) {
  4995. let mut map = HashMap::new();
  4996. for i in array_data.items {
  4997. let key = i.listing_id.clone().unwrap_or_else(|| i.goods_id.clone());
  4998. map.insert(key, i);
  4999. }
  5000. LocalSweepSuccessData {
  5001. items: map,
  5002. total: array_data.total,
  5003. }
  5004. } else {
  5005. // 旧格式数据(goods_id 为 key 或纯数组),转换为新格式(listing_id 为 key)
  5006. if let Ok(items) = serde_json::from_str::<Vec<LocalSweepSuccessItem>>(&content) {
  5007. let mut map = HashMap::new();
  5008. for i in items {
  5009. let key = i.listing_id.clone().unwrap_or_else(|| i.goods_id.clone());
  5010. map.insert(key, i);
  5011. }
  5012. let total = map.len() as i64;
  5013. LocalSweepSuccessData {
  5014. items: map,
  5015. total,
  5016. }
  5017. } else if let Ok(data_old) = serde_json::from_str::<LocalSweepSuccessData>(&content) {
  5018. let mut map = HashMap::new();
  5019. for (k, v) in data_old.items {
  5020. let key = v.listing_id.clone().unwrap_or(k);
  5021. map.insert(key, v);
  5022. }
  5023. let total = map.len() as i64;
  5024. LocalSweepSuccessData {
  5025. items: map,
  5026. total,
  5027. }
  5028. } else {
  5029. LocalSweepSuccessData::default()
  5030. }
  5031. }
  5032. }
  5033. } else {
  5034. debug!("[sweep] [account_file] 文件不存在,将创建新文件");
  5035. LocalSweepSuccessData::default()
  5036. };
  5037. // 使用 listing_id 作为 key 存储/更新(每次购买独立记录)
  5038. let listing_id = item.listing_id.clone().unwrap_or_else(|| item.goods_id.clone());
  5039. let prev_count = data.items.len();
  5040. debug!(
  5041. "[sweep] [account_file] 插入前: listing_id={}, prev_count={}, key_exists={}",
  5042. listing_id,
  5043. prev_count,
  5044. data.items.contains_key(&listing_id)
  5045. );
  5046. data.items.insert(listing_id.clone(), item.clone());
  5047. let new_count = data.items.len();
  5048. data.total = data.items.len() as i64;
  5049. if new_count > prev_count {
  5050. info!(
  5051. "[sweep] [account_file] 新增记录: account={}, listing_id={}, 当前总数={}",
  5052. account, listing_id, new_count
  5053. );
  5054. } else {
  5055. warn!(
  5056. "[sweep] [account_file] 更新记录: account={}, listing_id={}",
  5057. account, listing_id
  5058. );
  5059. }
  5060. // 按购买时间倒序排列后写入文件(最新在前)
  5061. let content = serialize_sweep_success_data_ordered(&data)
  5062. .map_err(|e| format!("序列化数据失败: {}", e))?;
  5063. fs::write(&path, content).map_err(|e| format!("写入账号文件失败: {}", e))?;
  5064. debug!(
  5065. "[sweep] [account_file] 写入成功: {} 条记录",
  5066. new_count
  5067. );
  5068. Ok(())
  5069. }
  5070. /// 将扫货成功数据序列化为按购买时间倒序的格式(最新在前)
  5071. fn serialize_sweep_success_data_ordered(data: &LocalSweepSuccessData) -> Result<String, String> {
  5072. // 将 HashMap 转换为 Vec 并按 created_at 倒序排列(最新在前)
  5073. let mut items_vec: Vec<_> = data.items.iter().collect();
  5074. items_vec.sort_by(|a, b| b.1.created_at.cmp(&a.1.created_at));
  5075. // 提取排序后的数据(只取 value,忽略 key)
  5076. let ordered_items: Vec<&LocalSweepSuccessItem> = items_vec.into_iter().map(|(_, v)| v).collect();
  5077. // 使用数组格式输出(数组能保证顺序)
  5078. #[derive(Serialize)]
  5079. struct SweepSuccessDataArray<'a> {
  5080. items: Vec<&'a LocalSweepSuccessItem>,
  5081. total: i64,
  5082. }
  5083. let array_data = SweepSuccessDataArray {
  5084. items: ordered_items,
  5085. total: data.total,
  5086. };
  5087. serde_json::to_string_pretty(&array_data).map_err(|e| format!("JSON 序列化失败: {}", e))
  5088. }
  5089. /// 读取本地扫货成功数据(分页)
  5090. #[tauri::command]
  5091. pub async fn read_local_sweep_success_items(
  5092. page: Option<i64>,
  5093. page_size: Option<i64>,
  5094. ) -> Result<String, String> {
  5095. let page = page.unwrap_or(1).max(1);
  5096. let page_size = page_size.unwrap_or(10).clamp(1, 100);
  5097. let all_items = load_all_local_sweep_success_items().unwrap_or_default();
  5098. // 按时间倒序
  5099. let total = all_items.len() as i64;
  5100. let start = ((page - 1) * page_size) as usize;
  5101. let end = (start + page_size as usize).min(all_items.len());
  5102. let items: Vec<LocalSweepSuccessItem> = if start < all_items.len() {
  5103. all_items[start..end].to_vec()
  5104. } else {
  5105. Vec::new()
  5106. };
  5107. let resp = serde_json::json!({
  5108. "code": 200,
  5109. "msg": "ok",
  5110. "data": {
  5111. "page": page,
  5112. "page_size": page_size,
  5113. "total": total,
  5114. "items": items
  5115. }
  5116. });
  5117. serde_json::to_string(&resp).map_err(|e| format!("序列化失败: {}", e))
  5118. }
  5119. /// 添加本地扫货成功数据(供外部调用)
  5120. #[tauri::command]
  5121. pub async fn add_local_sweep_success_item(item_json: String) -> Result<String, String> {
  5122. let item: LocalSweepSuccessItem =
  5123. serde_json::from_str(&item_json).map_err(|e| format!("解析扫货成功数据失败: {}", e))?;
  5124. append_local_sweep_success_item(item)?;
  5125. Ok(serde_json::json!({"code": 200, "msg": "添加成功"}).to_string())
  5126. }
  5127. /// 添加演示用的扫货成功数据
  5128. #[tauri::command]
  5129. pub async fn add_demo_sweep_success_item() -> Result<String, String> {
  5130. let demo_item = LocalSweepSuccessItem::new(
  5131. 1, // sweep_request_id
  5132. 1, // selection_request_id
  5133. 1, // selection_item_id
  5134. "12345".to_string(), // goods_id
  5135. "AK-47 | 血腥运动 (略有磨损)".to_string(), // goods_name
  5136. Some("https://community.steamart.io/skins/730/ak47_bloodsport_in_field-worn.webp".to_string()), // icon_url
  5137. Some("略有磨损".to_string()), // wear_range
  5138. Some(0.12345678), // purchase_wear
  5139. Some(10.50), // target_price_usd
  5140. Some(9.80), // purchase_price_usd
  5141. Some(73.50), // purchase_price_cny (9.80 * 7.5)
  5142. Some("demo_steam_account".to_string()), // steam_account
  5143. Some("999999999999999999".to_string()), // listing_id
  5144. None, // created_at(演示数据使用自动生成的时间)
  5145. );
  5146. append_local_sweep_success_item(demo_item)?;
  5147. info!("[demo] 添加演示扫货成功数据成功");
  5148. Ok(serde_json::json!({"code": 200, "msg": "演示数据添加成功"}).to_string())
  5149. }
  5150. /// 异步追加扫货成功数据(供扫货进程内部调用)
  5151. pub async fn append_sweep_success_item_async(
  5152. sweep_request_id: i64,
  5153. selection_request_id: i64,
  5154. selection_item_id: i64,
  5155. goods_id: String,
  5156. goods_name: String,
  5157. icon_url: Option<String>,
  5158. wear_range: Option<String>,
  5159. purchase_wear: Option<f64>,
  5160. target_price_usd: Option<f64>,
  5161. purchase_price_usd: Option<f64>,
  5162. purchase_price_cny: Option<f64>,
  5163. steam_account: Option<String>,
  5164. listing_id: Option<String>,
  5165. // 购买时间(由调用方在发起购买前生成,确保排序正确)
  5166. created_at: Option<String>,
  5167. ) {
  5168. let item = LocalSweepSuccessItem::new(
  5169. sweep_request_id,
  5170. selection_request_id,
  5171. selection_item_id,
  5172. goods_id,
  5173. goods_name,
  5174. icon_url,
  5175. wear_range,
  5176. purchase_wear,
  5177. target_price_usd,
  5178. purchase_price_usd,
  5179. purchase_price_cny,
  5180. steam_account,
  5181. listing_id,
  5182. created_at,
  5183. );
  5184. if let Err(e) = append_local_sweep_success_item(item) {
  5185. app_logger::error(&format!("[sweep] 写入本地扫货成功数据失败: {}", e));
  5186. } else {
  5187. app_logger::info("[sweep] 写入本地扫货成功数据成功");
  5188. }
  5189. }
  5190. /// 读取指定 Steam 账号的扫货成功数据(从 {account}_sweep_items.json)
  5191. #[tauri::command]
  5192. pub async fn read_steam_account_sweep_items(
  5193. account: String,
  5194. ) -> Result<String, String> {
  5195. let path = steam_account_sweep_items_path(&account)?;
  5196. if !path.exists() {
  5197. let resp = serde_json::json!({
  5198. "code": 200,
  5199. "msg": "ok",
  5200. "data": {
  5201. "items": [],
  5202. "total": 0
  5203. }
  5204. });
  5205. return serde_json::to_string(&resp).map_err(|e| format!("序列化失败: {}", e));
  5206. }
  5207. let content = fs::read_to_string(&path).map_err(|e| format!("读取文件失败: {}", e))?;
  5208. if content.trim().is_empty() {
  5209. let resp = serde_json::json!({
  5210. "code": 200,
  5211. "msg": "ok",
  5212. "data": {
  5213. "items": [],
  5214. "total": 0
  5215. }
  5216. });
  5217. return serde_json::to_string(&resp).map_err(|e| format!("序列化失败: {}", e));
  5218. }
  5219. // 尝试解析新格式(items 为数组,按时间倒序存储)
  5220. #[derive(Debug, Deserialize)]
  5221. struct SweepSuccessDataArray {
  5222. items: Vec<LocalSweepSuccessItem>,
  5223. total: i64,
  5224. }
  5225. if let Ok(data) = serde_json::from_str::<SweepSuccessDataArray>(&content) {
  5226. // 新格式:数组已按时间倒序,直接使用
  5227. let resp = serde_json::json!({
  5228. "code": 200,
  5229. "msg": "ok",
  5230. "data": {
  5231. "items": data.items,
  5232. "total": data.total
  5233. }
  5234. });
  5235. return serde_json::to_string(&resp).map_err(|e| format!("序列化失败: {}", e));
  5236. }
  5237. // 尝试解析旧格式(items 为 HashMap)
  5238. if let Ok(data) = serde_json::from_str::<LocalSweepSuccessData>(&content) {
  5239. let mut items: Vec<LocalSweepSuccessItem> = data.items.into_values().collect();
  5240. items.sort_by(|a, b| b.created_at.cmp(&a.created_at));
  5241. let resp = serde_json::json!({
  5242. "code": 200,
  5243. "msg": "ok",
  5244. "data": {
  5245. "items": items,
  5246. "total": data.total
  5247. }
  5248. });
  5249. return serde_json::to_string(&resp).map_err(|e| format!("序列化失败: {}", e));
  5250. }
  5251. // 兼容旧格式(纯数组)
  5252. let items: Vec<LocalSweepSuccessItem> =
  5253. serde_json::from_str(&content).map_err(|e| format!("解析数据失败: {}", e))?;
  5254. let total = items.len() as i64;
  5255. let resp = serde_json::json!({
  5256. "code": 200,
  5257. "msg": "ok",
  5258. "data": {
  5259. "items": items,
  5260. "total": total
  5261. }
  5262. });
  5263. serde_json::to_string(&resp).map_err(|e| format!("序列化失败: {}", e))
  5264. }
  5265. /// 获取应用日志文件路径(exe 同目录下 logs/app_YYYY-MM-DD.log)
  5266. #[tauri::command]
  5267. pub async fn get_app_log_path() -> Result<String, String> {
  5268. crate::app_logger::get_current_log_path()
  5269. .ok_or_else(|| "日志系统未初始化".to_string())
  5270. }