122 lines
4.4 KiB
Markdown
122 lines
4.4 KiB
Markdown
# 后端 API 契约(账号 + 支付/授权)
|
||
|
||
客户端(Flutter)与后端之间的 REST 契约。账号体系:手机号 + 密码注册登录,登录返回自签名 token,后续接口携带 `Authorization: Bearer <token>`;**识别入口(搜索按钮)必须强制服务端校验授权**,不走本地缓存。
|
||
|
||
- Base URL: `AppConfig.apiBaseUrl`(占位 `https://YOUR_BACKEND.example.com`)
|
||
- 响应统一格式: `{"code": 0, "message": "ok", "data": {...}}`,`code != 0` 视为失败;登录失效返回 `code 61`,客户端应回登录页
|
||
- 授权语义: 自然日(当天 24:00 失效 / 7 天 / 30 天),以服务端为准
|
||
- 账号标识: 手机号(服务端 license 表即账号表,手机号为主键;无独立用户表)
|
||
|
||
## 1. 注册
|
||
|
||
`POST /api/v1/auth/register`(公开,无需登录)
|
||
|
||
```json
|
||
{"phone": "13800138000", "password": "pass123456"}
|
||
```
|
||
|
||
响应 `data` 为空对象。重复注册报错;已被运营手动授权(发卡占位行)的手机号可注册补密码,授权保留。
|
||
|
||
## 2. 登录
|
||
|
||
`POST /api/v1/auth/login`(公开,无需登录)
|
||
|
||
```json
|
||
{"phone": "13800138000", "password": "pass123456"}
|
||
```
|
||
|
||
响应 `data`:
|
||
|
||
```json
|
||
{"token": "<HMAC-SHA256 自签名 token>"}
|
||
```
|
||
|
||
- token 无状态,有效期 `auth.tokenTtl`(默认 30 天),客户端存 secure storage
|
||
- 后续所有接口携带 `Authorization: Bearer <token>`
|
||
|
||
## 3. 创建订单
|
||
|
||
`POST /api/v1/orders`(需登录)
|
||
|
||
```json
|
||
{"planId": "day|week|month", "channel": "wechat|alipay"}
|
||
```
|
||
|
||
响应 `data`:
|
||
|
||
```json
|
||
{
|
||
"orderId": "O20260822001",
|
||
"payParams": {
|
||
"partnerId": "1900xxxxx", "prepayId": "wx...", "nonceStr": "...",
|
||
"timeStamp": "1728000000", "sign": "...", "packageValue": "Sign=WXPay"
|
||
}
|
||
}
|
||
```
|
||
|
||
- 手机号由 token 识别,请求体不含 deviceId
|
||
- `channel == wechat` 时 `payParams` 为微信 APP 支付下单参数
|
||
- `channel == alipay` 时 `payParams` 为 `{"orderStr": "alipay_sdk=..."}`
|
||
|
||
## 4. 支付结果确认
|
||
|
||
`POST /api/v1/orders/{orderId}/confirm`(需登录)
|
||
|
||
```json
|
||
{}
|
||
```
|
||
|
||
客户端拉起 SDK 支付成功后调用(幂等)。服务端以微信/支付宝异步回调为准落授权;confirm 仅用于加速刷新。响应 `data: {"status": "paid|created|closed"}`。
|
||
|
||
## 5. 查询授权
|
||
|
||
`GET /api/v1/license`(需登录)
|
||
|
||
响应 `data`:
|
||
|
||
```json
|
||
{"active": true, "expiresAt": "2026-08-29T23:59:59+08:00"}
|
||
```
|
||
|
||
- `active: false` 或 `expiresAt` 已过期 → 客户端展示付费墙/充值入口
|
||
- **识别入口(搜索按钮)必须调本接口做强制校验**:网络失败视为不可用并提示,禁止用本地缓存放行
|
||
- 主界面到期时间展示可用本地缓存(服务端为准,刷新时覆盖)
|
||
|
||
## 6. 套餐
|
||
|
||
`GET /api/v1/plans`(需登录)拉取价格方案,**客户端不硬编码价格**(进充值页时拉取,价格以服务端为准)。
|
||
|
||
响应 `data`:
|
||
|
||
```json
|
||
{
|
||
"list": [
|
||
{"planId": "day", "days": 1, "priceCents": 1000},
|
||
{"planId": "week", "days": 7, "priceCents": 5600},
|
||
{"planId": "month", "days": 30, "priceCents": 18000}
|
||
]
|
||
}
|
||
```
|
||
|
||
- `priceCents` 为整数分,客户端展示 ÷100 转元
|
||
- 展示名由客户端按 `days` 派生「N天」,接口无 label 字段
|
||
- 后端套餐来自 `config.yml` `plans` 节点(静态定价,改价改配置重启生效)
|
||
|
||
## 7. 版本更新检查
|
||
|
||
`GET /api/v1/app/update`(**公开接口,无需登录**)在 App 启动时调用(协议层见 `lib/update/update_checker.dart`)。
|
||
|
||
响应 `data`(无记录时字段为空串,视为无需更新):
|
||
|
||
```json
|
||
{
|
||
"version": "1.1.0",
|
||
"notes": "修复识别准确率问题"
|
||
}
|
||
```
|
||
|
||
- **仅 Android 调用**:`UpdateChecker.fetch()` 在非 Android 平台直接返回空(iOS 不做版本下发,用户从 App Store 自行更新)
|
||
- 客户端以「语义化版本号」按数字段比较(`1.10.0 > 1.9.9`),**服务器版本 > 已装版本即强制更新**:弹全屏阻塞页(禁返回,仅「立即更新」),`url_launcher` 打开系统浏览器下载固定地址 `{apiBaseUrl}/download/observer-latest.apk`(后端静态托管,永远是最新 APK)
|
||
- **双版本比较防反复提示**:用户点「立即更新」时把服务器版本号写入本地(`SessionStore.accepted_update_version`);判定条件是服务器版本 > 已装版本 **或** 服务器版本 > 已接受版本。APK 版本号不递增(每次打的包 versionName 相同)时,已更新完成的手机重启也不会再次弹更新
|
||
- 网络异常/响应异常时静默跳过检查,不阻塞启动
|