From 497d65d41d8ee8b86aa13cfdfbeba62a9267b6ff Mon Sep 17 00:00:00 2001 From: super <2903208875@qq.com> Date: Sun, 22 Mar 2026 11:20:09 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=B0=E5=A2=9E=E4=B8=89?= =?UTF-8?q?=E4=B8=AA=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 1.xlsx | Bin 15829 -> 0 bytes app/__pycache__/app.cpython-312.pyc | Bin 2014 -> 2014 bytes app/__pycache__/config.cpython-312.pyc | Bin 2050 -> 2050 bytes .../__pycache__/brand.cpython-312.pyc | Bin 47455 -> 48086 bytes app/blueprints/brand.py | 25 +- app/main.py | 3 +- .../convert/model/dto/ConvertRunRequest.java | 3 + .../model/dto/UploadedSourceFileDto.java | 3 + .../convert/service/ConvertRunService.java | 253 +++- .../controller/DedupeRunController.java | 1 + .../controller/DedupeTotalDataController.java | 93 ++ .../dedupe/mapper/DedupeTotalDataMapper.java | 9 + .../dedupe/model/dto/DedupeRunRequest.java | 8 +- .../dedupe/model/dto/DedupeSourceFileDto.java | 3 + .../dto/DedupeTotalDataCreateRequest.java | 16 + .../dto/DedupeTotalDataUpdateRequest.java | 16 + .../model/entity/DedupeTotalDataEntity.java | 19 + .../model/vo/DedupeTotalDataImportVo.java | 21 + .../model/vo/DedupeTotalDataItemVo.java | 20 + .../model/vo/DedupeTotalDataPageVo.java | 23 + .../dedupe/service/DedupeRunService.java | 148 ++- .../service/DedupeTotalDataService.java | 215 ++++ .../file/controller/FileUploadController.java | 5 +- .../modules/file/model/vo/UploadFileVo.java | 3 + .../file/service/LocalFileStorageService.java | 3 +- .../split/controller/SplitRunController.java | 3 +- .../split/model/dto/SplitRunRequest.java | 3 + .../split/model/dto/SplitSourceFileDto.java | 3 + .../split/model/vo/SplitArchiveEntryVo.java | 15 + .../split/model/vo/SplitResultItemVo.java | 10 +- .../split/service/SplitRunService.java | 307 +++-- .../db/V3__create_dedupe_total_data.sql | 8 + ...ame_convert_template_to_five_countries.sql | 5 + backend/.device_id | 1 + backend/__pycache__/ali_oss.cpython-311.pyc | Bin 0 -> 1764 bytes backend/__pycache__/ali_oss.cpython-312.pyc | Bin 0 -> 1514 bytes backend/__pycache__/ali_oss.cpython-39.pyc | Bin 0 -> 988 bytes backend/__pycache__/app.cpython-311.pyc | Bin 0 -> 2119 bytes backend/__pycache__/config.cpython-311.pyc | Bin 0 -> 1262 bytes backend/__pycache__/config.cpython-312.pyc | Bin 0 -> 1510 bytes backend/__pycache__/config.cpython-39.pyc | Bin 0 -> 1043 bytes backend/__pycache__/db.cpython-311.pyc | Bin 0 -> 7450 bytes backend/ali_oss.py | 36 + backend/app.py | 38 + backend/blueprints/__init__.py | 1 + .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 159 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 153 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 127 bytes .../__pycache__/admin_api.cpython-311.pyc | Bin 0 -> 26163 bytes .../__pycache__/admin_api.cpython-312.pyc | Bin 0 -> 36208 bytes .../__pycache__/admin_api.cpython-39.pyc | Bin 0 -> 16876 bytes .../__pycache__/admin_history.cpython-311.pyc | Bin 0 -> 5445 bytes .../__pycache__/admin_users.cpython-311.pyc | Bin 0 -> 16921 bytes .../admin_versions.cpython-311.pyc | Bin 0 -> 5607 bytes .../__pycache__/auth.cpython-311.pyc | Bin 0 -> 5312 bytes .../__pycache__/auth.cpython-312.pyc | Bin 0 -> 4403 bytes .../__pycache__/auth.cpython-39.pyc | Bin 0 -> 2461 bytes .../__pycache__/main.cpython-311.pyc | Bin 0 -> 2820 bytes .../__pycache__/main.cpython-312.pyc | Bin 0 -> 2506 bytes .../__pycache__/main.cpython-39.pyc | Bin 0 -> 1448 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 2059 bytes .../__pycache__/version.cpython-39.pyc | Bin 0 -> 1333 bytes backend/blueprints/admin_api.py | 723 +++++++++++ backend/blueprints/auth.py | 74 ++ backend/blueprints/main.py | 38 + backend/blueprints/version.py | 40 + backend/config.py | 38 + backend/requirement.txt | 61 + backend/static/bg.jpg | Bin 0 -> 164259 bytes .../tool/__pycache__/devices.cpython-39.pyc | Bin 0 -> 7380 bytes backend/tool/devices.py | 249 ++++ backend/utils/__init__.py | 1 + .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 154 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 148 bytes .../utils/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 122 bytes .../utils/__pycache__/auth.cpython-311.pyc | Bin 0 -> 5806 bytes .../utils/__pycache__/auth.cpython-312.pyc | Bin 0 -> 4805 bytes backend/utils/__pycache__/auth.cpython-39.pyc | Bin 0 -> 2788 bytes backend/utils/__pycache__/db.cpython-311.pyc | Bin 0 -> 7457 bytes backend/utils/__pycache__/db.cpython-312.pyc | Bin 0 -> 7467 bytes backend/utils/__pycache__/db.cpython-39.pyc | Bin 0 -> 5172 bytes .../utils/__pycache__/helpers.cpython-311.pyc | Bin 0 -> 1955 bytes .../utils/__pycache__/render.cpython-311.pyc | Bin 0 -> 1851 bytes .../utils/__pycache__/render.cpython-312.pyc | Bin 0 -> 1608 bytes .../utils/__pycache__/render.cpython-39.pyc | Bin 0 -> 977 bytes backend/utils/auth.py | 81 ++ backend/utils/db.py | 140 ++ backend/utils/render.py | 22 + backend/web_source/admin.html | 1121 +++++++++++++++++ backend/web_source/login.html | 164 +++ .../brand/components/BrandConvertTab.vue | 1000 ++++++++++++--- .../pages/brand/components/BrandDedupeTab.vue | 773 +++++++++--- .../pages/brand/components/BrandSplitTab.vue | 92 +- frontend-vue/src/shared/api/brand.ts | 9 +- frontend-vue/src/shared/api/java-modules.ts | 32 + frontend-vue/src/shared/bridges/pywebview.ts | 3 +- 96 files changed, 5393 insertions(+), 588 deletions(-) delete mode 100644 1.xlsx create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/controller/DedupeTotalDataController.java create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/mapper/DedupeTotalDataMapper.java create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/dto/DedupeTotalDataCreateRequest.java create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/dto/DedupeTotalDataUpdateRequest.java create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/entity/DedupeTotalDataEntity.java create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/vo/DedupeTotalDataImportVo.java create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/vo/DedupeTotalDataItemVo.java create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/vo/DedupeTotalDataPageVo.java create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/service/DedupeTotalDataService.java create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/split/model/vo/SplitArchiveEntryVo.java create mode 100644 backend-java/src/main/resources/db/V3__create_dedupe_total_data.sql create mode 100644 backend-java/src/main/resources/db/V4__rename_convert_template_to_five_countries.sql create mode 100644 backend/.device_id create mode 100644 backend/__pycache__/ali_oss.cpython-311.pyc create mode 100644 backend/__pycache__/ali_oss.cpython-312.pyc create mode 100644 backend/__pycache__/ali_oss.cpython-39.pyc create mode 100644 backend/__pycache__/app.cpython-311.pyc create mode 100644 backend/__pycache__/config.cpython-311.pyc create mode 100644 backend/__pycache__/config.cpython-312.pyc create mode 100644 backend/__pycache__/config.cpython-39.pyc create mode 100644 backend/__pycache__/db.cpython-311.pyc create mode 100644 backend/ali_oss.py create mode 100644 backend/app.py create mode 100644 backend/blueprints/__init__.py create mode 100644 backend/blueprints/__pycache__/__init__.cpython-311.pyc create mode 100644 backend/blueprints/__pycache__/__init__.cpython-312.pyc create mode 100644 backend/blueprints/__pycache__/__init__.cpython-39.pyc create mode 100644 backend/blueprints/__pycache__/admin_api.cpython-311.pyc create mode 100644 backend/blueprints/__pycache__/admin_api.cpython-312.pyc create mode 100644 backend/blueprints/__pycache__/admin_api.cpython-39.pyc create mode 100644 backend/blueprints/__pycache__/admin_history.cpython-311.pyc create mode 100644 backend/blueprints/__pycache__/admin_users.cpython-311.pyc create mode 100644 backend/blueprints/__pycache__/admin_versions.cpython-311.pyc create mode 100644 backend/blueprints/__pycache__/auth.cpython-311.pyc create mode 100644 backend/blueprints/__pycache__/auth.cpython-312.pyc create mode 100644 backend/blueprints/__pycache__/auth.cpython-39.pyc create mode 100644 backend/blueprints/__pycache__/main.cpython-311.pyc create mode 100644 backend/blueprints/__pycache__/main.cpython-312.pyc create mode 100644 backend/blueprints/__pycache__/main.cpython-39.pyc create mode 100644 backend/blueprints/__pycache__/version.cpython-312.pyc create mode 100644 backend/blueprints/__pycache__/version.cpython-39.pyc create mode 100644 backend/blueprints/admin_api.py create mode 100644 backend/blueprints/auth.py create mode 100644 backend/blueprints/main.py create mode 100644 backend/blueprints/version.py create mode 100644 backend/config.py create mode 100644 backend/requirement.txt create mode 100644 backend/static/bg.jpg create mode 100644 backend/tool/__pycache__/devices.cpython-39.pyc create mode 100644 backend/tool/devices.py create mode 100644 backend/utils/__init__.py create mode 100644 backend/utils/__pycache__/__init__.cpython-311.pyc create mode 100644 backend/utils/__pycache__/__init__.cpython-312.pyc create mode 100644 backend/utils/__pycache__/__init__.cpython-39.pyc create mode 100644 backend/utils/__pycache__/auth.cpython-311.pyc create mode 100644 backend/utils/__pycache__/auth.cpython-312.pyc create mode 100644 backend/utils/__pycache__/auth.cpython-39.pyc create mode 100644 backend/utils/__pycache__/db.cpython-311.pyc create mode 100644 backend/utils/__pycache__/db.cpython-312.pyc create mode 100644 backend/utils/__pycache__/db.cpython-39.pyc create mode 100644 backend/utils/__pycache__/helpers.cpython-311.pyc create mode 100644 backend/utils/__pycache__/render.cpython-311.pyc create mode 100644 backend/utils/__pycache__/render.cpython-312.pyc create mode 100644 backend/utils/__pycache__/render.cpython-39.pyc create mode 100644 backend/utils/auth.py create mode 100644 backend/utils/db.py create mode 100644 backend/utils/render.py create mode 100644 backend/web_source/admin.html create mode 100644 backend/web_source/login.html diff --git a/1.xlsx b/1.xlsx deleted file mode 100644 index a6e124d0dbe2f6c1357bab05ac304a389d469c0b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15829 zcma)jWmp|cwlySsjy$XHSkNy9*7`Db);9KZG9WTk&;yuXWW#(K z;0)kkU>p!&U`YQ-rfXwE>uhO}9^D}Z$$%7a>~oA{S8f^_6lW3gbxn|3rKRt{0((Z1 z&qzZpvnLd~gY9Hvp8N*Wi0!NT>4qig^9b*<^oIMyWQl*!RFjYN+uk&9rq&uO#go*{ z+nppP;zKb_H|W>P5NF@#mSaPAAO)(vPz$nC-=vWL^!rpDAhiBDMpKRwA6wEioNBdr z0E_){s})>Wj}s3cb_S^dWkB}k?FNVK)VLSFxHTtFv!)e4gwh$Y-yD`?VQym$sp|z= zWp%i>TF5tYx=&7<7;{1jRk^D00qzDS_;QaF#!gt@LTQ|fvw;zE?#6@Sywl(mMUm6n z*Y$lP`4|*28OwLJCb+{_GLo+RSdQVEpSDCgX;;VP5S$fh06Dy0$oN+>2-c0#_F2F- zQ~6t4Z5uDB)E^$c-$DEn-jmW8GHwvOS|E6l{}Wz)Ydgaie8Xd8#k=TH{g+=q^Z#&5 z&5LK?oA(~bz=266Z}s0OgJ(4r$L}Oc$_d?r`<2w(@`i10|m5o(k&c#HKA{kzTmzE9Zjt;v4wuIW7ql zAFpP-5<4%}umOL0(dE>N35p{U^%K#wMPV{y{b>i$#7G7{PoHI0>3LhA%HFCHj*KFV z)c4n0>govhsZTSgH(BgB6}rRLou$_(-5%PO@fKg(+x)?>tEB~yWJUP%4~#ciyqp(G zW#e;a38l`HsGa;|s=k`u@%(Jr!ylJ??U3>$y@oqZ^#)z%y`gv*B38N^izEd$Y>i*S zuHM7)VcPk+;`%&`)9vE1YvOQb6Z*lX4pu3ni2ECF1wQ5QIinNZmNPw=u+5^iQYA)T zXw#LCZOtOq(2Ii|tm9yTGNis-=J)UUgbob)!jH!X49&O!3ys?qf{kU^1mZNkQk0?O zy~rrpm5t|j(l{Sajoj|l4RU+NhApN7mv*;yp&!8ZhFKM%v!uYHWIbnHEYkq<336B4W?mU% z_o_40Ej02sVynBznfl{LepQ(NPVn|Ofs?hJ zxxI;@A>hwybbBj#fQ<$Q#_J9ShWS5ef3Yz9%9?a_Yi!XN|7DDs=MOs`SYFb(NCDvcpXVN9o@h&d5aJ%zxFSpSQYfI7PxpTJhENdUMmA5Umc(`yc zdg1tFWFGC=egE#(>gE0&SNLZ4Qn;7h>+a&lhBFP{(~GA?qxZ9xwDdK`y4U@~&F0~~ z$@9$(|GI~J%d^q(B^TlcJfTfaEme-z?Y_Y2^ZDBe!&4j2R_w8driJChT@N1K25+Aq zuB~2=hu3soRo-ngsrQ?wyQgO(tB0P@oSIvgMrzecE?XThw7k9@WpBy=O z@sH@P>+zQmyu905mOPxVPsfZZpC^`^Gk99s9>s5WRqBRHDZ0#`o}BNlpq^X5FFc=V z=jTs!ZC;F_4;~I}K5UlfOD7&(To{bGagEc3Y7cKO-;H|JW6<26BM>~Q9^unQYD=#V zK5Hq3mzF+b4}W~Kf_NyPMx1>YK>FU(TdmyxsM}=hfnAYg=6QwlCPJoq7~|`b)}$Xms;RlLM=aTUUq_De7${l!6Hit{XK6>rX`na&w@6j zu*77@12QLSeD&?{cV($CF?MBweB2g0^jrIS;sFXSR5U=mkDdaA85MVu3>T;GK#G}Y zTH*x#B5nxWTz=m)qNFPG*-|8C{1+>}m+?I~64^xZ5V*$op9UQXJ`2AQW3LAZOy}4H z&;*}Xtovn=dj}q}e{-mmmkCP|&QaLIoy(8GP5;c2C{vOmPENUFrIFPnNEEsoBnw+$ zISyWc+v207DoqpoYtS=oY%^;>j}tT_wF64>4~K{&1&rV{#VJxblqJFZe6MRz6IZXc zR+K+gb}^Mn@DCPIH$^A}szWU}To!*>W7Mb)gu&3v9^jbg+xvKyq@E+T)6uQ#xUWYu z9=*2pnW81+yMSNlmMlxh-PipEwiMkQxQUK#R1D!En;|MfjT)rIx_Q2$t@nj*odBhz zDVjJeT`YY;He(`ya#Q$1TA++WL#&fv17_lCimgDy4q3Q3i=1ZBw>=wS=;8#ks_ux_ z>!3MAg65DLZC~d<>9Roql!+hp^}jM_>+WfAk&$9yiiW{x5#0VpQ&6Vy!b!#zYJ~B^ z6$68TYm^(IB;rsQP}GGhS!#ve*d4)a=A*8!ypO0|UliYnRh(j$Ls=Hwr3Yg}LE9Cs zS(;T`$_!KiDi8rzBg)W+XnPDBDvK2_O5@Y24IS`YBv|A~E_C!bo1BMTWW;K*n$*0U zwcb>1*5s7207V=cl$GcEmk&g2-&-A+q2U^p7WNk9VZ9j7;^tG4D9SFk;5v+vp>-ak zV_smwVE!X7K`}WmLZOdqc5dMQRgd9>xfaT5R!<2@>YP{+l0#02Gres?Z?KK+6X=^B=`Dq;)KLg|89@J1xaG@L)@RPS*sf7;_%k` zT!rDcX-t+HMbUe52#SGRjcP%mwy;+3K=Z$8jJ7nuQ&S>Au6hvZ=4dmMkyW>MPm`E33~Bf3Ot}8MM)tHy@p>qXnT? zC{(l-k%zvfrxAdis^$w-jh1Ms3VK=@!jYW6Wf32%B0u?k}tG<`wf zNzi=lsqk||b9=!c{LlbPO^IbpL{aVC{rANLe?MFb>g;eDOTnuwm)@5Mj&Q802}+Q1 z(LAoPQm)Lt4BY-6FJD(Od`_SDz7WNg-nJrWQICphN5uC0YZx|{+Vj|MSh1-{voG5M%nL#(;jiX9;T8>#QNk5|Bt_`q9syF( zSJw(av1CF8NNgEomeb4w1$h2^4Gbv27iPwkcEsE`jDMqnN%ajHUJ?VccgWYSs3)S1 zPkB5aoTkul*ABrK_t?aAb*-l%Y&wltt=WX$a5pC0Fwv5nYh_oy)`VL}1EM*?vXs|- zad1FxLegM}Ee36-TlajC?gHu_6w5XT7Ung{Rg-@2RGA_Spg+3K=TWns2}PnRC)g9y ztT|X%Kk6o}-#)%I1%^<2jz@pYbPnQ9ni)AYt5q4)m9eb@*~Z4P=ru;2^8_7z_3X_!LKl!)3EUrqcoH~Q!7!L3Dd2rV^+FRAhHo=s zA`BA&FLp*7yb7~D=Ws#jQ>qu5rclEi4>^c=n~i(2&eN2o%cN7CXd?P`zX6eP zx&XaYZePT)IUDHT%x=-y6kJ-y`_Z~f(a~GUK=en1!E4q^?M`I+9>exZpk@DFO+p@; zs$3P(EO?|62E@85)5^>as{x=aOkl(1fPjQ62KU4SKtP}2fEQW;-;o@x9Hw%T|Lh|+#N zU)&#j4)m4R-@hbwGA_+*)tRsxlDK`Ms?vgwJ#fxPTuFd0K*Ky{C8vdqu(S#dkiUrS zS2EDu6G1=KS+k+R0sn={)`2=n1L4-nshWy`ZO4nBXBU>1ndCtcGDlcfIRr{7l9C?U z(tcMOi7Lnp3#+ynQ6n6Rf6LTrMw{Kl0ip0?i}1x<{nYuaRa9jJqAGFDFvs@TA;ES` zkWH-hf?c-wfEcwJwLGHPQz=@E(=}11rJ2pT9lN7gDY?)DAy6zW{n#MX`e@pVow|@9 zA!QCwi(bU@9((KDm>&9tffjtJhZSW1HB9Naso)g156I`X4e9V-UMAyVCCpPFy_k=t zdJO~eEr1K-w|U!+JM4m)!F7cDc%MPjx7sGu`DhM>bH5PbZbgK#UE1F{8BM*`(G3t? zd5Mv2#|`^b4rgYgXqH_O$M)|dvLGB7w=@szA6GecYqawJ^6ii9sn$LDfOMw7X#M>8 zZ`(5%7Uh`|()K^AG&~$p52NzQ5(j(a)*?_7hinf3T+Fzogchob`XHI05;z!yA)RvC zD^<@wL7|@LKn*W1JO;{?G}^$&SL+q;Z7dwPnA5A$ee=;ess=gZK#}UelA;@yIwC6W zjIXZgw-@g zJjwh6;#Bo$;2y}NX?0G-U$I?0pS_FvxMfCFMejfy>P*j|iSm-&b&cGr>7F;!Atjl*8qJ2OprdFY$RT$@v@;|oGcTFI&5`W6-1-+T3p;;3bd{QI%FzcU z!oXX0Cr(ZfKy{_!xJ&U8GfI`sRBi{jLlPyE`T4 z96xqT)X0eOBD71pfT~p%nie-^W%`=QZVpL?_hI}fSN+ix4hK$3yO06Onv0QpdO-o< zr+97`ANOy+s#?xqChPT;DJPhG0xCWi(7rdD!91=M-VUu~VC;!#85xK(sWUXLFaj$P z&%k2c1F^MXViU<9%zzJ~(9o;Qkt-K4V8s9nWUnVD9+#|Sjmrqer(lGJi--*@e3kSB zW4Qw(-GcTSgDZ|I^P5_cutz>-Dp%+;QHx(x@0lejBp$XKVXt#vU4$`p_x7W5)d^71 zbj>=(OB5A{Rv%Rgt6NO95=Xu$#2iH7E^2fo1(N7n)-f#UM{do`T#moF^03t3C>bCO zKCGl3TP-R!lhbT}gS{@uePMRYDk+oJ7yaIG*^Ai5p}5~yuqryNdW(=;nZ~2)im-so zOddK?gYF{LO*lfL@Lgoi6>+Vz1)!?vwM;;GV_(J*JikSlL?vi|mE>sMfr{0yO%RC3 zDHPtA-@1geXN=A|i{8X3~!qkV{F1gc)PyrOcp zVSEhp{E?3zAd=fP=a6WQsn-ZwSn5S`203RP2{D!`kdIcKsK9M+*J=#Qu-_qDR^LD@ zU4@v1p-_=F_6>G;$sQJ?#U%F?U<6Z7-yj4uR4Y>GTB|=}@a=Az{$<17UIE{s*5^$paP#S)~_yQgt0^_eS1`#uNxt8)!9z@>7@Mw@jeZwlfxY-oauv_6dXzCy|69L{#**+|9VGz^r=sTf zT**U~BJ{V8N(_ib8!yKT_9}Ti7deSr3 z4AxRzz)6`n$L(N@X!=S)_$h*Qo_8(UV?NF%`fDGs)&ezPsPUoD@|V5BWmB!%^ul@7 z>CVKpc7!>Fe62UxiVh~@QoTi`_vZfMfx??T} zyv{$xK$F#ro-EDXTQaK-7gDOtjn~ZvrFLJ**o1D#&x(5vkaV{*&stTfgZ$7gFz;kQ#e}e%lls6MBEa#TA zw?}z+@7_>XYo7eexo-VPzw(2)utx$o1Vo2T(E%PA{z`Le!cY%EF=lbmmqHnvlRy`q zAcMu4qLW=41{(i53&oyd5ElLtG%=G5q;p_n11Y&~0YaenkLz%w`p#kg5Nkx5?zM+j zR=VF^+bir3yWt5sj{bl^%RNBpVI>8~Jc*dbKIXe2|GJ$cA z9{wnC;d@+c(2R#-J7L)b5m5fwg4tn-$mFKUW_ zie5f+snP}6cZ->qWL8eHG0c`{NnvH3FS$QN{FST-pto)MVK-5sxy1UusvrivI`Ix5 z3(vKd4ZFz#;dcPVCYjU~x=3WZ6+e=qFNv_;-CFiFcGd0R&F|e_iY7iT(MF`{k>QEb z7+}$;uz+4;y@>%aN0nre7U*{cgB7+f(JNy1X~JYTUpE_;)hvs3zQFBZSG4#s$WL4} z$j3{?B7dNEW%y!pbVb#;attHR0K+n6ETPbOzK`n}pmUfhp;kNf43bk=Or zR}(+4;D3g;UJmZx#P+Xy9T=wuE;B!Ex>suZ<2~KZdM4i+@H$&uQq{9%4sQ=$KhH!m zM$V0Nbv&`9l=D75u0DhR@2j&fyFj4TS%8V5rQsjDKo=Kq%T`ceVBFZhm(Kr9_KWz} zlIx+WwDmkIY8&E=(+ALMI$0eyv_r^DWooG;cDGd^o@sJ|ihS0maZwNKx%DsLU5&bl z;7y107F44UO@K^N^}tfdJG3hiu`0|#@o^~ZDPW)gp_lvf@;Z^j<(zNOH-19mP22fm z%`(@ZZXWyzb`v+JfJbp`ycoDX{h!Vfb4zI=$8%Hn$C^E?kkd}tn+0>UjxU@+9` zS?X#oKa4x<`kVrWPEb3@jn($2)s-FX?x5Ny8Ox^k9o%ZuGQf3~p(RVH1ye4cgi2$_ zsJ|SnGag#i%9b|WKs;WOB&(-O<&TLORtg~W^Gqe4U;~Xj+=e=^qk7m9PA-QX>*oO{ zPzI+%B<{~*#i~xHP^gSp(ut#U-yS{1xC15F=V|<{!Q8V`Ix)zD0zNh}hr&hzi&R2S zJZSI0Mn46hg-5558Tfbvt@L?OewmS=c9 z@7>B@?A^w$2aZJGKLH+g#!kENX=sdBX(N2sIun;YuePW0vw1T-&(BUVHl069E&D|6Aw0M}11&jfZGTXzHOn%x+eRt&+0 z|D00AnLX6kZc9(88&}6&rMD?mzpu7M6+)2-UaaqGF^XO9hzW-ZCn||NXZt2D3gz%f zc5lWZ^3F$`M|KBK5LZH6k}-#fEDAl;(e#yPa^mirGDnMQCkzL6stMOXoJJ85n4e;P z_$XURr<@rSIPuXqYe8rTp6`k)B>FovZUi-?DNg{OFojzfqN^;1EWz*4(>x*hTPPjw z@lu6p<;Xc{vR&TUYY7~kn6h=t$_3z#vQz8vM#;m-hFyK9^gXs^()^hiYM zTg=Ds;bOKCP_UZsN9Xoj+Z?1V`P=F7qSwYAQPXV z?)AW&-2Vw4y%Cs4?AKS*z}4_cry2^jj3+%0satlMo!Gvx(0uwai;sOC!}hg&15pU^fyC zny@RDXC5?wXLCs#)LDXC^R~*Z!rmM%4-vhEm&?WMeP&X;@PwFty{~@=j&Ei6T8LA7 z9uI?ZL&iTQ>dpf^+_w-HiN*#Oega9sCez{x-mc(zKCPNrLanP%8g~>77VbV8?lr-^ zOoGHkI$ue`k!Aw1IBaqdWf0Xns;eLz6xKsfr_25J_ltK1wN9 z-gAwV;_*eejUd-yTXH9RAT0%Is*S z+c)KCkz3eNO(u^TKsx)mm%Y~=Na^XkmVLjngWIr{$v{G52|0HM|IeLW*DBQ0w=iH} z%6MR4FGm+ZuP1vGT{}Yqd4Qd%m9hQHmY}8TV8n(P@*>eGV(y%M)7h$U)=}%;Pn`K_ zx-`Sc^s79@pQsZwlFEphK$x4?01_pOnF{?t9Z2{UYs?iUm=OyZA!@F31@^V>;Qs!D zeQU4V3)}P1^}*ZITR^qp@ft=Wy=>!R8eQPTeOKT1#HrDJ@w&_X z;US`zjR%i5>&g7YtvY%VY3p|4^c5Gco2Lt>#>z+{qVl#hU0>Jz zdDp44%bjzNQ`61f?uql9)e?7G1})pXO7P*<<;BhUboOl{p>uOf3lHy-mbMlCvbMIS zReAYTTWi_E<#O%n-1hSG`FZ9OR@u}3vDf`I{u6SU=hKtZm&b?siFo#~E+iJb5gau2!M8uRm%`aG~GEECQ{&a6b*&AdH%-Fa5Za zZ0Oo(ftkJ-ZYWt4eg_9@A-`imZmcZv4R_p9zqeUBl<}1~rBLi#L8&XMhk!Rz?;zu8 ze|XTE{_CbRqP(?x;)^$DNvNS{Jn&Aan*p{=ekwhf60*~R$tl929e&t4vnfSF76tdr z4=qcEs@R*bQ0GZv-(FKLtVo(bj|LI05N(Tc9JoP*cS6;qUKL@>9`+Es1urFX2$S%; zTB2~z4Hu6hj+=sA&$1sdu)`&O1w%PhpCHQQpdgi>Rx~4xW88s@J4_H1E!;XOo>+}@ z>XUEy>7)S1Sa@@A7X3+H4=L+-Qo+xpWg@sjD6C(yF1t!UloH)E43fm7rt*D=#M@P6 zqL}y|7OkG3)13^04t~!qGy3x-|2$Q`LUp%q-K|%zQe>R+uBCRcD3X~5>VPpnG}yjb zejT?3Luy51S<`T9r$V{QkX4@;KmIftRX>Bx%<)DgC)E_ zGKtZ$D63=}3k@r@kmvOdVFh&p4^rXKKr~aNlu1V=fzwjl$SB4# zn*>scDI4LP)y%K&5%|XsH><^!@(&;L;4a~^Stm$7Kb&4d_^!LiBeDuq#tnoq7y^?# z(6YYgOq#_M{RqL_d^1X}w?i!33g`*4tB?W&NwX`324AaA`InX0bh;{qo%gG6z_;Jh z)__m3ST~oS*+56=@>RH6HGS{XhXq`I$EDQ7@WbsC{Z!9Li2)%g>hfqoO{$?diU89K zc9f#F0tIW6wOu-#Gg$hT#H!o4nD^bMfDWYA@y215kB=5mi1t-LnO zN6Q7RGINJK7pIR88I<@%gw33{*cO4Df3Ow@3L@4{%rGM+8n;Kl| zZuro_Da%;wU>;;EA_pmz5YZcMh&-_Va0176J8`>eb9Vx8ep|naC1xUUZ&m{BuM)r+ zknU{?Q1Znsdfjm%w}Wg8ZsIcxyi!>eMIC*`O~siimh+Q^kTb`F2EGQ;rV8BqvekHp zS#wnY)`v-4GoCo@q6Mk7hFn!g6Nz%FRm3b+hFR~_d)N<~Sv_i}qmkHMdo{SP)WtuR zgC=L4hb)d*v;$R^R0zLQ*HP}{6@R2(d%uuDdRRz={%UkM%&6TM18Gt#&KJv;oOf4a zjI$DRSq(Zlqy^vr?l2TI_OG&WHkM%XtnY2B38>`SO%#XpcL-;g@o);PJ$GY!6RRY0~Sqgh>_v$Nizr~oA~x}#6hWgFeXu;F|26ckAv9eZ1Y{atQMCZ zX1@hcfpVDN6*c!E=X_HE01r;uo%S=-Qr|t`%Vcj35^ZPe1YN(7kY(UD8hE|_TJ_w% zI|FVEVm>r$+vMDVtY!xbQo6Ja8*(lM#)IC6!jid=@2w(8>bxE(EzwDUN+Iard zTpBxn_Xn9VgVG<-VLC9RQjG7oa3#}WV_{8v6l*7|5*z3su^3uN+09)-@Ga%cTq~+a za`&R#4HC0R$lDuWa@P!{bj6@p0- z)vB`Ryx08MG;kK7)ce?0t(#PM=v_u*kX%JHR#sMT=eOs`*V3i2R7mVmiR22KN^E=`Gwc?NVDR8(%A)pR=IQmtq(U zwP_|k1+`CtK$Xe%AyHZb!CKMH=mIr}AqDWS#WBti_WYCs>_@0Zc0mFC_6eP0Ir(r6 zM*_rg3aQ)0KENX7!MBHk%0p0Z*YlLtRNP%{_gd_Q1po^Nhsu-@Vlbeq6r#IxbCRR`yxDmHi_ zN{Fqdx5KGr^4?|&QLG+0ojCL)at2Y)7{xW)bd5B-5Sufby)`-FL6Py4x1qSHHuK@2 zlt;biVGqy-jwYTfLpZ&zppb*Tpv|=cl~W2;Bt2d-rMFZ#M4h5BN|of)o_onln`Eh6 z7cYMK95iB7D~>55bp;vh6q&kc^x;~!<^C?_4GA6FsToIF+k#c1w&VWDBicr001A_R zwZ`Ly=rbFg=dX2I{jBrd z2Qg@|o6cq5vW|JS7BKEDWfmO;rnuBSTWhn{V_USyFXFGI%C11H@YRMhcwH*j{VP09 zP;rF$ai;0`qVxm&M<#u@4AI_7^|LjLobbb+=TE8I$){m0_J=vg6!qykJ9oZ$MV5(* zJ?{YzWnAnQMa!+GmOw>A%sJn(!&}Jw!VFIS%ekY(wi>24(%jp<(d&M26_{^Q_hd2g zv}xi4(uwk|5$hlIjltS39==)!+F;=%1oV09I|cd`9LHbSeOKF?Kl)^dQ+CDWl5}yn z0-e2H8nMh<$A8^^xo{G9CKvCyy_XsY$yke86^UQ5#b&+_Q>!nP6FJXAp^r~GY1=O_ou#Q3{k}K`F}5&P+`lzv;CbE*HAXZkCgQ^6Bh&Q@5G@;{MX5b# z0#mW8>^7))hR%uqU}@Vd*g9CF$E^Q4799tVS#glEE7K5po++Ga+g?4OofD=+UMUoX z{M^t0x7s>-3ifNkyJ5bQFpa93W+cEibh%NqG;K78FIkeZYMqM@^IoGNr+gG^DmOY# zBS!%> zBU2yq4ODGUfMrC55H@88pIe89E^y$CCbY9wW@Ru;jhcGrt1-+v)%1CcZmn(JQDX5>$ zjT8)$@VS)#AhL#_X=CfcYF9I7ifK!rC%QF%(GWOvJ|7Pn%fGr zri!|J)+J%-8+kEDOQ$}2R{{xQ$m^3J_rNEHWTiV@m}1!`F3oA43+OuNz}zIlC(^hI za_D_)NtTa3m(@2y72I%-CKOo$Sb_^o^K!TaaLJONhg1fC*8NPHt|3XWtp}VD0I4Uk zR72i08NpVSq~`XU6JdS`1srs>PB;iZ5=Z?=`{JLeLpUE^PC>YPPMYkP%u=nvMCBTP z`?1`m5*Gr8$oe$irc$O7@XuA+O{%wH( zZr{08RLp6*mRy8HRqkp|;1y>35n|PoL>)sEf5Sc&Zq;~~RJ%1qwCMFKdde!{&r-QF zeSJ%XuNNq+9ib#|RYTFmJv|rg5urVx-!;6XGD8Cz!7;aM}dOd34S5!b1^Tbjv?_oa4YXcE#RkOC(r{=KD zhZSp8r;3%$0~Ppju>)38k%9+(d=>=-{fuIPV}U-iQ?deK0`q9wBB|HLoUe#sBg~}% z&92MNRva#w8Ut0BKPh2Q(T`aPi7c-Orf6Hq_BAART)9rH!J1+ai*#XB|5ib>2E7^i z2tVMvTvYK?J}TvjkDU1>YSHRf=xfxC{!16iQW!q0wCuRrPC$OHA~$FwTNXKBVq z(UZ}X#>+Ec0=mfJtM%u2fgXeln!Fg!Q!@qc&&xiP!9}kr9I?NWm}tn*$hM1i%!ASR zgF_VXlF8R8r33)N#J{m)D2abl!w?e>8|*~KOQWsrRKf#K?4jXqxJIqN`k)cBgTbo} zx+#dG+B$>I|I|=h9ddranE8~dj@T2JnWy|>`LoF_@L4T@>ns_ zY;WRc_(uI$Mj1X1{ceI;KWE-)a5*wJnsb<9*YUwrEDtY>Krwz|v}KWr`MK5U zcI9?MIZ}@!r`Hzis^%sRyEoI*&i1Kiv+F$ZxXq10Qo0B+Kc7n`-%#r5HgT}}TF*NN z$p)e!5b|CikAgPi!R2B9nzW58)%|2TDI&*6pbu{+eH?JMI=d;{7D=)5NFYGy@Z7T= z`T6^g^u8?SDoXEe#he?RHB^%U*EcP}KCi^G(UHMZ2nmsi=;Pzqi3xr3kfUJ`C9&#* zU{Ztx2zh4S@iUcqJ?Xg9eTM!n9=-rpjTdZ(@yYH=l~7RNmBeQQ;%zTimnV7xWCrka zaM&Lk>s+zCJXp)mhc`=$KLRt!Y6xiore85Pj7@flBl^S>uXca%4wE`Py$j7|q7)$P znY5T{?uO<|qo5$eH=@F?kMK6#+-tVq-2CVojO1y! z=^?|ZJ83UQGB72Z8q}7pi~DXNP|{8=h$6_88Xkm?DI_wZP(2%z`2k1;i_NLB{zcasm~;dv)LOOZb~g~QP258phKi8!V%@Mw>k*4_ZbyKCq~MWn z=>*AO$w`vDgfk{0oQ@)715%}h2WiNMXuvAP{4@(n@>k|+fm33~My{@am+U0{Jv`$? zp&Ppw?}yL;9yk|gBpj^#fwW{PicN!{B3bTdo}Pa-GK0AoLn=tAkU$xQpjDAFJN5}8 zt5}FeuriolwDK1;qYph6FHe!H(I||5&7;3mB>Fdpl|NzPh=q;`eoSKNSbEQo8N3sq zgX>j4yfrB=tD@(FA2-t!7x zHOoaTS64J1#adRtdWo(=3l_@KmeP<}tHVRp@G32~7G6z4$?kAupoY$3fQTlXbsitL%xpv&y26NJ$G2(nZ0h z^HtN{nclsh@7Bk-Ia=aqO?SCb#y~ELSktjx1({EoTr7h59K8BOzN{@)GFAg;LcN)I zGLj#AmMDYVnxJgi{hM#Ct&8@DSGbe2`A3+jHWZ@M9W0{I!hGGcXWEc11)-ogM(iAT z29KS@0!vDbXb=Q7{FCHE7B)57Y^js`G^FrDNWKo;ewvur?(B#jQ?D5m;q)WP+J7cy z?Cai0aN%Lm7O^{{DR%nbvHWX0^S{B_Z!v3k0D`d*1Sb~gCuqI+H`z<$$-ln({z6yM ztcxDW|Jr})^Uv0V^aMGv4GS@X`6=!%QN0PlG_XS#ef8xw1hyY?u&rNNL$zEFiOesK zum{meOhT7`r4&Qjk*sJrMs;T{car`PR9o)~gNG&|Qc-MeU1!a1GMw0d87<&=n$ynE zXH4;f9ft*tAZsL@HAmJyp)gi9)%WNHUD>=`=t<)YJ!`a_77ws72?ny~W8Aqqwi}e6 z+d&z00wQas{g6RI`EOM_GHDr!`Ug+1i+j1!M_cU&<~8N02d8yd%{y&8i9VLXwXYFJ3T203}v&U}%O;d648L7@Zj**h#% zl=JYm?fIeQjS!W22#LVt60k)3QUzSfHFvSZ2tPLQECVgcPrlO`nnfM5YV73Bj9wMG z-KPk5szj9HqfJB=I90_NIL?rSM-AZ^{jf8V9j#HjwOb1pZHxGdT(TMhE6&Fb1!@V( z4CM>Abz+~-N*}Gz>q=6Bk-`!PqvcxxdtcT@%SVaP}HUvF>^6dnD7Y;UL09rw65e+yf-lcDFScCvw=ANs@)B3 zcHN!c8boY;A`>E3g6fvU&47)Cz^ls)!gP@Xi9V#yl)a?n@9Ra`%k>{!r~lOVXW!`y@o#-(|H<}O7wVr7{n=Le zH+KLir2nn-Urm;Ovj5pB_&56*sABxh{_noQ|I6r?rnleMi_s>aB_>e0`M((ci4u_Y zKkCz;6aBN0z3BOk(!+nU{g1Ntr<^}G(EgV5^TU7NPWw~Bp9>~`OTeT0E#cqGC;$Jp zLxGC(zmJ;ysqxP#yh_McshFTB6KR{KAA|L$p&5r6$FS&>1F!q6bP296iL{|BR|EKdLc diff --git a/app/__pycache__/app.cpython-312.pyc b/app/__pycache__/app.cpython-312.pyc index 234a9f46cad782738cc7b453f3bb75c52196acc9..c0ed153bb9a4d26b4b2b33534d9208f3e72e9af8 100644 GIT binary patch delta 22 ccmcb|e~+K{G%qg~0}vcc*_ZixBkx6a08$zUqyPW_ delta 22 ccmcb|e~+K{G%qg~0}wn&-J7{@Bkx6a08&v0mH+?% diff --git a/app/__pycache__/config.cpython-312.pyc b/app/__pycache__/config.cpython-312.pyc index d650bd0849696c17ba82ca90c6bbc007cd5aae28..6b0f230d57a54eec28ae094546f4d9cf1de94a9f 100644 GIT binary patch delta 20 acmZn?XcFK)&CAQh00eob_ig0<&JF-HU-UW^T diff --git a/app/blueprints/__pycache__/brand.cpython-312.pyc b/app/blueprints/__pycache__/brand.cpython-312.pyc index 122490f3a06009643de6f09ed8e06659ed3f7c90..05adae356dc465925a9b554b48c85edbdd20ec3a 100644 GIT binary patch delta 4959 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 delta 4498 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 diff --git a/app/blueprints/brand.py b/app/blueprints/brand.py index 01924dc..1bf2a65 100644 --- a/app/blueprints/brand.py +++ b/app/blueprints/brand.py @@ -57,14 +57,23 @@ def _push_task_event(task_id, event): def _expand_folder_xlsx(folder_path): - """返回文件夹下所有 .xlsx 文件的绝对路径列表""" + """返回文件夹下所有 .xlsx 文件的绝对路径和相对路径列表(递归)""" if not folder_path or not os.path.isdir(folder_path): return [] - paths = [] - for name in os.listdir(folder_path): - if name.endswith('.xlsx') or name.endswith('.XLSX'): - paths.append(os.path.normpath(os.path.join(folder_path, name))) - return paths + items = [] + root = os.path.normpath(folder_path) + for current_root, _, filenames in os.walk(root): + for name in filenames: + if not (name.endswith('.xlsx') or name.endswith('.XLSX')): + continue + absolute_path = os.path.normpath(os.path.join(current_root, name)) + relative_path = os.path.relpath(absolute_path, root).replace('\\', '/') + items.append({ + 'absolutePath': absolute_path, + 'relativePath': relative_path, + }) + items.sort(key=lambda item: item['relativePath']) + return items def _upload_local_xlsx_to_oss(local_path, user_id): @@ -478,8 +487,8 @@ def api_brand_expand_folder(): folder = (data.get('folder') or '').strip() if not folder: return jsonify({'success': False, 'error': '请提供 folder 路径'}), 400 - paths = _expand_folder_xlsx(folder) - return jsonify({'success': True, 'paths': paths}) + items = _expand_folder_xlsx(folder) + return jsonify({'success': True, 'items': items}) except Exception as e: return jsonify({'success': False, 'error': str(e)}), 500 diff --git a/app/main.py b/app/main.py index 517c4ac..96bac03 100644 --- a/app/main.py +++ b/app/main.py @@ -145,7 +145,7 @@ class WindowAPI: ) return result[0] if result else '' - def upload_file_to_java(self, file_path): + def upload_file_to_java(self, file_path, relative_path=None): """按本地路径读取文件并上传到 Java 后端临时目录。""" if not file_path or not str(file_path).strip(): return {'success': False, 'error': '文件路径为空'} @@ -157,6 +157,7 @@ class WindowAPI: response = requests.post( f"{JAVA_API_BASE}/api/files/upload", files={'file': (os.path.basename(normalized_path), file_obj)}, + data={'relativePath': str(relative_path).strip()} if relative_path else None, timeout=120, ) response.raise_for_status() diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/convert/model/dto/ConvertRunRequest.java b/backend-java/src/main/java/com/nanri/aiimage/modules/convert/model/dto/ConvertRunRequest.java index 5aecfa2..bad0cbc 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/convert/model/dto/ConvertRunRequest.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/convert/model/dto/ConvertRunRequest.java @@ -20,4 +20,7 @@ public class ConvertRunRequest { @NotBlank(message = "请选择模板") @Schema(description = "模板编码,例如 uk_offer") private String templateId; + + @Schema(description = "文件夹上传场景下的总压缩包名称(不含扩展名)") + private String archiveName; } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/convert/model/dto/UploadedSourceFileDto.java b/backend-java/src/main/java/com/nanri/aiimage/modules/convert/model/dto/UploadedSourceFileDto.java index f4205c6..de0a6a1 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/convert/model/dto/UploadedSourceFileDto.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/convert/model/dto/UploadedSourceFileDto.java @@ -14,4 +14,7 @@ public class UploadedSourceFileDto { @Schema(description = "原始文件名") private String originalFilename; + + @Schema(description = "相对上传目录路径") + private String relativePath; } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/convert/service/ConvertRunService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/convert/service/ConvertRunService.java index 64e3d97..50445f8 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/convert/service/ConvertRunService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/convert/service/ConvertRunService.java @@ -3,6 +3,7 @@ package com.nanri.aiimage.modules.convert.service; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.nanri.aiimage.common.exception.BusinessException; @@ -22,24 +23,38 @@ import org.apache.poi.ss.usermodel.DataFormatter; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; import org.springframework.stereotype.Service; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; - -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; @Service @RequiredArgsConstructor public class ConvertRunService { + private static final String MODULE_TYPE = "CONVERT"; + private static final String TEMPLATE_CODE_FIVE_COUNTRIES = "uk_offer"; + private static final List FIVE_COUNTRY_OUTPUT_FILES = List.of( + "\u82f1\u56fd.txt", + "\u6cd5\u56fd.txt", + "\u5fb7\u56fd.txt", + "\u897f\u73ed\u7259.txt", + "\u610f\u5927\u5229.txt" + ); + private final FileTaskMapper fileTaskMapper; private final FileResultMapper fileResultMapper; private final StorageProperties storageProperties; @@ -51,8 +66,8 @@ public class ConvertRunService { ConvertTemplateEntity template = convertTemplateService.getByCode(request.getTemplateId()); FileTaskEntity task = new FileTaskEntity(); - task.setTaskNo("CONVERT-" + IdUtil.getSnowflakeNextIdStr()); - task.setModuleType("CONVERT"); + task.setTaskNo(MODULE_TYPE + "-" + IdUtil.getSnowflakeNextIdStr()); + task.setModuleType(MODULE_TYPE); task.setTaskMode("IMMEDIATE"); task.setStatus("SUCCESS"); task.setSourceFileCount(request.getFiles().size()); @@ -63,63 +78,107 @@ public class ConvertRunService { task.setFinishedAt(LocalDateTime.now()); fileTaskMapper.insert(task); + boolean folderMode = request.getArchiveName() != null && !request.getArchiveName().isBlank(); List items = new ArrayList<>(); int successCount = 0; int failedCount = 0; + List archiveEntries = new ArrayList<>(); for (UploadedSourceFileDto sourceFile : request.getFiles()) { - ConvertResultItemVo item = new ConvertResultItemVo(); - item.setSourceFilename(sourceFile.getOriginalFilename()); try { File inputFile = findLocalSourceFile(sourceFile.getFileKey()); if (inputFile == null || !inputFile.exists()) { - throw new BusinessException("上传文件不存在,请重新上传"); + throw new BusinessException("Uploaded source file does not exist, please upload again."); } - String inputName = sourceFile.getOriginalFilename() == null ? inputFile.getName() : sourceFile.getOriginalFilename(); - String outputFilename = template.getOutputFilename() == null || template.getOutputFilename().isBlank() - ? "结果.txt" - : template.getOutputFilename(); - File outputDir = FileUtil.mkdir(FileUtil.file(storageProperties.getLocalTempDir(), "convert-result")); - File outputFile = buildNamedOutputFile(outputDir, outputFilename); + String inputName = sourceFile.getOriginalFilename() == null + ? inputFile.getName() + : sourceFile.getOriginalFilename(); + List generatedFiles = generateOutputFiles(inputFile, template); + if (folderMode) { + for (GeneratedConvertFile generatedFile : generatedFiles) { + archiveEntries.add(new ConvertArchiveEntry(sourceFile.getRelativePath(), inputName, generatedFile)); + } + successCount++; + continue; + } - writeTxtByLegacyRules(inputFile, outputFile, template); + boolean useZipPackage = request.getFiles().size() == 1 && generatedFiles.size() > 1; + File packagedResultFile = useZipPackage + ? packageGeneratedFilesAsZip(inputName, generatedFiles) + : generatedFiles.getFirst().file(); + String packagedFilename = useZipPackage + ? FileUtil.mainName(inputName) + ".zip" + : generatedFiles.getFirst().filename(); + String contentType = useZipPackage ? "application/zip" : "text/plain"; - String ossObjectKey = ossStorageService.uploadResultFile(outputFile, "CONVERT"); + String ossObjectKey = ossStorageService.uploadResultFile(packagedResultFile, MODULE_TYPE); String downloadUrl = ossStorageService.generateDownloadUrl(ossObjectKey); + ConvertResultItemVo item = new ConvertResultItemVo(); + item.setSourceFilename(inputName); item.setSuccess(true); - item.setOutputFilename(outputFilename); + item.setOutputFilename(packagedFilename); item.setDownloadUrl(downloadUrl); - successCount++; FileResultEntity resultEntity = new FileResultEntity(); resultEntity.setTaskId(task.getId()); - resultEntity.setModuleType("CONVERT"); + resultEntity.setModuleType(MODULE_TYPE); resultEntity.setSourceFilename(inputName); - resultEntity.setResultFilename(outputFilename); + resultEntity.setResultFilename(packagedFilename); resultEntity.setResultFileUrl(downloadUrl); - resultEntity.setResultFileSize(outputFile.length()); - resultEntity.setResultContentType("text/plain"); + resultEntity.setResultFileSize(packagedResultFile.length()); + resultEntity.setResultContentType(contentType); resultEntity.setSuccess(1); resultEntity.setCreatedAt(LocalDateTime.now()); fileResultMapper.insert(resultEntity); item.setResultId(resultEntity.getId()); + + items.add(item); + successCount++; } catch (Exception ex) { + ConvertResultItemVo item = new ConvertResultItemVo(); + item.setSourceFilename(sourceFile.getOriginalFilename()); item.setSuccess(false); item.setError(ex.getMessage()); + items.add(item); failedCount++; FileResultEntity resultEntity = new FileResultEntity(); resultEntity.setTaskId(task.getId()); - resultEntity.setModuleType("CONVERT"); + resultEntity.setModuleType(MODULE_TYPE); resultEntity.setSourceFilename(sourceFile.getOriginalFilename()); resultEntity.setSuccess(0); resultEntity.setErrorMessage(ex.getMessage()); resultEntity.setCreatedAt(LocalDateTime.now()); fileResultMapper.insert(resultEntity); } - items.add(item); + } + + if (folderMode && !archiveEntries.isEmpty()) { + File zipFile = packageFolderConvertResultsAsZip(request.getArchiveName(), archiveEntries); + String ossObjectKey = ossStorageService.uploadResultFile(zipFile, MODULE_TYPE); + String downloadUrl = ossStorageService.generateDownloadUrl(ossObjectKey); + + ConvertResultItemVo item = new ConvertResultItemVo(); + item.setSourceFilename(request.getArchiveName()); + item.setOutputFilename(zipFile.getName()); + item.setSuccess(true); + item.setDownloadUrl(downloadUrl); + + FileResultEntity resultEntity = new FileResultEntity(); + resultEntity.setTaskId(task.getId()); + resultEntity.setModuleType(MODULE_TYPE); + resultEntity.setSourceFilename(request.getArchiveName()); + resultEntity.setResultFilename(zipFile.getName()); + resultEntity.setResultFileUrl(downloadUrl); + resultEntity.setResultFileSize(zipFile.length()); + resultEntity.setResultContentType("application/zip"); + resultEntity.setSuccess(1); + resultEntity.setCreatedAt(LocalDateTime.now()); + fileResultMapper.insert(resultEntity); + item.setResultId(resultEntity.getId()); + items.add(0, item); } task.setSuccessFileCount(successCount); @@ -138,18 +197,18 @@ public class ConvertRunService { public List listHistory() { return fileResultMapper.selectList(new LambdaQueryWrapper() - .eq(FileResultEntity::getModuleType, "CONVERT") - .eq(FileResultEntity::getSuccess, 1) + .eq(FileResultEntity::getModuleType, MODULE_TYPE) .orderByDesc(FileResultEntity::getCreatedAt) - .last("limit 50")) + .last("limit 200")) .stream() .map(entity -> { ConvertResultItemVo vo = new ConvertResultItemVo(); vo.setResultId(entity.getId()); - vo.setSourceFilename(entity.getCreatedAt() == null ? "" : entity.getCreatedAt().toLocalDate().toString().replace("-", "")); + vo.setSourceFilename(entity.getSourceFilename()); vo.setOutputFilename(entity.getResultFilename()); - vo.setDownloadUrl(entity.getResultFileUrl()); - vo.setSuccess(true); + vo.setDownloadUrl(entity.getSuccess() != null && entity.getSuccess() == 1 ? entity.getResultFileUrl() : null); + vo.setSuccess(entity.getSuccess() != null && entity.getSuccess() == 1); + vo.setError(entity.getErrorMessage()); return vo; }) .toList(); @@ -157,41 +216,71 @@ public class ConvertRunService { public void deleteHistory(Long resultId) { FileResultEntity entity = fileResultMapper.selectById(resultId); - if (entity == null || !"CONVERT".equals(entity.getModuleType())) { - throw new BusinessException("记录不存在"); + if (entity == null || !MODULE_TYPE.equals(entity.getModuleType())) { + throw new BusinessException("Convert result record does not exist."); } fileResultMapper.deleteById(resultId); } public File getResultFile(Long resultId) { FileResultEntity entity = fileResultMapper.selectById(resultId); - if (entity == null || entity.getResultFileUrl() == null || !"CONVERT".equals(entity.getModuleType())) { - throw new BusinessException("结果文件不存在"); + if (entity == null || entity.getResultFileUrl() == null || !MODULE_TYPE.equals(entity.getModuleType())) { + throw new BusinessException("Convert result file does not exist."); } + File file = new File(entity.getResultFileUrl()); if (!file.exists()) { - throw new BusinessException("结果文件不存在"); + throw new BusinessException("Convert result file does not exist."); } return file; } - private void writeTxtByLegacyRules(File inputFile, File outputFile, ConvertTemplateEntity templateEntity) throws IOException { + private List generateOutputFiles(File inputFile, ConvertTemplateEntity templateEntity) throws IOException { + List outputFilenames = resolveOutputFilenames(templateEntity); + List rows = buildTxtRows(inputFile, templateEntity); + File outputDir = FileUtil.mkdir(FileUtil.file(storageProperties.getLocalTempDir(), "convert-result")); + + List generatedFiles = new ArrayList<>(); + for (String outputFilename : outputFilenames) { + File outputFile = buildNamedOutputFile(outputDir, outputFilename); + FileUtil.writeLines(rows, outputFile, StandardCharsets.UTF_8); + generatedFiles.add(new GeneratedConvertFile(outputFilename, outputFile)); + } + return generatedFiles; + } + + private List resolveOutputFilenames(ConvertTemplateEntity templateEntity) { + if (TEMPLATE_CODE_FIVE_COUNTRIES.equals(templateEntity.getTemplateCode())) { + return FIVE_COUNTRY_OUTPUT_FILES; + } + + String outputFilename = templateEntity.getOutputFilename(); + if (outputFilename == null || outputFilename.isBlank()) { + return List.of("result.txt"); + } + return List.of(outputFilename); + } + + private List buildTxtRows(File inputFile, ConvertTemplateEntity templateEntity) throws IOException { List requiredColumns = readStringList(templateEntity.getRequiredSourceColumnsJson()); List headerColumns = readStringList(templateEntity.getHeaderColumnsJson()); Map fieldMapping = readStringMap(templateEntity.getFieldMappingJson()); Map defaults = readStringMap(templateEntity.getDefaultsJson()); List preambleLines = readStringList(templateEntity.getPreambleLinesJson()); - int blankHeaderRowsAfterSchema = templateEntity.getBlankHeaderRowsAfterSchema() == null ? 0 : templateEntity.getBlankHeaderRowsAfterSchema(); + int blankHeaderRowsAfterSchema = templateEntity.getBlankHeaderRowsAfterSchema() == null + ? 0 + : templateEntity.getBlankHeaderRowsAfterSchema(); DataFormatter formatter = new DataFormatter(); - try (FileInputStream fis = new FileInputStream(inputFile); Workbook workbook = org.apache.poi.ss.usermodel.WorkbookFactory.create(fis)) { + try (FileInputStream fis = new FileInputStream(inputFile); + Workbook workbook = WorkbookFactory.create(fis)) { Sheet sheet = workbook.getSheetAt(0); Row headerRow = sheet.getRow(0); if (headerRow == null) { - throw new BusinessException("Excel 表头为空"); + throw new BusinessException("Excel header row is empty."); } - Map headerMap = new HashMap<>(); + Map headerMap = new LinkedHashMap<>(); for (int i = 0; i < headerRow.getLastCellNum(); i++) { String value = normalizeCellText(formatter.formatCellValue(headerRow.getCell(i))); if (!value.isBlank() && !headerMap.containsKey(value)) { @@ -199,9 +288,11 @@ public class ConvertRunService { } } - List missing = requiredColumns.stream().filter(column -> !headerMap.containsKey(column)).toList(); + List missing = requiredColumns.stream() + .filter(column -> !headerMap.containsKey(column)) + .toList(); if (!missing.isEmpty()) { - throw new BusinessException("缺少列:" + String.join("、", missing)); + throw new BusinessException("Missing required columns: " + String.join(", ", missing)); } List rows = new ArrayList<>(); @@ -219,6 +310,7 @@ public class ConvertRunService { if (row == null) { continue; } + String asin = asinIndex >= 0 ? normalizeCellText(formatter.formatCellValue(row.getCell(asinIndex))) : ""; if (asin.isBlank()) { continue; @@ -233,7 +325,9 @@ public class ConvertRunService { if (fieldMapping.containsKey(column)) { String sourceColumn = fieldMapping.get(column); Integer sourceIndex = headerMap.get(sourceColumn); - String value = sourceIndex == null ? "" : normalizeCellText(formatter.formatCellValue(row.getCell(sourceIndex))); + String value = sourceIndex == null + ? "" + : normalizeCellText(formatter.formatCellValue(row.getCell(sourceIndex))); lineValues.add(value); continue; } @@ -243,10 +337,11 @@ public class ConvertRunService { } lineValues.add(""); } + rows.add(String.join("\t", lineValues)); rowIndex++; } - FileUtil.writeLines(rows, outputFile, StandardCharsets.UTF_8); + return rows; } } @@ -255,20 +350,70 @@ public class ConvertRunService { if (json == null || json.isBlank()) { return new ArrayList<>(); } - return objectMapper.readValue(json, new TypeReference>() {}); + return objectMapper.readValue(json, new TypeReference>() { + }); } catch (Exception ex) { - throw new BusinessException("模板配置解析失败"); + throw new BusinessException("Template config parsing failed."); } } + private File packageGeneratedFilesAsZip(String inputName, List generatedFiles) { + File outputDir = FileUtil.mkdir(FileUtil.file(storageProperties.getLocalTempDir(), "convert-result")); + File zipFile = buildNamedOutputFile(outputDir, FileUtil.mainName(inputName) + ".zip"); + try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { + for (GeneratedConvertFile generatedFile : generatedFiles) { + ZipEntry entry = new ZipEntry(generatedFile.filename()); + zos.putNextEntry(entry); + Files.copy(generatedFile.file().toPath(), zos); + zos.closeEntry(); + } + } catch (Exception ex) { + throw new BusinessException("Convert result packaging failed: " + ex.getMessage()); + } + return zipFile; + } + + private File packageFolderConvertResultsAsZip(String archiveName, List archiveEntries) { + File outputDir = FileUtil.mkdir(FileUtil.file(storageProperties.getLocalTempDir(), "convert-result")); + File zipFile = buildNamedOutputFile(outputDir, archiveName + ".zip"); + try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { + for (ConvertArchiveEntry entry : archiveEntries) { + String zipEntryName = buildFolderZipEntry(entry.relativePath(), entry.inputName(), entry.generatedFile().filename(), true); + zos.putNextEntry(new ZipEntry(zipEntryName)); + Files.copy(entry.generatedFile().file().toPath(), zos); + zos.closeEntry(); + } + } catch (Exception ex) { + throw new BusinessException("Convert folder result packaging failed: " + ex.getMessage()); + } + return zipFile; + } + + private String buildFolderZipEntry(String relativePath, String inputName, String outputFilename, boolean includeSourceStemFolder) { + String normalizedRelativePath = relativePath == null ? "" : relativePath.replace('\\', '/'); + String relativeDir = normalizedRelativePath.contains("/") + ? normalizedRelativePath.substring(0, normalizedRelativePath.lastIndexOf('/')) + : ""; + List parts = new ArrayList<>(); + if (!relativeDir.isBlank()) { + parts.add(relativeDir); + } + if (includeSourceStemFolder) { + parts.add(FileUtil.mainName(inputName)); + } + parts.add(outputFilename); + return String.join("/", parts); + } + private Map readStringMap(String json) { try { if (json == null || json.isBlank()) { return new HashMap<>(); } - return objectMapper.readValue(json, new TypeReference>() {}); + return objectMapper.readValue(json, new TypeReference>() { + }); } catch (Exception ex) { - throw new BusinessException("模板配置解析失败"); + throw new BusinessException("Template config parsing failed."); } } @@ -277,8 +422,11 @@ public class ConvertRunService { if (!baseDir.exists()) { return null; } - List matchedFiles = FileUtil.loopFiles(baseDir, pathname -> pathname.isFile() && pathname.getName().startsWith(fileKey)); - return matchedFiles.isEmpty() ? null : matchedFiles.getFirst(); + List matchedFiles = FileUtil.loopFiles( + baseDir, + pathname -> pathname.isFile() && pathname.getName().startsWith(fileKey) + ); + return matchedFiles.isEmpty() ? null : matchedFiles.get(0); } private File buildNamedOutputFile(File outputDir, String filename) { @@ -286,6 +434,7 @@ public class ConvertRunService { if (!candidate.exists()) { return candidate; } + String mainName = FileUtil.mainName(filename); String extName = FileUtil.extName(filename); int index = 2; @@ -314,4 +463,10 @@ public class ConvertRunService { .trim() .replaceAll("\\s+", " "); } + + private record GeneratedConvertFile(String filename, File file) { + } + + private record ConvertArchiveEntry(String relativePath, String inputName, GeneratedConvertFile generatedFile) { + } } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/controller/DedupeRunController.java b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/controller/DedupeRunController.java index 4e9c247..850266a 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/controller/DedupeRunController.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/controller/DedupeRunController.java @@ -45,6 +45,7 @@ public class DedupeRunController { - 仅保留 selectedColumns 中指定的列; - keepIntegerIds=true 时保留纯数字 ID; - keepUnderscoreIds=true 时保留类似 1_1 的 ID; + - keepIntegerMainIdsWhenNoSubIds=true 且文件中不存在 1_1 这类子 ID 时,会自动保留纯数字主 ID; - 输出文件命名遵循 原文件名_cleaned.xlsx,若重名自动追加序号。 """) @ApiResponses({ diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/controller/DedupeTotalDataController.java b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/controller/DedupeTotalDataController.java new file mode 100644 index 0000000..21df710 --- /dev/null +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/controller/DedupeTotalDataController.java @@ -0,0 +1,93 @@ +package com.nanri.aiimage.modules.dedupe.controller; + +import com.nanri.aiimage.common.api.ApiResponse; +import com.nanri.aiimage.modules.dedupe.model.dto.DedupeTotalDataCreateRequest; +import com.nanri.aiimage.modules.dedupe.model.dto.DedupeTotalDataUpdateRequest; +import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataImportVo; +import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataItemVo; +import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataPageVo; +import com.nanri.aiimage.modules.dedupe.service.DedupeTotalDataService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/admin/dedupe-total-data") +@Tag(name = "数据去重总数据", description = "维护数据去重模块的总数据列表,支持增删改查。") +public class DedupeTotalDataController { + + private final DedupeTotalDataService dedupeTotalDataService; + + @GetMapping + @Operation(summary = "分页查询总数据", description = "分页查询数据去重总数据,支持按值模糊搜索。") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = DedupeTotalDataPageVo.class))) + }) + public ApiResponse page( + @Parameter(description = "页码") @RequestParam(defaultValue = "1") Long page, + @Parameter(description = "每页数量") @RequestParam(defaultValue = "15") Long pageSize, + @Parameter(description = "模糊搜索关键字") @RequestParam(required = false) String keyword) { + return ApiResponse.success(dedupeTotalDataService.page(page, pageSize, keyword)); + } + + @PostMapping + @Operation(summary = "新增总数据", description = "新增一条数据去重总数据。") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "创建成功", content = @Content(schema = @Schema(implementation = DedupeTotalDataItemVo.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "参数不合法或数据重复") + }) + public ApiResponse create(@Valid @RequestBody DedupeTotalDataCreateRequest request) { + return ApiResponse.success("创建成功", dedupeTotalDataService.create(request)); + } + + @PostMapping("/import") + @Operation(summary = "导入总数据", description = "上传 xlsx 文件,读取 ASIN 列并批量导入数据去重总数据。") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "导入成功", content = @Content(schema = @Schema(implementation = DedupeTotalDataImportVo.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "文件不合法或缺少 ASIN 列") + }) + public ApiResponse importExcel( + @Parameter(description = "xlsx 文件", required = true) @RequestParam("file") MultipartFile file) { + return ApiResponse.success("导入成功", dedupeTotalDataService.importFromExcel(file)); + } + + @PutMapping("/{id}") + @Operation(summary = "更新总数据", description = "按 ID 更新一条数据去重总数据。") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "更新成功", content = @Content(schema = @Schema(implementation = DedupeTotalDataItemVo.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "参数不合法或数据重复"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "数据不存在") + }) + public ApiResponse update( + @Parameter(description = "主键ID", required = true) @PathVariable Long id, + @Valid @RequestBody DedupeTotalDataUpdateRequest request) { + return ApiResponse.success("更新成功", dedupeTotalDataService.update(id, request)); + } + + @DeleteMapping("/{id}") + @Operation(summary = "删除总数据", description = "按 ID 删除一条数据去重总数据。") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "删除成功"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "数据不存在") + }) + public ApiResponse delete(@Parameter(description = "主键ID", required = true) @PathVariable Long id) { + dedupeTotalDataService.delete(id); + return ApiResponse.success("删除成功", null); + } +} diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/mapper/DedupeTotalDataMapper.java b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/mapper/DedupeTotalDataMapper.java new file mode 100644 index 0000000..bc6b89d --- /dev/null +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/mapper/DedupeTotalDataMapper.java @@ -0,0 +1,9 @@ +package com.nanri.aiimage.modules.dedupe.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.nanri.aiimage.modules.dedupe.model.entity.DedupeTotalDataEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface DedupeTotalDataMapper extends BaseMapper { +} diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/dto/DedupeRunRequest.java b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/dto/DedupeRunRequest.java index 63baff1..c141275 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/dto/DedupeRunRequest.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/dto/DedupeRunRequest.java @@ -21,8 +21,14 @@ public class DedupeRunRequest { private List selectedColumns; @Schema(description = "是否保留纯数字 ID") - private boolean keepIntegerIds = true; + private boolean keepIntegerIds = false; @Schema(description = "是否保留带下划线 ID") private boolean keepUnderscoreIds = true; + + @Schema(description = "当文件中不存在下划线子 ID 时,是否自动保留纯数字主 ID") + private boolean keepIntegerMainIdsWhenNoSubIds = true; + + @Schema(description = "文件夹上传场景下的总压缩包名称(不含扩展名)") + private String archiveName; } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/dto/DedupeSourceFileDto.java b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/dto/DedupeSourceFileDto.java index 48d1720..73df87e 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/dto/DedupeSourceFileDto.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/dto/DedupeSourceFileDto.java @@ -14,4 +14,7 @@ public class DedupeSourceFileDto { @Schema(description = "原始文件名") private String originalFilename; + + @Schema(description = "相对上传目录路径") + private String relativePath; } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/dto/DedupeTotalDataCreateRequest.java b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/dto/DedupeTotalDataCreateRequest.java new file mode 100644 index 0000000..b4eda85 --- /dev/null +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/dto/DedupeTotalDataCreateRequest.java @@ -0,0 +1,16 @@ +package com.nanri.aiimage.modules.dedupe.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Data +@Schema(description = "数据去重总数据新增请求") +public class DedupeTotalDataCreateRequest { + + @NotBlank(message = "数据不能为空") + @Size(max = 128, message = "数据长度不能超过128个字符") + @Schema(description = "总数据值", requiredMode = Schema.RequiredMode.REQUIRED) + private String dataValue; +} diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/dto/DedupeTotalDataUpdateRequest.java b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/dto/DedupeTotalDataUpdateRequest.java new file mode 100644 index 0000000..380ba75 --- /dev/null +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/dto/DedupeTotalDataUpdateRequest.java @@ -0,0 +1,16 @@ +package com.nanri.aiimage.modules.dedupe.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Data +@Schema(description = "数据去重总数据更新请求") +public class DedupeTotalDataUpdateRequest { + + @NotBlank(message = "数据不能为空") + @Size(max = 128, message = "数据长度不能超过128个字符") + @Schema(description = "总数据值", requiredMode = Schema.RequiredMode.REQUIRED) + private String dataValue; +} diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/entity/DedupeTotalDataEntity.java b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/entity/DedupeTotalDataEntity.java new file mode 100644 index 0000000..9928220 --- /dev/null +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/entity/DedupeTotalDataEntity.java @@ -0,0 +1,19 @@ +package com.nanri.aiimage.modules.dedupe.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("biz_dedupe_total_data") +public class DedupeTotalDataEntity { + + @TableId(type = IdType.AUTO) + private Long id; + private String dataValue; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/vo/DedupeTotalDataImportVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/vo/DedupeTotalDataImportVo.java new file mode 100644 index 0000000..022b78d --- /dev/null +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/vo/DedupeTotalDataImportVo.java @@ -0,0 +1,21 @@ +package com.nanri.aiimage.modules.dedupe.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "数据去重总数据导入结果") +public class DedupeTotalDataImportVo { + + @Schema(description = "读取总行数") + private Integer totalRows; + + @Schema(description = "读取到的 ASIN 数量") + private Integer asinCount; + + @Schema(description = "实际新增数量") + private Integer insertedCount; + + @Schema(description = "跳过数量") + private Integer skippedCount; +} diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/vo/DedupeTotalDataItemVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/vo/DedupeTotalDataItemVo.java new file mode 100644 index 0000000..1b3fd6a --- /dev/null +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/vo/DedupeTotalDataItemVo.java @@ -0,0 +1,20 @@ +package com.nanri.aiimage.modules.dedupe.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Schema(description = "数据去重总数据项") +public class DedupeTotalDataItemVo { + + @Schema(description = "主键ID") + private Long id; + + @Schema(description = "总数据值") + private String dataValue; + + @Schema(description = "创建时间") + private LocalDateTime createdAt; +} diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/vo/DedupeTotalDataPageVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/vo/DedupeTotalDataPageVo.java new file mode 100644 index 0000000..6bc2ca7 --- /dev/null +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/model/vo/DedupeTotalDataPageVo.java @@ -0,0 +1,23 @@ +package com.nanri.aiimage.modules.dedupe.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +@Schema(description = "数据去重总数据分页结果") +public class DedupeTotalDataPageVo { + + @Schema(description = "列表") + private List items; + + @Schema(description = "总数") + private Long total; + + @Schema(description = "页码") + private Long page; + + @Schema(description = "每页数量") + private Long pageSize; +} diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/service/DedupeRunService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/service/DedupeRunService.java index f2d8755..8319c2c 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/service/DedupeRunService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/service/DedupeRunService.java @@ -28,11 +28,14 @@ import org.springframework.stereotype.Service; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.nio.file.Files; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -46,7 +49,7 @@ public class DedupeRunService { private final OssStorageService ossStorageService; public DedupeRunVo run(DedupeRunRequest request) { - if (!request.isKeepIntegerIds() && !request.isKeepUnderscoreIds()) { + if (!request.isKeepIntegerIds() && !request.isKeepUnderscoreIds() && !request.isKeepIntegerMainIdsWhenNoSubIds()) { throw new BusinessException("请至少选择一种 ID 保留规则"); } @@ -63,9 +66,11 @@ public class DedupeRunService { task.setFinishedAt(LocalDateTime.now()); fileTaskMapper.insert(task); + boolean folderMode = request.getArchiveName() != null && !request.getArchiveName().isBlank(); List items = new ArrayList<>(); int successCount = 0; int failedCount = 0; + List archiveEntries = new ArrayList<>(); for (DedupeSourceFileDto sourceFile : request.getFiles()) { DedupeResultItemVo item = new DedupeResultItemVo(); @@ -81,13 +86,28 @@ public class DedupeRunService { File outputDir = FileUtil.mkdir(FileUtil.file(storageProperties.getLocalTempDir(), "dedupe-result")); File outputFile = buildNamedOutputFile(outputDir, outputFilename); - cleanExcelByLegacyRules(inputFile, outputFile, request.getSelectedColumns(), request.isKeepIntegerIds(), request.isKeepUnderscoreIds()); + cleanExcelByLegacyRules( + inputFile, + outputFile, + request.getSelectedColumns(), + request.isKeepIntegerIds(), + request.isKeepUnderscoreIds(), + request.isKeepIntegerMainIdsWhenNoSubIds() + ); + + if (folderMode) { + archiveEntries.add(new DedupeArchiveEntry(sourceFile.getRelativePath(), inputName, outputFile)); + successCount++; + continue; + } String ossObjectKey = ossStorageService.uploadResultFile(outputFile, "DEDUPE"); String downloadUrl = ossStorageService.generateDownloadUrl(ossObjectKey); + String downloadFilename = request.getFiles().size() == 1 ? inputName : outputFile.getName(); + item.setSuccess(true); - item.setOutputFilename(outputFile.getName()); + item.setOutputFilename(downloadFilename); item.setDownloadUrl(downloadUrl); successCount++; @@ -95,7 +115,7 @@ public class DedupeRunService { resultEntity.setTaskId(task.getId()); resultEntity.setModuleType("DEDUPE"); resultEntity.setSourceFilename(inputName); - resultEntity.setResultFilename(outputFile.getName()); + resultEntity.setResultFilename(downloadFilename); resultEntity.setResultFileUrl(downloadUrl); resultEntity.setResultFileSize(outputFile.length()); resultEntity.setResultContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); @@ -119,6 +139,32 @@ public class DedupeRunService { items.add(item); } + if (folderMode && !archiveEntries.isEmpty()) { + File zipFile = packageFolderDedupeResultsAsZip(request.getArchiveName(), archiveEntries); + String ossObjectKey = ossStorageService.uploadResultFile(zipFile, "DEDUPE"); + String downloadUrl = ossStorageService.generateDownloadUrl(ossObjectKey); + + DedupeResultItemVo item = new DedupeResultItemVo(); + item.setSourceFilename(request.getArchiveName()); + item.setOutputFilename(zipFile.getName()); + item.setSuccess(true); + item.setDownloadUrl(downloadUrl); + + FileResultEntity resultEntity = new FileResultEntity(); + resultEntity.setTaskId(task.getId()); + resultEntity.setModuleType("DEDUPE"); + resultEntity.setSourceFilename(request.getArchiveName()); + resultEntity.setResultFilename(zipFile.getName()); + resultEntity.setResultFileUrl(downloadUrl); + resultEntity.setResultFileSize(zipFile.length()); + resultEntity.setResultContentType("application/zip"); + resultEntity.setSuccess(1); + resultEntity.setCreatedAt(LocalDateTime.now()); + fileResultMapper.insert(resultEntity); + item.setResultId(resultEntity.getId()); + items.add(0, item); + } + task.setSuccessFileCount(successCount); task.setFailedFileCount(failedCount); task.setResultJson(JSONUtil.toJsonStr(items)); @@ -136,17 +182,17 @@ public class DedupeRunService { public List listHistory() { return fileResultMapper.selectList(new LambdaQueryWrapper() .eq(FileResultEntity::getModuleType, "DEDUPE") - .eq(FileResultEntity::getSuccess, 1) .orderByDesc(FileResultEntity::getCreatedAt) .last("limit 50")) .stream() .map(entity -> { DedupeResultItemVo vo = new DedupeResultItemVo(); vo.setResultId(entity.getId()); - vo.setSourceFilename(entity.getCreatedAt() == null ? "" : entity.getCreatedAt().toLocalDate().toString().replace("-", "")); + vo.setSourceFilename(entity.getSourceFilename()); vo.setOutputFilename(entity.getResultFilename()); - vo.setDownloadUrl(entity.getResultFileUrl()); - vo.setSuccess(true); + vo.setDownloadUrl(entity.getSuccess() != null && entity.getSuccess() == 1 ? entity.getResultFileUrl() : null); + vo.setSuccess(entity.getSuccess() != null && entity.getSuccess() == 1); + vo.setError(entity.getErrorMessage()); return vo; }) .toList(); @@ -173,7 +219,8 @@ public class DedupeRunService { } private void cleanExcelByLegacyRules(File inputFile, File outputFile, List selectedColumns, - boolean keepIntegerIds, boolean keepUnderscoreIds) throws Exception { + boolean keepIntegerIds, boolean keepUnderscoreIds, + boolean keepIntegerMainIdsWhenNoSubIds) throws Exception { DataFormatter formatter = new DataFormatter(); try (FileInputStream fis = new FileInputStream(inputFile); Workbook workbook = org.apache.poi.ss.usermodel.WorkbookFactory.create(fis); @@ -215,6 +262,26 @@ public class DedupeRunService { } Integer idColumnIndex = headerMap.get("id"); + Map mainIdHasSubIdMap = new HashMap<>(); + if (idColumnIndex != null) { + for (int rowNum = 1; rowNum <= sheet.getLastRowNum(); rowNum++) { + Row row = sheet.getRow(rowNum); + if (row == null) { + continue; + } + String idValue = normalizeCellText(formatter.formatCellValue(row.getCell(idColumnIndex))); + String mainId = extractMainId(idValue); + if (mainId.isEmpty()) { + continue; + } + if (!mainIdHasSubIdMap.containsKey(mainId)) { + mainIdHasSubIdMap.put(mainId, false); + } + if (idValue.matches("\\d+_\\d+")) { + mainIdHasSubIdMap.put(mainId, true); + } + } + } int outputRowIndex = 1; for (int rowNum = 1; rowNum <= sheet.getLastRowNum(); rowNum++) { Row row = sheet.getRow(rowNum); @@ -223,7 +290,7 @@ public class DedupeRunService { } if (idColumnIndex != null) { String idValue = normalizeCellText(formatter.formatCellValue(row.getCell(idColumnIndex))); - if (!shouldKeepId(idValue, keepIntegerIds, keepUnderscoreIds)) { + if (!shouldKeepId(idValue, keepIntegerIds, keepUnderscoreIds, keepIntegerMainIdsWhenNoSubIds, mainIdHasSubIdMap)) { continue; } } @@ -242,14 +309,68 @@ public class DedupeRunService { } } - private boolean shouldKeepId(String text, boolean keepIntegerIds, boolean keepUnderscoreIds) { + private File packageFolderDedupeResultsAsZip(String archiveName, List archiveEntries) { + File outputDir = FileUtil.mkdir(FileUtil.file(storageProperties.getLocalTempDir(), "dedupe-result")); + File zipFile = buildNamedOutputFile(outputDir, archiveName + ".zip"); + try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { + for (DedupeArchiveEntry entry : archiveEntries) { + String zipEntryName = buildFolderZipEntry(entry.relativePath(), entry.inputName(), entry.inputName(), false); + zos.putNextEntry(new ZipEntry(zipEntryName)); + Files.copy(entry.outputFile().toPath(), zos); + zos.closeEntry(); + } + } catch (Exception ex) { + throw new BusinessException("去重结果打包失败:" + ex.getMessage()); + } + return zipFile; + } + + private String buildFolderZipEntry(String relativePath, String inputName, String outputFilename, boolean includeSourceStemFolder) { + String normalizedRelativePath = relativePath == null ? "" : relativePath.replace('\\', '/'); + String relativeDir = normalizedRelativePath.contains("/") + ? normalizedRelativePath.substring(0, normalizedRelativePath.lastIndexOf('/')) + : ""; + List parts = new ArrayList<>(); + if (!relativeDir.isBlank()) { + parts.add(relativeDir); + } + if (includeSourceStemFolder) { + parts.add(FileUtil.mainName(inputName)); + } + parts.add(outputFilename); + return String.join("/", parts); + } + + private boolean shouldKeepId(String text, boolean keepIntegerIds, boolean keepUnderscoreIds, + boolean keepIntegerMainIdsWhenNoSubIds, Map mainIdHasSubIdMap) { if (text == null || text.isBlank()) { return false; } + if (keepUnderscoreIds && text.matches("\\d+_\\d+")) { + return true; + } if (keepIntegerIds && text.matches("\\d+")) { return true; } - return keepUnderscoreIds && text.matches("\\d+_\\d+"); + if (keepIntegerMainIdsWhenNoSubIds && text.matches("\\d+")) { + String mainId = extractMainId(text); + return !mainId.isEmpty() && !Boolean.TRUE.equals(mainIdHasSubIdMap.get(mainId)); + } + return false; + } + + private String extractMainId(String text) { + if (text == null || text.isBlank()) { + return ""; + } + if (text.matches("\\d+")) { + return text; + } + if (text.matches("\\d+_\\d+")) { + int idx = text.indexOf('_'); + return idx > 0 ? text.substring(0, idx) : ""; + } + return ""; } private File findLocalSourceFile(String fileKey) { @@ -300,4 +421,7 @@ public class DedupeRunService { .trim() .replaceAll("\\s+", " "); } + + private record DedupeArchiveEntry(String relativePath, String inputName, File outputFile) { + } } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/service/DedupeTotalDataService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/service/DedupeTotalDataService.java new file mode 100644 index 0000000..3629af9 --- /dev/null +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/service/DedupeTotalDataService.java @@ -0,0 +1,215 @@ +package com.nanri.aiimage.modules.dedupe.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.nanri.aiimage.common.exception.BusinessException; +import com.nanri.aiimage.modules.dedupe.mapper.DedupeTotalDataMapper; +import com.nanri.aiimage.modules.dedupe.model.dto.DedupeTotalDataCreateRequest; +import com.nanri.aiimage.modules.dedupe.model.dto.DedupeTotalDataUpdateRequest; +import com.nanri.aiimage.modules.dedupe.model.entity.DedupeTotalDataEntity; +import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataImportVo; +import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataItemVo; +import com.nanri.aiimage.modules.dedupe.model.vo.DedupeTotalDataPageVo; +import lombok.RequiredArgsConstructor; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Service +@RequiredArgsConstructor +public class DedupeTotalDataService { + + private final DedupeTotalDataMapper dedupeTotalDataMapper; + + public DedupeTotalDataPageVo page(long page, long pageSize, String keyword) { + long safePage = Math.max(page, 1); + long safePageSize = Math.min(Math.max(pageSize, 1), 100); + String safeKeyword = keyword == null ? "" : keyword.trim(); + LambdaQueryWrapper query = new LambdaQueryWrapper() + .like(!safeKeyword.isEmpty(), DedupeTotalDataEntity::getDataValue, safeKeyword) + .orderByDesc(DedupeTotalDataEntity::getId); + Long total = dedupeTotalDataMapper.selectCount(query); + List items = dedupeTotalDataMapper.selectList(query.last("LIMIT " + ((safePage - 1) * safePageSize) + ", " + safePageSize)) + .stream() + .map(this::toItemVo) + .toList(); + DedupeTotalDataPageVo vo = new DedupeTotalDataPageVo(); + vo.setItems(items); + vo.setTotal(total); + vo.setPage(safePage); + vo.setPageSize(safePageSize); + return vo; + } + + @Transactional + public DedupeTotalDataItemVo create(DedupeTotalDataCreateRequest request) { + String dataValue = normalizeDataValue(request.getDataValue()); + ensureUnique(dataValue, null); + DedupeTotalDataEntity entity = new DedupeTotalDataEntity(); + entity.setDataValue(dataValue); + dedupeTotalDataMapper.insert(entity); + return toItemVo(getById(entity.getId())); + } + + @Transactional + public DedupeTotalDataImportVo importFromExcel(MultipartFile file) { + if (file == null || file.isEmpty()) { + throw new BusinessException("请上传 xlsx 文件"); + } + String filename = file.getOriginalFilename() == null ? "" : file.getOriginalFilename().toLowerCase(); + if (!(filename.endsWith(".xlsx") || filename.endsWith(".xls"))) { + throw new BusinessException("仅支持 .xlsx 或 .xls 文件"); + } + + DataFormatter formatter = new DataFormatter(); + try (InputStream inputStream = file.getInputStream(); + Workbook workbook = org.apache.poi.ss.usermodel.WorkbookFactory.create(inputStream)) { + Sheet sheet = workbook.getSheetAt(0); + Row headerRow = sheet.getRow(0); + if (headerRow == null) { + throw new BusinessException("Excel 表头为空"); + } + + Map headerMap = new HashMap<>(); + for (int i = 0; i < headerRow.getLastCellNum(); i++) { + Cell cell = headerRow.getCell(i); + String value = normalizeExcelText(cell == null ? null : formatter.formatCellValue(cell)); + if (!value.isBlank() && !headerMap.containsKey(value)) { + headerMap.put(value, i); + } + } + + Integer asinIndex = headerMap.get("ASIN"); + if (asinIndex == null) { + throw new BusinessException("缺少 ASIN 列"); + } + + Set seenInFile = new HashSet<>(); + int totalRows = Math.max(sheet.getLastRowNum(), 0); + int asinCount = 0; + int insertedCount = 0; + int skippedCount = 0; + + for (int rowNum = 1; rowNum <= sheet.getLastRowNum(); rowNum++) { + Row row = sheet.getRow(rowNum); + if (row == null) { + skippedCount++; + continue; + } + String asin = normalizeExcelText(formatter.formatCellValue(row.getCell(asinIndex))); + if (asin.isBlank()) { + skippedCount++; + continue; + } + asinCount++; + String dataValue = normalizeDataValue(asin); + if (!seenInFile.add(dataValue)) { + skippedCount++; + continue; + } + if (existsDataValue(dataValue)) { + skippedCount++; + continue; + } + DedupeTotalDataEntity entity = new DedupeTotalDataEntity(); + entity.setDataValue(dataValue); + dedupeTotalDataMapper.insert(entity); + insertedCount++; + } + + DedupeTotalDataImportVo vo = new DedupeTotalDataImportVo(); + vo.setTotalRows(totalRows); + vo.setAsinCount(asinCount); + vo.setInsertedCount(insertedCount); + vo.setSkippedCount(skippedCount); + return vo; + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + throw new BusinessException("导入 Excel 失败"); + } + } + + @Transactional + public DedupeTotalDataItemVo update(Long id, DedupeTotalDataUpdateRequest request) { + DedupeTotalDataEntity entity = getById(id); + String dataValue = normalizeDataValue(request.getDataValue()); + ensureUnique(dataValue, id); + entity.setDataValue(dataValue); + dedupeTotalDataMapper.updateById(entity); + return toItemVo(getById(id)); + } + + @Transactional + public void delete(Long id) { + DedupeTotalDataEntity entity = getById(id); + dedupeTotalDataMapper.deleteById(entity.getId()); + } + + private DedupeTotalDataEntity getById(Long id) { + DedupeTotalDataEntity entity = dedupeTotalDataMapper.selectById(id); + if (entity == null) { + throw new BusinessException("数据不存在"); + } + return entity; + } + + private String normalizeDataValue(String value) { + String dataValue = value == null ? "" : value.trim(); + if (dataValue.isEmpty()) { + throw new BusinessException("数据不能为空"); + } + return dataValue; + } + + private void ensureUnique(String dataValue, Long excludeId) { + LambdaQueryWrapper query = new LambdaQueryWrapper() + .eq(DedupeTotalDataEntity::getDataValue, dataValue) + .last("LIMIT 1"); + DedupeTotalDataEntity exists = dedupeTotalDataMapper.selectOne(query); + if (exists != null && (excludeId == null || !exists.getId().equals(excludeId))) { + throw new BusinessException("数据已存在"); + } + } + + private boolean existsDataValue(String dataValue) { + LambdaQueryWrapper query = new LambdaQueryWrapper() + .eq(DedupeTotalDataEntity::getDataValue, dataValue) + .last("LIMIT 1"); + return dedupeTotalDataMapper.selectOne(query) != null; + } + + private String normalizeExcelText(String value) { + if (value == null) { + return ""; + } + return value.replace("\ufeff", "") + .replace("\u3000", " ") + .replace("\r\n", " ") + .replace("\r", " ") + .replace("\n", " ") + .replace("\t", " ") + .trim() + .replaceAll("\\s+", " "); + } + + private DedupeTotalDataItemVo toItemVo(DedupeTotalDataEntity entity) { + DedupeTotalDataItemVo vo = new DedupeTotalDataItemVo(); + vo.setId(entity.getId()); + vo.setDataValue(entity.getDataValue()); + vo.setCreatedAt(entity.getCreatedAt()); + return vo; + } +} diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/file/controller/FileUploadController.java b/backend-java/src/main/java/com/nanri/aiimage/modules/file/controller/FileUploadController.java index 210e3f2..12cffdf 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/file/controller/FileUploadController.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/file/controller/FileUploadController.java @@ -46,8 +46,9 @@ public class FileUploadController { }) public ApiResponse upload( @Parameter(name = "file", description = "待上传的 Excel 或业务源文件", required = true, in = ParameterIn.QUERY) - MultipartFile file) throws Exception { - return ApiResponse.success(localFileStorageService.saveTempFile(file)); + MultipartFile file, + @RequestParam(required = false) String relativePath) throws Exception { + return ApiResponse.success(localFileStorageService.saveTempFile(file, relativePath)); } @GetMapping("/excel-info") diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/file/model/vo/UploadFileVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/file/model/vo/UploadFileVo.java index de83fe0..c500ff9 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/file/model/vo/UploadFileVo.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/file/model/vo/UploadFileVo.java @@ -18,4 +18,7 @@ public class UploadFileVo { @Schema(description = "文件大小") private Long size; + + @Schema(description = "相对上传目录路径") + private String relativePath; } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/file/service/LocalFileStorageService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/file/service/LocalFileStorageService.java index ca18468..865bf52 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/file/service/LocalFileStorageService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/file/service/LocalFileStorageService.java @@ -29,7 +29,7 @@ public class LocalFileStorageService { private final StorageProperties storageProperties; - public UploadFileVo saveTempFile(MultipartFile file) throws IOException { + public UploadFileVo saveTempFile(MultipartFile file, String relativePath) throws IOException { File tempDir = FileUtil.file(storageProperties.getLocalTempDir()); File parentDir = tempDir.getParentFile(); if (parentDir != null) { @@ -46,6 +46,7 @@ public class LocalFileStorageService { vo.setOriginalFilename(file.getOriginalFilename()); vo.setLocalPath(target.getAbsolutePath()); vo.setSize(file.getSize()); + vo.setRelativePath(relativePath); return vo; } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/split/controller/SplitRunController.java b/backend-java/src/main/java/com/nanri/aiimage/modules/split/controller/SplitRunController.java index d46a0c2..879dd69 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/split/controller/SplitRunController.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/split/controller/SplitRunController.java @@ -46,7 +46,8 @@ public class SplitRunController { - splitMode=rows_per_file 时按固定条数切分; - splitMode=parts 时按总条数尽量均分; - 输出文件命名遵循 原文件名_split_序号.xlsx; - - 返回结果中 rowCount 表示该结果文件包含的数据行数。 + - 本次拆分任务产生的所有 xlsx 会统一打成一个 zip 上传; + - 返回结果中 rowCount 表示该压缩包包含的数据总行数,entries 表示压缩包内文件摘要。 """) @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "执行成功,返回拆分结果统计", content = @Content(schema = @Schema(implementation = SplitRunVo.class))), diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/split/model/dto/SplitRunRequest.java b/backend-java/src/main/java/com/nanri/aiimage/modules/split/model/dto/SplitRunRequest.java index de8fefb..97dbcb9 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/split/model/dto/SplitRunRequest.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/split/model/dto/SplitRunRequest.java @@ -30,4 +30,7 @@ public class SplitRunRequest { @Schema(description = "按份数拆分时的份数") private Integer parts; + + @Schema(description = "文件夹上传场景下的总压缩包名称(不含扩展名)") + private String archiveName; } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/split/model/dto/SplitSourceFileDto.java b/backend-java/src/main/java/com/nanri/aiimage/modules/split/model/dto/SplitSourceFileDto.java index 2316ba4..f5a5b25 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/split/model/dto/SplitSourceFileDto.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/split/model/dto/SplitSourceFileDto.java @@ -14,4 +14,7 @@ public class SplitSourceFileDto { @Schema(description = "原始文件名") private String originalFilename; + + @Schema(description = "相对上传目录路径") + private String relativePath; } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/split/model/vo/SplitArchiveEntryVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/split/model/vo/SplitArchiveEntryVo.java new file mode 100644 index 0000000..c2977de --- /dev/null +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/split/model/vo/SplitArchiveEntryVo.java @@ -0,0 +1,15 @@ +package com.nanri.aiimage.modules.split.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "拆分压缩包内文件项") +public class SplitArchiveEntryVo { + + @Schema(description = "文件名") + private String filename; + + @Schema(description = "该文件包含的数据行数") + private Integer rowCount; +} diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/split/model/vo/SplitResultItemVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/split/model/vo/SplitResultItemVo.java index 95e1c3d..80dc455 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/split/model/vo/SplitResultItemVo.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/split/model/vo/SplitResultItemVo.java @@ -3,6 +3,8 @@ package com.nanri.aiimage.modules.split.model.vo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import java.util.List; + @Data @Schema(description = "数据拆分结果项") public class SplitResultItemVo { @@ -22,9 +24,15 @@ public class SplitResultItemVo { @Schema(description = "错误信息") private String error; - @Schema(description = "结果行数") + @Schema(description = "结果总行数") private Integer rowCount; @Schema(description = "下载地址") private String downloadUrl; + + @Schema(description = "压缩包内文件数量") + private Integer entryCount; + + @Schema(description = "压缩包内文件列表") + private List entries; } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/split/service/SplitRunService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/split/service/SplitRunService.java index 555b546..53015a0 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/split/service/SplitRunService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/split/service/SplitRunService.java @@ -3,13 +3,15 @@ package com.nanri.aiimage.modules.split.service; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.nanri.aiimage.common.exception.BusinessException; import com.nanri.aiimage.config.StorageProperties; +import com.nanri.aiimage.modules.file.service.oss.OssStorageService; import com.nanri.aiimage.modules.split.model.dto.SplitRunRequest; import com.nanri.aiimage.modules.split.model.dto.SplitSourceFileDto; +import com.nanri.aiimage.modules.split.model.vo.SplitArchiveEntryVo; import com.nanri.aiimage.modules.split.model.vo.SplitResultItemVo; import com.nanri.aiimage.modules.split.model.vo.SplitRunVo; -import com.nanri.aiimage.modules.file.service.oss.OssStorageService; import com.nanri.aiimage.modules.task.mapper.FileResultMapper; import com.nanri.aiimage.modules.task.mapper.FileTaskMapper; import com.nanri.aiimage.modules.task.model.entity.FileResultEntity; @@ -20,6 +22,7 @@ import org.apache.poi.ss.usermodel.DataFormatter; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; @@ -28,18 +31,28 @@ import org.springframework.stereotype.Service; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.nio.file.Files; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; - -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; @Service @RequiredArgsConstructor public class SplitRunService { + private static final String MODULE_TYPE = "SPLIT"; + private static final String ZIP_CONTENT_TYPE = "application/zip"; + private static final String SPLIT_MODE_ROWS_PER_FILE = "rows_per_file"; + private static final String SPLIT_MODE_PARTS = "parts"; + private static final String HEADER_SUFFIX_MARKER = "idASIN\u56fd\u5bb6\u72b6\u6001\u4ef7\u683c\u53d8\u4f53\u6570\u91cf"; + private static final String HEADER_STOP_COLUMN = "\u7f29\u7565\u56fe\u5730\u57408"; + private static final DateTimeFormatter ZIP_NAME_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); + private final FileTaskMapper fileTaskMapper; private final FileResultMapper fileResultMapper; private final StorageProperties storageProperties; @@ -49,8 +62,8 @@ public class SplitRunService { validateRequest(request); FileTaskEntity task = new FileTaskEntity(); - task.setTaskNo("SPLIT-" + IdUtil.getSnowflakeNextIdStr()); - task.setModuleType("SPLIT"); + task.setTaskNo(MODULE_TYPE + "-" + IdUtil.getSnowflakeNextIdStr()); + task.setModuleType(MODULE_TYPE); task.setTaskMode("IMMEDIATE"); task.setStatus("SUCCESS"); task.setSourceFileCount(request.getFiles().size()); @@ -61,56 +74,42 @@ public class SplitRunService { task.setFinishedAt(LocalDateTime.now()); fileTaskMapper.insert(task); + boolean folderMode = request.getArchiveName() != null && !request.getArchiveName().isBlank(); List items = new ArrayList<>(); int successCount = 0; int failedCount = 0; + List allChunkResults = new ArrayList<>(); + List successSourceNames = new ArrayList<>(); for (SplitSourceFileDto sourceFile : request.getFiles()) { try { File inputFile = findLocalSourceFile(sourceFile.getFileKey()); if (inputFile == null || !inputFile.exists()) { - throw new BusinessException("上传文件不存在,请重新上传"); + throw new BusinessException("Uploaded source file does not exist, please upload again."); } - String inputName = sourceFile.getOriginalFilename() == null ? inputFile.getName() : sourceFile.getOriginalFilename(); + + String inputName = sourceFile.getOriginalFilename() == null + ? inputFile.getName() + : sourceFile.getOriginalFilename(); List chunkResults = splitExcelByLegacyRules(inputFile, inputName, request); for (SplitChunkResult chunkResult : chunkResults) { - SplitResultItemVo item = new SplitResultItemVo(); - item.setSourceFilename(inputName); - item.setOutputFilename(chunkResult.outputFile().getName()); - item.setSuccess(true); - item.setRowCount(chunkResult.rowCount()); - - String ossObjectKey = ossStorageService.uploadResultFile(chunkResult.outputFile(), "SPLIT"); - String downloadUrl = ossStorageService.generateDownloadUrl(ossObjectKey); - - FileResultEntity resultEntity = new FileResultEntity(); - resultEntity.setTaskId(task.getId()); - resultEntity.setModuleType("SPLIT"); - resultEntity.setSourceFilename(inputName); - resultEntity.setResultFilename(chunkResult.outputFile().getName()); - resultEntity.setResultFileUrl(downloadUrl); - resultEntity.setResultFileSize(chunkResult.outputFile().length()); - resultEntity.setResultContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); - resultEntity.setRowCount(chunkResult.rowCount()); - resultEntity.setSuccess(1); - resultEntity.setCreatedAt(LocalDateTime.now()); - fileResultMapper.insert(resultEntity); - item.setDownloadUrl(downloadUrl); - item.setResultId(resultEntity.getId()); - items.add(item); + allChunkResults.add(new TaskSplitChunkResult(sourceFile.getRelativePath(), inputName, chunkResult.outputFile(), chunkResult.rowCount())); } + successSourceNames.add(inputName); successCount++; } catch (Exception ex) { SplitResultItemVo item = new SplitResultItemVo(); item.setSourceFilename(sourceFile.getOriginalFilename()); item.setSuccess(false); item.setError(ex.getMessage()); + item.setEntries(new ArrayList<>()); + item.setEntryCount(0); items.add(item); failedCount++; FileResultEntity resultEntity = new FileResultEntity(); resultEntity.setTaskId(task.getId()); - resultEntity.setModuleType("SPLIT"); + resultEntity.setModuleType(MODULE_TYPE); resultEntity.setSourceFilename(sourceFile.getOriginalFilename()); resultEntity.setSuccess(0); resultEntity.setErrorMessage(ex.getMessage()); @@ -119,6 +118,38 @@ public class SplitRunService { } } + if (!allChunkResults.isEmpty()) { + File zipFile = packageTaskSplitResultsAsZip(request.getArchiveName(), successSourceNames, allChunkResults); + String ossObjectKey = ossStorageService.uploadResultFile(zipFile, MODULE_TYPE); + String downloadUrl = ossStorageService.generateDownloadUrl(ossObjectKey); + + SplitResultItemVo item = new SplitResultItemVo(); + item.setSourceFilename(folderMode && request.getArchiveName() != null && !request.getArchiveName().isBlank() + ? request.getArchiveName() + : successSourceNames.size() == 1 ? successSourceNames.get(0) : "Current split task"); + item.setOutputFilename(zipFile.getName()); + item.setSuccess(true); + item.setRowCount(allChunkResults.stream().mapToInt(TaskSplitChunkResult::rowCount).sum()); + item.setDownloadUrl(downloadUrl); + item.setEntryCount(allChunkResults.size()); + item.setEntries(allChunkResults.stream().map(this::toArchiveEntry).toList()); + + FileResultEntity resultEntity = new FileResultEntity(); + resultEntity.setTaskId(task.getId()); + resultEntity.setModuleType(MODULE_TYPE); + resultEntity.setSourceFilename(item.getSourceFilename()); + resultEntity.setResultFilename(zipFile.getName()); + resultEntity.setResultFileUrl(downloadUrl); + resultEntity.setResultFileSize(zipFile.length()); + resultEntity.setResultContentType(ZIP_CONTENT_TYPE); + resultEntity.setRowCount(item.getRowCount()); + resultEntity.setSuccess(1); + resultEntity.setCreatedAt(LocalDateTime.now()); + fileResultMapper.insert(resultEntity); + item.setResultId(resultEntity.getId()); + items.add(0, item); + } + task.setSuccessFileCount(successCount); task.setFailedFileCount(failedCount); task.setResultJson(JSONUtil.toJsonStr(items)); @@ -135,19 +166,21 @@ public class SplitRunService { public List listHistory() { return fileResultMapper.selectList(new LambdaQueryWrapper() - .eq(FileResultEntity::getModuleType, "SPLIT") - .eq(FileResultEntity::getSuccess, 1) + .eq(FileResultEntity::getModuleType, MODULE_TYPE) .orderByDesc(FileResultEntity::getCreatedAt) .last("limit 100")) .stream() .map(entity -> { SplitResultItemVo vo = new SplitResultItemVo(); vo.setResultId(entity.getId()); - vo.setSourceFilename(entity.getCreatedAt() == null ? "" : entity.getCreatedAt().toLocalDate().toString().replace("-", "")); + vo.setSourceFilename(entity.getSourceFilename()); vo.setOutputFilename(entity.getResultFilename()); - vo.setDownloadUrl(entity.getResultFileUrl()); + vo.setDownloadUrl(entity.getSuccess() != null && entity.getSuccess() == 1 ? entity.getResultFileUrl() : null); vo.setRowCount(entity.getRowCount()); - vo.setSuccess(true); + vo.setSuccess(entity.getSuccess() != null && entity.getSuccess() == 1); + vo.setError(entity.getErrorMessage()); + vo.setEntryCount(0); + vo.setEntries(new ArrayList<>()); return vo; }) .toList(); @@ -155,55 +188,45 @@ public class SplitRunService { public void deleteHistory(Long resultId) { FileResultEntity entity = fileResultMapper.selectById(resultId); - if (entity == null || !"SPLIT".equals(entity.getModuleType())) { - throw new BusinessException("记录不存在"); + if (entity == null || !MODULE_TYPE.equals(entity.getModuleType())) { + throw new BusinessException("Split result record does not exist."); } fileResultMapper.deleteById(resultId); } public Resource getResultFile(Long resultId) { FileResultEntity entity = fileResultMapper.selectById(resultId); - if (entity == null || entity.getResultFileUrl() == null || !"SPLIT".equals(entity.getModuleType())) { - throw new BusinessException("结果文件不存在"); + if (entity == null || entity.getResultFileUrl() == null || !MODULE_TYPE.equals(entity.getModuleType())) { + throw new BusinessException("Split result file does not exist."); } + File file = new File(entity.getResultFileUrl()); if (!file.exists()) { - throw new BusinessException("结果文件不存在"); + throw new BusinessException("Split result file does not exist."); } return new FileSystemResource(file); } - private List splitExcelByLegacyRules(File inputFile, String originalFilename, SplitRunRequest request) throws Exception { + private List splitExcelByLegacyRules( + File inputFile, + String originalFilename, + SplitRunRequest request + ) throws Exception { DataFormatter formatter = new DataFormatter(); - try (FileInputStream fis = new FileInputStream(inputFile); Workbook workbook = org.apache.poi.ss.usermodel.WorkbookFactory.create(fis)) { + try (FileInputStream fis = new FileInputStream(inputFile); + Workbook workbook = WorkbookFactory.create(fis)) { Sheet sheet = workbook.getSheetAt(0); Row headerRow = sheet.getRow(0); if (headerRow == null) { - throw new BusinessException("Excel 表头为空"); + throw new BusinessException("Excel header row is empty."); } - Map headerMap = new HashMap<>(); - for (int i = 0; i < headerRow.getLastCellNum(); i++) { - Cell cell = headerRow.getCell(i); - String value = normalizeCellText(cell == null ? null : formatter.formatCellValue(cell)); - if (value.isBlank()) { - continue; - } - value = value.split("idASIN国家状态价格变体数量", 2)[0].trim().isEmpty() - ? value - : value.split("idASIN国家状态价格变体数量", 2)[0].trim(); - if (headerMap.containsKey(value)) { - continue; - } - headerMap.put(value, i); - if ("缩略图地址8".equals(value)) { - break; - } - } - - List missing = request.getSelectedColumns().stream().filter(column -> !headerMap.containsKey(column)).toList(); + Map headerMap = buildHeaderMap(headerRow, formatter); + List missing = request.getSelectedColumns().stream() + .filter(column -> !headerMap.containsKey(column)) + .toList(); if (!missing.isEmpty()) { - throw new BusinessException("缺少列:" + String.join("、", missing)); + throw new BusinessException("Missing required columns: " + String.join(", ", missing)); } List> rowsData = new ArrayList<>(); @@ -215,39 +238,23 @@ public class SplitRunService { List projected = new ArrayList<>(); for (String column : request.getSelectedColumns()) { Integer sourceIndex = headerMap.get(column); - String value = sourceIndex == null ? "" : normalizeCellText(formatter.formatCellValue(row.getCell(sourceIndex))); + String value = sourceIndex == null + ? "" + : normalizeCellText(formatter.formatCellValue(row.getCell(sourceIndex))); projected.add(value); } rowsData.add(projected); } - int totalRows = rowsData.size(); - if (totalRows == 0) { - throw new BusinessException("没有可拆分的数据行"); - } - - List>> chunks = new ArrayList<>(); - if ("rows_per_file".equals(request.getSplitMode())) { - int chunkSize = request.getRowsPerFile(); - for (int i = 0; i < totalRows; i += chunkSize) { - chunks.add(rowsData.subList(i, Math.min(i + chunkSize, totalRows))); - } - } else { - int actualParts = Math.min(request.getParts(), totalRows); - int base = totalRows / actualParts; - int remainder = totalRows % actualParts; - int start = 0; - for (int i = 0; i < actualParts; i++) { - int size = base + (i < remainder ? 1 : 0); - int end = start + size; - chunks.add(rowsData.subList(start, end)); - start = end; - } + if (rowsData.isEmpty()) { + throw new BusinessException("No data rows available for splitting."); } + List>> chunks = buildChunks(rowsData, request); File outputDir = FileUtil.mkdir(FileUtil.file(storageProperties.getLocalTempDir(), "split-result")); String sourceStem = FileUtil.mainName(originalFilename); List results = new ArrayList<>(); + for (int i = 0; i < chunks.size(); i++) { File outputFile = buildNamedOutputFile(outputDir, sourceStem + "_split_" + (i + 1) + ".xlsx"); try (XSSFWorkbook outputWorkbook = new XSSFWorkbook()) { @@ -269,19 +276,63 @@ public class SplitRunService { } results.add(new SplitChunkResult(outputFile, chunks.get(i).size())); } + return results; } } + private Map buildHeaderMap(Row headerRow, DataFormatter formatter) { + Map headerMap = new HashMap<>(); + for (int i = 0; i < headerRow.getLastCellNum(); i++) { + Cell cell = headerRow.getCell(i); + String value = normalizeHeaderName(cell == null ? null : formatter.formatCellValue(cell)); + if (value.isBlank() || headerMap.containsKey(value)) { + continue; + } + headerMap.put(value, i); + if (HEADER_STOP_COLUMN.equals(value)) { + break; + } + } + return headerMap; + } + + private List>> buildChunks(List> rowsData, SplitRunRequest request) { + int totalRows = rowsData.size(); + List>> chunks = new ArrayList<>(); + + if (SPLIT_MODE_ROWS_PER_FILE.equals(request.getSplitMode())) { + int chunkSize = request.getRowsPerFile(); + for (int i = 0; i < totalRows; i += chunkSize) { + chunks.add(rowsData.subList(i, Math.min(i + chunkSize, totalRows))); + } + return chunks; + } + + int actualParts = Math.min(request.getParts(), totalRows); + int base = totalRows / actualParts; + int remainder = totalRows % actualParts; + int start = 0; + for (int i = 0; i < actualParts; i++) { + int size = base + (i < remainder ? 1 : 0); + int end = start + size; + chunks.add(rowsData.subList(start, end)); + start = end; + } + return chunks; + } + private void validateRequest(SplitRunRequest request) { - if (!List.of("rows_per_file", "parts").contains(request.getSplitMode())) { - throw new BusinessException("拆分模式不合法"); + if (!List.of(SPLIT_MODE_ROWS_PER_FILE, SPLIT_MODE_PARTS).contains(request.getSplitMode())) { + throw new BusinessException("Invalid split mode."); } - if ("rows_per_file".equals(request.getSplitMode()) && (request.getRowsPerFile() == null || request.getRowsPerFile() <= 0)) { - throw new BusinessException("每份条数不合法"); + if (SPLIT_MODE_ROWS_PER_FILE.equals(request.getSplitMode()) + && (request.getRowsPerFile() == null || request.getRowsPerFile() <= 0)) { + throw new BusinessException("Invalid rowsPerFile value."); } - if ("parts".equals(request.getSplitMode()) && (request.getParts() == null || request.getParts() <= 0)) { - throw new BusinessException("拆分份数不合法"); + if (SPLIT_MODE_PARTS.equals(request.getSplitMode()) + && (request.getParts() == null || request.getParts() <= 0)) { + throw new BusinessException("Invalid parts value."); } } @@ -290,8 +341,11 @@ public class SplitRunService { if (!baseDir.exists()) { return null; } - List matchedFiles = FileUtil.loopFiles(baseDir, pathname -> pathname.isFile() && pathname.getName().startsWith(fileKey)); - return matchedFiles.isEmpty() ? null : matchedFiles.getFirst(); + List matchedFiles = FileUtil.loopFiles( + baseDir, + pathname -> pathname.isFile() && pathname.getName().startsWith(fileKey) + ); + return matchedFiles.isEmpty() ? null : matchedFiles.get(0); } private File buildNamedOutputFile(File outputDir, String filename) { @@ -299,6 +353,7 @@ public class SplitRunService { if (!candidate.exists()) { return candidate; } + String mainName = FileUtil.mainName(filename); String extName = FileUtil.extName(filename); int index = 2; @@ -314,6 +369,61 @@ public class SplitRunService { } } + private File packageTaskSplitResultsAsZip(String archiveName, List successSourceNames, List chunkResults) { + File outputDir = FileUtil.mkdir(FileUtil.file(storageProperties.getLocalTempDir(), "split-result")); + String zipFilename = archiveName != null && !archiveName.isBlank() + ? archiveName + ".zip" + : successSourceNames.size() == 1 + ? FileUtil.mainName(successSourceNames.getFirst()) + ".zip" + : LocalDateTime.now().format(ZIP_NAME_DATE_FORMATTER) + ".zip"; + File zipFile = buildNamedOutputFile(outputDir, zipFilename); + try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { + for (TaskSplitChunkResult chunkResult : chunkResults) { + String zipEntryName = buildFolderZipEntry(chunkResult.relativePath(), chunkResult.sourceFilename(), chunkResult.outputFile().getName(), true); + ZipEntry entry = new ZipEntry(zipEntryName); + zos.putNextEntry(entry); + Files.copy(chunkResult.outputFile().toPath(), zos); + zos.closeEntry(); + } + } catch (Exception ex) { + throw new BusinessException("拆分结果打包失败:" + ex.getMessage()); + } + return zipFile; + } + + private String buildFolderZipEntry(String relativePath, String inputName, String outputFilename, boolean includeSourceStemFolder) { + String normalizedRelativePath = relativePath == null ? "" : relativePath.replace('\\', '/'); + String relativeDir = normalizedRelativePath.contains("/") + ? normalizedRelativePath.substring(0, normalizedRelativePath.lastIndexOf('/')) + : ""; + List parts = new ArrayList<>(); + if (!relativeDir.isBlank()) { + parts.add(relativeDir); + } + if (includeSourceStemFolder) { + parts.add(FileUtil.mainName(inputName)); + } + parts.add(outputFilename); + return String.join("/", parts); + } + + private SplitArchiveEntryVo toArchiveEntry(TaskSplitChunkResult chunkResult) { + SplitArchiveEntryVo vo = new SplitArchiveEntryVo(); + vo.setFilename(chunkResult.outputFile().getName()); + vo.setRowCount(chunkResult.rowCount()); + return vo; + } + + private String normalizeHeaderName(String value) { + String normalized = normalizeCellText(value); + if (normalized.isBlank()) { + return normalized; + } + + String prefix = normalized.split(HEADER_SUFFIX_MARKER, 2)[0].trim(); + return prefix.isEmpty() ? normalized : prefix; + } + private String normalizeCellText(String value) { if (value == null) { return ""; @@ -330,4 +440,7 @@ public class SplitRunService { private record SplitChunkResult(File outputFile, int rowCount) { } + + private record TaskSplitChunkResult(String relativePath, String sourceFilename, File outputFile, int rowCount) { + } } diff --git a/backend-java/src/main/resources/db/V3__create_dedupe_total_data.sql b/backend-java/src/main/resources/db/V3__create_dedupe_total_data.sql new file mode 100644 index 0000000..0f9ca2d --- /dev/null +++ b/backend-java/src/main/resources/db/V3__create_dedupe_total_data.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS biz_dedupe_total_data ( + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', + data_value VARCHAR(128) NOT NULL COMMENT '去重总数据值', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + UNIQUE KEY uk_data_value (data_value), + KEY idx_created_at (created_at) +) COMMENT='数据去重总数据表'; diff --git a/backend-java/src/main/resources/db/V4__rename_convert_template_to_five_countries.sql b/backend-java/src/main/resources/db/V4__rename_convert_template_to_five_countries.sql new file mode 100644 index 0000000..8b923aa --- /dev/null +++ b/backend-java/src/main/resources/db/V4__rename_convert_template_to_five_countries.sql @@ -0,0 +1,5 @@ +UPDATE biz_convert_template +SET template_name = '五国模板', + output_filename = '英国.txt、法国.txt、德国.txt、西班牙.txt、意大利.txt', + updated_at = CURRENT_TIMESTAMP +WHERE template_code = 'uk_offer'; diff --git a/backend/.device_id b/backend/.device_id new file mode 100644 index 0000000..6e07d8f --- /dev/null +++ b/backend/.device_id @@ -0,0 +1 @@ +8cecee3bc02a178bf372ca1c3d02fc5cae3c0a51c8346f6e6a710988599c08be \ No newline at end of file diff --git a/backend/__pycache__/ali_oss.cpython-311.pyc b/backend/__pycache__/ali_oss.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26cc96a2657e3b9a1394689288ccb23631edaacb GIT binary patch literal 1764 zcma(QT}vES^v=%g?Ch7Wu4yzIjbt}M7*SP+WZyscPs0)jmK#F3P2<=)Y0sgQH;k?#Q@H1t;_sq+9Ea{A4i>!EjN7nJ zbI*W}70+oqjT?o`M^VK4oV{@?;-n(M2|b;n5>9EEw4So47|q5eutj}w-M~tg7)pj< z>a9tYjB0)M@+y{2Sr`oX2|OKZWCwm-uzA?~ckF6gz}8_LBsEw(Zc!Jallk9woUmm+ zZCHf<{ufT!@;OJ2SDbY{%U9}!pdmJ zOj?)&J{C{F+chsRh$g&hGSzMyUW9>t|Lw7Xo6n#AvHN6oYtWa zx&jIJW~Qk(o3R6D14}x<$P3$?1C&RVP}fiIZH8`dgl-o@qovSjL1?dd{BxH#Jv|$q zo}wpQ@`Uq)2vC0LMF9EQ7s5qvXF=%Phj+1}etbi@*4KZ%cX;Fmb&%+cWI-8wGpR&`vuhVQ zLnW0YGAc1K{H&^p``!daOa)6%Vj6_PMAfJoGtya&RY;kQvMKigJly0X?*boZ6qeO1IcyMON~pKX6U`NQP*Q$J44`XF>? zw|!xwJ*INN(2?v_+~MKjtHfJEbN<75-(u*QTkZ=wnGh3l_ U>)e@vi~RaUVc?Q$J;DM07qd^O;{X5v literal 0 HcmV?d00001 diff --git a/backend/__pycache__/ali_oss.cpython-312.pyc b/backend/__pycache__/ali_oss.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1053f3e31380b6625af7a8b7092fc0f9457267c7 GIT binary patch literal 1514 zcmYjQUu@e%7{9X}$8q8&Sqf{Hwp&#VNHsbbESnHwnkq6e!PYSeX;vl6i7&yaV~4Zd zE>(u|Fo{(I4TPo@@c@sTI>BBh@iHWyd1=_7zr+EQG{T0o!F5hQ4%B^U zU-|i7Q3JZsWz|swvac5b0nHIdqbO!XR;kZjd0?r*8k_+y0~fAv5L^O~ejkrr<}ZO2 zn2G+&GM>wXh=@_ibZjCa+o(FG?GZ8W7nYDmVnx$JnvX55iV=x+v|3g7-tU?Uj_n~D zz%QX%p?im*&@kP3`nPkCb<+!!Y%33Yb+zSC-`r<<_r1am#r81jd(nK6A)(9mWBEE) z=e2%zFV^#=+I}u?WJt^E4wgsj?yPd6#|>zm-{{(}2z8;H8d!SVQ2c05H=y3LJ-!a> z;4a)?y`rn1HRO$TGyv2g!}TJ_aOW~SW|zV2&P2#NMlGWd-f>+LEns9&Buw3M$#ZYo z%O-a03RS$PV^hytXntT{z|OL1AWTFGU(qa)nC0k(W}u?(TOJu5N=0$+_+nNdrf&ui zao#c+=5p2dG$&t11&<^aeD8d~zKcHak?VEpWIfJ^SYolPW}IXR_(HKn&+cTQ!{^dd zUFLcySWl1SUR$K+vc!u8tgl!IzgV^Wl4&pIbvl=}v8Y?7Mz1|rt&yl-4a#SuipOb| zDE-|J>DBm~7Ju0FgAUqMNp+`3I@Ed;j`B-`@QD%kLkreYAV?-tH&g z9DVDZGupB2$>Vcp7tRn4=T{|_oc>9|zYv5V-#)LoQ)l{z9KcxDBW z6m3Obr+u*;-(Wq_mXBdJ=S0FCC|n$;;sC$_G?-003FT|_NWxRlg8s3ZZM}jtjR;N^ z*#s7efRSzlINkyH087WllSr>u9K*NJ8+ewA>_xbkZhJh0a0kS9fU*N3J0Q9Prm6TC zXzX&Jy!az9iTO_*~$v$-%=ns;o5A#194xOG0{|}sOXA1xT literal 0 HcmV?d00001 diff --git a/backend/__pycache__/ali_oss.cpython-39.pyc b/backend/__pycache__/ali_oss.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..573a87fbab1a58a00d3cea77c5257abc0e58f407 GIT binary patch literal 988 zcmYjQ&ui2`6rP#PX0u7wu5E35D0nOErE1Y4BBE3&6>N)w;!p_5&g_~tznn}8E89~A z5iOp2(!=&5=s~^o>K`&!vFg7dh`wY?>pbSYc{4BXee=DE-)!0l#u_;cDMjd8>HI$| zaBjlPKZ4?j;|Q^l#SB+IVWhGKGb&42t+FOF!IG#RTg-|ZtP$JHjveO2E^{#wO>u@B z?+B;7_MEaY(cUgy>>c{9Vt#rSaI0I|tOP?sW#b`6Y@)+Lm`zKC!ok3owu zF)3W$m`2>*BN$>Iptkc1@S@drO`B5m!ZgvANO+cpiPC0Jbhd=jZa0jCSIEfAq|nZ2 z*UNnMq)md}3^geutDPkB14i4s!`+z6wgAO*|wAXotYc7*bdn12QnYnleF~ zst&rY(9jwf2Y81L=&%8iwsHn|0GRj6-I{U-cp3qIvf4HzGz7`A1DXaY@3))UNb_7f z9Vs}V3jHW&b9a*MP^L)?8xMUM`avX?PS4k6x*c*MwFy`0hCSv+sn0zwx_%KUJ^5cM zU||$1%Zo~*tZObsAtYvYRwyqGHbqCV)_S4Vs^xw0v=F%(;jBS?+mt0K6=vBi6BwOt z4{BFg%2wo1^#hXQaQ1uV_O*>$D;t3i-AMR`AB7%VZ$9g5r^qUpWrG|D&1XR&Y=fAU z*o0z=NhyEmUuwxoIM?fkBBUJs>m9t fi86(CJx+NMi5nQadPlU`Gttkt;cn<#qk6i?Z literal 0 HcmV?d00001 diff --git a/backend/__pycache__/app.cpython-311.pyc b/backend/__pycache__/app.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d267e739ea63cd567af3ad1b193e26b38b4a9abb GIT binary patch literal 2119 zcma)6TTdHD6rR1{%i3H+AcmA0UW$uqH4zbQg^DP4Tq_NUFfU!9t`>H{8+L7Lb{&-{ zQkRydBuyK7Q4{5&kgB3oAkvpg^r}Ae2dpDYwN{FRlsMprs+OX@svOVR*sb_fKkv7(N2ymE_y!w$l}5FRZ?Z9`G^;Iq z3k4u`YG2iy<<=^yogEVUYj>&`wyjVAtl?g1&K2KI1L%SWU^{GI_pRVNSi`;2_yFGX zz<^W3wyL8lv)oxlwX@{Mn)IoIRi0GuJYxml=o;>o1_Qz~RgCpmsbRg*DGvjux~dXV z(;$X0u>5S5Q#(VyQxjYJUy=<^E!p(ck}Xe4O8cdK3ltxO2c*_n+hauTCb)u)UfVMq zN1aaCJ=B(c2eY~2M_+7T+Z>JuUkDymL~S-$T>5x>?Kbz|;`!o*n>+9Q{NTfL#l>^? zH-Fgq>_+k0FMs}Yb!X$No!j%hVpdOv?iusI^7yr{D+!GIk(F_yry zTTaQkAWagM{aZZSKf{WT&!;SI_h4Z%6QZp~9jX~c)S)n)RwRh5dQBH~d8)eP648;d z=(un!`erGJ?I^kNKz<>KbDGt0GCFpAcr-dXE+nFfMEu04@J9S-bUc1M`pA3e@Id%Q z{2eMWxLT(6G7KL;s+nZ=!?*Bo8R&cU0Kk*6&};OTYcN*aZD+w*`wduUR#+TI+$#;o z5hOr}9UVU!Vl6hA)^v-_q>*lU^dy3!1SQL#)u4c&lEy0(Vh}ORV#G|wVo){}q7kWL z)P@I*{5u%@izIsFm5Jea>DKq#*ES|_Dikk$TZ!fbRt#t6EH~E0(aa!Hb3xp-K0FxU zF6*0*FT{=bUDo|U?A_Q>>y^lA|0XnIIX04KBW9HzVsT)31c3xl5U?FBn1RUi6Y93y zf*_@*1i_-yn&sBu6oR^Dc_bV~k|BuPtW`HnG8K{Aus9nFQ)#53I>PW|by?BE(qzTq z#z`d$Ge}P9T9~8`B0;ulxx^0SA>7tl*=pJ%FWG9T+1gDqL|$}!>!O~t+>(q^q6#gy zIH}nj?BKMlKyL+}ki;rw20`T}%>V!Z literal 0 HcmV?d00001 diff --git a/backend/__pycache__/config.cpython-311.pyc b/backend/__pycache__/config.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e65d9f5df9246f1fdcf05414feb64ee811093828 GIT binary patch literal 1262 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 literal 0 HcmV?d00001 diff --git a/backend/__pycache__/config.cpython-39.pyc b/backend/__pycache__/config.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81cae7a70a27fb3215eb3070531f1ed694b87a4b GIT binary patch literal 1043 zcmaJC~Q*5#-waUk*sIW1G|9j_?cw= zK1Q!qKR~aQ`el6WsUM)1p85=%YSlyE$n)nnqu-3*JW?vjk~qI*es})LNYWok@c9v# z^sV^VPeP#t5~d)9X-H!RGMI%dZow9A!#3t1hk3~34(y-|G8UkKMJQqkO1KNVxJL^= zOIZFT1ADlS2a7V4X_1zH+{eRHiI&#hvr`F=*4}gBy?_IJ35WOvJfnwe^h;5>@xBuI z16_Laia)o*&}VO}RpL7p%j2|Sxzz_1dW?jIs=1T8G8lUAK6Zkj_rbiaFZ8arLi2t; zx*c1--EFtOyVB@%67i2qHm~2e)Jg9{jq=It9vX1iJW*OLRXeFQTFqu#QEHm@1lC&3 zTD?`PDN0*u@*P!cRumyBopIl|xxRdJ6z5ICsIdwbASY`#LT^r8 zC_H+>e7f)+4F};s9}mXeb7L~PzBs!aO~jC~yo}iW!mvFSKKaQa;@u7I6UGDuKE=s? zl18y?5zD5APeL2E13Gg&H_lTRea~^jc-vfAa~eW{SQcgMf<^;`y?+CpT0xTCRBcF# zQ)jWB-|zz(r#%+usQcgqf_Dq4xthhf2O2QZC&-E$|5Xb1g;}e=U-aJIbq04P5lvmZ wvpjd|%qo5q=TDa&t`_v0l&EEjP}$U4vY%`woBre_c)FO*r{qL38&ppH12*V1fdBvi literal 0 HcmV?d00001 diff --git a/backend/__pycache__/db.cpython-311.pyc b/backend/__pycache__/db.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9216b0d2a6a8e4f35b28eb0b6f6a8e2fc8e65ac1 GIT binary patch literal 7450 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 literal 0 HcmV?d00001 diff --git a/backend/ali_oss.py b/backend/ali_oss.py new file mode 100644 index 0000000..b31ee99 --- /dev/null +++ b/backend/ali_oss.py @@ -0,0 +1,36 @@ +import argparse +import base64 +import re +import time +import alibabacloud_oss_v2 as oss +import requests + +from config import region, endpoint, bucket, file_url_pre, bucket_path + + +def upload_file(file_content: bytes, key: str): + credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider() + cfg = oss.config.load_default() + cfg.credentials_provider = credentials_provider + cfg.region = region + cfg.endpoint = endpoint + client = oss.Client(cfg) + + result = client.put_object( + oss.PutObjectRequest( + bucket=bucket, # 存储空间名称 + key=key, # 对象名称 + body=file_content # 读取文件内容 + ) + ) + # print(result) + return file_url_pre + key + + + + +# 脚本入口,当文件被直接运行时调用main函数 +if __name__ == "__main__": + with open("测试图片数据/IMG_2685.JPG", "rb") as f: + file_content = f.read() + upload_file(file_content,key=bucket_path+"test.png") \ No newline at end of file diff --git a/backend/app.py b/backend/app.py new file mode 100644 index 0000000..201ace9 --- /dev/null +++ b/backend/app.py @@ -0,0 +1,38 @@ +""" +卖相AI - Flask 后端 +按功能拆分为蓝图:认证(auth)、主页面(main)、管理员API(admin_api)、版本(version) +""" +import os +import secrets +from datetime import timedelta + +from flask import Flask +from flask_cors import CORS + +from utils.db import init_db +from blueprints.auth import auth +from blueprints.main import main +from blueprints.admin_api import admin_api +from blueprints.version import version_bp + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +app = Flask(__name__, template_folder=BASE_DIR, static_folder=BASE_DIR) + +CORS(app) +app.secret_key = os.environ.get('SECRET_KEY', secrets.token_hex(32)) +app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7) + +# 注册蓝图 +app.register_blueprint(auth) +app.register_blueprint(main) +app.register_blueprint(admin_api) +app.register_blueprint(version_bp) + + +def run_app(host='0.0.0.0', port=15124): + init_db() + app.run(host=host, port=port, threaded=True, use_reloader=False) + + +if __name__ == '__main__': + run_app() diff --git a/backend/blueprints/__init__.py b/backend/blueprints/__init__.py new file mode 100644 index 0000000..836aa1e --- /dev/null +++ b/backend/blueprints/__init__.py @@ -0,0 +1 @@ +# Blueprints 包 diff --git a/backend/blueprints/__pycache__/__init__.cpython-311.pyc b/backend/blueprints/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..936e378c64f5061980fc71983a8462ff0ebfb95c GIT binary patch literal 159 zcmZ3^%ge<81njNrGKGNjV-N=h7@>^MY(U0zh7^Wi22Do4l?+8pK>lZttlay1MT2PdkS5h1kAD@|*SrQ+wS5SG2!zMRB br8Fniu80+A5F-#5gBTx~85tRin1L(+FS#X| literal 0 HcmV?d00001 diff --git a/backend/blueprints/__pycache__/__init__.cpython-39.pyc b/backend/blueprints/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7806be6aab64fba1e5cd1d3477df030fbb9020b GIT binary patch literal 127 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 akszc7P>%SGuAU^`Cw=v0>i0`Yp#KLl?kcDN literal 0 HcmV?d00001 diff --git a/backend/blueprints/__pycache__/admin_api.cpython-312.pyc b/backend/blueprints/__pycache__/admin_api.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1a753687edf5692f6c4cc1b06edbd7b6884049e8 GIT binary patch literal 36208 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 literal 0 HcmV?d00001 diff --git a/backend/blueprints/__pycache__/admin_api.cpython-39.pyc b/backend/blueprints/__pycache__/admin_api.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a2a7bb95d30a7fd96257e6b74f9ffb7ea4b4e6e GIT binary patch literal 16876 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 literal 0 HcmV?d00001 diff --git a/backend/blueprints/__pycache__/admin_users.cpython-311.pyc b/backend/blueprints/__pycache__/admin_users.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c88c41b2e7fab76ee369733583673f35c94dfc0 GIT binary patch literal 16921 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 literal 0 HcmV?d00001 diff --git a/backend/blueprints/__pycache__/auth.cpython-311.pyc b/backend/blueprints/__pycache__/auth.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5f820223f6bdcbe91be617f8f1a91506b0ca870 GIT binary patch literal 5312 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 M)R9q_A}OB#7p8Ic4gdfE literal 0 HcmV?d00001 diff --git a/backend/blueprints/__pycache__/auth.cpython-39.pyc b/backend/blueprints/__pycache__/auth.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f8fdc11ae4bfd67c346de9b6fa4ba4887c88ac4 GIT binary patch literal 2461 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 literal 0 HcmV?d00001 diff --git a/backend/blueprints/__pycache__/main.cpython-39.pyc b/backend/blueprints/__pycache__/main.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a407705321a393b4f11c7e645d01accd5d23127c GIT binary patch literal 1448 zcmZWp&1>976xZlurPY4eiNBJ@EvBdHVqZfVLY=s!zPM2PBn(2{8QXHC-ObD>P0a=p z5?W|UZD^a)7=zVAA%~hmNeQG)p?}6)gx8x7xp z^_lr3`NKAh7ibJG7mSbK75{>uMrcq%&6I=$ey!BZtkBBr(9WFD$tqzbb3>P4jGcN} zHLPZS=x4RCrhQIY&l+Ka7|fy-)}$`=9$4WBBU_^fXNKfiHdYXP!g1}LVB@q}nBgQF zIhdk;L1>NE?;H0>cusp71z}a@(Z^wQ01Tq)fSj36XT#fu)*hXCrAxoZZMF zo~mX{vtfH85z%RMlnchAn{k@Z$3)dQqyu|XW~s8XIB6>*Aj-$iw^T*QSSGF4 zWUhrJ4lfIgYw(H>KuF^NhF1_iEzNrdNK4w(#HKA9TDM9)u$KT17Lm~H5xjD?thO6( zux&ri<>su06F9sAd6l)nB(H+T@jec0G9DXz40RN*XBIB4&M&U6#jWcAW_9gN`^2hF z-u+TJ34pvc3c&(18s;de6Fvdjc4baq{y%6YY-(s8C=!^5FamQ43dGjVV#7F6{B0qn zHV!^j21STy3T%Pd44Y4a+@6{%8#;Hn8_Ug0ILdaMu`4ZtL8l8rVyU`>6;PzHMGj0Y zp|3roFbi^LHL3LgNo(g9A@%Xj z?$PtTnX;;xlfA?K-#_+uzUlwATk1zozdd<$f0(Q<5C0kbbQqYt0mqXcpzu$CG>uq9 zv6OLZGh=NTRF&Bg%0`gNYj=32HG8WA9P^h~=2sT4MGK3cD>q&fddr6jxD;O`aG;V1 zYz%kcg1}bZn=X|ahKCpqyo}&@sP|DDnuZ=k9e8$=MP=xFkWAD;7}g{ZpiU-2K@Oj0q6Waao*#*rqPq^e$Sl^+(bVKy&{ zaKw-tZ|CRH=fanbPX6v`1VW4=FJSC6?mE1aOQs2{IizbIf~vm-jn})J jb!eWlD@Z1RfNjeo)8;s-n@w|?G{N#K9x>r*+8+522?dyx literal 0 HcmV?d00001 diff --git a/backend/blueprints/__pycache__/version.cpython-312.pyc b/backend/blueprints/__pycache__/version.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e7f2ea862e580148407ba50126e2b24e5e657834 GIT binary patch literal 2059 zcmbVNZ%k8H6uQa0!Tcf%hJ+we&Ujz1q?M ztBXtMWLCmrnV`YAB`%oA2i?qun(@^yC|kx}NR~xg;ge;MnCO??*Y;sG+n3#!o_o$c zf9}2Kckb!Ek`fERqk*{TyGH@=P#26r7ntQsm`MQr{a5X4)7C2q2LnEwLhVjh!I#G1^R$$?r4*znDeS1k6jK`81d)DB3LlVQpEpwia1i zdkB{;T!)=4j}rr#v1{osuBE5q+}_4|ZejRhdi2hdsSD}b!|B9VdHuuqsmxeBbAB=} zKDu>g;r8vt;X9eL-&q#*GaPmin%NTwOCjV7sv7H8#DMwV1@g3IlA;dFQ&+?xEeO z+M(L-7>o%_pL;oJ=gT2siJjfy|5gOP*fGz%|R*V>Z zgqZsb+_63In~OpfSi>HI&j_VdwHg*VZ*q(<_G<<&biIT~y@m~=P$6(Lps~yp%i>FZ zo4^jBzzDW%1rUFO-YD4$B5)H>byRX=Ge8^r$QL_^tZFxOy{q-=TfMz#>LLW96^KM1 z*-thX<_L^{d~BCFT#x-f^tNydXMap5&leXhuHoVy@MLNrGnhyZjHZA7DUd!`%L+hC^IL>f{AE*yTIUH8kz>HgTR- zPCyc_4w3IrxqaT|I__Zoq52kX2W~d`cVIF?k)eMoS3E$hU&)$7Mo#6=;kQ zMv9E^D~U=%Sd}zWm!t}x%0Wq^g@CL``G;ioMg=LP;zp&Bii%v6M&VOo0lRA?k|P?F z6#dC@cp$w&Yh^_*h0m8W+K3hUNt7Fy#6gy^)7A6#b!Vaj)Le-pc{Fi!@YskjXR{{{ zBo552Il4j;C8b#gILlL8hqey82kM@hL1|Ui0?Z{zOTsc}`@>!~?_58w-dcOVs&>}7 zXU4W?-d;Xev3{gxsQ*F5J6Fr*Dyv35`o{jCa@*B+muRxmvJA*I)+`{+)~5~}Y~a}Q zl}cb+_cwt0Vai9?Z=K^GUn!rd*fMK(UEMijcmHN`KhKg_{Yv4y4gtfez&a z(qC*o&!({I3n*@}Gmnmm=Yt*H63=UvLVfU^A&o{dMk?2Rav4Pq-Cu7Y`OKR>=$6HB zK&nM9yr%y>D4m$#8$h^35Rh7C0JQuGHm5<=UnS+|dd9a*p8U0>Hg3VGCTzb$$9?EH o_x3D&eTiToyG#ONLssXqWvl9$vQ4va^Ah2J)w=$T9G5%8KS4Mj3;+NC literal 0 HcmV?d00001 diff --git a/backend/blueprints/__pycache__/version.cpython-39.pyc b/backend/blueprints/__pycache__/version.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6da875c1ba7bd39649b0c5177bcbe12e41ec735a GIT binary patch literal 1333 zcmY*Y&ube;6rS1Hl~ya+aeFBAP*~bh5ENU!l~CF`wps*EY$T__!eo(lM(H{$t(aXU zwvdW~Qm~pPKDZHZWg2x^Z}LaJ2O_xb=&61Yyv* zv?LN|>;|iItLBOLs_WFO%6b}$*d^u}v@GIR72DgnmXBBI$sZI@7 z%`n*Lh=UO(>V62qLmpn0c1Z|JU!r7a$&`Sl?2s;o!eWijVQcVnCwO=x*xrMrp_s#G zKll=kxPv!o!vcnKIWD1PV&aoP-|9aYdZfu2|fu2?7xOiugDzMB>#4bEj8kax3MM zd70JdO8EpgcV!5}WPM#E0VZQ;r{Jg?afe7ah2j63Pk^s^>IAqcyW}y~djmh97zz(X z_&pA{w>;EEZPY@$xQ{m9Z{rr;l_>6Gj6frO)RrzFD#=JjU4l_xhrfAyv}Mr2QAQr`Zv;Q|{Ecn@ z$>ZS8??-p`{@?%NQ75?9Ki=;(bA{}Dc6Lc00{pRFv212Q2lX%W3upDKtZbN0tzuo$ z7xHu2ynbd`H#q~0sZsLuxolxppU<7mE$P!h{Q(QqlMs=~L+0|gKnR6#?r`fbj8IzT zVOK=VY;f1%z>zg(HasTO3iHfwoEj5~X*(_(&?u2zGg;k(XCsK~@w6;t$O;TNE(mw7 z3d~;fik}KYzdgbmW59}c!0+lH5YQ)vH7sN4UzvP_RnR7YP8!z!(d2Q=--A8rl!zM! zbxgzHNw9@du}kh{E`dHsOKo_T?M~A2pcvIKYNaYO451C-G|F|Ma0d#DXc~nb$uoKh fQL^f%Rfjfg_8DM<+>p{TQSpS7lEz`y5-R==>OY(C literal 0 HcmV?d00001 diff --git a/backend/blueprints/admin_api.py b/backend/blueprints/admin_api.py new file mode 100644 index 0000000..51db218 --- /dev/null +++ b/backend/blueprints/admin_api.py @@ -0,0 +1,723 @@ +""" +管理员 API 蓝图:用户管理、生成历史、版本管理(后台) +""" +import json +import os +import re + +import requests +from flask import Blueprint, request, jsonify, session + +import pymysql +from werkzeug.security import generate_password_hash + +from utils.db import get_db +from utils.auth import admin_required, get_current_admin_role +from ali_oss import upload_file as oss_upload_file + +try: + from config import bucket_path, backend_java_base_url +except ImportError: + bucket_path = os.environ.get('BUCKET_PATH', 'nanri-image/') + backend_java_base_url = os.environ.get('BACKEND_JAVA_BASE_URL', 'http://127.0.0.1:18080').rstrip('/') + +admin_api = Blueprint('admin_api', __name__, url_prefix='/api/admin') + + +def _safe_version_key(version): + """将版本号转为安全的 OSS 对象名部分""" + s = (version or '').strip() + s = re.sub(r'[^\w.\-]', '_', s) + return s or 'unknown' + + +def _proxy_backend_java(method, path, *, params=None, json_data=None, files=None, data=None): + url = f"{backend_java_base_url}{path}" + try: + resp = requests.request(method=method, url=url, params=params, json=json_data, files=files, data=data, timeout=10) + except requests.RequestException: + return None, jsonify({'success': False, 'error': 'backend-java 服务不可用'}), 502 + try: + data = resp.json() + except ValueError: + data = None + if resp.status_code >= 400: + if isinstance(data, dict): + return data, jsonify({'success': False, 'error': data.get('message') or data.get('error') or 'backend-java 请求失败'}), resp.status_code + return None, jsonify({'success': False, 'error': 'backend-java 请求失败'}), resp.status_code + if not isinstance(data, dict): + return None, jsonify({'success': False, 'error': 'backend-java 返回格式错误'}), 502 + if not data.get('success'): + return data, jsonify({'success': False, 'error': data.get('message') or '操作失败'}), 200 + return data, None, 200 + + +# ---------- 用户管理 ---------- + +@admin_api.route('/users') +@admin_required +def list_users(): + """分页获取用户列表;支持用户名模糊搜索、指定管理员所属普通用户筛选""" + role, current_row = get_current_admin_role() + if not role: + return jsonify({'success': False, 'error': '需要管理员权限'}), 403 + page = max(1, int(request.args.get('page', 1))) + page_size = min(50, max(5, int(request.args.get('page_size', 15)))) + offset = (page - 1) * page_size + search_username = (request.args.get('username') or request.args.get('search') or '').strip() + created_by_id_arg = request.args.get('created_by_id') or request.args.get('admin_id') + created_by_id = int(created_by_id_arg) if created_by_id_arg and str(created_by_id_arg).isdigit() else None + if role != 'super_admin': + created_by_id = None + try: + conn = get_db() + with conn.cursor() as cur: + if role == 'super_admin': + where_parts = ["1=1"] + params = [] + if search_username: + where_parts.append("u.username LIKE %s") + params.append("%" + search_username + "%") + if created_by_id is not None: + where_parts.append("u.created_by_id = %s") + params.append(created_by_id) + where_sql = " AND ".join(where_parts) + cur.execute( + """SELECT u.id, u.username, u.is_admin, u.role, u.created_at, u.created_by_id, + creator.username AS creator_username + FROM users u + LEFT JOIN users creator ON creator.id = u.created_by_id + WHERE """ + where_sql + """ ORDER BY u.id LIMIT %s OFFSET %s""", + tuple(params) + (page_size, offset), + ) + rows = cur.fetchall() + cur.execute("SELECT COUNT(*) as total FROM users u WHERE " + where_sql, tuple(params)) + total = cur.fetchone()['total'] + cur.execute("SELECT id, username FROM users WHERE role = 'admin' ORDER BY id") + admins = [{'id': r['id'], 'username': r['username']} for r in cur.fetchall()] + else: + admin_id = current_row['id'] + where_parts = ["(u.id = %s OR (u.role = 'normal' AND u.created_by_id = %s))"] + params = [admin_id, admin_id] + if search_username: + where_parts.append("u.username LIKE %s") + params.append("%" + search_username + "%") + where_sql = " AND ".join(where_parts) + cur.execute( + """SELECT u.id, u.username, u.is_admin, u.role, u.created_at, u.created_by_id, + creator.username AS creator_username + FROM users u + LEFT JOIN users creator ON creator.id = u.created_by_id + WHERE """ + where_sql + """ ORDER BY u.id LIMIT %s OFFSET %s""", + tuple(params) + (page_size, offset), + ) + rows = cur.fetchall() + cur.execute( + "SELECT COUNT(*) as total FROM users u WHERE " + where_sql, + tuple(params), + ) + total = cur.fetchone()['total'] + admins = [] + items = [ + { + 'id': r['id'], + 'username': r['username'], + 'is_admin': bool(r.get('is_admin')), + 'role': r.get('role') or 'normal', + 'created_by_id': r.get('created_by_id'), + 'creator_username': r.get('creator_username') or '', + 'created_at': r['created_at'].strftime('%Y-%m-%d %H:%M') if r.get('created_at') else '', + } + for r in rows + ] + conn.close() + return jsonify({ + 'success': True, + 'items': items, + 'total': total, + 'page': page, + 'page_size': page_size, + 'current_user_role': role, + 'admins': admins, + }) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}) + + +@admin_api.route('/user', methods=['POST']) +@admin_required +def create_user(): + data = request.get_json() or {} + username = (data.get('username') or '').strip() + password = data.get('password') or '' + role, current_row = get_current_admin_role() + if not role: + return jsonify({'success': False, 'error': '需要管理员权限'}), 403 + want_role = (data.get('role') or 'normal').strip() or 'normal' + if want_role not in ('admin', 'normal'): + want_role = 'normal' + if role == 'admin' and want_role == 'admin': + return jsonify({'success': False, 'error': '仅超级管理员可创建管理员'}) + if want_role == 'admin': + want_created_by = current_row['id'] + elif role == 'super_admin': + want_created_by = data.get('created_by_id') + else: + want_created_by = current_row['id'] + if not username or not password: + return jsonify({'success': False, 'error': '用户名和密码不能为空'}) + if len(username) < 2: + return jsonify({'success': False, 'error': '用户名至少2个字符'}) + if len(password) < 6: + return jsonify({'success': False, 'error': '密码至少6个字符'}) + if want_role == 'normal' and role == 'super_admin' and want_created_by is None: + try: + conn = get_db() + with conn.cursor() as cur: + cur.execute("SELECT id FROM users WHERE role = 'admin' ORDER BY id LIMIT 1") + r = cur.fetchone() + conn.close() + want_created_by = r['id'] if r else current_row['id'] + except Exception: + want_created_by = current_row['id'] + if want_role == 'normal' and want_created_by is None: + want_created_by = current_row['id'] + is_admin = 1 if want_role in ('super_admin', 'admin') else 0 + pwd_hash = generate_password_hash(password, method='pbkdf2:sha256') + column_ids = data.get('column_ids') + if column_ids is None: + column_ids = [] + try: + conn = get_db() + with conn.cursor() as cur: + cur.execute( + "INSERT INTO users (username, password_hash, is_admin, role, created_by_id) VALUES (%s, %s, %s, %s, %s)", + (username, pwd_hash, is_admin, want_role, want_created_by), + ) + new_uid = cur.lastrowid + _set_user_column_permissions(cur, new_uid, column_ids) + conn.commit() + conn.close() + return jsonify({'success': True, 'msg': '用户创建成功'}) + except pymysql.IntegrityError: + return jsonify({'success': False, 'error': '用户名已存在'}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}) + + +@admin_api.route('/user/', methods=['PUT']) +@admin_required +def update_user(uid): + """更新用户(密码、角色)""" + data = request.get_json() or {} + password = data.get('password') + want_role = (data.get('role') or '').strip() or data.get('role') + role, current_row = get_current_admin_role() + if not role: + return jsonify({'success': False, 'error': '需要管理员权限'}), 403 + if want_role is None and not password: + return jsonify({'success': False, 'error': '请提供要修改的内容'}) + try: + conn = get_db() + with conn.cursor() as cur: + cur.execute("SELECT id, role, created_by_id FROM users WHERE id = %s", (uid,)) + target = cur.fetchone() + if not target: + conn.close() + return jsonify({'success': False, 'error': '用户不存在'}) + if role == 'admin': + if target['role'] != 'normal' or target.get('created_by_id') != current_row['id']: + conn.close() + return jsonify({'success': False, 'error': '只能编辑自己创建的普通用户'}), 403 + want_role = None + else: + if target.get('role') == 'super_admin': + conn.close() + return jsonify({'success': False, 'error': '不能修改超级管理员'}) + if want_role == 'super_admin': + return jsonify({'success': False, 'error': '不能将用户设为超级管理员'}) + if want_role not in ('admin', 'normal', None, ''): + want_role = None + if password: + if len(password) < 6: + conn.close() + return jsonify({'success': False, 'error': '密码至少6个字符'}) + pwd_hash = generate_password_hash(password, method='pbkdf2:sha256') + cur.execute("UPDATE users SET password_hash = %s WHERE id = %s", (pwd_hash, uid)) + if want_role is not None and want_role != '': + is_admin = 1 if want_role == 'admin' else 0 + cur.execute( + "UPDATE users SET is_admin = %s, role = %s WHERE id = %s", + (is_admin, want_role, uid), + ) + column_ids = data.get('column_ids') + if column_ids is not None: + _set_user_column_permissions(cur, uid, column_ids) + conn.commit() + conn.close() + return jsonify({'success': True, 'msg': '更新成功'}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}) + + +@admin_api.route('/user/', methods=['DELETE']) +@admin_required +def delete_user(uid): + """删除用户""" + if session.get('user_id') == uid: + return jsonify({'success': False, 'error': '不能删除当前登录账号'}) + role, current_row = get_current_admin_role() + if not role: + return jsonify({'success': False, 'error': '需要管理员权限'}), 403 + try: + conn = get_db() + with conn.cursor() as cur: + cur.execute("SELECT id, role, created_by_id FROM users WHERE id = %s", (uid,)) + target = cur.fetchone() + if not target: + conn.close() + return jsonify({'success': False, 'error': '用户不存在'}) + if target.get('role') == 'super_admin': + conn.close() + return jsonify({'success': False, 'error': '不能删除超级管理员'}) + if role == 'admin': + if target.get('role') != 'normal' or target.get('created_by_id') != current_row['id']: + conn.close() + return jsonify({'success': False, 'error': '只能删除自己创建的普通用户'}), 403 + cur.execute("DELETE FROM users WHERE id = %s", (uid,)) + affected = cur.rowcount + conn.commit() + conn.close() + if affected == 0: + return jsonify({'success': False, 'error': '用户不存在'}) + return jsonify({'success': True, 'msg': '删除成功'}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}) + + +# ---------- 生成历史 ---------- + +@admin_api.route('/history') +@admin_required +def history(): + """管理员分页获取所有生成记录""" + page = max(1, int(request.args.get('page', 1))) + page_size = min(50, max(10, int(request.args.get('page_size', 15)))) + offset = (page - 1) * page_size + user_id = request.args.get('user_id', type=int) + time_start = (request.args.get('time_start') or '').strip() + time_end = (request.args.get('time_end') or '').strip() + conditions, params = [], [] + if user_id: + conditions.append("h.user_id = %s") + params.append(user_id) + if time_start: + conditions.append("h.created_at >= %s") + params.append(time_start) + if time_end: + conditions.append("h.created_at <= %s") + params.append(time_end + ' 23:59:59' if len(time_end) <= 10 else time_end) + where_clause = " AND ".join(conditions) if conditions else "1=1" + params_count = params[:] + params.extend([page_size, offset]) + try: + conn = get_db() + with conn.cursor() as cur: + cur.execute( + """SELECT h.id, h.user_id, h.created_at, h.panel_type, h.original_urls, h.params, h.result_urls, + h.long_image_url, u.username + FROM image_history h + LEFT JOIN users u ON h.user_id = u.id + WHERE """ + where_clause + """ ORDER BY h.created_at DESC LIMIT %s OFFSET %s""", + params, + ) + rows = cur.fetchall() + cur.execute("SELECT COUNT(*) as total FROM image_history h WHERE " + where_clause, params_count) + total = cur.fetchone()['total'] + conn.close() + + def _parse_json(val, default=None): + if val is None: + return default if default is not None else [] + if isinstance(val, (list, dict)): + return val + try: + return json.loads(val) + except Exception: + return default if default is not None else [] + + items = [] + for r in rows: + items.append({ + 'id': r['id'], + 'user_id': r['user_id'], + 'username': r.get('username') or '-', + 'created_at': r['created_at'].strftime('%Y-%m-%d %H:%M') if r['created_at'] else '', + 'panel_type': r['panel_type'] or '', + 'original_urls': _parse_json(r['original_urls'], []), + 'params': _parse_json(r['params'], {}), + 'result_urls': _parse_json(r['result_urls'], []), + 'long_image_url': (r.get('long_image_url') or '').strip() or None, + }) + return jsonify({'success': True, 'items': items, 'total': total, 'page': page, 'page_size': page_size}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}) + + +# ---------- 栏目权限配置 ---------- + +@admin_api.route('/columns') +@admin_required +def list_columns(): + """获取栏目列表(用于栏目配置与用户权限选择)""" + try: + conn = get_db() + with conn.cursor() as cur: + cur.execute( + "SELECT id, name, column_key, created_at FROM columns ORDER BY id" + ) + rows = cur.fetchall() + conn.close() + items = [ + { + 'id': r['id'], + 'name': r['name'] or '', + 'column_key': r['column_key'] or '', + 'created_at': r['created_at'].strftime('%Y-%m-%d %H:%M') if r.get('created_at') else '', + } + for r in rows + ] + return jsonify({'success': True, 'items': items}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}) + + +@admin_api.route('/column', methods=['POST']) +@admin_required +def create_column(): + """新增栏目""" + data = request.get_json() or {} + name = (data.get('name') or '').strip() + column_key = (data.get('column_key') or '').strip() + if not name: + return jsonify({'success': False, 'error': '栏目名不能为空'}) + if not column_key: + return jsonify({'success': False, 'error': '栏目标识不能为空'}) + try: + conn = get_db() + with conn.cursor() as cur: + cur.execute( + "INSERT INTO columns (name, column_key) VALUES (%s, %s)", + (name, column_key), + ) + cid = cur.lastrowid + conn.commit() + conn.close() + return jsonify({'success': True, 'msg': '创建成功', 'id': cid}) + except pymysql.IntegrityError: + return jsonify({'success': False, 'error': '栏目标识已存在'}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}) + + +@admin_api.route('/column/', methods=['PUT']) +@admin_required +def update_column(cid): + """更新栏目""" + data = request.get_json() or {} + name = (data.get('name') or '').strip() + column_key = (data.get('column_key') or '').strip() + if not name: + return jsonify({'success': False, 'error': '栏目名不能为空'}) + if not column_key: + return jsonify({'success': False, 'error': '栏目标识不能为空'}) + try: + conn = get_db() + with conn.cursor() as cur: + cur.execute( + "UPDATE columns SET name = %s, column_key = %s WHERE id = %s", + (name, column_key, cid), + ) + if cur.rowcount == 0: + conn.close() + return jsonify({'success': False, 'error': '栏目不存在'}) + conn.commit() + conn.close() + return jsonify({'success': True, 'msg': '更新成功'}) + except pymysql.IntegrityError: + return jsonify({'success': False, 'error': '栏目标识已存在'}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}) + + +@admin_api.route('/column/', methods=['DELETE']) +@admin_required +def delete_column(cid): + """删除栏目(会同步删除用户栏目权限关联)""" + try: + conn = get_db() + with conn.cursor() as cur: + cur.execute("DELETE FROM user_column_permission WHERE column_id = %s", (cid,)) + cur.execute("DELETE FROM columns WHERE id = %s", (cid,)) + if cur.rowcount == 0: + conn.close() + return jsonify({'success': False, 'error': '栏目不存在'}) + conn.commit() + conn.close() + return jsonify({'success': True, 'msg': '删除成功'}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}) + + +@admin_api.route('/user//columns') +@admin_required +def get_user_columns(uid): + """获取某用户的栏目权限 ID 列表(编辑用户时回显)""" + try: + conn = get_db() + with conn.cursor() as cur: + cur.execute( + "SELECT column_id FROM user_column_permission WHERE user_id = %s", + (uid,), + ) + rows = cur.fetchall() + conn.close() + column_ids = [r['column_id'] for r in rows] + return jsonify({'success': True, 'column_ids': column_ids}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}) + + +@admin_api.route('/user//column-permissions') +@admin_required +def get_user_column_permissions(uid): + """根据用户获取其拥有的栏目权限详细信息""" + try: + conn = get_db() + with conn.cursor() as cur: + cur.execute( + """ + SELECT c.id, c.name, c.column_key, c.created_at + FROM user_column_permission ucp + JOIN columns c ON c.id = ucp.column_id + WHERE ucp.user_id = %s + ORDER BY c.id + """, + (uid,), + ) + rows = cur.fetchall() + conn.close() + items = [ + { + 'id': r['id'], + 'name': r['name'] or '', + 'column_key': r['column_key'] or '', + 'created_at': r['created_at'].strftime('%Y-%m-%d %H:%M') if r.get('created_at') else '', + } + for r in rows + ] + return jsonify({'success': True, 'items': items}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}) + + +def _set_user_column_permissions(cur, user_id, column_ids): + """设置用户栏目权限:先删后插。cur 为已打开的游标。""" + cur.execute("DELETE FROM user_column_permission WHERE user_id = %s", (user_id,)) + if column_ids: + column_ids = [int(x) for x in column_ids if x] + for cid in column_ids: + cur.execute( + "INSERT INTO user_column_permission (user_id, column_id) VALUES (%s, %s)", + (user_id, cid), + ) + + +# ---------- 版本管理(web_config) ---------- + +@admin_api.route('/versions') +@admin_required +def list_versions(): + """获取版本列表""" + try: + conn = get_db() + with conn.cursor() as cur: + cur.execute( + "SELECT id, version, file_url, created_at FROM web_config ORDER BY created_at DESC" + ) + rows = cur.fetchall() + conn.close() + items = [ + { + 'id': r['id'], + 'version': r['version'] or '', + 'file_url': r['file_url'] or '', + 'created_at': r['created_at'].strftime('%Y-%m-%d %H:%M') if r.get('created_at') else '', + } + for r in rows + ] + return jsonify({'success': True, 'items': items}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}) + + +@admin_api.route('/version', methods=['POST']) +@admin_required +def upload_version(): + """接收版本号 + zip 文件,上传到 OSS,写入 web_config""" + version = (request.form.get('version') or '').strip() + if not version: + return jsonify({'success': False, 'error': '请填写版本号'}) + file_storage = request.files.get('file') + if not file_storage or file_storage.filename == '': + return jsonify({'success': False, 'error': '请选择要上传的 zip 压缩包'}) + if not (file_storage.filename or '').lower().endswith('.zip'): + return jsonify({'success': False, 'error': '仅支持 .zip 格式'}) + try: + file_content = file_storage.read() + if not file_content: + return jsonify({'success': False, 'error': '文件为空'}) + safe_key = _safe_version_key(version) + key = f"{bucket_path}versions/{safe_key}.zip" + file_url = oss_upload_file(file_content, key) + conn = get_db() + with conn.cursor() as cur: + cur.execute( + "INSERT INTO web_config (version, file_url) VALUES (%s, %s)", + (version, file_url) + ) + conn.commit() + conn.close() + return jsonify({ + 'success': True, + 'version': version, + 'file_url': file_url, + 'msg': '上传成功', + }) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}) + + +# ---------- 数据去重总数据 ---------- + +@admin_api.route('/dedupe-total-data') +@admin_required +def list_dedupe_total_data(): + page = max(1, int(request.args.get('page', 1))) + page_size = min(100, max(1, int(request.args.get('page_size', 15)))) + keyword = (request.args.get('keyword') or '').strip() + data, error_response, status = _proxy_backend_java( + 'GET', + '/api/admin/dedupe-total-data', + params={'page': page, 'pageSize': page_size, 'keyword': keyword}, + ) + if error_response is not None: + return error_response, status + payload = data.get('data') or {} + items = [ + { + 'id': item.get('id'), + 'data_value': item.get('dataValue') or '', + 'created_at': (item.get('createdAt') or '').replace('T', ' ')[:16], + } + for item in (payload.get('items') or []) + ] + return jsonify({ + 'success': True, + 'items': items, + 'total': payload.get('total') or 0, + 'page': payload.get('page') or page, + 'page_size': payload.get('pageSize') or page_size, + }) + + +@admin_api.route('/dedupe-total-data/import', methods=['POST']) +@admin_required +def import_dedupe_total_data(): + file_storage = request.files.get('file') + if not file_storage or file_storage.filename == '': + return jsonify({'success': False, 'error': '请选择 Excel 文件'}) + files = { + 'file': (file_storage.filename, file_storage.stream, file_storage.mimetype or 'application/octet-stream') + } + result, error_response, status = _proxy_backend_java( + 'POST', + '/api/admin/dedupe-total-data/import', + files=files, + ) + if error_response is not None: + return error_response, status + summary = result.get('data') or {} + return jsonify({ + 'success': True, + 'msg': result.get('message') or '导入成功', + 'summary': { + 'total_rows': summary.get('totalRows') or 0, + 'asin_count': summary.get('asinCount') or 0, + 'inserted_count': summary.get('insertedCount') or 0, + 'skipped_count': summary.get('skippedCount') or 0, + }, + }) + + +@admin_api.route('/dedupe-total-data', methods=['POST']) +@admin_required +def create_dedupe_total_data(): + data = request.get_json() or {} + payload = {'dataValue': (data.get('data_value') or '').strip()} + result, error_response, status = _proxy_backend_java( + 'POST', + '/api/admin/dedupe-total-data', + json_data=payload, + ) + if error_response is not None: + return error_response, status + item = result.get('data') or {} + return jsonify({ + 'success': True, + 'msg': result.get('message') or '创建成功', + 'item': { + 'id': item.get('id'), + 'data_value': item.get('dataValue') or '', + 'created_at': (item.get('createdAt') or '').replace('T', ' ')[:16], + }, + }) + + +@admin_api.route('/dedupe-total-data/', methods=['PUT']) +@admin_required +def update_dedupe_total_data(item_id): + data = request.get_json() or {} + payload = {'dataValue': (data.get('data_value') or '').strip()} + result, error_response, status = _proxy_backend_java( + 'PUT', + f'/api/admin/dedupe-total-data/{item_id}', + json_data=payload, + ) + if error_response is not None: + return error_response, status + item = result.get('data') or {} + return jsonify({ + 'success': True, + 'msg': result.get('message') or '更新成功', + 'item': { + 'id': item.get('id'), + 'data_value': item.get('dataValue') or '', + 'created_at': (item.get('createdAt') or '').replace('T', ' ')[:16], + }, + }) + + +@admin_api.route('/dedupe-total-data/', methods=['DELETE']) +@admin_required +def delete_dedupe_total_data(item_id): + result, error_response, status = _proxy_backend_java( + 'DELETE', + f'/api/admin/dedupe-total-data/{item_id}', + ) + if error_response is not None: + return error_response, status + return jsonify({ + 'success': True, + 'msg': result.get('message') or '删除成功', + }) diff --git a/backend/blueprints/auth.py b/backend/blueprints/auth.py new file mode 100644 index 0000000..5a459c1 --- /dev/null +++ b/backend/blueprints/auth.py @@ -0,0 +1,74 @@ +""" +认证蓝图:登录、登出、登录状态校验 +""" +from flask import Blueprint, request, redirect, url_for, session, jsonify +from werkzeug.security import check_password_hash + +from utils.db import get_db +from utils.auth import login_required, is_session_user_valid +from utils.render import render_html + +auth = Blueprint('auth', __name__, url_prefix='') + + +@auth.route('/login', methods=['GET', 'POST']) +def login(): + if session.get('user_id') and is_session_user_valid(): + return redirect(url_for('main.admin_page')) + if request.method == 'POST': + data = request.get_json() if request.is_json else request.form + username = (data.get('username') or '').strip() + password = data.get('password') or '' + if not username or not password: + if request.is_json: + return jsonify({'success': False, 'error': '请输入用户名和密码'}) + return render_html('login.html', error='请输入用户名和密码') + try: + conn = get_db() + with conn.cursor() as cur: + cur.execute( + "SELECT id, password_hash, machine, is_admin FROM users WHERE username = %s", + (username,) + ) + row = cur.fetchone() + if row and check_password_hash(row['password_hash'], password): + session.permanent = True + session['user_id'] = row['id'] + session['username'] = username + if request.is_json: + return jsonify({'success': True, 'redirect': url_for('main.admin_page')}) + return redirect(url_for('main.admin_page')) + conn.close() + except Exception as e: + if request.is_json: + return jsonify({'success': False, 'error': str(e)}) + return render_html('login.html', error='登录失败,请稍后重试') + if request.is_json: + return jsonify({'success': False, 'error': '用户名或密码错误'}) + return render_html('login.html', error='用户名或密码错误') + return render_html('login.html') + + +@auth.route('/api/auth/check') +@login_required +def api_auth_check(): + """校验登录状态,用于页面加载时判断是否已登录""" + if not session.get('user_id'): + return jsonify({'logged_in': False}) + try: + conn = get_db() + with conn.cursor() as cur: + cur.execute("SELECT machine, is_admin FROM users WHERE id = %s", (session['user_id'],)) + row = cur.fetchone() + conn.close() + if not row: + return jsonify({'logged_in': False}) + except Exception: + return jsonify({'logged_in': False}) + return jsonify({'logged_in': True, 'redirect': url_for('main.admin_page')}) + + +@auth.route('/logout') +def logout(): + session.clear() + return redirect(url_for('auth.login')) diff --git a/backend/blueprints/main.py b/backend/blueprints/main.py new file mode 100644 index 0000000..7bbcdfc --- /dev/null +++ b/backend/blueprints/main.py @@ -0,0 +1,38 @@ +""" +主页面蓝图:首页、管理后台页、静态文件 +""" +import os +from flask import Blueprint, redirect, url_for, send_file, session + +from utils.auth import login_required, admin_required, is_session_user_valid +from utils.render import render_html + +main = Blueprint('main', __name__, url_prefix='') + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +STATIC_DIR = os.path.join(BASE_DIR, 'static') + + +@main.route('/') +def index(): + if session.get('user_id') and is_session_user_valid(): + return redirect(url_for('main.admin_page')) + return redirect(url_for('auth.login')) + + +@main.route('/admin') +@login_required +@admin_required +def admin_page(): + return render_html('admin.html') + + +@main.route('/static/') +def serve_static(filename): + """提供 static 目录及子目录下的静态文件访问""" + filepath = os.path.normpath(os.path.join(STATIC_DIR, filename)) + static_abs = os.path.abspath(STATIC_DIR) + file_abs = os.path.abspath(filepath) + if not file_abs.startswith(static_abs) or not os.path.isfile(file_abs): + return '', 404 + return send_file(file_abs, as_attachment=False) diff --git a/backend/blueprints/version.py b/backend/blueprints/version.py new file mode 100644 index 0000000..300d3cc --- /dev/null +++ b/backend/blueprints/version.py @@ -0,0 +1,40 @@ +""" +版本公开 API 蓝图:当前版本、最新版本下载链接 +""" +import os +from flask import Blueprint, jsonify + +from utils.db import get_db + +version_bp = Blueprint('version', __name__, url_prefix='/api') + + +@version_bp.route('/version') +def api_version(): + """检测更新:返回当前版本及可选的最新版本信息""" + return jsonify({ + 'version': os.environ.get('APP_VERSION', '1.0.0'), + 'desc': '', + 'url': os.environ.get('APP_UPDATE_URL', ''), + }) + + +@version_bp.route('/version/latest') +def api_version_latest(): + """GET 获取最新版本的下载链接(按创建时间取最新一条)""" + try: + conn = get_db() + with conn.cursor() as cur: + cur.execute( + "SELECT version, file_url FROM web_config ORDER BY created_at DESC LIMIT 1" + ) + row = cur.fetchone() + conn.close() + if not row: + return jsonify({'version': None, 'file_url': None}) + return jsonify({ + 'version': row['version'] or '', + 'file_url': row['file_url'] or '', + }) + except Exception as e: + return jsonify({'error': str(e)}), 500 diff --git a/backend/config.py b/backend/config.py new file mode 100644 index 0000000..f32d0ac --- /dev/null +++ b/backend/config.py @@ -0,0 +1,38 @@ +base_url = "https://api.coze.cn/v1" +coze_token = "sat_12nW40INoJxArrDXbY4lSCoudbqkOYTcphC99BP2efWyzxmsk4q81WDX3ezWgqZ5" +workflow_id = "7608812635877900322" +STITCH_WORKFLOW_ID = "7608813873483300907" + + +# MySQL 配置 +mysql_host = "8.136.19.173" +mysql_user = "aiimage" +mysql_password = "WTFrb5y6hNLz6hNy" # 请修改为您的数据库密码 +mysql_database = "aiimage" + + +cache_path = "./user_data" + +region = "cn-hangzhou" +endpoint = "oss-cn-hangzhou.aliyuncs.com" +bucket = "nanri-ai-images" +accessKeyId = "LTAI5tNpyvzMNz9f2dHarsm8" +accessKeySecret = "bQSZnFH455i8tzyOgeahJmUzwmhynz" +bucket_path = "nanri-image/" + +file_url_pre = f"https://{bucket}.oss-cn-hangzhou.aliyuncs.com/" + +import os +backend_java_base_url = os.environ.get('BACKEND_JAVA_BASE_URL', 'http://127.0.0.1:18080').rstrip('/') +os.environ['OSS_ACCESS_KEY_ID'] = accessKeyId +os.environ['OSS_ACCESS_KEY_SECRET'] = accessKeySecret +os.environ['SECRET_KEY'] = "ddffc7c1d02121d9554d7b080b2511b6" + + +debug = True +version = "1.0.0" +APP_UPDATE_URL = "" +os.environ['APP_VERSION'] = version +os.environ['APP_UPDATE_URL'] = version + + diff --git a/backend/requirement.txt b/backend/requirement.txt new file mode 100644 index 0000000..1eb81b2 --- /dev/null +++ b/backend/requirement.txt @@ -0,0 +1,61 @@ +alibabacloud-oss-v2==1.2.4 +annotated-types==0.7.0 +anyio==4.12.1 +Authlib==1.6.8 +blinker==1.9.0 +bottle==0.13.4 +certifi==2026.1.4 +cffi==2.0.0 +charset-normalizer==3.4.4 +click==8.1.8 +clr_loader==0.2.10 +colorama==0.4.6 +cozepy==0.20.0 +crcmod-plus==2.3.1 +cryptography==41.0.0 +distro==1.9.0 +et_xmlfile==2.0.0 +exceptiongroup==1.3.1 +Flask==3.1.3 +flask-cors==6.0.2 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +idna==3.11 +importlib_metadata==8.7.1 +itsdangerous==2.2.0 +Jinja2==3.1.6 +MarkupSafe==3.0.3 +Nuitka==2.8.6 +numpy==1.25.2 +openpyxl==3.1.5 +ordered-set==4.1.0 +pandas==2.3.3 +pillow==11.3.0 +pip==26.0.1 +proxy_tools==0.1.0 +psutil==7.2.2 +pycparser==2.23 +pycryptodome==3.23.0 +pydantic==2.12.5 +pydantic_core==2.41.5 +PyMySQL==1.1.2 +PyQt5==5.15.11 +PyQt5-Qt5==5.15.2 +PyQt5_sip==12.17.1 +python-dateutil==2.9.0.post0 +pythonnet==3.0.5 +pytz==2026.1.post1 +pywebview==6.1 +requests==2.32.5 +setuptools==80.9.0 +six==1.17.0 +typing_extensions==4.15.0 +typing-inspection==0.4.2 +tzdata==2025.3 +urllib3==2.6.3 +websockets==14.2 +Werkzeug==3.1.6 +wheel==0.45.1 +zipp==3.23.0 +zstandard==0.25.0 diff --git a/backend/static/bg.jpg b/backend/static/bg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..de6e38d8f381a8c2e06f259aab20665bda03e0ed GIT binary patch literal 164259 zcmbTec|4Ts`v?Av-9%BNY-NixLu4|_QVbf**k(aB=@d#vMSCeZ8;io}Z6?K1Vbd z?sRtqheHq?{D=H}jJP2hlhh`w;WZ|!PoAo&F-@DOt37?XHrd2mN=|UO`bwSw$5;2@a^#K;&@p@^T9D zii!#faP)5Y98u6zoN7dNQJTgIRyK|z%u7f=p*$)A z{Xry|Q*7riu(Nk?TTFBJ@T4<1{sDnYmM-Ilu3i(iHk=m`y?M*lZQFOm?A@1`wEw_C zG$S)BJLgDl9>1_iC=wT!l$M=4FR8q6vFg(0+MBneb@jI!?zFVFwRb#x^mpfr?w7A# z_q=)A+xPj)z~Io=;cwqZuyNrK`H5k{^TfdZ&$u*UTyhEu@(Rk>xNvgY;32Q6plC!^ zn(D$*4vv~;JTF0o;F^Bo>9Z*>Oh02ola1{E-N5$z{~Fmp1N(Pe zorsz|4i-;d6LCh~^pe`L-|FwtWpKTjIGQCvg~25XP7dWDiYXDav{m=W~wR*pW;bTG=#vSA(jM|w;W0oC<+Tv zgjk3w5hN_x!8p1XO>SyVMov5)JN~~9Qn)~&5G5kN9@zaV!MWsQ2f=_QKFERdi3;~D zE`%2hQn*+SyCGA8g!$_Q+ze8f%t8?u2vHD>l4%6w_aQhM0jB5wJiKU@@O>6_56TT< zk|l7TdkG;&C`@aZn~57Fg-hjR6N6n4?(qmABv+BZ^27$H%*Mt}v(y$SNU@t>H)E56 zjdfzY2#Kd?DVq{(TK@Ae{5AH3e~-rI6z&!;TRUw1{(UIIq+k<*%_WSCM!;p#2pr1P zWOhrFd^jwkD$R;0P$(kwF%SlerXx@+gejgu2|G%YpXtSqv{X3UXvLFA5J|2`Bt;ew zLh|8+RYep@NIafQ_u?boY)-F=AT(SgHAM(n?_kZR<|MPbWh;+#vA~va#Nv=l0+Pk< zPM(--*+QFIVsFXj045r4zmEjVUYH?Uri6pPjWL);kRWgtF0!6P8r;W}hruYwrsKcg zWqF}wm`$1S5vqPoKiQoWt^rqt^$voA)v@`H$1{Sk36^BZu8Cb&_V{00ko;%0F{hBN zUe3Q(rN%;on)ynY_25u0xCl18vIi{v)FYT}|9eMr_+MY2uma3Frh>_Ea1u#X5g{J? z8kY$E^2>Y?kyI`xei|VQRs6va?P%!tJ>M^Y(KRHQ}2lT_d{b;O+}cbsP3B-czWl1-5s z5zdQbvAn4Ju=6Aw^l~Q(To_X*Sxor>hPn+!(4sLy_EfiHf?IRWx|8DXvCvo&RiG4- zsb$rPZ&Fw)e9V4dM8HiaN*AKuY+RK{BoruhgRP)2lT|{gHB1zM2TUqhrj&qmPJqI% zfn%1WohvHLnXoL(bQJ&bUP}Vg5c>${<*DdMi1&trHxiIqlF)x2Thm+RvpeaVP-=*Q zQ4P!&73K?22D40<7@-kSRG3@W(u{Hu7w{t$*+R+|GzVKLBG}M`nW`~lP{j+!Nq)V9 z$RKQ=R2vp`8X*Lr1-mq?0lWeDNCvYqBT8Y}zyZL?LymxVE6Lu-1sqb)o>+4(I8irT zouOz&lmnzhw-Tj>aJqcdn@I*6fO~h0?<74cOi-6<7Xw7WMF6Fc^+c&NEQK1p*$ocX zC0le$09iF+5tzCfJ0vF=P!_XC9DG5Bzt~*>?0Wu29e_!&F{S2T4uGMY%!+`ZeFTaCp_92X47^GGq;e7U_kq?k+@asu5ghZ&7eBwP4&XXh4 z+2>lx;EnloZ*cA`1e?n}ITn%*MIMEZLc` zk4ergBD`av3b4|!9AKV~|MQ$HCX+}}?nDg;o>DnAHm6huRivn24lR+{JKW(ffb{+C zs|ks};=w<__U}O$-u-L-*j@b(n5uy5VA1JUV1Zc$OGnH|RFT|qMV~rr{MOEmgy*E7 zIUW~MD(9UD!4E zu2C5cImS89SFqFNt}2$NPNM0E1d5G>tit#igjPS8_*xjbiqJS-XX1Lv*qA4RjUE<} ztS1aY2PHiMxR+TFk_8x=E4D7oNoHc`11l|I8c8JV792nzETsfulpGi)DIB|zy*`ar z;kdS3fi4$ObT|q@mrO^u5RiK;UrHy&Y9NaL6B-B@7YRXEK>+c_PX61PFkFEs3eW+# z2iS{UM#d~;mK2gKn?#urfn8<&vL=ih0DNI#`=0HWC*of zq&OsPAK^I>EY0F@Esky8G+ihs%1ZQHmUZUM%<#wcjFx79+vb!yR@)wP$I%&*w1j(W zKU9XkpYv{AgQ%`|N0=SZ9umJ2t|{ z5;cf64iIxlQNB{EP9%&}(+nnor$+FSIr|Bp8+$O%08^jJtz#tOB*)Ox# z$O_3hLQsPtc%eZ|BbXe3?f(Q*7@iBz8X=YmA%d0`{6;~DE)PIZC}Sk16Bz}-3C0R= zB+?@pX8~x(L6C-XVgcC^!jKG@5C#_g2_Zn2;nQFAAHInNZoj(JI*g1?`;2j^iDUsH zCj_dAb7K<+?gZ;7#~$562{?)yee)AJ73pSrMGJzt-3|1Ea&(nq^b+`WER zdy#U?-z!QI2*I>q2eha8V8P_%y<@BQzpW&-yx;%n>_5)q2yGF zWBIXc%2vz2ZO@$>D_l52eiis75`Xkxt0NR}A9bvLa71H$Ug)x^$B|Qf#+^5|OST3EvuqjddVwc2 zbA`pNKE}6-fKtiqiUAa2A`U_H2dkL~dqBCs!B}1s7;6X&OAHPP3;zrKv6aOEM+EXI z7m@pn3T*gh=tE5*fh{T2sOayP+3+0!TgsylCcm}18bj?tNa2C zH~y{&BoA_UAsV7js^QQUvN8JuQ-$ykupyT=g-|6%w+mG68MemWQ};uOa^)^)Vv+Ib z1(e$A^ZZq8sdlL_wW{QjAT&ybGxNb|vas-~0D(b*R6*#FsIeF%63uXyq7_NdngCOf zTMSHIZXU6e?0&Dwcph(o{}nRpTz78GBD?t!=0R^n2;Gadhj*hyqmN9#d<4cSlsX(C z;B*MtMuZk3E{pTE1-KJk24}YlQBpj_78RmEmP!Hc(Y@$YMWT>Um~hC8rY$rSHELs= zCk*o+$Y#4CWs1}mHNpyDK@|rYgQqN+R5v9F#W+u1ThfHrVlFljmM2`16fT0(V6jq; zg)uLs<_Z3lPebNsU{6?}?Ig8fh>DTyPA>QxB1;4yBSNY$OOQb+2pKXj&j(c8CtcoJ z|FK~FQe~i&`|H&oXXiaCQNBYW`xO% zH<8vRPs<}k1%jSJRGxArw70)^MIYUyk-tsFXh)FoFtY@|NuHr(cXMD-NLVM?x6w!}9|iUZEJ+&+xW9NSSjBHPPb1U-Ar&eGSeg>* zEQxU8nvEjDo?|2xmakkIL>P#@@FoK9kRem=BQ4`;l|Jtgs!Y+EH6FopH@{+KFGk+j zs`j&W*Wp*qLx~@~6^t_p19VhreloBEP|Sc~6PWUemXN5VDcW(OeC6WxNzXYn&%(6H znyA}MPa!|HiHB3_+EOA!2FrIk>xR~-bmtN#yA+DH-#MOjPZwb@Hg@r9=PxQfu&Nlp zE$3z><-^Ppq|r|IaF#BTNy*A9q9BrxWB@1BmDHAnn@JF|&`^kjjNf7mI0Y8)%)p@p za>>nJp72CNtav<$ph+I0G!xQhn3oX-vIfHL1(_2H(H3-UrG zi~!>&&Ku?B0p1O$F~M#y%~2LIf6q0J4*8W#d&Euv;smCNg-jfSg;hwZ;ZrM84bw;v zQ0+YKWB~rTL>m66ldyqu=H>~+ASTbz*z8^M*CxZ&ekvvc_^O)c~L3@_#x&66zW zw0-73I`h&m_1PaoWiuQyf|j2nteHJr`u?Nw9;wz*E1NH=6;X}z50}u5hiv~|^3j_0 zA%ef?{j-s(ACjS;NPk;{sQ77B+Vz*Hl=ETTdnMZ+s>4gKH~EK+J^nzA)8|XLwQU=x znT#v&_6J-r*u3C*RYid9qx}oIPJg|7ylKkltLCp)4u68bZO=Lz{{A!d{pt@HvDt;9 zSF8Ktwx@hNc7mf)=NRBWn{>l&QQ6zJ(BJ{1ABi^Pp!ttpuBz|6h_c>B2OODy@3fkw zM~GJNLmd1oZ%M~V3vX`PHHAq7+x zu%H}Bxv|C5W_i&JWh;$EF<9&0k|GO}5NMbpDRXL!g~?nNbZ5*FAp}F9lGnpL7u>6h z-Fg;|kW>NuBVfR691nP$Oc0vTUSxR@&|`3>Uyq5bn*f=nyfMl!kKazYB$oGWl%J)( z$j4QEp{{}t-`u+G6AyXeE5>h?$>dbRYf39spgN_|d$mZJAnb{^u$H>}a5p!)hcsGS zY@t;32+0HVi#_6b^F{Cd)(9=6h8VGh7?1e|NEZqeJS66l=Om<^&`qKiCkhp)oEnh@ z$aDxtY6xkS_6y%e3M7ipTN6s8c1#6!e1zP-!SXeebOR1dmIUEHOZx*ejL`X^B70xr zUy7Q0e^3^ktLjaSiQ#XLSTYlxy3|fbznyNfef*R0#l5dKmz*-1I+yTaI$4>)4P+m_ zvs0H*NCW-}9268OH(!|YJ2Iw=O(o|4Sd_zpmnDJ`gMX4{8EMYC1Zc|SMf->d@mmN$ z7WT12V3BMHAq5Z?)d1)qT4NDaCajp6I$*jmwt(35b5|{h1bK_94*Z=Lj!4ab)TF6m z3z(Cj20FuTBgp163EeV)0B?rG8q#a$_ zlNfD0h2yI?gT7+6`%Q(@Q9h+2gX*}_lCyz%C$n<_qr6?gLJ^P?nmrLt;{0PI!hSS6 z!nCw-^UA!;d+YBu#oDS9mR`7!^QMQY5PCAD7QLc>VS2tz^xUxeI~McTm9Fg${O++> zpZJ4xqI|(5K51#o(ZIZK)U&C<0mru;`FOi=$abZ@=mf`8|Iv?TA3~wTv*F(MVtF5_ zmzs}7A}cdsoz8qGBHsgFG`r&}*;Kg9`IDJ&525nV9QUZZ0h7uzEn?gqrf?rDBS)6n zbR9huU#+p3@b#}K|M&O9xEG%ADnm(E65hsUAAVD@?)d(<>t;S!DGH(=)TGq%e5^Z? z8gSy47F|4+>L-~3x(mqw#}nETXeAP?Mk4c1ENA&AfziN(0Sv$~XR4KfJme@CmyMTI z5vnjB#Zs34c=WH_2eb((wyeN!t+2EZTYuDT8W2oaWm$q1lAww)B`gDLlkw8@TnUR7 zj!|9(VlgMYQC?qtq3yin7P(bzmr4>qvpE>;#ZT7VNQvKKX$azt0Kz6oC}?5IO_nq{ zMQrCXuP>4}Uh}==F~{LYTOu!EMNUy~Alg&7S*#Fpj8rAHV{1HzdW=Infs=^&-Xj5C z5p+RLypZsMDwRuSqg*P&hMS_|Njwry4a;!TeeBfQLeU%>_8qtVa>74phCyih61g6Y5ZPz&}_&F>ZuDg$?}D9=)M%H|y?M@dr8h0We> z;TTuMsHvGOE@FwqJDJh9{iYnUe1kS2`C$eLR6Q(h5)CSHGKMgTK~l71;x1%HI^@Kg zJa%tovzrvQg9-%H1xxaKilP?b8?`5-@PW6k6q$*P|S$6&N_Uc2Uk&HiosN3NeJ zONVc#_AmKZ_j1Nd?>(l7jn#6>AL03S;r5ht_YFhfFgjXA(Ym>JS8eI{&kb;SqT8`| z-^semkxRe#&EsW<1dmq5JsGc5WbM%d5dpO0P}$J5A>jd;%Zn<-ik~k$t>p532$H2% zb{wfSr{fU*W#S?pQhFjK??Tz@R2P>yo5_^JTJH3U1BQoNrY;?5=jS|qVp-S~b!=CC z%lXsmlN&3yC&yN1?z(?F@Eo`y+3tn1OF#4O@U&jmRFz^ztmpnw+p<8Ln2gS!NPTU4 zYe&g4c+nDTyR+K6A>=T)qSgcMqnT5hI&6lEH4f^Cb$5t$&u=z0wKO%vGq|K=4D(16 zMAI6e6)Pgruw)bXEkG(}guexai2{{uwSQqShM9_Xcq-5#9H1i3Pc98BER295}~XHbOXnG7qszbz%zL{)x0$*$0dW3u{U=nHj=i2UlYC2N))w3$)FO?h`;Ldgn?P0ns>bqdhj((cwkBPSE zDhOm5xiUKPD_#7(1Q@1e{F8J{#5tL84d}%SFHBRk1g@hX5=t%Qrso{>0&&@l7p~n( zXM_Va<|2)9c=IiLowtJunYJZ__vrZQz&c{-ngC@qSf5Q~FhF?4r0N+IHXEf{64*@Z zMtCyf5k;Uk{6je)LboVD3=)*YazWpUCP>5yCXd#lMP5D@?zo=0Q3qIw(e3|{nX_3D~=jf-hy%LY){8x+K69nHM+%DGNaGQbeTeQfYRR$ z%2By5DY@NZlVP3VlG_#P{4@8!+CVnc0rmh?5zGnb6V6vEhg>Sv;%RL!EnHEzXSsnI z?UABRXj7n6qPlL@8BI(ylNdk@ZT zPgPFk-VaTbD^6TJpnPZR^RFeVQ?hZv0osp(9vU|~alX`#zjx=E*PIQ7*|8Y3-8H1; zdUM=~^EP5QYT50)$$zE{+M+uqRH=I^Go>rE5}Cenuq|7C$X_}e7_ zjj=N3ugnHgA0VmNV;V+g!Jt9!#GqOB415^FSSsl`8;7Nm;LS*J0@PnHZzUo3*dam) z7s;uF+)^?fRgl%hv`NYwd}jD#`h^P`kYb|>rV~P!J4uZJIkYw0Yr-U74xK?#+fI4m z!{TRILf9bnEt=<}JX-ap8ky7rUd;Z(&2RtMDuK7hXxpr;-q>ME-Sa4F6N^qWO2p0= z0(uTgg&T!v1W^T(tS4k{q!@4y*(egDQKh(Oh7vEow;$rK5DGIDhGyJ!pk35%QP%!a zo#J%<-e4u+lmySvu;g^UX!f#~w%e;rwB+kEJNCUZTYGs`Ky-)1jJ4N)Wl}}@Q9YUt zG_`>0goW-^1YmVC2!;4X0wVfvSo3Dpi!4nM?XN|%+MtrVsHDCUU>EKeBgPOTA;GjH z>@&qGCe8H5iMj}sbVPzsADKRYIOuNLLWqy@^=+Hl;75^kDs#7K{2lwfB*ka$GiS!Z zQgv0RL6Lz&gu~Zh?PXw&k#WF|f5{I>R6@DP=f`W&eBiMg%Gx|C8bO=)~KSdu=MZIJPFe)L`42N|yKGor5ymY!by#3EGi)?B=!47IXwd7cnxv>|iv z)6z`NynM=2ut zJ{+-UmR0CTyL*RC^d+DGzq?rj<#(XDruE}!)5?K=IR zh?a_>2Pjqh0(4Kv6#j`ynnEGie44C{2V-xTTfqmv3VVQSYFH%4V;1(SA*vf(0*m2b zqFaF`tIHiH%4OlH#8O3TPNHW$ux9)g%!hr2pin{X1iTI6_dD=-tSI!3-F+zj9%oN$ zZ8N%L=7HPdbias5xiei8Cp)=-kdzM{9GyYf5iM1k&s!3U6MW?kC5I7zoa1w;`YIXB`0yrECe+k7m#^3T- z^R?ujwA+tb>9$7Rt@w~!O}pE+jDQ>E^=LwCRJnH!nky{j8$0-~4mtG-YRph|`9&Xr zpcm%ulY;n&2@O($nm4ehlYGh1}{a2>7Z~g9pa7W|{M@{X|}{n3RbVgX#&) z2b3tPpz`;e)Vm@Tw8;Dr z+GOLJS-s;U-LtsIfa~4jKyDm<+|PM@t^Rvf`r&q{em%{1RqbnZ*_vJ=ANA6%_$sTb z@aobB7l%&v>V5c$T=LuVqHx#VA-Lu=<*=n|zTfWk|3f>kJlK01Df`9s?64=b&7RWz zN`;R3*~N_t2aHGS$68ru>g>zY3*RZvqxqPS%;`FiiqcWC=T<#*6UvlS z3G5dOi*fv|cU(f|gdLk&a1o;ZOPw<;zruKX2*MS&^#Dn35h>3j9GE~o&V|MZ za%LdE;g*}4DAfg4r(}~SZ+?J~BGm9SNvHc;D8fu3SucRT941IQgIaNI0!}nT9f7_V zUKCqC$`^BaBqWOoo=o#p_wZH9Mh*BjJ`X6L_mI?Z#cJ$GVXvnW<65?o`NNJA>ATI- z4jg>Xx;$Ajl5*bPMF#_k6Ih;gmi~)oK+7J^SCy*S>_?&}vvG>V!W^u#^xydh{sJfi zyzw7=`L}rsz6|P%Q5AHRK^3HKqTmJC1OaB#?iX}lmjOJogMe!!^;7kmJblK`e=6x) zcD*B3YnF`V2|{EGtm^e|O9(6~oGL+GR74QSq*n$17dmj74`uMds1^0*(1JDBfGRg)GFlyd220(3I#%P~msX-n8vhRI>j- z?X~6B^8NJHP5^YA+b2VbSNV&T%gm9L^?i8C^ZZoY)hjY2c8xr=`yTXUU0 ziLyQwd}#IHs?zqQ7rEZv_;-0zkVmwh2l3T-i=%DvFVi;iDB+Krs%NWf>+R6u zz9}4A=Q=6=MxdXsh>g;J`3$19zl=42Cnf6Z*I%^GpWGAti^S}JBg)1x>#D= zXc)q@4F4E?kJq)PFl^wA~wy~7b@<1 zaIj&cdeF5EU2{j{!XI^gmIeaG7nvk6ou5Qwd9k-2M*tC}159Gvp zxWaZk>AYWzTl^hTc$w`~I9kSSp;_=31;+GVNLOS{K@wR@os1_lerpc07gz>^SA^P{ zJP=$i)gdPh`VYP6G{hl`17ebVq8-P^S1GNYMfb(xR59ipU2Nzg-;>6I(k@6DkwmQB z*R`#DrsEom!?#>WH_WM|N1~`l``751|Ai;hYHv~5?;D*X=Q7+(CZXyk*UW899j89} zYOA@6JzcE3)$HZfn=}v9{G_!%Vu^OMCHF4fCq6VlB@_HAN{j(y4 zMuo!UUEO>|FYWJAg|qATf85`aYrgL6=!?OUj)W=6Q3WxZo%@gUKh0miKxN%eCy46#^PI zthN5*p8ZS59Vh?rxfo5Govz-~x@pu;KVVmD?koLc{M5?u?1O&=we;k9@eAoVkx~(~ ze#r%~H9d8Y*$YCL>Lfy|r}AEgZs8M@IjC}!5>}+{bvYrUiIh-!qXajYC(v*93_)Wm zJ@`Q_lQP$fd-C``ZECT+ibk{We(Hfc936G8s-Matl6bn!kCSbmUB(-OmOg9|2FPWG zoL%y6yqr~iD)w!~WMU_=$0DmfW4hbz?xIfGUzba@4K@A4_Uc$9bJ#|=%1F;!LXrb; z^PaclCY1Q6MFfO;x_KMt2HS>i6hbeMIgNsz0YJ{Z9p!HBDrq4d_)qJ@M1;UZdBBW+ zh{lWhT=`9l%pdUgez>7AlH6pGP4=V(jV>;zy>p!=b#WefNe$YM;Td#BU?~o8B;Ydg zQwt2961q4q4Xg#qd@=|p>F484{vc(22gD-WadVONQ?W4m=PJXkTNp4V{6;TJ1mv#( zI1!5)M3#E)O=pyU8K-=E_{M;%^4H}URAhIrVyMCeUr^mP__b^X=^UrvC$eJA)Zk0n z2mGfJ)gqc+YOQFSL1}G!SC+9SJj{lU=1}hUttSUYADXJPp@wh1)MVqft%d`hp=+G2 zrK){C4ezd}ekvF2Ame_nJ&w2z#rO@!Rrt{k{*^R3=K2$< z_D{c1y`uUj!o2=5KrNtR|Ew2Jj~B!oMK_K0;aBgikpGBR<|;oWXn01DMy6CSWY5R} zAHpmNbD8DEk)a+BWblK+fKFhr_s**CpW{%vrf4K3E6D6abni zOz4yd*|32^8`d3g3F>?BNp2yQ82{>ag@^{36ZUqNtiuK(X6dgw7O^l5G83$BVTpw( zHsnrJ1WpZS;L-uoWf=I(tZiRbpdn&tfGJ#CgP1H>G_R zq^k!5pvOS~37DH{VSKpIF%i_*CeWkV>=x}$yytDymc7)VTwALMLtahG8Z!ozTjO=p zeSDHEtZ0G9R_UHwjwZ3Hbfcyb`Ri@`qEK3eiY|0Mluq7F)PPn6jNWWx@goV7pYx#i z$_VVHfDST&8EG&}mRoVKY%5%71h&#g#048v6J9J{*{^6yp7b=|d8Bm^NM!F%q@zF} z(3~zy+Z1IbC$fxw0lQTqeNH{b;e8$hs;Nu5#d4XwDfMYAq02u*_D7bCfq3N^iWMPA z2N+y1w_7q>R`HfKl6y0O;s}^(WZwXh+_CC#qjSN^4Hp*;aF*XPF@5#c!M}O2_WJcm z*?Hd;pX^wjCsw3gZFu_1M$VDB!fMI7jkRSPbly}@&Z<{!yaeYI0@LFD?`7jgON+iATY16~K8`O)=8L_6M=F*Yz% zc9s{k#;BO%!*ZQmX{gUp=4MSMDv|4rkIW=ymK2AI zqBmRUyq!~8{1LA@yQKL2Q0bxw`zuTp6dDSMyA1l5d@h$OzcF4Jr+bEI?73;n5A*!x zzSk4|h4q@C|Ap=OUA6*jVPKkzwn#3U_f4Sk8H5zm2~`D3(XuAL5OfPjE0{IOs^YTx z7wl{RT7)&I0N*g2X#d8u;1EQFsrn2q!v?xz_#~Q9JmCe(P^Q#uDuZc+wQQqU|Z2MDYwjm|RS?L2eUCc=oGi{uv!0J^MIO zRBZFed*Nen4j^I~@p}Efeph3&a{GN*0|p{;ib(j;SBzuVj&nY)d|2&HYPSs9b~bF? zK~uf0?^n98>YRdy3jM0T;g{Cmzs@E2Z#DBdx32OhLO1x(@^UyN=WU#DipX(&FyXe@ zd8X$GpAZr}K4e)^=OoT{Uv2P_t*O;LK2#TdyJUru54MWSjD`u%hz;|*hD1AY%kMt< zmZs=2$B!TiT7|YIY5hbz#s|C}J_>N`dA8H!CsLVuC($BBYs%BoDlVF2hOIUIXE?nFL`YE3Yd*IMn>I)5G4pFX@oN0)rW4&`0YU^%QbQXrA zgXOqHE1F|wh*(`KTtK-wL*$rv0N)Z>`s4CSt3#+Xh;^Zu+=}l`SehEC$NL6(uuUY^ z*Q`m^?9Mn2sd0Q4yZe};ohd@QkjX38Bh7D8uq)Csg54#C`(g)K=w`R!!c4*~L($Q( z;2!|FtkIiPV@R05z>Jf+u@+HTT^95`#G6xSU~5X({$8TdZ7-P23zr(|%zY*f5k?I# zXYg0eDIbWdq1k9r@|TP?dre($CLWn=k@x7}!=CTE6&GmnUalx^!&jiwE^$&VWHkUY z^8>&2t?n#5RO2B-yF-){vZ(u5S$|xM3bb4`pf`JCH7{7Q_WSNH5$p1D?CwQv{t%&e z?ZDAVbK;+qY7d+5-96y<;6v(Y`%JsH7E1O)$|q8Vf8BnPafO@l{VTKkAGN#OKCwy9 z)^dhb;*kTl9yuq75B&Ihe#O$HHQ5UJgQBQO&QD5e zx<$cK8+|5Qo93k=)@0uz$f4i1V>N6+WJsB+ZhzQ%T$+2K4v_GIvTHj^B-Ah2-c>Ntm z(U>8D*sjIkDygmD7SWL7lPR)mUHz7*Ym7C~-kCXLr1@K0yV}ZrkCzxjYEn;;d?SbtU08gKNoc+eHE^=PgE*JETM@g+;?{ETXWyUFe|oLb^qj zAr0nt*yHZ%lFxzqQ7qjvdLRzr_^`_px`jp1PuR7k*xhMEO-pDd`<7GaAVqB-8)cHH zJ-K`GTQapQJTAWeD`}8|*nR(r;A5ub#Y#M#;_e*Z+q1Gl;oR~QdG#&FXK1iCRbA2| z2HkWq5$LmV13!_P-p`hX2F^e5X-Pr5?!Vmd8K1%Xs^K~q#0fLF8doCB-Re}Beac9& zr{4YXu#5cXlV9!Q>Q3Itp8o8OkHz!@2YQ5C0A^A@i_)!mjX#lDpT{Y6OM<_kq4@0m zcSy-3wL=)yk;gzi3tE>T1|Yd2Y^XpcVKUSV89P*3u?}!1|6mNyQEl+C~pcvz0vhAywa&b+#o=mrWvM6M{KnzmxPWP>}3Dt zr8i1(W=%Y?HP4OxL9Dnn71-6uW4Z@x1dIKXVCO@K4js0;k>d9pBTBU?DqI_%p5!J3 zPz6wG)tu1bJ!$yOas7&hf^>4DlgsFX+RSPPzoCLetS>7E#s5bn!c~B?3{Y;1KZB>u=y=kz1*%_t6No;a-v?B zT}r#MrrLbl%mt+(+oLAU>xk<(Rln(NuARm4tT##asu@OUF{eeE=0$D7pUC#O%cSg0 z{I!`7q{ zt4H|vLmzgv7e*gzpI^E>BYvziqJJj+mV*9t+wr@;RWuzbf-XqF-ARSKQdq%la#LAWrpd3|hl`6L;NWe9Lvw zd8_;xCzK5b7q*@Kg5&O5(HD5Y>)!6TD!(sccWci&@aYK?3C?8h{6592P zL|&}Boe8&AL6pYpL1~wU(5D2B_-E@c@@o-_-E^D*R+2RCTr zbJE6MP}7Pl$fKOc@WfP!B(PjUi=b3u&OPjW&78ZM94Pq6!5^yjOxp+kzKAF|Bcq~f zz*^ukvTQn@d}D;Ov-LAPM>UcsbunWnl2 zeVSaFOuD2qXi%w;ZNVo()ly?ZWu#%!DqxDDtRe;-UaUG^iuNLNhI!HwB5i?Icyilc z$FuOa?10J3_8bFDA`7aW=3b3WI(nG*c}nh>y0rNSn$$6Dd~>=N|8zG;P!E`Fn7~+#yZ){gtYY0v#c|v2gV+i+%6?)rqj_K zu#YGda)(C%{^6k4vOE<5Ci$Cj%62Wnu4V?Pw`$FNSwo0O2*qdkH34m* zs1fpE$SdI}a@JT@OD-;NQr7r%etq0fAfRURij&(lI&5?{aj9K&RMoYRp&Yxm6VO*v?jC zzV=z{%L_mU3vc`pO)aVNz4NWN-=Di6@J5hswBwJe@wc`is_ZR)&z?g9>zxk2xn32iYOCKYLgIY1@+rgj6w@>@uMkI5+^HZ!MV_uFZOylQtx9OJ$^#j+G|GyYZ-n zSxjd6u$1n-vf59OLoD&*=K6;vEavI6hl?sLjc);a!g@k7C{G_Xnw;t|X~|WC{p6mC zGsm-9{S2goVt$Bb46650cVizhcz}=flH}whq!eN`k%>CkZ+ZfCb=U}kbxdG6I)*Yh zzH2mWHiA7~P?ZAZyU_tvSg>ZU+!0DH=1!m&NT9tK5b--aiPyya5)1F|X zV=@4wY#To0UZCz&!LED^FY!>n_eV&lY@o3kH~EfFr4va-uvm{r=4gqt1uCPh-F>l1 zX@_~u7;rJwV3_b53W{2@VSVCoKT7wAj>QgtB21O0#SVPZBI=*+$TJ-K_}o08uYjvM zb7Nd=u?>BT>Rp(j4&EbNSfT)2f6+yYN1E0%Mhxd0>$f;gF-NJu4Hp{uR*rz=< zD+S|4V?IXdnt)pA?2993S$vwYjf=?E*k$&iEcP>okC5w=*6g=>&0p4rqbOn5&3@Ny zrseA9;d0`%0dGPLs78+Z&6t|P0M~~eBk1W!oJdByv8F++4}lD;0kbxt<;k%?yCXo~ zt~S=KDyvFNwCF%rz!Yx4+wc!W+_&$5YG@{4+fh%K!-_L|cjC#7@(76r9E~&Hx0AnV z*LA1ndSuq%Ntb|{deFxY{zT?HXycHc)x;L2!3OL~N}?LQ|3{e#wGM9MQV}Id796o3 z0Fhej@9iOI5LpB4bgJrwgUa`LP>>kV4e{Jhi1!yTrzfNX$i;SgM?*?&hu;dTrch5P z0RrWlvO^SQjgPM9<|-bH{t-~Y4i(g9UvwL#7bu6Z~tG?p%eIpifJTbig1{$Uz zCTtUsipRaKQK?)X= zo?3S;X;t0Dc7FYAN>@?+YY*^^GR_I&IeX!wvJZUt#_gia@QrwlyL4s<7@wAWB zuN5ki^8K)5?T@~Ctxbz2yA9{h=xtOODAFJ;!WFADykDWqn}0U0B6EDikhxxRH#FWq zeXNVD-VPS45pV?cVZUp7KySc`$JCmk%>yF2J!JJ>4JCvHepfd^d{djxmTvrk;eY;B0BJsebLSds0A!v>b4_?6E zFcwYggwaGV9Vl}^4#`x_?0(nDek~%48^VBBW5lYw$)qSh_(=;ZIpQn-*3@UQa{L)0 zr!7t!U|ag?sZ*ZD$_eW&d}Kgt*ms?);I+|aW990&a$`Z}SPcJcvvymC;dgf4q}cC% z)kd)k7Wmc*QtCdxi>7C#c)dMTr@ckrK*Wjuvg>J)R>^*%O;>~8#H zmWUTSl z_fo+6~{%4c~rSGpltRMd-yw$KUG4d(v{*)(+BIYNbW_olK-+7%?7yDg@;Ax}%{YKF9 zwIzk!mzXF|i&FaftatKW7C%%6!fGAxPftZ5_KRFll3{B=mlo7Dup}8C8K9Bs7wx6K z_jS1;_$rpa{+d7LGY|5@oOMH#JFfa3A6h>PXOim2xUrPIA_O}FQ=u%tqpM+fJ!ws5 z%#<{>7(2+;Ei_zTs*(P7;>9y0tbeUWd;9zHY3Kqnl`u zVr@(qBA|uB0HM*9=$g_!2aXg}_x8Fk_BvkixU)zhGt<-cxUY|?CF}>CRU{`0_Lc(u z0Yw!PUrxMEaT+Zt`x2q?hg;PsQ!$yfETgaZn@N2~IejOkK$`I0C2)S{;+0ox{O88W z#c9|kNgsbCQ)&fG(`{dODE~OGGt$1c-AeC-e6Rn7X6mx3mG{o=?$s>}@#FA=rZxKz z*$&TRZQU-Lh)M%3pKY@o)QXsC)wkh_rhHoy{HOx%c-9s5^=<;MADhZXah#t>81GZu zsVsxxqlANB^AAUd`hQ;LH+Q6J^@f{T7r(x@dOXHfa(-9gW_7J-%wHj{nhNE(rng5n z3}o|bd}Ad| zz0^p7+BH)T+H71RNR!Pn_?|1T8&9dH++1kUbbhy;us(#rh<`HW#X-MOcyr4iA%wVd zr0B`gvxCApdq3K(the>NwEkp$AMbji{*90qe9a%e&s>)4x}VtaYL1%Jw4o*z_hVf? zcZ)^h;Z70b^}8*+_h(Y_Ovd|Xc{DwAS-VrQbG*0ibNdt)dvH=v)ydnNia&|(ZqT~9 zb+U(Y$Nt4fPT&2bbfgza$H4r#3 zHd-osB75SXktU?|t%m>VXm%^Pk$eK{^&?Tc(_L8rzJ;|aaUB&VL(be?Zo}n1Q4J%l zGg$BoNpuvi<Kmk{el>qnO<- zR0_$#lDehq;YWp}mbhT-GpW&|NG!)A&fEtILt*@xtS-n9BjwM|!qk-%i6TghvdnhQ z0_Z-6pr)AA4V;OYbxOYoX;k!~C$rL_w_|$|L5!{f>IIv$fPg~>qu5A->59gONv;<~ z3Ywj1OF^%})XZr*tFBc#=bRla;2zYvS`%?A|Nr6X+~b*W-~YeJA&F#&axM%B%{k_f zV@{j%q2!QQA*6$1B-ETwIWugSQwceAP|6`^Gm>-2`H-0N*7w%?^ZWh5_9%JeVY{#U zx?Zo>^Mc7wJ?AC9BidTXY07q;KhIkz>;W9V|LtlC(<@SZpIwNC2#6G#`Tr{UC{{Zhc;nfcJ=>7%H69tl)z(E@5r_gBZ~HgDZ4 zs<{M_T23eqX~ZBGtJStgt~&BDE!$?vH2KMX&7M2NPLfp51=JpQ3uIL7os@7F|?avaC{mwfN2DPb&Pk)dc0Z z%J@6o%G&V!8p-A0mcBrNzrpGQhhV^7MsG4u?eAK;6P*9BIP**L=JT4GebAL;&v^Q5 z&D80=M)mmS%>I`p1A(UYVDwE5=MBqyUqi6Y_CX*taEVL$tWjiNl8mam zKS!b6O!NvzEKFEq58ca$P7WDR>;dWmv~w>(CnBoBc}eLHrS@YkV2Wo z90)34dLgID+!&apD-xHHjfMCGTJN`{o+Z8BjRCQSfb(FKeU_9HrKf_6Urr`Tqkc^t3XSUFZ?e5O~5udXnu zcI`&cZ59*MlQQc3^BXjEB$LmO#K-InbK2#A*Bo-^WQvFucE+RhfBUx z`cgc5#>^$F#sDJW(NNF{eZBS@N0Z1&JAJ0~(9H-#wo{5eIGjuZ*XeTRd!dw5+nt36%&?36kWl%u=zd6~D^ zeu9cX-KWym^AAk6+x7P{?hPN5e?^wWInI`7l;5X%GjCmoMZWi}2xnm(b(A|uws_IE z5}541)xkG6la5!pFCDa7+_A^P)c&LQZS7Ia;)U#wbx14s?-g;Q9IczYWqvI&EuqES zg^hJ-2OCw7-AmJtEp=UjbF63?NnD%RH#dZL+pKXp5euvPE?Qrptw^P|8|rF$+w8dZ z?6;(O-(0D|j6ogmU$~Lel7KDRYmEsmQZUTW-|z0Cz{kP8Fv@-QNZ(lF*y%!rUfSu# zwW^FVQ?-895_Op4ed(IcqMfI4Tj*1}h)bLl1&-wV-|*wD`Ee}?SKfVq$v>u5cfH98 z6Es57D~-GE^g?Xe@a!#dazTcBEO%?Q1{#Gk9%AJV&QSqNMX8d;$CMVx-e&b0GLJ>@ zu`LHQ)77(7@;@SS)jlRCtk^RCYERnfzdjQ4JNru=O_&0(5Q!>$N^H?qk=kljH~lmy zMn%27Oj^$6Vb9lO*|j9&W~Dsc7<6E4<-Nqo@9Z{rUKPFZ8LR&>#;#*=Lv?wBlvAcP zRaI6aaS;O9_1m_fp73A~Ogt-dZm@EcfBp#(d|R@VT2qr+&Cp|ndns?xD%-pBN61_7 zRWm6EX7zXd$q80n?_YiiB5pXZaSG^@@DcaY1f~C|<}Jc(eN+7`P9~-7I`vmsvc5B9 zmHkp&3Z2qnRc1vXP1H^_t79MHMG6x|vSO{%qX)cixj&yTLK-vn9g0-KF+htL0*!m; zKg|ksv(-4zGk;=nS0OSeoh2Pr%wmGNfDb6R<0E~K>TGuyOwan1Yh0vsnD}le4L%Kw z$$$AW?K2bmW+4dB%6*~2(x%cyHFv|M;>P*z3d-iSuALC#1quv6oh5^L3@CHJ^UfbJ zm5k~SAs&tBLopQHf%3HLVC7I*7KVhouSZcKu)-vUEN0JwFF6INm=kO5~PRC55PaT!`JQP-9~Vf*BE4kZaHgBPLv-tjQ6s zz<&CSV_k9{m+SZ?72`?1hPqFuPMIiXc}ko-9C=6pQ^Db9meZhx!#Y~yIi|Ay>2!yu zBDV{5=1z9|bh%oYkvZppcEZwNDV^oaSwVCHKH(#XK;U0Kee&eF+@G-ASA;7dm3Rc) zbG?);ctrx7$~DXyjZF)dOjFnT^?ZE$*+H+9Df-BF^?*Tf^_OcV`j5fcoB^$0?g=__ zQcJp_BDY_#XLnt3@@0CBuUAf^H*q;ORAbHmg38n zDkbAJ4Nlmi=#}=|>yjTDcJHrKCjC(++bNO0gY|^^_lnZ@OCDG+cGT;URFv1OMUzz% zKRm|X=}(FpqWr93a*_(^h~ei2;w)^vU@u}b8>TBRo_RLDVk+8f;{ z6SQCz?9+r71;cn|4WFj8_F0f%aw>;i_w0Ms*=VohVyuD2mVr-EP&yq6X!X+&?kre3 zfiW#@eK0GK<;I$+Is*jy3TcM!?)CB+n#b8Ikl?4GL zKJa__UmnBaWZHkgFmDOE!@9td>pR~p$({n#$-B@`zJl+-F*ODLW1x2#H5USpJ0#ao zv;RG6MkHuL|A01~?tE5I4?#aJ8FZZQHjCH)U{8LfpFtoEnA$xjpF}RgAiE|I1t2xT zIyI06`p}3MZpVLZ%@MS)DO=(2g_aocz~{EY-dGiUI=$;2KN zp)Lj-|DMGXadqS&Z4*b8;FVZ1MLZn5*YH(`isC-hu59ak8n_Uw{jbm17GCacX}nYn zuEUStr3W9BVa{5ft6cnX;PLv*o5gr>maDnwvc0d7t3pmKZ{~~I8R`{|N7AIN+!OWp zJ3S>18ddM}_on3&VtJBQrIhybD|GSKuabnq24c<}Pv@OV_43xwJ34zC_puF9&_?p2 z9Xz$(dzn8qE4#Ht(@}7dI-VZ9d@*eFx0FfERRg_|+t+hGqmKo2r0$1`9>`2kOSK?lQxB51oo`ScLsO}%kxEYN(p4$rja#w#ehG)^wgFh04GWJ+dvMQ2|YkVae z?Nm3>Az!4RMbBVLd*z*u)BB`3Ft>89M{c`bpeUDo-Q$aHhwmg=vxMe)?DR&2@{zVo?)6UsAa9EKWgxO{?fq$v+H(t<_O$YMr+HO zrbxd?{#6_I?iO{R%lIT)2+Mtqk2V-`)IbNtdlw9 zpJ(pFj|jj#r!HEUF&Dh`9AY-W5r;=H$4ViOS@gRHfxysrL&WMYMAxibh{04#XfL+r zzA|rh-H7KW*KWxwaWEHpZHW0U1HLjRYGP6Hn9xI0o7J3X6$@*+{!q7t-mS}F6lj_J zn{VA`!YoklkBN^SaoB(MJ2)b*0kN zLWC}v_LTF5KkAb2VF#qfm<0XyPui9JXQ&Z_ zK>?2lTz}~aLQ5dQ%LXQ>MXod8ka?0DLnrz#C&M88%xTGH$>1*}=wtPKQdB9QkJ9)A?(mBN3({?H|tCxo-1Xe~)E6nhC`PJ1>3tu8K35*9GT}OODCgm`H zk_{$&P*jF-uZhUh_`VEd(jki=|4KgA=n%VAc;p`f9S@&^NgR0(@^c{)Q$V-x4FUA{ z|0Rj&)=e=BhC>#=&c+cSSAbjqKo;~KYo=vCDtL=zVxsR#a1_F7PkH(HTcIe%j&Zp{ zu6yK*VmEyp`_JcX^WIK;d;@j&nkJf(#^bubknuMBSbSN)G2TBm>b_y- zzx%udKM$AbX=(LzAq^CbdG)U|S6nY#a$(NxNEI)z%Du#PiB!^_HnEH4=(vKXl+-kB z@t7OGK@>M5FSj&W*ptLFz1E_!9lqg$7B(l!ZX)dxUhgvK=^9PkwO}qhr~DT}6tw3g z?x9ksy?cFpe}a=Mu$9LH;PbTKdnDPJ{v==nhO?gE-yD9@_@rOr?0r^tBWLKIoMzkG zK2(Cqh^DRRTE>&yhpwaq&I(Twdzve$I0tVDQjqb7F8DMYQ_(n=Odwf`Y*Fm(=0SY8XWdzN?gK%4WJw*7$0@sul!dB!J}bpGBbE_)#b-?n)C&y@StD%{M@s= zR;($p59%J%7<_%3oot~$25%Anjny$p^e8|!SPI5irCQWp56_*4oe){rf0}ORcbmPI z<15eThLw}~p)8uPtc<&_R4YlH1u_RW`9j{^tE-}~D}wL|J)N!VaO923SBkIkNoO(X zC9jCxe<2T!ZvV4Mi7 zX-YtHLNn)KEUs%@X4^65%i-ttQSKSn1y*hMfS?;AoXnO$JJ+jr3R%nR8t948hF|Ja`JA-JB+t!mz zuhUv2NBrs;)zkpK4(>CvtF7yW)bX8Sy3udF9GlR>Zfl^}f5N=?MAK(j@;gw9I~P^y z&~DW)G&a-!LIV6z&&yj4(i_L`U?inK;obfF(fVf$?@=w~f6O!tgSGOgECtjET zfT2_6bB84=9yW-;RSYW8A;AWcO`Q1CvyNXe1v7t44Beq(z-rq#cyTbLwR6JWHgb(; zNryU3!ClikoXs3gDs6uG5H4{^#-)5r?lixuqdyK+nt)F>$`WqwcFEaBePAM7E*HKo zas1HF3(Rb%L^_MNqi{+$hn(czXZ@Oe+!D2c^YL)F>d z1Smo|V{1N7SWD)AnwczccOHUsWMD%@M~tRkA41j@Nh|r_Acoy1XL@S)`IYH{!&z}y z5&S2)hXI9Bx2eSnigY;r0w`TVhhwwQNOU3+yln4T23#&kpv~itf*U|H^azTEk>M}7 zyVm;C?#Zp;+yJ>8zp)0E(99at(MbA@=sv3OQFVXg z%V%@a@*F@cJO8SU)1|;0R(HoLy11*1jvNYNs(!m2xLf$q6?Ggn3iOg)#I zu4agN0>KA{zehCKWq`*fkMuszAC)WfWxToDW4u*F+dxR;eY0-3q^q6Ceemnk+ST0A zgOXz{$Gy3JSrTP#5$E+-zM7oQcA&pga;f^BvlY|0pnWZ;i<|d;$uUk&AjYBJ$<{cg zq<39sQR8S2_D6Cyn}CazPOQ}HIR{9Qd!}Iq%b}g~24LX2Rc_}yM;^e69}>m)r{^U^ ziHwJ%b1b+60f0xVl&NTv;h5(f5s0AKx)H`K>n%Wa=%Xo_jT(=3uARj9U2kC7oKkUq`Q;?Z*^YTW)LS>r=n_gKb>En zmn>bP-w`>O=Zd*(VK)%G5$yhYmb@ysn%!tr)^8|24jP!)*lF_`lls)zW{V`-kf5&@ z_N-xZ<>!yM>VkRVC`*df>a8TITN${G19(7&L+D(E8M)6w_x9x?j;58J8&*S`4y`R# zqI+2x4BgS~Qf?lD>+GAQuMsC{Vckn&Vw!Kb|OlT z&!)+nLd54`KLKpxnBa8@L5YcS=#zpqh z=$Cs1g$@g-C<&gmj(5~Pl(hN9vFO}wB11dI!++cLU_pBfq1#G&MW;V%=|m8g3Etq; zjr4ow3qMGT^Fuq|V&3mIBjw(v6;si!I~H`pXQj4X#HrI}M`J^6<|USTQOo|rp^o0R ze<7T~_ril7?e%}XFp7QnOrRcTBfPM1HNoPMftq_3lr6+uEoFP>5Bo;( zX-~ew)Hn!|TKXRhdV8ZxKjlYToK6Gh3P>ych1BJ)S%tPPm`}uwiSG}fuY5jW0KPz+ zMJ4d`0e|2jjO(Zp$v}aqcad ze<4D)jY~x03H`#qkTxy^cxrM~uKKVAVUC%$WBkrrVzKb+(eoM1gAX3l8TO3nA#GZq z#nLeuaY!6#mE9!=Jsvo=>=t*Pk+QWYA^8i@z&6TC*+hivNPE_Tx9+V2$!0|}ihF2E zW@pq|psy+F^g&~a+4onX{WW@9i=!=ePWwf#el8i%mD@S>xJ|sfLtKnp&HY`rE_(44 zo4P(41lR%w`LdK;S?(K-o!JW<7^gIO12@g@@=Shj155q8ti{SlgMQK!WYi-cXG{F7 zJ6I5a*Quob@+IjOd#$?EMPHWT#ULqMVrYdc)p8kEU0<}!tcYwP?6Xd4-h@l7*F^kM zkI}y2w!7jWx|5F}<;)aL*DK%g?H}5NL-{TK6bomcR|{Uw9rLb1n3Xfkw*+}I6WPA+ zPdxs9;K{Gpn^vX|3g#!TLlfv#U}gFX;WKjDy|vRT4A?=Tv2j!vst4v{a5Z?-l{uA* zg5VY|2dCOkJn3(S)`iSZ7{zf(=A2nDOjs~D{YeGtItxGWlW6VqsIzsK5Csk_gGunJ zj{-`aODZ(dInVR6M-jcTC*$xnARy>Dl7}AXWJ3FlyVUKo!+n2x-i2ink7rmPPrNHJ zDtX~6$=~!#y_~P+T{J(wf578)h0zINS3^>UAzm%!T)!Editm4FaW2wJ?1bGg!)bdN zZmD8)idNB2U4{^XZz;syoI$A*dJM*O58+X81J4eqQ>Xj+&p?W10iiD3^O@yW3uas< z2Usqo2zHdzm1(1rZrc7Zd;>ZZ?Y=ifhb8?i{tO6?n^&(?S7@O4tMeqilEcBz76)in zNl@VQE`a`lJ(~)lZ-!?W&qlld)1Ay2tq%i@qo4lM7eoIi;tSUi35$raL)uDU+_o+& zb4f*>QqrBF{nlSx9)o(si|MjjeTZykmTX3}Mq$=m2rR$Y^XW%%8h+y;cbuC{>{ zuQ)+E_X+FFMW=FzXr=q-(*nu3aA%XnR(g2UcAZM0h@n{zDdF^M(bLG0!AOvw+R3uFDNc`x2niJEgoDS4*|N>A3J z(0X$jH}>1v{T+Jqi3KbJDs5n`fo*UN2iN84?Z_o0$2Bp7L+T)K3gv-e>j7#i^!RC_ zw|=L_(*int+LJFDPeX*7|1E@wZ(I1c7XsKk{i)LiAM^*az)-7~-lOaM1$uq}*XZKK z(^p+JIo#F4TewR9530=bvDR!CeR|*D>W1{}%PJ>p-Th*RC3B5ZxqU3pf^NhevizvN z;CO&wjI%md4FOg!D&4PXPhEfW zS|F;~TBbAD-TlB5(Zl5~We9#Qo8vTFoC#hFD`)TPk!mWx?<%;$m$a|iBDk~rj_HR9 z-K^ZYQNkta!07lFg;!hIViQzH6X9*pF1`V!E{5sD(RxM8IOF$8t6wpk*lM`7;L@A{ zEteWd)F=&58cz$Ie{ZFiT-lLm)(CIv%J>VpNdl7Z;Apy8;o5HkUv^Agb9sDEMK2=f z_L=hY+=3Uj6$P)?JMUM=F!d`K-QEYC*`V=EAbsebmAwi0mrJ-OL^f%&>oCMAk6nE` zXfQmpK=5nVCbazx%LDj?3e#8|Z4W-7sWUzLZh;-5}7dpOqw7&+SpSeBE;_f_^d(@Ou*jb2|r| z`8%_rak!$WV}ew--1PiOl|o&b@Q%fara(?qd9u`YR&EpiWYY)H9x^}zT;BGn7o0N^ zX-F#6?{%q&6b-j@!r`S`*OZnt{kimV#qHea7Y9UsjKqC_Ay% z1~|$@YgFT6ALs8bI7}Vd|Miqo4MZ+`WX)hA{E`Q7R1GBDrp_Y5l^t-)W`o%wMd2$~ ztG5^{9--A(Oqq<$c_f*Kwu+d~ZkNJy!|5f4^~yPxwoBSx1YJV@zz`8@R$%p1H5>Ub zjd${pXaU?mp0IroD`d^j1&R`+Nn*svqZ`I*Qhfu(%K&eekibBnxsmmJj7L=7Ou*+&@*uAej8Te9HLk~T*5 zhjWPlD9AtaR{s0JN+UnLGi2+auB<31-jx}bs8LgiGy|@K2f36kOg0a1)Nq=9&7DzO z=|=g3n9xmyOgKBI^LGJ?j_@5Nw#z-3sYA9oyZ5*KHV3JBwIE%)=CfM|^K@@Ik9~=W zz!T45RrEU*Yk^_r-MM(dG;gzwUJ_j=XUEadsrZ7xL27Ibv@Uk(j-TL^U_fJ2ef;F+ z0dYXyhJX2i-R6R3o61?fUw0Jj%9&A5+8XN~2TA2se~egH(%w}kQ@Ap%1){4&TJOq9 zt^|`V+CP8)c8K#}eJcS|Zp7xPb5gcIv`+m5k8sNfUHm8cc&$~~U&!)b2z$|v6{af1 zWiZ?-&bCMADs=MgAS#iqc!HEhZjg63W_=|}0W69y0-nKOB671)N6Q-Ey$NuOb4GIe zjV&)zX?8UDVLr#e75wbB)v+KUQlv`*Y3e6)OPUeIvsvWKM{o|!-WgDwjjX^YQ%}f; zW)QvN9My3FG3A&!-nipp^1yJR93I;w=MBcZ0!DtPmOeF6qJc;?kuu!3+B zZ0F$PPG$*!wm@(6hY;wwlun15C|@|UZr}!uP@ZIYk@Ix7kkiUpk>Le?Ho8wsT0RJf zqILOAV1I(Ch)`vPqvXfL2j+5y@MJzMO*ZIn7|w#odR<~FQwt42 z!Q@OX8h%WxFp`B`LZ;%*lGt1vNu0)Ayf_&cJRL6<;`!W>#0B<966PVp47_w8tL!RA{*0k?z+L_Y_^UaDdrD)C# zHd>7kJVNQLWmT;HettnK7r65rhRB$U{ADA!gNtx`gPf^=n~5DY(0h3}J_9uX`y z%<2C^qBCp|vUsLB_#i^jgANs}DG{}Xy`gLl>!U!A_&TiC1l;#K<|X3W&(#)t()wfE zqhI1l!n-GF2 z`}c{A+WLj)<2V7tE72w6n)q`M{k!D2U2e2nZkEW)eB;INrR26}^2bPjt5@1Si?TnO ziY%fUQ9YgaduS`dr^EkyVwcWw*1SMkqk5Y3Y=4P@W1bUyuOJ}#sCA96pxL|^sF=YL z%~HvifVv4@kiNDC1-H6KeV0t@_9uRGXs?^42V-`ai~R1`;E62#2KkfnhX#DzPns$O z(F_PdBs3vGR~hRimCSYYdxF-Vk5>baSPYt$G%iTdR#4<)=LVkd$I~tVe@oqygB^jV zrQuV_FvJ7#-zB{^9QX%|GgOoF_K~LVGLvhC0&^sXJra+k7sU#%dhp1l=-xi=CBshH z7PFYIZuIE~g@`<~x*e$fPJa5u%szIFwm4hy1%Dk(8Kj@G+n}E`P&!V$&cE`psl>2& z?A!LN>!vc!zUqnx@nl!mjN!5}?H&pK*t=pw33qf*O=CLo(e`X+L#SVs&%VpJ;Ldg> zlGe{bl{}{KuIMMT**5o^y*^TQSbWaadV)%#o)58jo62zIn*O+u1?xInGx24Of$bhk^hb^L9x_?Oq+@fG>6ejo7Ija7!IH6#xfMaG2 zT)|JX>@rIQyVI2#VA-7%M{j5=%`yG65-5c`(3yDk`C3#UwMHp5Mi}?5B@F<6gaU-ty$H?n)&O1zK=Vxr>6DGVnDeq9v9VtRqUAgv7 z(kQ+J1BciZvy(zAx#bQg_fgLc7Yv>>JHhmSW6g7o-oNyodp<;Eur-mK`3vc|9P|*U z5{of6OVOxt=G^>)QS2jla1F!EH9O1=T6EW$)l9>jILD6U}a#I!jkAXs@*`f{1b{H7qv{ zp4UB4LT$eg6bKXE-;mg&Lw^lqMKnw0=7-vq6CDxb*_HEbuosU+_}iA*D;A~qL+>wX zCtiLpeO@59*)DM;Ma?Kai1~B*W3M|E+&53qdM5`%UiO%qlv(u@+--^!=WoqxyX7ol=oLDruLyFy3^W-Gf*qv}8 zhx|p8wb10hTHlFLeVMLw@Ms+KUG883YJiEHN2G{~G_2fU-X%6qW8#(?7^{`hy#pQ%})d;<#O4=%nbtP3^kJU@v6_r=n zg~-aWbJr1)=dPsRZ?9L+3gF6hEz!MCS9$DW!Gk@%#ub#0Sj5XD z8s}rrRB#4+*rt;vm83pz;x=1U@~2*d6Z*PGGwU&t!+U|!1j7FRKhc!j#&>}akr18K z)ml<|f~ix(gwajNMPC!d6VhMENjxGr?HOE2O9%COSC@|WaD|qymJV>`9vY<54j&G8 z9sOHOw{&$Azt^{km-7HPFrd+Xp1)`U<&)-Noy{~Y?EkmI!Q;PegR-oBNlPH%;kHLw z)u>K3r^DK7T}8=)jX>UD$KE#b-FUh#H-BrAiC7XG>FjtIr@JoE1^k?Jb@lm)Sh^`v zKY#}Cg78K$V}_STV(20G+MLl#U@lbm*7%tBuIPRkk)n~3mvCa4cCU56AY5<|w^^8Y z^%nFm#B8wq$o;|T{Q8-oeyWtAtt_kB;K@yp zSmTn#MfGH#Kto9na7W=Y*II`H;1iv3TI2Y!cf8a5(EB*81IqxFydAnwO*VRw?9&~g z2C9~?Wo{Wk()?;J>mR4B!K+5#r&fHXW;jLz$p-foPnfi%#4O#8-x_hk&7)PY+~?jh zDr->mhRr#?s|fj;rq_yaF0>Bj<);_hs=sA7N7xfCc0?@eE1buFyyN;kZ*SmH+KSNT z3GFLyzybCCOyuv_;G7!i3m;DAHfSC+Th*pXmezJQ21M4_MnCd{JAE1Ogu^wE;q>E) z936il@63|jSPb+pY~rF$CXm7fmG}DF7PMvD1nud*xPg}yT7v>F-5)Go?^28Y7rFVj z9#T#d;&_&vboL|WMSbO?_=uN|>2aYUsAs%)nU9ixRNC;3tqCtgaYW&MqhdLl}4Z2NMO#>oD%Ez7};~ zJH0fma_R7nQ4qI!pk#-Q(`vSSbS>5{iqCxlSYS`8Rd|I%LVL0|h(9MjI0 zP`S5mtc1d_;PzPmLR4)))veA60kYI~w*IsgpXZr1l>?JYoyY;AqqKwk*ALr44Divo zpK%)shK1%2Sv};{HqDl_tfdt?BlGCzB^48TtQ7=PR8~UQn(0RWLe5Mtbn%=fwCWgd zgs7iTaY4e4CZWpEm%^b8ZSPyvxwAx5sQL@mjKRq_0{d>%GrvWA{j%F1-oRFmFqOQ) zj=pO_9R{#LZi(;shkF}lN4Gu|JEgl6LYaAdd`S z`ctXx^;STlF*DnS;RHm5{&?QM7#Q5Yq$hFC>JRuWqKN!4Iw0tstFm^rR)P76#nBDoYwt(Zs|%jkycF(}8w!l}hvUp(>6@T#0B!?zX_ z&}AOMxIp9LH4_e{bp>VZ4}UyfH*csjsJo$`_=-DceDJwLQY33Xoqp5WM)Gjw>6@FcsU3{w~f>l$5gL0#wnb~ia3Nvty0mw3yyx7cNU9GFW1Ti z)D228%H>xu&#*K{3|~#->ZQ*qQU>?MlbALIT7KmPnd?#t$6^iVxcZG&nbdVXSu!k( zN&&??fP};h8d4?ksYyz9XMQV}bVTN1@d6sUiE;VUzL-HD9U&LN(pZ73}z zw*+&@qFT-t1F(#!2um~y6QJY2w$yL9kNA^G0vaq5dWTF8!Im5#96&JR6#G7swMDGbK2wNk<3KEx+y1!vbN=YC zed#F;mxraKAM$?8zAJ+%luErv$OU9tE4uny*aD@HBbMA^E!h+@WBJQ7{79i(XcgEo zl?SDdt%fXH$na3v)@b!{bEf&2oj(oW)__sfxsuj`7!zzyx=zn!x2Tle@83UIRbWedZb3(!n%7#gq?-rl%3C(f1XIu4RU7qPg-#uh!Yu%e+>2M9q8;iY}2|H)L_7l3DYAbudpCor3wfd6tTTA))E8 ze4aWJghzsiN26)-FF%bn)5%^P>mjuA4PI0IV&c&d>RQhS8xP3&)j6P0CW0n{_U(sIo8qBDB z9?SCww$tZ+)WJQAOJ;{9-kkwLUyP?+GUeA5-k%LUW)#SxV>EFO&%)Lm~;Pp+eRGO}I-20@gB=TOrnhi2!Il-tU|3awVquJ0n_BevfG8L8`oLqp_ zISV2*c4W+8D~QE3Ta_yPg#;tEA-F_>9MJ*m?)g#-1`M#%1R>76F5#uCIVXe{H0dZr zrgAxb=z*^$bFk~xVL(!1fdp=rTEA%cscVf)WVo|n!s5s&GIw`m6ThiVU;h{4w>z2@ zN3af<`EKy~wAC}hHTJRTl(HXpu2#9evf3^qW97r{+(kRdzSmgvM;hVfn07q9MtP)u z)Rk)A%v5>XK`G_Yne|=tw`;!}^trTLJe{s~{UT#1hpwDn>+jiDS0>xOY?QTZm9p&1 zi2D2t;3dZQRmX)N?T;@O7f7KO9G$eXOnDZU)Q9FK7&Krm23FK!-aB2~|8#4xYOem) zY4-&~FR=wz5YRqdQVGjURdbjXs6*Jl!q!wPWo2*FS@}ttk55a0@zF_?MWumfNGYk% zx1Ty3&Bg$%Z2K4Jq(F-H>OZa^p;{vU-E0o?nD#(a7SC5Gerkm;ih)B?2FHM z$*hyn@F4v2o%rPVw{tBgEFT}U8u@jasYZ_X-HiIF__9bM-77fj2di+U4yM-#dujR9 zWW9YhMF=+H*uGVmXx(%O}j+cm!sPp1I4Mq!fkI zs5YjXN3=MiyXiKG);z?#*+u6C)YMaN8@b+-meDGOSVkd7zK3`~S4wEgHh^iC zK=?hILTZHULGhJC0wwvyQ%t?}TbKMR|2+l1dV%UJ@^Y?R;WF-l2+s1I#1GS^y58!^ zYKk#AU%FSuO&i+(G$TtJZ50})E0&#bx!Jb?kF-wgw%M<2q;6Oz8D=;BN)udD(DA*d zyQZFy#RHGwzU{5fXEt@u3D;$3A|Sl|BaTEEczOYqsq(7oV%~wL?%P269hN0dQ=l@q zvkww%UCkP>8c*}SUpqN)?16IXb7FD(z|z|>Vu#?qUP;|&g@ysgZ}JyXMD#TkBrRT# zh&>(4TqPcqKG3g`i+JF8E7b0_qY>`O4}qzME z5=ODJtzb4Tqk!ux=B6QCY??^Jv6BYTk$I;T+C@%#i@o)3mAVGKHV}LH%AHlC;Rubz zVE244ZJmDl-(5BVU-+W2<1NQgts`PSyJ#SQqik-0(imgI<-$d{5y&r)1X{c(io)X> zB&l&4tg?Gt<7+l`EAQ&0-t|VH;LaULWdVk3oB@y?7Zm*_+TdIMV^A(Z#=XHK{-aFT*JI$r}}eU1+i3fATNL42g2=l zIfIHYkHgd-9XNr&YGBkCVDPnAS32%;LK#ox8pQhSyI`ve~rZ&#_tj zEj6wmZ%%1}xXqr@K`1~VJsD{KRvPi)#Kq0W2}8NTCu}ml#=5EqM3!52OFpc?U%!+v z?`c<#ZS>=QPCT^lIve%d<)yi3#l14R^HTVuN2cI)UA6G&rfL#ypkCOco8NbBI3K3T zg-swOVs_U5D~4I+@^iGyGC<-as{f((>Y|m2d<@7bhgjfxE4RGEY+haMyYvYSQ zo>BdcQ)6gGGHB28;u*bQ-#Fj#_zG={oMaLc!PxCnvux~UtDh=t#AC$R)!?Y#r4>sV zX|4)6Hcno@0u;TVOT z%|`!to9o(2y2e9?2gRj&iZRav7CK3GR6t1>X>D+r83#fB(XChEKY|jLA zuWJ4nA~oRKkA~%da@krrsMU1sH)ak;%da6n&5Mr!PO}7y8@_c-ZecHiAMPKDbUEc& zs*U_{^pl^*u!7#I${PGmi3T_*$w1_#)yJd3Gp`Sv((ZId{FUEdAU zY`rL$a$eav!sgE^>?8TBN}v)ceK_X>z!I!zNh7zznbyoTe&eXqnkV4p+q%L8OoPPf zVTt^wPYlUIMbPBMEi*1^+Dp z0E1tROfX#zYF@qn=82&@wSsw&_TH2;gYYXQuF1u!)yqmJ~&qV<&X^tjW7 zKjE!Ef5b)xKpDC(eo)9fi>mz8d}-<0E#>4zBT%Jt3ZS8ndrc+iR$?+8D_!p67F%AK z74mAqBv_fwq(W#4u4Ya4E#_?cA{B){oZT5-%v*j>wc$K}fvw@5RYkYnEYDNz#EL<# z>$Q}J+auCxyYb0A#VfWBw;N(iAC5NRA6$=|qDKS3iknr# zS~aRidym9!{#M?`BfHU7{QKrd6N>Z{Zgk8*a0gxAwuEDI5?WP`4FI%_k@Sphx<7S6*EsL{#;gOZk6Occ? z{W`^NO1ayba*;ZbOg-UNee*-Ko#_hsZR<9Xg3rEh^kqEC`<%s^{ie_&Cr#tQePw9J z^DP74-hnHb@~VNwqKlG~$!2%X)GgUATMWI@g<740g6&VoctWb@W`dznZBBVbjr6~9 zET9Pn*&HBB1Jkn(TT|Fz@+Op{OO6$8@f^RVrF}@Nh9swfEi35)!r(xB?6bFIt~8Dz z65s29BXnzmGMMl4e!{}xLiG5wLGkrr@g=@FvahwTNi!YnZBFiIVJGjJuiN^dO4POTY>y%T$f8>D3?2TssU>kxLvq=D=1RhBq3w$m z8)@Ma0VgGy#p7?6ICv!C(Hc+R5G_)MigkNjV{hLAq2TRu8(ZIE;$MhM!!4lkSi`M zT!K2Oa->HA111Cq-Q#aNe;UqwhnLR=GjZX?2$fu%vA?o4IvqPj#eI0Yy!G=nH8#|O zQ!?|xJYkP|;@&`yy_Zo-HH;|r1|R742Gb!MT;qHrMq^RpKvJrst}JiE{hW+%v%BGx zgYN!Qy#uLpMWQ}_jN`rP#D^7VGfrTCvGK<6y%MV`?${PRzGsbnHa^qv+kxNRI-|d( z@lnX$cY%I13YcWFB>qAIGhX`u5bfR81-v9=2y340_Ap)pSFdFuEp;Lp6hu)-(LKo zKik0<@NI74nn?5oH;>9E-8&1P$}^_Q)NoXM@Sd!PM@>UpQ)^1kg1?f2}P zCs+MzM4#P5$d#&!uDsYBjpbqC_$#)jo=55zl*60jwGC-3S>4rG6g7-gk%RaT)&`e>*qC&;SrR@92!y}&wF zM=V&eVR(NEgp5Tua6b&*S1=R&(n|@LR@?y^SRe_7to9*6CXVV-u^3LZg(u9N-~U2p z_{9MyNN{~Yko;Zu6>xB=0B7Ia4e-*pr4|0G{g`O17E*S$Fy7K@u-Z-JGRox;bPE*& zVjT)a7eS;IBDNPdFr{I%dMp9Vj!NRFd+#R}Cqe)0l5maj5Lp0X|KPl2ln03rMzR0s zTEl=c0FhV#Z>rYn2})mP(ngKra2Hh2>(KCRA= zmp4aWN$Fp$5 zXmIr0#;mf>*-XE4{u0xaKY))lk@=YvR_zuH??2hr3dTowtwwTpD6x4F+#Ur|V@zBM zy&xHdjS#Q2SGLy`k5zw6ib#}U-VHjFpnve@FC?_T`LkK_LC5@no-2EjIPzER0JNpgg7b$J>w&uYrD833s_It%n0(*CbpMCjC_Wh`^`~sAk1Zax zqQ^4ERU$!GUQm9^?8drV>&B~Eq?Ir48=d+V4GS!~s%7Nn*}#D}&+5brVZ8odSj!<= zIg984!ogt7enARoaPNWs)%x}m|KqMytVe>5bEqEF_A$S1VF5!Qi&?7pwo_y8z<5MG z;NqM8LYj9fji!W_JyEl3*WY=d56HYwk__mc%1#o+<>Df{aDklPnMM1q6EN zTi7#iLW`$%f+kP>knH_P4bef}ZC(Gc=bp(D2X_fUmu+D-9$_q409Rqz_M zYPr**LK}&<1&0aVK^q1<(n$=x3vU$SCTYe;v+&Nu6yp_ zuRU)&G!x1h{dE3kTXTsDk9Oi<-+YYOTR$kYZg*tyw#V|V3vHEp zlGl>&6<&Kmm9HCf?Zed`>R+f5rYin$;@PcR!B; z7nVQcOUnkg2XhDZ&3)`!?g(L;vB?pyMn!gb+wvFOjYsM+M#?olc|_1CO^<0L_n&*J zuIm0o#AnZ{ZBq>xZeKbF7gpcP-PuK~X8dS>g)94Ijl8d`$6~aS#xZ{ia|XBPZ#~^R zDflS+W0uvz#S2sW8~HQ^l*#>O#IK4qT=5)Hq#oAl_we4*L$;Z?6N{$k$+Cw5^A$2E zDAw%%c>3-@sQ>tXk&%Q%8F4~pWN$8HZ)ct&+Z~yqh^s_8JNsIa)`-9!(&AfW&kaJC!Gb7W#CrmjSJ~mk~g=DnK(Zi$TCWgnX4QDSkm(Myr;> zI%+weOk12i?cOKW`qsigpWFxSJ|1t{E7*}3F8X^jY#4A7o7vl};jlf{UlvdrsK;P9$z17SXrsl62xazU?{+WMiw{UOi6Tz^1lqyE^I-GUS z=>A4clHt?lTY54o-rt?B4Gzt@g%YXFcaTYWQoin=om*zD*D{;H;hp_;BCK&6@B47J zbwyyMF$gb@q3S7q?6g<@E8(;~yWFzkO4@4C*BH2^enrt8mn|)EfvBm|{`xhz@~szt zVt&ZyE`M;k^~vsA6#CDb7PgFQv3FjR>w1mb3oJ$b?Lgq=kO@oSTm}uGJ3a7g8@C300d&Jm${1vNFO+8Q4$_^h5L|`Mebd(kBJ4o0C087p z#Oe0=`18B~kg?5!HJQwN;b;g^3A_<1oL~NbEJ0Bb3pfoxApzuYSaJeAdwGr6g!vG2 z#nCbi_;7wQ5>sa>0Ol6w!HiVo;&`G;z3mprkShdvdp*)CgwJPdFOiq1$swEQTCV`> zE`5`z&<})R^Q~ufk&IbO{q_vlb}-=yOnaFjTNEyI12SJAPI3CB_;tita+h)YsZl~d zOE>rTM#eqrEaN(in?%e_4~`o0G6qEt@f!{&wBh;MtlUYDi>70RZ4=#r68=`)t_s5K=YM#hz!$LE-l#KWGKpk@N8OJI8RK*(E=Y^%jpbek>j z)|(=46IZMSFv?a0o5U;}a_6D%q-B505;zq-u9z0pJ~brs37kj_#eXBPV!N6D7%V>8 zSM=6@qD{dA7|YsO^O{5B-gntTA>khi!hXtnX%^)clJmxB$w5BG9x(3v#W@)SYZmAm zD_-sKX^d{s)N$ZJBW`&OH>z#AIS-j>d!;Z(gIl-q+Bt6QSJZURkNK)FI=19wpw3$% z#2$jM?oadAX@27{hhklEKM*MY+ZL=u=UMRBdQQa$>c1f4r=+f?dYi%;b&!@UGIH1K zWUOcLMc=2G4w#xa$0Z7k9ZlyVmkjiop$4w%fbXJ=Z6Yx|bRyR(fIUW9dls^0@{00! z?VgZm5p<4m+BbOk9=si(1guda==XtV>kDc)NI3)}aRCGku-O7M0x*dMC;p8c00IBe zQRl?Me;?Z5vO52Xb7MOI2mWIl06l>H@4*H1V}ZbEY~iire6Ac3*IC5WrMHFG^kxwf z245f=0?lg$YnEz92YGozEsQju#Z)+teX@&-Z3dbOnNiiQ*AnoFkRm2Qt)krN;EjrI zP6aTj0p%6@~<8c z_AtkFbp_Vqx(Y`J2>gLJCMm(q4re9XyJq@Ylq7$7V=B;8q`S!1L`uR_u7v*6N7xIX z8E8h(ILqZwhfI^?IQS=wmMc*_wQ1{1qzOiva?(<=50#5D7$%Ykwg#@W=!*3+ZyVLa zFsfC6bcyS(LtMbAr|?_72))d_hs{oFEyLL1!;JIqi449nx#2(=P2u- zaH*8S_9Gp9P)(z}eDo5B^a{67wxN#hjNhdOy15=7Iy~403ew-rubfxQ{!%Xanc~|_ zUYwKRBP4~rTlfKxL=$tP@_F+Q(M6zm8 zztisr-nw|r7U#mzMvqHrlZUOQ>66PEORSX_2wMyAiDN508t9Wxi-8cGo_JOkDkam& zeAP<%_q`@J^GESMaq==u9+$%|sCj2etRi2#YxShtm+`T*g66&v9bBn1w_DruLB*`0zy$mX?LUw`8(ubrqYq64u|1gGjZ4kfa0LU4m z!?=$K(%(;!4{rHd%G2AJ_{i7rdR(RD6U-9Z{t99X-V2X< zERU2_szbsQA)XVBGmp(rf{MqwtHq~0gmRg#>3bk9Y3Qm^c07Kc3xy9pS&}KHsCjOP zw>!Z^@Bz;MRkHxp`=(R~uWo+q{%7J^#fu4!RIz{~)>$GefEetSsMp;)d}mMZp8h(S zxO3H`Uge?VUrqV2cs2eNig;nW*sy5LA7<1a-@j+#Z|qwUK6F-M%+49m!8{)r;XTt3 z!ckG`nJmJdhU>HkxJYBv+x@7O3(E@83mxlFnu!4>+L|=v>+72_({vxcaL;7~;0$&6 zm4t-m(raD6-}IY2dt(b0=uf)}0j(XkLC{xScsiL63F8QwHD#*LYj1M)QC``Til^_V zUVD;SXO+)UxfkM>0X_SI{#oWGsj|K61SVO!E;3zsNXQ<}J6Z>`vUc z*)(r3Ht4a$+>DgsLt+x*j^p%8d!G`qQ6@n|LRS3C1F4Di`-_LZ!I$NEKL6Mq@=$j( zHw(|c{ekr1(+#*^w}MRWoVpa7I9tR0!`)k3LMai~*g6&hdAol+^z(&h*gB1c?rT4l zDrb2jQOQxjjmHgV#B1~QP30+zJ(QMfV&_c)msRLG@^6W}#aUnoZzc6&*5+2hU+%l0 zc*i|S3Ui*Sja_#OddM=0ExT;?kK{ghR2cikXToNO4^uu4rFW%({&)^i>r~zE8|gya z82Rbix{(pIE9f^yMx#$%b-?uQW~CbXX)2y%69- zL))DqO`$H|Yw`O9&J^a&o_8+MrH1*OxwPoBhAh+Jp}2gTEbY8nD+5dINv3@Ji8hC~ ze6Z&1bdKd{^s2ECNJ+JvQoAd?N0+;tn_!RzeIa^SJ*Fvr0Ox`g(&u{70Mun5xaZ}pGk+W^ZvYHk2NW2!pu+(Fczu)5Rfp`{HauBJ zF3U<;Xn)cb08&>B8y>KCCaU~#r*!+l#L_cA56u?TSZTK6< zqxURru0!$a57G&GW`;)&=Mtb^=vb%rmhM!=!uky0E5`ZH}hFRCOxs^oqX&Q3!LGqTjJ^Nh9ES z<4{PgE#nrs3g1Ar`qVr@(CjvqBM-#bMA41wN7s)NbcMsay9X|TCmeaHZ2s_8AV)Mj zUZvb)Dv6?^c}@T9qR$l{0Zxw$j>YkG-Ntfjyt^tM_HC>oyxVBz_QMKsO10Twfj7_9 z?w(GywJPrt)Xi(#!dD;Bze9n*5o?;hr;8JhCbaGI?nl!b4dURSZjupfZan7rwF(@5Q28D0p!nn_u_hJr)U;} z$DNwQ+)Lh6Z)v}fRe!34zB&4qWP0(23fD4-)iUT5Wy9!=?r6W%Qxot9$%-Qv(p^E~ z_)VOCjbHiqcii>MHf_7#{La)>ZbzB;ywA*Uszd3lH+B`elrF+LzAx@sSZLFEE?>Ij z{+A7n6E3SFEJ1C?JftKM2)r>BM&{FyDsuB(Sd@n!Gqk{0F?3LKB)jE3KH7JP7Q6MN zt2{nrSpo6(k-wRCIlG3jHe}vLo$IT@U$BG{V>3 zgU8T$8>miW}W=nF5jICBQD#4_oOCZNZ@&SbdnxyE0%Fh?9U1=C| z$-axOYdcsp9hX*&@4Db-K3kiZl|O@f)8QjGvyi7lr_jEF+_#Nem!IwepV+2g zI%K6B)p6H_KN~taJ(LPw@*^xxgS(Zp&&vcT*%eH!cLNU;$7ZUjpn4O2Qqkzpu_@p5 zr?FMV_~&c8TgQ!LBWZSgnXV2Fdwh^L<+lbmJ*G7u_*HVb!J6dr4seFTaom;3J~Aks zh#u@e637{qJxIwjl$J@Unnr}p67UOaU4j(x%G>jr#3Q0c@q+Zmo5M~^^Cg+T70=2V zo%dY)hhW_!%~kOMayx9aXuan}?|&AcFw#ns7+f}RP!{F6_U*5Fpt)=E7*#-v>V z@f{k9l8?qt`z*Adm0Z1l_>QzKw^T}9`SACurcUj(gwJREP7I)N)Yg!BOL@ZHMtC9_ z)g}>)nCF46`h%4*K=}$XpA8(8+x<=L_Xg3zHB>sF7Z6>6w51RZiGE;Zw0j%Cqu{no zO?2LyN)?O8sPR}_iSGY=&Y}K`>M>sd&Ka)ML!m^~dZ-#f=$=vUbroU+VBP$RK^m1| z0R!E>=84I4?EaGz*ecgozu}^j+7k;oNZ{%y0ZtI2Zn>P_EMD7>EAO~W_F6dp1=dsx*z$w*-=E^Otqk%9WFDKpj{Kg7W`SPVWmL9qUm~_`=_(+ zmj|W8T~|qCzLd zu{+i{=p$iafI)6zl5ReXe|Dyu#Z~Xwl^g7TO#IzoTP*qM=-AX@RX|1tS8gUN5-w7< zV9aJT?zE~(B!N+Z6X?_nQ8S@9C=xcyYr6U@K)R;KlI3Q)nv@26Ff2=$S+r89w=KWJ zv$dR*CpxQ-3}WxsraCTd#aqZqe_MM}d9;6~Z^NVsG)S?A9pl3ft{tT<7%pn~Hv>Sh zCX`(VU42iKYO4S*guG#a?_oFLI#7eSp79BLMmX#j4+zyWajcIGT*gRfHzjysjsA?K zN8dssBrjN)_$;_w{L4|pOlNZG5TF;`jrk~w{aW+W>dV_q9o=ZmU%ksshF$wmcMB#ga^ z2+13oqRhWtJ6-#W`ZA5;*FTOs&Br29$5RbpInG`B0ZTc7u3( zQh4SOq}+tm4u1}$Oh@bKW7P{auT^&w7jZ68nJpBQl7822LDyp^a`{5db?Epi1MQ;B z;#4#QQ3AoPyQ8OH;^-HLLt%dmw~ZqRuB7x-jAK1%t*^>%k{}sBWwyS0!PDcB7t4PD zmvO-{{@{g17+nOE*E84YqTEiU>`98eJ9J1&yAGxt;GIExZ#fEgm}AZ%!XX~IEfOfD zGj9qi09nv}N!dNelhOK{S#LIURo36~yiM4Ca=oP?2^8q`e{l7t$l(1Nu1YDcyjG2O7@6V zBv)t%)lmOPF;)EMB@Q8jQM0F?v{6s928xCQ8kptIqwvG~w#39ouPpE+DB=t|s>+lMhLJQBvL= z8`h~7S)CHcnE4xb8^WU(Egt`^Pm&?ZFBlt1<;wIe1XLFA3SJPPVKCcWBG7#s`_cCL zAIXAgq`4hhV9W%FPky2w2{R_#~2o-F*G1X>s%-{zz0{;Q?-vf&o}h| z`oR&`3t%mt@zaA5F#Hle59XkH%*2=Mn&Wb(-sJ9_`{O?*-NniwPO$AL*8KV8rV1Y) zUXRbE{1N$kM7$JV<}&ZpVHB;XTQ-h6Ky7z9UL@PWU)Qx@CXA*Vchk%|TDa?%|j2I+jYVyw88{uIQP5S4ID-<8`kAHU_w@E)CsH|lp_+s&Z^h9J}y z!usWX+P9z~Wom-CeNrm7%iiL>ei5?&7Hg&ie6C6$IBV+>-*0+qyW%b{!u1e;91Bqt}7J0nYAPf-s z?Bay*$7Uq%YiimcJkZaINjk(@5=98rI@qON3+<#;h_bI*GD z&H@auux8Hlb2skZ7Gih}<&MceHuv~bY2oN8{`JZ672|}2hx@o)ZVdJ~l`y+AQB<4Y zp8xHjyYSM++kVD(Lxc=J1Rsik6rf&wqzjkDow1nSD}JBFTFN@8oo z^=7VwCXCi9+hVS*3jzyvY{1RYKgVwq56krH&BZEYhbBt1YJGM@4JU9}weJ(v0p8*_ zHmAg=G<-f8_l9x5qTV0UNjBlx6Hk=gV-!6@-u{Jo{y4nb!Ni2h5;ujSu8z*CHS3mR zS68z3nw34sjcUBSR~xNwYOd~S4}~WT`Qa}nt|6{W~HJ&HgJFDmDtMV+Ud%qCuo}SZFa6oN+MCn|* z)BTVkxbwt9f&Ga#{>GZ+_$os4r}JF^*Cwok4fo5#Ae<-Wci)B_)Q^FGqx{f3W&5Gl z_suKRH~W1a96RsJ-%<>EmGW>t4Of~~Gx|;;V$R&$>5)&Mg{r)%j(|iSgU@V!P|3`9 zQ7i3P4KKmSr|7%KE|=?n7>@0Kuc+vVxaRSVR$oi9YYv}e!A?@nmHSxFdEHh`Tp_$^ z+{BlSlZV!=kSWrSVR{Z;@l&_g`jM#_V!G4V#G-NND4p;C3e$sl1I`;@6IAJ&b)*>n z8e#aMB=Mrp_)(<1%UKA>oYQ}d?ihvs!UMqNZlPowv7Y-kbskj1SWqcYC+1E-rKujd z>ShQ|%4+sDc$P!8kk$C$E1rgnqNO|0N(d61juAt}Qv6Fp(v0g_*ApB+xEO#RS3g5# zT1&xu{ikzEDH?w7uR#&{q1y$Hr9WQY zLv8iv$E$%IdTh8Y#(*kP%+V{Gh6C@K&NGq@*TBwZzTZhvI=te5h`9rP5 zt{Rcx3Oy-Boh(7quof!5x|j%7FC@9$alwFu9~+J;PFmn6%-9)CQg!=)An>Ex!OU0Z z66kA&?JU2=th*97GD7@hTeUSrI4yvIo0l8E9OI6#5a-y)?j6w8SO`R&$d+I-)76Rw zPm@wSjgYrE3vIO1FMV?VO~+_?xrsp%BOk`1Y$uf`?5OmjZ0>UOEoIMRS0tYOHxr$& zy)h!D8VujROp8r86;q}V_6)DwC}oXjI5ywFWBFG3!F9&Y6s_K>+yjirT7o#wR;w7c zUOz!643J^D^7rcYNlaK~@kNhr?-dZ4Kb2r`z6B4q_925=j-K0Q54C$owp!iK@{dQW zZshGL*35jGRv4`aa3PWkR)Ibt|JZad_%m@aA9qKMSb&If^*v20F&W@N?S9TgefL1V zRWEUmS{Hx&@x+Bm-;XNtah&$0gLp5fd?Smt#%iDJO(!Zzn#x0b?Dlk@a;Rj!=?lUh zj|%}?iWY1wxirdWM1ePoJh(&7yJ=!^v9%u6EbH#~1%uST6d#fJgtT<+YCrkex3A6C zz|mqM=Kp5hkCSXbGhU-!$=uE%`JS@9~^491Bklhi)!?+}lVGGpB$-f8aZgTAG$-(|44oPma8K+{jiFs= z3N)Mb5+ETQK{o%9zyqqjzP;t>F3WiWW0mp_XM59zoO^fMJDdcauN28QaQ#7G0F!n^ zuWOdXZbnh=h7RqIq{S1ncVvF+g1oQgHo`a6!GddB@>Cd99QEYz#C)J-R)!4!crO4p z(O7@?1_O8oVRASB_I%&x+{b>~@)Hh!m_V|z8f2=#6Poxn?ga9&?U$+hfk>qH9;!xu zBCkr2L%K&vQ1}s5hWsm96`VjZV@A?(EJ*f3ha>30(y4vCstH<5os$M=G~pEgv)D4M zdykr26 z>AM*Bv^4HP12@lDwVBMfj}z*2eG;+om+_(FQzDU^@W1FH0;)5 z=zd|3n)G*=q;KKPa%SJocVg>EN!5#?gxC%Eq<;p53YeB(rFT5|PCzEU>I$p?BHEW` z&op(~>aRlZCD`_&k-Erh>e4qygG?&~mB&jY=uPWZ+;dKLj7KM9g5`3q+}ztQ)H^k$ z?aGgN*hlGm|ExP(C-KGGh%0~YxqIu`7%j)-u;xIT%6evoZ?6Z+OqBTJxZI;#V)t?= zx7Rd?wUBa-#22+>9%%KsRduN*lkb&j{Ez@SNCCHB?gfadz~YaWg)+#10;7WW*>hduDN5e6K=fu(;3&cHjB1>*bRv5lwW#qz2#g1NY&KR=siosO3q)1Ct=d`V z!@`c0zV(vG6a!i`sIHOf` zBVI8@h$bz4vay!z^w`izdxxA z?`p1FqHY+~f6(QJ@xPyv@^^mW`!S`rwj9$iZBhcFP+W_B{06(&MD04?Wwt5N$hW&^f94>8hGhBH)28NHtDZmF}=J zDl^2CI#0%x2weytlCoLQc@pLzBD;;A$SW|=wqjiCY_3MHy*)GNUNdBULU!7{Q_AZ) zS$g3>>YAnRR+1@$D z+ASB#2jpZ1nfGmwQKAK9sBtHC6y_# zEVj?Y*3_`>yKOQ;PluEX&og!0541DqI6&mVW*FN!bI|%>+~p)8CO9% z5@}*VbgJ4cTCt9JJ%<{e{Jf!Uq5P8d5@-M_R6qDBeUbe@IBAiv6W;qrq8P^2M9w1l z?bB%26;ieYoy)$uby@l4q--C&wYR%E@T9%>jMU9WG~C2MStFY=u(myJJ$5bptP{lhEpKUPWuq#0Ml z6IoSn5AY%+wDiw+PSP>_BvN2L34}&am9zCumGha~Ml4=)ukgkDx$_0wGJX6F!m!zp zDY#Nmg6~C-!K3rJ+uOFW%85MORLJj;+Us(K+BPS5rKj5QAG=5@0;86QuX2aW3c!%_X7RUvX`e8}UYXDQ!O)28x?PPw)fM z>!0SV6-5SRel176P@&|i{zvk`W0YXFCcF71r?H`RG;goS>FHh+j*pS?9m9&@SWWlo zeOSwvm_h?)I^X$%*bYNTuU8nPjisa;{wyxnzHgpTlDKiPS zt&ww5_8cJerQUhmCr#G^d{~l;2(z&6n?KX8^WAD+isI;i-L?fp-~yyviMf^s{XC)$ zw}?qL4Y<`sE&q)O{o=n3 z==}Ce?-`Zc*eU_meco2}zW2)jvI4}Gebk6Y4moy!tr&=;MUY&sSMD*4jFQ1V+`BL! zxtt9GZSHGhmkR{>KNVhl#A;NC1p#oEuaKsIfQsutL-jIRicLyuoCSXIKB87YZ}j;G zGaq*iSJLe2C?KkRJ)>owi9twqNRBusj(Xn<`!5?7tUO-?%I*I|TQnof35*R`(q*(B z7$#zX&5yH{ve6OQSm5wjj+uenM!Xq#pH~a~0BTmxcFacOTOrS@ab_0Y-8LX;MP#jc znO#(3P>{WCE)MmnOW;A{`i1U5a{C_J=F_&tFHw63xqMEtB34ZdPdTQfn3JIjM=48- zc!~s~Wmj{G$1{%oH}+=%;uXIiF$Z2dYeDFI7W15Qe4QG) zY4yd}of0$FYiRa6SLg}fXWRNpYIJ!IRR(9Tn%gx!@g^T6%VO#Vra@!|Sy#favAN8T z6oMx8z^qWJiK4^ZbcxB3<5wNpul|o+~1H8T+trDE?NA9t{e8Uvz*v?w= zrHT7}I(IkeIT>nCq9e;)0C&D}6<;zJID%8q8Lgs{Q;Vr733&j^w+X4~V!L?)OlXYt zZ|)f|zIck2B-7F-<$Yy%hd{+uZo|)`mF?Hal$9M=-gWxNo6toNxIn*TACqD9RIWfV zYWyvRp7f68>4@soTf);q0j`b#sVhBfT}u4zrU@xRg0)gjLdLpiv4<<1M}x!JfkCl7V{Vfi z|6mh*?sv;;xhQwaLn5KmS)qJX+X){U1obQQh++B zMtwvIyAYM~K*dVp{zp;{wvOP!Z{q|CC=rFu8MNxgw^L`LIQCD9+3LMV3bjA9n@XBE zoBkqj+2Uu8N9;3|75}5hwC{BqTL^1zwJmCtls~BbM*;)OppqBhGw0ZDD|^l2lsXqs zZnuIe@rL`QgfMAdW!s~DRKfMOovmyg0E^C1-(!IK*7oyrBDEJ-Dqe~0-c>5igneEY zdNe=trF2<`l6`{}{d)u<<9oN$c0Kn56aM`l$?#8L4wLrt@|4KCTQn~t_+;}IBAX?_M{HjunhgBKG=;2MSTHFdcPSTwxMYwF=0IGD9`^FNCL~pA~)+Ep)(d>`R`woPE6e?Hg$g zGO+lpgts0xDzslpfkC{CF3;8CmLtkHbDhPVHMjYhE8;z>4Gvf&jx{1U)q)PI zFzy)hg13~3q6(vbsg_2^!BNmRu0~b)F|@4YGUJ7bmo%tmy=@b~OMVh}y})1eIR7y@ zjpQs?jNqsL3Y!J0CE^e4fD%||nDE43A#nxt>@NBFbZxk#2?(_lfK;(Y-4TBW#L*Vn zCsN-75qjBZJtL4+$b5*CmMLJf9*K-_76cdhS!e}7PJ+$Vna%%D0uh z75m$~XtiyN8ZArwUqB@v{fp3NF{bZofjSU60NZ?TY-a(mni&vx*Yt&CA|&~UaE<;G zZ)4Zp<7xexH^urz>$K1UI59Eq{(B7usp;lOhuI8_+HI_7qJMW$xqPu3Oj6p(;dJXh zg184VV^}qmx>7_+_8wT56f|Yxz87*=elw%w(A-*h67+P*N2n%T>J=k!WMX{{33J%c zJ&bI)`jE6Hovw1Cu(NY^iFI1;!STBvjh{cW5R;^`((X)ptKIw@kl|cA(WCv0CguI| zwBk?kML8Be+5Pes)e#oS?R`hnr(`xBQE|x#Nh89WGc6fq(EeT=tDLV2-|Ch?5>WNiA>Qn zLFF3Xsi9xVXvb>;!DBl55Z;26YqUYnekjmVyWCE6B%09#jDFYB$4)&UOhAN!HIXCD zzoe2xVc9FH&TlO8Z7cGxCU*X=eqPa{5nOuz<(j1p=w)NAv+PiG0Y;!9NlbVlB5{arap;Y6iNY;#hmfk1p^%J@$6PnHSiejsvDI*Uz=(g<2-(ap+{O z1^J8}0D6HaWJd4>R&e2W$_f-+lmwPtiPUkoWp4|Gg}divtCPKNcaN<5eG)ZtY;{^u zwC=6@`@%?c^r~C&3aee5pW2bH-tS;_y^E2vvALbHI=?E|2tP27)EaT9AtsSWo^7-X zexTm#E`cYzzCuRWL6f-3K?e&pHugJZHWT>fO;B^oNwR09a^iWm^=8kMfEoZkHEMxZ zMnE>$_L(BDJ&taBQ|E1+tY6o4^^c7+ufd^$YqzRZ%*y4X>~a-vu`rp4j+X|O0o}EC zVoxW*nnYa2$o;RY@4S#mPaO{KJu$-xwv<%8)27<44SAJ)CoO?VflAw6+o&`uRKGf{zV_pa zcurr!nH%uK#8f_oOX()DxYByB7rlZG705P=y86v3TSK;FxTkT-RG(R3@JWZ~EI09G zh^O%V;=a`-9J@}y;neYPJ)gtcrkn7)y5|EiozKWaoHmiz!+x$b&ZPi@4WiU{_L!iw zD1pKPZ*&CueS|*0zpG78OwPlYL5P;G&8>jI5}++fA1F(CUUa&sR9BK zx%xtZ-$-c%gZw+f3^i_!ha4A?MLATsve$xD-?rPw|R=#dDv2 zJ{lc!c`npFvPDx>{u>a7GAthk=C;1iloB3H)$evNTKLp36%G9UnA#sS&PBZ(R1#7M zXpz6+x=gMxrhyJ^l1$RPjmuZ(U!sExIdC%m&}aOiMS}70HtduvClOHUyqgvus+Awb z>UP&#%Q>VPbzc2Lu!;Xjux&ubK7sl*e;2>D3;vV+U>^iI(eb{+sE8utjil&uaXj|y zdlA`u>U?73FUAY1YNy{prsH5fCFV)?1`n|Q9i@b;=RI0s3hewx;tRlw)V<*876)Px z@gm{^Q`=d%NZTBAq!OJ=`Tq}@Q;$In8E;Lr2EVW=m_M7SWWA=|b{14L67z}|>u2S? zEV5~AfFp%+(La5Qn4_Qsi=l75ul;8Lksk9IGZ^3Y(+35nD}MRt{&b?6Fi_KmnOa(o z4>1Bkgv!sTvARpN10;L;1?;Yk=0ZhhA-emx66UsFyS4>-?@b~TtJRiKoIevNQiz0!hS0VHI!~-pTi`T?mPkc!eO}3oW605wZ7!~-IN~3PmThh!*=naSsyRX#- z#!0XLk>nMfzADXO+LPs z(Ohxp1jaB|rieaJ|8M>rM4c=nHXF`x)%15(0Bm)B$qI*@y}Cx&gERlEuDKLnQ^i!R zmVq8;ikuxL0hD7(t)w4T+yFRCVZ4GI0iQiC>;pT&lL z#1n9m=A~SFUk4*m#{sroIkDx2|42kOjz%2I`!vfnpuaYV0Jp>A4*suN>gDOY zzcI&TQ>G65R`P5P7*RWTkO5n;Z4BB%h%c(CCX$a`AjpI^v86;Kg6=G&{`6IHDEL-6 z@wYIUnzyd5_B~5DSgZmg69F2X{{o|d#s$QKk?;!mkGwPy9QLaERI^xfBBt}JDm5=t z3qg`tlOD#qPU77u8Luy_cgOWI+9nmAKq=Xb$fc1?#Yqp>mHg!DWX3&jQVoxM$-&Q+ zT{T=Y+5i%jOw;xGbws%C7^5?8-%aLUCZ%ysc>9d?-T54E%{!2|4Yq&niW3kMAwPFwCG{Ll3it-B(RYMfC88;0duHN7jKS9W+B{JnSu3 ztcGXAdM^Np7pnYO!|OoC?4}1PZ><+7_pozlNaB@QuaHb!Qpdors-q=sk>_uIEzQzZ z%1U`mP`ST;>HOtdW?1-z!?N$MSu8RGXu<})qs9{ory+^$Cm2^&DVZqGKj=I^HsOxS z)b43<)Jke3ZGxdhuBfaxeo1jE%= z?C7Dh-M>4X27s4e2;L~TRHL1!#C2x4?j}eGad9N>RGdl3A#-Cc=A@n!_j-h`nT=>% znPWR0>yH1-y%s;66;;i<&@|`s%rmD_gEhnCy5+^iCUOSaVtJW{vMz%wHdfS`G0*<2 z3nJ{TTYWATwYh-PH$D}{eKU}ij_J;OMjz}Wbi|%Yp4})Wo{gHaTr(^2_u2tg>_hOD zK+d7moltJ6t2yM7zn9K0WO5Sb0@8QMsl7iQ=g&0Y4!62jktLW9mzQXd)>Q7aTKH#V zb&OrzA&uWK=T_J}JjGN}iqZ$MI_nMdNvoFfvn3b4#ZSloF&11B*q5XaPwdS2HT=OU zsUqL2A$H8U`U=JHHAWXk=O|}vHKC@hndX_0Nv}*wbOV1NWX-fZ+dWl@j&X-qJQ{CF z)QF4p)~U?u@W%-$#C?AG`GCoT?ImZs*RNek+K!*eaVBF?K#qUW$qls*ZptC&gPmUD z4t5ec2J`^eviHICSju}US&mlVc;J4K-7P5?cxX7il@P@SHtDl9H^>jgu(bH9=4&;2J5|j{5hUzWUlJvqsk9mR9 zxqj9itf5`CxXS8Kz!;iSfIAn;)r4C_5}~{&W}(xipw>eYf(1YSG35mOAFwtSWMF9*Kbz8p~X69Ugvc#Ob^xd)fBfgDs5 zkldy4*yHt(cuYl826D@sB|9K*^q%XmRkv{*Va5u@K+bm<_Ib^f@^l1N#cEoc=vfI} zNopcE7N+-qn6NfG8xXossqVm5nuRx}!#fnRH8#)JpHYaZzj5$v0o&m_f7{foD1OIU zmxk_>!1Qts-ua*O8hm!}Pg~Jw+Z~3AS&}E5#)MR}>J^>+we7&%xqjEpSM&Bq6?V%v zy(DAbY-TP$ZP&X%O(7)V6BEyQCNLk6*9BdaB#*upF`3P6TR{FiUU!_X&Pv^C zYWzbZbb!QaR_L>-UjW}R8^V`Qt_|F30zGwhams31$F<|B)<02g^wpzA&{$oB0I~6&aM2 zH^Avfa(px4mjQ~)zIVK0L0#Z zP4NGg_XLJnY~Yv!QMv6;_5qF#9);?jBzz#&bm<^FY}Y4BG4F4j)M6#I|F4z{!qL#9 z)AaKe*l*21NuYEjBhWO_0`$}8tuNqNP=zw5iDyQV;XtZ-Zjg0uXP&@lABpZTh4qa* z&bcF^@4!Fk1PB!kumLz=(wvo+>FiSx~^ni*g1nK0e`d_OT(zPln6O9;|1Cg zgg?r-T&l%AH#sWN$F2sP9xy?phBzNO=Qb8}4VL8ndT_+W{|>Ogz_TgriJ zv7=||svGaC0{xY1HT7F}_ZS;@QxAx12jwz)SKd9stuFkqd7kH2XqGG$PhHhx7pixv zi0T+z*p|c1%S5?s-y+^JX1L;H-#MD0x_3|M{)-^i=YKmK=Zynocy`Pgr zf%R2U1-j}_#=2Sph!;B}*_R)4r7D2cb}iQA*mkmm!ourT^!Vk{8D$XEQVlK`>&6TK?6hsa3Dj4FbNrxn# zp|Nc7e|z^o6}EFBlwENwCYO|lXnif`D~$q>uM!~D(w_VO-=r~+ zj|UiUh2A8Usw7#T5CzRHp@D)Yx9jR=|6HzujI`IEfLH4f_))Dtp4v?>ai!%_v(wcU ziN~e9BbDLAx`9Df5yjL}q5M(TZuR8vk@^aShz@rNqM*5$bwwuToTl;8lR~4x$g(Q@9E`xMP^4 zF*#C61-zmK7us^y39X>dubBoC3GIQ`xHHs%3V=0N=uYirIq0B6;yb#a$k z3`pw%GycQE`*{B2Vh73}qYrjrsI8NZGAq9rhKq*RJV?{WFb*AHb^l||v~iTG!{X%GwXyGMxYoo2D`S1H?#f^5q-)5Jag}6R4r(KP^A>_PI{*%X_C>Q)f6~=F zd*TG)ac(r{+ud7gBnrJrjVY==?bGm`+ky0u)AW{yAXUAIKtVr{&Xo-#Pl;__BfYPj zRrA?wbb08Bz-#1vpMUe0@+AVqe-{AZz1H^`Lv8c3@XmUX%TGpShSyN)}d}ak^(n+rP-BQxrp>&vY$z%CM`@n&$v`DYpW(@~k%cIv>2Dq20kf z1sw>*``io}=>oH={UdZ$pP*JTJi7BgYtjfT?K4lo8jRFB;k-V zasyVHylq9$FKCl@R*xafMtJI*`%bj9%E^CZnb6A+El6J(Al0?9T9;VOP^z`oj7RYw zY9Q|EV$=s>d^Uh5(}$Z3GwpTfo?T_g(v%brJ!{<1*rXrW3JL#PXMCVJd^5zbF-6 za0eYpS)(l^J$<{-I`{l#$CmJuA*Z^A=T5?Q$@^ymL1kf^g0N@a@OS~NWQ6!b10SXz!`sQ)O-=tkezUMUH z!#&Ik9Ft0Jr95VNHmIJ>)A-4K>rYa}5?PBHmIb`gz)}mahc2plYMZ@+S;NVPde`SKy?{9;aQ5aZ^=w~LRX4f{ zZEzvmYUKNamo#2XuAgkWzMXCag*BC*{uEz+_9r4!*fCAOK0_4>^9`Z}YARqX*?m+w zjQ!He_OC0p*{555UW~SUn6ix8hUT8(ZMq6#D|HadfPsvju2=dtB=(H(A6WxptWX&y z4g5WZ;B*O#*UPN8h`h8MbsKdQ$GwJjg)e8sNRUziETgCdSSwLNJ^B&^W?vrtDqW=6N8081Dkje)+7%nm6~%3KFXrfvU~ z8~=W8{Ld#gYES|&7~@N`Oc(K#N-uIl@c|fvD%_=rwfwrb^202wH{d1%grk3UrQfY{ zEuwHPKKIG77XHvH0X_G5Q|wX0DbT532dRTLRj7sNhHOH2fz-A|JFcQ6?ot6@UO4*^ zX(`ae9@6=zf&51_f=h4!bm8Hex7Wl|8u|PBE1|3}?!ARflS%iO&mBbZuRoUgZ>AWz1D)o?ZT zVsBRB0{XSFAJ1lYD(S~0^J@UlK{JR1Sm35_-OeNwF9t|p|Dxw}H! z*6oR-kE+4USr?D^5X*BXrMxI3MKl$|VP7*ke;!KS(D0C@)%WaW%`9bgy-S-QzN*7x zGzK(!%B4=!vvaRF#9}vk3fcU|arOyrPM3c5B*EsXNO{9+Ww; zF@Ax`hBVLK0hEnx3JP1c3g4XX52u_r4^$fanayU27bzfcr3qS|$Z+XRXmpI=)^m`U ztlMV$NpV8o4+lB#p<0vW^pTfQZSJo5TAmJSMvExs?q+-tcrioL%v&V)% z_r|CTOT4OWt|9De)Wau>k-kQQgZh1WOR6FGaD=6?SNL?3wDN4u=rIq7-*d*~ng2`9 z;x@~dmcF{5e>M-`pEm)uloH*|#c&2s<}Qx_8_V=$KK<&Rop;ETU_}<@(>x?LQr|y| zFG%h0f+EyrvunVqze&b3-khM`-|JSe>n%$^V~oDOtItzMo@ev{4bXOeN#Tk)lc2c5 zhZ<8q>$>%9)Lh3+-zA1T0PlKBoFD zbt=QHh;%COg~=C;g8;UGj64fcFy(7@7Fdb-DO{VJb;M z5?_=F4!bv?>kqPwa~>}ynJ*urRJ~b%RqVA`xF0+Z*vOOLgFWDB-OD;lYZ4L~!tdV# z8@c8=tH;5%e>RxhV8g0g8EIwqREoKK?i;b@@sKOkT+6_cp!LnPb(e{%5Q41Ui^ivb zil~IZ8Pn!CK%|b$YaZA^IbpwXWC$S`5eiDXEO0)=0$;8qt$ps~3F=Q($nKQo6*TJmzZ;=ZpTAj-8ZbAz-Q#H{oLB@xLFLKXqV}BV(SI_2z zNt_uyw{8V@p*HGZoCh?FC%6brB6XoPi7Y|?G zzYkF0sKTeus%`_g$^9)^{)&O~VVa%wirDj^60e?VPEb_fHtL+(W=9c%uVOo?pz!A2 z+C3lEtN52AKk~j}1MyV~M;J~7Mpai*31A(YHd&H-)tjC`ea=^)hbvC%r-2L#UvtYd zWSvWgo{{d4%VVV6OB>U?I6zZ%zJwa!>S*xU-xZYz+5w;6GLr_NYNLOTa*tJv@3qh4 zd+3Lku7}SyfboM2fA>&Q7IbkNbe{u74{>m{CBEG&0DxEZPRe1(K}F5CRn>D!u6w~H z+@@7oDgVT>6X5I(z7MN??O#l?6u2OVG*koc!9r{v(6Z08<*kF+zyLXz*X2rBg=Q!~WaKd1IxU;~o+Wb@G- za8}^9-?7HA{!}0hn_+&zVo&JYh&#S$R|R#9awn)A7p!`5&E~Ad{u=f|ku3SwHPb>! zmg4K`9m#Z6v4G$_&H=nH?X3bs45(LLitSB`q{wD7B*)(JeAaERWfZJ}K|f6ZcsG8| z(;GN0qltSbCCyfVh;@I};I7<;%>nH53LNYB;j2hwI@KX!UKc95Oq*{wxKz3_lp&(~ z@zt=BIDV08D{qA6wLQPqC>`N`rD}%kGs9Xw!WH29x|wb^ndwow5tNYx;%hTje zv~_$#@;T;XWZcpaMR|an#FzE@Cj`Hb+RQix=brb;ir;chx;z`NQkZ!8^2TfwQ`_5a zzzpTtQi6j@oXr>G-v2-dxlb-qg$bWX%cvhqUHv&fucjxFzsVO=kY!8SX(kFiVn^kf z7KLQfKD>k10_)f!4firsUTw1>+Fg`>DkzGNWdru_@3%+nl9Yw|=I2ZSRW>hO4!Adx zcF7Horh^0lNzUds*}5wQq{(3L95}=_#vS*Ygg+_Oy!i1f5pUS0{G4uE8@U<=7QA3x zbe}F>wgv!Pz!-GFJ#!5dttJ(rhppsBTP7M+9JmwTStDA($!CHE&3r>>={Wzclg4V8&tXx{(PKgBhh--B1WXsDOCh zUckDRo&qL`P39r5*o59dkYbEHle%9EQBqc>ywe(jlfZ!=8&blcAPtDm5kTiXv5SH( z3~600$W|wlrFSg`#Cab({QhB0H9zd;NpG7jl$k?Fnc@pHVgoRlcV_waxl=b*pTMo| zkWQQ~plmOLb`Bax8-Pw8T9+yC#k)v+(urbK!`v4 znz5o|3g#bAiOrkF@uN}Udy8*5qQL}IiAfJtM*py0J)~gp?GHJ17Wqws*B|qOyX%d` zOte{Sk~|P-@`yK1&J;>m;{;G$v#XnV;Fz@YyvU!gTM}HPgT+vXFW#Ykev&#M;KkP-ZlZf7;)imOq>k=D_&21;So@f+5*FlfkKIX^R4R z_OY!gbxC*IkS)cLyDpy*qcY2v<4d&{zr^i*HT13X8<>PgF0Y@UEY24rQbn4nWR-UO)R`T-EsCwOjQuh?ouk!je#j(o9j*gbPN-UOUh_Jv0{IfTD=lw zF3hukH#`Wn%c{G@49)MPOhl8p2p;DT9MWQz4I1CbjBT2Tq9wlX_rXWI9?vU49Re7Q zt}bE}8eOlHh|T+BYnKvvsq&;t`WPiPu6ZuOn#y?#LhsK`>~`-FKfqo4O_~~!Hu3Si zLC8ON?GxU5Wm!4u=Zab{=TL)$;avP@LPJBKqS;lLw&Ki=hrW4x#@*PDP3u>wBN@sA z8CNYH{}w(D)P(ojzNi=U?pnV(E)4V~s5XXt9^W#5VZwg9$qrO>=63(_kGAk_<4=9F z;ZolrCzaK^bvDh8A=^lo!>hR+SD!>{OQy_e5XuaP^yMsOIOG1+F=G*jG~=&62Qjd{ z#&@431u~ZHsJ|L^X9H@O-a=OCZN#4zx>EYeN*h^XefOXdxIrAVzKEd~YfttOUIsem z#~1Zl-$8B4rT|EDrOXw8K`mB_*rrpqm2&Z;qUr&xydC2d``XkbpTosdl5kr1qWRjA z(U=+dfLC9#X3Lx;g`;~rP5K!cbh%@{aZS#}^aj2=@fc?PQwbUqlh`%Ut48d&n$U+2#53Cbes1imyIgcHp4 zY+8WXMGVq#H&X%+Jg#1a@NYu-w=vSH<^4zY>7xYRRdY?S_?JFWW(&;PKG2LhM%y*h z3v>vK^c9g&c@aRuBaNgAYMJGO5~Z0sI1b)?HjQATnfW$>G} zx;2VJ8uGp1GrH%aO0vb<8p9i41vr(UZ8e{=W89_rh^t~wRX4zu$)sys*R7pJC{QFR zb!;IN^w*+MEnEN`2LY%bIYY5$3=daTn}%Ak_pWa%1O=dcK*tA|_`LTe{%Lb{Ez@HK z$D&lv^MWFE*C%7o->h%c-Aumg=5DFqJ%%Efq><$5h?KGQQ;1>ro@eZZ>EIPCka>SJ z{#6ITYS^ecH3u}4SMvNG+&9uz8R_uLu8h4raVgsIYXISgiWu+Dk~}pGOG<(!E-MFv ze8To#3~>>Ko^&GhMveTz4ytERwsO=4m|VSgRdjt@13W3MyDR(j@=)i&H}DZQ`DbSz zNKIpmv|0ATVZEX&b}pwjsLo2ndIF#uOSvKp$4VAk{#;<+aIECLx-6bPM!ArVNWN-z zhyk^8NDa7k9^X&)LQ&Jq@Nc3Q-8n2b+vsQ<3!E@H>JJAh;e%K}{C{KvCWXiO`=VyI zn>TM2Wr;d~kM_BV&WxYFzXu%;#S$qKcOTO3xfG1Kja8myysd3g7VTbE|G42amA_Mk zU{x6h5s%#e=AH`Yp5t{Owtk4!1iIc2%}i6nbvBhCSB`v}zeTKp=6h$(R?HhWzt|9YdPQ7p|0DmC((ix6_mmL5bv~a=hm*bvb99%K zi@tUP;PPukxk6eE@yX|3s5VAc33@i3t2TJ^u}qiK^*rAT>4%#OfxpuNoxjrB6~Iyt zeVP$xxBCq;F1IcZ?_C-Z1zE>d0w;|xVKN_Ae33HfUKP8kw-K0CbXhLm9GRmGAN@`= z;$Nb1NXrV`1~_KqCB6I}e{h!aPu3r2)Q6ljPm?am&gnx0*>@Q$V%X4J}y}5X$Ipf z%_eDG+GZdWfMg0*2-uIsdk@?31wR~g9&q_eefQW8!IgNpQ0J=ZS(T*XUB{$}0!K^A zH^s39KTc5Ibe6Wg+N%h^mxwuf=3GKPW>#w8AvQwb=0mP(6Wbv7! zzTpUzMicQz8lZkGp-rQzX4BS{Sd|KuS;EINR6Pq+kqgqi_x<&vTD0!J9M22judQD0 zG-(;;Z3b=1YzlDPUV_ap`E6?V@QZ*qVlSiv3*GS}Cv0 zo*|pqTWLkmi31Tc z76}nrTja$z=y6N=?YkPvE*4ae_^)zC8~nhPjV%1~kyb*NCJx1Rr9E)l68x#YA8*tP z&0tK4X%i?F0Y;Yp^y!uYURPjp8x1oGC)+w2Y_}VagBZq`~g=sto3{q z`UHgYfL(4 z;E)->pn8Wv3r2A+Rk8^(ia%PN@}QEMtXK*DkjNedgb%X!Buw@5MKsc7zO}lbcF6E# zJZy3+^w2B?FG7T}qo%tF@f$#j*-U7~#G{fQQyzg^yc8upR#)UZt?w%A zpZ^dsoTJZ+UoAD5TyEoPdGyOQCoD;jB5Ukp5})4sG*5g%)Tge%!^eA&>rw4sGW?aK zpO}Zy9dt!B&tJcqJ!(|JvHjH02*5FLdzG5LK+#im!!sqtA@TcqcG07rCxs?I^8Ng^;V-dr(`BXt%GKQZ~D%k zIo?Yevsa|uN5xXu2S1|XS!IMeZYhKqAnifQWe6!987mr6j0!N9VGYw_rs?%BE(11h zp-%?XJ^s9|(n?PiT@lFykmX+rnq=u$d}ikka>|+5qUY38+4TCEzQm?dqcteo+VtR< zN#)?D&nxm8B<_ie(F4pcv~suo3uPTQNvGl)f=^?%h8*-;!`MH*BbYvoPn!k?iK3At6YWynTyDYG*+*Lf8nQUU%IJ&pyDiC48d_afBYJbORjP6cAQ40bXWO_;E9OUokt0bxosi}wZ%b}Zt-|AlWy;4` z74SYOX{{WAwl;vtn#MRz9PwZ@fBUbE40U8 z>1hOb{WEG;5;!9XGnH;YK&JeFxEb`@bsH#(uEb``lWq$tZO1+nerjO}2TJr{Xr!*; z`5Is~Fvz6glnExD&}XiJkH+f1|9K67q2X&TLH%wDPm`_<#i!F*7_Ms%1h6)!47lgw zAY-q3gMGy)<(cWP4NhS_=3DNn5UruxrScM&+XjR@SdaLOufLfx+q z?I1_3fGmW5+GFCH)ab3HzpFQGuVKG|l6H}5sXoW2lFstPoZg(G?-0z?{TEM>fB71< zU&UP>cK_*wkUB@e>!Ta`H`DqioF`x-&sq~ck7|2)BF8X8i(l&^`!bot9`lwIuEg01{k2@nHnpBO6*u!ya%~sshJfHCL(Xddqk+J7thnT92d~j|~7=Bvwa}IRA zhDlaZr6V5O^cLUDcpH;`&GxPzHJI8)RPz6>mVmrQXlQGG86PUEu@WR+hboluRd57y18)AOln%butp>L z#t)vVvjLLQE@Hd#`N6otYdUSZqvAj@Mf$F6AH-^oO5Mq@7^VrVu+ac%KxN0qGlM5( z0UcO2=zqW57vPZKO-)7Gr{3=9nQCHf|C2GPK;D>mGjZDwYSBz>|7{}Z&;L#WMP+E|CGSzMUa5J zh&{O(ix(~lsaG?{nu_x5Jx$#EWpEue#eF2@=v)d6xkm8zW~|ahXI?WiF1`65 ze(>j3!er9Loz!&G8SF*61dF)0E%l4~H|Ulj4cWj{Jtd%EBU?wsET29Ha#HT%Dj+WyYE=`EFqD~nBl(K3u{Yc; zlT7yxeGKpc7NxQ9`13Io@HT6w1sWTHYYY6RQf~|^OBihUzXCP%VgksCf*sF5 zzj7-r6-;^vRY0wMrDWQ$<;53DB6-0fdE~nrTZP7vN}@yUC7e_0gwu%&L54e_ieW}L zlN8(AkVYb&qWJXGH^sA_gopNl;8G2xTbstF`20Ywm28RiJ zv1aOMtd!G67KnErs-LsIZTpP78bog{C4OT9jy*@tn&=zf`*{gEE)KIu_#K_4FAFXUNJl~S zD-UA#aw`=wnP$M2zD^HH>-;$W&$3EeHC|opn%L0eTLoF_4}j_usxI{`}E8mU2g=Hf&i5Zsc%&kH55J$nX8`I2M%xUC$_tnr>#3(SJa1 z`ss$w4Wl@%!h+y?Hz&i{GLH)K3nICiMc!bhXs5%lD5^2QKyk`FGrOA|S9YVMXZ{yh zC;)>n0e>j3`Q7`+3(*~=Q+e$6LQpJ8TWIBkO0Mh?EKH^B6OMv`d@Qq7Vz7?Z)XG=| z;$`l{^+B#=3^d1k<;gNXPbOy#o5vun=0TPb_F^(q@c~5+S6Y%-!6>t+}7njZx`r@8mXkX`m&N}z9l3WPyA`lnYQpu z`NnDcLEv%A;HZD&Ejh~k`=N_X#Ud3=D@bSQji+OMt-%?#Gjg`^`(|T{&%;^p^vm>K zbV(t*bJ-#3xJMqv-)Xq@yocwiwCyx`krhL&PWfoNqzHPgluG`CMh;0DJ_bhc;(+{L zRWy_SYQ-(oA1Z!!<60@Rd&24jbT?G4pZoZB&b`pBKyN*u1vo+@BxzbOv08aG_f)LAm*g?*qDUVex4>_wYLa1ZqNT#PK>qcPO@>#NQX;R{ujFEG%qR<*m=Px2wp`+cOJ?6p{zvu_pe=8Y{J!t~zE-T8vlV*hPLxkK zXHGI3Uyy~0FEIJ9vv=>`lOoFq0fb*fPpt|5ZVv4+IRm?Isg?~ zc6kS6Pk27Qkzx5n9-WsEmAehZ+3M{Z{7+rr(zYRb)3h=iP-c~lw%8JCa8hCFOvF!O zDjzFL$^hgs%_{KPbBNwY!2%PF<@rjvCzxFyT=6U&0K3hi0W?M{{v-M1z`6nBBbreslQucW(@zU>hh-TS!*rDR3R_<=LyBwh{WYd&-ANV)u`wscivHVw^nE36p= z4z49CUTf4$O(8i(C|`HrK1h`RI0GnW*hCWF3swt--|jz% z;EU1hd_<4k{1zhl{^ii!x1oq7Pw^o{@|!uJCUTiG%XE%zb$DqC{)||W_;n95h+I23jZ>Yqvob{WG>BC0Fxv5RR_ zl%ao>FgmZ$t8H`XZwH@WzfyXODoBW0zEVavP0ZNR_1J-#47s_(WSsoqIhNNE#BJsA zI%f#8!taW!ir(3qB3YoD5WETT8Z-4SkgX>yb{W-Evev&prz+igKl=Pxq{X~c#KRcr z-(;L4yDEwe&Bql7!odds(?$f!uyl3^6dcVB>`-K8v$3`0wK$ARzofTWI+g16G z>=IsEVlijk)p(p^wa9zIQS;1M!OO-f;A;cVrz`FFauUzC1h#3r=upFSO;|(QA`8lJ zgpOE~dn~nB2m?Szk^5jHBhX!&oG>t9sSei$UF#Q}qM~^FyJrB#Frd-%zhJEZ5;S0|ae(|0@OQVMNxet`fW&Vc zw0nxqtFz4S8{t2`fWEoox1n7!&hy1DPRvt)KE@fS>l$pZIjIer70egZT}PQi2#Uwi zmsINpJ&~EYE?>Y$1E5g0lhe}*tA`}eSgJR|x71wBmHvxcjQYdr$wuuO3${!`^*N*! znB#+~#F2dJuXInYa6B8T2|vh) z_Px=47y_ly@w^;9Gv2b4AZk3{IRIeOoMb1Rl&1}fhKHwR-}zfy78-ge?m8@0yO@EU z>V$^&|M(X*T$yN1L)3_B44*rI)HJ6>2h72|>aC%EO0a~Oe1KocT1#72 z&os4bNUQ;8gPiNZM?KiDJM4W~Fj5WDV>GqZ{FIHE#{G=GkTBDlCSmr@n-` z1Nsnt=L*VsweicXal(7d4#^~T@%L4Qw|2qdyg;3O*$iG!LS&07GI-9r;760K4P; zh*Q5?nIVy~h%~}m=%RgDaTzZ#{^fQ66yXW9&!y$OVyP+%n!Vs`XW(7XzWi0j+CM$B znOJUL%v@A;b?Ddrt*6|Ckj8rO?^LiaM#y}BPR^bgv^CyXue2qZ*frl9ty-wLqJ23| zEIE2W+72?_-@Sop1q&JO-UvkKz>z)fsp5Fc=!6StkXtG+d`JKLwSI(pw)$izbqFIQ zqq{Mm%s-7LofAudcVZr#%w*g9>6PaQsJE=teV5YY@4h(tIl%`51lbgNeTDS{)1Vxi zvB;W9#t?-QGx|kOE)qX|7Y3N~vk=Q2|PHN++J(u=X?-7IP~FV=(p+MlQPcN{`^O%KH!T*GN$fKeNMCSK__&;9?E8J zc+rwe*!sHWNYKZOjF9Y&zEoJrh`{yl1xep?N#I0ApGZ?>Xxg43ZR&u6x zS78HF#yBMJ1@w)`ua9Ol=`4P+I?v1JYp14DqGWcvLD8IVXz+R91I9A=2ZGT@+D=HQ zjVcr;B)g^ct(AZO*EUr2vK+wzf>`iPhCiWO)H}*j8AQnqQ059rH4lmc%->grf2(3~ zf;e0i#2zqOI3KHX;i?U#mz|T!(p&oj4g5ufiIQ+H5Q;lrc4u?EO@7siV@8a=6!deQ zpMW8nJwYe-2W324kWq4?%hEn!X!#Ir_j`7CdCLm4SIrk`!QDJ#m--+;XolT{nImP@ zF;Q8Td=GIkG6^24&d_SQ8qlj;8*a00A+HT*J>qNa?=hnrkhcE_tFLtnk}to1te;TF zh;#!)142BN{l~RO`8vV7*z{tGdja=}dAC*ahIig(Zc{g|X%`x%Y~*TxCU$DSX;lZL zg9<}Pl#DLZEZFjGkvg7ktdJqAQ7Y!=khZSY7gUC&<>QBI8Q#!mF2Oe_qm23=P_Q*b z*VWa1k_GZNwI9;}jM|JpCjSITco)pF98l1(WW6hjOib^@#M zbFfaPxfGrJ_+BdP^|ZLhJebj$dUrz?Md z^Wbv+imBHW<_+s>jb0B8!DDVO*61rbUQKQaG4&R^Y|h>6__7UiAO7P~?CIO48FM(N zOT6Rf9ImE;tAmp0hJ?Va!IqDz5xl~Cb%2lB`$j7N1z7l&D#LKe`S+KBFs#x&aj;-n z^_vZenB*JRG!z_zEqugui^HsnH;q^76CUNjewj&fZ^zZU374!e$HTEBmg$dow=n2O z?(oTk{`)Mkl%=|`!Aq;q~gZQ8|I7tCNOskBQ|f8i|=tkA$xj=5t&xOQ?UqfKkd%yjyr8Pz$=$3|V9hE~XwPvkZzFno82>aQ~Y@b*7HolvJo{yi=C}D)t zUg-jKST;aWJO{-0rM{p))R8p1MM-5RIHO(Xz)-YQ&pO4wA;Vhz09DPq#yP@RXhe@2 zus1yV5~xyFpg`Ru{6+ee8^wp(=newVMY2}b4Z@#2(viPUffrvZMjiZ&Rp5Vf8`52U z)zn#p?rQA&k!`O9SC9KPr9o|YX+Lj^we*aUR{;{^H*T`8)i8F3#Qi`sOBtkP z7~*4(%i+5v(XMH?RnvTmtmx){w*WcQqxs1KrAU65t=d%Dop{w^2AE48wItmfV9Sw? zd>?`bghfS5@y=xBDIqH$BNKJ2I#3J9NQ;Hup2TMc+Cw&=;!T!8hmV>73ni0!cBI3d zS-ND*Y%w`L=wrf$$NZjA`yyhNe&SO>Hc}5t26#`C7ww)(m3PC)uG7y@r@Fl}GJp4s zLesJnD83T|UwfE}&J%+SafMyTx0l-_1}^qtPr^7+aKT%f^^uxZJ;5~p*4t&p*g-SR zG8n6K(~;Oq$-4JG3#OEbB~_WczgGjImYvs`%TgcJ9sd^QwEpT`KY44ru4S98@-!tb ztwZp>eTW6Cv5nIq^{-h-Pes4VVMW!L4~y{hh)HdEVp-)zs2X|idL`qR2qG}z=Z*-9 zB$gXJ8+~){&n&goBK*K_{87n#J#P80nd9E*b5C5sG?(JL=qFPgLM$1>J_bR?a_wbf zmX&Qg7(3*kM(q{mBDtm73g0tS-SgKiwvyEbFwZ46xnP1c66MFsz>dCc*`D&9OY7cV zVcNi=Wy72PrFZr7L@Elf&f4yN15DVTOZ3={i?gnAeC^$ObGXm*w)1k&rkIQ_8hx=p zFLVi6oRUI0z-XRRA$Meqo$2e1_`(DYtJPG`6#%+Exq>wp6(Lqj1uns?%=_LtB`6h0 z+~lL6gJet`K3`f(S8`YQ7eX;+wbZrCLLc}|uokXnsdo*Le~ z6lx3mCMs&m*l^c%KLb?`ON2>=0dDAcV<`NKKy!&@R==K<^*JN}b@uzQzd0(J7sC${ml7FVf^-q7>^1B30}|c7 z#L{%D559$>qB6w$hCJInHY%t7lj}>{!WUaFBHU5Z|GJFGYF}q=dpQ`u(Bn{kjc}$+zR<;k+ zQZwMRLJQh!%nY&#K%aZ5&Z+w!nLmz!r+1eP4Xt1i5T1{2!T~^Hs%T)N$$i z>eEn8Gs2TJ>pPyBpcUiqz;UebWj^rPav*+2`2blkiM^Z0)JpjSe+8}pRplYh9VacL zyqTC!Flm~d87Wdx!98r6p>ywzqEkILYM|SX?M;12IwO|cj3(E7OtomxzF#*I$q0W4 z4(07KD?xh}G=UB!fIiFDiGB1XV#yw0pleRj z0MUHu{8%UZH6ZVo2-J7rIxqj<(XEIbfXlu$4442@XId-ndVhjuhr>^}+mIhBd04oIc?uD}~bLIXmPW%4kz-V{`Kk?v;f$Cw0#BGmw5Z9m1`gp`C2tm@2#PScM)M$FCrv2E=DNiJE^GyTWd zoa}2-Rk{vQ3r4JOj77RK^q~<-zvco?3y&lAgxIk`(pH7Xk{Vucz?{rqX~dQPC4w~~ z4alX~oG`PWa`Ow;cbXE++hvgo5%V^&wX#aRfPSGC5Tm!tyPufhch5f3Q?FlCCY@WI zzOEe*Q>jz@V^ByuzJdH#6h7bC&r#=3YCn-W^g`toWhT5cZPb8AAN6y`#PxY=Iq)S9 z?sOh)K6M&PyANI&ZX}TEsu)2v;quasjJ=EBPNmu3ROH!L!$0@j={b=Woe_BvGXv~(Z2rb|(A-bSEq#r96 z2)$*8@-ip|)r)vFYOrwP_)}se6$n^+HIrjJMxxWg41`u&^+YyaLur$G31dbX$PqN< zFRS==V%by9LS$XTV3&&Rc?Ma-z+6<2bnl^rlFDM@ zc2C(F6=xl`s?J%G!!tf*{51t(F$+2nD-Xm!$s6Md?zH&| zY9{JKUL@zgA{8G>bM?Fg9m+HV_J<~;45s8j=l?nvf6e-)Uc$|q@ylKhkPzYBjO@GQJSVL%C(L&SB(_uI`L27n2pEy$nWmyQtl(uWa7 zBumVqA==6z8-*4C>dGKvUJY55wWwIjAjE`CFYu{+!6ZxEno)M1Q+D}}?BFV~WSJ0g zZgfStgYYxegLF_T_s<_}Udr=w^dEiVit_RZq!o@#C0)Vb`&mwH2x}jB=l=aQ%;pDT zKLZRba^Icra2{?6%qgpbRsNhqa$gZk$TPd~rLwV>i7J19f@LTdj}RgYe~Zie2DH@| zLB>v-=g+R$b&Mr?;L7<`=96V}RnMVcdLA9qRa(~pJA+DC-a10{+_(X8c%Tv%oZ!;> z^Pj|c>Iff!g)z6pHfi8Fd0Mkj%2-0Cm3m~?)OvfzGf$`CN!dDL%el9jxKi18&>B|pj**{GV!X2Pa` zU_zuPdxnHoejbHx9W@+~h>EDUJiF%oq-b*xerT#x@gLcZ?$W}U8c=E~FUyW#@Xa!m zt^Ac-ali%7yR9^jj7CK-KzDjg>SGsY8*p+!K>`#(Oc%)))cJsMN_PbU2xSDS2P4qn zGaFSHmM$((2m}8bvSNe2MMmvG|G_9!tCtD19j!hyf~UZDAC$CY6g>HtE6kvfd*X|? ze}+t=I{~IOZwL8=$|W{iIB^_Dji-SFMICc!t=0kcZg0rEYOP_j$b7r=T~PipIu_q= zbu?GH0g_3PxI8v%aQZH>uhk&?Cg)>x^`>s)M}HsG%Xax~Ej7PJwAeUllep=3U;uwc zzW}l%(*x%ho?p_)RX#+L*<=omy4tv(x^~S!CHXq#T2=x)sqWD3A#gUC#pCxS2Xr$w zs|MX1@`al2qB6M)z>PW2@eFmEA18%Vfr8WXV_7T%2yF1JsX(xfOd}qq*6I&Vmg?yk z`@mL%Y0>B+?TV+(&lMg67FA?ETqb}PDGne!RGB#E&WU5oz{Jr1$aa&-tS5K18>iCS z+Vth_Z8_g|JuP-UEq(c^?~G=eugKXpcGb~qZno5lIukKjw0nLWGy3{A0&dfr$OwEl zQ;&*09yuDGv>-}Li5OTmcCs*P?G5m~xL#`F`XAY5^`^mc^V2RWORpa-d&%W!F49ge zT7a>mcF04p;3ak@aM=t{&KxC#YuQvl>3$hENb@o|T>$){aqzb3_3|J+l^{Re>>N~) z%k)3Y&pXH`k=8Cn@OtG{q$uHiGi9K@r1KpkFWNJ%(;}%or<|e;@bU_x36U*#xvcg7 z9ZErj0=I_4FZFEpS^EszEFp-p$jfClo_DuW_r#YWs(_(IUMVP#=o0TKoO|k`{E>>g zRCn$5y|1U97si-({v!+UT+6@7_!qr%mAPZpH&ibEA5GsK&u067Z?vcmtx{X6)QH`h zHA0EK_v%A!T2!r?p@Z6c#U>H4T1xHFfz}p#x3wv1kBsMc_4#~%|BySp!hPq?b)EM) z@Ao=EOhWVDrbNl--I>oIS6MJeN2Qh9+?z znjACzV=tyx#w<7=4e_-tl zI8bRr$3k+Crrs$fb#@08i=@;Mf!`v7>u6`?tiKJ_7?i_1PUE*&gplDA7Mb-1CE<(OGSRr^ z=2Pdb@nS>ssE{h(OV{y@c$OEdXr?Sh z87<<90^I@XyX%B?*~;cKchjjQ=$8$Pt@IyPSnF@HNA7OGzt_Cz0ZGMa(PFz@IxMz5hcltq(pJ%hO@|ujyo#Gn5YkL`+0iqrCIwn0_O9~OB$kv$e2U0;2RlRT_ z0nzW$ba53ClUuu%qcg2fpGnghT9tT1Z9=98-La_|B(D7W(Kors4`N^Mau`0GgZ92T zvpKLj_~^0h9B--5Kfh;cC9}M9|BUseD~3kNe5+B={+@NIi&&dM-j`b znSr0kIH6CO?n$m^NYsPERN)Q$Zu$gzV_ zVC=R!vVH9Pm}~fG>nru#j(PL}Xcwd^)!8GR14kZJhgDSecfhX2t2bw(RHv;TxRaKN zPe7;H;t?S9wON1z?VFvBIYwVvG+3FrV&9!ybS*2SsUDaA$aC;9?O{CY^}rc{$()d= zZ?dym9A{suyHvVDqfZ@C`L zAoR(Fx$VP><0r8dfBiEGhbuT!m{2QlWL=j z-+}#{ctO@^$9oaPTBU7vRaVcr>Y}nyLfXy#e#r031!r4KtFEL^02Aidg!#3>nJ#~z z##4Y7>pH`M9)w3g^s_+hmM>a`f}iB1!huFhr2(=lsOW-{PQ%tczb5CJ%@3XT>7Q>C zp)PD$L^ob07g=Id01+_nu=myfl65exJ-Q_zuJ@}&cJVh`$1b4WLk)?q{{F&l`FahU ziDP;Y{G|m%kSKl=Im7sCDJwxw@yIuAVv7ksW+O5fjHkkrvH?Rg`l<>Gx9Y3yjRnsN0E{qD*aba;TSQhoca4oT@aI%Ou1?IztKdQIC&ldWGTb(6R z|76R(-(Kn*$CZ&oQYiWBLi~bs*Fg`qw+q4doEs({chIUiXi(>sqCUzd;Y!sP9_w7F zSaEyXa_5SVsdF!JCY{gBwxG&9{RHCfsuPYgi3l)>DU)?3PzO1XmA$>3D^6m4>^!K? zyrC|n%~{7kMB|Zc- zlPk-cU*x1{CZccE1xQmOal}Mk1z@;jz+(+7;{mhcYhci}i30(qrvDsyDE=by+fn2S zTohJfs8-({`e_hQ!35g^-%bM=ZoW2s$7!O_S-gG z$b51+?`y$)e%*=-%Z&HH-B~%e6S)M0zpGu8t97Qw6Ew0Ce>)2RC~(hFDW^EzIffAy9p9 z>f`O9k#2A@SCw0AzaF=+iRzC-`i4jz=Y5HrZu$l-Qe!$B1@^RmR+f(0WHM1 zq5>3@P@FyOtZ4VE@os?P=o1Ib=g_$$%*v7@b7g*isj1Ae0RQEnyylMF%90KLYObzX zdPgV(zlcMN4w%NM?7Io%l+9~(YS0bnrX6TtR*UuOEUt5lHQGRc!C~o54LYjwQu&p& zQ@R7J@@jgdh2~-qVVq0$B{1N*YN`|v^SzE9-=jF$E>ZSif>bcnCVH^{oe_&7jNXI zSet@KpS2su=}nZb`NU6`D%;sbo31SmGAv>0b(~6DF)qL8wzPhvN4mIi<1!IeF=}7m z=`41U=|PaSY+O%dq$T~OzF93~57`zywqAx=Q5u^A6nDGAwO3RbUpD7fM{5RMI)L95 zHu#DHVcKqF&Pm9}63$s~gmo8OQQr#98B&69*B*>&8($6@)iA#+bW!zd!DC&btE|Lt z!RYV@J{r&6rWhQCjmWc(QCNQ$Y>6wwS9sj88q`RjuG5)+78xest#l*3g!!1WG@8fa zbTbtCK|3{*<(T6lnQj}bBXu1r7~AM@W`8!@Bu{S2mz93|nH=rA1Oe2A-DkKf9}t~z zb0HrP!otzT|4^Sp;4NIYM;6|42x${!3` zp1*8Q?`lHYBG@E-82Uj?9;3nlw~?Vl>sXc@d$6h7 zZ6^qSc~!1A($_~G(N$1oB2f0!MOJo425Z|2V-3=5C>pB4puc*2iF3}i(^NBv!L6FH zhG41ta{za9l~g%a%5rngSo_8-gH18jn-Z89_uS~{o8-1zm6erh%v8(_8}w3Y{BanF zRd03o*g}c#ME`{#NuBLlwe#_-Qjco0hV~K^i{I%yzfchI?v7Ufq1jgvX6-fA1)Kb~ z#F`54{ss-h?rD>dF9c{@f7i7IwvTS#{p9Xc`v0^YPh^ySKc`EjKMy^>ViHOP(-}#X z{5YMny0O1!n#|RJNKdxdo8$rJ^7Mn~lC!Wsa+y;xDRv^l6Yw2r+L!`YACHFN_Z4Vq zmOJjmtHdH|AxI(T77DR_% zPS%d~nK3+lW!kTsZ7V;TDYIo9v3^8R0G;kn#LrgHwRvancSv}*PK<(RsU49YG zX<~aYd03+Ketxr@+BQeQHBd!drdwBYMP)&yr(V`V6ciU6XAc!mZkkjYo})6DDvkKMX9p*x@gvP+Y`9(=liOsd^@-< z7BLL$vp`bU{^UgJcjJZ3@3w~O8STZfF`olD(^}Yl2Y44%&T-)j9aVziuixW8r@qsJ z8f?$I_U4x`^k8kO1u5_(7~kJcvPb1X?30opt`Bkmu!}}x%0})7B6PcOV0?|}3+csf zsEG36-c7~L0})|TnSf^SlR18VNj?jj%3S*hifIl3auCS?CQ zuwJN+j3v)0IK5OZ!8;PQ1A6tV(?2{_G(M`#yB@acb-rP3a7Ex8cQ+MsdZK@lb8hUm zO~N7;V7Sy3((aZ;!C$4TG+(->5JN+GTgyp27ib=1(xght)t2&TZ2NuD00d8dTU1kjH|^Q@%}H z2LQhO)4yZ}hZJ9;qV(smqbG;u&eeS9_2&ki{HTC)&%-iRw7GALIWl&Ng=3g_Mu1*9 zFzTH}^Uaj>^t+Gp@S}^%E+{~~NhPF<2At_K|B?YMi~`I1kngB;4%k;q?atiSS92U( zIBb3;=+Q+B1hz~Es-_Ct7p7ix+a6+|3JI$paj4kY%@ z*L$W%4x|Wg(l6i|l2_hd1t7)Ef5{p<2D6xM?qG;Mwku{onLqH2f#i=nh{lfIs@ae0 zmI_eSPs)6XX@fdhSSsb7DxUCn1Iibi)Yy`d%BinZ1msuTWVWUZ?rAiGlXM)vIZ^HM z)N}rAXSvHnwubtTcc8qKCV)|f?Y9;8h(Af|<@0{rAPxg=aMLm2bxhC)7cAxzbymR^ z-LT<`Oo-^JD`THoKfHCCZzsm0DLUNqa4LO*r4|;MLV4>mxUUt6y~QS`BO9(?bIcE_UU>$j0Dz>?+y zDt|eE0JlN3S#b+mWT`Mi!F^~)@?{Tc{3m!Ep{SPaFrQ8-gE=s|uy4mek=!w+3npFy z56C3B00SyLX1va!b#-jiocIh7;^5-@_(_a|YXAnE2D6JK5Mg!Ttr+V8z-fv^bh8FV zH^e2)WQ9oBylwXfzgG>qO^^lJj_fL4e8uVb)rXVYZ}+^WrY^9%ATy#UGm*l{Z;tNd zz?G z1?D`>^cF>=Yui`Df8Y&C%{!nr^d)~-`gIfaJl{)ldPDu>8DbKeA$rx#`{>Ow>~Q2j zRqSoSH5Szn0mE(qEy{S+zTkGPr&S+eIV%LW6gB7BS^--Q>UjpNJE$ln>jYmBbW;rq zr}a<@@7}+TMXg(ZL^RS|e315sfL^SiSGRCOG=1Z?Hq$4Qtb+75Ps_mb;B#mWx zX${Y`r+Mh_jP&2Se_Ws?1<@M*d~Oudo_6*st?2!^(Qn1{;FUwv8UHGifWAvTzrK6J zK910y=5sM7*i8!A^aefCSdt}VG!@Ff-?9X;(ymhG?uAEsuD!2p@?=%(`?z#enqx14 z;?Hd%KP<=1r6}rApHwKU-rk#BKw2gWE#plsqf<3+E9iylS3Nv<)iZB6WpWRun0|{H ziHg2EX9HRa6EH|SqZf?w-cpNRR?JtsgB@n#Tq3%(7}b?5&T%`JPu`-v<5Xc3v!E0%W#*_>I=R@=$M%Dc+|H+EoiSV@ff9b4!S?u&OMe`p_xl4sfSN7cBD&; zW9`nQF?vh53dRa030k^(nK<7E;RVhw#@ zh>W@Px?9i5+02j=PGhPnq_=>^3>;B~minItktK;uX zfjUBE8=ZSuN?U7AbA#V@%z^u;TmBo_(n`JcZ{Twqaa6`Mq}iZ0Z6p@pUs0pY@zoM? zFMob)l$Bmqr4<2Pah)N4bI8}IMudv2ct>=TNeWDNge@YAQW%mRpJ7z!;U{t=Nhuas zmewKmF&j60hn%Ei;6SvfoISoXvp^ zL748K;sMD_?w_E3sJg|gkG~Z6N33`C=rPUFQI4@*s>Dfg_9)_=F?HjCxM zs(3+?LLz?5m*Xv1|Ne)&Ob^R}fU(a8u*LIt=-X@oB=Dp8QoeH(_+%Rcj%U0BUpMwI znMuJ;%%xkYe~skHTp9K>wFJnT6iee5j{fbaB|k&^FSJ$AAg{`DK&kGbzFR z4!{PS0oAYib+Ea@|3_U)zbr?3cF~|Xe`i5=2uOnSO<20g2=#wY)d@wNn-u|dG-|)F zcDr$<{27$_LA@T-Fz3$yAXlNW17w4qcu|X*LHr63E=OXNq!}7P`kNM6l`kw1XpfIN z(0Yq6i0*ng$Pooa-e%&O0`dZV3(*N+Oe=M?sss*o}Gq zVm6b~lGI%|>B#6l2S9bGQto(g+DY+4L|=(*Ig(-`zBGk^_}aY&q7eO&Fx(Knw2JkX zFIU!o(5?RZ8Qny!yyATi7+{9pk~Yq9jUn%@zW zMK`p=Mw}q|UD+?s8;CDylL=Q_6sPvA&np*7(J8WBo=wqLR_lU4O1bs#qr#0;6_#!?4ltuhhgQY~dTnMztF&*KW1}2x`dN_ij>oH?zd9Ai zjWpiMczoHg6c-b^pDuYPIb8XB*+2JC;uk3Ol{xWzobIIk@gie# zG2}Fz?xcKmrL2B{b9p4MMc`+n%IM3hW^?UzEtwOsH=nnoLxht3-?(sJUKhM4?EYK3 zDcpNrsUoP7(?NCdOYhoyKD3ayrk8t7rQsDvafL7dk6P~_B{NKr9GhWghOZ2VNbeEN z(E1kVX<2O0fi8=rv)bp1&Bgs|Im9lKpHq1?P0`?WP7thCYWR8$l$7y7;t5;{wtNS%^%m=dUm}he zB%aX#zj%hu2F@Rf#>&F^nx%qLG7?lz_uXi_z6H?meJ9<}q`6Y9@2=sqtcRFpc!)P7 zZqPqmTSXa9L_#E?qE=4D%3o_jFA|y^ zdmJ)?lR=`}JAAr*iK?xFWzK03ZB16&(Acn2XV}v*3{kbiPo<5Wi)6w0p?t zth`^Pl-!W}FPWe(M;SdQ9Ya$g_q8!rc&U&ue)JIAU~(gI0fN?zq4+hTw4sfp_z7n) zV4^i}?IJTG?~HlIy?~VVw@Om$sjx3yGKWCs32tY;G=*GRSL)~t~-?*7?m zrjg1xP30l7wE5{!SqJ2I)8DeDS1QKQVia>%O_eNdeL-$#j^>0J!sTW*}F<4 z)cxM{5te{v@KBzpo?!e&wgel@>0`fwY(>KwVXQXYauM*arq_EuVz#U*8}_kt06T`&(BVKTK_`L|Fen5T+q^zd4w z5oXGDe}K$XAjaWZkm)F-o+}0&S6$2Qfr)QRlt{cpeFtgYCU_-7;G3Lv)dP+3Gd((Q zgY6-OP1SE3@Ym`q=P|aTxXMyU6_t3tKwKrmXlwI(Zp>Tv$~)OMM%NygMst^G-GGOi zdIq99odFH`vOx7Awzl2tAT8(eHQEsGmDe&(~GZ#JZoysbn}sW-2- zARw+I#Q$`(8>&#}qhZEN`ykHvZfrj@Pt1+CyJw?cQJmeShDK&~IkC<6dk3EWNDv>c zvPd+z073`7b}^@>o$JbT_Ycb!W@c<#TYSb61QT9(gpO7-zd25-@MF(C>%_mugci|V zzJ1PL)?MJ0&%A`gXxPVv#9TTWaD0zzN4Kg5UE&;`mL<49+Pk$T@kVo7N8qTs*SF+i z(f0=lI%!#on5|AO*wK+&*(_b!xv=-$UM~#+J2q zc&iP=-WtEBKTAr8McRtX-7c%G^Wu1Oi$x~wm{x#Q9o7}|_I{|qCD`Jfr2f3XtS*nn zf}^?*9tZ4>eq~3;{r+HH=w_ukw zwoTic)C_b$_(ECHcnu2C1J<0d8ob$HBG{LDh@dM&<*yhRCDJihbz#fI1FMa~)tok?WOuGW2E6dTwtk^=B7^ogBG0 zI9+T_rn{#$|LP5+9<@1fG<0H3G4kVt;SD2--_xTFKb{D*>Ae-Evi_G0Yk2DXeiy`D zk`N~YUZD@nuAUGOL!`4R!vdq7H>76U6ZfM`LdYiMXbJ%>DFmy14zah*36NH-QAB@8 zZ$xI{=D1jy-R8LD1#wW=KjIrUnJ;g3IRSK=4jn@_cUkFb+*sNJFg!Fs2KOV?j* zk5h)q=$*=srdh|2pk?{nv?rPVF=gMj#@h0PDfww~$KrCs*w};gT1{3K?%53d z;y+Ob*dMT_M?F5iydUCxp>?ApH;+p-!dyERF#csLFaViMvv0B+YoOL7eq1Htl&OO@;a5`CUGXoF1f&}n7MWDi?N3N1L+kF zZA2jUb<;ZVjINPpzn)(r(V}!ny`^i)w=2$hIBDZ6ds6(liK}z7&p;=Be|wW|RRO9m zx{QxX&i#clY?}OoI)sWE<<&PkrGNhYPfk-$z14nZ*jG=QGxG-Ahidy2O-JHdM)}%a z+0*{x69;cE(UPFhClbDzk8EPUMx@N#UOd3nXM(<+9nt;Df*X0qVg=`}Pb^`8zyQP& zbnKgll)p1=$No_JiviIfb<E}4!+IXxJT%DT=$RjiAR+N z&8uPL@>1T$5|?N!3uT;SxyKC3pE$T^YXcZ#2r_Nj5$W&+H0S`L1nHrvp^5P=l#XMs2z6#BboGJjv<^Y9x{IE2Lwd=oV(X zL#kUbJ;cnGpY`n%rudM$Q)$)TAAbled0NUCDYn>UDasx#R{tzeUb@~3b!H!bYHA%D ziW7YIC$E#W`#jIk46ZNuoh``!6S_j$=nB^Ri?^xP_6Dv#w&`_x7AvL_@$H^1(A_Fv z3*|E%gqO>}@&u4X#fBbIPvh7D>M|h|Z6Mv7F0s%m{=LGN^Edf#^6zzKy zIw?w>3F*>=mVr}KxcoS{2NDCpN&}%2ZUXs_mQMfq`HWS=s#)WE-p`N)}d;%b)Mf#OGJ^20{G&j@K>9zpRi#L zBQPy_j6w}C*N9f{4@_Ev8CbfS$MY8sXQq}n{Kbay3r9mnz#=qLuxrF-Ox*N;*upJ+F^K%kSO?u4ab{BB zj4)K6UK}$l{MPzqTRkhZT)(!VtmJpqCjYR|L(8Fu0q$_`1!Ks4q&t8!V+dy4VhO@crM&Z&#_?bU(x-CKvx0L5+ zQk~tx-+a*B(%)a7R=Pr~EAqwYB4^!R!$i`Ja)+{kD;;k%tZ^mW3I|FzM)YUl7j1@S zm90PzaT)x1+KN_M6|{;BT8R1003FLJOc8sT_ujY@2$$2akU=v0PCCt$Ag1 zd<3YM1bMA^J;L}Dq-{*ZUU*42iYt7fB<~0|yXm`5WU{oNFLV2JjDEHi5#qfyHQHG1 z#N1SN_u*CddpSLO2GA&isi#%j)7oC5U*}qe?H{ch(;Y$@b+8?yXZYM8I#S zCZy#*<0vElsQ?#vdt*aaGd$)3RXI7WU1LHFEGcff440f0Se-c+O)q1_Z8eG^ZQ zVY?GD>=T1Y9qT>Ba~(&sj(zslzYGw@s^|&e8~TP$1mOboW^J%7#g|Et`5w}G=|aI# zfwBhOcL=`tr8ED0X0(4Y$|Bo;#e?WJ>(J@D= zFLH$8BptRB2CLlIZ{<2Usb>)E+}K7Vb8txm6h+ct2R zIr!lPGhtI&J^ysupbOf?){@K5B55VO-&4>>nR;0M0JYe%&#)%{F;wuXq(-%+Lf_J( z`k@P27_V_gd0w^)YZhS-`@UFPC?Vi!tfChbcT|ScH5Yb0c zt!8TE&l(|c2%u3I+FhnsbfPxo0b+Os>~;mX`i^jtJ^H4?>&N#n14hzZu=yg!eiw;? z&H@$PWcs=v^u2`geYyN#vydQhx06@<`AWxC>mDbFsx$K=$>NtXk=$f-#+C}#zV6;+ z^Bis1`QgBjUL)b%tLVgX#Fz?YUWNvT)qAH827dZVcRrf%I-qQkjB*qi95QQhG;8?n zdDc4FfV-XciP};_e)?tK>R_X>8T0*-4*w{!grk4tazpyKsW)ZId^Bbxa@3t#9BDd& zeM4T&AYZK*Zwm5hk0F;%d^qPI8&(lf844fZqj$Tyre6M(npa$zzZo%9RK9!A{BC|m z<`B!*WcE~aP4m`#w%}sixtZD6y3xr~rzdl6dx%LDYXMUAe`HOq-^sjPhv0OoKD4{~7ypHDLlZd&rBpEnv;cc2v!I;IWB&7&@5L|ue z-MYrm+(rJeL8BYn#+Hs>Vo;rKiz~bTfNuG$Lj5N* ze0FdC6!0OHm{n`$bgy1_kdOGzTU4djJt+`;4rYGW+ii2M=9<`NGUA>|K6CWiVU668 zA`La3(GVApu*zP$Vgz@#Utjc_RuXrJq3d=TC3X5|7l4uafaPlOpYvTD)x>m^JdoH# z^v`yYIT<6?NaV=BWH@q(oHRcdB7cG%kLg*{NztZ+{5SaMbt*Y~^i@GMy+M7nvP4Ng z=R#VsC1iyrw8o;&JqsD>4?n{2{Nu{$Id*vA2QIKSF_WsIR)}vPt?WEs_p(xo4$`Az z^T#|--lfe7lOu~q5=~$fIunv7TmR%m*stWnvZKpcJHFBLYypA=o=@O+fV21w(hlzo zKsElyMdO*f&ZxJfO-2CZOaod-jNuJ3-`!CTyux=m9Wgt_qGQf+y+QrdBk=o*j%=5H zyZPUkJt+NwV8B6&(D6}2ucpMl56DrAeSc*C2***3)hzQ=bA&PkJ0im>Q*6{|u}klF zE$AA(M{A6YrduET`Pd8V_ooSUhRjj1J8YSau8die8YdrA%{2VZAqoeCEH*KW-i(h` zCfmduU>=jPsGN<)_7pUJ*(=Q2BdF{ZFHhBbCGsLp;O5@@h|cnX4|Kd?9lnSM(Ql_0 zca3y1>IHwxVojg%3uR+=Y>fF6j8xv?s|XdzNE>r&x%e&876STfhpo!VU?$<0g_H<2 zAoHOk!j`vmqkG0C!oAYWyrq+yimrTrhL|=ypU4gqlvaYYG$M}EeNN4CfbJT6SpE9B zzIRK)(b}ml_wo|bOLGiS>o$<}Jl0H|5UhJ@7OzHg&;#j2 z(PJ^iH@N9Z$mAv`X! z(6PztQQwAP)G>N){tcPtJ|5?3xTU*5>|!ID;F$MrlQz%`ux7WyR|JVdS|u3`IV^@s zg}{rH3LcH@Re4CjG;C?tD}XKyTdij%hYYdGb;qWJ^|OxVjy%!t+iJ$cM$Cvs;G=Ug zw5G;16DL|P_I?guB^j<5nV#-Woqo$S&QC#CLdDHf5PHnW5_gQ%x1rQ9Czb~=zP;iy zQ{qZWcf_=b_3Q}DWyGxL7nUZ4UbGO{5qLvaR(eMylz$CZP}5d6wJ^3rd4F4N*WJJS z7|ga@Bm*+Ru`bu+bvF%(*N`Clo7}@AX@{`%Ya@V3sCwMN(q)>l$D-EVxf_FeZ8?Ji zqnOxPZqn|CNl6pEix(AYLEi@+UP~{!LrQ1+ib2DhqV^0|rBASAOEgENS?X1s4nZTU z5GyLaRE@`&z{o_2VM_UOd1OGA>%mn@mK9ck1t*5^{=We#jP|T>eumOC11`AmU12hQ z45bd5Dx7D4rPLE8to4OwUK1G`DzYhb;kB`pj}~VdE0vJ;1e!8gH?~cN)9vuSQ#NA4 zgT(NIHf4qpk=-6{av$-IZ;bNXwC`CtQ{$kUv^|U23sn!&ccMV-UxH@z#V<^oyaPtI z47Rek3Dy#10R5fWgE242kkQ&zoq)4-=eKd)3IbOgaOio>MkAruU->V^fh>np3LU%bGcDR2N;1ojS9Pqi89#p9(VjBoI&GdQ}S*?)NJ@T^AI4CMUB zXt|mY-lLpCZbz8K3Aa$1}EP7tja$Ikr2< zmzst4mv1v4$_zl~z*0~a5937SIeKP4w~p%sLIkBvJs_`a5FJE8kGKSy+ulQx(ZPVW zX@CHNORF)urFQ2PIJ}8yva&)DLfrr3v<3j1LkJllt+)ZXZf~y)G5MHK9$rU%^@x#2 z>J_42I@rIoTk&L~s`Kn&M;Bqe#hN|UJSge=Q$>5~!>)%VcN;_pwrA=t&E8nFT)Zzk zed04_c&mSgLFDdGgJgO+uM2ds=R9Vd1lx6(C~DeA#nRO^c)25E))(R`2cL5&8cPi` zd0}N3$JHXZ<6+H(w1Wv`Va{hAv~83>A0BbEl{?hb9o_Q3^W?w2_b-qI2)4XI7>@_k znVBPZzgLwJw*xY*&Zo9v!K_qMB5Eh3KlF+JK0Ic|-0>f#YN{v6gMp;_szV0T0XI=E z`GRbmLdhu|Z^09^La*?b#vX%`)Ast*23SzDFoo_!6^p#!GX+7ZO7}w{73b?BJFw{< zt(x0sMVwKR!f?LD#7(yw0#X`Ft7R)Jm-Xk7aUa19b=5U}LqMIp8N#okaImE#;I@=w zED1TInM1U@ox=!W4MZRJ5{uuD{+tj32f!Vszh7nhgYZO92^8Dlssr#@_1bj0sdW0c zxN-1Jo`tt8(^01;f68m=obSB;82#x>OY-DuY5SJ%e1jK8LYDVN>1Y$ret9)n=e8!} zH*T`!3ElB8>{s?*FyLMGr$mCrEas*U^d5}Sla6h+w-W>(1aI7OP7toBv&;2CR(qBT z)b&10nCdc$zL}|F(vt`$JSo__+xPX>2;IV27Cy*y79Q98kCA_IhGd^RlSg2H(DX_o z=a+ivBC(gA%g?!>Jhs_kJ94Xa65I0kD<0iaC9Sg;yhEFfes^X8_L8VDs^(iS5dH&@ z!M1Bblk(Hk7nrWYiA9$?bAmM(?0kP!&80`eGWQ1_Hu(4PB#b!aBoCP8`#fKwn3;-At^tM$NtN*@(!GKVoF<^^YohOCl1rM0l0_Beyhyjw-a0*OnXj zh^ttJ>tPn$kVrQgRNXXUyx#4V>^eoF-9@7m`j&?8^qxnLmFV64e_F`J<8JIH0C(v+ zC;#cDu>B*(jLeToyJp$eeDUGA#@1%xuuDP(f37$eefj|BM~R@`c|V=j5&b7nV&E#0 z#^nZr{5xm+A=YOtBlf%N31x%#<`Y?I&HJixeH{?rC5f}AM(KU58C4Wg*B;@^V+K7Q zx1Xf5;>EvL_DwlA__S#($B&zb>D!DQ+&eQIx7yT}n%~==A3nyu1B(g-G;1DJ>)v(z zX%Tl^ogd&byzGJ>pn+9crT4F}-6ALevGUz1%W75K$1_-~^SQ7gxCdW${7^MTr=Do< z-}m@|bhGI-de3r)#O&ntjv^IsMfu+E##SS32>mVXrHNeSzg-5U2a^!2qIj(G9MQ`8 zoSjykS-&+P$fQ@LrzK!&XJ%;gktY0)wQE0fRz#QZ8dcj_wiEt1ScERLC8xJ(&1%oH9JKnmk3f!}) zExX=E^4?j06i=#d!kU2A`Hj<2 zwn$?l4jMsc+kxzvyls;A$b7{Uv+&XFZjJS@F^gx<=t3=WY!j^*#oM^Jy=B#H7W*`I zhKsNva_DoV<+1%7f+^_f``(^jnt(Hv#O@S{jMB>Im$=P2n4Qzh*|%w_}7JJ%Gi=tO1NIs#XEz|NjW8W;t+|*R{~T`%+_=`W&OwkheiM^$g_Q93z*38`(ByB2Cc0XH9p1 zfa3m^Qnc6-E`EUc^UUZ1_bK)ZY@yIj8WEJ2s{8%8gl{5gM`Y6`(Mq|%7KdVMgK4Q; zzaGT;Y{=QRH#2|fu+YvAhUQAR61@!D*Up-5I6ri~67w!-pyj#4!%*lzs%T1KF5X}X z-NQ0JRjb~{#rZ35AE`s1@L&Qr0D#!DhcSB^K8 zH|tWvmdWn!-Kgbgnx)J;)jJa1YgJ-mEHe)+zuN~RoyEkhyVMKIkPA}yUd)zxziynL zJq8+BF>w&BX#B-wb($J(rOa&H#k+Hhqha24cBigUam)@M=1Yc)!hcS}9;3Nul7_`2 zQ*w2AcYTzu4mF~_1EG6yjRB{Q+0c1hfM&=(Ch`~3I!Ea8CP|9Z8zP-Ib;oEjC7g@r zwVNYSoa4r<%jT8nZLYfT9 z!YcoEDbvkR{-75Ta&0o|*GW){pVNuA38==ek4qzi)dWg4<#_oo$FU{;LE7fjB8}IiLyVrLwhRkK zP992>yeJHR&?ZrPtu*iD(kFzKO+9>-uS#TZx=XByU*dP-&bSZPw&rU6?WQNsAfI&a z(e2PyIwGuehqmBvo)6`KZfAN#Ewp;=2z{~5^?jc;F=|EaVB_30q`-C}4!E;e&{};7 z+qqV-C2-KNi`oE6Y(?VymrrT$qYr5w5aXV!*IsR5FO+&`Y#cQ1v1M2XD2K3s7Z>TnDaTG zD9uM80O&O&VB<5Bb5>_hY6VmpPZaBtJ%{=@N$QL;+9kkI*HE$a^>3d+;aZM;7-S-i zr=*m6$&7ws*)QEF{l{I!;L0C9b+k6?T4~vtWdqAgURQ!9jBHW}$W)`u{@n1{k0+A3 zxNR5?-I6D<)LSpbRUq$N`GuFf(mL?@YF%N9#C+4E_vA7gLt6A~LN>5nP%mtu_3wVp zw%v4(?OU6-+%_uITtFfJB|E{9Rz)zWCdp~pw$`acGBBJA_xaUuL}Rr%MAMqw$x)GS z6bPKCUzwO(nrLX1x&EEs-;{HW@nw1y3ch;j-Y2D^jKMPNqV-GXtXJzC%ESB;mmBdZ zqCbGWcl3!9>}1@d5We__9T#tA_%#Wjd7;nis{r3# zvhmz%9Yg*P;7Sa@3G)vF#ZPi1dH-s~RivL)WDP&T>Zz7VYDP}n(EQ-5-Qu=u83qBU zR}-;SQ*m%sb;2{#xPgomoSw^&UHRxS_&Nw#;TL!u*&XCtMjFzu%KlKVg-EaskD z*_}MK?;;_|t}z{3@||DLM{>m^4R;(K*K>I{#(nU|Rx4U)yL`MTcR4$`dv-eO$X-{D zBAe#(t5Xv|wH=w3NSB>j(zB&<+XV{SR8^%9{O%Gi7MWh-VA_0AhQHoKdzZS`xG75K zoOU`~HJPS3+Fl`*nW`4s)ONRMKoj*b(xJ|UD}C>c&5*IuYxniy zXSD;7JLu>?|GfQ*vg#{|Ju=vpH-r2LO0Fpn>@yxgg$xKmgpHTM*6J< zv-(Qv({{0R3XRPT*L}KZBh;+Z|C-T}<67`US#Rkw@&*_=H8}P$f5jN+z}ZCDjEp#N z<0dYdC-=-mC=9crdGf&tAEXtQ!9rm z5S~V3hE_HYzUtl5`F}KhbzIZm`!*q>AR+?NOhOuwk`hKpcS|TKGE!P{h!WBrLwdwW zX%G+t7!ATe8l<~nu=+f2zrWY>-^Tc3J3Bk)zOVZVY9vXEN0dVh=6+c_#JUDoTYjfh z0~hH>!#iJ1KQ$E)zkVrM{W53dkuPV)Owe7_W?Ll9I}FNcg0bcLEYn`R$uv&m0QRze z{sRFm8Pfl2wxJ023>V(|7e-+{*rpwTS)>1!LXRyn#{bq$heP*XBT*=<@)WK7L)Jsa z^43G)c`%PUZ`*kl@k-jEwCAf}vsVa(06zI3)N&52!aF|D2T4wULm!awdSV?6l$V8N zpZ-Ur2NdBtE~&JyOUtkCw~jO=`ShBmVl==3w@jt4v*Qa2D|dt@pFnv0#}MUqESV+u ztp63gPyQpS1Rog#C*9f-6it3sR&b$?-@P-B3ZGbeucgYzO7WVezx$h3dbz}af6w4) z13Yv#k<2*Dx`n#smQMB|lJpda1F~o!%;~#~GB4Di?PJ3H#9UN?E#22`QaYM_%`D7Z zUb%qJr!&QloPi3f3Zo1Ly{T+n>j>Guu$5%@gXqZ+#AmPWjZpo3-S#|8bkErGnf)n~ z!|3V?>shjkX6>wdnTba>NXgW<@!PJ%t1~`O_{i%A?%XM;I%6|x0AIbZtnJc$0X{j9 znsPO-A3}|$9l?NkKC&N}w7j{Vgkv|eyH8HLFNOg+!)sdqs(Yb>HFMRk7zk6FvHmXrL^0smc96BAF6$Kzp(m z@Uko!+M#$eG=T>acrGz{?qW~!GtGMfgeWVhA(G>a$4*L@elHW9->$eSPNtIzHiYRe zoqJT?Ye&OzkN~5@%fg#L;;Md^oKFz&#qiPh02Xf{%Zm~#j^$CQ?|oit=E7TcLp#zl)5cv@i;px(j#QwZPMQovQN%oh6 zw)0+7enamh9}^sK&ygHQez4N4u>7R{>?N zZhg+nXi4_>850=<@l+`9SLH+!z=NS^0eyCXIVqxAv*z zZnanf43xdHz)D#jDD%e*!U4oFUFniM|NAbo$D8MLQF74Y-(B4ux(oPv zwXKUn3Rp?lAqcb0KlvX~U0V5)1~drrjw1AGXd5TpU~SHcdj@GK<`$%XZh9UA6%Tk!zpNUefJf#O#ovS z3XIS{9V}xbn(ikLGA!@GF%+K1fRHc~3joiN;thDRTPe;ZvGiMUhv==0%6o;hHS0wX)F10{QnWnjo}PP&qIuyoqLn?Iy|of>@HTTYtw?D8?YpFezRH? z`N@xmn5QnUQPnI}vVbv0PK8se_<=-DNrq#_8sq+5S6(9)Nx>At@;UvK@JJed{v)6Q zax@pIa==>d-Jt0P7Z%+rPy*_?9DudS2F8b?@qVDe#wO6!bsBzb&r%kO!5;mSlstL8 zy`+H-3;~rv>ih#}geOV2t72gz883&DrFV6oZtV-V4G0JZeHhWE47lZ46BW|uaM)LZ z@_cwoF)wA)87jzgDo6*=1-yiEzupOHuU>7iO)@qf@;*%Qp1v*eh6aiB4;vIH-KX?~ z3JIoG%+QxQ_`BPm{>7v%z{*2(8gp*V4U4(7yFKFWFI-i!Bt6n4>_Z0|SO4xQDd2#F zi~oo+Om63JgHFx>JQfhGsDm-HV89;`{(+$24&G}$uh{yJ$cp!$A+(=pOJtjenItJ& zq>nkHR2nOk%{M@+vQ}|h)#nc{LnD#ggi{I|<7V^uw}(*w>UU%G_2ty(2B9y;V>5fd zJCBj>X{y^@lsU?6l1Cwoc^DRgW8;d-AM1!*(0%kl7o4D_uzm0e=-L&adKNcW0-X zEO=34IdJT6uQIpwmL%op$!}xr3dc4zqG@s)@9g>R3qBT)5>Ayfg*FZ9QSG~^At*vL1N9q{%qeBhmEVp2V7ih1v5o;YMRdR9ai5B4GF_4dz zt`3AoMjWix&MgKJxTe!HhU4l8@WbjqvzPf&K`CY%?ROMBGeU_6BsK0^y&JP0&*+P; zB$>I*J1hzdTYftIVE(s1;o)jtci7@`@VIFgAK}y&xoEp~g2<2h5-?toS;v`vn(OL! zVB(oCXn&tucW6{`wQdiSrW9lA*)`oh@L1(O<3>U-WJ(sK&v;ARn z)^(GMw)~>4gxr8&r;@WGy;>@ngKB`w{Gzw35#~8p;{fUi2mP$v9!gf280#^V!;?uX z1@kU`c!TY0;>aWWDsHEJ%0T}FCO*}DHzT*|jl=6`|Mqm>p)%|l8HOU&xwmEK_84oZ ztG>$`xW?86t>rjV9$8&!>R;c?{4~JH?W`#pR^;Ek-71Vw4NZBX2ae;8wxwv&VV4hhLM{1;0!RugJPR6j8E9)z(vu;KzwBq|2$)Hr!2aXOoVS zGt!Sw2D3)1dbxVUk8#h7@2Z+54Z4rGa%l|wVXG>DSd3aX7Sy;o|H-^e9-L?$?y)7# z>}9Ri_WseG6ZDB3#SG2!l&i`bBJQ_KF&wL{f+AXl4>v1D(Nc*lgSW5jOO-Nrxr{6h zilfndl_RLSpuRj98KSU8=&0(W@_m8+`n`oTqj}(xaOPYKEK|HnQsBLwE+`AsBHXyL{<-#7y(!D-pRVZZuopizRmP#8iXNuD)cc_3AIkY4>_wv^ zK{BIGZ7;-8z+d_X^HS?4_k*?fGS3-rTu5@cdo`n^>X~YUi;^VsMvX_JS#nyRLZ=de z58hE^(LLwbnJOPkOcU>rX1Qd-?_Ff4y4Dc;1(oi0H^7WcQs&

管理后台

+ +
+
用户管理
+
栏目权限配置
+
数据去重总数据
+
查看生成记录
+
版本管理
+
+ + +
+
+

创建用户

+

无注册入口,仅管理员可在此创建用户。层级:超级管理员 → 管理员 → 普通号。

+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+ +
+ +

+
+ +
+

用户列表

+
+
+ + +
+ + +
+ + + + + + + + + + + + +
ID用户名角色所属管理员创建时间操作
+ +
+
+ + +
+
+

新增栏目

+
+
+ + +
+
+ + +
+ +
+

+
+
+

栏目列表

+ + + + + + + + + + + +
ID栏目名栏目标识创建时间操作
+
+
+ + +
+
+

发布新版本

+
+ + +
+
+ + +
+ +

+
+
+

版本列表

+ + + + + + + + + + +
版本号下载链接发布时间操作
+
+
+ + +
+
+

新增总数据

+
+
+ + +
+ +
+

+
+
+

总数据列表

+
+
+ + +
+ +
+ + + + + + + + + + +
ID总数据值创建时间操作
+ +
+
+ + +
+
+

筛选条件

+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+

生成记录

+ + + + + + + + + + + +
ID用户类型创建时间结果预览
+ +
+
+ + + + + + + + + + + + + diff --git a/backend/web_source/login.html b/backend/web_source/login.html new file mode 100644 index 0000000..6e2b784 --- /dev/null +++ b/backend/web_source/login.html @@ -0,0 +1,164 @@ + + + + + + 登录 - 南日AI + + + +
+ 南日AI +
+ + + + + diff --git a/frontend-vue/src/pages/brand/components/BrandConvertTab.vue b/frontend-vue/src/pages/brand/components/BrandConvertTab.vue index 85d2977..3fd8a4c 100644 --- a/frontend-vue/src/pages/brand/components/BrandConvertTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandConvertTab.vue @@ -4,142 +4,167 @@
+ +
+
转换结果
+ +
+
+
+ 已处理文件 + {{ convertSummary.total }} +
+
+ 成功结果 + {{ convertSummary.successCount }} +
+
+ 失败文件 + {{ convertSummary.failedCount }}
- -
-
转换说明
-
- +
+
+ 生成的结果压缩包列表 +
- -
+
+ 暂无转换结果,完成格式转换后会在这里展示结果压缩包 +
-
- - - {{ convertRunning ? '正在生成并上传结果,请稍候…' : '选择文件后即可执行格式转换,结果会显示在右侧供下载' }} - -
- +
    +
  • +
    + {{ item.sourceFilename || '-' }} +
    压缩包文件:{{ item.outputFilename }}
    +
    错误信息:{{ item.error }}
    +
    -
    -
    转换结果
    - -
    -
    -
    - 已处理文件 - {{ convertSummary.total }} -
    -
    - 成功结果 - {{ convertSummary.successCount }} -
    -
    - 失败文件 - {{ convertSummary.failedCount }} +
    + + {{ item.success ? '已完成' : '失败' }} + + + +
    +
  • +
- -
-
- 生成的 txt 列表 -
- -
- 暂无转换结果,完成格式转换后会在这里展示输出文件 -
- -
    -
  • -
    - {{ item.sourceFilename || '-' }} -
    输出文件:{{ item.outputFilename }}
    -
    错误信息:{{ item.error }}
    -
    - -
    - - {{ item.success ? '已完成' : '失败' }} - - - -
    -
  • -
-
- - + @@ -148,53 +173,89 @@ import { computed, onMounted, ref } from 'vue' import { ElMessage } from 'element-plus' import BrandTopBar from './BrandTopBar.vue' -import { expandBrandFolder } from '@/shared/api/brand' -import { deleteConvertHistory, getConvertHistory, getConvertTemplates, runConvert, type ConvertResultItem, type ConvertRunVo, type ConvertTemplateVo } from '@/shared/api/java-modules' +import { expandBrandFolder, getBrandTemplateXlsxUrl, getBrandTemplateZipUrl } from '@/shared/api/brand' +import type { BrandExpandFolderItem } from '@/shared/api/brand' +import { + deleteConvertHistory, + deleteConvertTemplate, + getConvertHistory, + getConvertTemplates, + importConvertTemplate, + runConvert, + setDefaultConvertTemplate, + type ConvertResultItem, + type ConvertRunVo, + type ConvertTemplateVo, +} from '@/shared/api/java-modules' import { getPywebviewApi, type UploadedJavaFile } from '@/shared/bridges/pywebview' const convertSelectedPaths = ref([]) +const convertArchiveName = ref('') const convertUploadedFiles = ref([]) const convertRunning = ref(false) const convertResultItems = ref([]) const convertSummary = ref({ total: 0, successCount: 0, failedCount: 0, items: [] }) const convertTemplates = ref([]) const convertTemplateId = ref('') +const templateUploading = ref(false) +const templateSavingDefault = ref(false) +const templateDeleting = ref(false) +const templateInputRef = ref() + const convertDisplayPaths = computed(() => convertSelectedPaths.value.slice(0, 8)) const currentConvertTemplate = computed( () => convertTemplates.value.find((item) => item.id === convertTemplateId.value) || null, ) +const canDeleteCurrentTemplate = computed( + () => Boolean(currentConvertTemplate.value && !currentConvertTemplate.value.builtIn), +) -async function uploadPathsToJava(paths: string[]) { +async function uploadPathsToJava(paths: Array) { const api = getPywebviewApi() if (!api?.upload_file_to_java) { throw new Error('当前桌面端未提供文件上传桥接能力') } + const uploaded: UploadedJavaFile[] = [] - for (const path of paths) { - const result = await api.upload_file_to_java(path) + for (const item of paths) { + const filePath = typeof item === 'string' ? item : item.absolutePath + const relativePath = typeof item === 'string' ? undefined : item.relativePath + const result = await api.upload_file_to_java(filePath, relativePath) if (!result?.success || !result.data) { - throw new Error(result?.error || result?.message || `上传失败:${path}`) + throw new Error(result?.error || result?.message || `上传失败:${filePath}`) } uploaded.push(result.data) } return uploaded } -async function loadConvertTemplates() { +async function loadConvertTemplates(preferredTemplateId?: string) { const templates = await getConvertTemplates() convertTemplates.value = templates || [] + + if (preferredTemplateId && convertTemplates.value.some((item) => item.id === preferredTemplateId)) { + convertTemplateId.value = preferredTemplateId + return + } + + const existing = convertTemplates.value.find((item) => item.id === convertTemplateId.value) + if (existing) { + convertTemplateId.value = existing.id + return + } + const defaultTemplate = convertTemplates.value.find((item) => item.isDefault) if (defaultTemplate) { convertTemplateId.value = defaultTemplate.id return } - if (convertTemplates.value.length && !convertTemplates.value.some((item) => item.id === convertTemplateId.value)) { - convertTemplateId.value = convertTemplates.value[0].id - } + + convertTemplateId.value = convertTemplates.value[0]?.id || '' } async function handleSelectedPaths(paths: string[], successMessage: string) { convertSelectedPaths.value = paths + convertArchiveName.value = '' convertUploadedFiles.value = await uploadPathsToJava(paths) ElMessage.success(successMessage) } @@ -225,10 +286,13 @@ async function selectConvertFolder() { try { const folder = await api.select_brand_folder() if (!folder) return + convertArchiveName.value = folder.split(/[/\\]/).filter(Boolean).pop() || '' const result = await expandBrandFolder(folder) - if (result.success && result.paths?.length) { - await handleSelectedPaths(result.paths, `已选择文件夹内 ${result.paths.length} 个 xlsx 文件`) + if (result.success && result.items?.length) { + convertSelectedPaths.value = result.items.map((item) => item.relativePath) + convertUploadedFiles.value = await uploadPathsToJava(result.items) + ElMessage.success(`已选择文件夹内 ${result.items.length} 个 xlsx 文件`) return } @@ -238,6 +302,116 @@ async function selectConvertFolder() { } } +function openTemplatePicker() { + templateInputRef.value?.click() +} + +async function handleTemplateFileChange(event: Event) { + const input = event.target as HTMLInputElement + const file = input.files?.[0] + input.value = '' + + if (!file) { + return + } + + try { + templateUploading.value = true + const templateContent = await file.text() + const templateName = file.name.replace(/\.[^.]+$/, '') || file.name + + const template = await importConvertTemplate({ + templateName, + templateContent, + }) + + await loadConvertTemplates(template.id) + ElMessage.success(`模板已上传:${template.templateName}`) + } catch (error) { + ElMessage.error(error instanceof Error ? error.message : '模板上传失败') + } finally { + templateUploading.value = false + } +} + +async function saveCurrentTemplateAsDefault() { + if (!currentConvertTemplate.value) { + ElMessage.warning('请先选择模板') + return + } + + try { + templateSavingDefault.value = true + await setDefaultConvertTemplate(currentConvertTemplate.value.templateCode || currentConvertTemplate.value.id) + await loadConvertTemplates(currentConvertTemplate.value.id) + ElMessage.success('默认模板已更新') + } catch (error) { + ElMessage.error(error instanceof Error ? error.message : '设置默认模板失败') + } finally { + templateSavingDefault.value = false + } +} + +async function removeCurrentTemplate() { + if (!currentConvertTemplate.value) { + ElMessage.warning('请先选择模板') + return + } + if (currentConvertTemplate.value.builtIn) { + ElMessage.warning('内置模板不允许删除') + return + } + + try { + templateDeleting.value = true + await deleteConvertTemplate(currentConvertTemplate.value.templateCode || currentConvertTemplate.value.id) + const deletedTemplateId = currentConvertTemplate.value.id + await loadConvertTemplates() + if (convertTemplateId.value === deletedTemplateId) { + convertTemplateId.value = convertTemplates.value[0]?.id || '' + } + ElMessage.success('模板已删除') + } catch (error) { + ElMessage.error(error instanceof Error ? error.message : '删除模板失败') + } finally { + templateDeleting.value = false + } +} + +async function saveTemplateFromUrl(url: string, fallbackFilename: string, bridgeMethod?: 'save_template_xlsx' | 'save_template_zip') { + const api = getPywebviewApi() + + if (bridgeMethod && api?.[bridgeMethod]) { + const result = await api[bridgeMethod]!() + if (result.success) { + ElMessage.success(`已保存:${result.path || fallbackFilename}`) + } else if (result.error && result.error !== '用户取消') { + ElMessage.error(result.error) + } + return + } + + if (api?.save_file_from_url) { + const result = await api.save_file_from_url(url, fallbackFilename) + if (result.success) { + ElMessage.success(`已保存:${result.path || fallbackFilename}`) + } else if (result.error && result.error !== '用户取消') { + ElMessage.error(result.error) + } + return + } + + window.open(url, '_blank') +} + +async function downloadTemplateXlsx() { + await saveTemplateFromUrl(getBrandTemplateXlsxUrl(), '品牌文档格式_模板.xlsx', 'save_template_xlsx') +} + +async function downloadTemplateZip() { + await saveTemplateFromUrl(getBrandTemplateZipUrl(), '模板2-以文件夹方式上传.zip', 'save_template_zip') +} + async function submitConvertRun() { if (!convertUploadedFiles.value.length) { ElMessage.warning('请先选择待转换 Excel 文件或文件夹') @@ -251,8 +425,16 @@ async function submitConvertRun() { try { convertRunning.value = true const result = await runConvert({ - files: convertUploadedFiles.value.map((item) => ({ fileKey: item.fileKey, originalFilename: item.originalFilename })), + files: convertUploadedFiles.value.map((item) => ({ + fileKey: item.fileKey, + originalFilename: item.originalFilename, + relativePath: item.relativePath, + })), templateId: convertTemplateId.value, + archiveName: + convertUploadedFiles.value.some((item) => !!item.relativePath) && convertArchiveName.value + ? convertArchiveName.value + : undefined, }) convertSummary.value = result convertResultItems.value = result.items || [] @@ -318,58 +500,490 @@ onMounted(() => { diff --git a/frontend-vue/src/pages/brand/components/BrandDedupeTab.vue b/frontend-vue/src/pages/brand/components/BrandDedupeTab.vue index 6789bcc..bbafb93 100644 --- a/frontend-vue/src/pages/brand/components/BrandDedupeTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandDedupeTab.vue @@ -4,143 +4,141 @@
-
读取首个 Excel 文件的第一行作为表头,用于动态选择需要保留的列。
-
+
+
去重结果
-
ID 保留规则
-
-
-
+ @@ -149,16 +147,18 @@ import { computed, onMounted, ref } from 'vue' import { ElMessage } from 'element-plus' import BrandTopBar from './BrandTopBar.vue' -import { expandBrandFolder } from '@/shared/api/brand' +import { expandBrandFolder, type BrandExpandFolderItem } from '@/shared/api/brand' import { deleteDedupeHistory, getDedupeHistory, getExcelInfo, runDedupe, type DedupeResultItem, type DedupeRunVo } from '@/shared/api/java-modules' import { getPywebviewApi, type UploadedJavaFile } from '@/shared/bridges/pywebview' const cleanAvailableColumns = ref([]) const cleanSelectedColumns = ref([]) const cleanSelectedPaths = ref([]) +const cleanArchiveName = ref('') const cleanUploadedFiles = ref([]) -const cleanKeepIntegerIds = ref(true) +const cleanKeepIntegerIds = ref(false) const cleanKeepUnderscoreIds = ref(true) +const cleanKeepIntegerMainIdsWhenNoSubIds = ref(true) const cleanRunning = ref(false) const cleanResultItems = ref([]) const cleanSummary = ref({ total: 0, successCount: 0, failedCount: 0, items: [] }) @@ -172,16 +172,18 @@ function clearAllCleanColumns() { cleanSelectedColumns.value = [] } -async function uploadPathsToJava(paths: string[]) { +async function uploadPathsToJava(paths: Array) { const api = getPywebviewApi() if (!api?.upload_file_to_java) { throw new Error('当前桌面端未提供文件上传桥接能力') } const uploaded: UploadedJavaFile[] = [] - for (const path of paths) { - const result = await api.upload_file_to_java(path) + for (const item of paths) { + const filePath = typeof item === 'string' ? item : item.absolutePath + const relativePath = typeof item === 'string' ? undefined : item.relativePath + const result = await api.upload_file_to_java(filePath, relativePath) if (!result?.success || !result.data) { - throw new Error(result?.error || result?.message || `上传失败:${path}`) + throw new Error(result?.error || result?.message || `上传失败:${filePath}`) } uploaded.push(result.data) } @@ -209,6 +211,7 @@ async function loadCleanHeaders(fileKey: string) { async function handleSelectedPaths(paths: string[], successMessage: string) { cleanSelectedPaths.value = paths + cleanArchiveName.value = '' cleanUploadedFiles.value = await uploadPathsToJava(paths) if (cleanUploadedFiles.value.length > 0) { await loadCleanHeaders(cleanUploadedFiles.value[0].fileKey) @@ -242,10 +245,16 @@ async function selectCleanFolder() { try { const folder = await api.select_brand_folder() if (!folder) return + cleanArchiveName.value = folder.split(/[/\\]/).filter(Boolean).pop() || '' const result = await expandBrandFolder(folder) - if (result.success && result.paths?.length) { - await handleSelectedPaths(result.paths, `已选择文件夹内 ${result.paths.length} 个 xlsx 文件`) + if (result.success && result.items?.length) { + cleanSelectedPaths.value = result.items.map((item) => item.relativePath) + cleanUploadedFiles.value = await uploadPathsToJava(result.items) + if (cleanUploadedFiles.value.length > 0) { + await loadCleanHeaders(cleanUploadedFiles.value[0].fileKey) + } + ElMessage.success(`已选择文件夹内 ${result.items.length} 个 xlsx 文件`) return } @@ -264,7 +273,7 @@ async function submitCleanRun() { ElMessage.warning('请至少选择一列保留列') return } - if (!cleanKeepIntegerIds.value && !cleanKeepUnderscoreIds.value) { + if (!cleanKeepIntegerIds.value && !cleanKeepUnderscoreIds.value && !cleanKeepIntegerMainIdsWhenNoSubIds.value) { ElMessage.warning('请至少选择一种 ID 保留规则') return } @@ -272,10 +281,15 @@ async function submitCleanRun() { try { cleanRunning.value = true const result = await runDedupe({ - files: cleanUploadedFiles.value.map((item) => ({ fileKey: item.fileKey, originalFilename: item.originalFilename })), + files: cleanUploadedFiles.value.map((item) => ({ fileKey: item.fileKey, originalFilename: item.originalFilename, relativePath: item.relativePath })), selectedColumns: cleanSelectedColumns.value, keepIntegerIds: cleanKeepIntegerIds.value, keepUnderscoreIds: cleanKeepUnderscoreIds.value, + keepIntegerMainIdsWhenNoSubIds: cleanKeepIntegerMainIdsWhenNoSubIds.value, + archiveName: + cleanUploadedFiles.value.some((item) => !!item.relativePath) && cleanArchiveName.value + ? cleanArchiveName.value + : undefined, }) cleanSummary.value = result cleanResultItems.value = result.items || [] @@ -340,65 +354,440 @@ onMounted(() => { diff --git a/frontend-vue/src/pages/brand/components/BrandSplitTab.vue b/frontend-vue/src/pages/brand/components/BrandSplitTab.vue index 48d5696..903fa2b 100644 --- a/frontend-vue/src/pages/brand/components/BrandSplitTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandSplitTab.vue @@ -12,7 +12,7 @@ -
+

|2!~hYkh>j5F9L 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®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 Optional[str]: + """ + 执行WMIC命令并返回结果 + + Args: + command: WMIC命令 + + Returns: + 命令执行结果,失败则返回None + """ + try: + result = subprocess.run( + command, + shell=True, + capture_output=True, + text=True, + timeout=10 + ) + if result.returncode == 0 and result.stdout.strip(): + return result.stdout.strip() + except (subprocess.TimeoutExpired, Exception): + pass + return None + + def _get_motherboard_serial(self) -> Optional[str]: + """获取主板序列号""" + return self._run_wmic_command("wmic baseboard get serialnumber /value") + + def _get_cpu_id(self) -> Optional[str]: + """获取CPU ID""" + return self._run_wmic_command("wmic cpu get processorid /value") + + def _get_bios_serial(self) -> Optional[str]: + """获取BIOS序列号""" + return self._run_wmic_command("wmic bios get serialnumber /value") + + def _get_disk_serial(self) -> Optional[str]: + """获取系统盘序列号""" + return self._run_wmic_command("wmic diskdrive get serialnumber /value") + + def _get_machine_guid(self) -> Optional[str]: + """获取Windows机器GUID""" + try: + result = subprocess.run( + 'reg query "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography" /v MachineGuid', + shell=True, + capture_output=True, + text=True, + timeout=10 + ) + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'MachineGuid' in line: + return line.split()[-1] + except Exception: + pass + return None + + def _extract_value(self, wmic_output: str) -> str: + """从WMIC输出中提取实际值""" + if not wmic_output: + return "" + + lines = wmic_output.split('\n') + for line in lines: + if '=' in line and not line.strip().endswith('='): + return line.split('=', 1)[1].strip() + return "" + + def _collect_hardware_info(self) -> dict: + """ + 收集硬件信息 + + Returns: + 包含各种硬件信息的字典 + """ + hardware_info = {} + + # 主板序列号 + motherboard = self._get_motherboard_serial() + hardware_info['motherboard'] = self._extract_value(motherboard) if motherboard else "" + + # CPU ID + cpu_id = self._get_cpu_id() + hardware_info['cpu'] = self._extract_value(cpu_id) if cpu_id else "" + + # BIOS序列号 + bios = self._get_bios_serial() + hardware_info['bios'] = self._extract_value(bios) if bios else "" + + # 硬盘序列号 + disk = self._get_disk_serial() + hardware_info['disk'] = self._extract_value(disk) if disk else "" + + # Windows机器GUID + machine_guid = self._get_machine_guid() + hardware_info['machine_guid'] = machine_guid if machine_guid else "" + + # 计算机名称 + hardware_info['computer_name'] = platform.node() + + # MAC地址(作为备用) + hardware_info['mac_address'] = ':'.join(['{:02x}'.format((uuid.getnode() >> elements) & 0xff) + for elements in range(0, 2*6, 2)][::-1]) + + return hardware_info + + def _generate_device_id(self, hardware_info: dict) -> str: + """ + 基于硬件信息生成设备ID + + Args: + hardware_info: 硬件信息字典 + + Returns: + 32位十六进制设备ID + """ + # 过滤掉空值,并按键排序确保一致性 + filtered_info = {k: v for k, v in hardware_info.items() if v and v.strip()} + + # 如果没有任何硬件信息,使用MAC地址作为后备方案 + if not filtered_info: + filtered_info = {'mac_address': hardware_info.get('mac_address', str(uuid.getnode()))} + + # 将所有信息连接成字符串 + info_string = '|'.join(f"{k}:{v}" for k, v in sorted(filtered_info.items())) + + # 使用SHA256生成哈希值 + hash_object = hashlib.sha256(info_string.encode('utf-8')) + device_id = hash_object.hexdigest() + + return device_id + + def _load_cached_device_id(self) -> Optional[str]: + """从缓存文件加载设备ID""" + try: + if os.path.exists(self.cache_file): + with open(self.cache_file, 'r', encoding='utf-8') as f: + cached_id = f.read().strip() + if len(cached_id) == 64: # SHA256哈希长度 + return cached_id + except Exception: + pass + return None + + def _save_device_id_to_cache(self, device_id: str) -> None: + """将设备ID保存到缓存文件""" + try: + with open(self.cache_file, 'w', encoding='utf-8') as f: + f.write(device_id) + except Exception: + pass + + def get_device_id(self) -> str: + """ + 获取设备唯一ID + + Returns: + 64字符的十六进制设备ID + """ + # 如果启用缓存,先尝试从缓存加载 + if self.use_cache: + cached_id = self._load_cached_device_id() + if cached_id: + return cached_id + + # 收集硬件信息 + hardware_info = self._collect_hardware_info() + + # 生成设备ID + device_id = self._generate_device_id(hardware_info) + + # 保存到缓存 + if self.use_cache: + self._save_device_id_to_cache(device_id) + + return device_id + + def get_device_id_short(self, length: int = 16) -> str: + """ + 获取短版本的设备ID + + Args: + length: 返回ID的长度 + + Returns: + 指定长度的设备ID + """ + full_id = self.get_device_id() + return full_id[:length] + + def get_hardware_info(self) -> dict: + """ + 获取硬件信息(用于调试) + + Returns: + 硬件信息字典 + """ + return self._collect_hardware_info() + + +# 使用示例 +def main(): + """使用示例""" + # 创建设备ID生成器实例 + device_generator = DeviceIDGenerator() + + # 获取完整设备ID(64字符) + device_id = device_generator.get_device_id() + print(f"完整设备ID: {device_id}") + + # 获取短版本设备ID(16字符) + short_id = device_generator.get_device_id_short(16) + print(f"短设备ID: {short_id}") + + # 查看硬件信息(调试用) + hardware_info = device_generator.get_hardware_info() + print("\n硬件信息:") + for key, value in hardware_info.items(): + print(f" {key}: {value}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/backend/utils/__init__.py b/backend/utils/__init__.py new file mode 100644 index 0000000..ea41342 --- /dev/null +++ b/backend/utils/__init__.py @@ -0,0 +1 @@ +# Utils 包:数据库、认证装饰器、模板渲染等 diff --git a/backend/utils/__pycache__/__init__.cpython-311.pyc b/backend/utils/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bebca675bfca2a267976bc9db00c135f4a1ece17 GIT binary patch literal 154 zcmZ3^%ge<81fN^hW!eJi#~=<2FhUuh*?^4c3@Hr344RC7D;bKIfc(!O$*)>K!o@1a z(evfPozG{li+MVs?`i*r=j#?epU@UlT9TPl91|a(nU`4-AFo$X`HRCQH$SB`C)KWq Z6{rzpelb6g_`uA_$oPQ)Miemv#Q=ppD02V+ literal 0 HcmV?d00001 diff --git a/backend/utils/__pycache__/__init__.cpython-312.pyc b/backend/utils/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac66fbc4fefa2c2f0049a351d426018e5b41e0f2 GIT binary patch literal 148 zcmX@j%ge<81pZ80Gi`zNV-N=&d}aZPOlPQM&}8&m$xy@utQ(BUlQydc?pP83g5+AQuPg`k0)M8hnYKXsF^Gc<7=auIATDMB5-AM944RC7D;bJF!U*D*yo*(gqi0M~ sVsdtBUP?@9NoG!QOniK1US>&ryk0@&Ee@O9{FKt1R6CHy&p^xo0Cl(+r2qf` literal 0 HcmV?d00001 diff --git a/backend/utils/__pycache__/auth.cpython-311.pyc b/backend/utils/__pycache__/auth.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b0f64994ae3d8e752b3fc0b87abb2db08dab640 GIT binary patch literal 5806 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 U^EoQ}alV;KAIZp|(p7E$0R^%%WdHyG literal 0 HcmV?d00001 diff --git a/backend/utils/__pycache__/auth.cpython-39.pyc b/backend/utils/__pycache__/auth.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f652ac1861c040f9c017ea8c329b8de8b49c4e59 GIT binary patch literal 2788 zcmb`I-)|IE6vywKnVp^8wo9!AC7>=K)kUaaHGA+Nd8xbi9KVqU(DdF8eVP0kXS5erXMGX1{P!nyc=1Fh2h=A8dxK|Bdn>L#3JrGSRy=k=la~; zV{_l1nE&BkdE(~dsnbzm31R1R(V1dt{?@nQc^F0}sTVlHQv)Iv@Pgd>)U|};8Qfwv zw@k+ix;&Fi=eZYLoa_Y;i+s*b55}mc_gPMo^_u9X@@P;n;J*X@VjPS^ij>h39VSCW zv@oTc6quuokYPGRi)x9SX&Y9Cl%mGeA(Eyghz8UGB{Hn;Crs0bMn+Uha?P8Gh<6Yw ze+R+BY`uhURd84%h-H`o{=o*iVV+|Qp7Y07`VY$f4)6Jxp;5p)SdEg=V8F(2dB!n$Dwgy`tswc#d3LcVrJ^Z{KWOD ztMaeE%sskLzI-j(mC6VULg_p0Dy;49+|{{bUlbPT+Pi1BTunsx@9f;$iEZyj*NgbR zScnHQUrppDU!a8c1u48#d+H%Z8`JCki%uF7ZfR~lxUa0e^X$?4#>X_!K zuFX8n%;$2R3JyG#=Z{Phz5+ZxS@pr&J@0+k)0;B;tsLud9Xlg>Qm%8TePGaAZj0n% z(vx!QpQSQ3L&`-I3{k>VgP%fm8m1B2Mh&PLP}6B6y!neTrVb4i8meeqhf3iahX4ow zMv(#t8zd46qmIgyl@%ChQ3DthNUB-~!}dBD2qJ^%@J|3AooOX1(mn{Xu9xV*aajtM z;IO%bf60uT!a_qz8hlG!f`iw5TmTaQ2ry9rld$_SY<2GR{qmXb$`8+%PoA2ccu;PXy_r) zROcblP_subAeV|6k}=*0K6n!vgpWtiEJFjDM)-4JVk&^8%E-6{re8 z8n<$e{~bx(#LLhpR)Zmmj|`$?{Ho;ypFcY#8+_QE;-Q`BHoPhBepr}VGUXoHSCW4SG(~HQ5L;%gl4cXETfz@{&@TX)|qsa2M9d6_cp@p!ckO zYbI}kSZ!;s&f*xyNrk_T3&zmMbialrneIrviw@`zxG@4Ljp+OhDE&VPMW?uZnb7wk zXqB5qyyYl|)tXdpbhLk^GT`WyR2D8cL69B1bqpyx>X2Ftv5*7za8-8HVi;sc0|^2A zYVM$;b+V%r8>H-Le0&ID3WMzYzGOGkayJc&U^V)3Jt_4sM}j zI6z5ZSG|`p%3IKn(=-$SJbg$b=u0OcS1{*ukgQs-we5a?2T0lyjFo~=YHVUmm5Fn{HXxf{o-k~ed8WPW1IJ`c$&EX$_sT)PyX z1w4>HCl}pqv0Qb1;PUW literal 0 HcmV?d00001 diff --git a/backend/utils/__pycache__/db.cpython-311.pyc b/backend/utils/__pycache__/db.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2115e3924e0c35fb3b256e8d2232f546983a25fc GIT binary patch literal 7457 zcmb^$TWk|o_KrP%+rc4rUc9DInz&6N2~Y|xuqJjlE{OxS(~|Hoj%SjX`jO5|nun4$ zs@hecX`w7_XiJwMwUjPh7VSsHmR7rMvMT#$Oa8D%id1P69$z&om9qWXbI11hfdfIy zc;?Kx=bm%!y=U${_nhl53%VVZ4j9W2`VTH-E=3yoWjBG)B%)C#q6u0SBsvN4 zE$fuQTOO2$$WD?#8eB^T6(MD(GNkHMh18wukfu{Zpzy#t#AqK!Y=l4zaa5OSU`n6C za^JLqR$h=HbP1l+)u~*9l!{gtNU4?}rJ=P2QtBm0>1cg{l%@lP$pJJd??3|tZFmcH z8t4*$CG;|Y%V?P!S&aRwU_9CzA6&Wd?&bM!{&?#jf4cGB=W|!zn;-vl{)>0!CqC5P z#shX*2%)@(2{WvZXS{trjvI)uw71vC^;$?l8yezX4SIVc9G}7aIffNVQuGXarcC>I zUzd+#1Z``FdtZyUxy|hnG{K197fh*0k=xyNmq4V<~l0jJei>(#4B@fES3iB=U<{zy2?_<0uhoy7}-#jUZp3s5Nvae~$s@bgV#;Y;)j zRH0uV2VmQN(CWB-{_i(GxqNW`kwV`iFQ1&pqm+oTM zfIulcTqA%nG^<-av1`^)f$!2aa3_pav!)fN+v27TNrOtSyn_JT#UP1P3MGrDSl&0m z{S?2+OR%0MVU^~7l?zaL37*t7ibl|s^qn$P{Lbgwf<}oEVoF+{xtug{0W@5av=9h5 zar^gN`8wIP(00zu$g=12iGD^#!!LgY5(uxc+X+BdEI>-rpMuGlIe&jDQoDML4JpzZPI4VL=WqgT+e= z1S6qPfEP6OK|j;S2O?pMLLjlv66By$(267UivCZN!utkjK@C9cGlxAIg)eqB)@Szm zSq5)DfpCBi_=2hNu$!T7B|KaRmiHJ+lBEj6>@xGQ!HL0fGGQn^ap3rYV}~XVC6P{3 za>k#~FFUd4_?~FxzL?$>*Sn&6S7O=n6C=k*j=etddJ@SD6$zthQhQ4KzW%iSq&{IR zPijC?6c@oAtmrNVS(=Fbszuc+&sLvX{qgFH`YS!Ls-5wwo#QXgR#uV0M9r% zPA26*1+APF%>L$DTQ&M@SJQg=_v>_keqV33Kw-K{4~6OVWRpcTZINTVj==cSPXRtt zt+mz4XKDz5#rK6d@VfucUQH9Q|ALN7lJ_2kT?RZ%CVqsjXRmxul-@~{+4?L-%lVRQ z=@UegpUR8uEyZOO1+vPAkX0?Y6?GvejGLG$-Xl#RZ`cT))tDV2cvd;~tl2p7KH9X8 zRGaRRYSTZwwneRGDAaWyo0JstE~<4|5zZy-o1?N38QV1?o63n!v)_+4=4)kJM`X0I zN@5t5FWR5d5qUR}AB%`_Q}LO!{GqtZig7=9?$IXvE9}#K;tnaRlLidP1$eUYr5K+S z&G`!X!d2_A2U!wtCJNzKWLqxON1GrgF!w(V98$8C&BM-Lgm?p@m$|F&&j01i{Mn0A zxWeLi&O)-w;l)CJj945BvE=}U*EG58R*#*^q(zkD1?lZTC>=lw?=|@d$O~H{yuzRoF_k;?|o3(wZUNYOxonO+`Mn_lRI=h>jpY zp{(tmHm}18N?Pqu-0O0*T3sE~9(zY!QL#)Q1A&SQceM7I%@%Pa&i0lTs@>_>*KRK^ znHTs-;y84Y*KM-?H)%fm_B3Pc9#o=>cvF2 z$J)C0o^e|8C_W41E_oDezWZ1`5c2gf-rfMmN7y0ikz3!J{&INwV52_*3lvw;=L<7I zFF(}BNM8$o?2H|2YKn_R*g#Jp3~^vT8|0`L-EGd|e0@IF7rGw<%W(Zc{y}Pj5ja{% znH(CFdfHImnA6wA?c{LU?E7Ic42l77I+Ud;F<=Kv*HO7s$?k5-pBs24iX$Q1!T8DyL;E!<*8HGUU9q0UE^IRH#_eEJ^O58U#783~ySrlX;1CD(423J0O=G z-nFQZm}Sv4 zYB0d3LhJ?%YB8w807nR{1p}Py!p^@D(EjOrWHwMnF|GTuRIL+M=%pa@_&G8l0}oD*_|m$qI%UC z=IoOvn-Zpq$vvm`M5*@Z{sYmM55-J}gd^CT(~O_;Pcs(A z$=t&YP%=wa#mE(Laz!+Ab91mtzBD;B=sRmovswPbdh_n*WIt>tF#epjNv)Vx$N-;K z5g1p?F|Ic^t(8qbhIyvfYMVAHrt3tW4FtwFiaeXlHjQjXjd^A?T3fkdrVR7Umvr*)!*V=X{X10qw&l4EmQ3*W%D&M{K_uFJYK8u-t+@@_-Dz3{h({%-b zaV1uCU89EqTsM==RjTV%a*VGeFutk+@PBK{9c$zkqoDG7>4@L!WuJsGi(W}69bnj3 zhME4J4ICWvu>pQaI>p2LWue0o|6|ZOLVXdIx3g@7WuL;TM0bV%Ef77G=zLgwI4AxC zAbO_X)X#?^bbpZ9$?k*-*uW820T320K_q1|f`l_T07^t?$29ntKqX`1FM&2k^Y0|7 zBh7AUP!2x!;A-kT$m^=J6%1w`qb(v{l!;erQ74B+s9taC3eS3x5P`g0MT4= zx^wdIsl(C7H(%+BnYYEw+s2%E)KJW9i<@m@PW(%1a9eo7mZ%B%BSZq_Wdt0mNuaKo bAi$Ruha5hOIi+8N&RH9Fm&t!vP5}HLywb6K literal 0 HcmV?d00001 diff --git a/backend/utils/__pycache__/db.cpython-312.pyc b/backend/utils/__pycache__/db.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fef6ce0d54d5ef9dcd840480c87e2249f55693d9 GIT binary patch literal 7467 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 W)geP#dj!54mC^uF zkm0bQ6qYoWv`YCuJxY65rU0^O0Rpf@ex3en<~B>5Z1FLY;Uec2B&HA8jgfW|XUQ$j z=dS&dTl*n*{p+U>uH-JSo}lb47fkZd!x6~8^N?8QfJW334VV3rOvzR$ zwTgq4U=%EnhAoj_rxSDW2&w)X6y$cZUpER zYIS?LqQ=UzvYR6cS=(N@pPio7;iTh@AZ`2en~&Fi){&N*{p|7md)fFm*-zs--kv^~ z%#MGdL+;VoC%LP)i=Oe>A|P+v-Tv`rZth-g@=`7_MKY9~v1LRa6jddHyfS9c;8{f5 zV~3cRWG}i9);=;sG~cTZ%p;i~*DI4Hql}P(RJ#>3ii)|gM;pT2u;LG4Q_rcsUeR^t zeQflrK7T+)6l9oF!g2_6NcKor7}A0Pv5;CpX1Z!5J1LTk&Ap>uIjs4W5Td}d^N2nS znD;6nO&-+{#S(@-%p%W-9;q*dG?hZAL8rf8A-e00fm3Im%>T6Y?eYM*joIndt>yWx znel;$<`1X?`rT^_U%*1~8q%UrJ&B~SP}|9M8UtCuI@vkhHPLm=lx$tKriJc|&^_kb zESS3|x)V)l;b=xUng`HuaQKX9+w+ruG|u6dLWEa30EK%cCIUYW literal 0 HcmV?d00001 diff --git a/backend/utils/__pycache__/render.cpython-312.pyc b/backend/utils/__pycache__/render.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..71c8de0fd3fd4921f51e213811cfe9b8525cb4e6 GIT binary patch literal 1608 zcmbtUZ)jUp6uG5l*qZCj=cMoL5Y@(MY?o@R{-H z_r}WC#@xmGH$O2xUN*+wDV?7$EuU+A>8;mZZQT6gb843+$ITe~9}NGbmm_YBk_y7I zjuNSONJpgp5r?elIGP+Fu4;sw3?)#wQee4Ek!lD1w`Zw$8t7#wQ!%b)){K~?X4VXy zA(Tad8L&hf$0W=B2a?T<+pRNP^~f}*&T1-421H;KWZ)$@4o?BS*2WB{a7&dM+d-Dk z@UcDGu~b3H3JTA@Y6b*0pa_a3ZcW&-76>?yv78mE`^V}kX}bnRtgdE7y`j305qDSD z1dag>4sj6l0!Y8HXNLJ+Fa(=`USG{<2Dl!Av%=ncDAHAHwZ^R23&xHQ0TaV(ORE%P zbNNPTV#*|j$sEd=%?p<|R=zViV@!RpapQU^H&c2)XENvh&9TzC4^8si8-3T9y;6zH zO;xCM>8s6eE*sO=jj_B@7^eueU}7Ia{j#Q}aTpN}_n*Y<$i0wA3JT+ql>W9Qtw)ad ziG&g5U>MOQ5n-%i&FdnZsu3X-(g%qUQ=>^@?Rw!7v zMj?eb2K7W-E<>xBk%WkJCsjej)-xPNDLtwtvA}AOFk>T;!fH}S!#ZZnATUCB92zn+ z&84KSvDc%~l!wPoJlB`PY7B+-zAz1?MI1@R(*x0DUwYv>uIXedb*{ni?UF z%C%yp;r$egy)W9ARE>fShik%@_f2%t7yyj?MI8D{6(Eh+pQ&w#afA83Vv+gedE&jWny6KjWS_-EZ zdM8_o9^ZLsi>K~~+Cf_JtpksDHS)K72fA9p?M6$NSGwK$#ETC82Ro$N>m-saD{5Gl zv6Ds?Uc8MWpsYrB6aYrTQc)Vx!^rCz9*V2hUri=w62g8yS)+br>^?>;$`tx}xfoZYkEIcLv&^I2_eNg&rv^xZzM z0kExt{mYQSC29Uu=?F*z0ts?p8p@~UprV%6ia3oSps*0D>ifJw<29&uIzf{JFGCU| z`hA>kA!rb!jPe=TFd2TRg>Qa_8(+h>&vv(7htJo-xx4kp)%w~@*Oj3g*Si|;-^us- z(}gG}`Ab%TffN!478i4_*X%*Crae!*jKfz-G$U_?ha0M>FLBRC@5j2e!J1bhCRgD^`;9)>C z3^171AaL7H1AnL+z~&y2e(D&(B<946iAoHDAQs?hSWObW1}pk7aJw+zEtAVpet-)S zAqI)hql!KPT+|JCYoPC+AeAV4a_Gj|W_@ufA}nH5a@lyYy0h^k;xb%%xU;!lpM6z- zFdH$tyERvznU6@_zH={J{E0yAonbjv6&I%c!A$8?42@=qS2BGEUIqa|*8E|cn&vSL`l zM$U6No#0G~Tfn@~m^B_%Mv%)t0@fkH-$#4TWqSHESt~at1(hjt+wn7zlunf + + + + + 管理后台 - 南日AI + + + + +