Files
DjangoAdmin_FastAPI_EleVue/docs/ARCHITECTURE.md
T
2026-09-12 17:39:13 +08:00

420 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 系统架构说明
本文档详细说明 DjangoAdmin FastAPI + EleVue 版本的整体架构设计、分层模式、核心模块以及关键设计决策。
## 📐 整体架构
### 前后端分离架构
```
┌─────────────────────────────────────────────────────────────┐
│ 前端 (Vue 3) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Vue 3 │ │ Element │ │ Pinia │ │ Vue │ │
│ │ + TS │ │ Plus │ │ 状态管理 │ │ Router │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ ↕ Axios HTTP Client (Bearer Token) │
└─────────────────────────────────────────────────────────────┘
↕ JSON / JWT
┌─────────────────────────────────────────────────────────────┐
│ 后端 (FastAPI) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 中间件链 │ │
│ │ CORS → Redis注入 → 限流 → DB会话 → 操作日志 → 日志 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ API 路由层 │ │
│ │ /api/v1/auth/* (免认证) /api/v1/* (login_required) │ │
│ │ @permission_required @check_demo │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 业务模块层 │ │
│ │ Service → Repository → Model │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↕ ↕ │
│ ┌──────────┐ ┌──────────┐ │
│ │ MySQL / │ │ Redis │ │
│ │ PG / │ │ 缓存 │ │
│ │ MSSQL / │ │ 会话 │ │
│ │ Oracle │ │ 黑名单 │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## 🔄 请求处理流程
### 完整请求生命周期
```
1. 客户端发起 HTTP 请求
↓
2. CORS 中间件(src/middleware/cors.py)
- 处理跨域预检请求
↓
3. Redis 注入中间件(src/core/redis_client.py: redis_middleware)
- 将异步 Redis 客户端注入 contextvars
↓
4. 限流中间件(src/middleware/rate_limit.py)
- 基于 Redis 滑动窗口的 per-IP 限流
- 超限返回 {code: 1} 标准响应
↓
5. DB 会话中间件(src/middleware/db_session.py)
- 创建请求级数据库会话
- 请求结束后自动 commit/rollback
↓
6. 操作日志中间件(src/middleware/operation_log.py)
- 记录写操作(POST/PUT/DELETE)的行为日志
↓
7. 路由匹配
- auth 路由(/api/v1/auth/*):免认证
- v1 路由(/api/v1/*):login_required 依赖校验 JWT
↓
8. 权限装饰器 @permission_required
- 校验当前用户是否有该接口的权限
- 用户 ID 1(admin)自动跳过权限检查
↓
9. 演示模式装饰器 @check_demo
- 演示环境下拦截写操作
↓
10. 端点处理函数
- 参数由 Pydantic 自动校验
- 调用 Service 层业务逻辑
- 返回 R.ok() / R.failed() 统一响应
↓
11. 异常处理
- RequestValidationError → {code: 1, msg: "field: message"}
- BusinessException → {code: 1, msg: "业务错误"}
- AuthorizationException → {code: 401, msg: "认证失败"}
```
## 📦 分层模块模式
### 四层架构
每个业务模块遵循统一的四层架构设计:
```
src/modules/{group}/{name}/
├── models.py # 数据模型层 — SQLAlchemy ORM 模型定义
├── schemas.py # 数据校验层 — Pydantic 请求/响应模型
├── repository.py # 数据访问层 — 通用 CRUD 与自定义查询
└── service.py # 业务逻辑层 — 事务管理、缓存、业务规则
```
#### Model 层(数据模型)
```python
# src/modules/system/level/models.py
from core.base_model import base_model
from core.base_db import base_db
from core.config.database import DB_PREFIX
class SysLevel(base_model, base_db):
"""职级表"""
__tablename__ = DB_PREFIX + "level"
name = Column(String(100), nullable=False, comment="职级名称")
code = Column(String(50), nullable=False, comment="职级编码")
sort = Column(Integer, default=0, comment="排序")
status = Column(Integer, default=1, comment="状态 1=启用 0=禁用")
remark = Column(String(500), comment="备注")
```
- 继承 `base_model`:提供 `id`, `create_user`, `create_time`, `update_user`, `update_time`, `is_delete`
- 继承 `base_db`:提供 `save()`, `delete()` 实例方法
- 表名使用 `DB_PREFIX` 前缀,支持多项目共库
#### Schema 层(数据校验)
```python
# src/modules/system/level/schemas.py
from pydantic import BaseModel, Field, field_validator
class LevelForm(BaseModel):
"""职级表单"""
id: Optional[int] = Field(None, description="ID")
name: str = Field(..., min_length=1, max_length=100, description="职级名称")
code: str = Field(..., min_length=1, max_length=50, description="职级编码")
sort: int = Field(0, ge=0, description="排序")
status: int = Field(1, description="状态")
remark: Optional[str] = Field(None, max_length=500, description="备注")
@field_validator('name')
@classmethod
def validate_name(cls, v):
if not v.strip():
raise ValueError("职级名称不能为空")
return v.strip()
```
- 继承 Pydantic `BaseModel`
- 使用 `Field(...)` 定义约束(`min_length`, `max_length`, `ge`, `le`)
- 使用 `@field_validator` 自定义校验逻辑
#### Repository 层(数据访问)
```python
# src/modules/system/level/repository.py
from core.base_repository import BaseRepository
from .models import SysLevel
class LevelRepository(BaseRepository[SysLevel]):
model = SysLevel
level_repo = LevelRepository()
```
- 继承 `BaseRepository[Model]`,自动获得通用 CRUD 方法
- 模块级单例 `{name}_repo`,全局复用
- 基类提供:`get_by_id`, `get_one`, `get_all`, `exists`, `exists_by_field`, `count`, `filter`, `filter_by`, `create`, `update`, `paginate`, `batch_delete`
- 等值查询自动软删过滤且跳过 None 值
#### Service 层(业务逻辑)
```python
# src/modules/system/level/service.py
from core.base_service import BaseService
from .models import SysLevel
from .repository import level_repo
class LevelService(BaseService[SysLevel]):
repo = level_repo
model = SysLevel
page_like_fields = ["name", "code"]
page_eq_fields = ["status"]
page_order_by = ["sort", "id"]
unique_fields = [("name", "职级名称"), ("code", "职级编码")]
level_service = LevelService()
```
- 继承 `BaseService[Model]`,声明差异即可获得完整 CRUD
- 可覆盖钩子:`_apply_page_filters`, `_serialize`, `_build_create_fields`, `_build_update_fields`
- 删除统一使用 `BaseService.batch_delete_with_r(repo, ids)` 静态方法
### HTTP 层(API 端点)
```python
# src/api/v1/endpoints/level.py
from fastapi import APIRouter, Request
from core.access_decorators import permission_required, check_demo
router = APIRouter()
@router.get('/list')
@permission_required("sys:level:list")
async def list(request: Request):
"""职级列表"""
return await level_service.page_list(request)
@router.post('/add')
@permission_required("sys:level:add")
@check_demo
async def add(request: Request, data: LevelForm):
"""新增职级"""
return await level_service.add(request, data)
```
- 路由定义、权限装饰器、请求/响应处理
- 无业务逻辑,纯委托给 Service 层
## 🔐 认证与授权
### JWT 认证流程
```
1. 登录(/api/v1/auth/login)
- 验证用户名 + 密码(bcrypt)
- 验证图形验证码
- 生成 JWT Token(HS256 签名)
- 返回 {access_token, token_type}
2. 请求认证(login_required 依赖)
- 从 Authorization: Bearer <token> 提取 Token
- 解析 JWT,验证签名与过期时间
- 检查 Token 是否在 Redis 黑名单中
- 设置 request.state.user_id / username / realname
3. Token 刷新(/api/v1/auth/refresh)
- 使用当前 Token 获取新 Token
- 旧 Token 加入 Redis 黑名单
4. 登出(/api/v1/auth/logout)
- 当前 Token 加入 Redis 黑名单
```
### RBAC 权限模型
```
用户(User) ←→ 角色(Role) ←→ 菜单(Menu)
│ │ │
└─ user_role └─ role_menu └─ 权限标识
(sys:level:list)
```
- **用户-角色**:多对多关系(`sys_user_role` 中间表)
- **角色-菜单**:多对多关系(`sys_role_menu` 中间表)
- **菜单**:存储权限标识(如 `sys:level:list`),支持目录/菜单/按钮三种类型
- **权限检查**:`@permission_required("sys:level:list")` 装饰器在端点执行前校验
- **管理员豁免**:用户 ID 1(admin)自动跳过所有权限检查
## 🗄️ 数据库设计
### 多数据库支持
框架通过 `DB_DRIVER` 环境变量切换数据库方言:
```python
# src/core/config/database.py
DB_DRIVER = os.getenv("DB_DRIVER", "mysql")
# 驱动注册表
DRIVERS = {
"mysql": "pymysql",
"postgresql": "psycopg",
"mssql": "pymssql",
"oracle": "oracledb",
"sqlite": "sqlite3",
}
```
- SQLAlchemy 引擎根据驱动自动选择方言
- Model 层使用 SQLAlchemy 声明式映射,与具体数据库解耦
- Alembic 迁移脚本自动适配方言 DDL
### 软删除机制
所有业务表使用 `is_delete` 字段实现软删除:
- `is_delete = 0`:正常记录
- `is_delete = 1`:已删除记录
Repository 基类的等值查询方法(`get_one`, `get_all`, `count`, `filter_by`, `exists_by_field`)自动过滤已删除记录。
### 跨库数据迁移
`scripts/migrate_db.py` 支持从 MySQL 迁移数据到任意目标数据库:
1. 拓扑排序确定表依赖关系
2. 分块 `SELECT * ... ORDER BY id` + `executemany` 显式带 ID 插入
3. 自增序列重置(PG `setval` / MSSQL `DBCC CHECKIDENT`)
4. 逐表 `COUNT(*)` 校验数据一致性
## ⚡ 同步/异步约定
| 场景 | 方式 | 说明 |
|---|---|---|
| 纯 DB CRUD | 同步 `def` | FastAPI 自动在线程池执行,不阻塞事件循环 |
| 需要 `await`(Redis/异步IO) | `async def` + `asyncio.to_thread` | 同步操作用 `to_thread` 包裹 |
| 批量删除端点 | `async def` | 需 `await parse_batch_ids(request)` 异步读请求体 |
**禁止**在 `async def` 端点或 Service 内直接调用同步 DB 查询(会阻塞事件循环)。
## 📁 目录结构
```
DjangoAdmin_FastAPI_EleVue/
├── src/ # 后端源码
│ ├── main.py # 应用入口
│ ├── core/ # 核心基础设施
│ │ ├── app.py # 应用工厂(create_app)
│ │ ├── config/ # 配置模块(app/database/redis/auth/captcha/email/sms)
│ │ ├── database.py # 引擎/会话/上下文
│ │ ├── redis_client.py # Redis 异步客户端
│ │ ├── jwt.py # JWT 认证
│ │ ├── password.py # 密码加密
│ │ ├── response.py # 统一响应封装
│ │ ├── exceptions.py # 异常定义
│ │ ├── security.py # 安全工具
│ │ ├── access_decorators.py # 权限/演示模式装饰器
│ │ ├── logger.py # 日志配置
│ │ ├── base_model.py # ORM 基础模型
│ │ ├── base_db.py # 数据库操作基类
│ │ ├── base_repository.py # Repository 基类
│ │ └── base_service.py # Service 基类
│ ├── api/ # API 路由层
│ │ ├── __init__.py # 路由注册(register_router)
│ │ ├── deps.py # login_required 依赖
│ │ └── v1/
│ │ ├── router.py # 路由汇总
│ │ └── endpoints/ # 各模块端点定义
│ ├── modules/ # 业务模块层
│ │ ├── auth/ # 认证模块
│ │ ├── system/ # 系统管理(user/role/menu/dept/position/level)
│ │ ├── dictionary/ # 字典管理
│ │ ├── configuration/ # 配置管理
│ │ ├── article/ # 文章管理
│ │ ├── notice/ # 通知公告
│ │ ├── job/ # 定时任务
│ │ └── ... # 其他业务模块
│ ├── middleware/ # HTTP 中间件
│ │ ├── cors.py # 跨域处理
│ │ ├── rate_limit.py # 限流中间件
│ │ ├── db_session.py # DB 会话管理
│ │ ├── operation_log.py # 操作日志
│ │ └── logger.py # 请求日志
│ ├── constants/ # 常量与枚举
│ └── utils/ # 工具函数
├── ui/ # 前端源码(Vue 3 + TypeScript)
├── scripts/ # 运维脚本
│ ├── init_db.py # 数据库初始化
│ ├── seed_dict.py # 字典数据种子
│ ├── migrate_db.py # 跨库数据迁移
│ └── demo_job_loop.py # 定时任务演示
├── tests/ # 测试代码
│ ├── contract/ # 契约快照测试
│ ├── unit/ # 单元测试
│ └── integration/ # 集成测试
├── migrations/ # Alembic 迁移脚本
├── document/ # 项目文档
├── .env # 环境变量配置
├── docker-compose.yml # Docker 编排
├── Dockerfile # Docker 镜像构建
├── Makefile # 常用命令
├── requirements.txt # Python 依赖
├── alembic.ini # Alembic 配置
└── pytest.ini # 测试配置
```
## 🔧 核心设计决策
### 1. 为什么选择 Repository + Service 双层?
- **Repository** 封装数据访问细节,提供通用 CRUD,隔离 ORM 实现
- **Service** 封装业务逻辑,管理事务、缓存、权限等横切关注点
- 两层分离使得业务逻辑可独立测试,数据访问可独立优化
### 2. 为什么使用 contextvars 管理 DB 会话?
- FastAPI 的依赖注入不适合跨层级传递数据库会话
- `contextvars` 提供协程安全的请求级上下文
- 中间件创建会话,`get_db_session()` 在任意层级获取当前会话
- 请求结束自动 commit/rollback,无需手动管理
### 3. 为什么使用软删除?
- 保留历史数据,支持数据审计和恢复
- 避免外键级联删除导致数据丢失
- Repository 基类自动过滤,业务层无需关心
### 4. 为什么 BaseRepository 等值查询跳过 None?
- 避免 `filter_by(name=None)` 生成 `WHERE name = NULL`(SQL 中应为 `IS NULL`)
- 简化 Service 层的条件构建,无需逐一判断 None
- 提供 `filter()` 原始出口供复杂查询使用
## 📚 相关文档
- [部署指南](DEPLOYMENT.md) — Docker / 手动 / 生产环境部署
- [数据库迁移](../document/数据库迁移_runbook.md) — 跨库迁移操作手册
- [开发文档](http://docs.fastapi.elevue.djangoadmin.cn) — 在线开发文档