> **For AI assistants**: Read this entire file before writing any code. All decisions here are final. Do not suggest alternatives unless asked. # Fonrey 客户端发布管理技术方案 **版本**: 1.0 **项目**: Fonrey 房产经纪管理系统 **技术栈**: Django 4.x + HTMX + PostgreSQL 16(public schema)+ Redis + Celery + Electron + electron-updater + Cloudflare R2/CDN **关联 PRD**: `PRD/发布管理/客户端发布管理模块PRD.md`(v1.2) **关联数据模型**: `DATA_MODEL/DATA_MODEL_PUBLIC.md`(`client_releases` / `client_heartbeats`) **关联枚举字典**: `DATA_MODEL/ENUMS.md`(含中文显示标签) **关联契约规范**: `TECH_STACK/API_CONTRACT.md` **最后更新**: 2026-04-30 --- ## 变更历史 | 日期 | 变更人 | 变更内容 | |---|---|---| | 2026-04-30 | Atlas | 补充“变更历史”章节(文档治理) | ## 一、文档定位与边界 本文件定义客户端发布管理模块(`apps/release`)的实现口径: 1. Electron 客户端壳应用与运行时安全边界 2. EV 代码签名与构建发布链路 3. Heartbeat 上报与版本分布统计方案 4. 自动升级、强制升级与失败回退策略 5. 客户端下载完整性校验(SHA256) 6. R2 版本资产管理(对象键、状态流转、回滚) 7. 公司下载站点(官网)分发方案 8. 便携版(Portable ZIP)落地方案 > 本文件不重复 DDL。表结构与索引以 `DATA_MODEL_PUBLIC.md` 为唯一权威。 --- ## 二、范围定义(以 PRD v1.2 为准) ### 2.1 P0 必须覆盖 - Windows 客户端(win32)下载安装与登录使用 - 平台运营后台版本管理(草稿/发布/下线、普通/强制) - 自动更新(启动 + 每 4 小时检测) - SHA256 完整性校验(EXE/ZIP) - Heartbeat(启动时上报)与版本分布/租户活跃统计 - 官方下载页(公司站点) - 便携版 ZIP(可选上传、受控分发) ### 2.2 非目标(本期不做) - macOS / Linux 客户端 - 移动端 App - 客户端离线模式 - 客户端反逆向加固(v2) --- ## 三、模块架构边界 ### 3.1 模块职责(`apps/release`) - 管理端:客户端版本元数据管理、发布状态流转、回滚 - 公共接口:客户端更新检测、下载引导、Heartbeat 上报 - 统计接口:版本分布、租户活跃数、历史装机总数 - 资产管理:R2 对象键规范、发布包引用与审计 ### 3.2 分层与鉴权 | 子能力 | Schema | 鉴权 | 说明 | |---|---|---|---| | 版本管理后台 API | public | Platform Admin 必须认证 | 跨租户统一运营能力 | | 更新检测 API | public | 公开(客户端调用) | 仅返回当前发布版本,不暴露草稿/下线版本 | | Heartbeat API | public | 已登录客户端会话或签名设备票据 | 防伪造上报、防刷统计 | | 统计 API | public | Platform Admin 必须认证 | 提供 Story 5 所需聚合指标 | ### 3.3 外部依赖 | 依赖 | 用途 | |---|---| | Electron + electron-updater | 客户端壳应用、更新下载与安装 | | electron-builder | 打包 NSIS EXE + Portable ZIP | | EV 代码签名证书 | Windows SmartScreen 信任 | | Cloudflare R2 + CDN | 发布包存储与分发 | | Celery | 异步计算 checksum / 文件扫描 / 可选预热 | --- ## 四、API 设计原则 1. **路径以 PRD 为准**:统一使用 `/api/release/...` 命名空间。 2. **只允许单一生效版本**:同 `platform + arch` 仅 1 条 `published`。 3. **公开接口最小暴露**:客户端仅获取更新必要字段,不返回后台内部字段。 4. **统计可信优先**:Heartbeat 仅“启动上报”,按 `(tenant_id, device_id)` Upsert。 5. **完整性优先于安装**:校验失败禁止安装,保留当前版本可用。 6. **强制更新可控**:`release_type=force` + `min_required_version` 双保险。 --- ## 五、端点清单(核心) ### 5.1 页面路由(平台运营后台) | 路径 | 方法 | 鉴权 | 说明 | |---|---|---|---| | `/platform/release/updates/` | GET | Platform Admin | 版本列表页 | | `/platform/release/updates/new/` | GET | Platform Admin | 新建版本页 | | `/platform/release/updates/{id}/edit/` | GET | Platform Admin | 编辑版本页 | | `/platform/release/metrics/` | GET | Platform Admin | 版本分布与租户活跃榜 | ### 5.2 JSON API(对齐 PRD + 数据模型) | 端点 | 方法 | 说明 | |---|---|---| | `/api/release/updates/latest/` | GET | 客户端检查最新版本(公开) | | `/api/release/updates/` | GET | 管理端查询版本列表 | | `/api/release/updates/` | POST | 管理端创建版本(草稿/发布) | | `/api/release/updates/{id}/` | PATCH | 修改状态、版本类型、日志 | | `/api/release/updates/{id}/rollback/` | POST | 回滚至历史版本(原子切换 published) | | `/api/release/heartbeats/` | POST | 客户端启动上报(Upsert) | | `/api/release/metrics/version-distribution/` | GET | 版本活跃分布 | | `/api/release/metrics/tenant-installs/` | GET | 指定租户活跃安装数 + 历史装机数 | | `/api/release/metrics/tenant-leaderboard/` | GET | 全平台租户活跃榜 | > 说明:`heartbeats/metrics` 为实现 Story 5 与 `DATA_MODEL_PUBLIC` 聚合查询所需端点,归属同一模块。 --- ## 六、关键 API 规范(请求/响应) ### 6.1 更新检测 `GET /api/release/updates/latest/?platform=win32&arch=x64¤t_version=1.2.0` 响应(有更新): ```json { "has_update": true, "latest_version": "1.3.0", "force_update": false, "min_required_version": "1.0.0", "download_url": "https://download.fonrey.com/releases/system/v1.3.0/fonrey-setup-1.3.0-win.exe", "portable_url": "https://download.fonrey.com/releases/system/v1.3.0/fonrey-portable-1.3.0-win.zip", "checksum_sha256": "", "portable_checksum_sha256": "", "file_size_bytes": 157286400, "release_notes": "## v1.3.0\n- ...", "release_date": "2026-05-01" } ``` ### 6.2 Heartbeat 上报 `POST /api/release/heartbeats/` ```json { "device_id": "9e6de37b-8c49-4f9b-af47-52f4e5b8b7f2", "client_version": "1.3.0", "platform": "win32", "arch": "x64", "os_version": "Windows 11 23H2" } ``` 处理要求: - 服务端从登录上下文解析 `tenant_id`、`user_id` - 使用 `INSERT ... ON CONFLICT (tenant_id, device_id) DO UPDATE` - 更新 `last_seen_at` 与 `launch_count = launch_count + 1` ### 6.3 版本发布(管理端) `POST /api/release/updates/` ```json { "version": "1.3.0", "platform": "win32", "arch": "x64", "release_type": "normal", "min_required_version": "1.0.0", "download_url": "https://download.fonrey.com/releases/system/v1.3.0/fonrey-setup-1.3.0-win.exe", "portable_url": "https://download.fonrey.com/releases/system/v1.3.0/fonrey-portable-1.3.0-win.zip", "checksum_sha256": "", "portable_checksum_sha256": "", "release_notes": "## v1.3.0\n- ...", "status": "published" } ``` --- ## 七、HTMX 交互约定(平台运营后台) - 列表筛选(状态/版本号)使用 HTMX 局刷表格区域 - 发布/下线/回滚操作使用确认弹窗 + 局部刷新 - 成功:`HX-Trigger: toast-success` - 失败:`HX-Trigger: toast-error`(同时返回标准错误码) 模板建议: - `templates/release/updates_list.html` - `templates/release/fragments/updates_table.html` - `templates/release/fragments/version_distribution_chart.html` - `templates/release/fragments/tenant_leaderboard_table.html` --- ## 八、权限与数据范围 ### 8.1 最小权限矩阵 | 能力 | permission_code | |---|---| | 客户端版本列表查看 | `platform.release.view.allow` | | 创建/编辑发布版本 | `platform.release.edit.allow` | | 发布/下线/回滚 | `platform.release.publish.allow` | | 版本分布与租户统计查看 | `platform.release.metrics.view.allow` | ### 8.2 范围规则 - 所有管理能力仅 Platform Admin 可访问 - Tenant Admin / Agent 不可访问平台发布后台 - 数据物理存储在 public schema,逻辑上属于平台级共享数据 --- ## 九、异步任务与缓存策略 ### 9.1 异步任务 | 任务 | 触发时机 | 说明 | |---|---|---| | `release_compute_checksum_task` | 文件上传后 | 计算 EXE/ZIP SHA256 并回填 | | `release_publish_cdn_warmup_task` | 版本发布后 | 可选,预热热点下载节点 | | `release_scan_artifact_task` | 文件上传后 | 可选,执行恶意文件扫描与审计 | ### 9.2 Redis Key 建议 | Key | TTL | 说明 | |---|---|---| | `release:latest:{platform}:{arch}` | 60s | 最新发布版本缓存 | | `release:metrics:version_distribution` | 60s | 版本分布聚合缓存 | | `release:metrics:tenant:{tenant_id}` | 60s | 单租户安装/活跃统计缓存 | | `release:download:ratelimit:{ip}` | 60s | 下载链接接口限流 | --- ## 十、性能与可靠性约束 - 更新检测接口:`p95 < 120ms`(缓存命中) - Heartbeat 写入:`p95 < 80ms` - 版本列表页:`p95 < 200ms` - 发布状态切换使用事务,保证“下线旧版 + 发布新版”原子完成 - 任意更新失败不影响当前版本继续运行(可恢复原则) --- ## 十一、安全与合规 1. Electron 必须启用:`contextIsolation=true`、`nodeIntegration=false`、`sandbox=true`。 2. 更新包仅允许 HTTPS 下载;域名白名单固定为 `download.fonrey.com`。 3. EV 证书私钥仅在 CI 密钥库中可用,禁止落盘到开发机。 4. 校验值由服务端生成并签名传输(至少 TLS + 服务端可信源)。 5. Heartbeat 接口必须防重放/防刷(鉴权 + 频控 + 审计)。 6. 管理端操作(发布、回滚、下线)全部记录审计日志。 --- ## 十二、错误码建议 | code | HTTP | 中文含义 | |---|---|---| | `RELEASE_VERSION_INVALID` | 400 | 版本号不符合 SemVer | | `RELEASE_PUBLISHED_CONFLICT` | 409 | 当前平台架构已存在发布版本 | | `RELEASE_ARTIFACT_NOT_FOUND` | 404 | 发布包不存在或不可访问 | | `RELEASE_CHECKSUM_MISMATCH` | 400 | 安装包完整性校验失败 | | `RELEASE_HEARTBEAT_INVALID` | 400 | 心跳参数非法 | | `RELEASE_PERMISSION_DENIED` | 403 | 权限不足 | | `RELEASE_RATE_LIMITED` | 429 | 请求过于频繁 | --- ## 十三、测试映射(P0) | Story | 测试关注点 | |---|---| | Story 1 下载安装 | 官网下载链接可用、签名有效、安装步骤 ≤3、首次启动直达登录 | | Story 2 客户端使用 | Chromium 内核能力、HTMX/Alpine/Tailwind 渲染一致性、文件上传下载 | | Story 3 自动升级 | 启动 + 4h 检测、普通更新/强制更新分支、失败可恢复 | | Story 4 发布管理 | 版本创建/发布/下线/回滚、唯一 published 约束 | | Story 5 版本分布 | Heartbeat Upsert、活跃统计(24h)、租户活跃榜排序 | 测试文件建议:`tests/integration/release/test_us_release.py` --- ## 十四、落地顺序建议 1. `apps/release` 模型/服务/API 基础骨架(先打通 `/api/release/updates/latest/`) 2. 平台运营后台版本管理页(列表 + 新建 + 发布/下线) 3. Electron 壳应用最小可运行版本(加载 Web + 标题版本) 4. 自动更新链路(electron-updater + 后端 latest API) 5. Heartbeat 上报 + 统计 API + 后台图表 6. 官网下载页上线(公司域名) 7. 便携版 ZIP 与企业无安装权限场景验收 --- ## 十五、文档同步规则 - PRD 发布管理模块变更:同步本文件 - `client_releases/client_heartbeats` 字段变更:同步 `DATA_MODEL_PUBLIC.md` - 枚举值变更:同步 `DATA_MODEL/ENUMS.md`(含中文标签) - API 包络/错误契约变更:同步 `TECH_STACK/API_CONTRACT.md` - 若将来新增独立测试用例文档:同步 `TECH_STACK/测试规范.md` 与测试用例注册表 --- ## 附:你关注的 8 个专题落地结论 1. **Electron 方案**:采用 Electron + electron-updater,客户端坚持“壳应用”原则。 2. **EV 证书方案**:CI 自动签名,证书私钥仅在密钥管理系统。 3. **Heartbeat 方案**:仅启动上报,`(tenant_id, device_id)` Upsert,支撑活跃与版本分布。 4. **自动升级方案**:启动 + 每 4h 轮询,普通/强制双模式,失败可恢复。 5. **完整性验证**:下载后先 SHA256 校验,再安装;失败禁止覆盖当前版本。 6. **R2 版本管理**:`releases/system/v{version}/...` 路径规范,发布状态驱动可见性。 7. **公司网站下载**:`download.fonrey.com` 静态下载页 + 版本信息 + 更新日志。 8. **便携版实现**:electron-builder 输出 ZIP,首次运行写入用户目录配置,不修改系统级安装。