Skip to content

Commit e1edcad

Browse files
authored
Add RBAC authorisation and some tools or optimisations (#41)
* WIP: add rbac authorization * Perform pre-commit fixes * add rbac route whitelist * add init test data user role associations * Restore database table id naming to fix generic crud base * Add database section value uniqueness settings * Update the test directory to tests * Update route_name file name to health_check * Split user auth and user action interfaces * Fix conflict between merge and current branch * Add pymysql dependencies * Fix RBAC authentication method * Add the select serialisation tool * Fix missing return messages due to global exception handler slicing * Update the user interface with associated relationships * Add items to be completed * Perform pre-commit fixes * Add pre-made routers * Paging data return structure optimisation * Split user auth and user interface tests * Fix user register test data structure error * Fix duplicate named test classes
1 parent cee2c58 commit e1edcad

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1149
-127
lines changed

README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# FastAPI Best Architecture
22

3-
This is a base project of the FastAPI framework.
3+
This is a base project of the FastAPI framework, in production
44

55
It‘s purpose is to allow you to develop your project directly with it
66
as your base project
@@ -29,19 +29,24 @@ git clone https://github.com/wu-clan/fastapi_best_architecture.git
2929
### 1:Tradition
3030

3131
1. Install dependencies
32+
3233
```shell
3334
pip install -r requirements.txt
3435
```
3536

3637
2. Create a database `fba`, choose utf8mb4 encode
3738
3. Install and start Redis
3839
4. create a `.env` file in the `backend/app/` directory
40+
3941
```shell
4042
cd backend/app/
43+
4144
touch .env
4245
```
43-
5. Copy .env.example to .env and view `backend/app/core/conf.py`, update database configuration information
46+
47+
5. Copy `.env.example` to `.env` and view `backend/app/core/conf.py`, update database configuration information
4448
6. Perform a database migration [alembic](https://alembic.sqlalchemy.org/en/latest/tutorial.html)
49+
4550
```shell
4651
cd backend/app/
4752
@@ -51,7 +56,8 @@ git clone https://github.com/wu-clan/fastapi_best_architecture.git
5156
# Perform the migration
5257
alembic upgrade head
5358
```
54-
7. Execute the backend/app/main.py file startup service
59+
60+
7. Execute the `backend/app/main.py` file startup service
5561
8. Browser access: http://127.0.0.1:8000/v1/docs
5662

5763
---
@@ -63,6 +69,7 @@ git clone https://github.com/wu-clan/fastapi_best_architecture.git
6369
```shell
6470
docker-compose up -d --build
6571
```
72+
6673
2. Wait for the command to finish automatically
6774

6875
3. Browser access: http://127.0.0.1:8000/v1/docs

README_zh.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# FastAPI 最佳架构
2+
3+
这是 FastAPI 框架的一个基础项目,在制作中
4+
5+
它的目的是让你直接用它作为你的基础项目来开发你的项目
6+
7+
支持 python3.10 及以上版本
8+
9+
## 技术栈
10+
11+
- [x] FastAPI
12+
- [x] Pydantic
13+
- [x] SQLAlchemy
14+
- [x] Alembic
15+
- [x] MySQL
16+
- [x] Redis
17+
- [x] APScheduler
18+
- [x] Docker
19+
20+
## 克隆
21+
22+
```shell
23+
git clone https://github.com/wu-clan/fastapi_best_architecture.git
24+
```
25+
26+
## 使用:
27+
28+
### 1:传统
29+
30+
1. 安装依赖项
31+
```shell
32+
pip install -r requirements.txt
33+
```
34+
35+
2. 创建一个数据库`fba`,选择 utf8mb4 编码
36+
3. 安装并启动 Redis
37+
4. 在`backend/app/`目录下创建一个`.env`文件
38+
```shell
39+
cd backend/app/
40+
touch .env
41+
```
42+
5. 复制 `.env.example``.env` 并查看`backend/app/core/conf.py`,更新数据库配置信息
43+
6. 进行数据库迁移[alembic](https://alembic.sqlalchemy.org/en/latest/tutorial.html)
44+
```shell
45+
cd backend/app/
46+
47+
# 生成迁移文件
48+
alembic revision --autogenerate
49+
50+
# 执行迁移
51+
alembic upgrade head
52+
```
53+
7. 执行 `backend/app/main.py` 文件启动服务
54+
8. 浏览器访问:http://127.0.0.1:8000/v1/docs
55+
56+
---
57+
58+
### 2:Docker
59+
60+
1. 在 `docker-compose.yml` 文件所在的目录中运行一键启动命令
61+
62+
```shell
63+
docker-compose up -d -build
64+
```
65+
66+
2. 等待命令自动完成
67+
68+
3. 浏览器访问:http://127.0.0.1:8000/v1/docs
69+
70+
## 初始化测试数据
71+
72+
执行 `backend/app/init_test_data.py` 文件
73+
74+
## 测试
75+
76+
通过 pytest 进行测试
77+
78+
**提示**: 在测试开始前,请先执行初始化测试数据,同时,需要启动 fastapi 服务。
79+
80+
1. 首先,进入app目录
81+
82+
```shell
83+
cd backend/app/
84+
```
85+
86+
2. 执行测试命令
87+
88+
```shell
89+
pytest -vs --disable-warnings
90+
```

backend/app/api/routers.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,23 @@
33
from fastapi import APIRouter
44

55
from backend.app.api.v1.auth import router as auth_router
6+
from backend.app.api.v1.user import router as user_router
7+
from backend.app.api.v1.casbin import router as casbin_router
8+
from backend.app.api.v1.dept import router as dept_router
9+
from backend.app.api.v1.role import router as role_router
10+
from backend.app.api.v1.menu import router as menu_router
11+
from backend.app.api.v1.api import router as api_router
612
from backend.app.api.v1.task_demo import router as task_demo_router
7-
from backend.app.api.v1.sys_config import router as sys_config_router
13+
from backend.app.api.v1.config import router as config_router
814

915
v1 = APIRouter(prefix='/v1')
1016

1117
v1.include_router(auth_router)
12-
18+
v1.include_router(user_router, prefix='/users', tags=['用户管理'])
19+
v1.include_router(casbin_router, prefix='/casbin', tags=['权限管理'])
20+
v1.include_router(dept_router, prefix='/depts', tags=['部门管理'])
21+
v1.include_router(role_router, prefix='/roles', tags=['角色管理'])
22+
v1.include_router(menu_router, prefix='/menus', tags=['菜单管理'])
23+
v1.include_router(api_router, prefix='/apis', tags=['API管理'])
24+
v1.include_router(config_router, prefix='/configs', tags=['系统配置'])
1325
v1.include_router(task_demo_router, prefix='/tasks', tags=['任务管理'])
14-
15-
v1.include_router(sys_config_router, prefix='/configs', tags=['系统配置'])

backend/app/api/v1/api.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
from fastapi import APIRouter
4+
5+
router = APIRouter()
6+
7+
# TODO: 添加 api 相关接口

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
33
from fastapi import APIRouter
4-
from backend.app.api.v1.auth.user import router as user_router
4+
from backend.app.api.v1.auth.auth import router as auth_router
55

6-
router = APIRouter(prefix='/auth', tags=['用户管理'])
6+
router = APIRouter(prefix='/auth', tags=['认证'])
77

8-
router.include_router(user_router, prefix='/users')
8+
router.include_router(auth_router, prefix='/users')

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
from fastapi import APIRouter, Depends
4+
from fastapi.security import OAuth2PasswordRequestForm
5+
6+
from backend.app.common.jwt import DependsUser
7+
from backend.app.common.response.response_schema import response_base
8+
from backend.app.schemas.token import Token
9+
from backend.app.schemas.user import Auth
10+
from backend.app.services.user_service import UserService
11+
12+
router = APIRouter()
13+
14+
15+
@router.post('/swagger_login', summary='swagger 表单登录', description='form 格式登录,仅用于 swagger 文档调试接口')
16+
async def swagger_user_login(form_data: OAuth2PasswordRequestForm = Depends()) -> Token:
17+
token, user = await UserService.swagger_login(form_data)
18+
return Token(access_token=token, user=user)
19+
20+
21+
@router.post('/login', summary='用户登录', description='json 格式登录, 仅支持在第三方api工具调试接口, 例如: postman')
22+
async def user_login(obj: Auth):
23+
token, user = await UserService.login(obj)
24+
# TODO: token 存储
25+
data = Token(access_token=token, user=user)
26+
return response_base.response_200(data=data)
27+
28+
29+
@router.post('/logout', summary='用户登出', dependencies=[DependsUser])
30+
async def user_logout():
31+
# TODO: 加入 token 黑名单
32+
return response_base.response_200()

backend/app/api/v1/casbin.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
from fastapi import APIRouter
4+
5+
router = APIRouter()
6+
7+
# TODO: 添加 casbin 相关接口

backend/app/api/v1/sys_config.py renamed to backend/app/api/v1/config.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3-
from fastapi import APIRouter
3+
from fastapi import APIRouter, Request
4+
from fastapi.routing import APIRoute
45

5-
from backend.app.api.jwt import DependsSuperUser
6-
from backend.app.common.response.response_schema import ResponseModel
6+
from backend.app.common.casbin_rbac import DependsRBAC
7+
from backend.app.common.response.response_schema import response_base
78
from backend.app.core.conf import settings
89

910
router = APIRouter()
1011

1112

12-
@router.get('', summary='获取系统配置', dependencies=[DependsSuperUser])
13-
async def get_sys_config() -> ResponseModel:
14-
return ResponseModel(
13+
@router.get('', summary='获取系统配置', dependencies=[DependsRBAC])
14+
async def get_sys_config():
15+
return response_base.success(
1516
data={
1617
'title': settings.TITLE,
1718
'version': settings.VERSION,
@@ -49,3 +50,12 @@ async def get_sys_config() -> ResponseModel:
4950
'middleware_access': settings.MIDDLEWARE_ACCESS,
5051
}
5152
)
53+
54+
55+
@router.get('/routers', summary='获取所有路由', dependencies=[DependsRBAC])
56+
async def get_all_route(request: Request):
57+
data = []
58+
for route in request.app.routes:
59+
if isinstance(route, APIRoute):
60+
data.append({'path': route.path, 'name': route.name, 'summary': route.summary, 'methods': route.methods})
61+
return response_base.success(data={'route_list': data})

backend/app/api/v1/dept.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
from fastapi import APIRouter
4+
5+
router = APIRouter()
6+
7+
# TODO: 添加 dept 相关接口

backend/app/api/v1/menu.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
from fastapi import APIRouter
4+
5+
router = APIRouter()
6+
7+
# TODO: 添加 menu 相关接口

backend/app/api/v1/role.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
from fastapi import APIRouter
4+
5+
router = APIRouter()
6+
7+
# TODO: 添加 role 相关接口

backend/app/api/v1/auth/user.py renamed to backend/app/api/v1/user.py

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,18 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3-
from fastapi import APIRouter, Depends
4-
from fastapi.security import OAuth2PasswordRequestForm
3+
from fastapi import APIRouter
54

6-
from backend.app.api.jwt import CurrentUser, DependsUser, DependsSuperUser
7-
from backend.app.api.service.user_service import UserService
8-
from backend.app.common.pagination import Page
5+
from backend.app.common.jwt import DependsUser, CurrentUser, DependsSuperUser
6+
from backend.app.common.pagination import paging_data, PageDepends
97
from backend.app.common.response.response_schema import response_base
10-
from backend.app.schemas.token import Token
11-
from backend.app.schemas.user import CreateUser, GetUserInfo, ResetPassword, UpdateUser, Avatar, Auth
8+
from backend.app.database.db_mysql import CurrentSession
9+
from backend.app.schemas.user import CreateUser, GetUserInfo, ResetPassword, UpdateUser, Avatar
10+
from backend.app.services.user_service import UserService
11+
from backend.app.utils.serializers import select_to_json
1212

1313
router = APIRouter()
1414

1515

16-
@router.post('/swagger_login', summary='swagger 表单登录', description='form 格式登录,仅用于 swagger 文档调试接口')
17-
async def swagger_user_login(form_data: OAuth2PasswordRequestForm = Depends()) -> Token:
18-
token, user = await UserService.swagger_login(form_data)
19-
return Token(access_token=token, user=user)
20-
21-
22-
@router.post('/login', summary='用户登录', description='json 格式登录, 仅支持在第三方api工具调试接口, 例如: postman')
23-
async def user_login(obj: Auth):
24-
token, user = await UserService.login(obj)
25-
data = Token(access_token=token, user=user)
26-
return response_base.response_200(data=data)
27-
28-
2916
@router.post('/register', summary='用户注册')
3017
async def user_register(obj: CreateUser):
3118
await UserService.register(obj)
@@ -41,7 +28,8 @@ async def password_reset(obj: ResetPassword):
4128
@router.get('/{username}', summary='查看用户信息', dependencies=[DependsUser])
4229
async def userinfo(username: str):
4330
current_user = await UserService.get_userinfo(username)
44-
return response_base.response_200(data=current_user, exclude={'password'})
31+
data = GetUserInfo(**select_to_json(current_user))
32+
return response_base.response_200(data=data, exclude={'password'})
4533

4634

4735
@router.put('/{username}', summary='更新用户信息')
@@ -60,9 +48,11 @@ async def update_avatar(username: str, avatar: Avatar, current_user: CurrentUser
6048
return response_base.fail()
6149

6250

63-
@router.get('', summary='获取所有用户', dependencies=[DependsUser])
64-
async def get_all_users() -> Page[GetUserInfo]:
65-
return await UserService.get_user_list()
51+
@router.get('', summary='获取所有用户', dependencies=[DependsUser, PageDepends])
52+
async def get_all_users(db: CurrentSession):
53+
user_list = await UserService.get_user_list()
54+
page_data = await paging_data(db, user_list, GetUserInfo)
55+
return response_base.response_200(data=page_data)
6656

6757

6858
@router.post('/{pk}/super', summary='修改用户超级权限', dependencies=[DependsSuperUser])

0 commit comments

Comments
 (0)