# 版本检查接口契约(启动强制升级) > 客户端在启动时请求版本接口,判断是否需要**强制升级**。本文是**服务端响应格式的权威契约**——字段以 `app/update_check.py` 的实际读取逻辑为准,服务端按此返回,避免格式漂移。 > T-544 已实现启动检查与浏览器下载引导;T-615 起增加自动安装所需的发布契约。T-615 只生成可验证发布包,客户端下载与替换由后续任务实现。 ## 一、接口 - 客户端在 `main()` 启动流程里、进主界面之前请求(`app/gui/__init__.py` 的 `_run_startup_update_gate` → `app/update_check.py`)。 - URL 硬编码于 `app/version.py`: ``` APP_UPDATE_CHECK_URL = "https://cm.833729.com/api/v1/client/releases/latest?platform=windows" ``` - 方法:`GET`,请求头 `Accept: application/json`、`User-Agent: cmshopee/<版本>`。 - 客户端当前版本见 `app/version.py` 的 `APP_VERSION`。 ## 二、客户端读取的字段(`update_check.parse_update_info`) 响应是一个 JSON 对象。以下字段**顶层 payload 或 `release` 子对象里都认**(客户端按 `payload.get(x) or release.get(x)` 取,顶层优先): | 字段 | 类型 | 说明 | | --- | --- | --- | | `version` / `latest_version` | string | 最新版本号。客户端取 `payload.latest_version` 或 `release.version`。**`latest_version` 与 `min_supported_version` 至少要有一个**,否则客户端判为"接口缺字段"、按失败放行 | | `force_update` | bool | 是否强制升级。`true`/`1`/`yes`/`y`/`on`(大小写不敏感)都识别为真 | | `min_supported_version` | string | 最低支持版本;本地低于它一律强制 | | `download_url` | string(URL) | 下载地址;自动安装时必须是 HTTPS | | `sha256` | string | zip 的 SHA-256;自动安装时必须是 64 位十六进制且不可为空 | | `size_bytes` | integer | zip 的准确字节数;自动安装时必须大于 0 | | `package_format` | string | 自动安装固定为 `cmshopee-portable-v1` | | `updater_protocol` | integer | 发布包要求的更新器协议版本,当前为 `1` | | `min_updater_protocol` | integer | 可安装此包的最低更新器协议,当前为 `1` | | `release_notes` / `message` | string | 升级说明,显示在强制升级弹窗里。客户端取 `message` 或 `release_notes` | 版本号比较用**语义化数字段比较**(`compare_versions`,逐段比数字),不是字符串字典序:`0.1.10 > 0.1.9`。 ## 三、强制规则(`is_forced_update`) 满足**任一**即判为强制: 1. `force_update = true` **且** 本地版本 `<` `latest_version`; 2. 本地版本 `<` `min_supported_version`。 两者都不满足 → **非强制**。 ## 四、客户端行为(T-544 已实现) - **强制**:弹**模态框**,显示 `release_notes`/`message`,按钮只有「下载新版」(打开 `download_url`)和「退出程序」;无论点哪个,**程序退出、进不了主界面** → 必须升级后才能用。 - **非强制**:**不弹任何提示**,直接进主界面(当前无"温和可跳过提示"分支;如需另立任务)。 - **失败放行**:接口断网、超时、返回非法 JSON、缺 `latest_version`/`min_supported_version` 时,客户端记诊断日志(`data/logs/cmshopee.log`,`step=startup_update_check`「已允许继续使用」)并**放行**,不因服务器故障导致全员打不开。 ## 五、发版约定(服务端据此控制) 自动安装使用的全部字段必须放在同一个 `release` 对象内,不得把版本取自一个对象、hash 取自另一个对象。构建脚本生成的 `release/release-metadata.json` 是服务端录入模板;发布人员只补 HTTPS `download_url`、强制策略和中文发布说明,不得手工改写 hash、大小、包格式或协议版本。 | 想要的效果 | 服务端返回 | | --- | --- | | **强制升到最新**(方案 A) | `release.force_update: true` + `release.version` 高于要淘汰的客户端版本 | | **强制淘汰旧版**(方案 B) | `min_supported_version` 设为要淘汰之上的版本(顶层或 `release` 里都可)| | **不强制** | `force_update`/`min_supported_version` 都不给 → 客户端不弹(当前不提示)| ## 六、示例响应 ### 强制升级(方案 A,推荐) ```json { "platform": "windows", "release": { "version": "0.1.1", "download_url": "https://cm.833729.com/media/downloads/%E8%9D%A6%E7%9A%AE%E5%9C%88%E5%84%AA%E5%8C%96%E5%8A%A9%E6%89%8B0.1.1.zip", "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "size_bytes": 123456789, "package_format": "cmshopee-portable-v1", "updater_protocol": 1, "min_updater_protocol": 1, "release_notes": "优化了ai模块的生图的功能", "published_at": "2026-07-08T14:30:26.935549+08:00", "force_update": true } } ``` 效果:本地 `0.1.0` < `0.1.1` 且 `force_update=true` → 强制弹窗、必须升级。 ### 强制淘汰旧版(方案 B) ```json { "platform": "windows", "min_supported_version": "0.1.1", "release": { "version": "0.1.1", "download_url": "https://cm.833729.com/media/downloads/...0.1.1.zip", "release_notes": "优化了ai模块的生图的功能" } } ``` 效果:本地低于 `0.1.1` 的一律强制升级。 ### 不强制(有新版但不逼升) ```json { "platform": "windows", "release": { "version": "0.1.1", "download_url": "https://cm.833729.com/media/downloads/...0.1.1.zip", "release_notes": "优化了ai模块的生图的功能" } } ``` 效果:客户端解析成功但判为非强制 → **当前不弹提示**,直接进主界面。 ## 七、注意 - 服务端返回 `force_update`/`min_supported_version` 属**本仓库外的服务端配置**;客户端代码(`app/update_check.py`/`app/version.py`/启动流程)已按本契约实现,**发强制版不需要改客户端**。 - `download_url` 里的中文可用 URL 编码(如 `%E8%9D%A6...`),系统浏览器能正确打开。 - 若将来要"非强制也温和提示(可跳过、不阻断)",需在客户端加一个非强制分支,另立任务。 - 空 `sha256` 只兼容 T-544 的人工下载引导,绝不能进入自动安装。自动安装还必须同时校验 HTTPS、`size_bytes`、包格式和更新器协议。 - `manifest_signature` 与 `signature_algorithm` 是预留字段;当前未启用数字签名,不能将 SHA-256 描述为发布者身份认证。