新网站服务端 App 接口要求

# 新网站服务端 App 接口要求

更新日期:2026-09-15

本文提供给新网站后端、网页端和运维人员。目标是在不修改 App 业务协议的前提下,让新网站接入现有 App API 网关。网站后端需要实现或复用本文列出的上游接口;网关自有的分流、首页转换、App 更新和 Facebook CAPI 不要求网站重复实现。

## 1. 架构与责任边界

```text
App
  -> 新站 App API 网关
       -> 新网站业务 API:用户、套餐、余额、IAP、上传、创作记录、站点内容
       -> 生成服务 API:提交任务、查询任务
       -> D1:App 发版记录
       -> Facebook CAPI:服务端事件
```

网站服务必须提供稳定的 HTTPS 地址,并向网关交付:

| 配置 | 必填 | 说明 |
|------|------|------|
| `HIGOON_PAYMENT_BASE_URL` | 是 | 新网站业务 API 根地址,不带末尾 `/` |
| `HIGOON_VIDEO_BASE_URL` | 是 | 新网站生成 API 根地址;与业务 API 相同时可填同一地址 |
| `HIGOON_SITE_ID` | 是 | 新网站站点 ID |
| `APP_ID` | 是 | 默认 `com.veloxreel.app`;新 App 应使用自己的 Bundle ID/应用标识 |
| iOS 商品配置 | 有内购时必填 | 数据库套餐 ID、App Store Product ID、Bundle ID 和 Apple 验证凭据 |
| App 发版信息 | 是 | platform、bundle ID、version、build、商店地址 |
| Meta Pixel ID | 使用广告事件时必填 | profile 返回当前站点有效 `pixelId` |

当前 Worker 配置是单套上游地址和单个默认站点。若新网站需要与现有网站同时独立运行,建议为新网站部署独立 Worker 或新增按域名选择配置的代码;不能只把新域名绑定到旧 Worker 而继续使用旧站 `SITE_ID`。

## 2. 通用协议要求

### 2.1 请求与站点隔离

- 所有业务接口使用 HTTPS。
- Header 名大小写不敏感,但实现必须正确读取 `Authorization`、`X-Site-Id`、`X-Locale`、`X-App-Version`、`X-App-Id` 和 `X-IOS-Proof`。
- `X-Site-Id` 必须参与数据隔离。Token 用户不属于该站点时应拒绝请求,不能跨站查询用户、余额、套餐或创作记录。
- JWT 必须能稳定解析出用户 ID;创作记录和余额接口不能相信客户端传入的 `userId`。
- 所有 JSON 请求和响应使用 UTF-8。
- 登录、登录码、资料、支付和验单响应必须设置 `Cache-Control: no-store`。
- 网站不能把密码、JWT、登录码、proof、Apple/Google 凭据或第三方密钥写入普通日志。

### 2.2 统一响应信封

成功:

```json
{
  "code": 0,
  "info": "ok",
  "data": {}
}
```

失败:

```json
{
  "code": 401,
  "info": "Unauthorized",
  "data": {}
}
```

要求:

- 成功同时使用 HTTP 2xx 和业务 `code=0`。
- 参数错误使用 HTTP 400,认证失败使用 401,权限/站点不匹配使用 403,限流使用 429,依赖不可用使用 503。
- `info` 用于展示或诊断,不作为唯一机器状态;同类错误应保持 `code` 稳定。
- 字段暂时无值时使用约定的空值或省略,不随意改变字段类型。

## 3. 网关与网站的路径映射

| App/网关路径 | 网站服务必须提供的路径 | 方法 | 鉴权 |
|--------------|------------------------|------|------|
| `/api/user/register` | `/api/user/register` | POST | 否 |
| `/api/user/login` | `/api/user/login` | POST | 否 |
| `/api/user/login/app-code` | `/api/user/login/app-code` | POST | 否 |
| `/api/user/app-login-code` | `/api/user/app-login-code` | POST | 是,网页端调用 |
| `/api/user/profile` | `/api/user/profile` | GET | 是 |
| `/api/user/change-password` | `/api/user/change-password` | POST | 是 |
| `/api/user/delete-account` | `/api/user/delete-account` | POST | 是 |
| `/api/payment/coins/package/list` | `/api/client/payment/coins/package/list` | GET | 否 |
| `/api/payment/subscription/package/list` | `/api/client/payment/subscription/package/list` | GET | 否 |
| `/api/payment/coins/balance` | `/api/client/payment/coins/balance` | GET | 是 |
| `/api/payment/iap/verify` | `/api/iap/verify` | POST | 是 |
| `/api/payment/iap/verify-subscription` | `/api/iap/verifysub` | POST | 是 |
| `/api/client/upload` | `/api/client/upload` | POST | 否 |
| `/api/client/creations` | `/api/client/creations` | GET | 是 |
| `/api/client/creations/{taskId}` | `/api/client/creations/{taskId}` | DELETE | 是 |
| `/api/plugins/video/generate` | `/api/plugins/video/generate` | POST | 是 |
| `/api/plugins/video/query` | `/api/plugins/video/query` | GET | 是 |

`POST /api/home/template/generate` 是网关自有接口,不要求网站新增同名路径。网关会校验 Home `item`,选择受支持模型并转换为现有 `POST /api/plugins/video/generate` 请求。

另外,网关生成自然量首页需要网站提供:

```http
GET /api/client/site/plist?site_id=<SITE_ID>&orderType=0&page=1&pageSize=80
```

## 4. 用户接口

### 4.1 POST /api/user/register

Header:`X-Site-Id` 必填,`X-App-Version` 可选。

```json
{
  "email": "user@example.com",
  "password": "12345678",
  "nickname": "user"
}
```

| 字段 | 类型 | 必填 | 服务端校验 |
|------|------|------|------------|
| `email` | string | 是 | 合法邮箱;按产品规则归一化和唯一校验 |
| `password` | string | 是 | 至少 6 个字符,安全散列保存 |
| `nickname` | string | 否 | 做长度和安全字符限制 |

成功 `data`:

```json
{
  "accessToken": "<jwt>",
  "siteId": "<SITE_ID>",
  "ver": "1.0.0"
}
```

### 4.2 POST /api/user/login

Header:`X-Site-Id` 必填。

请求:

```json
{
  "email": "user@example.com",
  "password": "12345678"
}
```

成功 `data` 至少返回 `accessToken` 和 `ver`。Token claims 必须包含后续中间件可识别的用户 ID,并有明确有效期。

### 4.3 GET /api/user/profile

Header:`Authorization: Bearer <token>`、`X-Site-Id` 必填。

成功 `data`:

```json
{
  "id": "user-id",
  "email": "user@example.com",
  "nickname": "user",
  "avatarUrl": "",
  "premiumType": 0,
  "onetimeSub": 0,
  "premiumExpiresAt": 0,
  "autoUnlock": false,
  "status": 1,
  "referer": "",
  "pixelId": "1234567890",
  "loginType": 0,
  "hasPurchased": false,
  "ver": "1.0.0"
}
```

`pixelId` 被网关 Facebook 事件接口使用。启用 CAPI 时,必须返回新站自己的 Pixel ID,不能复用其他站点值。

### 4.4 POST /api/user/change-password

Header:`Authorization`、`X-Site-Id` 必填。

```json
{
  "newPassword": "new-password"
}
```

`newPassword` 必填,长度 6~64。成功 `data={}`。修改后是否撤销旧 Token 必须形成明确策略并告知 App。

### 4.5 POST /api/user/delete-account

Header:`Authorization`、`X-Site-Id` 必填。无请求参数。根据认证用户执行软删除或合规删除,成功 `data={}`;不得接受 body 中的其他用户 ID。

## 5. 网页到 App 的六位码登录

该能力包含网页端签发和 App 端兑换两个接口。新网站网页必须增加“在 App 登录”入口,只有已登录用户可以生成登录码。

### 5.1 POST /api/user/app-login-code

调用方:已登录网页端。

Header:

```http
Authorization: Bearer <web-user-token>
X-Site-Id: <SITE_ID>
Content-Type: application/json
```

请求体:

```json
{
  "appId": "com.example.app"
}
```

`appId` 必填,最长 128 字符,服务端归一化后必须命中允许的 App。用户 ID 只从 Token 获取。

成功:

```json
{
  "code": 0,
  "info": "ok",
  "data": {
    "code": "003821",
    "expiresIn": 300
  }
}
```

网页必须把 `data.code` 当字符串显示,保留前导零,并显示服务端返回的倒计时。登录码属于敏感凭据,不得进入分析平台。

### 5.2 POST /api/user/login/app-code

调用方:App。

```json
{
  "loginCode": "003821"
}
```

`loginCode` 必须是六位 ASCII 数字字符串。单 App 默认模式可以省略 `X-App-Id`;多 App 场景必须验证 `X-App-Id`。严格模式必须验证一次性 `X-IOS-Proof` 与请求方法、路径、body、App ID 和有效期匹配。

成功 `data`:

```json
{
  "accessToken": "<new-jwt>",
  "ver": "",
  "iosLoginReward": {
    "granted": true,
    "amount": 500,
    "balance": 500
  }
}
```

服务端验收要求:

- 登录码默认 300 秒有效,最多成功消费一次。
- 同一用户和 App 重新签发时,旧码立即失效。
- 码签发、消费、首次登录标记、奖励入账和流水具备事务一致性。
- 首次奖励按用户只发一次;重复登录不能重复领取。
- 登录码、proof 和 IP/安装实例必须有限流。
- 多副本部署时,状态、限流和防重放数据必须共享,不能只存在进程内存。
- 错误码至少覆盖 400、401、403、429、503。

当前 `shortpress-server` 已实现这两个路由及数据库/Redis 降级逻辑。复用该项目时,应完成迁移并将默认 App ID 改为新 App,而不是继续沿用旧 Bundle ID。

## 6. 套餐与余额

### 6.1 GET /api/client/payment/coins/package/list

站点可同时来自 `X-Site-Id`、`siteId` 和 `site_id`。三者同时出现时必须一致;冲突时返回 400 或 403,不能随机选择。

只返回当前站点启用的套餐。`data[]`:

| 字段 | 类型 | 必填 |
|------|------|------|
| `packageId` | string | 是 |
| `name` | string | 是 |
| `description` | string | 否 |
| `features` | object/array | 否 |
| `coinAmount` | integer | 是 |
| `price` | number/string | 是 |
| `originalPrice` | number/string | 否 |
| `currency` | string | 是 |
| `discountPercentage` | integer | 否 |
| `status` | integer | 是 |
| `iosProductId` | string | iOS 必填 |

### 6.2 GET /api/client/payment/subscription/package/list

只返回当前站点启用的订阅套餐。`data[]` 至少包含:`packageId`、`siteId`、`name`、`description`、`interval`、`price`、`originalPrice`、`currency`、`discountPercentage`、`coins`、`rights`、`status`、`iosProductId`、`createdAt`。

### 6.3 GET /api/client/payment/coins/balance

Header:`Authorization`、`X-Site-Id` 必填。用户 ID 从 JWT 获取。

成功 `data`:

```json
{
  "balance": 500,
  "totalEarned": 500,
  "totalSpent": 0,
  "totalRealMoneySpent": 0
}
```

余额读取、生成扣币、失败退款、购买入账和登录奖励必须使用同一套账本口径。

## 7. IAP 验单

### 7.1 POST /api/iap/verify

用途:一次性金币商品验单和发货。

Header:`Authorization`、`X-Site-Id`、`Content-Type: application/json`。

```json
{
  "transactionId": "2000000123456789",
  "account": "apple",
  "packageName": "com.example.app",
  "packageId": "server-package-id",
  "productId": "app-store-product-id",
  "purchaseToken": ""
}
```

服务端必须校验交易真实性、Bundle ID、商品 ID、站点套餐关系、购买状态和重复交易。`packageId` 是网站数据库 ID,`productId` 是商店商品 ID,不能混用。交易 ID 必须具备数据库唯一约束或等效幂等机制。

成功:

```json
{
  "code": 0,
  "info": "ok",
  "data": {
    "productId": "app-store-product-id"
  }
}
```

### 7.2 POST /api/iap/verifysub

请求字段同金币验单。成功 `data`:`isActive`、`sandbox`、`isInFreeTrial`、`autoRenewStatus`。

订阅有效期、续订和退款状态以商店服务端数据为准。沙盒和生产交易必须明确区分。网关在上游验单成功后可能异步上报 Facebook Purchase,因此网站返回成功前必须确保交易已经持久化。

## 8. 文件上传

### POST /api/client/upload

请求为 `multipart/form-data`,字段 `file` 恰好一个。当前兼容协议允许无 Token,但必须实施文件安全限制:

- 限制请求体和单文件大小。
- 校验实际 MIME 和文件签名,不只看扩展名。
- 生成不可预测文件名,禁止目录穿越和覆盖已有文件。
- 存储桶默认不可执行上传内容。
- 返回的路径必须可被生成服务访问。
- 对匿名上传实施 IP、设备或站点级限流,并配置生命周期清理。

成功:

```json
{
  "code": 0,
  "info": "ok",
  "data": "res/user_upload/directory/file.jpg"
}
```

上线前必须与 App/网关约定 `data` 是绝对 URL 还是相对路径。如果返回相对路径,应提供唯一且稳定的 CDN 基地址。

## 9. 生成服务

生成服务可与网站业务 API 同域,也可独立部署。无论部署方式,网关必须能通过 `HIGOON_VIDEO_BASE_URL` 访问以下两个路径。

### 9.1 POST /api/plugins/video/generate

Header:`Authorization`、`X-Site-Id` 必填。生成服务必须验证真实 JWT 用户身份和站点权限,不能只检查 Header 非空。

```json
{
  "model": "viduq2_image",
  "video_id": "template-1",
  "number": 1,
  "args": {
    "image": "https://cdn.example.com/input.jpg",
    "prompt": "跳舞",
    "duration": "5",
    "resolution": "720p"
  }
}
```

处理要求:

- 同一接口也会接收网关由 `/api/home/template/generate` 转换出的 `aitubo_dance`、`aitubo_template`、`sora_template` 和 `mulerouter/w3.0-video` 请求;网站服务应保持这些模型及其现有参数协议可用。
- `model` 必须来自服务端允许清单,拒绝未知模型。
- `args` 按模型 schema 校验必填字段、枚举、URL、数量、时长和分辨率。
- 服务端根据模型和 `number` 计算价格,不能相信客户端提交价格。
- 扣币和任务创建需具备一致性;第三方失败后按实际成功数量退款。
- 任务记录必须写入共享存储,并包含 `taskId`、`siteId`、`userId`、模型、状态和时间。
- 输入 URL 需防 SSRF,只允许可信对象存储/CDN 或经过下载代理校验。

成功 `data`:`task_id` 和 `model`。

### 9.2 GET /api/plugins/video/query

Query:`task_id` 必填,`model` 可选。Header:`Authorization`、`X-Site-Id` 必填。

必须验证任务属于 JWT 用户和当前站点。不能只凭 `task_id` 返回其他用户结果。

`data.status`:`1` 处理中、`2` 成功、`3` 失败。结果字段兼容:

```json
{
  "task_id": "task-id",
  "status": 2,
  "model": "viduq2_image",
  "prompt": "跳舞",
  "videos": [
    {
      "url": "https://cdn.example.com/result.mp4",
      "cover_url": "https://cdn.example.com/cover.jpg"
    }
  ],
  "images": [],
  "error_msg": "",
  "created_at": "2026-09-15T00:00:00Z",
  "updated_at": "2026-09-15T00:01:00Z"
}
```

## 10. 当前用户创作记录

### GET /api/client/creations

Header:`Authorization`、`X-Site-Id` 必填。

| Query | 类型 | 默认 | 限制 |
|-------|------|------|------|
| `page` | integer | `1` | 小于 1 时按 1 |
| `pageSize` | integer | `20` | 1~100,非法值按 20 |

用户 ID 只能从 JWT 获取。当前兼容范围是最近 24 小时的生成记录。

成功:

```json
{
  "code": 0,
  "info": "ok",
  "data": {
    "items": [
      {
        "taskId": "task-id",
        "status": 2,
        "model": "viduq2_image",
        "videoId": "template-1",
        "siteId": "site-id",
        "userId": "user-id",
        "prompt": "跳舞",
        "referenceImages": ["https://cdn.example.com/input.jpg"],
        "videos": [
          {
            "url": "https://cdn.example.com/result.mp4",
            "coverUrl": "https://cdn.example.com/cover.jpg"
          }
        ],
        "images": [],
        "errorMsg": "",
        "createdAt": 1789430400,
        "updatedAt": 1789430460
      }
    ],
    "total": 1,
    "page": 1,
    "pageSize": 20,
    "hasMore": false
  }
}
```

注意查询接口的 `coverUrl` 使用驼峰,而生成查询接口当前兼容字段是 `cover_url`。如需统一,必须同时升级网关和 App,不能单方面改字段。

### DELETE /api/client/creations/{taskId}

Header:`Authorization`、`X-Site-Id` 必填。服务端必须从 JWT 获取用户 ID,并校验 `taskId` 同时属于该用户和当前站点;不得接受客户端提交的 `userId`。

仅允许删除状态为 `2`(成功)或 `3`(失败)的已结束任务。状态 `1`(处理中)必须返回 HTTP 409;不存在或所有权不匹配返回 HTTP 404。删除应为用户可见层面的软删除,生成任务、扣费、退款和运营审计记录必须保留。

成功:

```json
{ "code": 0, "info": "ok", "data": {} }
```

错误响应必须保持稳定:

```json
{ "code": 409, "info": "只能删除已结束任务", "data": {} }
```

```json
{ "code": 404, "info": "创作记录不存在", "data": {} }
```

实现要求:

- 删除条件必须以服务端读取到的任务状态为准,不能相信客户端提交的状态。
- 所有权不匹配与任务不存在统一返回 404,避免泄露其他用户任务是否存在。
- 重复删除同一条已隐藏但仍保留快照的记录应保持幂等,不得重新扣费、退款或修改任务状态。
- 删除成功后,后续 `GET /api/client/creations` 不再返回该 `taskId`,并同步修正 `total`。

## 11. 自然量首页数据

### GET /api/client/site/plist

网关请求:

```http
GET /api/client/site/plist?site_id=<SITE_ID>&orderType=0&page=1&pageSize=80
Accept: application/json
```

无需用户 Token,但必须按 `site_id` 返回当前站点已发布、可公开展示的列表,排除不应出现在自然量 App 的内容。

成功 `data`:

```json
{
  "total": 1,
  "page": 1,
  "pageSize": 80,
  "hasMore": false,
  "items": [
    {
      "playlistId": "playlist-id",
      "title": "Example",
      "slug": "example",
      "description": "",
      "tags": "",
      "cover": "https://cdn.example.com/cover.jpg",
      "status": 1,
      "videoCount": 1,
      "accessType": 0,
      "singleVideoPrice": 0,
      "videos": [
        {
          "vid": "video-id",
          "status": 2,
          "unlockStatus": 1,
          "cover": "https://cdn.example.com/cover.jpg",
          "local_path": "https://cdn.example.com/video.mp4",
          "config": {
            "coin": 0,
            "effectId": "",
            "templateType": ""
          }
        }
      ]
    }
  ]
}
```

网关会把该结构转换成首页统一 Feed,并随机返回部分条目。`cover` 和 `local_path` 必须是 App 可访问的 HTTPS 地址。

## 12. 网关自有能力的网站配合项

以下接口不由网站直接实现,但网站开发和运维需要提供数据或配置:

| 网关接口 | 网站/运维配合 |
|----------|---------------|
| `/api/create-page/config` | 提供新站模型、价格、参数、默认 Tab 和流量版本的配置 JSON |
| `/api/home/*` | 提供 `/api/client/site/plist` 内容;确认广告流量 Feed 来源是否继续复用现有服务 |
| `/api/app/update/check` | 为新 App 的平台和 Bundle ID 写入 published 发版记录 |
| `/api/ads/facebook/events` | profile 返回新站 `pixelId`,运维在网关安全配置 CAPI Token |
| IAP 成功后的 Purchase | 确保套餐、币种、金额和交易 ID 可供网关正确上报和去重 |

## 13. 网页端开发要求

网页端至少增加以下功能:

1. 已登录用户可点击“在 App 登录”。
2. 网页调用 `POST /api/user/app-login-code`,携带当前用户 Token、新站 `X-Site-Id` 和新 App `appId`。
3. 展示六位字符串和 `expiresIn` 倒计时,保留前导零。
4. 用户重新生成后立即替换旧码,不继续展示多个码。
5. 401 时引导网页重新登录;429 时显示稍后再试;503 时显示服务暂不可用。
6. 不在 URL、HTML 源码、埋点、日志或客服截图中长期保存登录码。

网页不需要实现 App 兑换请求,也不能获取 `X-IOS-Proof` 的签名私钥。

## 14. 部署前交付清单

网站团队应一次性交付:

- 新网站业务 API 根地址和生成 API 根地址。
- 生产、预发布各自的 `SITE_ID`,不得混用。
- 新 App 的 `APP_ID`、iOS Bundle ID、Android 包名。
- 所有上游接口的可执行 curl/Postman 集合,凭据通过安全渠道提供。
- 测试账号、测试套餐、Apple 沙盒账号和可安全使用的测试图片。
- 上传文件的公开 URL 拼接规则、大小上限和生命周期。
- 视频模型清单及每个模型的 `args` JSON Schema、价格和退款规则。
- App 发版记录和商店地址。
- Meta Pixel ID 与 CAPI 环境归属。
- 数据库迁移、回滚、监控和报警方案。

## 15. 联调验收用例

| 编号 | 用例 | 预期结果 |
|------|------|----------|
| `AUTH-01` | 新站注册、登录、profile | Token 仅能访问新站用户 |
| `AUTH-02` | 用新站 Token 请求旧站 ID | 返回 401/403,不泄露数据 |
| `CODE-01` | 网页生成六位码并由 App 兑换 | 首次成功,返回原账号 Token |
| `CODE-02` | 重复兑换同一码 | 第二次返回 401 |
| `CODE-03` | 过期、错误 App 或错误站点兑换 | 返回 401/403 |
| `PAY-01` | 获取金币和订阅套餐 | 只出现新站启用套餐 |
| `IAP-01` | 沙盒金币交易验单两次 | 只发货一次 |
| `IAP-02` | 沙盒订阅验单 | 状态和有效期正确 |
| `UPLOAD-01` | 上传合法图片 | 返回生成服务可访问地址 |
| `UPLOAD-02` | 上传超限或伪造类型文件 | 明确拒绝,不落入公开可执行目录 |
| `GEN-01` | 图生视频完整流程 | generate、query、creations 数据一致 |
| `GEN-02` | 查询其他用户 task ID | 返回 401/403/404,不返回结果 |
| `COIN-01` | 生成成功、失败和部分成功 | 扣币及退款账本正确 |
| `HOME-01` | 获取自然量首页 | 内容只来自新站且媒体可播放 |
| `UPDATE-01` | 新旧版本检查 | 正确返回更新状态和商店 URL |
| `ADS-01` | 测试环境上报 Lead/Purchase | 进入新站 Pixel,eventId 可去重 |

全部用例通过后,再把新站域名、上游地址和站点 ID 切换到生产配置。