Files
djangoadmin-wiki/DjangoAdmin_Flask_NaiveVue/zh/volume3/database-design.md
T
2026-09-12 23:12:15 +08:00

401 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.
# 数据库设计
本章详细描述项目的数据库设计,包括核心表结构、通用字段规范、ER 关系和命名约定。
:::tip 设计原则
所有业务表继承 `BaseModel` 基类,自动获得 6 个通用字段(id / create_user / create_time / update_user / update_time / is_delete)。表名使用 `DB_PREFIX` 前缀(默认 `flask_`),软删除通过 `is_delete` 字段实现。
:::
## 通用字段规范
所有业务表共享以下通用字段,由 `BaseModel` 基类自动提供:
| 字段名 | 类型 | 约束 | 默认值 | 说明 |
|:---|:---|:---|:---|:---|
| `id` | INT | PRIMARY KEY, AUTO_INCREMENT | — | 主键自增 |
| `create_user` | VARCHAR(50) | NULLABLE | NULL | 创建人(realname) |
| `create_time` | DATETIME | NOT NULL | CURRENT_TIMESTAMP | 创建时间(UTC) |
| `update_user` | VARCHAR(50) | NULLABLE | NULL | 更新人(realname) |
| `update_time` | DATETIME | NOT NULL | CURRENT_TIMESTAMP | 更新时间(UTC,onupdate) |
| `is_delete` | INT | NOT NULL, INDEX | 0 | 软删除:0=正常,1=已删除 |
## 核心表结构
### 系统管理模块
#### 用户表 `flask_user`
| 字段名 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| id | INT | PK | 用户ID |
| username | VARCHAR(50) | NOT NULL, UNIQUE | 登录账号 |
| password | VARCHAR(200) | NOT NULL | 密码(bcrypt 哈希) |
| salt | VARCHAR(50) | NULLABLE | 密码盐值 |
| realname | VARCHAR(50) | NULLABLE | 真实姓名 |
| avatar | VARCHAR(500) | NULLABLE | 头像地址 |
| email | VARCHAR(100) | NULLABLE | 邮箱 |
| mobile | VARCHAR(20) | NULLABLE | 手机号 |
| gender | INT | DEFAULT 0 | 性别:0=未知 1=男 2=女 |
| dept_id | INT | NULLABLE, INDEX | 部门ID |
| position_id | INT | NULLABLE, INDEX | 岗位ID |
| level_id | INT | NULLABLE, INDEX | 职级ID |
| status | INT | DEFAULT 1 | 状态:1=正常 2=禁用 |
| create_user | VARCHAR(50) | NULLABLE | 通用字段 |
| create_time | DATETIME | DEFAULT NOW() | 通用字段 |
| update_user | VARCHAR(50) | NULLABLE | 通用字段 |
| update_time | DATETIME | DEFAULT NOW() | 通用字段 |
| is_delete | INT | DEFAULT 0, INDEX | 通用字段 |
#### 角色表 `flask_role`
| 字段名 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| id | INT | PK | 角色ID |
| name | VARCHAR(100) | NOT NULL, INDEX | 角色名称 |
| code | VARCHAR(100) | NOT NULL, UNIQUE | 角色编码 |
| status | INT | DEFAULT 1 | 状态:1=正常 2=禁用 |
| sort | INT | DEFAULT 0 | 排序 |
| remark | VARCHAR(500) | NULLABLE | 备注 |
| + 通用字段 | | | |
#### 菜单表 `flask_menu`
| 字段名 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| id | INT | PK | 菜单ID |
| parent_id | INT | DEFAULT 0, INDEX | 父菜单ID(0=顶级) |
| name | VARCHAR(100) | NOT NULL | 菜单名称 |
| type | INT | NOT NULL | 类型:1=目录 2=菜单 3=按钮 |
| path | VARCHAR(200) | NULLABLE | 路由路径 |
| component | VARCHAR(200) | NULLABLE | 前端组件路径 |
| permission | VARCHAR(200) | NULLABLE | 权限标识(如 sys:user:add) |
| icon | VARCHAR(100) | NULLABLE | 菜单图标 |
| sort | INT | DEFAULT 0 | 排序 |
| visible | INT | DEFAULT 1 | 是否可见:1=是 2=否 |
| status | INT | DEFAULT 1 | 状态:1=正常 2=禁用 |
| + 通用字段 | | | |
#### 部门表 `flask_dept`
| 字段名 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| id | INT | PK | 部门ID |
| parent_id | INT | DEFAULT 0, INDEX | 父部门ID(0=顶级) |
| name | VARCHAR(100) | NOT NULL | 部门名称 |
| leader | VARCHAR(50) | NULLABLE | 负责人 |
| mobile | VARCHAR(20) | NULLABLE | 联系电话 |
| email | VARCHAR(100) | NULLABLE | 邮箱 |
| sort | INT | DEFAULT 0 | 排序 |
| status | INT | DEFAULT 1 | 状态:1=正常 2=禁用 |
| + 通用字段 | | | |
#### 岗位表 `flask_position`
| 字段名 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| id | INT | PK | 岗位ID |
| name | VARCHAR(255) | NOT NULL, INDEX | 岗位名称 |
| status | INT | DEFAULT 0, INDEX | 状态:1=在用 2=停用 |
| sort | INT | DEFAULT 0 | 排序 |
| + 通用字段 | | | |
#### 职级表 `flask_level`
| 字段名 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| id | INT | PK | 职级ID |
| name | VARCHAR(255) | NOT NULL, INDEX | 职级名称 |
| status | INT | DEFAULT 0, INDEX | 状态:1=在用 2=停用 |
| sort | INT | DEFAULT 0 | 排序 |
| + 通用字段 | | | |
### 关联表
#### 用户角色关联表 `flask_user_role`
| 字段名 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| id | INT | PK | 主键 |
| user_id | INT | NOT NULL, INDEX | 用户ID |
| role_id | INT | NOT NULL, INDEX | 角色ID |
| + 通用字段 | | | |
#### 角色菜单关联表 `flask_role_menu`
| 字段名 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| id | INT | PK | 主键 |
| role_id | INT | NOT NULL, INDEX | 角色ID |
| menu_id | INT | NOT NULL, INDEX | 菜单ID |
| + 通用字段 | | | |
### 数据字典模块
#### 字典表 `flask_dict`
| 字段名 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| id | INT | PK | 字典ID |
| name | VARCHAR(200) | NOT NULL | 字典名称 |
| code | VARCHAR(200) | NOT NULL, UNIQUE | 字典编码 |
| status | INT | DEFAULT 1 | 状态 |
| remark | VARCHAR(500) | NULLABLE | 备注 |
| + 通用字段 | | | |
#### 字典项表 `flask_dict_item`
| 字段名 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| id | INT | PK | 字典项ID |
| dict_code | VARCHAR(200) | NOT NULL, INDEX | 所属字典编码 |
| label | VARCHAR(200) | NOT NULL | 显示名 |
| value | VARCHAR(200) | NOT NULL | 字典值 |
| sort | INT | DEFAULT 0 | 排序 |
| status | INT | DEFAULT 1 | 状态 |
| remark | VARCHAR(500) | NULLABLE | 备注 |
| + 通用字段 | | | |
### 内容管理模块
#### 文章表 `flask_article`
| 字段名 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| id | INT | PK | 文章ID |
| category_id | INT | NULLABLE, INDEX | 分类ID |
| title | VARCHAR(200) | NOT NULL | 文章标题 |
| cover | VARCHAR(500) | NULLABLE | 封面图片 |
| summary | VARCHAR(500) | NULLABLE | 摘要 |
| content | TEXT | NULLABLE | 正文(富文本) |
| author | VARCHAR(50) | NULLABLE | 作者 |
| source | VARCHAR(200) | NULLABLE | 来源 |
| view_count | INT | DEFAULT 0 | 浏览次数 |
| is_top | INT | DEFAULT 0 | 是否置顶 |
| status | INT | DEFAULT 1 | 状态 |
| sort | INT | DEFAULT 0 | 排序 |
| + 通用字段 | | | |
#### 文章分类表 `flask_category`
| 字段名 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| id | INT | PK | 分类ID |
| parent_id | INT | DEFAULT 0, INDEX | 父分类ID |
| name | VARCHAR(100) | NOT NULL | 分类名称 |
| sort | INT | DEFAULT 0 | 排序 |
| status | INT | DEFAULT 1 | 状态 |
| + 通用字段 | | | |
#### 通知公告表 `flask_notice`
| 字段名 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| id | INT | PK | 通知ID |
| title | VARCHAR(200) | NOT NULL | 标题 |
| content | TEXT | NULLABLE | 内容 |
| type | INT | DEFAULT 1 | 类型:1=通知 2=公告 |
| status | INT | DEFAULT 1 | 状态 |
| + 通用字段 | | | |
### 日志模块
#### 登录日志表 `flask_login_log`
| 字段名 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| id | INT | PK | 日志ID |
| username | VARCHAR(50) | NULLABLE | 登录账号 |
| ip | VARCHAR(50) | NULLABLE | 登录IP |
| location | VARCHAR(200) | NULLABLE | 登录地点 |
| browser | VARCHAR(100) | NULLABLE | 浏览器 |
| os | VARCHAR(100) | NULLABLE | 操作系统 |
| status | INT | DEFAULT 1 | 状态:1=成功 2=失败 |
| msg | VARCHAR(500) | NULLABLE | 提示消息 |
| + 通用字段 | | | |
#### 操作日志表 `flask_operation_log`
| 字段名 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| id | INT | PK | 日志ID |
| username | VARCHAR(50) | NULLABLE | 操作人 |
| module | VARCHAR(100) | NULLABLE | 操作模块 |
| action | VARCHAR(100) | NULLABLE | 操作动作 |
| method | VARCHAR(10) | NULLABLE | 请求方法 |
| url | VARCHAR(500) | NULLABLE | 请求URL |
| params | TEXT | NULLABLE | 请求参数 |
| ip | VARCHAR(50) | NULLABLE | 操作IP |
| status | INT | DEFAULT 1 | 状态 |
| duration | INT | NULLABLE | 耗时(毫秒) |
| + 通用字段 | | | |
### 定时任务模块
#### 任务表 `flask_job`
| 字段名 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| id | INT | PK | 任务ID |
| name | VARCHAR(200) | NOT NULL | 任务名称 |
| job_group | VARCHAR(100) | NULLABLE | 任务分组 |
| invoke_target | VARCHAR(500) | NOT NULL | 调用目标 |
| cron_expression | VARCHAR(200) | NOT NULL | Cron 表达式 |
| misfire_policy | INT | DEFAULT 3 | 执行策略 |
| concurrent | INT | DEFAULT 0 | 是否并发 |
| status | INT | DEFAULT 1 | 状态:1=运行 2=暂停 |
| remark | VARCHAR(500) | NULLABLE | 备注 |
| + 通用字段 | | | |
#### 任务日志表 `flask_job_log`
| 字段名 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| id | INT | PK | 日志ID |
| job_id | INT | NOT NULL, INDEX | 任务ID |
| job_name | VARCHAR(200) | NULLABLE | 任务名称 |
| invoke_target | VARCHAR(500) | NULLABLE | 调用目标 |
| job_message | VARCHAR(500) | NULLABLE | 执行结果 |
| status | INT | DEFAULT 1 | 状态 |
| exception_info | TEXT | NULLABLE | 异常信息 |
| start_time | DATETIME | NULLABLE | 开始时间 |
| end_time | DATETIME | NULLABLE | 结束时间 |
| + 通用字段 | | | |
## ER 关系图
```
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ flask │ │ flask │ │ flask │
│ _user │ │ _user_role │ │ _role │
│──────────────│ │──────────────│ │──────────────│
│ id (PK) │←───│ user_id (FK) │ │ id (PK) │
│ username │ │ role_id (FK) │───→│ name │
│ password │ └──────────────┘ │ code │
│ realname │ └──────┬───────┘
│ dept_id (FK) │───→┌──────────────┐ │
│ position_id │ │ flask │ │
│ (FK) │ │ _dept │ │
│ level_id (FK)│ │──────────────│ │
│ │ │ id (PK) │ │
│ │ │ parent_id │ │
│ │ │ name │ │
│ │ └──────────────┘ │
│ │ │
│ │ ┌──────────────┐ │
│ │ │ flask │ │
│ │ │ _role_menu │ │
│ │ │──────────────│ │
│ │ │ role_id (FK) │←───────────┘
│ │ │ menu_id (FK) │───→┌──────────────┐
│ │ └──────────────┘ │ flask │
└──────────────┘ │ _menu │
│──────────────│
┌──────────────┐ │ id (PK) │
│ flask │ │ parent_id │
│ _position │ │ name │
│──────────────│ │ permission │
│ id (PK) │ │ type │
│ name │ └──────────────┘
└──────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ flask │ │ flask │ │ flask │
│ _dict │ │ _dict_item │ │ _config │
│──────────────│ │──────────────│ │──────────────│
│ id (PK) │←───│ dict_code │ │ id (PK) │
│ name │ │ label │ │ name │
│ code │ │ value │ │ code │
└──────────────┘ └──────────────┘ └──────────────┘
┌──────────────┐ ┌──────────────┐
│ flask │ │ flask │
│ _article │ │ _category │
│──────────────│ │──────────────│
│ id (PK) │ │ id (PK) │
│ category_id │───→│ parent_id │
│ title │ │ name │
│ content │ └──────────────┘
└──────────────┘
```
## 命名约定
| 约定项 | 规范 | 示例 |
|:---|:---|:---|
| 表名 | `{DB_PREFIX}{module_name}` | `flask_position` |
| 字段名 | 小写下划线(snake_case) | `create_user`、`dict_code` |
| 主键 | `id`,INT,AUTO_INCREMENT | — |
| 外键 | `{关联表名}_id` | `user_id`、`dept_id` |
| 索引 | 外键字段、高频查询字段 | `status`、`username` |
| 布尔状态 | INT 类型,0/1 或 1/2 | `is_delete: 0/1`、`status: 1/2` |
| 时间字段 | DATETIME 类型 | `create_time`、`update_time` |
:::warning 外键约束
不使用数据库层面的外键约束(FOREIGN KEY),而是通过应用层代码维护引用完整性。这样做的好处是:简化跨库迁移、避免级联删除风险、提升写入性能。删除关联数据时,由 Service 层的 `before_delete` 钩子检查引用关系。
:::
## 多数据库驱动支持
通过 `DB_DRIVER` 环境变量切换数据库驱动,`config/database.py` 的 `_build_database_url()` 函数按驱动构建方言感知的 SQLAlchemy 引擎:
| 驱动 | DB_DRIVER | 连接 URL 格式 | 特殊处理 |
|:---|:---|:---|:---|
| MySQL | `mysql`(默认) | `mysql+pymysql://user:pass@host:3306/db` | `CLIENT.FOUND_ROWS` 避免重复提交时 rowcount=0 误判 |
| PostgreSQL | `postgresql` | `postgresql+psycopg://user:pass@host:5432/db` | `options="-c timezone=UTC"` 确保 naive UTC 一致 |
| SQL Server | `mssql` | `mssql+pymssql://user:pass@host:1433/db` | `login_timeout` 连接超时 |
| SQLite | `sqlite` | `sqlite:///./db.sqlite3` | `check_same_thread=False`,跳过连接池参数 |
| Oracle | `oracle` | `oracle+oracledb://user:pass@host:1521/db` | thin 模式,UTF-8 编码 |
业务代码无需修改,SQLAlchemy 自动处理方言差异(如分页语法、自增策略等)。
### 请求级会话管理
Flask 使用 `g` 对象和 `before_request` / `after_request` 钩子管理数据库会话:
```python
# src/middleware/transaction.py
from flask import request, g
from src.core.database import db
def register_transaction(app):
@app.before_request
def _transaction_begin():
g.transaction_active = True
@app.after_request
def _transaction_commit(response):
if request.method in ('POST', 'PUT', 'DELETE', 'PATCH'):
if response.status_code == 200:
body = response.get_json()
if body and body.get('code') == 0:
db.session.commit()
else:
db.session.rollback()
return response
@app.teardown_request
def _transaction_rollback(exception):
if exception:
db.session.rollback()
db.session.remove()
```
:::tip 约束命名约定
`database.py` 中定义了自定义约束命名约定,索引前缀使用 `idx_` 而非默认的 `ix_`:
```python
convention = {
"ix": "idx_%(table_name)s_%(column_0_name)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s",
}
metadata = MetaData(naming_convention=convention)
Base = declarative_base(metadata=metadata)
```
:::
## 总结
数据库设计以「通用字段 + 软删除 + 应用层外键」为核心策略。所有业务表继承 `BaseModel` 获得统一的通用字段,通过 `is_delete` 实现软删除,通过应用层代码维护引用完整性。核心表覆盖系统管理(用户/角色/菜单/部门/岗位/职级)、数据字典、内容管理(文章/分类/通知)、日志管理、定时任务等功能域。多数据库驱动通过 `config/database.py` 的 `_build_database_url()` 按方言构建引擎,业务代码与具体数据库解耦。请求级会话通过 Flask 的 `g` 对象和 `before_request` / `after_request` 钩子实现,审计数据可通过 `commit_independently()` 绕过请求事务独立提交。