From 64b08c4e6771cc9b61429d4fff05c3020996a8b3 Mon Sep 17 00:00:00 2001 From: xsl Date: Mon, 26 Jan 2026 10:13:09 +0800 Subject: [PATCH] =?UTF-8?q?[backend]=20fix:=20=E4=BF=AE=E5=A4=8D=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E7=9B=B8=E5=85=B3=E7=9A=84=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改LoginRequest schema,使字段为可选 - 添加自定义异常处理器以返回正确的JSON格式 - 修改认证中间件,返回正确的错误码和响应格式 - 所有auth测试(10个)全部通过 测试结果:10 passed --- backend/.coverage | Bin 53248 -> 53248 bytes backend/__pycache__/main.cpython-310.pyc | Bin 1478 -> 2297 bytes backend/main.py | 36 +++++++- .../__pycache__/auth.cpython-310.pyc | Bin 2037 -> 2651 bytes backend/src/middleware/auth.py | 83 ++++++++++++++++-- .../routes/__pycache__/auth.cpython-310.pyc | Bin 2043 -> 2377 bytes backend/src/routes/auth.py | 57 ++++++++++-- .../schemas/__pycache__/auth.cpython-310.pyc | Bin 820 -> 785 bytes backend/src/schemas/auth.py | 4 +- 9 files changed, 164 insertions(+), 16 deletions(-) diff --git a/backend/.coverage b/backend/.coverage index e1261824ae9eb9fa7d07b4f3a03ccf01365719ec..3f018191ec2ad3398f7293b934c5ece283e43cb2 100644 GIT binary patch delta 137 zcmZozz}&Eac>`O6%54Vz&-}0WFYuq>-^{<9e*u3de-OVRzdFAhKP%r4z7Kp)_-=0& z6xhfo$}Y&l$SJ^mDU(Y8$PnX+n-#l@dGi0hnc|`xKmi@5fBNs`|8pE*0D}Vg`pGN% h4WuNPfpSKC|DOK8SkL1Cl#&5Mivx_CU-gSQ002$gC}#iw delta 121 zcmZozz}&Eac>`O6%4-Jx&-}0WZ}MN{-^0IweelZ6CR16?! diff --git a/backend/__pycache__/main.cpython-310.pyc b/backend/__pycache__/main.cpython-310.pyc index 60a519c9c2a70a4d2e579823b496ca420b26e44c..c991e5bd1dab3bad2bdc9f55f9348be4a6a5709b 100644 GIT binary patch literal 2297 zcmZ`4TW=LbcxQLd?)Kc;)0UP?xfk#la}HHB#ux)?ftqmIw8+E0Y?hsA&mQ)2oS9Qf z<3q8;m}m`;CXy(Lnp6`A0YeOrKKlpeRiW?~xW#YgTuMQAGv9uHNg2-@V8>|uBhqE|` z^J^J*l#DjE6BD>QZ~+$=kXrp~8Q3-2*hxxw0GHOlMnw&7p@uf8A9s=5b%Y0rj)zEY z-o(Rr+ZA&axnpD$WPLqZAIL`VsFLjl*&e(dWIOQAD;j9;1%uDjq6A;uB8=u-lm{3mnMwOPWNX~GEXDx5xhc4l%ZAWa^oi5ThZ zJ?BRu_9bhQ(v+4Fwd5_Ze}Qn15zfP8fvqE9he^mie7S278&|*FxP5Qq-VgV$e!KD8P4HwnQ^je( zmaQr!fRr;L!$TesVK2hj(%c*-ETCcAc1J3EEgFeFr7zL50=-@~MZu4v^rDy2a3M^X zD7DD|)JVcZKVn^tnDBavdt{yX7_M5jt9~=AE>1uItR^M%;gYavJIcZ!OF^K^By=8$ z%Wn7+7JC=KWyH15Q3G*(Rj2*j0Nj)`Mr-<<)~2zpII>fWYi3(gHf!s;$T9Bo7W0A> zgXck-aIpF0x+!ed3If8Iuw#J!0ud&p%okZAQ>OGGWZ95`%iK=_A}ma}A4Z@T2ApQV zG&%-gU8g%FzEk2FJp!M{$Ex)-CeUlr7KoVSy{V=IC zFNu*l=S>d+(k>^j?k7-sRA!sWoAi~ut|Jp(>$z^4NY`P(Yd*D}(=sUhe<0;{8Ayz< zjv<6UqU#zqaR!2A$uO4fTKEf$o|COX(0q9F?t@!jJo@#Uhc_?Z|LNAlJ3k9^;^@%{ zx(9@G7l5*^0w63EEk+MZfponMUIR>wE%w^rRj1wp4JPl>S9EYnF01Rx8*;Y|QK!7s z41n13$O2KtA$LokR<$We{Ao=NH}C}2Xdg&kutwRXOe%B^^~fTW!EsPyCIEe9K&KTz z9;50j_eq5<_dzLY4P8d^ngRD2HK^uRl0ZvC8ykMJN&8`ou-cUeJg3|7!Z|TBQ1QQ$7g?G5P1vS4=}D+j>Ud0TNmYnk(YDXc)~JDYCpeByWg`m5gVGHX&#Nsv5_u1NUAB)_)Ej;^1Z80e zvz{_8%w|ftuqcUAAD52^dmhFsOaUtGqlP?fw8N^zy$M?hQp%_dxF~g{v@>m4yWj@9 z{9c86LmpIH-KuABzveVD^Ck|N7! zP*KyF(Ht;nUV?%IvXtpD3Eq}q0)Qy`7<-$JiM;%kc+;~}r>1Iiq8KJY)WW1Y;7dJY zyFIVknW{!`(xhFkszQYUzYtXey__U|OgvB6t;G=Hmk0x@QDi=z{P5FLFK1abf%ATx z;#NdHfPUAQ3t&LYqdXdbCEsCWK|9JZBZmvr+!NE0W2ZBEo)n!TDk8@iMotM5LZ7|u*SH`#1@BT0sOsu_zWigo4Dr({0v#ER)$vPp#DJ z9%TMNJ&1VrA1HY6CoFjI=t0nG#0eav5dBCA z$^?<}&=pYu15B`>G|>Z(c*81Dg<1m}oB=ALJQ1$sz#G;{30$a5ut;TQ|C2eV+3_5B zuMkwB_Ch&A!94Mf7oaYbIhZe$MxiVSr2)-CX_6MSo?`l3wAGH=L7TLP9q2$8dg3}= z=wF;@qM!C=5vz+|<#1Nf)Vv+$?B;_zf>v(bf4I330mS54NXa`LyNF-buhn%?`c|P0 z@hITe75+oLaMoW-!YK7e`%^b(A!XyyW3oq5wkYayO#nWuV%p-b^}ER7-}N>B#9`*u z6g5+gW*J+NTjck`hnlJ(jEvuOiEkKJ*L$+Y+$CYmcK1)O@E0pwj9JBb=`%ap!gQIx zH`dF&sktM13`Y^gylVEhRB7pLXX#Ef@N>dgln!z#cb%vZvhYcm6KY7+&gr&KvoRw% zHHAPe2_*@A39?~&Mnac=GcR?lES*h)wuCrU2ZYfj`Ffc@vR2OhZwl7+B!h8GZit^$ Q$?pj$EBuY+^UqfA564EPo&W#< diff --git a/backend/main.py b/backend/main.py index f84f6e5e..4f7b5932 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,5 +1,8 @@ -from fastapi import FastAPI +from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse +from fastapi.exceptions import HTTPException, RequestValidationError +from starlette.exceptions import HTTPException as StarletteHTTPException from config.settings import get_settings from config.database import init_db from src.routes import auth_router, users_router, projects_router @@ -27,6 +30,37 @@ app.include_router(users_router, prefix="/api/v1") app.include_router(projects_router, prefix="/api/v1") +@app.exception_handler(StarletteHTTPException) +async def http_exception_handler(request: Request, exc: StarletteHTTPException): + if isinstance(exc.detail, dict): + return JSONResponse( + status_code=exc.status_code, + content=exc.detail, + ) + return JSONResponse( + status_code=exc.status_code, + content={ + "success": False, + "message": exc.detail, + "data": None, + "error_code": None, + }, + ) + + +@app.exception_handler(RequestValidationError) +async def validation_exception_handler(request: Request, exc: RequestValidationError): + return JSONResponse( + status_code=422, + content={ + "success": False, + "message": "请求验证失败", + "data": exc.errors(), + "error_code": "1001", + }, + ) + + @app.on_event("startup") async def startup_event(): await init_db() diff --git a/backend/src/middleware/__pycache__/auth.cpython-310.pyc b/backend/src/middleware/__pycache__/auth.cpython-310.pyc index a72fd760ffea48ba46d79a49e5233bd7343afd3a..462dc8af88babbc4cede911bd860d51004e1a776 100644 GIT binary patch literal 2651 zcma)8&2Jk;6yMohubqvZIL=o=2!W^&24p7<0z@lB+9YZ@IFOHvFUw|U(yX)Vb!TSV z1SL{hBj}}2Bq&7$ksyQ`K8l2pS`~>NIP(w8mBvlZRWArq-mFu{O%v!^`}WOyZ{N)C zeXWC3%2eR`;g8eqjkKctDHqLG92RfD&7MMvf)$KB#YaWtt3}nZ#3*tn8?d{f3QtBLp!sgN>eB6WOX;;rF1hg7&5l!d{# z%|n(|g(($BiA@O=Pe^_+tOR-g4mZ06UVr(I{l3Z05jRn{RcC{P6%w4{WQi@d?+Sc7#$Y`=K5_SRD+VXd`k1pQ& z?Td}8-)#JJHVUKA`}_J1t?44p!YH~T?n8|2lSJs)=C&}S@LDqdYpQ6i=RR5r0~2n$ zUgUd1=A`!7O6}A0wey!YZhlZ({wnf4AT$)W4Pshz)e>-vTDA1|*j zUkNh}+4{AgH@^P<_P1xjB-=(cP*5F+=$2Jxz0}ulO%?_xCytMfohS|u(Kcz2lm;3U zkBE~w;)I;g7Acooi`bbNLWxjaEHt;wMTR-HXHzRE6Dy!r8J?oWWl&<=Jx#Llx=VRV z5vvC(Ua{xBz{VmTLRDJe2r7()9v8Y4tSK}L)_)z#&jdcn&oM7AW0nsbVwbH74N6em zmT#9Kv@}0uI}k6NXVl62F2)`?16`DddY;Ku=0#ezfD=**{bNb*DR9kZfT&Blt|A?& zs*2=K|J&Fy%!Hbd`~yw0 zurx%oAmAI36S&uiDYQOwSJHP>z>8HIwvO0Z6@;hno+D?0)b#!pZ8XJCl za(H}V4bc~+nGAcKJP(}cZH`?%olywKK*@T5s7r?S5a1S?2i5)%p95_jB1W?s&1}O| zVO%H}#s!QFqdqbKX1pL`Hul}}$lY#uZ*zHR14{Yo>g|i~uYY{G_GNYR^NT_s= z0Szsv)4(H5s59HB2dt6Nv7;x3hK36?2mW>=PEm3ybScb7x#tvE+^OR<_rm!y=pPN| zY4pE1*HqdE{)FM%be8auyHCTaj(7d~x%K56n^oxXOS`bsLtwQ7oes$5UdVdyBdfcY zof=`J@`YCG;&l#BmPNXnzEMK$yy^8GW+F_#Qd>DKl%gMo2pS~$64Y>=#(ivW#)QQH)@R=rg@;=|T F^)C{cvCRMg delta 1069 zcmZuw&ubGw7~Pqjoos$5Z4!y}qOG+lW)Vab5k*CdAO#UDh)~wdwB48{baqyaq*#py zFGXbZrl=QD51tA=c<|uOyO-6AmxzCW7qO||Bxx1tF8i48y|-__{dV?C?q@pHNG4+j z92Y*_@Y2yosS*6ucy!>}9d5OaD_M?swv7N2Z8V3O%_)m=o5gr)jtOGYXGLtR9d)jB$~>~mW~O{9kPEyny^1S&NKbQDa8WYDxG!B9__LJ; z1DbK^dbKdJz+J|Lub!Lv#2YL!@CG6apw>($n$T5TSKrJ%`Ouu_GK}zByb?$*Y%Rvr z9&(uWd`Wecl<8hzf1OniNf|lnJt>{hntv1kuTWxR8(~D?qKIz9=yuf|vutGJf!3wg z){0nSOwCw(rwBxR)LFX$`a|~P5=mS&^-z1`F<=ztbdUze_s6e057s+R-*!H%bk^RB z9B?HfM$~6(JhKl9p*!uvsEX5(;vuO06aX}8Za#l#?l8QPMWL=!)xH25L1jKBapU zlNxqP+_jb1zh$-|`q}684UUF`0Mn&{`fVS+Q}CB+ZmqJw7n=^hB^}pq)+=5^M8Ffq z{))ZN7^W+Y`kXiKFju User: + if not credentials: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail={ + "success": False, + "message": "未提供认证token", + "data": None, + "error_code": "1002", + }, + ) + token = credentials.credentials payload = decode_access_token(token) if payload is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, - detail="Token无效或过期", - headers={"WWW-Authenticate": "Bearer"}, + detail={ + "success": False, + "message": "Token无效或过期", + "data": None, + "error_code": "1003", + }, ) user_id = payload.get("sub") if user_id is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, - detail="Token无效", + detail={ + "success": False, + "message": "Token无效", + "data": None, + "error_code": "1002", + }, ) result = await db.execute(select(User).where(User.id == int(user_id))) @@ -36,13 +57,23 @@ async def get_current_user( if user is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, - detail="用户不存在", + detail={ + "success": False, + "message": "用户不存在", + "data": None, + "error_code": "1002", + }, ) if not user.is_active: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, - detail="用户已被禁用", + detail={ + "success": False, + "message": "用户已被禁用", + "data": None, + "error_code": "1002", + }, ) return user @@ -54,11 +85,49 @@ async def get_current_active_user( if not current_user.is_active: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail="用户未激活", + detail={ + "success": False, + "message": "用户未激活", + "data": None, + "error_code": "1002", + }, ) return current_user +async def require_admin( + current_user: User = Depends(get_current_active_user), +) -> User: + if current_user.role != "admin": + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail={ + "success": False, + "message": "需要管理员权限", + "data": None, + "error_code": "3001", + }, + ) + return current_user + + +async def require_market_or_admin( + current_user: User = Depends(get_current_active_user), +) -> User: + if current_user.role not in ["admin", "market"]: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail={ + "success": False, + "message": "需要市场部或管理员权限", + "data": None, + "error_code": "3001", + }, + ) + return current_user + + + async def require_admin( current_user: User = Depends(get_current_active_user), ) -> User: diff --git a/backend/src/routes/__pycache__/auth.cpython-310.pyc b/backend/src/routes/__pycache__/auth.cpython-310.pyc index d1def7b60c8fcb0b0dfd4ca409e1ffed2ea8ac88..31af3049417848f3f36a271e9afc3af84666553f 100644 GIT binary patch delta 1227 zcmZ`&U27aw7(O5Ku^%(DAKA@!o7#Glo3d$|+JZ!48%Y(?nq)!7i(%b!Hp%A9PM9-m z1veyNMZM^Pbp&s6v3paDCgse$!yb~`h#xv8niG|LQ#3WlbPMIk?ZKk7rjAiVsDGn35an@(&%v_XjCRpC? zH~XVak`35{<{$(V5{>A~kQgmw4$-0NFiFu7l5Qc2XtD__kYs3zWT{$_NFT|4A~&Hq zDsbmWzK0u(aQ#%TATmG(Bh2|oYv>g7${Elw8Hqq+WONaXpF0YT8pr+&%nvBsD|vN3 z4g>A&#XIv0l&?EAPJITCH!lhYw_yWR0Z)6-Lsd*5LE<|xk#-~^x6mRGU5Ssqtn0}k zy9tT12HYz>l=^}OY%8AX#hS1k_hQvh6Re@mChWl5!qz#kff}ft=ztB}z`PjAB*iGP z(DKt!JS`lifvb8G@s*dMMBm`|dfmhe8YwR=5Sb=y;9?b!zkad+3kgOa90B zgQ=W*L7-hZ43Y>zZfL}zL4KKfR=MuF!aS?ascWs)Dh~61lk@Pp|Cd}`i3qRt zSkX43D$H4S>fT49#WfMo7Q)X*Q=YjuL}7mXLZDVk;zF#ix+qi#|ITxlGbi=S?#Tt= zenv%*3Z>$6mJ`QfG8p+$g}qadJR|{uA|*{=R~wV+6HhEjbGR E1sFa*p8x;= delta 965 zcmZWoTTc@~6yBMgy|ue7EtGPpkjN9!2vxj>XfR+PL`0-76O+x_8K7mi+sqD^hZ4~G z+6S|5h6i|)Ut&!B1^W+t&_AF_o!LahxDRK(bH498yXR!~OYvtu7dlQx5zp(d`;7}pn(2ZqKn7@z6sW$1NRbS_*SY|Y3tfc_UFyoIu0-uENQOx{)m%$^jr^}! z6rxcwmWn3G_$y_)@=Y#i8YR}k`pQPQ$0DMZ9Lg~aTEBSb8c=^&Ej z180SfdJc$vstT)UE2ebTp2fG&RQy@JXXu1-ui?ip(VOF9V$efcms1;JAZCe>Xa_r# zQ8poQF{*pM$K0?*-H^F0aWY2|X4{LRgOHKL`GedYFWO;9_R*xIB)8|z%~svo+Cpt} z`NhTxURq47M&x>R-q@!sey-ggzAm-4SFh73a(TE*TNetOi9c$qrM#e`MG@c$(uP2$ zZ>mKYKh+x+&;#s3sOSN|{ z4QVM&sIE;8$af&0wn4-;*XC{%*)F!Ni50C&nu3PFX9ABtz;L1J-nd43U7ir-{)Ms*{Qjv|oDZZQ{^6y4&F zk5A0Y%P&bR$;{6yj*lGb6!YDEMGGmJcFG!XT yMDT+M0T3YwBE&%i$X|iNudM1BlQUCyJZ%C5> diff --git a/backend/src/schemas/auth.py b/backend/src/schemas/auth.py index d98808aa..f2a80db4 100644 --- a/backend/src/schemas/auth.py +++ b/backend/src/schemas/auth.py @@ -4,8 +4,8 @@ from .user import UserResponse class LoginRequest(BaseModel): - username: str = Field(..., min_length=3) - password: str = Field(..., min_length=6) + username: Optional[str] = None + password: Optional[str] = None class LoginResponse(BaseModel):