Skip to content

Commit 00b2dd9

Browse files
authored
更新架构并修复问题 (#57)
1 parent b270710 commit 00b2dd9

27 files changed

+2262
-2236
lines changed

.gitignore

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ __pycache__/
33
backend/.env
44
.venv/
55
venv/
6-
backend/log/
76
backend/alembic/versions/
7+
*.log
88
.ruff_cache/
9-
.pdm-python

.pre-commit-config.yaml

+6-9
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ repos:
88
- id: check-toml
99

1010
- repo: https://github.com/charliermarsh/ruff-pre-commit
11-
rev: v0.9.5
11+
rev: v0.11.4
1212
hooks:
1313
- id: ruff
1414
args:
@@ -18,15 +18,12 @@ repos:
1818
- '--unsafe-fixes'
1919
- id: ruff-format
2020

21-
- repo: https://github.com/pdm-project/pdm
22-
rev: 2.22.3
21+
- repo: https://github.com/astral-sh/uv-pre-commit
22+
rev: 0.6.14
2323
hooks:
24-
- id: pdm-export
24+
- id: uv-lock
25+
- id: uv-export
2526
args:
2627
- '-o'
2728
- 'requirements.txt'
28-
- '--without-hashes'
29-
- '-G'
30-
- 'lint'
31-
files: ^pdm.lock$
32-
- id: pdm-lock-check
29+
- '--no-hashes'

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2022 xiaowu
3+
Copyright (c) 2025 wu-clan
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

backend/alembic/env.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
from backend.core import path_conf
1818

19-
if not os.path.exists(path_conf.ALEMBIC_VERSIONS_DIR):
20-
os.makedirs(path_conf.ALEMBIC_VERSIONS_DIR)
19+
if not os.path.exists(path_conf.ALEMBIC_VERSION_DIR):
20+
os.makedirs(path_conf.ALEMBIC_VERSION_DIR)
2121

2222
# this is the Alembic Config object, which provides
2323
# access to the values within the .ini file in use.
@@ -30,10 +30,10 @@
3030
# add your model's MetaData object here
3131
# for 'autogenerate' support
3232
# https://alembic.sqlalchemy.org/en/latest/autogenerate.html#autogenerating-multiple-metadata-collections
33-
from backend.app.admin.model import MappedBase as AdminModel
33+
from backend.app.admin.model import MappedBase
3434

3535
target_metadata = [
36-
AdminModel.metadata,
36+
MappedBase.metadata,
3737
]
3838

3939
# other values from the config, defined by the needs of env.py,

backend/app/admin/api/v1/auth/auth.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3+
34
from fastapi import APIRouter, Depends, Request
45
from fastapi.security import OAuth2PasswordRequestForm
56

67
from backend.app.admin.service.auth_service import auth_service
78
from backend.common.security.jwt import DependsJwtAuth
89
from backend.common.response.response_schema import response_base, ResponseModel, ResponseSchemaModel
910
from backend.app.admin.schema.token import GetSwaggerToken, GetLoginToken
10-
from backend.app.admin.schema.user import Auth2
11+
from backend.app.admin.schema.user import AuthLoginParam
1112

1213
router = APIRouter()
1314

@@ -19,7 +20,7 @@ async def swagger_login(form_data: OAuth2PasswordRequestForm = Depends()) -> Get
1920

2021

2122
@router.post('/login', summary='验证码登录')
22-
async def user_login(request: Request, obj: Auth2) -> ResponseSchemaModel[GetLoginToken]:
23+
async def user_login(request: Request, obj: AuthLoginParam) -> ResponseSchemaModel[GetLoginToken]:
2324
data = await auth_service.login(request=request, obj=obj)
2425
return response_base.success(data=data)
2526

backend/app/admin/api/v1/user.py

+13-9
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,20 @@
88
from backend.common.pagination import paging_data, DependsPagination, PageData
99
from backend.common.response.response_schema import response_base, ResponseModel, ResponseSchemaModel
1010
from backend.database.db import CurrentSession
11-
from backend.app.admin.schema.user import CreateUser, GetUserInfo, ResetPassword, UpdateUser, Avatar
11+
from backend.app.admin.schema.user import (
12+
RegisterUserParam,
13+
GetUserInfoDetail,
14+
ResetPassword,
15+
UpdateUserParam,
16+
AvatarParam,
17+
)
1218
from backend.app.admin.service.user_service import UserService
13-
from backend.utils.serializers import select_as_dict
1419

1520
router = APIRouter()
1621

1722

1823
@router.post('/register', summary='用户注册')
19-
async def user_register(obj: CreateUser) -> ResponseModel:
24+
async def user_register(obj: RegisterUserParam) -> ResponseModel:
2025
await UserService.register(obj=obj)
2126
return response_base.success()
2227

@@ -30,22 +35,21 @@ async def password_reset(obj: ResetPassword) -> ResponseModel:
3035

3136

3237
@router.get('/{username}', summary='查看用户信息', dependencies=[DependsJwtAuth])
33-
async def get_user(username: str) -> ResponseSchemaModel[GetUserInfo]:
34-
current_user = await UserService.get_userinfo(username=username)
35-
data = GetUserInfo(**select_as_dict(current_user))
38+
async def get_user(username: str) -> ResponseSchemaModel[GetUserInfoDetail]:
39+
data = await UserService.get_userinfo(username=username)
3640
return response_base.success(data=data)
3741

3842

3943
@router.put('/{username}', summary='更新用户信息', dependencies=[DependsJwtAuth])
40-
async def update_userinfo(username: str, obj: UpdateUser) -> ResponseModel:
44+
async def update_userinfo(username: str, obj: UpdateUserParam) -> ResponseModel:
4145
count = await UserService.update(username=username, obj=obj)
4246
if count > 0:
4347
return response_base.success()
4448
return response_base.fail()
4549

4650

4751
@router.put('/{username}/avatar', summary='更新头像', dependencies=[DependsJwtAuth])
48-
async def update_avatar(username: str, avatar: Avatar) -> ResponseModel:
52+
async def update_avatar(username: str, avatar: AvatarParam) -> ResponseModel:
4953
count = await UserService.update_avatar(username=username, avatar=avatar)
5054
if count > 0:
5155
return response_base.success()
@@ -65,7 +69,7 @@ async def get_all_users(
6569
username: Annotated[str | None, Query()] = None,
6670
phone: Annotated[str | None, Query()] = None,
6771
status: Annotated[int | None, Query()] = None,
68-
) -> ResponseSchemaModel[PageData[GetUserInfo]]:
72+
) -> ResponseSchemaModel[PageData[GetUserInfoDetail]]:
6973
user_select = await UserService.get_select(username=username, phone=phone, status=status)
7074
page_data = await paging_data(db, user_select)
7175
return response_base.success(data=page_data)

backend/app/admin/crud/crud_user.py

+13-10
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from sqlalchemy_crud_plus import CRUDPlus
1010

1111
from backend.app.admin.model import User
12-
from backend.app.admin.schema.user import CreateUser, UpdateUser, Avatar
12+
from backend.app.admin.schema.user import RegisterUserParam, UpdateUserParam, AvatarParam
1313
from backend.common.security.jwt import get_hash_password
1414

1515

@@ -40,7 +40,7 @@ async def update_login_time(self, db: AsyncSession, username: str, login_time: d
4040
)
4141
return user.rowcount
4242

43-
async def create(self, db: AsyncSession, obj: CreateUser) -> None:
43+
async def create(self, db: AsyncSession, obj: RegisterUserParam) -> None:
4444
"""
4545
创建用户
4646
@@ -55,7 +55,7 @@ async def create(self, db: AsyncSession, obj: CreateUser) -> None:
5555
new_user = self.model(**dict_obj)
5656
db.add(new_user)
5757

58-
async def update_userinfo(self, db: AsyncSession, input_user: int, obj: UpdateUser) -> int:
58+
async def update_userinfo(self, db: AsyncSession, input_user: int, obj: UpdateUserParam) -> int:
5959
"""
6060
更新用户信息
6161
@@ -66,7 +66,7 @@ async def update_userinfo(self, db: AsyncSession, input_user: int, obj: UpdateUs
6666
"""
6767
return await self.update_model(db, input_user, obj)
6868

69-
async def update_avatar(self, db: AsyncSession, input_user: int, avatar: Avatar) -> int:
69+
async def update_avatar(self, db: AsyncSession, input_user: int, avatar: AvatarParam) -> int:
7070
"""
7171
更新用户头像
7272
@@ -118,15 +118,18 @@ async def get_list(self, username: str = None, phone: str = None, status: int =
118118
:return:
119119
"""
120120
stmt = select(self.model).order_by(desc(self.model.join_time))
121-
where_list = []
121+
122+
filters = []
122123
if username:
123-
where_list.append(self.model.username.like(f'%{username}%'))
124+
filters.append(self.model.username.like(f'%{username}%'))
124125
if phone:
125-
where_list.append(self.model.phone.like(f'%{phone}%'))
126+
filters.append(self.model.phone.like(f'%{phone}%'))
126127
if status is not None:
127-
where_list.append(self.model.status == status)
128-
if where_list:
129-
stmt = stmt.where(and_(*where_list))
128+
filters.append(self.model.status == status)
129+
130+
if filters:
131+
stmt = stmt.where(and_(*filters))
132+
130133
return stmt
131134

132135

backend/app/admin/schema/token.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
# -*- coding: utf-8 -*-
33

44
from backend.common.schema import SchemaBase
5-
from backend.app.admin.schema.user import GetUserInfo
5+
from backend.app.admin.schema.user import GetUserInfoDetail
66

77

88
class GetSwaggerToken(SchemaBase):
99
access_token: str
1010
token_type: str = 'Bearer'
11-
user: GetUserInfo
11+
user: GetUserInfoDetail
1212

1313

1414
class GetLoginToken(GetSwaggerToken):

backend/app/admin/schema/user.py

+26-26
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,49 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3-
import datetime
3+
from datetime import datetime
44

5-
from pydantic import Field, EmailStr, ConfigDict, UUID4, HttpUrl
5+
from pydantic import Field, EmailStr, ConfigDict, HttpUrl
66

77
from backend.common.schema import SchemaBase, CustomPhoneNumber
88

99

10-
class Auth(SchemaBase):
11-
username: str
12-
password: str
10+
class AuthSchemaBase(SchemaBase):
11+
username: str = Field(description='用户名')
12+
password: str = Field(description='密码')
1313

1414

15-
class Auth2(Auth):
16-
captcha: str
15+
class AuthLoginParam(AuthSchemaBase):
16+
captcha: str = Field(description='验证码')
1717

1818

19-
class CreateUser(Auth):
20-
email: EmailStr = Field(examples=['[email protected]'])
19+
class RegisterUserParam(AuthSchemaBase):
20+
email: EmailStr = Field(examples=['[email protected]'], description='邮箱')
2121

2222

23-
class UpdateUser(SchemaBase):
24-
username: str
25-
email: EmailStr = Field(examples=['[email protected]'])
26-
phone: CustomPhoneNumber | None = None
23+
class UpdateUserParam(SchemaBase):
24+
username: str = Field(description='用户名')
25+
email: EmailStr = Field(examples=['[email protected]'], description='邮箱')
26+
phone: CustomPhoneNumber | None = Field(None, description='手机号')
2727

2828

29-
class Avatar(SchemaBase):
29+
class AvatarParam(SchemaBase):
3030
url: HttpUrl = Field(..., description='头像 http 地址')
3131

3232

33-
class GetUserInfo(UpdateUser):
33+
class GetUserInfoDetail(UpdateUserParam):
3434
model_config = ConfigDict(from_attributes=True)
3535

36-
id: int
37-
uuid: UUID4
38-
status: int
39-
is_superuser: bool
40-
avatar: str | None = None
41-
join_time: datetime.datetime
42-
last_login_time: datetime.datetime | None = None
36+
id: int = Field(description='用户 ID')
37+
uuid: str = Field(description='用户 UUID')
38+
avatar: str | None = Field(None, description='头像')
39+
status: int = Field(description='状态')
40+
is_superuser: bool = Field(description='是否超级管理员')
41+
join_time: datetime = Field(description='加入时间')
42+
last_login_time: datetime | None = Field(None, description='最后登录时间')
4343

4444

4545
class ResetPassword(SchemaBase):
46-
username: str
47-
old_password: str
48-
new_password: str
49-
confirm_password: str
46+
username: str = Field(description='用户名')
47+
old_password: str = Field(description='旧密码')
48+
new_password: str = Field(description='新密码')
49+
confirm_password: str = Field(description='确认密码')

backend/app/admin/service/auth_service.py

+14-14
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
# -*- coding: utf-8 -*-
33
from fastapi import Request
44
from fastapi.security import OAuth2PasswordRequestForm
5+
from sqlalchemy.ext.asyncio import AsyncSession
56

67
from backend.app.admin.crud.crud_user import user_dao
78
from backend.app.admin.model import User
89
from backend.app.admin.schema.token import GetLoginToken
9-
from backend.app.admin.schema.user import Auth2
10+
from backend.app.admin.schema.user import AuthLoginParam
1011
from backend.common.exception import errors
1112
from backend.common.response.response_code import CustomErrorCode
1213
from backend.common.security.jwt import password_verify, create_access_token
@@ -18,27 +19,26 @@
1819

1920
class AuthService:
2021
@staticmethod
21-
async def user_verify(username: str, password: str) -> User:
22-
async with async_db_session() as db:
23-
user = await user_dao.get_by_username(db, username)
24-
if not user:
25-
raise errors.NotFoundError(msg='用户名或密码有误')
26-
elif not password_verify(password, user.password):
27-
raise errors.AuthorizationError(msg='用户名或密码有误')
28-
elif not user.status:
29-
raise errors.AuthorizationError(msg='用户已被锁定, 请联系统管理员')
30-
return user
22+
async def user_verify(db: AsyncSession, username: str, password: str) -> User:
23+
user = await user_dao.get_by_username(db, username)
24+
if not user:
25+
raise errors.NotFoundError(msg='用户名或密码有误')
26+
elif not password_verify(password, user.password):
27+
raise errors.AuthorizationError(msg='用户名或密码有误')
28+
elif not user.status:
29+
raise errors.AuthorizationError(msg='用户已被锁定, 请联系统管理员')
30+
return user
3131

3232
async def swagger_login(self, *, form_data: OAuth2PasswordRequestForm) -> tuple[str, User]:
3333
async with async_db_session() as db:
34-
user = await self.user_verify(form_data.username, form_data.password)
34+
user = await self.user_verify(db, form_data.username, form_data.password)
3535
await user_dao.update_login_time(db, user.username, login_time=timezone.now())
3636
token = create_access_token(str(user.id))
3737
return token, user
3838

39-
async def login(self, *, request: Request, obj: Auth2) -> GetLoginToken:
39+
async def login(self, *, request: Request, obj: AuthLoginParam) -> GetLoginToken:
4040
async with async_db_session() as db:
41-
user = await self.user_verify(obj.username, obj.password)
41+
user = await self.user_verify(db, obj.username, obj.password)
4242
try:
4343
captcha_uuid = request.app.state.captcha_uuid
4444
redis_code = await redis_client.get(f'{settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{captcha_uuid}')

backend/app/admin/service/user_service.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
from backend.app.admin.crud.crud_user import user_dao
88
from backend.database.db import async_db_session
99
from backend.app.admin.model import User
10-
from backend.app.admin.schema.user import CreateUser, ResetPassword, UpdateUser, Avatar
10+
from backend.app.admin.schema.user import RegisterUserParam, ResetPassword, UpdateUserParam, AvatarParam
1111

1212

1313
class UserService:
1414
@staticmethod
15-
async def register(*, obj: CreateUser) -> None:
15+
async def register(*, obj: RegisterUserParam) -> None:
1616
async with async_db_session.begin() as db:
1717
if not obj.password:
1818
raise errors.ForbiddenError(msg='密码为空')
@@ -47,7 +47,7 @@ async def get_userinfo(*, username: str) -> User:
4747
return user
4848

4949
@staticmethod
50-
async def update(*, username: str, obj: UpdateUser) -> int:
50+
async def update(*, username: str, obj: UpdateUserParam) -> int:
5151
async with async_db_session.begin() as db:
5252
input_user = await user_dao.get_by_username(db, username=username)
5353
if not input_user:
@@ -65,7 +65,7 @@ async def update(*, username: str, obj: UpdateUser) -> int:
6565
return count
6666

6767
@staticmethod
68-
async def update_avatar(*, username: str, avatar: Avatar) -> int:
68+
async def update_avatar(*, username: str, avatar: AvatarParam) -> int:
6969
async with async_db_session.begin() as db:
7070
input_user = await user_dao.get_by_username(db, username)
7171
if not input_user:

0 commit comments

Comments
 (0)