Files
rxthinkcmf-wiki/RXThinkCMF_Laravel13_AntdVue/zh/architecture/database-design.md
T
2026-10-08 17:50:29 +08:00

415 lines
15 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.
# 4.9 数据库设计
:::tip 概述
项目支持 MySQL、PostgreSQL、SQL Server、Oracle、SQLite 五种数据库,通过 `.env` 的 `DB_CONNECTION` 一键切换,`document/` 下提供五套初始化脚本。所有数据库共用同一套代码,无需修改业务逻辑。本章涵盖命名规范、表结构设计、字段说明、索引策略和多数据库适配。
:::
## 命名规范
| 对象 | 规范 | 示例 |
|------|------|------|
| 表名 | 不含前缀的业务名 + `DB_PREFIX`(默认 `laravel_`) | `laravel_user`、`laravel_article` |
| 模型 `$table` | **不含前缀** | `protected $table = 'user'` |
| 字段名 | 小写下划线 | `create_time`、`user_name` |
| 主键 | 统一 `id`,INT 自增 | `id INT PRIMARY KEY` |
| 外键 | `表名_id` | `user_id`、`role_id` |
| 索引 | `idx_字段名` | `idx_status`、`idx_tenant_id` |
| 唯一索引 | `uk_含义`(常含 `is_delete` 协同) | `uk_tenant_username` |
## 公共字段
所有业务表**必须**包含以下公共字段,由模型 Trait 自动维护:
```sql
-- MySQL 格式
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
is_delete TINYINT(1) DEFAULT 0 COMMENT '软删除 0=正常 1=已删除',
create_user VARCHAR(50) DEFAULT '' COMMENT '创建人',
create_time DATETIME COMMENT '创建时间',
update_user VARCHAR(50) DEFAULT '' COMMENT '更新人',
update_time DATETIME COMMENT '更新时间'
```
| 字段 | 自动维护方 | 说明 |
|------|-----------|------|
| `is_delete` | `SoftDeletesByFlag` | `softDelete()` 设置为 1,`SoftDeleteFlagScope` 全局过滤 |
| `create_user` | `HasAuditColumns` creating 事件 | 取 `Auth::username()`,已传入则不覆盖 |
| `create_time` | `HasAuditColumns` creating 事件 | 取 `now()`,已传入则不覆盖 |
| `update_user` | `HasAuditColumns` updating 事件 | 取 `Auth::username()` |
| `update_time` | `HasAuditColumns` updating 事件 | 始终覆盖为当前时间 |
## 核心表清单(25 张)
### 用户与权限(5 张)
| 表名 | 说明 | Service 基类 |
|------|------|-----------|
| `laravel_user` | 用户表 | `BaseTenantService` |
| `laravel_user_role` | 用户角色关联表(N:N 中间表) | — |
| `laravel_role` | 角色表 | `BaseTenantService` |
| `laravel_role_menu` | 角色菜单关联表(N:N 中间表) | — |
| `laravel_menu` | 菜单/权限表(菜单即权限) | `BaseService` |
**关联关系:**
```text
laravel_user ──┐
│ N:N (laravel_user_role)
laravel_role ──┤
│ N:N (laravel_role_menu)
laravel_menu ──┘
laravel_dept ←── laravel_user.dept_id
laravel_tenant ←── laravel_user.tenant_id
```
### 组织架构(3 张)
| 表名 | 说明 | Service 基类 |
|------|------|-----------|
| `laravel_dept` | 部门表(树形,parent_id) | `BaseTenantService` |
| `laravel_position` | 岗位表 | `BaseTenantService` |
| `laravel_level` | 职级表 | `BaseTenantService` |
### 租户(1 张)
| 表名 | 说明 | Service 基类 |
|------|------|-----------|
| `laravel_tenant` | 租户表(状态、过期时间、用户数限制) | `BaseService` |
### 系统配置(5 张)
| 表名 | 说明 | Service 基类 |
|------|------|-----------|
| `laravel_dict` | 字典表 | `BaseService` |
| `laravel_dict_item` | 字典项表 | `BaseService` |
| `laravel_config` | 配置组表 | `BaseService` |
| `laravel_config_item` | 配置项表 | `BaseService` |
| `laravel_param` | 系统参数表 | `BaseService` |
**关联关系:**
```text
laravel_dict ←── laravel_dict_item.dict_id
laravel_config ←── laravel_config_item.config_id
```
### 内容管理(4 张)
| 表名 | 说明 | Service 基类 |
|------|------|-----------|
| `laravel_article` | 文章表 | `BaseTenantService` |
| `laravel_category` | 文章分类表(树形) | `BaseTenantService` |
| `laravel_link` | 友情链接表 | `BaseTenantService` |
| `laravel_notice` | 通知公告表 | `BaseTenantService` |
### 日志审计(2 张)
| 表名 | 说明 | Service 基类 |
|------|------|-----------|
| `laravel_login_log` | 登录日志表 | `BaseService` |
| `laravel_operation_log` | 操作日志表 | `BaseService` |
### 定时任务(2 张)
| 表名 | 说明 | Service 基类 |
|------|------|-----------|
| `laravel_job` | 定时任务表 | `BaseService` |
| `laravel_job_log` | 定时任务执行日志表 | `BaseService` |
**关联关系:**
```text
laravel_job ←── laravel_job_log.job_id
```
### 基础数据(3 张)
| 表名 | 说明 | Service 基类 |
|------|------|-----------|
| `laravel_city` | 城市行政区划表(树形) | `BaseService` |
| `laravel_file_template` | 文件模板表 | `BaseService` |
| `laravel_example` | 案例演示表(生成器参考模板) | `BaseService` |
:::tip 租户隔离范围
继承 `BaseTenantService` 的 9 个服务:User / Role / Dept / Position / Level / Article / Category / Notice / Link——业务数据按租户隔离;系统共享数据(字典、配置、菜单、日志等)继承 `BaseService` 不隔离。
:::
## 关键表字段说明
### laravel_user(用户表)
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | INT | 主键 |
| `tenant_id` | INT | 租户 ID(0=平台用户) |
| `code` | VARCHAR(100) | 用户编码 |
| `username` | VARCHAR(30) | 用户名(登录账号) |
| `password` | VARCHAR(255) | 密码(bcrypt 加密) |
| `salt` | VARCHAR(30) | 加密盐(外部盐,与 password 分离存储) |
| `realname` | VARCHAR(150) | 用户姓名 |
| `gender` | INT | 性别:1-男 2-女 3-保密 |
| `mobile` / `email` | VARCHAR(30) | 手机号 / 邮箱 |
| `avatar` | VARCHAR(255) | 用户头像 |
| `status` | INT | 状态:1-正常 2-禁用 |
| `dept_id` / `level_id` / `position_id` | INT | 部门 / 职级 / 岗位 ID |
| `province_code` / `city_code` / `district_code` / `street_code` | VARCHAR(30) | 省市县街道编码 |
| `city_info` | VARCHAR(255) | 省市区信息拼接 |
| `address` | VARCHAR(255) | 详细地址 |
| `intro` / `note` | VARCHAR(255) | 个人简介 / 备注 |
| `sort` | INT | 排序 |
| `is_delete` + 四个审计字段 | | 公共字段 |
**唯一索引(与软删除协同):**
```sql
UNIQUE INDEX uk_tenant_username (tenant_id, username, is_delete)
UNIQUE INDEX uk_tenant_mobile (tenant_id, mobile, is_delete)
UNIQUE INDEX uk_tenant_code (tenant_id, code, is_delete)
```
:::tip 为什么唯一索引含 is_delete?
软删除的用户 `is_delete=1` 仍占唯一索引——把 `is_delete` 纳入联合唯一后,同名账号删除后可重新注册(新记录 `is_delete=0` 不与已删记录冲突),同时保证**同租户内未删数据**用户名唯一。业务层的 `uniqueFields` 校验与数据库索引双保险。
:::
### laravel_role(角色表)
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | INT | 主键 |
| `name` | VARCHAR(50) | 角色名称 |
| `code` | VARCHAR(50) | 角色编码 |
| `tenant_id` | INT | 租户 ID(角色按租户隔离) |
| `status` | TINYINT | 状态 |
| `remark` / `sort` | | 备注 / 排序 |
### laravel_menu(菜单/权限表)
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | INT | 主键 |
| `parent_id` | INT | 父级 ID(0=顶级) |
| `name` | VARCHAR(50) | 菜单名称 |
| `permission` | VARCHAR(100) | 权限标识(如 `sys:user:add`,**菜单即权限**) |
| `type` | TINYINT | 类型:0=菜单/目录(可导航) 1=权限节点 |
| `path` | VARCHAR(200) | 路由路径 |
| `icon` | VARCHAR(100) | 图标 |
| `sort` / `status` / `remark` | | 排序 / 状态 / 备注 |
### laravel_tenant(租户表)
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | INT | 主键 |
| `name` | VARCHAR(100) | 租户名称 |
| `code` | VARCHAR(50) | 租户编码(系统自动生成) |
| `status` | TINYINT | 状态(字典 tenant_status):0=禁用 1=启用 |
| `expire_time` | DATETIME | 过期时间(ResolveTenant 中间件校验) |
| `max_user_num` | INT | 最大用户数限制 |
| `contact_name` / `contact_phone` | | 联系人 / 联系电话 |
### laravel_article(文章表)
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | INT | 主键 |
| `title` | VARCHAR(200) | 标题 |
| `category_id` | INT | 分类 ID |
| `cover` | VARCHAR(500) | 封面图路径 |
| `content` | TEXT | 内容(富文本,含 `[IMG_URL]` 占位符) |
| `author` | VARCHAR(50) | 作者 |
| `status` | TINYINT | 状态(字典 article_status) |
| `sort` | INT | 排序 |
| `tenant_id` | INT | 租户 ID |
## 索引策略
### 推荐索引
```sql
-- 用户表:高频查询字段(联合唯一索引见上文)
ALTER TABLE laravel_user ADD INDEX idx_tenant_id (tenant_id);
ALTER TABLE laravel_user ADD INDEX idx_dept_id (dept_id);
ALTER TABLE laravel_user ADD INDEX idx_status (status);
-- 角色关联表:外键索引
ALTER TABLE laravel_user_role ADD INDEX idx_user_id (user_id);
ALTER TABLE laravel_user_role ADD INDEX idx_role_id (role_id);
ALTER TABLE laravel_role_menu ADD INDEX idx_role_id (role_id);
ALTER TABLE laravel_role_menu ADD INDEX idx_menu_id (menu_id);
-- 文章表:常用过滤字段
ALTER TABLE laravel_article ADD INDEX idx_category_id (category_id);
ALTER TABLE laravel_article ADD INDEX idx_status (status);
ALTER TABLE laravel_article ADD INDEX idx_tenant_id (tenant_id);
-- 日志表:按时间查询
ALTER TABLE laravel_login_log ADD INDEX idx_create_time (create_time);
ALTER TABLE laravel_operation_log ADD INDEX idx_create_time (create_time);
ALTER TABLE laravel_job_log ADD INDEX idx_job_id (job_id);
-- 菜单表:树形查询
ALTER TABLE laravel_menu ADD INDEX idx_parent_id (parent_id);
-- 字典项表:按字典 ID 查询
ALTER TABLE laravel_dict_item ADD INDEX idx_dict_id (dict_id);
ALTER TABLE laravel_config_item ADD INDEX idx_config_id (config_id);
-- 城市表:按编码查询
ALTER TABLE laravel_city ADD INDEX idx_area_code (area_code);
ALTER TABLE laravel_city ADD INDEX idx_parent_code (parent_code);
```
### 索引设计原则
| 原则 | 说明 |
|------|------|
| 外键必加 | 所有 `_id` 外键字段都应加索引 |
| 过滤字段 | `pageEqFields` 中配置的精确匹配字段应加索引 |
| 模糊搜索 | `pageLikeFields` 中的字段可考虑全文索引,但 `LIKE '%xx%'` 无法用普通索引 |
| 排序字段 | `pageOrderBy` 中的默认排序字段可加索引 |
| 时间字段 | 日志表的 `create_time` 必加索引(按时间范围查询) |
| 联合唯一 | 软删除表的唯一约束必须含 `is_delete`(见上文) |
## 多数据库支持
### 支持的数据库
| 数据库 | `DB_CONNECTION` 值 | 默认端口 | PHP 扩展 |
|--------|---------|----------|----------|
| MySQL | `mysql` | 3306 | `pdo_mysql` |
| PostgreSQL | `pgsql` | 5432 | `pdo_pgsql` |
| SQL Server | `sqlsrv` | 1433 | `pdo_sqlsrv` |
| Oracle | `oracle` | 1521 | `pdo_oci`(自定义 oci 驱动接入) |
| SQLite | `sqlite` | - | `pdo_sqlite` |
### 切换数据库
只需修改 `.env` 文件,无需改动任何代码:
::: code-group
```ini [MySQL(默认)]
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=rxthinkcmf.laravel13.elevue
DB_USERNAME=root
DB_PASSWORD=root
DB_PREFIX=laravel_
```
```ini [PostgreSQL]
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=rxthinkcmf.laravel13.elevue
DB_USERNAME=postgres
DB_PASSWORD=root
DB_PREFIX=laravel_
```
```ini [SQL Server]
DB_CONNECTION=sqlsrv
DB_HOST=127.0.0.1
DB_PORT=1433
DB_DATABASE=rxthinkcmf.laravel13.elevue
DB_USERNAME=sa
DB_PASSWORD=root
DB_PREFIX=laravel_
```
```ini [Oracle]
DB_CONNECTION=oracle
DB_HOST=127.0.0.1
DB_PORT=1521
DB_DATABASE=rxthinkcmf.laravel13.elevue
DB_USERNAME=root
DB_PASSWORD=root
DB_PREFIX=laravel_
```
```ini [SQLite]
DB_CONNECTION=sqlite
# DB_DATABASE 指向 database/database.sqlite(默认值)
DB_PREFIX=laravel_
```
:::
### 配置文件结构
```php
// config/database.php
return [
'default' => env('DB_CONNECTION', 'mysql'),
'connections' => [
'sqlite' => [...],
'mysql' => [...],
'pgsql' => [...],
'sqlsrv' => [...],
'oracle' => [...], // 自定义 oci 驱动(AppServiceProvider 注册 DB::extend)
],
];
```
:::tip Oracle 接入说明
Laravel 核心不内置 Oracle 连接器,项目通过 `AppServiceProvider` 中 `DB::extend('oci', ...)` 注册自定义驱动,配套 `App\Support\Database\{OracleConnection, OracleGrammar, OracleProcessor}` 完成分页语法与自增主键回填适配。切换前需安装 `pdo_oci` 扩展(见 [2.1 开发环境准备](/zh/quickstart/environment.html))。
:::
### 字段类型映射
| 通用类型 | MySQL | PostgreSQL | SQL Server | Oracle | SQLite |
|---------|-------|------------|------------|--------|--------|
| 自增主键 | `INT AUTO_INCREMENT` | `SERIAL` | `INT IDENTITY` | `NUMBER` + 序列/触发器 | `INTEGER PRIMARY KEY` |
| 字符串 | `VARCHAR(n)` | `VARCHAR(n)` | `NVARCHAR(n)` | `VARCHAR2(n)` | `TEXT` |
| 文本 | `TEXT` | `TEXT` | `NVARCHAR(MAX)` | `CLOB` | `TEXT` |
| 布尔 | `TINYINT(1)` | `BOOLEAN` | `BIT` | `NUMBER(1)` | `INTEGER` |
| 日期时间 | `DATETIME` | `TIMESTAMP` | `DATETIME2` | `TIMESTAMP` | `TEXT` |
| 小数 | `DECIMAL(m,n)` | `NUMERIC(m,n)` | `DECIMAL(m,n)` | `NUMBER(m,n)` | `REAL` |
:::tip 业务代码零修改
Eloquent 的方言层自动处理大部分 SQL 差异,切换数据库只需修改 `.env` 中的 `DB_CONNECTION`,业务代码无需任何修改。跨库 DDL 由 `DbSchemaBuilder` 按驱动生成(Oracle 自增序列与触发器自动创建)。
:::
### 各数据库安装
数据库的安装与初始化步骤已在 [2.1 开发环境准备](/zh/quickstart/environment.html)(含四平台安装命令、扩展启用、Oracle Instant Client)与 [2.5 数据库初始化](/zh/quickstart/database-init.html)(建库、脚本导入、`.env` 配置)中完整说明,此处不再重复。
## SQL 脚本
项目为每种数据库提供了独立的 SQL 脚本,存放在 `document/` 目录下按数据库类型分组:
```text
document/
├── mysql/
│ └── rxthinkcmf.laravel13.elevue.sql # MySQL 版本
├── postgresql/
│ └── rxthinkcmf.laravel13.elevue.sql # PostgreSQL 版本
├── sqlserver/
│ └── rxthinkcmf.laravel13.elevue.sql # SQL Server 版本
├── oracle/
│ └── rxthinkcmf.laravel13.elevue.sql # Oracle 版本
└── sqlite/
└── rxthinkcmf.laravel13.elevue.sql # SQLite 版本
```
各数据库脚本包含完整的表结构定义和初始数据(用户、角色、菜单、字典、城市行政区划等),已针对各数据库的 SQL 语法做了适配,可直接导入使用。
:::warning
本项目 `database/migrations/` 为空,**建表以 SQL 脚本为准**,`php artisan migrate` 不会创建业务表。导入方法详见 [2.5 数据库初始化](/zh/quickstart/database-init.html)。
:::
## 慢查询排查
```ini
# MySQL my.cnf / my.ini
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
```
:::warning 注意
项目所有查询都通过 Eloquent 查询构建器构建(参数绑定),不会出现原生 SQL 注入。如需排查慢查询,建议开启数据库慢查询日志,而非在代码层面埋点;同时核对 [索引策略](#索引策略) 中的推荐索引是否缺失。
:::