添加变更历史 添加ARD文档
This commit is contained in:
@@ -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_apps(public 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 个查询:某租户活跃安装数 / 历史装机总数 / 租户内版本分布 / 全平台租户活跃榜) |
|
||||
|
||||
Reference in New Issue
Block a user