From 72ab0c0b56949c5e9b9a3c2a0e97e2625096cefa Mon Sep 17 00:00:00 2001 From: guoyoudu Date: Wed, 18 Feb 2026 09:36:18 +0800 Subject: [PATCH] good version for web --- backend/algorithm_showcase.db | 0 backend/app/__pycache__/main.cpython-39.pyc | Bin 2100 -> 2278 bytes .../__pycache__/settings.cpython-39.pyc | Bin 1678 -> 2862 bytes backend/app/dependencies.py | 20 +- .../app/models/__pycache__/api.cpython-39.pyc | Bin 0 -> 2557 bytes .../models/__pycache__/models.cpython-39.pyc | Bin 5795 -> 6459 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 639 -> 727 bytes .../__pycache__/algorithm.cpython-312.pyc | Bin 14383 -> 14856 bytes .../__pycache__/algorithm.cpython-39.pyc | Bin 8238 -> 8828 bytes .../data_management.cpython-312.pyc | Bin 13075 -> 13053 bytes .../__pycache__/services.cpython-312.pyc | Bin 41919 -> 41972 bytes .../routes/__pycache__/user.cpython-39.pyc | Bin 6465 -> 6669 bytes backend/app/routes/algorithm.py | 15 + backend/app/routes/data_management.py | 35 +- backend/app/routes/services.py | 2 +- .../__pycache__/algorithm.cpython-39.pyc | Bin 5172 -> 5317 bytes .../__pycache__/algorithm.cpython-312.pyc | Bin 18944 -> 21333 bytes .../__pycache__/algorithm.cpython-39.pyc | Bin 10591 -> 11045 bytes .../service_orchestrator.cpython-312.pyc | Bin 41357 -> 46940 bytes backend/app/services/algorithm.py | 86 +- backend/app/services/data_manager.py | 2 +- backend/app/services/service_orchestrator.py | 187 ++- .../utils/__pycache__/file.cpython-312.pyc | Bin 8586 -> 9380 bytes backend/app/utils/file.py | 21 + backend/backend.log | 2 - backend/check_admin.py | 79 - backend/check_algorithms.py | 38 - backend/check_user_role.py | 57 - backend/check_users.py | 54 - backend/create_sample_algorithms.py | 151 -- backend/database.db | 0 backend/migrate_add_algorithm_fields.py | 45 - backend/migrate_add_service_fields.py | 45 - backend/test_all_apis.py | 48 - backend/test_api.py | 53 - backend/test_frontend_proxy.py | 32 - backend/test_full_login.py | 48 - backend/test_login.py | 45 - backend/test_login_api.py | 53 - backend/test_system.py | 232 --- backend/update_db.py | 127 -- frontend/src/views/AlgorithmsView.vue | 1343 ++++++++++++----- 42 files changed, 1305 insertions(+), 1515 deletions(-) delete mode 100644 backend/algorithm_showcase.db create mode 100644 backend/app/models/__pycache__/api.cpython-39.pyc delete mode 100644 backend/backend.log delete mode 100644 backend/check_admin.py delete mode 100644 backend/check_algorithms.py delete mode 100644 backend/check_user_role.py delete mode 100644 backend/check_users.py delete mode 100644 backend/create_sample_algorithms.py delete mode 100644 backend/database.db delete mode 100644 backend/migrate_add_algorithm_fields.py delete mode 100644 backend/migrate_add_service_fields.py delete mode 100644 backend/test_all_apis.py delete mode 100644 backend/test_api.py delete mode 100644 backend/test_frontend_proxy.py delete mode 100644 backend/test_full_login.py delete mode 100644 backend/test_login.py delete mode 100644 backend/test_login_api.py delete mode 100644 backend/test_system.py delete mode 100644 backend/update_db.py diff --git a/backend/algorithm_showcase.db b/backend/algorithm_showcase.db deleted file mode 100644 index e69de29..0000000 diff --git a/backend/app/__pycache__/main.cpython-39.pyc b/backend/app/__pycache__/main.cpython-39.pyc index 6ede376a87f563f15955db784998a96655d2b692..03bda58107faa51e606adba0a0e3edf0c3603a00 100644 GIT binary patch delta 891 zcmZXSO^?!06oz}-(n9S3<@=+I$P}cAXw=OlW@6OMxG*M~Xc`kzxmRfI1=2FJ#YDFx zl5SiX<~QhHaOcuh_qy>nn0Q*|!%2H`-=03_^xXF*_gnV2k~VjCBnBS(d2M}{)@hX; zul#jB z317i7-dPDo3kIc~J*pWR43&vCY)9H5E&Dp*pudYVI6FqsQdS9=)UtJwv3pRvftR@z zdy2BB=z=IQl8cdij1*#|7$c=KvOM3ue1^7Uqmzo=I|)x7`mJ8B0;$DFJx0_RX-l|1y(9?3vxrCRU$^6b;a}d}wnTmB(uORMC&Y9Xli_GWM(-X0@mLUs zbGv>w@#8@ca#s)KVnXzv;t}_l43Dh^8IBwa93DcK$Ad7OjNCrFpX#^$d%`Dmdtx`V z<)%Ok$(+vz?(AqmJpZR~r{osGQZbZLDCJNpJ|}8u&+m#&wCf*=Zyt6++zln1{-2}B pTVyMFA2vI3$N_>5?=^^wAXph?As+jG!~+H8!r+ofO2JAb(LYSU%klsK delta 745 zcmZXR&ui2`6vs13c9ZO8(`?)9cH6kkHoMjpso)P#km|*&2o{taEU`0oH*U-_lO8-( zs0S|*mpOQC{{TVIi~oQp!F%xRKj2Led=o|8W`_68ywCeFZ^*ai2ipo8jhY1O`uOEI zGiH`6e?_wkca_)*W=DfVB#~OQ5_O1ywG4rWh!(AatzjJ-xQ6T4%&LKlCES>tiMv4; zNw`kByX%E|#Ep7Hf&2|@;e~xvn4MLilNB*;RE*d@{| zk;M{ODv{+=;;t9e_c=?5Ngn~`t0&#$}aJwMq3bF#E zgI5bO#B2AYq01l250J?}$NBTJN&!$=vGe@`a(q4|1K^@@POs|jX1#v6S9rK aV>=)wf-RfKM%VZoeY>vcsAhmwbo2}Pfv73~ diff --git a/backend/app/config/__pycache__/settings.cpython-39.pyc b/backend/app/config/__pycache__/settings.cpython-39.pyc index 0ca36294bf15c29c191f31fec661cb8c5b6cda28..52c667baaf22b9d9c48a52f80f83d90799b57d06 100644 GIT binary patch delta 1646 zcmZuxU2Gdg5Z=9iUz{{;XrNHF*OoxsGox69A z8taUa+^RhEM^&Jzpi~J$;w^xXKx%_{&z^1Bl=M{?2YZL59cyGEDNfW!s{As7m*iq#cNAq;L%GGeQoE`y6aa_CE3w zNaZ`+4%5xZBYme#ca5*k)XdDP`kII>98nD9O5&3`=+u3%Ki+n`L^QZ>37~3pL9-*O^uzLJT*D> zHbgl;s@&0r1i^;p|4;@SFGGx)00S%H$A$$doS7XV{bnCJHWxj24^D+&~;yBN-GsUoo6FR5U5^qMH8;E^(v0a+utgPJ6mB96A zgX=R?m`E7!b1RiR)OZl**N?En!il~02}xUl9RcU+`6^{|cBbS931!ao99K&|ixWHB z$>OXVR4G@zAc`r^OkVJ4YYK3+!u(JSfjDWfoXM4#2BlU!tN{LW8qPYR^6d-|EhDXq zw7p7Xnrm$b98=crC2@=$fIFEYoK71a7yEd>0Mylsvsg5kEQ$ZDq+to0;*%bmfudkd zG9GKvGq^_@lxuzKLo2^&D2JzV18hJTVlM!^D1hsZLn6;{m;tO;fwjeP+%SwpI>ktf zg(w3e=uWOoM^P{zs=FIofwv8y4`4e;Wswcm4_jz$_B6l_5Ga8kC+u0+?*!Ndup7Vv z*aNT+KmmYx*%}~h0;tW*v?xdq)8R@mZsO7TKh;BhipT<%{_59yRHh5|RL-0q*7m>K zvSs(g=L0wJ&eHN+K4N7C2U}{~h&!|W|GaSHm?)EKK#xOB$B>{behO#h-Vgo;#M{WQ delta 448 zcmZvYK~BOz6oxzPv=jt1N-zWzaDi0NC@$O>W8Anf(FMD1n#wfTq_k#QPk=5FaGHL7|GasB{^@h|zEpNgrGi51?BmXV)Mw=e8_aq&n<=M? z&;-9#1ozlfv$@BoimeNMs9453CM=c}Pu$oWdP(Ak>4=msMk_`5U`$MnWK zFKtfOP=zmW1ZpMUt@ WY-S!#B5@OVM+^qIN0Ak^rTtHL3|UM7 diff --git a/backend/app/dependencies.py b/backend/app/dependencies.py index be8cfbb..511d1e3 100644 --- a/backend/app/dependencies.py +++ b/backend/app/dependencies.py @@ -5,13 +5,14 @@ from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from sqlalchemy.orm import Session +from typing import Optional from app.models.database import get_db from app.services.user import UserService from app.schemas.user import UserResponse # OAuth2密码Bearer -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/users/login") +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/users/login", auto_error=False) async def get_current_active_user(db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)): @@ -25,4 +26,19 @@ async def get_current_active_user(db: Session = Depends(get_db), token: str = De ) if hasattr(user, 'status') and user.status != "active": raise HTTPException(status_code=400, detail="Inactive user") - return user \ No newline at end of file + return user + + +async def get_current_active_user_optional(db: Session = Depends(get_db), token: Optional[str] = Depends(oauth2_scheme)): + """获取当前活跃用户(可选,未登录返回None)""" + if not token: + return None + try: + user = UserService.get_current_user(db, token) + if not user: + return None + if hasattr(user, 'status') and user.status != "active": + return None + return user + except: + return None \ No newline at end of file diff --git a/backend/app/models/__pycache__/api.cpython-39.pyc b/backend/app/models/__pycache__/api.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0376dad5ca03074fd8b2af36ca7ec273551a542f GIT binary patch literal 2557 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 Nv{un}S~dJ;{|DjCs}KMH delta 2237 zcmb7FO>Y}j6wR~89>=lAXJ*=&yLWvR>zQ<4V<;u+0KbV`-Lv4(+8sw}a>4>IVz}G{7|AMZhqDt6sCX zW7lg%BhOF3dlK*xU<64>%3**c@1|z#1gT ziK04AT<#_Y#BKF6$)~Uzbi>V>eYI)Hm~q69zY3TI9OmLG7afbc3a?iz$);$e+P?oO z>=y=%0V1yfV*npIZm3BoIlf%Z(V8joTO`NMNiHLuj}{B$YhtrsKY6hK;aB?)?)|cJ zlfOxh6%O4B{0%}W)b$9i5A{6ppg8Xl-)rf4sf~h@J*{w3(dcfAqHv-kORkE&S83}B z>>2_5A4N~B3z*`k7#J;x$I&5HkSs^L{$|5sGdmZN>{1n;hcSr6fH%G z1qE)k$t$kNCq`$Vt)kyGm*|@BiT4u&Y-#UqVv0@To*e4PY%4tVZY{tG0puvQiPwy=0{$+aj}_vVe#3VTD8QF-P^S5ruZ>=LhK}ti^0^F zUJ)^ZUhz1OLyuGtj};yq)g{?Bqa!??VjxS&k&N=Y_$9i)Hr0$wSYzFcU&1TJ_NmdF z6yK27(fo-caub(nFQmpkm$$gPpP&rDED)5X`B1uwW>a%pxp%#aO2tv4xvXO-5FD=` wN0xFZ?(Mn)%O@$^$b7YKHY?WajAl4}1Y@{bAQsepF diff --git a/backend/app/routes/__pycache__/__init__.cpython-39.pyc b/backend/app/routes/__pycache__/__init__.cpython-39.pyc index 053409a4535d2b32b5d2fd53947130aa86e59153..811a7c8a5323d8ed1b9cf14356490b8f6d5522c6 100644 GIT binary patch delta 361 zcmey*a-Eegk(ZZ?0SMmpOw0^np2#P|_+g^9VSO%F6jv^H6gMM7Dq}N46i+Ho7H=wZ z7GDZ;3QIa;3TrPjPzEH%0u*CQVMh^T1&VQ`aH5E@0mZmdxKYH|fnq!Ha31-j~eF^fICgUwm#{kcu{L+%tB2CW8 z9E{Ggx7d>N^U^ZYZ*e8(=N2RuWfte>Rk7)p7N-^!-TXGJcIH}OHzw8*(aMYI&)UB>6aF#78OTLp2Dci w7&UnZV<0;>P_3UP?_?<^+sTPc>TCi)ev!cBJ|=xJAs~~7QH6<(S%eV*0V-=bCIA2c diff --git a/backend/app/routes/__pycache__/algorithm.cpython-312.pyc b/backend/app/routes/__pycache__/algorithm.cpython-312.pyc index b00de4fe5d16374dba83c9f7ee1fc311d3582448..4b671e15baeeb7159dee4e0f6275da6a23a2bf7d 100644 GIT binary patch delta 1015 zcmaJ=Nla5w6#ef{m7&Elq{vXff}~7^GS3APj3FdWapA&>{a>gll)Sbm$WT-gOn?Xv z117Q%i6JtYkBK35t0uaDkf>oJ3x<*yDZ)aHiN4pWDJ;CnzyIBP&pr3tm%O>=Uc2J0 zTpmba8|WN12j&kdB0=x736s(g2HyO0LAkv)X1fwVom{c zXp+-_Omvh>jm{EN1*Mqm?coKzUr!Ob3{)T+z2i=T9OUFRVYySl$465lC*%qF(*;Nk zl9Y^4cz~q{ea7fwKeP;^OMK{9Mkp0|znNuzGfRoiu<}El2482w;m!)b&azfkTPZcj zQdAMaI#Zp+qHA_sv6xM(ED20B0wLiF1D0Py0cwO2KnIPM{gZksz!%RghsQl%0toS zu-I)ZuE%48Is%L!@{M(tdMh;B8ybzit>86W^We{xf}NZC=A=9d>R93u8?nz*isK*T z*~{|m`O2k&Q*UxsI6LxaW)u87ZUaXu%H@ECD)2Gang{+ z0CI2QBmD2YVDW}%a@a+q$tsAj6XW%gEUAnIU8q`n28^LmZ6Ws*rw!73Z4wLm(9iVJ z@P5KOfL)uc#Vm+cn+{q~ZH6A)L(YtffFV+Tj2)WMs)M`;xPU{uw3jgmKsoBkJkKKn zdVoG!X5R<0E-2woqO=tN+5}Tv|#icSf%O v!!A#SMU9?7x?L^^gnkT+^)UFqb|mC0iT48-N>$DBo%~TA|r9jMw0uG%`44ar`c$!CD7ZP8TQK~yU; zr=Y5~M{=2MC5xuR<}K`6;vB5{$%#2Rw^$}SD|86o5=^Z~O)f1-jZe%;&o9a>$;h32 zK|z$`7F$tjacNG;_{N?#Zm(m~^%*h7YmaNNw!iJmkv{hLcJ0?%jw-xCGY3TwIRqSP%DXICz`bFuJpXwVi_Dq&A zaAfNPNzb1gZ=lY)0mw|<+-)$Ckfh;WAF!_|R6!&2u6BKF1*H|~dH-5*wS<>P)Bg-M62mnLnl)?Z2 diff --git a/backend/app/routes/__pycache__/algorithm.cpython-39.pyc b/backend/app/routes/__pycache__/algorithm.cpython-39.pyc index 92569ea527ac3545ae39783c7125b0667018ef74..6f3a1c7d702fc5bd690adb6f3de794f159a768c8 100644 GIT binary patch delta 2380 zcmZ`(ZA_C_6z+ZB(w5Sej|#M<6$C|#*zye(kq;HcZ@{VPl68gq0>whR+ZWUZbwrna zWSY$D=6-;|#h9@z&c!V;hMAZxi$9!y_G_IkTcU``67{b!Zs*(CSkwGmfQw_w%?W|`u6g@u`|hDhafjo{S+&r5;F z&q?`qyRs7Wo{2Q-r4>S72l`4{#wR3~uM#q;qSZoP5Asr4qc~skUjWv%u_>tUK`yY8Kz%ghVy!YTm#mSDI)!b0l6P(VE#Ri-881aTrwji|x!G{2!QAk+M=-t9@`_bX~( zfgKABM*^{MFg8#E{FoL%W5^-Q2k)oE%bWH4>RAKW| zWOSrwW$q!zy9vWlB(00;39xg(Ac5w4`nm51HJqc3=#{6SDw_BxMsZ z$vPQ!+Rj@HE`H0jJ%1q>TL~=ikc`8Cqmnx9}J9vBMAIBCG+KPtT z5Vj-B@Y-Tv9oP;8jF_;DMCpqa(Z7($E@TxWBvXQwKQqav^I0)Im6ch!8&sFziRA%k zjBrk(bmFXJ{IW<4ISc#be`VdR`M=ELw)S8stk5DgQbd)oqAEqH8n8D0B)fXQIMr7- zl4Q>3<<`Bhk(c+tHbN$^wtgqbZu7C6l4ZDxo4mcjaM&9i=?RB=-BLn}pP~%C62G2f zJBAyjlg2dXt1U#I0 zckbf&+@%Y1-;O_;`53hKuAQ2lIrHGcwTI__csTh53Ox9nW6U zQM4DW^{I5g#eVpHp`W^hi<3*T@=XL>h|yq3woD#Q7eD`g$sF0sCv#h^g~$+hD%~hK zr6m#z@F%&`S;3cGCbRl6&zxC>2FXEalvvM|-}C&WL+=@kz=8I}WzRq@Kva$EJ!)S# zq$+W(Cwd@^vYvRFXMpzAqC_14jEABiiVP1QLUxdO`r~}e)~pk4)W&bv8nSVjNbiHD z4McKC0n^%*v7qupQk^U O2>hR0Ed1xf!T$h3uVDB9 delta 1931 zcmaJ>No<={5YGF(#7=A{t`pmF96L*s)LHDbiPN}=o2^^gbRm@}b%E5*dr92dv3dTp zU=EdZ?{QVQZkp8gu6^CE@)^MdO##-IH>LpERV%sX4WujA9xpz(D zb=-H!w8|3QqV-&ud*E}{;HqCUazAfS@d(CyFn&$*)U_igux8*v-l(vhh_&%1;e#(& z?Rl>tycxMIe7nMTAs#}!Quyl19@@Q?x2eotmDw(Q4`h+b9oR9%J5*jD=7o7Xyr~6U zoogoE#k*BJit!$dU(*`K!>Ay_dsMt1<86GW@Nd-JzRETxpow~=cNbRg<$Wr9w^9)m zez?WzT>Xgb<^u{DP)H0e8d@*x!TN)INX28K^>Q1Jzr}=!@8!dML{0c^7X!|jrZS4P z#`w76>`|P3$SFt8{)cePigN%t2l*kz8C0AJ;`AWt@c)vI@JU4);>MF@p`%wHHC{5n zFBUfofz?vq8PcR5&qv9m=k#PIWRPC^a?A;RI+>Zv$+SM7EtCuyu@HjemQaoCMZ5){ zf)P!bWgC;d&}aRMMd1%?r!PW?g%Goi8JSI!G4w-58HISo8|Hrekh|e}MaUc_cMnum zHa8EVJA_ZchNjF~fi%ZDnGnf@Mo9G`W*h4>rI z<509UV%F=ncgNOyQ^9iCfiFo5P|KAAScMWeh%bbR&Ax zad~q^FISz6DJe>slH#O|*w8VZdU`s|l@=N(wHPNuyCH|cR~>ABk=j(FDYK|HG(`i= zLmtA|=0e8dZ1oSdcSY|*OztOh08Th-N0bI?FAtKTP9?`;p5Hr@@_PhKP*yXU4Jq$9 zx0?>*_uKHDGiWDoOEBDa{`eoQf87>fLs04Jt$t7d9CNvM-CgiKt*Gfq&G}@>)}q#U{As+R>aIAZuQn#FKKCxVkfD{R-cktio#+> zhsSDzRX-!Narmn>c^sateV_HiPWMFPQAA4Riv=NZpRmY@`-;d(c+K77`Hkr6pyL+N z_PA+#rr>+`O?DJM^NhNTZ1b#GD>T1Wqsdd?^uAYp`u=jq;2Upam4}k)yp&lgG?}>i zr#HbSOyPMx8!eWH^`s2X6^q7jUZ1DW*-Tm&MMF5B!dNk#Cs!|;!nuWo74+w$lzKjc zspoR3OgaT?zG1VP?-2at8*q|avd*S6VmhDH=j9M|)PK#+!0+{sxU+~QEDFn|&Z%7~ zF#&f1UUN3bMWz*2{4u6q{m4JXx{Tp$z7Lxvm(pmnxh1r-nI)96EK*qtou|-pc1b=C zJ%PCI39K%kL{ltZN^_AbL?n&-HjBUufev;SJ`RkUQ?} diff --git a/backend/app/routes/__pycache__/data_management.cpython-312.pyc b/backend/app/routes/__pycache__/data_management.cpython-312.pyc index 1b41356ddd458b20e9a995c2dc749c48d763f3ce..5ac3199c191023130850b69ff4be2428e3a45d52 100644 GIT binary patch delta 3515 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 diff --git a/backend/app/routes/__pycache__/services.cpython-312.pyc b/backend/app/routes/__pycache__/services.cpython-312.pyc index d25a028f36e994f5aad4c27b6838c9662d37fc98..f4dbb0d8a1301fdc9773098e7183a52d692595ff 100644 GIT binary patch delta 4486 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 diff --git a/backend/app/routes/__pycache__/user.cpython-39.pyc b/backend/app/routes/__pycache__/user.cpython-39.pyc index 67d80ac2877deca6c713821a94707bf6a683b66e..d405627561f27684fe02e771fdeef39ed38129b6 100644 GIT binary patch delta 2094 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 T+pJXW|1{-`SVEZcU99I1ZVT!b diff --git a/backend/app/services/__pycache__/algorithm.cpython-312.pyc b/backend/app/services/__pycache__/algorithm.cpython-312.pyc index 1a79f2aaf461ce1f9deca5d693e3cd61db6361cc..6ce6e3fd50df0371f368ad6b980cc9495b882870 100644 GIT binary patch delta 3989 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 delta 1878 zcmaKsdu&rx7{Kqjx9vJQRiKRrME>w3{e91KzSDEg z{l0mRY}-c?uS}gfg=525Kj1rN|0wZVB8kAS#u9D6N)l*+qyn3xEJ^5^!EvVvCUP8$ zsNf}sftLs*ICJ4tYCM!WO)beRc4zk|xAq*kefHqb^8Q<|9K7?^pe2qjW&s+HSc1r5 zh-!jfujmQUMl^(Wzu%&!28=F8pqH*d;9OD703RnU)Szn{2ieIbX1!sL^s02oab0h^ zp*N4}&4cRedg~2+>8QT+y1qQ3EvGJ)E&q@#67n3ZF!)HfJY)buw!`wYp|n*jNT(v& z7^13%;mBCFO%TG~ffbtukAfJQ`VG|)ee znfC-vlRU;VZOkCwO>a_@eX>1wDNoMHyN!DYIWOm#P7pFIpD%ck&v+iITN#{|L>fRt zM67|vq5@qHs%sJJpuZ?9wGY)ri1mmTL@S&rY9N>8hebz-?%xGE;Dot_zsfZ-E?d_LZF37*1(pY6Rtto! zCYK~MbA?=}esbb;b|C%PPfgAXEuD-@*^^(=QSLaFz?)Ep+Zb8~ZS(4i9&chb=Z@>o z-kSYdI3{~ZFV`om=6Z=GA4cZ=E%0^|ImH28+@rfDX~_9o%a`>9Y=+gLH1E%F7ik%l?ku;55y;>*<9I3~Wz zwa;NOsGj5~|En3d zsC1f@OtYva{xY0(t|NP8UEO9vnqgc{+8t*VfliGWFtldGk$1hF@*wnOPy7EaV vRsXw_sJ5CVhy9)!`Xk(Jns2f3#PBTED_cUByF5SDqZj&`}g4L diff --git a/backend/app/services/__pycache__/algorithm.cpython-39.pyc b/backend/app/services/__pycache__/algorithm.cpython-39.pyc index 0eb2cbcbabc1468c18dbbd61fb8b6a95e9c535b2..80c09f33d1e60f02fb69d53c3d6d04ab46231e23 100644 GIT binary patch delta 1496 zcmZvbT}&KR6oBuY8Fpu9e_*#PyEU@gLit%+Hx!7i6^jIdK2#o{p>_-6c6SyQW|`&A z&{Q)kn$ngSY?UiN@h7rcOw)$8bTzG}>4QFN?28X$j4`@^Nuv*H^uY)3nT7Vn-P~`^ z-`sP~+;e~IUFrA4-EIl7-__oC(vNqqdHxiq+)NeN>SRk>Vo2g7r=SeZ2^bk@nKOhu zUgq;CR|cBSw4BZ0$w9-mT&4@7OdjJbo6U-{C;6l>jf}D`bOz_dVU%;QB^mZEG`J_{ zG@PS$Lp+6uZa9`j!#S157)`_s36w3_VMqa#a~UoJvp*L=XStoI4@Jr*TUdRY7j6|V zE^J&|TrbQQXBIc}mw&%A`^%lB;@O$?rMnxKz9`VY#pR#fG$(JS8j`1``5FN%Xl!S@(Lcf>VKn9FM?>4LUE3~4j7N-iW zRbOJ9rGE$7F`);7VOtggC+I+MKj*`Ri@_0rIsSPxeMAMvB!g+w^te8$ktVS02R!WL z5U5WA2!kmk)a=WqC!WnlC-tAhxJ+ON|44F)uno6FXm5XhWVQ@Gyhd>w_0u2I_(}zF~S3a z@LReD5BRNHjU0{?6~5bI-l^{O*2Z-|&9uOJFQBN!#gTN9yBN>7Vsbd+E$=Cgzi=MK`Dfuf+Q)B-ZaJ68d7==klH}Db zi9}4S=Ih!NvgKZCm(A3d+gDsn)X#k-w`noYA2F2%c;|@E^dJ`=SxaThu-GqI;L}6^ zoh_h+Z#um8%?Ou+75u{Ca_9(Wf>y8+w6V?UrKdc>*>C^7zMa21r<*^R*k9VPXAS19 z1?#|iumKDKMPMihf@#1Ej2lr9b%0Kwfe_dPwt`uIGaFsp$#L0IF8NA7mbxjBvy?p` zityu64HV@9m&Y2x#BDsmRcHQT%58RqRXOlvOk`;o16`85HI)vgyHjE!GIikhBhEm# z9z-R1wIh+-k=KIBWNmjk5=+&FA~9WT=iQ!@w1XYx4fetPuprKXa*usG!d|eK50+1+ z13XaP>d44MzCh@yWTGpji(?o+%FnCIc-Ghw8qO_apHYmz6gP3u+bE+;UN0TtM>39Z zePt)dD(c+EPK&V8D6Yja@~ML*7vcLAljsD0sd#JuV+OyfY@*XLTbipM=Qcar<&sbM zG}?R$-a@A?gy`XD)o41;2dir60^hBgNWJ{NYQM}xt7~Rl!G2dkFSsVjTjg_f$I{|D zGB=Q+&^j%x$CClPoBdu*e^#IPPpKHSF$~wjS8N=SIEBclm(z$g0;6co zAUX?Lz&-G{H~t32 HqUpZ?3fc{H diff --git a/backend/app/services/__pycache__/service_orchestrator.cpython-312.pyc b/backend/app/services/__pycache__/service_orchestrator.cpython-312.pyc index 3d994cec7cb2fc687afafe6d4e07059df2aa835e..b597fc5b160b3a5c7b89b09f005553d1d387c036 100644 GIT binary patch delta 10422 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 OQkKGDmR>Tk0RJDA)Y^&w delta 5259 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 List[Algorithm]: - """获取算法列表""" + """获取算法列表,优先显示已注册的服务""" + from app.models.models import AlgorithmService as Service + + # 获取所有已注册的服务 + services = db.query(Service).all() + + # 获取所有算法 query = db.query(Algorithm) # 如果指定了算法类型,进行过滤 if algorithm_type: query = query.filter(Algorithm.type == algorithm_type) - return query.offset(skip).limit(limit).all() + algorithms = query.all() + + # 创建服务名称集合,用于快速查找 + service_names = {service.name for service in services} + + # 将算法分为两类:已注册的服务和普通算法 + registered_algorithms = [] + normal_algorithms = [] + + for algo in algorithms: + if algo.name in service_names: + registered_algorithms.append(algo) + else: + normal_algorithms.append(algo) + + # 合并结果:已注册的服务在前,普通算法在后 + merged_algorithms = registered_algorithms + normal_algorithms + + # 应用分页 + return merged_algorithms[skip:skip+limit] @staticmethod def update_algorithm(db: Session, algorithm_id: str, algorithm_update: AlgorithmUpdate) -> Optional[Algorithm]: @@ -358,14 +387,37 @@ class AlgorithmCallService: # 记录开始时间 start_time = time.time() + # 处理视频路径 - 如果是MinIO路径,下载到本地 + from app.utils.file import file_storage + video_path = processed_input_data.get('video', '') + if video_path and video_path.startswith('media/'): + # 从MinIO下载视频到本地 + video_content = file_storage.get_object(video_path) + if video_content: + # 保存到临时文件 + import tempfile + import uuid + suffix = '.' + video_path.split('.')[-1] if '.' in video_path else '.mp4' + with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp_file: + tmp_file.write(video_content) + local_video_path = tmp_file.name + # 使用本地路径 + processed_input_data['video'] = local_video_path + else: + db_call.status = "failed" + db_call.error_message = "无法下载视频文件" + db.commit() + return db_call + # 调用算法API + print(f"[DEBUG] 调用算法API: {version.url}, 输入: {processed_input_data}") response = requests.post( version.url, json={ "input_data": processed_input_data, "params": call.params }, - timeout=30 + timeout=120 ) # 计算响应时间 @@ -374,7 +426,35 @@ class AlgorithmCallService: # 处理响应 if response.status_code == 200: output_data = response.json() + print(f"[DEBUG] 算法响应: {output_data}") + + # 处理算法返回的视频文件 + result = output_data.get('result', {}) + if result and isinstance(result, dict): + # 如果返回了本地视频路径,需要处理 + if 'video' in result and result['video'] and result['video'].startswith('/'): + # 这是本地路径,需要转换为可访问的URL + local_video = result['video'] + if os.path.exists(local_video): + # 上传到MinIO并替换路径 + with open(local_video, 'rb') as f: + video_content = f.read() + + video_filename = f"results/{user_id}/{uuid.uuid4().hex[:12]}.mp4" + from app.utils.file import file_storage + success = file_storage.upload_from_bytes(video_content, video_filename) + if success: + result['video'] = video_filename + result['video_url'] = f"/api/v1/data/media/{video_filename}" + + # 删除临时文件 + try: + os.remove(local_video) + except: + pass + db_call.status = "success" + print(f"[DEBUG] 保存output_data: {output_data}") db_call.output_data = output_data db_call.response_time = response_time else: diff --git a/backend/app/services/data_manager.py b/backend/app/services/data_manager.py index 994b6ec..582a742 100644 --- a/backend/app/services/data_manager.py +++ b/backend/app/services/data_manager.py @@ -235,7 +235,7 @@ class DataManager: def get_media_file(self, file_path: str) -> Optional[bytes]: """获取媒体文件内容""" try: - content = file_storage.download_file(file_path) + content = file_storage.get_object(file_path) return content except Exception as e: logger.error(f"Error getting media file: {str(e)}") diff --git a/backend/app/services/service_orchestrator.py b/backend/app/services/service_orchestrator.py index d420ade..77a8806 100644 --- a/backend/app/services/service_orchestrator.py +++ b/backend/app/services/service_orchestrator.py @@ -11,18 +11,41 @@ import psutil from typing import Dict, Any, Optional from docker.errors import DockerException, NotFound +# 数据库相关导入 +try: + from sqlalchemy import create_engine, text + from sqlalchemy.orm import sessionmaker + DATABASE_AVAILABLE = True +except ImportError: + DATABASE_AVAILABLE = False + class ServiceOrchestrator: """服务编排服务""" - def __init__(self, deployment_mode="local"): + def __init__(self, deployment_mode="local", db_url=None): """初始化服务编排器 Args: deployment_mode: 部署模式,支持"docker"和"local" + db_url: 数据库连接URL,用于重新加载服务信息 """ self.deployment_mode = deployment_mode self.processes = {} # 存储本地进程信息 + self.db_url = db_url + self.db_engine = None + self.db_session = None + + # 初始化数据库连接 + if db_url and DATABASE_AVAILABLE: + try: + self.db_engine = create_engine(db_url) + self.db_session = sessionmaker(bind=self.db_engine)() + print("数据库连接成功") + except Exception as e: + print(f"数据库连接失败: {e}") + self.db_engine = None + self.db_session = None if deployment_mode == "docker": try: @@ -178,15 +201,17 @@ class ServiceOrchestrator: # 本地进程启动 if service_id not in self.processes: # 服务不在进程列表中,可能是服务重启导致的 - # 这种情况下,需要从外部重新注册服务 - # 暂时返回错误,建议用户重新注册服务 - print(f"服务 {service_id} 不在进程列表中,无法启动") - return { - "success": False, - "error": "服务不存在,请重新注册服务", - "service_id": service_id, - "status": "error" - } + # 尝试从数据库重新加载服务信息 + print(f"服务 {service_id} 不在进程列表中,尝试从数据库重新加载") + service_info = self.reload_service_from_db(service_id) + + if not service_info: + return { + "success": False, + "error": "服务不存在,请重新注册服务", + "service_id": service_id, + "status": "error" + } process_info = self.processes[service_id] @@ -209,11 +234,76 @@ class ServiceOrchestrator: project_info = process_info["project_info"] service_config = process_info["service_config"] + print(f"准备启动服务 {service_id}") + print(f"服务目录: {service_dir}") + print(f"服务配置: {service_config}") + + # 检查服务目录是否存在,如果不存在则从Gitea克隆 + if not os.path.exists(service_dir): + print(f"服务目录不存在: {service_dir},尝试从Gitea克隆代码") + repository_id = service_config.get("repository_id") + if not repository_id: + return { + "success": False, + "error": "无法获取仓库ID,无法克隆代码", + "service_id": service_id, + "status": "error" + } + + # 从数据库获取仓库信息 + try: + from app.models.database import SessionLocal + from app.models.models import AlgorithmRepository + + db = SessionLocal() + repository = db.query(AlgorithmRepository).filter(AlgorithmRepository.id == repository_id).first() + db.close() + + if not repository: + return { + "success": False, + "error": f"仓库不存在: {repository_id}", + "service_id": service_id, + "status": "error" + } + + # 克隆仓库 + from app.gitea.service import GiteaService + gitea_service = GiteaService() + clone_success = gitea_service.clone_repository( + repository.repo_url, + service_id, + repository.branch or "main" + ) + + if not clone_success: + return { + "success": False, + "error": f"克隆仓库失败: {repository.repo_url}", + "service_id": service_id, + "status": "error" + } + + print(f"成功从Gitea克隆仓库到: {service_dir}") + + except Exception as e: + print(f"克隆仓库时出错: {str(e)}") + return { + "success": False, + "error": f"克隆仓库时出错: {str(e)}", + "service_id": service_id, + "status": "error" + } + # 启动服务进程 + print(f"开始启动服务进程...") new_process_info = self._start_local_service_process(service_id, service_dir, project_info, service_config) + print(f"服务进程启动完成: {new_process_info}") # 验证服务启动 + print(f"开始验证服务启动...") if not self._verify_local_service_startup(service_id, service_config): + print(f"服务启动验证失败") return { "success": False, "error": "服务启动验证失败", @@ -221,6 +311,7 @@ class ServiceOrchestrator: "status": "error" } + print(f"服务启动成功!") return { "success": True, "service_id": service_id, @@ -610,6 +701,76 @@ class ServiceOrchestrator: "health": "unknown" } + def reload_service_from_db(self, service_id: str) -> Optional[Dict[str, Any]]: + """从数据库重新加载服务信息 + + Args: + service_id: 服务ID + + Returns: + 服务信息字典,如果未找到则返回None + """ + if not self.db_session: + print("数据库连接不可用,无法重新加载服务信息") + return None + + try: + # 从数据库查询服务信息 + query = text(""" + SELECT service_id, api_url, status, config, host, port + FROM algorithm_services + WHERE service_id = :service_id + """) + result = self.db_session.execute(query, {"service_id": service_id}).fetchone() + + if not result: + print(f"数据库中未找到服务 {service_id}") + return None + + # 构建服务信息字典 + # config字段已经是JSON类型,不需要再解析 + config_data = result[3] if result[3] else {} + + # 将host和port添加到config中 + config_data["host"] = result[4] if result[4] else "localhost" + config_data["port"] = result[5] if result[5] else 8000 + + service_info = { + "service_id": result[0], + "api_url": result[1], + "status": result[2], + "config": config_data + } + + # 从config中提取容器ID + container_id = service_info["config"].get("container_id") + if container_id: + service_info["container_id"] = container_id + + # 更新本地进程缓存(仅用于本地模式) + if self.deployment_mode == "local": + # 从config中获取项目类型,如果没有则默认为python + project_type = service_info["config"].get("project_type", "python") + + self.processes[service_id] = { + "service_dir": f"/tmp/algorithms/{service_id}", + "project_info": { + "project_type": project_type, + "name": service_info["config"].get("name", ""), + "version": service_info["config"].get("version", "1.0.0"), + "description": service_info["config"].get("description", "") + }, + "service_config": service_info["config"], + "pid": None # PID需要重新获取 + } + + print(f"成功从数据库重新加载服务 {service_id} 的信息") + return service_info + + except Exception as e: + print(f"从数据库重新加载服务信息失败: {e}") + return None + def get_service_logs(self, container_id: str, lines: int = 100) -> Dict[str, Any]: """获取服务日志 @@ -1429,7 +1590,10 @@ def main(data): import requests # 构建健康检查URL + # 使用localhost而不是0.0.0.0,因为健康检查是在本地执行的 host = service_config.get("host", "localhost") + if host == "0.0.0.0": + host = "localhost" port = service_config.get("port", 8000) health_check_url = f"http://{host}:{port}/health" @@ -1437,5 +1601,6 @@ def main(data): response = requests.get(health_check_url, timeout=10) return response.status_code == 200 - except: + except Exception as e: + print(f"健康检查失败: {e}") return False diff --git a/backend/app/utils/__pycache__/file.cpython-312.pyc b/backend/app/utils/__pycache__/file.cpython-312.pyc index fb997e50e5087effb767baece330f6face5c4a38..0505875bfc997814c36ffa83f869ea7322f9d2bc 100644 GIT binary patch delta 1287 zcmZ8f?{8C87(VCzT5sFi_2>F?w6sgH$O;lf&}CtpEL#?42>1aa)@|pubSdp}?wzA^ zV~JTrXLOPS%mhqk!Z&o$G%+OjK_u!2KM_r6su(}aML!t3%s;^M-mqPOK0qAp5j?Ib|#x??f~ z{QobD>3Gbx5$B?ORxhjb8k)@t-}iieB& zNYEmAbw3}dU01Ajh4^?aQ1xd*wQgZRjY(w89GejTGnDd;X+aVJ96Xmwt%41Y*OCi&{ z&FVrlIWMzg{`i7zPQHOY61L?rI)jKbFJF?YLTjJFGw7mt2F>7%IvwQ->v!M(?bDUt zm)_jC_SwdJYwLHG*1ufcxPE#4?iV!8>w{F_IQj=dt0*Hhl#*U9nCBbn)EG5$MsZd@ zP4{p@#!dqWEg%X%i-KDXp;&3O^;b>Z7=DSuunqgvw3(}nPwEpU^+Q7BYE%LG^($w%z&Okl5`vbZ%du4XD>xbZ;w}O?SJS_Ja8xK?r7Z|`Z*Z+@DQukBk86CX{o=FqPdzbmT~M&8u0zM`qaKGx|db^ z!{WW?Lj8X6Yro86dWaWmRmTwa(QY1QM4IIr-4Ae>J#r?J^dQ7wPRkd$tnG zI3nOdwh}CJR6=Py%(_FJ$_*f1x9%u}JOVz!s*wqfk0PUZjqQux#~rL09Xck1^9FsE z)2+WopMmHoKo0;+LBV)>oI^t@6%CUPLns!@COyeskL|+0vTLz@v1ZaIhgbC`;@h2+ zwSCpK1*gru?0)PfK399K^99TYyt(TV{*FcCeZg~}{0ax|ruByMkSH)C{yhGct;HiF zz!=R)6ilP10bT|;4*-qSl7JUQbKCF@4~#Yri)c%b)Od@NW4?re53tLL7rQJfJoG0t f8n&Jb6*67YdufIJm3YcY1svK$fXxz1?JoQS{&_X* delta 707 zcmZ9JO-vI(6o7ZO+ikb)E-h_srGH34ZKxL#g7K#)^#@{rL_7%8kd~R^Vrj{2fq;p6 zAjXiWhY2Po9yBrWVxqW-7w%p>=y{Dd&jz9gZ~ETC$w~Ihd*8hGX0r2oY^!X1*7bk{ z>+jPQb2qVLOcC-))X0D@O8rb*jEQ$7abII9Rhh5ur-4oHOptmZR}P&@gPY1s$aN~y znQ;`R)fE~7+i_@pkn50#X^a|fUj$4C%<80ZwG-g2z!Cthvy>?P$wY-k!6}4aSRbgB$z=P&7M}p1|^o z*v)hW`$6zLqA2>pWwivMbyrlA31I{^`$@PWtJl#jp|gF_EjjHdwA!xnG-@xP8yC+& z#D~P&c#6!6FM8Vk5nq$Zs$EH*mdORN-PHstspF(9`coP8A$C7=hq6>eCRfE;W*+!0 zGoe1my;b)TSF&$NT>Q+AmSnT#_(s;-%o>~I19;dV0x#frYv}n9ieVH=!?fDGjG@PD zw)r)&n#;l?Zs&^fevkN`dqJk{jqbAqZu9ZjJ@P>e_FPO%WB*M+!NXG+*AVmKanG6h z4un<&@aKW_TbA_-*;A!h?rSK^v2CKD@Nul3KujVi0;jnJ2mfalQ^b4tEFWM~`Xwt` z?x1E?oXT5dO1#Qn>UB%;<^P-E_|Gl1=t_f?_#L73o($cRNpfF8?v04^y$k;U(t4~t diff --git a/backend/app/utils/file.py b/backend/app/utils/file.py index 32a1156..7bdb27c 100644 --- a/backend/app/utils/file.py +++ b/backend/app/utils/file.py @@ -59,6 +59,27 @@ class MinioClient: logging.warning(f"MinIO upload error: {e}") return False + def upload_from_bytes(self, data: bytes, object_name: str) -> bool: + """从字节数据上传文件""" + if not self.is_connected: + logging.warning("MinIO is not connected. Upload skipped.") + return False + + try: + import io + file_obj = io.BytesIO(data) + self.client.put_object( + self.bucket_name, + object_name, + file_obj, + length=len(data), + part_size=10*1024*1024 + ) + return True + except S3Error as e: + logging.warning(f"MinIO upload error: {e}") + return False + def upload_fileobj(self, file_obj: io.BytesIO, object_name: str, content_type: str = "application/octet-stream") -> bool: """上传文件对象""" if not self.is_connected: diff --git a/backend/backend.log b/backend/backend.log deleted file mode 100644 index 47dcdcc..0000000 --- a/backend/backend.log +++ /dev/null @@ -1,2 +0,0 @@ -INFO: Will watch for changes in these directories: ['/Users/duguoyou/MLFlow/algorithm-showcase/backend'] -ERROR: [Errno 48] Address already in use diff --git a/backend/check_admin.py b/backend/check_admin.py deleted file mode 100644 index e9ca686..0000000 --- a/backend/check_admin.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python3 -""" -检查并重置管理员账号 -""" - -from sqlalchemy.orm import Session -from app.models.database import engine, Base, SessionLocal -from app.models.models import User, Role -from app.services.user import UserService - - -def check_admin(): - """检查并重置管理员账号""" - db = SessionLocal() - - try: - # 检查是否存在管理员账号 - print("检查管理员账号...") - admin_user = db.query(User).filter(User.username == "admin").first() - - if not admin_user: - print("⚠️ 管理员账号不存在,创建新的管理员账号...") - - # 初始化默认角色 - UserService.init_default_roles(db) - - # 获取默认管理员角色 - admin_role = UserService.get_role_by_name(db, "admin") - if not admin_role: - print("❌ 管理员角色不存在,创建失败") - return - - # 创建默认管理员账号 - admin_user = User( - id="user-admin", - username="admin", - email="admin@example.com", - password_hash=UserService.get_password_hash("admin123"), - role_id=admin_role.id, - status="active" - ) - db.add(admin_user) - db.commit() - db.refresh(admin_user) - print("✅ 管理员账号创建成功") - else: - print("✅ 管理员账号存在,重置密码...") - # 重置管理员密码 - admin_user.password_hash = UserService.get_password_hash("admin123") - db.commit() - print("✅ 管理员密码重置成功") - - # 显示管理员账号信息 - print("\n管理员账号信息:") - print(f"用户名: {admin_user.username}") - print(f"密码: admin123") - print(f"邮箱: {admin_user.email}") - print(f"状态: {admin_user.status}") - - # 检查角色信息 - if admin_user.role: - print(f"角色: {admin_user.role.name}") - else: - print("⚠️ 管理员角色信息缺失") - # 尝试修复角色关联 - admin_role = UserService.get_role_by_name(db, "admin") - if admin_role: - admin_user.role_id = admin_role.id - db.commit() - print("✅ 管理员角色关联修复成功") - - except Exception as e: - print(f"❌ 操作失败: {e}") - finally: - db.close() - - -if __name__ == "__main__": - check_admin() diff --git a/backend/check_algorithms.py b/backend/check_algorithms.py deleted file mode 100644 index e75aed9..0000000 --- a/backend/check_algorithms.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -"""检查算法数据""" - -import sys -import os -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -from app.models.database import SessionLocal -from app.models.models import Algorithm - -def check_algorithms(): - """检查算法数据""" - db = SessionLocal() - - try: - algorithms = db.query(Algorithm).all() - - print(f"数据库中共有 {len(algorithms)} 个算法:\n") - - for algo in algorithms: - print(f"算法名称: {algo.name}") - print(f" ID: {algo.id}") - print(f" 类型: {algo.type}") - print(f" 技术分类: {algo.tech_category}") - print(f" 输出类型: {algo.output_type}") - print(f" 描述: {algo.description}") - print(f" 状态: {algo.status}") - print(f" 版本数: {len(algo.versions)}") - print() - - except Exception as e: - print(f"检查算法数据失败: {e}") - sys.exit(1) - finally: - db.close() - -if __name__ == "__main__": - check_algorithms() \ No newline at end of file diff --git a/backend/check_user_role.py b/backend/check_user_role.py deleted file mode 100644 index 850746a..0000000 --- a/backend/check_user_role.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python3 -"""检查用户角色信息""" - -import requests - -def check_user_role(): - """检查用户角色""" - base_url = "http://localhost:8001/api/v1" - - # 登录 - print("步骤1: 登录") - login_data = { - "username": "admin", - "password": "admin123" - } - - try: - response = requests.post(f"{base_url}/users/login", json=login_data) - print(f"状态码: {response.status_code}") - - if response.status_code != 200: - print(f"登录失败: {response.text}") - return - - data = response.json() - access_token = data.get('access_token') - print(f"登录成功!") - - # 获取用户信息 - print("\n步骤2: 获取用户信息") - headers = {"Authorization": f"Bearer {access_token}"} - user_response = requests.get(f"{base_url}/users/me", headers=headers) - print(f"状态码: {user_response.status_code}") - - if user_response.status_code == 200: - user_data = user_response.json() - print(f"\n用户信息:") - print(f" 用户名: {user_data.get('username', 'N/A')}") - print(f" 邮箱: {user_data.get('email', 'N/A')}") - print(f" 角色ID: {user_data.get('role_id', 'N/A')}") - print(f" 角色名称: {user_data.get('role_name', 'N/A')}") - print(f" 角色对象: {user_data.get('role', 'N/A')}") - - # 检查是否是管理员 - role_name = user_data.get('role_name') - if role_name == 'admin': - print(f"\n✅ 用户是管理员,应该显示后台管理页面") - else: - print(f"\n❌ 用户不是管理员,角色名称是: {role_name}") - else: - print(f"获取用户信息失败: {user_response.text}") - - except Exception as e: - print(f"错误: {e}") - -if __name__ == "__main__": - check_user_role() \ No newline at end of file diff --git a/backend/check_users.py b/backend/check_users.py deleted file mode 100644 index ae8d6ab..0000000 --- a/backend/check_users.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python3 -"""检查数据库中的用户信息""" - -import sys -sys.path.insert(0, '/Users/duguoyou/MLFlow/algorithm-showcase/backend') - -from app.models.database import SessionLocal -from app.models.models import User -from app.services.user import UserService - -def check_users(): - """检查用户""" - db = SessionLocal() - - try: - # 获取所有用户 - users = db.query(User).all() - - print(f"数据库中的用户数量: {len(users)}") - - for user in users: - print(f"\n用户ID: {user.id}") - print(f"用户名: {user.username}") - print(f"邮箱: {user.email}") - print(f"状态: {user.status}") - print(f"角色ID: {user.role_id}") - print(f"密码哈希: {user.password_hash[:50]}...") - - # 测试admin用户认证 - print("\n\n测试admin用户认证:") - admin_user = UserService.get_user_by_username(db, 'admin') - if admin_user: - print(f"找到admin用户: {admin_user.id}") - print(f"密码哈希: {admin_user.password_hash[:50]}...") - - # 测试密码验证 - test_password = 'admin123' - is_valid = UserService.verify_password(test_password, admin_user.password_hash) - print(f"密码 '{test_password}' 验证结果: {is_valid}") - - # 尝试认证 - authenticated_user = UserService.authenticate_user(db, 'admin', test_password) - if authenticated_user: - print(f"认证成功: {authenticated_user.id}") - else: - print("认证失败") - else: - print("未找到admin用户") - - finally: - db.close() - -if __name__ == "__main__": - check_users() \ No newline at end of file diff --git a/backend/create_sample_algorithms.py b/backend/create_sample_algorithms.py deleted file mode 100644 index e1d9823..0000000 --- a/backend/create_sample_algorithms.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env python3 -"""创建示例算法数据""" - -import sys -import os -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -from app.models.database import SessionLocal -from app.models.models import Algorithm, AlgorithmVersion -from datetime import datetime -import uuid - -def create_sample_algorithms(): - """创建示例算法""" - db = SessionLocal() - - try: - # 示例算法数据 - algorithms_data = [ - { - "name": "目标检测", - "description": "识别图像中的物体位置和类别,支持人脸、车辆、物品等多种目标检测", - "type": "computer_vision", - "tech_category": "computer_vision", - "output_type": "image", - "versions": [ - { - "version": "1.0.0", - "url": "http://0.0.0.0:8001", - "is_default": True - } - ] - }, - { - "name": "视频分析", - "description": "分析视频内容,提取关键帧、识别动作、追踪物体等", - "type": "computer_vision", - "tech_category": "video_processing", - "output_type": "video", - "versions": [ - { - "version": "1.0.0", - "url": "http://0.0.0.0:8002", - "is_default": True - } - ] - }, - { - "name": "图像增强", - "description": "提升图像质量,包括去噪、超分辨率、色彩校正等功能", - "type": "computer_vision", - "tech_category": "computer_vision", - "output_type": "image", - "versions": [ - { - "version": "1.0.0", - "url": "http://0.0.0.0:8003", - "is_default": True - } - ] - }, - { - "name": "文本分类", - "description": "对文本内容进行分类,支持新闻分类、情感分析、垃圾邮件识别等", - "type": "nlp", - "tech_category": "nlp", - "output_type": "text", - "versions": [ - { - "version": "1.0.0", - "url": "http://0.0.0.0:8004", - "is_default": True - } - ] - }, - { - "name": "异常检测", - "description": "检测数据中的异常模式,适用于工业监控、金融风控等场景", - "type": "ml", - "tech_category": "ml", - "output_type": "json", - "versions": [ - { - "version": "1.0.0", - "url": "http://0.0.0.0:8005", - "is_default": True - } - ] - }, - { - "name": "医学影像分析", - "description": "分析医学影像,辅助医生进行疾病诊断,支持CT、MRI等多种影像格式", - "type": "medical", - "tech_category": "computer_vision", - "output_type": "image", - "versions": [ - { - "version": "1.0.0", - "url": "http://0.0.0.0:8006", - "is_default": True - } - ] - } - ] - - # 创建算法 - for algo_data in algorithms_data: - # 检查算法是否已存在 - existing_algo = db.query(Algorithm).filter(Algorithm.name == algo_data["name"]).first() - if existing_algo: - print(f"✓ 算法 '{algo_data['name']}' 已存在,跳过") - continue - - # 创建算法 - algorithm = Algorithm( - id=str(uuid.uuid4()), - name=algo_data["name"], - description=algo_data["description"], - type=algo_data["type"], - tech_category=algo_data["tech_category"], - output_type=algo_data["output_type"], - status="active" - ) - db.add(algorithm) - db.flush() # 获取算法ID - - # 创建版本 - for version_data in algo_data["versions"]: - version = AlgorithmVersion( - id=str(uuid.uuid4()), - algorithm_id=algorithm.id, - version=version_data["version"], - url=version_data["url"], - is_default=version_data["is_default"] - ) - db.add(version) - - print(f"✓ 已创建算法: {algo_data['name']}") - - db.commit() - print("\n示例算法创建完成!") - - except Exception as e: - db.rollback() - print(f"创建示例算法失败: {e}") - sys.exit(1) - finally: - db.close() - -if __name__ == "__main__": - create_sample_algorithms() \ No newline at end of file diff --git a/backend/database.db b/backend/database.db deleted file mode 100644 index e69de29..0000000 diff --git a/backend/migrate_add_algorithm_fields.py b/backend/migrate_add_algorithm_fields.py deleted file mode 100644 index c515cef..0000000 --- a/backend/migrate_add_algorithm_fields.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 -"""数据库迁移脚本:添加技术分类和输出类型字段""" - -import sys -import os -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -from sqlalchemy import text -from app.models.database import engine - -def migrate(): - """执行数据库迁移""" - try: - with engine.connect() as conn: - # 检查字段是否已存在(PostgreSQL语法) - result = conn.execute(text(""" - SELECT column_name - FROM information_schema.columns - WHERE table_name = 'algorithms' - """)) - columns = [row[0] for row in result.fetchall()] - - # 添加 tech_category 字段 - if 'tech_category' not in columns: - conn.execute(text("ALTER TABLE algorithms ADD COLUMN tech_category VARCHAR(50) DEFAULT 'computer_vision'")) - print("✓ 已添加 tech_category 字段") - else: - print("✓ tech_category 字段已存在") - - # 添加 output_type 字段 - if 'output_type' not in columns: - conn.execute(text("ALTER TABLE algorithms ADD COLUMN output_type VARCHAR(50) DEFAULT 'image'")) - print("✓ 已添加 output_type 字段") - else: - print("✓ output_type 字段已存在") - - conn.commit() - print("\n数据库迁移完成!") - - except Exception as e: - print(f"数据库迁移失败: {e}") - sys.exit(1) - -if __name__ == "__main__": - migrate() \ No newline at end of file diff --git a/backend/migrate_add_service_fields.py b/backend/migrate_add_service_fields.py deleted file mode 100644 index e5ce798..0000000 --- a/backend/migrate_add_service_fields.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 -"""数据库迁移脚本:为algorithm_services表添加技术分类和输出类型字段""" - -import sys -import os -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -from sqlalchemy import text -from app.models.database import engine - -def migrate(): - """执行数据库迁移""" - try: - with engine.connect() as conn: - # 检查字段是否已存在(PostgreSQL语法) - result = conn.execute(text(""" - SELECT column_name - FROM information_schema.columns - WHERE table_name = 'algorithm_services' - """)) - columns = [row[0] for row in result.fetchall()] - - # 添加 tech_category 字段 - if 'tech_category' not in columns: - conn.execute(text("ALTER TABLE algorithm_services ADD COLUMN tech_category VARCHAR(50) DEFAULT 'computer_vision'")) - print("✓ 已添加 tech_category 字段到 algorithm_services 表") - else: - print("✓ tech_category 字段已存在于 algorithm_services 表") - - # 添加 output_type 字段 - if 'output_type' not in columns: - conn.execute(text("ALTER TABLE algorithm_services ADD COLUMN output_type VARCHAR(50) DEFAULT 'image'")) - print("✓ 已添加 output_type 字段到 algorithm_services 表") - else: - print("✓ output_type 字段已存在于 algorithm_services 表") - - conn.commit() - print("\n数据库迁移完成!") - - except Exception as e: - print(f"数据库迁移失败: {e}") - sys.exit(1) - -if __name__ == "__main__": - migrate() \ No newline at end of file diff --git a/backend/test_all_apis.py b/backend/test_all_apis.py deleted file mode 100644 index b75fc9b..0000000 --- a/backend/test_all_apis.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -"""测试所有API端点""" - -import requests - -def test_apis(): - """测试API端点""" - base_url = "http://localhost:8001/api/v1" - - # 测试算法列表(不需要认证) - print("1. 测试算法列表(不需要认证):") - try: - response = requests.get(f"{base_url}/algorithms/") - print(f" 状态码: {response.status_code}") - if response.status_code == 200: - data = response.json() - print(f" 成功获取 {len(data.get('algorithms', []))} 个算法") - else: - print(f" 失败: {response.text}") - except Exception as e: - print(f" 错误: {e}") - - # 测试用户信息(需要认证) - print("\n2. 测试用户信息(需要认证):") - try: - response = requests.get(f"{base_url}/users/me") - print(f" 状态码: {response.status_code}") - if response.status_code == 401: - print(f" 需要认证(正常)") - else: - print(f" 响应: {response.text}") - except Exception as e: - print(f" 错误: {e}") - - # 测试服务列表(需要认证) - print("\n3. 测试服务列表(需要认证):") - try: - response = requests.get(f"{base_url}/services") - print(f" 状态码: {response.status_code}") - if response.status_code == 401: - print(f" 需要认证(正常)") - else: - print(f" 响应: {response.text[:200]}") - except Exception as e: - print(f" 错误: {e}") - -if __name__ == "__main__": - test_apis() \ No newline at end of file diff --git a/backend/test_api.py b/backend/test_api.py deleted file mode 100644 index d45044f..0000000 --- a/backend/test_api.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python3 -"""测试前端API调用""" - -import sys -import os -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -import requests - -def test_api(): - """测试API""" - try: - # 调用算法列表API - response = requests.get('http://localhost:8001/api/v1/algorithms/') - - if response.status_code == 200: - data = response.json() - algorithms = data.get('algorithms', []) - - print(f"成功获取 {len(algorithms)} 个算法\n") - - # 检查每个算法的字段 - for algo in algorithms: - print(f"算法: {algo['name']}") - print(f" 技术分类: {algo.get('tech_category', 'N/A')}") - print(f" 输出类型: {algo.get('output_type', 'N/A')}") - print() - - # 测试筛选 - print("测试筛选功能:") - - # 按技术分类筛选 - cv_algorithms = [a for a in algorithms if a.get('tech_category') == 'computer_vision'] - print(f" 计算机视觉算法: {len(cv_algorithms)} 个") - - # 按输出类型筛选 - image_algorithms = [a for a in algorithms if a.get('output_type') == 'image'] - print(f" 图片输出算法: {len(image_algorithms)} 个") - - # 按名称搜索 - search_results = [a for a in algorithms if '视频' in a.get('name', '')] - print(f" 包含'视频'的算法: {len(search_results)} 个") - - else: - print(f"API调用失败: {response.status_code}") - print(response.text) - - except Exception as e: - print(f"测试失败: {e}") - sys.exit(1) - -if __name__ == "__main__": - test_api() \ No newline at end of file diff --git a/backend/test_frontend_proxy.py b/backend/test_frontend_proxy.py deleted file mode 100644 index b9e6d87..0000000 --- a/backend/test_frontend_proxy.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 -"""测试前端代理配置""" - -import requests - -def test_frontend_proxy(): - """测试前端代理""" - try: - # 测试前端代理 - response = requests.get('http://localhost:3000/api/algorithms') - - print(f"状态码: {response.status_code}") - - if response.status_code == 200: - data = response.json() - print(f"成功获取 {len(data.get('algorithms', []))} 个算法") - - # 检查第一个算法的字段 - if data.get('algorithms'): - first_algo = data['algorithms'][0] - print(f"\n第一个算法:") - print(f" 名称: {first_algo.get('name')}") - print(f" 技术分类: {first_algo.get('tech_category')}") - print(f" 输出类型: {first_algo.get('output_type')}") - else: - print(f"请求失败: {response.text}") - - except Exception as e: - print(f"测试失败: {e}") - -if __name__ == "__main__": - test_frontend_proxy() \ No newline at end of file diff --git a/backend/test_full_login.py b/backend/test_full_login.py deleted file mode 100644 index 4b01d68..0000000 --- a/backend/test_full_login.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -"""测试完整的登录流程""" - -import requests - -def test_full_login_flow(): - """测试完整的登录流程""" - base_url = "http://localhost:8001/api/v1" - - # 步骤1: 登录 - print("步骤1: 登录") - login_data = { - "username": "admin", - "password": "admin123" - } - - try: - response = requests.post(f"{base_url}/users/login", json=login_data) - print(f"状态码: {response.status_code}") - - if response.status_code != 200: - print(f"登录失败: {response.text}") - return - - data = response.json() - access_token = data.get('access_token') - print(f"登录成功!") - print(f"Token: {access_token[:50]}...") - - # 步骤2: 使用token获取用户信息 - print("\n步骤2: 获取用户信息") - headers = {"Authorization": f"Bearer {access_token}"} - user_response = requests.get(f"{base_url}/users/me", headers=headers) - print(f"状态码: {user_response.status_code}") - - if user_response.status_code == 200: - user_data = user_response.json() - print(f"用户名: {user_data.get('username', 'N/A')}") - print(f"邮箱: {user_data.get('email', 'N/A')}") - print(f"角色: {user_data.get('role_name', 'N/A')}") - else: - print(f"获取用户信息失败: {user_response.text}") - - except Exception as e: - print(f"错误: {e}") - -if __name__ == "__main__": - test_full_login_flow() \ No newline at end of file diff --git a/backend/test_login.py b/backend/test_login.py deleted file mode 100644 index ce03114..0000000 --- a/backend/test_login.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 -"""测试登录功能""" - -import requests - -def test_login(): - """测试登录""" - base_url = "http://localhost:8001/api/v1" - - # 测试登录 - print("测试登录功能:") - login_data = { - "username": "admin", - "password": "admin123" - } - - try: - response = requests.post(f"{base_url}/users/login", json=login_data) - print(f"状态码: {response.status_code}") - - if response.status_code == 200: - data = response.json() - print(f"登录成功!") - print(f"访问令牌: {data.get('access_token', 'N/A')[:50]}...") - print(f"令牌类型: {data.get('token_type', 'N/A')}") - - # 测试使用令牌访问受保护的API - if data.get('access_token'): - headers = {"Authorization": f"Bearer {data['access_token']}"} - user_response = requests.get(f"{base_url}/users/me", headers=headers) - print(f"\n测试用户信息API:") - print(f"状态码: {user_response.status_code}") - if user_response.status_code == 200: - user_data = user_response.json() - print(f"用户名: {user_data.get('username', 'N/A')}") - print(f"邮箱: {user_data.get('email', 'N/A')}") - else: - print(f"失败: {user_response.text}") - else: - print(f"登录失败: {response.text}") - except Exception as e: - print(f"错误: {e}") - -if __name__ == "__main__": - test_login() \ No newline at end of file diff --git a/backend/test_login_api.py b/backend/test_login_api.py deleted file mode 100644 index fc256a1..0000000 --- a/backend/test_login_api.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python3 -"""直接测试登录API""" - -import requests - -def test_login_api(): - """测试登录API""" - base_url = "http://localhost:8001/api/v1" - - # 测试1: 使用JSON格式 - print("测试1: 使用JSON格式") - login_data = { - "username": "admin", - "password": "admin123" - } - - try: - response = requests.post(f"{base_url}/users/login", json=login_data) - print(f"状态码: {response.status_code}") - print(f"响应头: {dict(response.headers)}") - print(f"响应内容: {response.text[:500]}") - - if response.status_code == 200: - data = response.json() - print(f"✅ 登录成功!") - print(f"Token: {data.get('access_token', 'N/A')[:50]}...") - else: - print(f"❌ 登录失败") - except Exception as e: - print(f"错误: {e}") - - # 测试2: 使用form-data格式 - print("\n\n测试2: 使用form-data格式") - form_data = { - "username": "admin", - "password": "admin123" - } - - try: - response = requests.post(f"{base_url}/users/login", data=form_data) - print(f"状态码: {response.status_code}") - print(f"响应内容: {response.text[:500]}") - - if response.status_code == 200: - data = response.json() - print(f"✅ 登录成功!") - else: - print(f"❌ 登录失败") - except Exception as e: - print(f"错误: {e}") - -if __name__ == "__main__": - test_login_api() \ No newline at end of file diff --git a/backend/test_system.py b/backend/test_system.py deleted file mode 100644 index 3adff2d..0000000 --- a/backend/test_system.py +++ /dev/null @@ -1,232 +0,0 @@ -import requests -import json -import time -from typing import Dict, Any, List - -class SystemTester: - def __init__(self, base_url: str = "http://localhost:8001/api/v1"): - self.base_url = base_url - self.session = requests.Session() - self.token = None - self.user_id = None - - def login(self, username: str = "admin", password: str = "admin123") -> bool: - """登录系统""" - try: - response = self.session.post( - f"{self.base_url}/users/login", - json={"username": username, "password": password} - ) - if response.status_code == 200: - data = response.json() - self.token = data.get("access_token") - self.user_id = data.get("user_id") - self.session.headers.update({"Authorization": f"Bearer {self.token}"}) - print(f"✓ 登录成功: {username}") - return True - else: - print(f"✗ 登录失败: {response.status_code} - {response.text}") - return False - except Exception as e: - print(f"✗ 登录异常: {str(e)}") - return False - - def test_config_endpoints(self) -> bool: - """测试配置管理API""" - print("\n=== 测试配置管理API ===") - success = True - - try: - # 测试获取所有配置 - response = self.session.get(f"{self.base_url}/config/") - if response.status_code == 200: - print("✓ 获取所有配置成功") - configs = response.json().get("configs", []) - print(f" 当前配置数量: {len(configs)}") - else: - print(f"✗ 获取所有配置失败: {response.status_code}") - success = False - - # 测试添加配置 - test_config = { - "value": "test_value_123", - "type": "system", - "service_id": None, - "description": "测试配置" - } - response = self.session.post(f"{self.base_url}/config/test_config_key", json=test_config) - if response.status_code == 200: - print("✓ 添加配置成功") - else: - print(f"✗ 添加配置失败: {response.status_code} - {response.text}") - success = False - - # 测试获取单个配置 - response = self.session.get(f"{self.base_url}/config/test_config_key") - if response.status_code == 200: - print("✓ 获取单个配置成功") - config_data = response.json() - print(f" 配置值: {config_data.get('value')}") - else: - print(f"✗ 获取单个配置失败: {response.status_code}") - success = False - - # 测试删除配置 - response = self.session.delete(f"{self.base_url}/config/test_config_key") - if response.status_code == 200: - print("✓ 删除配置成功") - else: - print(f"✗ 删除配置失败: {response.status_code}") - success = False - - return success - except Exception as e: - print(f"✗ 配置管理API测试异常: {str(e)}") - return False - - def test_comparison_endpoints(self) -> bool: - """测试算法比较API""" - print("\n=== 测试算法比较API ===") - success = True - - try: - # 测试算法比较(使用模拟数据) - test_data = { - "input_data": {"text": "这是一段测试文本"}, - "algorithm_configs": [ - { - "algorithm_id": "test_algo_1", - "algorithm_name": "测试算法1", - "version": "1.0.0", - "config": "{}" - }, - { - "algorithm_id": "test_algo_2", - "algorithm_name": "测试算法2", - "version": "1.0.0", - "config": "{}" - } - ] - } - - response = self.session.post(f"{self.base_url}/comparison/compare-algorithms", json=test_data) - if response.status_code == 200: - print("✓ 算法比较API调用成功") - result = response.json() - print(f" 比较状态: {result.get('success')}") - if result.get('results'): - print(f" 结果数量: {len(result.get('results'))}") - else: - print(f"✗ 算法比较失败: {response.status_code} - {response.text}") - success = False - - return success - except Exception as e: - print(f"✗ 算法比较API测试异常: {str(e)}") - return False - - def test_existing_endpoints(self) -> bool: - """测试现有API端点""" - print("\n=== 测试现有API端点 ===") - success = True - - try: - # 测试健康检查 - response = self.session.get(f"{self.base_url.replace('/api/v1', '')}/health") - if response.status_code == 200: - print("✓ 健康检查通过") - else: - print(f"✗ 健康检查失败: {response.status_code}") - success = False - - # 测试获取当前用户 - response = self.session.get(f"{self.base_url}/users/me") - if response.status_code == 200: - print("✓ 获取当前用户成功") - user_data = response.json() - print(f" 用户名: {user_data.get('username')}") - else: - print(f"✗ 获取当前用户失败: {response.status_code}") - success = False - - # 测试获取算法列表 - response = self.session.get(f"{self.base_url}/algorithms/") - if response.status_code == 200: - print("✓ 获取算法列表成功") - algorithms = response.json() - print(f" 算法数量: {len(algorithms) if isinstance(algorithms, list) else 0}") - else: - print(f"✗ 获取算法列表失败: {response.status_code}") - success = False - - # 测试获取服务列表 - response = self.session.get(f"{self.base_url}/services") - if response.status_code == 200: - print("✓ 获取服务列表成功") - services = response.json() - print(f" 服务数量: {len(services) if isinstance(services, list) else 0}") - else: - print(f"✗ 获取服务列表失败: {response.status_code}") - success = False - - return success - except Exception as e: - print(f"✗ 现有API端点测试异常: {str(e)}") - return False - - def run_all_tests(self) -> Dict[str, bool]: - """运行所有测试""" - print("=" * 50) - print("开始系统自动化测试") - print("=" * 50) - - results = {} - - # 登录 - if not self.login(): - print("\n✗ 登录失败,无法继续测试") - return {"login": False} - - results["login"] = True - - # 测试现有端点 - results["existing_endpoints"] = self.test_existing_endpoints() - - # 测试配置管理API - results["config_endpoints"] = self.test_config_endpoints() - - # 测试算法比较API - results["comparison_endpoints"] = self.test_comparison_endpoints() - - # 输出测试结果 - print("\n" + "=" * 50) - print("测试结果汇总") - print("=" * 50) - - for test_name, result in results.items(): - status = "✓ 通过" if result else "✗ 失败" - print(f"{test_name}: {status}") - - total_tests = len(results) - passed_tests = sum(1 for result in results.values() if result) - - print(f"\n总计: {passed_tests}/{total_tests} 测试通过") - - if passed_tests == total_tests: - print("🎉 所有测试通过!") - else: - print("⚠️ 部分测试失败,请检查日志") - - return results - -def main(): - """主函数""" - tester = SystemTester() - results = tester.run_all_tests() - - # 返回退出码 - exit_code = 0 if all(results.values()) else 1 - return exit_code - -if __name__ == "__main__": - exit(main()) \ No newline at end of file diff --git a/backend/update_db.py b/backend/update_db.py deleted file mode 100644 index 7ac3886..0000000 --- a/backend/update_db.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env python3 -""" -更新数据库结构,删除api_keys表,添加roles表,修改users表 -""" - -from sqlalchemy.orm import Session -from sqlalchemy import text -from app.models.database import engine, Base, SessionLocal -from app.models.models import User, Role -from app.services.user import UserService - - -def update_db(): - """更新数据库结构""" - db = SessionLocal() - - try: - # 1. 删除api_keys表 - print("删除api_keys表...") - try: - db.execute(text("DROP TABLE IF EXISTS api_keys CASCADE")) - db.commit() - print("✅ api_keys表删除成功") - except Exception as e: - print(f"⚠️ 删除api_keys表时出错: {e}") - db.rollback() - - # 2. 创建roles表 - print("\n创建roles表...") - try: - # 直接执行SQL创建表,避免依赖模型的顺序 - db.execute(text(""" - CREATE TABLE IF NOT EXISTS roles ( - id VARCHAR PRIMARY KEY, - name VARCHAR UNIQUE NOT NULL, - description TEXT DEFAULT '', - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() - ) - """)) - db.commit() - print("✅ roles表创建成功") - except Exception as e: - print(f"⚠️ 创建roles表时出错: {e}") - db.rollback() - - # 3. 修改users表,添加role_id字段,删除role字段 - print("\n修改users表...") - try: - # 检查是否存在role_id字段 - result = db.execute(text("SELECT column_name FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'role_id'")).fetchone() - - if not result: - # 添加role_id字段 - db.execute(text("ALTER TABLE users ADD COLUMN role_id VARCHAR")) - print("✅ 添加role_id字段成功") - - # 初始化默认角色 - UserService.init_default_roles(db) - print("✅ 默认角色初始化成功") - - # 获取默认角色 - admin_role = UserService.get_role_by_name(db, "admin") - user_role = UserService.get_role_by_name(db, "user") - - if admin_role and user_role: - # 更新现有用户的role_id字段 - db.execute(text(f"UPDATE users SET role_id = CASE WHEN role = 'admin' THEN '{admin_role.id}' ELSE '{user_role.id}' END")) - print("✅ 更新用户role_id字段成功") - - # 删除role字段 - result = db.execute(text("SELECT column_name FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'role'")).fetchone() - if result: - db.execute(text("ALTER TABLE users DROP COLUMN role")) - print("✅ 删除role字段成功") - - # 添加外键约束 - db.execute(text("ALTER TABLE users ADD CONSTRAINT fk_users_role FOREIGN KEY (role_id) REFERENCES roles(id)")) - print("✅ 添加外键约束成功") - - db.commit() - except Exception as e: - print(f"⚠️ 修改users表时出错: {e}") - db.rollback() - - # 4. 检查并创建默认管理员账号 - print("\n检查默认管理员账号...") - try: - # 检查是否已存在管理员账号 - admin_user = db.query(User).filter(User.username == "admin").first() - - if not admin_user: - # 获取默认管理员角色 - admin_role = UserService.get_role_by_name(db, "admin") - if admin_role: - # 创建默认管理员账号 - admin_user = User( - id="user-admin", - username="admin", - email="admin@example.com", - password_hash=UserService.get_password_hash("admin123"), - role_id=admin_role.id, - status="active" - ) - db.add(admin_user) - db.commit() - db.refresh(admin_user) - print("✅ 默认管理员账号创建成功") - print(f"用户名: admin") - print(f"密码: admin123") - else: - print("❌ 无法创建管理员账号,因为admin角色不存在") - else: - print("⚠️ 管理员账号已存在") - - except Exception as e: - print(f"⚠️ 检查管理员账号时出错: {e}") - db.rollback() - - print("\n✅ 数据库结构更新完成") - - finally: - db.close() - - -if __name__ == "__main__": - update_db() diff --git a/frontend/src/views/AlgorithmsView.vue b/frontend/src/views/AlgorithmsView.vue index 0196832..5aa4183 100644 --- a/frontend/src/views/AlgorithmsView.vue +++ b/frontend/src/views/AlgorithmsView.vue @@ -1,185 +1,358 @@ + +@media (max-width: 1400px) { + .left-sidebar { + width: 200px; + } + + .right-sidebar { + width: 300px; + } +} + +/* 滚动条样式 */ +::-webkit-scrollbar { + width: 6px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; +} + +::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: #a8a8a8; +} + \ No newline at end of file