Skip to content

Commit 36d2e28

Browse files
authored
更新架构并修复问题 (#37)
1 parent 059fc55 commit 36d2e28

40 files changed

+1906
-1275
lines changed

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
repos:
22
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: v4.4.0
3+
rev: v5.0.0
44
hooks:
55
- id: check-added-large-files
66
- id: end-of-file-fixer
77
- id: check-yaml
88
- id: check-toml
99

1010
- repo: https://github.com/charliermarsh/ruff-pre-commit
11-
rev: v0.4.5
11+
rev: v0.8.2
1212
hooks:
1313
- id: ruff
1414
args:
@@ -19,7 +19,7 @@ repos:
1919
- id: ruff-format
2020

2121
- repo: https://github.com/pdm-project/pdm
22-
rev: 2.12.4
22+
rev: 2.21.0
2323
hooks:
2424
- id: pdm-export
2525
args:

README.md

Lines changed: 9 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,5 @@
11
# FastAPI SQLAlchemy MySQL
22

3-
[![Static Badge](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/downloads/)
4-
5-
> [!TIP]
6-
> 此仓库是我们构建的 FastAPI 基础架构便捷版,完整版请查看:
7-
> [fastapi_best_architecture](https://github.com/fastapi-practices/fastapi_best_architecture)
8-
9-
## 特征
10-
11-
- [x] FastAPI
12-
- [x] Async design
13-
- [x] Restful API
14-
- [x] SQLAlchemy 2.0
15-
- [x] Pydantic 2.0
16-
- [x] Docker
17-
- [ ] ......
18-
193
## 本地开发
204

215
* Python 3.10+
@@ -35,7 +19,7 @@
3519
```shell
3620
cd backend
3721
```
38-
22+
3923
5. 创建一个 `.env` 文件
4024

4125
```shell
@@ -63,40 +47,30 @@
6347
# 开发模式
6448
fastapi dev main.py
6549
```
66-
50+
6751
9. 浏览器访问: http://127.0.0.1:8000/api/v1/docs
6852

6953
---
7054

7155
### Docker
7256

73-
> [!WARNING]
74-
>
75-
> 默认端口冲突:8000,3306,6379
76-
>
77-
> 建议在部署前关闭本地服务:mysql,redis...
78-
7957
1. 进入 `docker-compose.yml` 文件所在目录,创建环境变量文件 `.env`
8058

81-
```shell
82-
cd deploy/docker-compose/
59+
```shell
60+
cd deploy/docker-compose/
8361
84-
cp .env.server ../../../backend/.env
85-
```
62+
cp .env.server ../../../backend/.env
63+
```
8664

8765
2. 执行一键启动命令
8866

89-
```shell
90-
docker-compose up -d --build
91-
```
67+
```shell
68+
docker-compose up -d --build
69+
```
9270

9371
3. 等待命令自动完成
9472
4. 浏览器访问:http://127.0.0.1:8000/api/v1/docs
9573

96-
## 互动
97-
98-
[WeChat / QQ](https://github.com/wu-clan)
99-
10074
## 赞助
10175

10276
如果此项目能够帮助到你,你可以赞助作者一些咖啡豆表示鼓励:[:coffee: Sponsor :coffee:](https://wu-clan.github.io/sponsor/)

backend/app/admin/api/router.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44

55
from backend.app.admin.api.v1.auth import router as auth_router
66
from backend.app.admin.api.v1.user import router as user_router
7-
from backend.core.conf import settings
87

9-
v1 = APIRouter(prefix=settings.API_V1_STR)
8+
v1 = APIRouter()
109

11-
v1.include_router(auth_router, prefix='/auth', tags=['认证'])
10+
v1.include_router(auth_router)
1211
v1.include_router(user_router, prefix='/users', tags=['用户'])

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from backend.app.admin.api.v1.auth.auth import router as auth_router
66
from backend.app.admin.api.v1.auth.captcha import router as captcha_router
77

8-
router = APIRouter()
8+
router = APIRouter(prefix='/auth')
99

10-
router.include_router(auth_router)
11-
router.include_router(captcha_router)
10+
router.include_router(auth_router, tags=['授权'])
11+
router.include_router(captcha_router, prefix='/captcha', tags=['验证码'])

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,27 @@
33
from fastapi import APIRouter, Depends, Request
44
from fastapi.security import OAuth2PasswordRequestForm
55

6-
from backend.common.jwt import DependsJwtUser
6+
from backend.app.admin.service.auth_service import auth_service
7+
from backend.common.security.jwt import DependsJwtAuth
78
from backend.common.response.response_schema import response_base, ResponseModel
8-
from backend.app.admin.schema.token import Token
9+
from backend.app.admin.schema.token import GetSwaggerToken, GetLoginToken
910
from backend.app.admin.schema.user import Auth2
10-
from backend.app.admin.service.user_service import UserService
1111

1212
router = APIRouter()
1313

1414

15-
@router.post('/login/swagger', summary='swagger 表单登录', description='form 格式登录,仅用于 swagger 文档调试接口')
16-
async def swagger_login(form_data: OAuth2PasswordRequestForm = Depends()) -> Token:
17-
token, user = await UserService.login_swagger(form_data=form_data)
18-
return Token(access_token=token, user=user) # type: ignore
15+
@router.post('/login/swagger', summary='swagger 调试专用', description='用于快捷进行 swagger 认证')
16+
async def swagger_login(form_data: OAuth2PasswordRequestForm = Depends()) -> GetSwaggerToken:
17+
token, user = await auth_service.swagger_login(form_data=form_data)
18+
return GetSwaggerToken(access_token=token, user=user) # type: ignore
1919

2020

2121
@router.post('/login', summary='验证码登录')
22-
async def user_login(request: Request, obj: Auth2) -> Token:
23-
token, user = await UserService.login_captcha(obj=obj, request=request)
24-
return Token(access_token=token, user=user) # type: ignore
22+
async def user_login(request: Request, obj: Auth2) -> ResponseModel:
23+
data = await auth_service.login(request=request, obj=obj)
24+
return response_base.success(data=data)
2525

2626

27-
@router.post('/logout', summary='登出', dependencies=[DependsJwtUser])
27+
@router.post('/logout', summary='用户登出', dependencies=[DependsJwtAuth])
2828
async def user_logout() -> ResponseModel:
29-
return await response_base.success()
29+
return response_base.success()
Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,35 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
33
from fast_captcha import img_captcha
4-
from fastapi import APIRouter, Request, Depends
4+
from fastapi import APIRouter, Depends, Request
55
from fastapi_limiter.depends import RateLimiter
66
from starlette.concurrency import run_in_threadpool
7-
from starlette.responses import StreamingResponse
87

8+
from backend.common.response.response_schema import ResponseModel, response_base
9+
from backend.core.conf import settings
910
from backend.database.db_mysql import uuid4_str
1011
from backend.database.db_redis import redis_client
11-
from backend.core.conf import settings
1212

1313
router = APIRouter()
1414

1515

16-
@router.get('/captcha', summary='获取验证码', dependencies=[Depends(RateLimiter(times=5, seconds=10))])
17-
async def get_captcha(request: Request):
18-
img, code = await run_in_threadpool(img_captcha)
16+
@router.get(
17+
'',
18+
summary='获取登录验证码',
19+
dependencies=[Depends(RateLimiter(times=5, seconds=10))],
20+
)
21+
async def get_captcha(request: Request) -> ResponseModel:
22+
"""
23+
此接口可能存在性能损耗,尽管是异步接口,但是验证码生成是IO密集型任务,使用线程池尽量减少性能损耗
24+
"""
25+
img_type: str = 'base64'
26+
img, code = await run_in_threadpool(img_captcha, img_byte=img_type)
1927
uuid = uuid4_str()
2028
request.app.state.captcha_uuid = uuid
21-
await redis_client.set(uuid, code, settings.CAPTCHA_EXPIRATION_TIME)
22-
return StreamingResponse(content=img, media_type='image/jpeg')
29+
await redis_client.set(f'{settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{uuid}', code, ex=settings.CAPTCHA_EXPIRATION_TIME)
30+
return response_base.success(
31+
data={
32+
'image_type': img_type,
33+
'image': img,
34+
}
35+
)

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

Lines changed: 19 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from fastapi import APIRouter, Query
66

7-
from backend.common.jwt import CurrentUser, DependsJwtUser
7+
from backend.common.security.jwt import CurrentUser, DependsJwtAuth
88
from backend.common.pagination import paging_data, DependsPagination
99
from backend.common.response.response_schema import response_base, ResponseModel
1010
from backend.database.db_mysql import CurrentSession
@@ -18,41 +18,41 @@
1818
@router.post('/register', summary='用户注册')
1919
async def user_register(obj: CreateUser) -> ResponseModel:
2020
await UserService.register(obj=obj)
21-
return await response_base.success()
21+
return response_base.success()
2222

2323

24-
@router.post('/password/reset', summary='密码重置', dependencies=[DependsJwtUser])
24+
@router.post('/password/reset', summary='密码重置', dependencies=[DependsJwtAuth])
2525
async def password_reset(obj: ResetPassword) -> ResponseModel:
2626
count = await UserService.pwd_reset(obj=obj)
2727
if count > 0:
28-
return await response_base.success()
29-
return await response_base.fail()
28+
return response_base.success()
29+
return response_base.fail()
3030

3131

32-
@router.get('/{username}', summary='查看用户信息', dependencies=[DependsJwtUser])
32+
@router.get('/{username}', summary='查看用户信息', dependencies=[DependsJwtAuth])
3333
async def get_user(username: str) -> ResponseModel:
3434
current_user = await UserService.get_userinfo(username=username)
35-
data = GetUserInfo(**await select_as_dict(current_user))
36-
return await response_base.success(data=data)
35+
data = GetUserInfo(**select_as_dict(current_user))
36+
return response_base.success(data=data)
3737

3838

39-
@router.put('/{username}', summary='更新用户信息', dependencies=[DependsJwtUser])
39+
@router.put('/{username}', summary='更新用户信息', dependencies=[DependsJwtAuth])
4040
async def update_userinfo(username: str, obj: UpdateUser) -> ResponseModel:
4141
count = await UserService.update(username=username, obj=obj)
4242
if count > 0:
43-
return await response_base.success()
44-
return await response_base.fail()
43+
return response_base.success()
44+
return response_base.fail()
4545

4646

47-
@router.put('/{username}/avatar', summary='更新头像', dependencies=[DependsJwtUser])
47+
@router.put('/{username}/avatar', summary='更新头像', dependencies=[DependsJwtAuth])
4848
async def update_avatar(username: str, avatar: Avatar) -> ResponseModel:
4949
count = await UserService.update_avatar(username=username, avatar=avatar)
5050
if count > 0:
51-
return await response_base.success()
52-
return await response_base.fail()
51+
return response_base.success()
52+
return response_base.fail()
5353

5454

55-
@router.get('', summary='(模糊条件)分页获取所有用户', dependencies=[DependsJwtUser, DependsPagination])
55+
@router.get('', summary='(模糊条件)分页获取所有用户', dependencies=[DependsJwtAuth, DependsPagination])
5656
async def get_all_users(
5757
db: CurrentSession,
5858
username: Annotated[str | None, Query()] = None,
@@ -61,33 +61,17 @@ async def get_all_users(
6161
) -> ResponseModel:
6262
user_select = await UserService.get_select(username=username, phone=phone, status=status)
6363
page_data = await paging_data(db, user_select, GetUserInfo)
64-
return await response_base.success(data=page_data)
65-
66-
67-
@router.put('/{pk}/super', summary='修改用户超级权限', dependencies=[DependsJwtUser])
68-
async def super_set(current_user: CurrentUser, pk: int) -> ResponseModel:
69-
count = await UserService.update_permission(current_user=current_user, pk=pk)
70-
if count > 0:
71-
return await response_base.success()
72-
return await response_base.fail()
73-
74-
75-
@router.put('/{pk}/status', summary='修改用户状态', dependencies=[DependsJwtUser])
76-
async def status_set(current_user: CurrentUser, pk: int) -> ResponseModel:
77-
count = await UserService.update_status(current_user=current_user, pk=pk)
78-
if count > 0:
79-
return await response_base.success()
80-
return await response_base.fail()
64+
return response_base.success(data=page_data)
8165

8266

8367
@router.delete(
8468
path='/{username}',
8569
summary='用户注销',
8670
description='用户注销 != 用户登出,注销之后用户将从数据库删除',
87-
dependencies=[DependsJwtUser],
71+
dependencies=[DependsJwtAuth],
8872
)
8973
async def delete_user(current_user: CurrentUser, username: str) -> ResponseModel:
9074
count = await UserService.delete(current_user=current_user, username=username)
9175
if count > 0:
92-
return await response_base.success()
93-
return await response_base.fail()
76+
return response_base.success()
77+
return response_base.fail()

0 commit comments

Comments
 (0)