添加变更历史 添加ARD文档

This commit is contained in:
Shen Wei
2026-04-30 20:33:51 +08:00
parent 57600598ac
commit 94d8061eb5
41 changed files with 1213 additions and 193 deletions

View File

@@ -2,7 +2,7 @@
# Fonrey — Public Schema 数据模型
> **作者**: Backend Architect
> **版本**: v1.3
> **版本**: v1.5
> **日期**: 2026-04-30
> **权威源**: 本文件是 `public` schema 所有表的唯一权威定义
> **设计依据**: 系统管理模块 PRD`PRD/系统管理/系统管理模块PRD.md`);客户端发布管理模块 PRD`PRD/发布管理/客户端发布管理模块PRD.md`
@@ -10,6 +10,12 @@
---
## 变更历史
| 日期 | 变更人 | 变更内容 |
|---|---|---|
| 2026-04-30 | Atlas | 补充“变更历史”章节(文档治理) |
## 一、概览
`public` schema 存储**平台运营层**数据,与各租户的业务 schema 完全隔离。
@@ -34,6 +40,7 @@ PostgreSQL Instance
│ ├── system_versions 版本历史
│ ├── upgrade_events 升级事件A/B/C 分级 + B 类分批编排)
│ ├── client_releases 客户端版本发布
│ ├── client_heartbeats 客户端启动心跳(活跃统计 + 版本分布)
│ ├── feature_flag_definitions Feature Flag 定义C 类灰度控制平面)
│ └── feature_flag_change_log Feature Flag 变更历史append-only
@@ -59,6 +66,7 @@ PostgreSQL Instance
| `public.system_versions` | 平台版本历史,唯一 current 约束 | §2.5 |
| `public.upgrade_events` | 升级/回滚事件A/B/C 升级类型分级B 类按租户分批编排 + 健康门控 | §2.5 |
| `public.client_releases` | Windows 客户端发布版本,含安装包 URL、SHA256、强制更新标记 | §2.6 |
| `public.client_heartbeats` | 客户端启动心跳(活跃统计 + 版本分布支撑) | §2.6 |
| `public.feature_flag_definitions` | Feature Flag 全局定义C 类运行时灰度控制平面) | §2.7 |
| `public.tenants.feature_flags` | 租户级 Flag 显式覆盖(`tenants` 表新增 JSONB 列) | §2.7 |
| `public.feature_flag_change_log` | Feature Flag 变更历史append-only | §2.7 |
@@ -538,6 +546,72 @@ CREATE INDEX idx_client_releases_status ON public.client_releases(status, create
-- 按版本号快速定位(客户端更新检测时传入 current_version 查询)
CREATE INDEX idx_client_releases_version ON public.client_releases(version);
-- ────────────────────────────────────────────────────────────
-- 客户端启动心跳表(活跃统计 + 版本分布数据支撑)
-- ────────────────────────────────────────────────────────────
-- 设计依据: 客户端发布管理模块 PRD §5.5 Story 5客户端版本分布查询
-- 上报模型: 客户端 App 每次启动时 Upsert 一条记录by tenant_id + device_id
-- 归属说明: 本表属于 shared_appspublic schema。虽然 tenant_id 字段做了租户归属,
-- 但 device_id 由客户端首次安装时生成(本机 UUID跨租户全局唯一管理
-- 将本表放在 public schema 便于平台运营做全量版本分布、活跃数统计,
-- 避免跨所有 tenant schema 做 UNION ALL。
-- 隐私说明: 不收集任何业务数据,仅用于平台运营版本管理与升级决策。
CREATE TABLE public.client_heartbeats (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- 归属
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
-- 心跳上报时所属租户(由客户端登录态决定,从 Tenant Code 解析得到);
-- 是"按租户统计安装数 / 活跃数 / 版本分布"的核心维度字段,
-- 配合 idx_client_heartbeats_tenant_lastseen 索引支撑租户维度聚合查询;
-- 租户被硬删除时心跳一并清理CASCADE
-- 同一物理设备登录不同租户视作多条独立记录Upsert 锚点为 (tenant_id, device_id)
device_id UUID NOT NULL,
-- 客户端首次安装时本机生成并持久化的设备 UUID
-- 卸载重装会变化(视作新设备);同一设备换租户登录视作不同记录
user_id UUID,
-- 上次启动时登录的用户 ID指向 tenant schema 的 staff.id
-- 不加外键约束(跨 schema 无法外键),仅作排查辅助;
-- 未登录或退出后启动允许为 NULL
-- 版本与环境
client_version VARCHAR(20) NOT NULL,
-- 上报时客户端 SemVer 版本号,如 '1.2.3'
platform VARCHAR(20) NOT NULL DEFAULT 'win32'
CHECK (platform IN ('win32')),
arch VARCHAR(10) NOT NULL DEFAULT 'x64'
CHECK (arch IN ('x64', 'arm64')),
os_version VARCHAR(80),
-- 操作系统版本字符串,如 'Windows 10 22H2';可选
-- 时间戳
first_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- 该 (tenant_id, device_id) 组合首次心跳时间Upsert 时不更新
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- 最近一次启动心跳时间Upsert 时刷新为 NOW()
launch_count INTEGER NOT NULL DEFAULT 1,
-- 累计启动次数Upsert 时 +1用于排查异常高频启动
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- 同一租户下同一设备唯一一条记录Upsert 锚点)
CONSTRAINT uq_client_heartbeats_tenant_device UNIQUE (tenant_id, device_id)
);
-- 活跃数统计("最近 24h 内有心跳")核心索引
CREATE INDEX idx_client_heartbeats_last_seen
ON public.client_heartbeats(last_seen_at DESC);
-- 版本分布统计:按版本聚合活跃设备数
CREATE INDEX idx_client_heartbeats_version_lastseen
ON public.client_heartbeats(client_version, last_seen_at DESC);
-- 租户维度排查:某租户下所有活跃设备
CREATE INDEX idx_client_heartbeats_tenant_lastseen
ON public.client_heartbeats(tenant_id, last_seen_at DESC);
```
### 2.7 Feature Flag 灰度体系
@@ -644,6 +718,9 @@ CREATE INDEX idx_ff_log_operator ON public.feature_flag_change_log(operator_id,
| `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`,禁止先读后写 |
| `client_heartbeats` Upsert 锚点 | 必须使用 `INSERT ... ON CONFLICT (tenant_id, device_id) DO UPDATE SET last_seen_at=NOW(), launch_count=client_heartbeats.launch_count+1, client_version=EXCLUDED.client_version`;禁止先 SELECT 再 INSERT |
| `client_heartbeats` 仅启动时上报 | 客户端 App 仅在每次启动时上报一次心跳;不做 4h 周期心跳,避免 public schema 写入压力 |
| `client_heartbeats` 不可跨租户隔离 | 本表属于 public schema所有租户共享禁止在租户 schema 中创建副本 |
| `upgrade_events.gray_tenant_ids``upgrade_type` 一致性 | A_app 类型必须为空数组CHECK 约束 `chk_app_upgrade_no_gray` 强制);只有 B_schema 类型才使用灰度名单与批次字段 |
| `upgrade_events` 状态流转单向 | 状态机详见 §4.2`succeeded` / `rolled_back` / `failed` 为终态,禁止再写回中间态 |
| `feature_flag_change_log` append-only | 禁止 UPDATE / DELETE任何 Flag 变更都必须经此表追溯,含 `reason` 强制非空 |
@@ -782,11 +859,83 @@ SELECT version, platform, arch, release_type, status,
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;
-- 客户端启动心跳 Upsert每次启动调用原子更新最后活跃时间与启动次数
INSERT INTO public.client_heartbeats (
tenant_id, device_id, user_id,
client_version, platform, arch, os_version,
first_seen_at, last_seen_at, launch_count
)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW(), 1)
ON CONFLICT (tenant_id, device_id) DO UPDATE
SET last_seen_at = NOW(),
launch_count = public.client_heartbeats.launch_count + 1,
client_version = EXCLUDED.client_version,
user_id = EXCLUDED.user_id,
os_version = EXCLUDED.os_version,
updated_at = NOW();
-- 统计各版本活跃客户端数(活跃 = 最近 24h 内有心跳)
SELECT cr.version,
cr.status,
cr.download_count,
COUNT(ch.id) FILTER (
WHERE ch.last_seen_at >= NOW() - INTERVAL '24 hours'
) AS active_devices_24h,
COUNT(ch.id) AS total_devices_ever
FROM public.client_releases cr
LEFT JOIN public.client_heartbeats ch ON ch.client_version = cr.version
WHERE cr.status IN ('published', 'archived')
GROUP BY cr.version, cr.status, cr.download_count, cr.published_at
ORDER BY cr.published_at DESC;
-- 平台总活跃数(最近 24h
SELECT COUNT(*) AS active_devices_24h
FROM public.client_heartbeats
WHERE last_seen_at >= NOW() - INTERVAL '24 hours';
-- 某租户下活跃设备列表(运营排查)
SELECT device_id, client_version, user_id, last_seen_at, launch_count
FROM public.client_heartbeats
WHERE tenant_id = $1
AND last_seen_at >= NOW() - INTERVAL '24 hours'
ORDER BY last_seen_at DESC;
-- ============================================================
-- 租户维度安装/活跃统计Platform Admin 运营看板)
-- ============================================================
-- 某租户当前活跃安装数(最近 24h 内启动过的设备)
SELECT COUNT(*) AS active_installs_24h
FROM public.client_heartbeats
WHERE tenant_id = $1
AND last_seen_at >= NOW() - INTERVAL '24 hours';
-- 某租户历史装机总数(不区分是否活跃,含已离线设备)
SELECT COUNT(*) AS total_installs_ever
FROM public.client_heartbeats
WHERE tenant_id = $1;
-- 某租户内部各版本分布(识别该租户内升级覆盖率)
SELECT client_version,
COUNT(*) FILTER (WHERE last_seen_at >= NOW() - INTERVAL '24 hours') AS active_devices_24h,
COUNT(*) AS total_devices_ever
FROM public.client_heartbeats
WHERE tenant_id = $1
GROUP BY client_version
ORDER BY active_devices_24h DESC;
-- 全平台租户活跃榜PRD §5.5 Story 5按租户维度查看版本分布识别落后租户
SELECT t.tenant_code,
t.name,
COUNT(h.id) FILTER (
WHERE h.last_seen_at >= NOW() - INTERVAL '24 hours'
) AS active_installs_24h,
COUNT(h.id) AS total_installs_ever
FROM public.tenants t
LEFT JOIN public.client_heartbeats h ON h.tenant_id = t.id
WHERE t.status = 'active'
GROUP BY t.id, t.tenant_code, t.name
ORDER BY active_installs_24h DESC;
-- ============================================================
-- 升级编排相关查询B 类 Schema 迁移分批升级)
@@ -878,3 +1027,6 @@ ORDER BY created_at DESC;
| v1.0 | 2026-04-24 | 从 `DATA_MODEL.md §三` 独立拆分;内容等价于 v1.2 DATA_MODEL.md §三 |
| v1.1 | 2026-04-24 | 新增 §2.6 `client_releases` 表(客户端发布管理);同步更新表清单、约束规则、查询模式 |
| v1.2 | 2026-04-26 | 配合系统管理技术文档 v1.2 升级分批专节§2.5 `upgrade_events` 增加 `upgrade_type``gray_tenant_ids``batch_size``batch_concurrency``batch_interval_seconds``failure_policy``current_batch_no``total_batch_count``health_gate_config``pre_backup_record_id``halted_reason` 字段,状态机扩展为 10 态;新增 §2.7 Feature Flag 体系(`feature_flag_definitions` / `tenants.feature_flags` JSONB / `feature_flag_change_log`同步更新表清单、§4.2 升级状态机、§4.3 Flag 流程、§5.1 查询模式、§5.2 禁止操作 |
| v1.3 | 2026-04-30 | 配合登录管理 PRD v2.0 / 系统管理 PRD§2.1 `tenants` 新增 `tenant_code` (CHAR(12), 全局唯一对外识别码) 与 `contact_phone` (CHAR(11)Tenant Admin 账号创建数据来源)`contact_email` 改为可 NULL同步更新约束、查询模式 |
| v1.4 | 2026-04-30 | 配合客户端发布管理 PRD v1.1 §5.5 Story 5§2.6 新增 `client_heartbeats` 表(启动时 Upsert by `tenant_id + device_id`,活跃定义为最近 24h 内心跳);同步更新表清单、§三 约束Upsert 锚点 / 启动上报 / 不可下沉到租户 schema、§5.1 查询模式Upsert 模板 + 版本活跃分布 JOIN + 平台总活跃数 + 租户维度排查MVP 不做升级趋势图v2 规划) |
| v1.5 | 2026-04-30 | 配合客户端发布管理 PRD v1.2 §5.5 Story 5 验收标准追加(按租户统计安装数):① §2.6 `client_heartbeats.tenant_id` 注释强化,明确为"按租户统计安装数 / 活跃数 / 版本分布"的核心维度字段;② §5.1 新增"租户维度安装/活跃统计"查询专区4 个查询:某租户活跃安装数 / 历史装机总数 / 租户内版本分布 / 全平台租户活跃榜) |