# App 客户端对接文档 本文面向 Android、iOS、Windows、macOS、Linux 等客户端,用于对接 VersionFlow 的公开检查更新接口。 ## 1. 接口概览 客户端不需要登录后台,也不需要 JWT。每个 App 在后台创建后会生成一个公开标识 `AppKey`,客户端使用它检查是否有新版本。 接口地址: ```http GET /api/client/v1/update ``` 本地 Docker 环境示例: ```http GET http://localhost:8080/api/client/v1/update?appKey={appKey}&platform=android&channel=stable¤tBuild=100 ``` 生产环境请替换为你的正式域名: ```http GET https://your-domain.com/api/client/v1/update?appKey={appKey}&platform=android&channel=stable¤tBuild=100 ``` ## 2. 请求参数 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `appKey` | string | 是 | 后台应用详情页中的 AppKey。它是公开客户端标识,不是密钥。 | | `platform` | string | 是 | 平台键,例如 `android`、`ios`、`windows`、`macos`、`linux`。 | | `channel` | string | 否 | 发布渠道,默认 `stable`。可使用 `beta` 或后台自定义渠道。 | | `currentBuild` | number | 否 | 当前客户端整数构建号,默认 `0`。必须大于等于 0。 | 注意:版本新旧只比较 `buildNumber/currentBuild`,不要用 `versionName` 做大小判断。 ## 3. 成功响应 无可用更新: ```json { "hasUpdate": false, "forceUpdate": false, "release": null } ``` 有可用更新: ```json { "hasUpdate": true, "forceUpdate": false, "release": { "id": "2f0f4c1a-7c8f-4d5b-90fa-5cb0b2f3e1a1", "versionName": "2.4.0", "buildNumber": 240, "downloadUrl": "https://downloads.example.com/app-2.4.0.apk", "releaseNotes": "- 新增功能\n- 修复问题", "sha256": "可选的 64 位 SHA-256", "fileSize": 104857600, "publishedAt": "2026-07-20T05:00:00+00:00" } } ``` 字段说明: | 字段 | 说明 | | --- | --- | | `hasUpdate` | 是否存在比 `currentBuild` 更高的已生效版本。 | | `forceUpdate` | 是否必须升级。后台版本标记强制更新,或当前构建号低于 `minimumSupportedBuild` 时为 `true`。 | | `release.versionName` | 展示版本号,例如 `2.4.0`。仅用于显示。 | | `release.buildNumber` | 目标版本整数构建号,用于比较版本新旧。 | | `release.downloadUrl` | 安装包下载外链,系统不上传、不代理安装包。 | | `release.releaseNotes` | Markdown 更新说明。App 内展示时建议做安全渲染或纯文本展示。 | | `release.sha256` | 可选校验值。客户端下载后可用它校验安装包完整性。 | | `release.fileSize` | 可选文件大小,单位字节。 | | `release.publishedAt` | 发布时间,UTC 时间。 | ## 4. 错误响应 | HTTP 状态码 | 场景 | | --- | --- | | `400` | 参数无效,例如缺少 `appKey`、`platform` 为空、`currentBuild` 为负数。 | | `404` | AppKey 不存在、App 已停用、平台不存在/停用、渠道不存在/停用。 | | `429` | 触发 IP/AppKey 限流。客户端应稍后重试。 | | `500` | 服务端异常。客户端应降级为“不提示更新”,并记录日志。 | 错误响应使用 ASP.NET Core 标准 ProblemDetails 结构,常见格式如下: ```json { "type": "https://tools.ietf.org/html/rfc9110#section-15.5.5", "title": "Application not found", "status": 404 } ``` ## 5. 版本选择规则 服务端会在指定 `appKey + platform + channel` 下选择已经生效、未归档且构建号最高的版本。 规则如下: 1. App 停用时,客户端接口返回 `404`。 2. 草稿、归档版本不会返回给客户端。 3. 定时发布版本在到达 `scheduledAt` 之前不可见,到期后可被选中。 4. 当前客户端 `currentBuild >= 最新 buildNumber` 时,返回 `hasUpdate=false`。 5. 存在更新且版本标记了 `forceUpdate=true` 时,返回 `forceUpdate=true`。 6. 存在更新且 `currentBuild < minimumSupportedBuild` 时,返回 `forceUpdate=true`。 7. `stable`、`beta` 和自定义渠道互相隔离,不会串用版本。 8. Android、iOS、Windows、macOS、Linux 等平台互相隔离,不会串用版本。 ## 6. App 端推荐流程 启动后或进入设置页时检查更新即可,不建议每次前后台切换都请求。 推荐流程: 1. 读取当前客户端构建号 `currentBuild`。 2. 根据当前包的平台和渠道组装请求。 3. 请求失败时静默降级,不阻塞 App 启动。 4. `hasUpdate=false` 时不提示。 5. `hasUpdate=true && forceUpdate=false` 时显示可取消的更新弹窗。 6. `hasUpdate=true && forceUpdate=true` 时显示不可取消的强制更新弹窗。 7. 用户确认后打开 `downloadUrl`,或进入系统下载流程。 8. 如果返回了 `sha256`,下载安装包后做完整性校验。 ## 7. JavaScript/TypeScript 示例 ```ts type UpdateResponse = { hasUpdate: boolean forceUpdate: boolean release: null | { id: string versionName: string buildNumber: number downloadUrl: string releaseNotes: string sha256?: string | null fileSize?: number | null publishedAt: string } } export async function checkUpdate() { const baseUrl = 'https://your-domain.com' const params = new URLSearchParams({ appKey: 'replace-with-app-key', platform: 'android', channel: 'stable', currentBuild: String(100), }) const response = await fetch(`${baseUrl}/api/client/v1/update?${params}`) if (response.status === 404) return null if (response.status === 429) throw new Error('检查更新过于频繁,请稍后再试') if (!response.ok) throw new Error(`检查更新失败:${response.status}`) const result = (await response.json()) as UpdateResponse if (!result.hasUpdate || !result.release) return null return result } ``` ## 8. Android Kotlin 示例 ```kotlin data class UpdateResponse( val hasUpdate: Boolean, val forceUpdate: Boolean, val release: ReleaseInfo? ) data class ReleaseInfo( val id: String, val versionName: String, val buildNumber: Long, val downloadUrl: String, val releaseNotes: String, val sha256: String?, val fileSize: Long?, val publishedAt: String ) // 使用 OkHttp / Retrofit 均可,示例只展示 URL 组装 val url = HttpUrl.Builder() .scheme("https") .host("your-domain.com") .addPathSegments("api/client/v1/update") .addQueryParameter("appKey", "replace-with-app-key") .addQueryParameter("platform", "android") .addQueryParameter("channel", "stable") .addQueryParameter("currentBuild", BuildConfig.VERSION_CODE.toString()) .build() ``` ## 9. iOS Swift 示例 ```swift struct UpdateResponse: Decodable { let hasUpdate: Bool let forceUpdate: Bool let release: ReleaseInfo? } struct ReleaseInfo: Decodable { let id: String let versionName: String let buildNumber: Int64 let downloadUrl: String let releaseNotes: String let sha256: String? let fileSize: Int64? let publishedAt: String } var components = URLComponents(string: "https://your-domain.com/api/client/v1/update")! components.queryItems = [ URLQueryItem(name: "appKey", value: "replace-with-app-key"), URLQueryItem(name: "platform", value: "ios"), URLQueryItem(name: "channel", value: "stable"), URLQueryItem(name: "currentBuild", value: "100") ] let (data, response) = try await URLSession.shared.data(from: components.url!) let http = response as! HTTPURLResponse if http.statusCode == 200 { let result = try JSONDecoder().decode(UpdateResponse.self, from: data) // 根据 result.hasUpdate / result.forceUpdate 展示更新弹窗 } ``` ## 10. 后台发布注意事项 为了让客户端能正确收到更新,后台发布版本时请确认: 1. App 处于启用状态。 2. 平台和渠道处于启用状态。 3. `buildNumber` 大于线上客户端的 `currentBuild`。 4. 版本状态是已发布,或定时发布时间已经到期。 5. `downloadUrl` 是可公开访问的 HTTP/HTTPS 绝对地址。 6. 强制更新策略按需设置:`forceUpdate` 或 `minimumSupportedBuild`。 ## 11. 本地调试 启动服务: ```bash docker compose up -d ``` 访问后台: ```text http://localhost:8080/ ``` 首次启动前配置引导管理员: ```bash export Admin__BootstrapUsername='admin' export Admin__BootstrapPassword='replace-with-a-random-password-of-at-least-12-characters' ``` 首次登录后必须修改密码。正式环境使用 HTTPS,并保持 `Admin__CookieSecure=true`;不存在固定 默认密码,也不再通过请求头管理密钥登录。 检查更新示例: ```bash curl "http://localhost:8080/api/client/v1/update?appKey=replace-with-app-key&platform=android&channel=stable¤tBuild=0" ``` 停止服务: ```bash docker compose down ```