> **For AI assistants**: Read this entire file before writing any code. All decisions here are final. Do not suggest alternatives unless asked. # Fonrey — Public Schema 数据模型 > **作者**: Backend Architect > **版本**: v1.0 > **日期**: 2026-04-24 > **权威源**: 本文件是 `public` schema 所有表的唯一权威定义 > **设计依据**: 系统管理模块 PRD(`PRD/系统管理/系统管理模块PRD.md`);客户端发布管理模块 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 升级记录 │ └── client_releases 客户端版本发布 │ ├── 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 | | `public.client_releases` | Windows 客户端发布版本,含安装包 URL、SHA256、强制更新标记 | §2.6 | --- ## 二、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); ``` ### 2.6 客户端发布管理 ```sql -- ──────────────────────────────────────────────────────────── -- 6. 客户端发布管理 -- ──────────────────────────────────────────────────────────── -- 设计依据: 客户端发布管理模块 PRD §5.3 -- 说明: 本表属于 shared_apps(public schema),所有租户共享同一套客户端版本, -- 不做多租户隔离。 -- 客户端版本发布表 CREATE TABLE public.client_releases ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 版本标识 version VARCHAR(20) UNIQUE NOT NULL, -- SemVer 格式,如 '1.2.3';由应用层校验格式,数据库层仅保证唯一性 platform VARCHAR(20) NOT NULL DEFAULT 'win32' CHECK (platform IN ('win32')), -- 当前仅支持 Windows;后续支持 darwin / linux 时扩展 CHECK arch VARCHAR(10) NOT NULL DEFAULT 'x64' CHECK (arch IN ('x64', 'arm64')), -- 版本类型与状态 release_type VARCHAR(10) NOT NULL DEFAULT 'normal' CHECK (release_type IN ('normal', 'force')), -- normal = 普通更新,提示但可延后;force = 强制升级,不可跳过 status VARCHAR(10) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'published', 'archived')), -- draft = 草稿,不对外生效;published = 已发布,客户端可感知;archived = 已下线 -- 兼容性约束 min_required_version VARCHAR(20), -- 低于该版本的客户端将被强制要求升级,NULL = 无最低版本限制 -- 由应用层在查询时比较 SemVer,数据库层不做运算 -- 安装包(EXE) download_url TEXT NOT NULL, -- Cloudflare R2 公开 URL,格式: -- https://download.fonrey.com/releases/v{version}/fonrey-setup-{version}-win.exe checksum_sha256 VARCHAR(64) NOT NULL, -- 安装包 SHA256 十六进制字符串(64 位),客户端下载完成后校验 file_size_bytes BIGINT, -- 安装包字节大小,用于前端展示下载大小 -- 便携版(ZIP,可选) portable_url TEXT, -- 无需安装的 ZIP 版本,供无管理员权限的企业环境使用 portable_checksum_sha256 VARCHAR(64), -- 更新内容 release_notes TEXT NOT NULL, -- 对外展示的更新日志,Markdown 格式,最多 2000 字 internal_notes TEXT, -- 内部技术说明,不对外展示 -- 统计 download_count INTEGER NOT NULL DEFAULT 0, -- 该版本安装包被下载次数,由应用层在每次下载时原子递增 -- 时间与操作人 published_at TIMESTAMPTZ, -- 首次设为 published 时记录,后续状态变更不更新此字段 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, published_by UUID REFERENCES public.platform_admins(id) ON DELETE SET NULL ); -- 只允许一条 published 记录(同平台+架构下的当前生效版本) CREATE UNIQUE INDEX idx_client_releases_published ON public.client_releases(platform, arch) WHERE status = 'published'; -- 快速查找草稿/已发布版本(管理后台列表查询) CREATE INDEX idx_client_releases_status ON public.client_releases(status, created_at DESC); -- 按版本号快速定位(客户端更新检测时传入 current_version 查询) CREATE INDEX idx_client_releases_version ON public.client_releases(version); ``` --- ## 三、关键约束与禁止操作 | 规则 | 说明 | |------|------| | `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` | | `client_releases` 唯一 published | `idx_client_releases_published` 部分唯一索引保证同平台+架构下只有一条 `status='published'` | | `client_releases` 不可跨租户隔离 | 本表属于 public schema,所有租户共享;禁止在租户 schema 中创建副本 | | `client_releases.download_count` 原子递增 | 必须使用 `UPDATE ... SET download_count = download_count + 1`,禁止先读后写 | --- ## 四、状态机 ### 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; -- 客户端更新检测:查询当前生效版本(platform=win32, arch=x64) SELECT version, release_type, download_url, portable_url, checksum_sha256, file_size_bytes, release_notes, published_at FROM public.client_releases WHERE platform = 'win32' AND arch = 'x64' AND status = 'published' LIMIT 1; -- 客户端版本管理列表(管理后台,含各状态版本) SELECT version, platform, arch, release_type, status, download_count, published_at, created_at FROM public.client_releases ORDER BY created_at DESC; -- 统计各版本活跃客户端数(需结合客户端上报心跳表,当前仅记录下载量) SELECT version, download_count FROM public.client_releases WHERE status IN ('published', 'archived') ORDER BY published_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 = ...` | 域名路由不可变 | | `UPDATE public.client_releases SET version = ...` | 版本号创建后不可修改,变更须新建记录 | | 在租户 schema 中创建 `client_releases` 副本 | 本表属于 public schema,多租户共享,禁止下沉到租户层 | --- ## 六、版本历史 | 版本 | 日期 | 变更 | |------|------|------| | v1.0 | 2026-04-24 | 从 `DATA_MODEL.md §三` 独立拆分;内容等价于 v1.2 DATA_MODEL.md §三 | | v1.1 | 2026-04-24 | 新增 §2.6 `client_releases` 表(客户端发布管理);同步更新表清单、约束规则、查询模式 |