Skip to content

Commit a1cea99

Browse files
authored
更新架构并修复问题 (#36)
1 parent dabbaf0 commit a1cea99

23 files changed

+2216
-2109
lines changed

.pre-commit-config.yaml

+6-7
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,13 +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-
files: ^pdm.lock$
30-
- id: pdm-lock-check
29+
- '--no-hashes'

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
fastapi dev main.py
5353
```
5454

55-
9. 浏览器访问: http://127.0.0.1:8000/api/v1/docs
55+
9. 浏览器访问: http://127.0.0.1:8000/docs
5656

5757
---
5858

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from fastapi.security import OAuth2PasswordRequestForm
55

66
from backend.app.admin.schema.token import GetLoginToken, GetSwaggerToken
7-
from backend.app.admin.schema.user import Auth2
7+
from backend.app.admin.schema.user import AuthLoginParam
88
from backend.app.admin.service.auth_service import auth_service
99
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
1010
from backend.common.security.jwt import DependsJwtAuth
@@ -19,7 +19,7 @@ async def swagger_login(form_data: OAuth2PasswordRequestForm = Depends()) -> Get
1919

2020

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

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

+12-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44

55
from fastapi import APIRouter, Query
66

7-
from backend.app.admin.schema.user import CreateUser, ResetPassword, UpdateUser, GetUserInfo, Avatar
7+
from backend.app.admin.schema.user import (
8+
RegisterUserParam,
9+
ResetPassword,
10+
UpdateUserParam,
11+
GetUserInfoDetail,
12+
AvatarParam,
13+
)
814
from backend.app.admin.service.user_service import UserService
915
from backend.common.pagination import DependsPagination, paging_data, PageData
1016
from backend.common.response.response_schema import response_base, ResponseModel, ResponseSchemaModel
@@ -14,7 +20,7 @@
1420

1521

1622
@router.post('/register', summary='用户注册')
17-
async def create_user(obj: CreateUser) -> ResponseModel:
23+
async def create_user(obj: RegisterUserParam) -> ResponseModel:
1824
await UserService.register(obj=obj)
1925
return response_base.success()
2026

@@ -28,21 +34,21 @@ async def password_reset(obj: ResetPassword) -> ResponseModel:
2834

2935

3036
@router.get('/{username}', summary='查看用户信息', dependencies=[DependsJwtAuth])
31-
async def get_user(username: str) -> ResponseSchemaModel[GetUserInfo]:
37+
async def get_user(username: str) -> ResponseSchemaModel[GetUserInfoDetail]:
3238
data = await UserService.get_userinfo(username=username)
3339
return response_base.success(data=data)
3440

3541

3642
@router.put('/{username}', summary='更新用户信息', dependencies=[DependsJwtAuth])
37-
async def update_userinfo(username: str, obj: UpdateUser) -> ResponseModel:
43+
async def update_userinfo(username: str, obj: UpdateUserParam) -> ResponseModel:
3844
count = await UserService.update(username=username, obj=obj)
3945
if count > 0:
4046
return response_base.success()
4147
return response_base.fail()
4248

4349

4450
@router.put('/{username}/avatar', summary='更新头像', dependencies=[DependsJwtAuth])
45-
async def update_avatar(username: str, avatar: Avatar) -> ResponseModel:
51+
async def update_avatar(username: str, avatar: AvatarParam) -> ResponseModel:
4652
count = await UserService.update_avatar(username=username, avatar=avatar)
4753
if count > 0:
4854
return response_base.success()
@@ -61,7 +67,7 @@ async def get_all_users(
6167
username: Annotated[str | None, Query()] = None,
6268
phone: Annotated[str | None, Query()] = None,
6369
status: Annotated[int | None, Query()] = None,
64-
) -> ResponseSchemaModel[PageData[GetUserInfo]]:
70+
) -> ResponseSchemaModel[PageData[GetUserInfoDetail]]:
6571
user_queryset = await UserService.get_list(username=username, phone=phone, status=status)
6672
page_data = await paging_data(user_queryset)
6773
return response_base.success(data=page_data)

backend/app/admin/crud/crud_user.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from tortoise.transactions import atomic
99

1010
from backend.app.admin.model.user import User
11-
from backend.app.admin.schema.user import Avatar, CreateUser, UpdateUser
11+
from backend.app.admin.schema.user import AvatarParam, RegisterUserParam, UpdateUserParam
1212
from backend.common.crud import CRUDBase
1313
from backend.common.security.jwt import get_hash_password
1414

@@ -28,7 +28,7 @@ async def check_email(self, email: str) -> bool:
2828
return await self.model.filter(email=email).exists()
2929

3030
@atomic()
31-
async def register(self, obj: CreateUser) -> None:
31+
async def register(self, obj: RegisterUserParam) -> None:
3232
salt = bcrypt.gensalt()
3333
obj.password = get_hash_password(obj.password, salt)
3434
dict_obj = obj.model_dump()
@@ -41,11 +41,11 @@ async def reset_password(self, pk: int, new_pwd: str) -> int:
4141
return await self.model.filter(id=pk).update(password=new_pwd)
4242

4343
@atomic()
44-
async def update_userinfo(self, input_user: int, obj_in: UpdateUser) -> int:
44+
async def update_userinfo(self, input_user: int, obj_in: UpdateUserParam) -> int:
4545
return await self.update_model(input_user, obj_in)
4646

4747
@atomic()
48-
async def update_avatar(self, input_user: int, avatar: Avatar) -> int:
48+
async def update_avatar(self, input_user: int, avatar: AvatarParam) -> int:
4949
return await self.update_model(input_user, {'avatar': avatar.url})
5050

5151
async def get_list(self, username: str = None, phone: str = None, status: int = None) -> QuerySet:

backend/app/admin/schema/token.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3-
from backend.app.admin.schema.user import GetUserInfo
3+
from backend.app.admin.schema.user import GetUserInfoDetail
44
from backend.common.schema import SchemaBase
55

66

77
class GetSwaggerToken(SchemaBase):
88
access_token: str
99
token_type: str = 'Bearer'
10-
user: GetUserInfo
10+
user: GetUserInfoDetail
1111

1212

1313
class GetLoginToken(GetSwaggerToken):

backend/app/admin/schema/user.py

+25-25
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 UUID4, ConfigDict, EmailStr, Field, HttpUrl
5+
from pydantic import ConfigDict, EmailStr, Field, HttpUrl
66

77
from backend.common.schema import CustomPhoneNumber, SchemaBase
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):
19+
class RegisterUserParam(AuthSchemaBase):
2020
email: EmailStr = Field(examples=['[email protected]'])
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

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from backend.app.admin.crud.crud_user import user_dao
77
from backend.app.admin.model.user import User
88
from backend.app.admin.schema.token import GetLoginToken
9-
from backend.app.admin.schema.user import Auth2
9+
from backend.app.admin.schema.user import AuthLoginParam
1010
from backend.common.exception import errors
1111
from backend.common.response.response_code import CustomErrorCode
1212
from backend.common.security.jwt import create_access_token, password_verify
@@ -33,7 +33,7 @@ async def swagger_login(self, *, form_data: OAuth2PasswordRequestForm) -> tuple[
3333
token = create_access_token(str(user.id))
3434
return token, user
3535

36-
async def login(self, *, request: Request, obj: Auth2) -> GetLoginToken:
36+
async def login(self, *, request: Request, obj: AuthLoginParam) -> GetLoginToken:
3737
user = await self.user_verify(obj.username, obj.password)
3838
try:
3939
captcha_uuid = request.app.state.captcha_uuid

backend/app/admin/service/user_service.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
from tortoise.queryset import QuerySet
44
from backend.app.admin.crud.crud_user import user_dao
55
from backend.app.admin.model.user import User
6-
from backend.app.admin.schema.user import CreateUser, ResetPassword, UpdateUser, Avatar
6+
from backend.app.admin.schema.user import RegisterUserParam, ResetPassword, UpdateUserParam, AvatarParam
77
from backend.common.exception import errors
88
from backend.common.security.jwt import get_hash_password, password_verify, superuser_verify
99

1010

1111
class UserService:
1212
@staticmethod
13-
async def register(*, obj: CreateUser) -> None:
13+
async def register(*, obj: RegisterUserParam) -> None:
1414
if not obj.password:
1515
raise errors.ForbiddenError(msg='密码为空')
1616
username = await user_dao.get_by_username(name=obj.username)
@@ -42,7 +42,7 @@ async def get_userinfo(*, username: str) -> User:
4242
return user
4343

4444
@staticmethod
45-
async def update(*, username: str, obj: UpdateUser) -> int:
45+
async def update(*, username: str, obj: UpdateUserParam) -> int:
4646
input_user = await user_dao.get_by_username(username)
4747
if not input_user:
4848
raise errors.NotFoundError(msg='用户不存在')
@@ -59,7 +59,7 @@ async def update(*, username: str, obj: UpdateUser) -> int:
5959
return count
6060

6161
@staticmethod
62-
async def update_avatar(*, username: str, avatar: Avatar) -> int:
62+
async def update_avatar(*, username: str, avatar: AvatarParam) -> int:
6363
input_user = await user_dao.get_by_username(username)
6464
if not input_user:
6565
raise errors.NotFoundError(msg='用户不存在')

backend/common/exception/errors.py

+20-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3-
"""
4-
全局业务异常类
5-
6-
业务代码执行异常时,可以使用 raise xxxError 触发内部错误,它尽可能实现带有后台任务的异常,但它不适用于**自定义响应状态码**
7-
如果要求使用**自定义响应状态码**,则可以通过 return response_base.fail(res=CustomResponseCode.xxx) 直接返回
8-
""" # noqa: E501
9-
103
from typing import Any
114

125
from fastapi import HTTPException
@@ -16,6 +9,8 @@
169

1710

1811
class BaseExceptionMixin(Exception):
12+
"""基础异常混入类"""
13+
1914
code: int
2015

2116
def __init__(self, *, msg: str = None, data: Any = None, background: BackgroundTask | None = None):
@@ -26,38 +21,50 @@ def __init__(self, *, msg: str = None, data: Any = None, background: BackgroundT
2621

2722

2823
class HTTPError(HTTPException):
24+
"""HTTP 异常"""
25+
2926
def __init__(self, *, code: int, msg: Any = None, headers: dict[str, Any] | None = None):
3027
super().__init__(status_code=code, detail=msg, headers=headers)
3128

3229

3330
class CustomError(BaseExceptionMixin):
31+
"""自定义异常"""
32+
3433
def __init__(self, *, error: CustomErrorCode, data: Any = None, background: BackgroundTask | None = None):
3534
self.code = error.code
3635
super().__init__(msg=error.msg, data=data, background=background)
3736

3837

3938
class RequestError(BaseExceptionMixin):
39+
"""请求异常"""
40+
4041
code = StandardResponseCode.HTTP_400
4142

4243
def __init__(self, *, msg: str = 'Bad Request', data: Any = None, background: BackgroundTask | None = None):
4344
super().__init__(msg=msg, data=data, background=background)
4445

4546

4647
class ForbiddenError(BaseExceptionMixin):
48+
"""禁止访问异常"""
49+
4750
code = StandardResponseCode.HTTP_403
4851

4952
def __init__(self, *, msg: str = 'Forbidden', data: Any = None, background: BackgroundTask | None = None):
5053
super().__init__(msg=msg, data=data, background=background)
5154

5255

5356
class NotFoundError(BaseExceptionMixin):
57+
"""资源不存在异常"""
58+
5459
code = StandardResponseCode.HTTP_404
5560

5661
def __init__(self, *, msg: str = 'Not Found', data: Any = None, background: BackgroundTask | None = None):
5762
super().__init__(msg=msg, data=data, background=background)
5863

5964

6065
class ServerError(BaseExceptionMixin):
66+
"""服务器异常"""
67+
6168
code = StandardResponseCode.HTTP_500
6269

6370
def __init__(
@@ -67,20 +74,26 @@ def __init__(
6774

6875

6976
class GatewayError(BaseExceptionMixin):
77+
"""网关异常"""
78+
7079
code = StandardResponseCode.HTTP_502
7180

7281
def __init__(self, *, msg: str = 'Bad Gateway', data: Any = None, background: BackgroundTask | None = None):
7382
super().__init__(msg=msg, data=data, background=background)
7483

7584

7685
class AuthorizationError(BaseExceptionMixin):
86+
"""授权异常"""
87+
7788
code = StandardResponseCode.HTTP_401
7889

7990
def __init__(self, *, msg: str = 'Permission Denied', data: Any = None, background: BackgroundTask | None = None):
8091
super().__init__(msg=msg, data=data, background=background)
8192

8293

8394
class TokenError(HTTPError):
95+
"""Token 异常"""
96+
8497
code = StandardResponseCode.HTTP_401
8598

8699
def __init__(self, *, msg: str = 'Not Authenticated', headers: dict[str, Any] | None = None):

0 commit comments

Comments
 (0)