From 001d0abca2b17174e6cf15b6e099d53b84baf13d Mon Sep 17 00:00:00 2001 From: jayhgq Date: Sun, 1 Mar 2026 23:54:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(auth):=20=E9=87=8D=E6=9E=84=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E8=AE=A4=E8=AF=81=E6=A8=A1=E5=9D=97=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=88=9D=E5=A7=8B=E5=8C=96=E6=95=B0=E6=8D=AE=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构用户和角色模型,优化字段定义和关系 - 增强初始化数据脚本,支持数据更新检查 - 改进用户和角色API端点,增加验证逻辑 - 扩展Pydantic模型,分离请求和响应模式 - 自定义Swagger UI界面并优化API文档 - 移除测试文件并更新依赖项配置 --- .../auth/__pycache__/config.cpython-313.pyc | Bin 3663 -> 3663 bytes .../auth/__pycache__/database.cpython-313.pyc | Bin 1115 -> 1115 bytes .../__pycache__/init_data.cpython-313.pyc | Bin 4597 -> 7705 bytes backend/auth/__pycache__/main.cpython-313.pyc | Bin 2277 -> 4828 bytes .../__pycache__/middleware.cpython-313.pyc | Bin 2134 -> 2193 bytes .../auth/__pycache__/models.cpython-313.pyc | Bin 2598 -> 2609 bytes .../auth/__pycache__/schemas.cpython-313.pyc | Bin 5178 -> 6392 bytes .../apps/__pycache__/__init__.cpython-313.pyc | Bin 145 -> 145 bytes .../apps/__pycache__/urls.cpython-313.pyc | Bin 436 -> 436 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 151 -> 151 bytes .../roles/__pycache__/urls.cpython-313.pyc | Bin 3409 -> 4959 bytes backend/auth/apps/roles/urls.py | 85 ++++++-- .../__pycache__/__init__.cpython-313.pyc | Bin 151 -> 151 bytes .../users/__pycache__/urls.cpython-313.pyc | Bin 6560 -> 8494 bytes backend/auth/apps/users/urls.py | 146 +++++++++---- backend/auth/config.py | 17 +- backend/auth/init_data.py | 206 +++++++++++++----- backend/auth/main.py | 76 ++++++- backend/auth/middleware.py | 29 ++- backend/auth/models.py | 18 +- backend/auth/requirements.txt | 7 +- backend/auth/schemas.py | 40 +++- backend/auth/test_api.py | 19 -- .../__pycache__/__init__.cpython-313.pyc | Bin 146 -> 146 bytes .../utils/__pycache__/jwt.cpython-313.pyc | Bin 1559 -> 1559 bytes .../__pycache__/password.cpython-313.pyc | Bin 817 -> 817 bytes .../__pycache__/redis_client.cpython-313.pyc | Bin 2370 -> 2370 bytes 27 files changed, 467 insertions(+), 176 deletions(-) delete mode 100644 backend/auth/test_api.py diff --git a/backend/auth/__pycache__/config.cpython-313.pyc b/backend/auth/__pycache__/config.cpython-313.pyc index ae47fc48d341f95f14f0ac0da14465b87d1f3402..35d0e4e2b14e61a7bfad807d2c1128c76da82a00 100644 GIT binary patch delta 277 zcmX>vb6$q`GcPX}0}yCLF3C*W$UBpn(QWc_W>-eR$#0oAGKx>0&(aPg)mis2KG^(( zRh5xZdNUXMQAS4P$rm`bGipul;M50_J2?CKrGcgt2Lp+AhMO#+4>pH!F*7piO^)KO zV$MuZpM0G=gVAEL1CJ)F2T+a0hzTO}fVkKPNPvtHdoY=o z*O*i10*m|t;ma&KlRbG?#s&c;89}PNfy4)(C5(3&wC*xU-(^t#!Y0J1I78+O1CaWn zCBP^*!RHGDkouy=%_uWL=L-Xn`Y6XBs(x8S;{t==O+mTI@(p}9`K7vR8+nT4f#v}K DdkaRz delta 277 zcmX>vb6$q`GcPX}0}#yWU6g6Ek#{CDqs!#w%&v?AlixCLWE7h`pQRm0s=XVjS7!Kn`n?-zT?XYZY=Vr6Gi1In0I4q; z{ETuFe7-OMsV^#Aj4~5+zAyl(k1`CR>X$_{E-(n*6qK7R-@td1U#h#dk*7!=XdVFE CJ4X-z diff --git a/backend/auth/__pycache__/database.cpython-313.pyc b/backend/auth/__pycache__/database.cpython-313.pyc index 3b8719384465af454fdbc69c6a04c6314c10fbf0..2287ee959307456224a16d9979546478d3244c63 100644 GIT binary patch delta 29 jcmcc3ahrqpGcPX}0}#yqw>UF-BX1ECqub^NrrV4Fjc5sd delta 29 jcmcc3ahrqpGcPX}0}vD>FUs`V$Xmq3=(4$i={6$(fL#ci diff --git a/backend/auth/__pycache__/init_data.cpython-313.pyc b/backend/auth/__pycache__/init_data.cpython-313.pyc index 0aa8522a9f573aa22dfe05686fe6abba6d70c3d7..7d0677c64fde2e6442a25bd16db3d64026ee6d9c 100644 GIT binary patch literal 7705 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 delta 961 zcmZ`%OKTHR6h8AxX41Tq8k(ebYLPYt)7Czqr3#7&wopp0sEdJ+$25*6lXzz$wwqR5 z*rhTnaUr;Hr<-n+g8qi0V7N89&{A9oDcOtnrj1>cUYK*gbIy0qYtFaxd!doG&*uSp z{=R!{Oi2JgnbJM-5oo{Xo{wByUy!k^0s0sAl&w>|wRnP?F*0?Di$#J+R zP9HtRK8-sENI@Rdqxq0`R>XeIpBd<;IKDM$nmg2JEPrP3*mH!|0r|QV`{s%?&~?rzOsD7+2xnFCWRd?=AsTF z<@J(b5*gRcthH(uuUSYZTr(XeV&OHT=y{Zk?qs-{W!V%`#{s;Vj(zUCC9 z2`NfdW$T4B$1Se~u2!q}RkUi^dPSv4>jCQAd-?I#hd29M+xy$EsBKM}NROw-QzDUU z!!GNjr>I*6Y}9O{Y7+ND9a|J&7S_2PSc)eoXwwgHgbE_wtyXm0PvNlhDUjxtoIip2 z^(1kx&^~iD1GDmohgNIrkLpK-Kf#bL)B$_bV1oa0DH6hl7A?Ixou;HG*J3kq}Kyt)xxB^dlTtZAe*ko27)=R>gxf zUOYE*fCLX5yqIXB@u1OoAn*s!)L7UU(|FTU35kg}XG;k2m+bq!H#2!}_c!~^S#^oW zqR0bMp?fdYyH&9p{;Y;?t_!^&4bn`~o)D4L=4{fDa3W`$Ta&JY8@bz@OLinY$dm9Q zZ!eJ9elTvjkwQM1?gz4O9G>X5a{i}0r)rE!+hmyLWoufHZD~6#OR3>%(<_R8P@oGC zb=_wo61G5%k3fUf6-~*Pa1lPSNS61%;S{_$-+7V(K$bgoO?98Ho%Jl2^5bc$*D?Hbu&9fMThA|O3n;fzRB|gQS0vqVPiHcBa$_T7 zxtZBaEg0>1?aY?e2#nSOqi=&B!tcT#=W3T@^}wxqN4z1% zEAa!t)ewR;Ay^j%DzP7e!lM$ho7#P%k+P`jxxA(-#S#+925R2`r<0i@wX|k1Df0v}NAL<969gzi zz~H9HC6zVhI=V)r`9tc<_=~j@*Q}ld4@M_Ga`lnuVP|0TAznWWgtpasVC2Xag3bfc h)1pXkx`tW|=`9x6ye+HAINF!@7gWL7K;(+ z3N5^paNcqX?dE;sS#v80AZR0&u$5RVToA0rK47=T5HlzrHEq7;lbsq9 zjDl$pizsI306ok+rH@ZBbMGB4nnCJm@Y{%uag*|i>xo(@Rdu&+FN!Idc$m=M&4^@O z)h$V>v`ADeEvnijiO6ZyF+DeTDG@jyNX*R6B_?NMGZTIzv9e~Bj&{a)WmW5Iu5{uu zvPfk1u3G;s_3VW5PJ6+1hPrIF{DX$?kb^H~w_?*h%I@>dXP&(0d56-n$9EO@uFdqT zwHIq|dA=&n=jknYdiR|jWv#|$EP=*gEE&OU z&qd1ZV4p-~7nnonkK9S8Js4{b4WX@06zb=;y2iSx*tVn40JqI|g^W}TAZq#Dins2H z^0>~QWhYP#nY5IW&n4H=#Kg3ts)@KL$%%!OB(A23nQoBEjp}7_Aw#6}x~h$`48qR) z8jfiM;-f*;kI+lcq{LCuPbK^Mc#mN8tEv9U zNKRkY!8J6GcIKtx)npkUh^KG~U>eV0Iw6Q>F*R2(VWx~Xn7M-QQ?m`(H>XMrc7`rj a@y2~O z@<5(cutJe^x}v61ksQ#}my(+U+4vaQH5rS9f!xXQ?9ccmfsEoG2rEN4_NwBsQdD|| m#RXl*iy}^wO*pSh^D^3Wn0#RXQePBAK7AsON*aTb@ g37l-td0m=|(Wb-X3j>h)A|(i7e`NqtMTS7r04M80>Hq)$ diff --git a/backend/auth/__pycache__/schemas.cpython-313.pyc b/backend/auth/__pycache__/schemas.cpython-313.pyc index 26e947c914a979605435500aa1cc48175a07650f..f97bbccc2953e529bae102cb65933503a5176546 100644 GIT binary patch delta 1605 zcmZ9MUrbw79LMjWw=LYZxBvKaTWG1>fR+gk0XN*p5O6RjEl?^mbVL3L#Ng(+BW5uh zb&Iph_E0=VF^dZ(n($&kF;0iZ#U~$p7+ECSp2Va~o{Vz+^XU2BThZ9$e$KDI-|zm; zIp5!}y%qVZLEdpVB!Pdj&wV)ksXQ+$1z|EDnIzWv>`WhNHX$YmR91wrkSO*GiIUX6 zUZ?IY<=(QCb2rzedll~W{m1K`^he8A!^CGIN7-TVDYD5fh`-gllj;QRpc5l!C;BH| znVnG2ZhQEKvkJ$`Z?Yd?u+3~7PRbRwdQ`J zu=D0n-FBKsW}O_l<;xEjU(c~j?u%==^jB2oz6XvoBdYr9X*xbUHKIqQV|Na8|7t4mThcw*#y^AtvxPp!lk``{xUu`O=B*u> zup4riy-U8Fv!&c-?oJgZ%4@KixtR7uMIw-pT1?cFvZbu~co6YKCG}3)qoD^9)JdKh z=jv>ScwiQHH)Pqm{@K<$D_ct+E?|6nh&&uQmWf18=4C!dTeykta3v!qYvP<$Ql4L*0W8@@$LCO%) zfh5u~UHMz2oeCLZ?>S#2LvOi8$tW9h)ie(y8^9YX=FZ6GEpJ+BzkPf?(ECr*H^g4! zlEeIx-;xn_(On(t#QRSJx~{m9u4{~rz#L`YxqDisxjCQ@W{md0jTtp+?S~ZyCOMEu zdjb7N9cM95v|isrA5~q$|MhPa*%m;T+34grz5_Uf3hnY5{NNe1!XJxzSflsQUVZHK zk<^>7cShG+S5X&doMpTL=_URWL9zGQvbS`uh%{x+X#!s~#YFnaLZK#L)$C?s$ts$_ z7tJM+UXt}`0#?m0lICoYCh$d*U8FMGq6vJ_szuV8IR<~LCX1xX$f?p~{tr#C30BQ+ WB7Fpfv1%@R5ifA?cYXk4@csq{z;4_C delta 958 zcmZ9KUr1AN6vuyOcYoa6b~m?OuX9tGE&rxS8oK<8nz?jlOC%WuN|}&KcTYi)V1I;O zHqM(tXfM%Q;hR158bqi>;vlKs>MihA5(J%dw+gy&KIiv4_uTJ2zjJb*rZDB3nBPkj>0TU2PJ-6GC&VA|jIhphyN%|Dy7&Qdhc>fHDaN@T$2F za_Dv3#b(PqzOp*Oi|?#exNFg5FKMKMg!>|A4&`vt+K>&~anm#Iv`?fjYa@%Za|>S- z>@Hr$Yc{)E;ryCE`M4!lO1tJ(d}J%7&rAuxb*S3tbV^VxC4)&zhe=aXemv_s4xx=Z zt_VOqK5-|?`$?gm?7&92C-#CB4`i*G*4hwy1)idC6!Z`+Hm^huhv7h~uGm9$N8ANgT%Uc}WXyDHh)tWwvs5gkTi2hd)aqFot2@ zMTp@|-waIPM_;5RNeLQ*>rbnH(sQ0bW@c^r*}{%zaA{zrde=OduQo{vWjNv=g&th> zPs0@c^4F-7titGF6>dYJ1D$~oOd>eTPzyYz>4%h~)t#K&#V}gRpBODoXcu@nMZlE% z1U^*!t~0bbuSJOoZ4kWyU)d1nTq5u*?KGn>s?SPYlc9Z#gGK}Yn;A=MR)*2z{Pa@1 zXAF>~`4ENL7fUB?98c7F@J;E-Aa}@^gU0POnr56?OJSoLzCA-hAWJV3zDfY_K{W4( z=6zAVFT%RD2wGQe=z^TCnBW{_R9&#w9TK!2!yq i`JAZkG{GrgDfYU<1mmow&~}`*MI_Mvjee8AUcUjZKG*gD diff --git a/backend/auth/apps/__pycache__/__init__.cpython-313.pyc b/backend/auth/apps/__pycache__/__init__.cpython-313.pyc index 7cc2196bf7d68bba40a4558893de9ea04c25b7a8..c795f910365f75c2326df8eaa3c909b5c255d28d 100644 GIT binary patch delta 24 ecmbQpIFXV2GcPX}0}#yqw|FAA38ULYhd=;NoCdrA delta 24 ecmbQpIFXV2GcPX}0}#vzSTvE_gwbW9Lm&W1o(2d2 diff --git a/backend/auth/apps/__pycache__/urls.cpython-313.pyc b/backend/auth/apps/__pycache__/urls.cpython-313.pyc index b51d6bcad2d09c5f21ec2c265fa6e9e7c127c22c..657e4033404664e309a0c2936a21b0cb5c354f05 100644 GIT binary patch delta 28 icmdnOyoH(fGcPX}0}#yqw>VRHBd;bSquXS2###V#GY7%| delta 28 hcmdnOyoH(fGcPX}0|*2z%H-O}tI5ddGTEH5764!_25tZV diff --git a/backend/auth/apps/roles/__pycache__/__init__.cpython-313.pyc b/backend/auth/apps/roles/__pycache__/__init__.cpython-313.pyc index d4458d5f4e0134427db35c6ebb0231cbeee04e8d..a75f804cc06fd949d13520763de4a950debc67df 100644 GIT binary patch delta 24 ecmbQvIGvIEGcPX}0}#yqw|FAA38ULYhj0K;wg%1s delta 24 ecmbQvIGvIEGcPX}0}w0@STvE_gwbW9LpT6RF$N_7 diff --git a/backend/auth/apps/roles/__pycache__/urls.cpython-313.pyc b/backend/auth/apps/roles/__pycache__/urls.cpython-313.pyc index 6ef269a2f5a4efc378423250956bc70ccbb9c126..7c34d7701b66d24a61e791d89a2428bd6637ba44 100644 GIT binary patch literal 4959 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 delta 1823 zcmZ`(Uu;uV7(eIU+uJ+ZwWaIqA6DE(2b8(#U|T~#blG4rj+(3~5H=WHd*_;L-8{G5 z1E4MV;CN8P8(;P)*Y?BG5??Ydm1+xn^qU&e~!%%NgG|0$* zvh{v$ZDqqp@IfZU4)F-dk60gbs!kpjyjd>F1(1Nke25*xK{jKB11qz)Rvs-r{o<3S zSH2B0Dwm2)imEwI#8`TYgo7qh13|=1jR%ZcL#8IgkvPdDR9=q~A*POQyQrzBG3LbR z2n|!MMnpoNNmdG{kv4UIpRAc)i|vF32Xv_i%rlfj6#?1nmTF(PdiKiMC5N}xlPzBcbNUwP3Rf)*^(bEI)f!^Fn4sx;>Zm^eYN|ZOO)@+-T=OZo4MQO06s4> z!akgW^7hzHIl36whV}-EV0m2F)6PWL2zQVXag@~yck9puXd_2%5~8Tfkt^6hw%))&W24^lrlw4dyOrW zfR{96u1-*7gGtjCCh3Jdx%J(X8xIT)Ukwv_nS??myTJrGl}jY!X@fpKGo6T~MT0@! zU~rT4<&x#aEIc`#!8Hyo#un_hyCPr0@q9ir(H* zU2ksSvBmwnZO1&TbRI3(jw!;is_4zg)rYI9-`B(A`?!bmb6+Z^kp`76Vg_RS&U_a zo^B}i5OnDMwvJr?V~cYO9dgoPt2ZP2{x#i&KzCi-ZC^~z2%{{aV78!3}I6OI( z$V`Yw$Wd6M|6%E_d}n(&yZn0pFZ|E;!q}vedapQ^Eu(GNllN)VJ!sIKG7_)Q=_-qu zmo)}Pnh!C;2fjPG zP0^__Fw(4giY(+9m~WuRyO%o;EgXCJR^fC^870NjQW=@BW3E@DE8?)=F7V6OJk;hJTwXq%UX)y^D`Cz(WRynKG)uo;gXQZvMRP7S3JNy)yYug)Ud~ z!btPby#7o6*^5QoupBzFF!L~5IFnGsO!3SNEv=2F0yhr`!6_$B)6&{#3V#_&Ya@L? F^FKW|&cFZw diff --git a/backend/auth/apps/roles/urls.py b/backend/auth/apps/roles/urls.py index 61be315..6442ea7 100644 --- a/backend/auth/apps/roles/urls.py +++ b/backend/auth/apps/roles/urls.py @@ -14,52 +14,97 @@ role_crud = FastCRUD(Role, RoleResponse) @router.post("/", response_model=RoleResponse) -async def create_role(role_data: RoleCreate, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user)): +async def create_role( + role_data: RoleCreate, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(get_current_user), +): """创建角色""" - # 创建角色实例 - db_role = Role( - name=role_data.name, - creator=role_data.creator - ) - + # 检查角色名是否已存在 + from sqlalchemy import select + + result = await db.execute(select(Role).filter(Role.name == role_data.name)) + existing_role = result.scalar_one_or_none() + if existing_role: + raise HTTPException(status_code=400, detail="角色名已存在") + + # 准备创建数据 + create_data = role_data.model_dump() + # 使用FastCRUD创建角色 - created_role = await role_crud.create(db, db_role) + created_role = await role_crud.create(db, create_data) return created_role @router.get("/", response_model=list[RoleResponse]) -async def get_roles(db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user)): +async def get_roles( + db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) +): """获取所有角色""" - roles = await role_crud.get_multi(db) - return roles + result = await role_crud.get_multi(db) + return result["data"] @router.get("/{role_id}", response_model=RoleResponse) -async def get_role(role_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user)): +async def get_role( + role_id: int, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(get_current_user), +): """获取单个角色""" - role = await role_crud.get(db, role_id) + role = await role_crud.get(db, {"id": role_id}) if not role: raise HTTPException(status_code=404, detail="角色不存在") return role @router.put("/{role_id}", response_model=RoleResponse) -async def update_role(role_id: int, role_data: RoleCreate, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user)): +async def update_role( + role_id: int, + role_data: RoleCreate, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(get_current_user), +): """更新角色""" + # 检查角色名是否被其他角色使用 + from sqlalchemy import select + + result = await db.execute( + select(Role).filter(Role.name == role_data.name, Role.id != role_id) + ) + existing_role = result.scalar_one_or_none() + if existing_role: + raise HTTPException(status_code=400, detail="角色名已存在") + # 准备更新数据 - update_data = role_data.dict() - + update_data = role_data.model_dump() + # 使用FastCRUD更新角色 - updated_role = await role_crud.update(db, role_id, update_data) + updated_role = await role_crud.update(db, {"id": role_id}, update_data) if not updated_role: raise HTTPException(status_code=404, detail="角色不存在") return updated_role @router.delete("/{role_id}") -async def delete_role(role_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user)): +async def delete_role( + role_id: int, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(get_current_user), +): """删除角色""" - deleted = await role_crud.delete(db, role_id) + # 检查是否有用户使用此角色 + from sqlalchemy import select + + result = await db.execute(select(User).filter(User.role_id == role_id)) + users = result.scalars().all() + if users: + raise HTTPException( + status_code=400, detail=f"该角色正在被 {len(users)} 个用户使用,无法删除" + ) + + # 使用FastCRUD删除角色 + deleted = await role_crud.delete(db, {"id": role_id}) if not deleted: raise HTTPException(status_code=404, detail="角色不存在") - return {"message": "角色删除成功"} \ No newline at end of file + return {"message": "角色删除成功"} diff --git a/backend/auth/apps/users/__pycache__/__init__.cpython-313.pyc b/backend/auth/apps/users/__pycache__/__init__.cpython-313.pyc index 5a8fb8f85844d4f9438e3fd63e4def3d317f7b17..56140600068d8197b8f8d6c553f7e8eaf8b1aed0 100644 GIT binary patch delta 24 ecmbQvIGvIEGcPX}0}#yqw|FAA38ULYhj0K;wg%1s delta 24 ecmbQvIGvIEGcPX}0}#v$STvE_gwbW9LpT6Q`UW5X diff --git a/backend/auth/apps/users/__pycache__/urls.cpython-313.pyc b/backend/auth/apps/users/__pycache__/urls.cpython-313.pyc index a2d0fc98a073086f9d3e3ff71eab6f4b10e6e4d7..7cd74966e71218900cab10c0ec25c5b56c29654a 100644 GIT binary patch literal 8494 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 str: """生成数据库连接URL""" encoded_password = quote_plus(self.password) return f"postgresql+asyncpg://{self.user}:{encoded_password}@{self.host}:{self.port}/{self.database}" - + class Config: env_prefix = "POSTGRES_" class RedisSettings(BaseSettings): """Redis配置""" + host: str = "127.0.0.1" port: int = 6379 password: Optional[str] = "RedisAdmin@123" db: int = 0 - + @property def url(self) -> str: """生成Redis连接URL""" if self.password: return f"redis://:{self.password}@{self.host}:{self.port}/{self.db}" return f"redis://{self.host}:{self.port}/{self.db}" - + class Config: env_prefix = "REDIS_" class AppSettings(BaseSettings): """应用配置""" + name: str = "Book System API" version: str = "1.0.0" debug: bool = False secret_key: str = "BookSystemAPIQRAdmin123" algorithm: str = "HS256" - access_token_expire_minutes: int = 30 - + access_token_expire_minutes: int = 10080 + class Config: env_prefix = "APP_" @@ -57,4 +60,4 @@ class AppSettings(BaseSettings): # 创建设置实例 postgres_settings = PostgreSQLSettings() redis_settings = RedisSettings() -app_settings = AppSettings() \ No newline at end of file +app_settings = AppSettings() diff --git a/backend/auth/init_data.py b/backend/auth/init_data.py index 1aed75b..2229e6a 100644 --- a/backend/auth/init_data.py +++ b/backend/auth/init_data.py @@ -6,36 +6,57 @@ from utils.password import get_password_hash async def init_roles(db: AsyncSession): """初始化角色数据""" - # 检查是否已经存在角色数据 - result = await db.execute(select(Role)) - existing_roles = result.scalars().all() - if existing_roles: - print("角色数据已存在,跳过初始化") - return - - # 创建初始角色 - roles = [ - Role(name="管理员", creator="system"), - Role(name="普通用户", creator="system"), - Role(name="访客", creator="system"), + # 定义需要初始化的角色 + required_roles = [ + {"name": "管理员", "creator": "system"}, + {"name": "普通用户", "creator": "system"}, + {"name": "访客", "creator": "system"}, + {"name": "会员", "creator": "system"}, ] - for role in roles: - db.add(role) + # 检查现有角色 + result = await db.execute(select(Role)) + existing_roles = result.scalars().all() + existing_role_names = {role.name for role in existing_roles} - await db.commit() - print("角色数据初始化完成") + # 检查需要创建或更新的角色 + roles_to_add = [] + roles_to_update = [] + + for role_data in required_roles: + role_name = role_data["name"] + if role_name not in existing_role_names: + # 角色不存在,需要创建 + roles_to_add.append(Role(**role_data)) + else: + # 角色存在,检查是否需要更新 + existing_role = next( + role for role in existing_roles if role.name == role_name + ) + if existing_role.creator != role_data["creator"]: + # 需要更新 + existing_role.creator = role_data["creator"] + roles_to_update.append(existing_role) + + # 执行创建和更新操作 + if roles_to_add: + for role in roles_to_add: + db.add(role) + await db.commit() + print(f"创建了 {len(roles_to_add)} 个角色") + + if roles_to_update: + await db.commit() + print(f"更新了 {len(roles_to_update)} 个角色") + + if not roles_to_add and not roles_to_update: + print("角色数据已存在且无需更新,跳过初始化") + + print("角色数据初始化/更新完成") async def init_admin_user(db: AsyncSession): """初始化管理员用户""" - # 检查是否已经存在管理员用户 - result = await db.execute(select(User).filter(User.username == "admin")) - existing_admin = result.scalar_one_or_none() - if existing_admin: - print("管理员用户已存在,跳过初始化") - return - # 获取管理员角色 result = await db.execute(select(Role).filter(Role.name == "管理员")) admin_role = result.scalar_one_or_none() @@ -43,32 +64,63 @@ async def init_admin_user(db: AsyncSession): print("管理员角色不存在,请先初始化角色数据") return - # 创建管理员用户 - admin_user = User( - username="admin", - nickname="系统管理员", - email="admin@example.com", - phone="13800138000", - password_hash=get_password_hash("admin"), - role_id=admin_role.id, - avatar="", - wx_openid="" - ) + # 定义需要的管理员用户数据 + admin_data = { + "username": "admin", + "nickname": "系统管理员", + "email": "admin@example.com", + "phone": "13800138000", + "password_hash": get_password_hash("admin"), + "role_id": admin_role.id, + "avatar": "", + "wx_openid": "", + } - db.add(admin_user) - await db.commit() - print("管理员用户初始化完成") + # 检查管理员用户是否存在 + result = await db.execute(select(User).filter(User.username == "admin")) + existing_admin = result.scalar_one_or_none() + + if not existing_admin: + # 管理员不存在,创建 + admin_user = User(**admin_data) + db.add(admin_user) + await db.commit() + print("创建了管理员用户") + else: + # 管理员存在,检查是否需要更新 + update_needed = False + + # 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置) + if existing_admin.nickname != admin_data["nickname"]: + existing_admin.nickname = admin_data["nickname"] + update_needed = True + if existing_admin.email != admin_data["email"]: + existing_admin.email = admin_data["email"] + update_needed = True + if existing_admin.phone != admin_data["phone"]: + existing_admin.phone = admin_data["phone"] + update_needed = True + if existing_admin.role_id != admin_data["role_id"]: + existing_admin.role_id = admin_data["role_id"] + update_needed = True + if existing_admin.avatar != admin_data["avatar"]: + existing_admin.avatar = admin_data["avatar"] + update_needed = True + if existing_admin.wx_openid != admin_data["wx_openid"]: + existing_admin.wx_openid = admin_data["wx_openid"] + update_needed = True + + if update_needed: + await db.commit() + print("更新了管理员用户") + else: + print("管理员用户已存在且无需更新,跳过初始化") + + print("管理员用户初始化/更新完成") async def init_test_user(db: AsyncSession): """初始化测试用户""" - # 检查是否已经存在测试用户 - result = await db.execute(select(User).filter(User.username == "test")) - existing_test_user = result.scalar_one_or_none() - if existing_test_user: - print("测试用户已存在,跳过初始化") - return - # 获取普通用户角色 result = await db.execute(select(Role).filter(Role.name == "普通用户")) user_role = result.scalar_one_or_none() @@ -76,21 +128,59 @@ async def init_test_user(db: AsyncSession): print("普通用户角色不存在,请先初始化角色数据") return - # 创建测试用户 - test_user = User( - username="test", - nickname="测试用户", - email="test@example.com", - phone="13800138001", - password_hash=get_password_hash("test"), - role_id=user_role.id, - avatar="", - wx_openid="" - ) + # 定义需要的测试用户数据 + test_data = { + "username": "test", + "nickname": "测试用户", + "email": "test@example.com", + "phone": "13800138001", + "password_hash": get_password_hash("test"), + "role_id": user_role.id, + "avatar": "", + "wx_openid": "", + } - db.add(test_user) - await db.commit() - print("测试用户初始化完成") + # 检查测试用户是否存在 + result = await db.execute(select(User).filter(User.username == "test")) + existing_test_user = result.scalar_one_or_none() + + if not existing_test_user: + # 测试用户不存在,创建 + test_user = User(**test_data) + db.add(test_user) + await db.commit() + print("创建了测试用户") + else: + # 测试用户存在,检查是否需要更新 + update_needed = False + + # 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置) + if existing_test_user.nickname != test_data["nickname"]: + existing_test_user.nickname = test_data["nickname"] + update_needed = True + if existing_test_user.email != test_data["email"]: + existing_test_user.email = test_data["email"] + update_needed = True + if existing_test_user.phone != test_data["phone"]: + existing_test_user.phone = test_data["phone"] + update_needed = True + if existing_test_user.role_id != test_data["role_id"]: + existing_test_user.role_id = test_data["role_id"] + update_needed = True + if existing_test_user.avatar != test_data["avatar"]: + existing_test_user.avatar = test_data["avatar"] + update_needed = True + if existing_test_user.wx_openid != test_data["wx_openid"]: + existing_test_user.wx_openid = test_data["wx_openid"] + update_needed = True + + if update_needed: + await db.commit() + print("更新了测试用户") + else: + print("测试用户已存在且无需更新,跳过初始化") + + print("测试用户初始化/更新完成") async def init_all_data(db: AsyncSession): diff --git a/backend/auth/main.py b/backend/auth/main.py index 0a528e1..38f0b25 100644 --- a/backend/auth/main.py +++ b/backend/auth/main.py @@ -1,9 +1,12 @@ from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles +from fastapi.responses import HTMLResponse from contextlib import asynccontextmanager from apps.urls import api_router from database import engine, get_db from models import Base from init_data import init_all_data +from pathlib import Path @asynccontextmanager @@ -12,39 +15,102 @@ async def lifespan(app: FastAPI): async with engine.begin() as conn: # 创建所有表 await conn.run_sync(Base.metadata.create_all) - + # 初始化数据 async for db in get_db(): await init_all_data(db) break - + yield # 关闭时的清理工作 await engine.dispose() + # 实例化FastAPI app = FastAPI( - title="Book System API", - description="图书系统API", + title="图书系统授权服务API", + description="""图书系统授权服务API是一套用于用户认证和授权的服务,提供用户注册、登录、权限校验等功能。 + 同时实现了基于角色的访问控制(RBAC),支持自定义角色和权限。还增加了日志记录功能,方便监控和调试。""", version="1.0.0", - lifespan=lifespan + lifespan=lifespan, + docs_url=None, + redoc_url="/redoc", ) +# 挂载swagger-ui静态文件目录 +swagger_ui_path = Path(__file__).parent.parent / "swagger-ui" +app.mount("/static", StaticFiles(directory=str(swagger_ui_path)), name="static") + + +# 自定义Swagger UI页面 +@app.get("/docs", include_in_schema=False) +async def custom_swagger_ui_html(): + html_content = """ + + + + + + Book System API - Swagger UI + + + + + +
+ + + + + """ + return HTMLResponse(content=html_content) # 包含路由 app.include_router(api_router) + # 声明装饰器方法和路径 @app.get("/") # 声明装饰器函数 async def home(): return {"message": "Hello World!!!"} + # 如果使用命令行启动,使用uvicorn 文件名:app --reload启动即可,下面命令就不用写 # 如果写下面的命令,就不需要命令行启动了,直接用IDE运行即可 if __name__ == "__main__": import uvicorn import os + name = f"{os.path.splitext(os.path.basename(os.path.abspath(__file__)))[0]}:app" uvicorn.run(name, host="0.0.0.0", port=8000, reload=True, reload_dirs=["_"]) diff --git a/backend/auth/middleware.py b/backend/auth/middleware.py index 7c1bf5f..e91864a 100644 --- a/backend/auth/middleware.py +++ b/backend/auth/middleware.py @@ -1,6 +1,7 @@ from fastapi import Request, HTTPException, status, Depends from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select import redis.asyncio as redis from database import get_db @@ -10,10 +11,15 @@ from utils.redis_client import get_redis, check_token_in_redis security = HTTPBearer() -async def get_current_user(request: Request, credentials: HTTPAuthorizationCredentials = Depends(security), db: AsyncSession = Depends(get_db), redis_conn: redis.Redis = Depends(get_redis)): +async def get_current_user( + request: Request, + credentials: HTTPAuthorizationCredentials = Depends(security), + db: AsyncSession = Depends(get_db), + redis_conn: redis.Redis = Depends(get_redis), +): """获取当前用户""" token = credentials.credentials - + # 解码令牌 payload = decode_token(token) if not payload: @@ -22,9 +28,9 @@ async def get_current_user(request: Request, credentials: HTTPAuthorizationCrede detail="无效的认证凭据", headers={"WWW-Authenticate": "Bearer"}, ) - + user_id = int(payload.get("sub")) - + # 检查令牌是否在Redis中 is_valid = await check_token_in_redis(redis_conn, user_id, token) if not is_valid: @@ -33,14 +39,13 @@ async def get_current_user(request: Request, credentials: HTTPAuthorizationCrede detail="令牌已过期或已登出", headers={"WWW-Authenticate": "Bearer"}, ) - + # 查找用户 from models import User - user = await db.query(User).filter(User.id == user_id).first() + + result = await db.execute(select(User).filter(User.id == user_id)) + user = result.scalar_one_or_none() if not user: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="用户不存在" - ) - - return user \ No newline at end of file + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在") + + return user diff --git a/backend/auth/models.py b/backend/auth/models.py index 4e629be..af3cf33 100644 --- a/backend/auth/models.py +++ b/backend/auth/models.py @@ -8,19 +8,23 @@ Base = declarative_base() class Role(Base): """角色数据库模型""" + __tablename__ = "role" id = Column(Integer, primary_key=True, index=True, comment="角色ID") name = Column(String(50), nullable=False, unique=True, comment="角色名称") createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间") - updatetime = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间") + updatetime = Column( + DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间" + ) creator = Column(String(50), nullable=True, comment="创建人") - + # 关联到User users = relationship("User", back_populates="role_rel") class User(Base): """用户数据库模型""" + __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) username = Column(String(50), unique=True, nullable=False, comment="登录名") @@ -32,8 +36,10 @@ class User(Base): avatar = Column(String(255), nullable=True, comment="头像路径") role_id = Column(Integer, ForeignKey("role.id"), default=2, comment="角色ID") createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间") - updatetime = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间") - lastlogintime = Column(DateTime, nullable=True, comment="最后登录时间") - + updatetime = Column( + DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间" + ) + lastlogintime = Column(DateTime, comment="最后登录时间") + # 关联到Role - role_rel = relationship("Role", back_populates="users") \ No newline at end of file + role_rel = relationship("Role", back_populates="users") diff --git a/backend/auth/requirements.txt b/backend/auth/requirements.txt index e1c41f1..ddcccb3 100644 --- a/backend/auth/requirements.txt +++ b/backend/auth/requirements.txt @@ -6,6 +6,7 @@ fastapi_cdn_host==0.10.0 # Pydantic数据验证 pydantic>=2.5.0 pydantic-settings>=2.1.0 +pydantic[email]>=2.5.0 # 密码加密 passlib[bcrypt]>=1.7.4 @@ -16,10 +17,12 @@ fastcrud==0.21.0 # JWT python-jose[cryptography]>=3.3.0 -# PostgreSQL + SQLAlchemy ORM(包含异步支持) +# SQLAlchemy ORM(包含异步支持) sqlalchemy>=2.0.46 -asyncpg>=0.31.0 alembic>=1.18.4 +# PostgreSQL +asyncpg>=0.31.0 + # Redis异步客户端 redis>=7.2.0 \ No newline at end of file diff --git a/backend/auth/schemas.py b/backend/auth/schemas.py index 1694921..b4208db 100644 --- a/backend/auth/schemas.py +++ b/backend/auth/schemas.py @@ -5,6 +5,7 @@ from datetime import datetime class UserBase(BaseModel): """用户基础模型""" + username: str = Field(..., min_length=3, max_length=50, description="登录名") nickname: str = Field(..., max_length=50, description="昵称") email: Optional[EmailStr] = Field(None, max_length=50, description="电子邮箱") @@ -14,19 +15,43 @@ class UserBase(BaseModel): role_id: int = Field(2, description="角色ID") +class UserCreateRequest(UserBase): + """用户创建请求模型""" + + password: str = Field(..., min_length=6, max_length=50, description="密码") + + class UserCreate(UserBase): """用户创建模型""" - password: str = Field(..., min_length=6, max_length=50, description="密码") + + password_hash: str = Field( + ..., min_length=6, max_length=128, description="密码哈希值" + ) + + +class UserUpdate(BaseModel): + """用户更新模型""" + + username: str = Field(..., min_length=3, max_length=50, description="登录名") + nickname: str = Field(..., max_length=50, description="昵称") + email: Optional[EmailStr] = Field(None, max_length=50, description="电子邮箱") + phone: Optional[str] = Field(None, max_length=11, description="手机号码") + wx_openid: Optional[str] = Field(None, max_length=100, description="微信OpenID") + avatar: Optional[str] = Field(None, max_length=255, description="头像路径") + role_id: Optional[int] = Field(2, description="角色ID") + password: Optional[str] = Field(None, max_length=128, description="密码") class UserLogin(BaseModel): """用户登录模型""" + username: str = Field(..., description="登录名") password: str = Field(..., description="密码") class UserResponse(BaseModel): """用户响应模型""" + id: int = Field(..., description="用户ID") username: str = Field(..., description="登录名") nickname: str = Field(..., description="昵称") @@ -37,40 +62,45 @@ class UserResponse(BaseModel): role_id: int = Field(..., description="角色ID") createtime: datetime = Field(..., description="创建时间") updatetime: datetime = Field(..., description="更新时间") - lastlogintime: datetime = Field(..., description="最后登录时间") - + lastlogintime: Optional[datetime] = Field(None, description="最后登录时间") + class Config: from_attributes = True class Token(BaseModel): """令牌模型""" + access_token: str = Field(..., description="访问令牌") token_type: str = Field(..., description="令牌类型") class TokenData(BaseModel): """令牌数据模型""" + user_id: Optional[int] = None email: Optional[EmailStr] = None class RoleBase(BaseModel): """角色基础模型""" + name: str = Field(..., max_length=50, description="角色名称") class RoleCreate(RoleBase): """角色创建模型""" + creator: Optional[str] = Field(None, max_length=50, description="创建人") class RoleResponse(RoleBase): """角色响应模型""" + id: int = Field(..., description="角色ID") createtime: datetime = Field(..., description="创建时间") updatetime: datetime = Field(..., description="更新时间") creator: Optional[str] = Field(None, description="创建人") - + class Config: - from_attributes = True \ No newline at end of file + from_attributes = True diff --git a/backend/auth/test_api.py b/backend/auth/test_api.py deleted file mode 100644 index 2ecdf2f..0000000 --- a/backend/auth/test_api.py +++ /dev/null @@ -1,19 +0,0 @@ -import urllib.request - -# 测试根路径 -print("Testing root path...") -try: - response = urllib.request.urlopen('http://127.0.0.1:8000/') - print(f"Status code: {response.status}") - print(f"Response: {response.read().decode('utf-8')}") -except Exception as e: - print(f"Error: {e}") - -# 测试/docs路径 -print("\nTesting /docs path...") -try: - response = urllib.request.urlopen('http://127.0.0.1:8000/docs') - print(f"Status code: {response.status}") - print(f"Response length: {len(response.read().decode('utf-8'))} characters") -except Exception as e: - print(f"Error: {e}") diff --git a/backend/auth/utils/__pycache__/__init__.cpython-313.pyc b/backend/auth/utils/__pycache__/__init__.cpython-313.pyc index 7de4ae4381ba833ee1818601957f70c3722853e5..c37b4db4c7494c5023b305995245c1603c7a5627 100644 GIT binary patch delta 24 ecmbQlIEj(_GcPX}0}#yqw|FAA38ULYhadn?%Lcyy delta 24 ecmbQlIEj(_GcPX}0}xCMUo?^1gwbW9Ll6K+#s&)j diff --git a/backend/auth/utils/__pycache__/jwt.cpython-313.pyc b/backend/auth/utils/__pycache__/jwt.cpython-313.pyc index 8047970faed443b6efc6427320dfb5ef40776ffe..f3dd183fbab68324e6ff54f090419d40e1c08070 100644 GIT binary patch delta 29 jcmbQvGo6R`GcPX}0}#yqw>UFqBd-KAquXXR=8sGOda?*x delta 29 jcmbQvGo6R`GcPX}0}%KoFUoY;$Sc9j=(1Uj`6CkmYl8<@ diff --git a/backend/auth/utils/__pycache__/password.cpython-313.pyc b/backend/auth/utils/__pycache__/password.cpython-313.pyc index f9bdf357f5f9cc435ea93e87e878100ace9bd0b8..25def63a637222ed311cf38f4d69ed376cbb7c30 100644 GIT binary patch delta 29 jcmdnUwvmnZGcPX}0}#yqw>WdtM&2++Mz_r=jAt1Eh3yF} delta 29 jcmdnUwvmnZGcPX}0}uplSd_VRBX1ZZqs!(L#kbV!KzGcPX}0}#yqw>WeDM&6lBjBcBkGtFiL0FGPkbV!KzGcPX}0}w