Sync: add infrastructure as code notes
This commit is contained in:
488
Project/fonrey/DATA_MODEL/DATA_MODEL_PUBLIC.md
Normal file
488
Project/fonrey/DATA_MODEL/DATA_MODEL_PUBLIC.md
Normal file
@@ -0,0 +1,488 @@
|
||||
# Fonrey — Public Schema 数据模型
|
||||
|
||||
> **作者**: Backend Architect
|
||||
> **版本**: v1.0
|
||||
> **日期**: 2026-04-24
|
||||
> **权威源**: 本文件是 `public` schema 所有表的唯一权威定义
|
||||
> **设计依据**: 系统管理模块 PRD(`PRD/系统管理/系统管理模块PRD.md`)
|
||||
> **索引文档**: [`DATA_MODEL.md §三`](./DATA_MODEL.md)(仅保留摘要索引,开发以本文件为准)
|
||||
|
||||
---
|
||||
|
||||
## 一、概览
|
||||
|
||||
`public` schema 存储**平台运营层**数据,与各租户的业务 schema 完全隔离。
|
||||
|
||||
### 架构定位
|
||||
|
||||
```
|
||||
PostgreSQL Instance
|
||||
│
|
||||
├── public schema(平台运营层)← 本文件覆盖
|
||||
│ ├── tenants 租户注册与生命周期
|
||||
│ ├── domains 域名路由
|
||||
│ ├── tenant_status_logs 状态变更审计
|
||||
│ ├── platform_admins 管理员账号
|
||||
│ ├── admin_mfa_devices TOTP 设备
|
||||
│ ├── admin_sessions 登录会话
|
||||
│ ├── ip_whitelist 访问控制
|
||||
│ ├── platform_audit_logs 操作审计
|
||||
│ ├── backup_schedules 备份计划
|
||||
│ ├── backup_records 备份记录
|
||||
│ ├── export_tasks 数据导出
|
||||
│ ├── system_versions 版本历史
|
||||
│ └── upgrade_events 升级记录
|
||||
│
|
||||
├── tenant_abc schema(租户业务层,见各子文档)
|
||||
└── tenant_xyz schema
|
||||
```
|
||||
|
||||
### 表清单
|
||||
|
||||
| 表名 | 说明 | 节 |
|
||||
|------|------|----|
|
||||
| `public.tenants` | 租户主表(每家房产公司一条记录) | §2.1 |
|
||||
| `public.domains` | 域名↔租户映射(多域名支持) | §2.1 |
|
||||
| `public.tenant_status_logs` | 租户状态变更不可变审计日志 | §2.1 |
|
||||
| `public.platform_admins` | 平台管理员账号(3 种角色) | §2.2 |
|
||||
| `public.admin_mfa_devices` | 管理员 TOTP MFA 设备(强制启用) | §2.2 |
|
||||
| `public.admin_sessions` | 管理员登录会话(30 min 超时,支持强制登出) | §2.2 |
|
||||
| `public.ip_whitelist` | 管理控制台 CIDR 白名单 | §2.2 |
|
||||
| `public.platform_audit_logs` | 所有写操作+高危操作审计(append-only,建议月度分区) | §2.3 |
|
||||
| `public.backup_schedules` | 全局/租户级定时备份计划 | §2.4 |
|
||||
| `public.backup_records` | 备份任务执行记录(自动/手动/升级前/恢复前) | §2.4 |
|
||||
| `public.export_tasks` | 数据导出异步任务(CSV/JSON/SQL Dump) | §2.4 |
|
||||
| `public.system_versions` | 平台版本历史,唯一 current 约束 | §2.5 |
|
||||
| `public.upgrade_events` | 升级/回滚事件,含灰度租户维度进度快照 | §2.5 |
|
||||
|
||||
---
|
||||
|
||||
## 二、DDL 定义
|
||||
|
||||
### 2.1 租户管理
|
||||
|
||||
```sql
|
||||
-- ============================================================
|
||||
-- 文件: shared_schema.sql
|
||||
-- 用途: django-tenants 公共 Schema,存放平台运营层数据
|
||||
-- 设计依据: 系统管理模块 PRD v1.0
|
||||
-- ============================================================
|
||||
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
-- 1. 租户管理
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
|
||||
-- 租户状态枚举(生命周期状态机,见 PRD §9.1)
|
||||
-- creating → active ←→ suspended → pending_delete → deleted
|
||||
-- ↑ 硬删除直接到 deleted
|
||||
|
||||
-- 租户主表(每家房产经纪公司一条记录)
|
||||
CREATE TABLE public.tenants (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
schema_name VARCHAR(63) UNIQUE NOT NULL, -- PG schema 名,最长 63 字符,创建后不可修改
|
||||
name VARCHAR(255) NOT NULL, -- 公司名称
|
||||
short_name VARCHAR(100), -- 简称/品牌名
|
||||
contact_name VARCHAR(100) NOT NULL, -- 主联系人姓名
|
||||
contact_email VARCHAR(254) NOT NULL, -- 联系邮箱(接收通知/欢迎邮件)
|
||||
region VARCHAR(100), -- 所在地区(省市,如「上海市」)
|
||||
plan VARCHAR(20) NOT NULL DEFAULT 'basic'
|
||||
CHECK (plan IN ('basic','professional','enterprise')),
|
||||
|
||||
-- 状态机
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'creating'
|
||||
CHECK (status IN ('creating','active','suspended','pending_delete','deleted','failed')),
|
||||
suspended_until TIMESTAMPTZ, -- NULL = 永久挂起,非 NULL = Celery Beat 定时恢复
|
||||
suspended_reason VARCHAR(50)
|
||||
CHECK (suspended_reason IN ('overdue','violation','requested','other')),
|
||||
deleted_at TIMESTAMPTZ, -- 软删除时间戳;硬删除直接物理删除行
|
||||
|
||||
-- 订阅
|
||||
paid_until DATE, -- 订阅到期日
|
||||
on_trial BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
|
||||
-- 灰度升级
|
||||
is_canary BOOLEAN NOT NULL DEFAULT FALSE, -- TRUE = 内测租户,参与灰度升级
|
||||
|
||||
-- 元数据
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID, -- 创建该租户的管理员 ID(可 NULL,初始化时)
|
||||
extra JSONB NOT NULL DEFAULT '{}' -- 预留扩展字段
|
||||
);
|
||||
|
||||
CREATE INDEX idx_tenants_status ON public.tenants(status);
|
||||
CREATE INDEX idx_tenants_suspended_until ON public.tenants(suspended_until)
|
||||
WHERE status = 'suspended' AND suspended_until IS NOT NULL;
|
||||
CREATE INDEX idx_tenants_canary ON public.tenants(is_canary) WHERE is_canary = TRUE;
|
||||
CREATE INDEX idx_tenants_pending_delete ON public.tenants(deleted_at)
|
||||
WHERE status = 'pending_delete';
|
||||
|
||||
-- 域名映射表(支持多域名绑定一个租户,子域名创建后不可修改)
|
||||
CREATE TABLE public.domains (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
domain VARCHAR(253) UNIQUE NOT NULL, -- 含子域名的完整域名(如 abc.platform.com)
|
||||
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
||||
is_primary BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_domains_tenant ON public.domains(tenant_id);
|
||||
CREATE UNIQUE INDEX idx_domains_primary ON public.domains(tenant_id) WHERE is_primary = TRUE;
|
||||
|
||||
-- 租户状态变更日志(append-only,不可删除)
|
||||
-- 记录所有 status 变更:creating→active / active→suspended / suspended→active / →pending_delete / →deleted
|
||||
CREATE TABLE public.tenant_status_logs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
||||
from_status VARCHAR(20), -- NULL 表示初始创建
|
||||
to_status VARCHAR(20) NOT NULL,
|
||||
reason TEXT,
|
||||
operator_id UUID, -- 操作管理员 ID;NULL = 系统自动(Celery)
|
||||
operator_name VARCHAR(100), -- 快照,防止管理员被删后失去可追溯性
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
-- 无 deleted_at,无 UPDATE,append-only
|
||||
);
|
||||
|
||||
CREATE INDEX idx_tenant_status_logs_tenant ON public.tenant_status_logs(tenant_id, created_at DESC);
|
||||
```
|
||||
|
||||
### 2.2 平台管理员
|
||||
|
||||
```sql
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
-- 2. 平台管理员
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
|
||||
-- 管理员账号(与租户 staff 完全独立,存于 public schema)
|
||||
CREATE TABLE public.platform_admins (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
username VARCHAR(150) UNIQUE NOT NULL,
|
||||
email VARCHAR(254) UNIQUE NOT NULL,
|
||||
display_name VARCHAR(100) NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL, -- Django PBKDF2 / Argon2 哈希
|
||||
role VARCHAR(20) NOT NULL
|
||||
CHECK (role IN ('super_admin','ops_operator','read_only_auditor')),
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
mfa_enabled BOOLEAN NOT NULL DEFAULT FALSE, -- 首次登录前为 FALSE,配置 TOTP 后变 TRUE
|
||||
last_login_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES public.platform_admins(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_platform_admins_role ON public.platform_admins(role) WHERE is_active = TRUE;
|
||||
|
||||
-- MFA 设备(TOTP,每管理员可注册多个设备,但通常一个)
|
||||
CREATE TABLE public.admin_mfa_devices (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
admin_id UUID NOT NULL REFERENCES public.platform_admins(id) ON DELETE CASCADE,
|
||||
device_name VARCHAR(100) NOT NULL DEFAULT 'Authenticator App',
|
||||
totp_secret VARCHAR(255) NOT NULL, -- Base32 加密存储
|
||||
is_confirmed BOOLEAN NOT NULL DEFAULT FALSE, -- 首次验证通过后置 TRUE
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
last_used_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX idx_admin_mfa_devices_admin ON public.admin_mfa_devices(admin_id)
|
||||
WHERE is_confirmed = TRUE;
|
||||
|
||||
-- 管理员登录会话(支持强制登出)
|
||||
CREATE TABLE public.admin_sessions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
admin_id UUID NOT NULL REFERENCES public.platform_admins(id) ON DELETE CASCADE,
|
||||
session_token VARCHAR(255) UNIQUE NOT NULL, -- 随机安全令牌
|
||||
ip_address INET NOT NULL,
|
||||
user_agent TEXT,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMPTZ NOT NULL, -- 默认 NOW() + 30 分钟,活动时滚动续期
|
||||
revoked_at TIMESTAMPTZ, -- 强制登出时记录
|
||||
revoked_by UUID REFERENCES public.platform_admins(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_admin_sessions_admin ON public.admin_sessions(admin_id) WHERE is_active = TRUE;
|
||||
CREATE INDEX idx_admin_sessions_expires ON public.admin_sessions(expires_at) WHERE is_active = TRUE;
|
||||
|
||||
-- IP 白名单(管理控制台访问限制)
|
||||
CREATE TABLE public.ip_whitelist (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
cidr CIDR NOT NULL, -- 如 203.0.113.0/24 或 203.0.113.5/32
|
||||
label VARCHAR(100), -- 备注,如「上海办公室」
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES public.platform_admins(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_ip_whitelist_active ON public.ip_whitelist(cidr) WHERE is_active = TRUE;
|
||||
```
|
||||
|
||||
### 2.3 审计日志(append-only)
|
||||
|
||||
```sql
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
-- 3. 审计日志(append-only)
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
|
||||
-- 平台操作审计日志(所有写操作 + 高危操作,无 deleted_at,无 UPDATE)
|
||||
CREATE TABLE public.platform_audit_logs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
operator_id UUID, -- 管理员 ID;NULL 表示系统自动操作
|
||||
operator_name VARCHAR(100), -- 快照(防止账号删除后失去溯源)
|
||||
action_type VARCHAR(50) NOT NULL,
|
||||
-- CREATE_TENANT | SUSPEND_TENANT | RESUME_TENANT | DELETE_TENANT | HARD_DELETE_TENANT
|
||||
-- RESTORE_DATA | TRIGGER_BACKUP | SYSTEM_UPGRADE | ROLLBACK
|
||||
-- RESET_PASSWORD | CREATE_ADMIN | DEACTIVATE_ADMIN | FORCE_LOGOUT
|
||||
-- UPDATE_IP_WHITELIST | UPDATE_BACKUP_SCHEDULE | EXPORT_DATA | ...
|
||||
target_type VARCHAR(30) NOT NULL, -- Tenant | User | System | Backup | Admin
|
||||
target_id VARCHAR(255), -- 操作对象 ID(UUID 或其他)
|
||||
target_name VARCHAR(255), -- 操作对象可读名称(快照)
|
||||
payload_summary TEXT, -- 操作内容摘要(非敏感字段)
|
||||
result VARCHAR(10) NOT NULL DEFAULT 'SUCCESS'
|
||||
CHECK (result IN ('SUCCESS','FAILED')),
|
||||
error_message TEXT,
|
||||
ip_address INET,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
-- 无 deleted_at,无 UPDATE;建议按月 RANGE 分区
|
||||
);
|
||||
|
||||
CREATE INDEX idx_audit_logs_operator ON public.platform_audit_logs(operator_id, created_at DESC);
|
||||
CREATE INDEX idx_audit_logs_action ON public.platform_audit_logs(action_type, created_at DESC);
|
||||
CREATE INDEX idx_audit_logs_target ON public.platform_audit_logs(target_type, target_id, created_at DESC);
|
||||
CREATE INDEX idx_audit_logs_created ON public.platform_audit_logs(created_at DESC);
|
||||
```
|
||||
|
||||
### 2.4 备份与导出
|
||||
|
||||
```sql
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
-- 4. 备份与导出
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
|
||||
-- 定时备份计划(全局策略 + 租户覆盖策略)
|
||||
CREATE TABLE public.backup_schedules (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID REFERENCES public.tenants(id) ON DELETE CASCADE,
|
||||
-- NULL = 全局默认计划;非 NULL = 该租户的独立计划(覆盖全局)
|
||||
frequency VARCHAR(10) NOT NULL DEFAULT 'daily'
|
||||
CHECK (frequency IN ('hourly','daily','weekly')),
|
||||
scheduled_time TIME NOT NULL DEFAULT '02:00', -- 执行时间窗口(UTC)
|
||||
retention_count INTEGER NOT NULL DEFAULT 10, -- 最多保留 N 个备份版本
|
||||
storage_target VARCHAR(20) NOT NULL DEFAULT 'r2'
|
||||
CHECK (storage_target IN ('local','s3','r2','gcs')),
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES public.platform_admins(id) ON DELETE SET NULL,
|
||||
UNIQUE (tenant_id) -- 每个租户最多一条独立计划;NULL tenant_id 用应用层保证全局唯一
|
||||
);
|
||||
|
||||
-- 备份任务执行记录
|
||||
CREATE TABLE public.backup_records (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
||||
trigger_type VARCHAR(10) NOT NULL
|
||||
CHECK (trigger_type IN ('auto','manual','pre_upgrade','pre_restore')),
|
||||
status VARCHAR(15) NOT NULL DEFAULT 'pending'
|
||||
CHECK (status IN ('pending','in_progress','success','failed')),
|
||||
storage_target VARCHAR(20) NOT NULL,
|
||||
storage_path TEXT, -- R2/S3 存储路径
|
||||
size_bytes BIGINT, -- 备份包大小
|
||||
started_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
error_message TEXT,
|
||||
triggered_by UUID REFERENCES public.platform_admins(id) ON DELETE SET NULL,
|
||||
upgrade_event_id UUID, -- 关联升级事件(pre_upgrade 类型)
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_backup_records_tenant ON public.backup_records(tenant_id, created_at DESC);
|
||||
CREATE INDEX idx_backup_records_status ON public.backup_records(status)
|
||||
WHERE status IN ('pending','in_progress');
|
||||
|
||||
-- 数据导出任务(异步 Celery 执行)
|
||||
CREATE TABLE public.export_tasks (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
||||
requested_by UUID REFERENCES public.platform_admins(id) ON DELETE SET NULL,
|
||||
modules TEXT[] NOT NULL,
|
||||
-- 'clients' | 'properties' | 'transactions' | 'system_config' | 'all'
|
||||
format VARCHAR(10) NOT NULL
|
||||
CHECK (format IN ('csv','json','sql_dump')),
|
||||
status VARCHAR(15) NOT NULL DEFAULT 'pending'
|
||||
CHECK (status IN ('pending','in_progress','done','failed')),
|
||||
storage_path TEXT, -- R2 临时目录路径
|
||||
download_url TEXT, -- 带签名下载链接
|
||||
expires_at TIMESTAMPTZ, -- 下载链接有效期(默认 24 小时)
|
||||
size_bytes BIGINT,
|
||||
started_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
error_message TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_export_tasks_tenant ON public.export_tasks(tenant_id, created_at DESC);
|
||||
CREATE INDEX idx_export_tasks_status ON public.export_tasks(status)
|
||||
WHERE status IN ('pending','in_progress');
|
||||
```
|
||||
|
||||
### 2.5 版本升级管理
|
||||
|
||||
```sql
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
-- 5. 版本升级管理
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
|
||||
-- 平台版本历史
|
||||
CREATE TABLE public.system_versions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
version_number VARCHAR(50) UNIQUE NOT NULL, -- 如 v2.3.1
|
||||
release_notes TEXT,
|
||||
artifact_url TEXT, -- 制品库地址
|
||||
status VARCHAR(15) NOT NULL DEFAULT 'previous'
|
||||
CHECK (status IN ('current','previous','archived')),
|
||||
released_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES public.platform_admins(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_system_versions_current ON public.system_versions(status)
|
||||
WHERE status = 'current'; -- 全局只允许一个 current 版本
|
||||
|
||||
-- 升级事件(每次执行升级或回滚对应一条记录)
|
||||
CREATE TABLE public.upgrade_events (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
from_version_id UUID REFERENCES public.system_versions(id) ON DELETE SET NULL,
|
||||
to_version_id UUID NOT NULL REFERENCES public.system_versions(id) ON DELETE RESTRICT,
|
||||
event_type VARCHAR(10) NOT NULL
|
||||
CHECK (event_type IN ('upgrade','rollback')),
|
||||
strategy VARCHAR(10) NOT NULL DEFAULT 'full'
|
||||
CHECK (strategy IN ('full','canary')), -- full = 全量,canary = 灰度
|
||||
status VARCHAR(15) NOT NULL DEFAULT 'pending'
|
||||
CHECK (status IN ('pending','health_check','in_progress','success','failed','rolled_back')),
|
||||
|
||||
-- 升级进度:每个租户的状态存为 JSONB 数组
|
||||
-- [{tenant_id, tenant_name, status, started_at, completed_at, error}]
|
||||
tenant_progress JSONB NOT NULL DEFAULT '[]',
|
||||
|
||||
-- 回滚触发条件
|
||||
rollback_reason TEXT,
|
||||
incident_report TEXT, -- 回滚后生成的事件报告
|
||||
|
||||
started_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
initiated_by UUID REFERENCES public.platform_admins(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_upgrade_events_status ON public.upgrade_events(status, created_at DESC);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、关键约束与禁止操作
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| `tenant_status_logs` append-only | 禁止 UPDATE / DELETE;状态机变更只追加新行 |
|
||||
| `platform_audit_logs` append-only | 禁止 UPDATE / DELETE;建议按月 RANGE 分区 |
|
||||
| `public.tenants.schema_name` 不可修改 | 创建后禁止 UPDATE,PG schema 绑定 |
|
||||
| `public.domains.domain` 不可修改 | 子域名创建后禁止 UPDATE |
|
||||
| `system_versions` 唯一 current | `idx_system_versions_current` 部分唯一索引保证全局只有一个 `status='current'` |
|
||||
| `backup_schedules.tenant_id` UNIQUE | 每个租户最多一条独立计划;`NULL` 全局计划由应用层保证唯一 |
|
||||
| `platform_admins` 与 `staff` 完全独立 | 不共享表、不共享 auth 系统 |
|
||||
| MFA 强制 | `platform_admins.mfa_enabled` 在首次 TOTP 确认后才变 TRUE;登录流必须检查 |
|
||||
| `admin_sessions` 30 分钟滚动超时 | 应用层每次活跃请求更新 `expires_at = NOW() + 30min` |
|
||||
|
||||
---
|
||||
|
||||
## 四、状态机
|
||||
|
||||
### 4.1 租户生命周期
|
||||
|
||||
```
|
||||
creating ──(初始化完成)──► active
|
||||
active ──(逾期/违规/申请)──► suspended ──(恢复条件满足)──► active
|
||||
active ──(申请注销)──► pending_delete ──(30天后/管理员确认)──► deleted
|
||||
suspended ──(申请注销)──► pending_delete
|
||||
creating ──(初始化失败)──► failed
|
||||
```
|
||||
|
||||
**字段映射**:
|
||||
- `status` 枚举:`creating | active | suspended | pending_delete | deleted | failed`
|
||||
- `suspended_until = NULL`:永久挂起;`suspended_until IS NOT NULL`:Celery Beat 定时自动恢复
|
||||
- `deleted_at`:软删除时间戳;硬删除时物理删除整行
|
||||
|
||||
### 4.2 升级事件状态
|
||||
|
||||
```
|
||||
pending → health_check → in_progress → success
|
||||
└─► failed → rolled_back
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、查询模式
|
||||
|
||||
### 5.1 常用查询
|
||||
|
||||
```sql
|
||||
-- 查询所有活跃租户
|
||||
SELECT id, name, plan, paid_until
|
||||
FROM public.tenants
|
||||
WHERE status = 'active'
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
-- 查询即将到期的租户(7 天内)
|
||||
SELECT id, name, contact_email, paid_until
|
||||
FROM public.tenants
|
||||
WHERE status = 'active'
|
||||
AND paid_until BETWEEN CURRENT_DATE AND CURRENT_DATE + 7;
|
||||
|
||||
-- 查询灰度租户(canary 升级目标)
|
||||
SELECT id, schema_name, name
|
||||
FROM public.tenants
|
||||
WHERE is_canary = TRUE AND status = 'active';
|
||||
|
||||
-- 查询某租户所有状态变更历史
|
||||
SELECT from_status, to_status, reason, operator_name, created_at
|
||||
FROM public.tenant_status_logs
|
||||
WHERE tenant_id = $1
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
-- 查询待自动恢复的挂起租户(Celery Beat 使用)
|
||||
SELECT id, schema_name, name
|
||||
FROM public.tenants
|
||||
WHERE status = 'suspended'
|
||||
AND suspended_until IS NOT NULL
|
||||
AND suspended_until <= NOW();
|
||||
|
||||
-- 查询某管理员近 30 天的审计记录
|
||||
SELECT action_type, target_type, target_name, result, created_at
|
||||
FROM public.platform_audit_logs
|
||||
WHERE operator_id = $1
|
||||
AND created_at >= NOW() - INTERVAL '30 days'
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
-- 查询进行中的备份任务
|
||||
SELECT br.id, t.name AS tenant_name, br.trigger_type, br.started_at
|
||||
FROM public.backup_records br
|
||||
JOIN public.tenants t ON t.id = br.tenant_id
|
||||
WHERE br.status IN ('pending', 'in_progress')
|
||||
ORDER BY br.created_at DESC;
|
||||
```
|
||||
|
||||
### 5.2 禁止查询
|
||||
|
||||
| 禁止操作 | 原因 |
|
||||
|----------|------|
|
||||
| `UPDATE public.tenant_status_logs` | append-only 审计表 |
|
||||
| `DELETE FROM public.platform_audit_logs` | append-only 审计表 |
|
||||
| `UPDATE public.tenants SET schema_name = ...` | schema 名绑定 PG 物理 schema |
|
||||
| `UPDATE public.domains SET domain = ...` | 域名路由不可变 |
|
||||
|
||||
---
|
||||
|
||||
## 六、版本历史
|
||||
|
||||
| 版本 | 日期 | 变更 |
|
||||
|------|------|------|
|
||||
| v1.0 | 2026-04-24 | 从 `DATA_MODEL.md §三` 独立拆分;内容等价于 v1.2 DATA_MODEL.md §三 |
|
||||
Reference in New Issue
Block a user