feat(private-room): migrate to album APIs
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
# Cozsweet 独立付费图片包前端接口
|
||||
|
||||
## 1. 用途
|
||||
|
||||
Cozsweet 前端通过本接口展示独立的 8/15 张私密图片包,并使用用户积分解锁。FB 只导入原始图片素材,后端素材池负责跨批次自动凑成 8 张或 15 张图片包。该功能不走现金支付,不使用旧 `/moments` 接口。
|
||||
|
||||
## 2. 接口地址
|
||||
|
||||
| 功能 | 方法 | 路径 |
|
||||
| --- | --- | --- |
|
||||
| 图片包列表 | GET | `/api/private-room/albums` |
|
||||
| 积分解锁 | POST | `/api/private-room/albums/{albumId}/unlock` |
|
||||
|
||||
## 4. 固定价格
|
||||
|
||||
| 图片数量 | `unlockCost` | 单张价格 |
|
||||
| ---: | ---: | ---: |
|
||||
| 8 | 320 credits | 40 credits |
|
||||
| 15 | 600 credits | 40 credits |
|
||||
|
||||
接口只会返回 `currency: "credits"`。前端不得显示现金价格,也不得调用支付套餐或创建订单接口。
|
||||
|
||||
## 5. GET /api/private-room/albums
|
||||
|
||||
### 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `character` | string | 否 | `elio` | 角色 ID。 |
|
||||
| `collectionKey` | string | 否 | 全部 | 只查看一个素材分组。 |
|
||||
| `limit` | integer | 否 | 20 | 1-50。 |
|
||||
| `cursor` | ISO datetime | 否 | - | 上一页返回的 `nextCursor`。 |
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
curl 'https://api.banlv-ai.com/api/private-room/albums?character=elio&limit=20' \
|
||||
-H 'Authorization: Bearer <TOKEN>'
|
||||
```
|
||||
|
||||
### 锁定图片包响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"success": true,
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"albumId": "a1b2c3d4-0000-0000-0000-000000000000",
|
||||
"momentId": "album:a1b2c3d4-0000-0000-0000-000000000000",
|
||||
"characterId": "elio",
|
||||
"collectionKey": "manila_202607",
|
||||
"title": "Private Manila set",
|
||||
"content": null,
|
||||
"previewText": "Only for you.",
|
||||
"imageCount": 8,
|
||||
"mediaCount": 8,
|
||||
"images": [
|
||||
{
|
||||
"url": "https://dbapi.banlv-ai.com/storage/v1/object/public/elio-schedules/01.jpg",
|
||||
"type": "image",
|
||||
"locked": true,
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
"locked": true,
|
||||
"unlocked": false,
|
||||
"unlockCost": 320,
|
||||
"requiredCredits": 320,
|
||||
"creditCostPerImage": 40,
|
||||
"currency": "credits",
|
||||
"canUnlockWithCredits": false,
|
||||
"publishedAt": "2026-07-13T00:00:00+00:00",
|
||||
"lockDetail": {
|
||||
"locked": true,
|
||||
"showContent": false,
|
||||
"showUpgrade": true,
|
||||
"reason": "private_album",
|
||||
"requiredCredits": 320,
|
||||
"currentCredits": 100,
|
||||
"shortfallCredits": 220,
|
||||
"mediaCount": 8,
|
||||
"unlockCostPerImage": 40
|
||||
}
|
||||
}
|
||||
],
|
||||
"nextCursor": null,
|
||||
"hasMore": false,
|
||||
"creditBalance": 100,
|
||||
"currency": "credits",
|
||||
"creditCostPerImage": 40,
|
||||
"packageOptions": [
|
||||
{"imageCount": 8, "creditCost": 320},
|
||||
{"imageCount": 15, "creditCost": 600}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
锁定和解锁状态都会返回完整 `images[].url`。前端必须根据 `locked` / `unlocked` / `lockDetail.locked` 决定是否显示锁层和是否允许查看原图,不能用 URL 是否为空判断解锁状态。
|
||||
|
||||
### 已解锁图片包
|
||||
|
||||
同一个用户解锁后,再次请求列表会返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"albumId": "a1b2c3d4-0000-0000-0000-000000000000",
|
||||
"locked": false,
|
||||
"unlocked": true,
|
||||
"content": "Only for you.",
|
||||
"imageCount": 8,
|
||||
"images": [
|
||||
{
|
||||
"url": "https://dbapi.banlv-ai.com/storage/v1/object/public/elio-schedules/01.jpg",
|
||||
"type": "image",
|
||||
"locked": false,
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 分页
|
||||
|
||||
`hasMore=true` 时,把 `nextCursor` 原样传回:
|
||||
|
||||
```http
|
||||
GET /api/private-room/albums?cursor=<nextCursor>&limit=20
|
||||
```
|
||||
|
||||
## 6. POST /api/private-room/albums/{albumId}/unlock
|
||||
|
||||
### 请求格式
|
||||
|
||||
```http
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <TOKEN>
|
||||
```
|
||||
|
||||
Body:
|
||||
|
||||
```json
|
||||
{
|
||||
"expectedCost": 320
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 示例 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `expectedCost` | integer | 否但建议 | 320 | 用户确认时看到的价格;价格变化则拒绝扣分。 |
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
curl -X POST 'https://api.banlv-ai.com/api/private-room/albums/a1b2c3d4-0000-0000-0000-000000000000/unlock' \
|
||||
-H 'Authorization: Bearer <TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"expectedCost":320}'
|
||||
```
|
||||
|
||||
### 解锁成功
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"success": true,
|
||||
"data": {
|
||||
"albumId": "a1b2c3d4-0000-0000-0000-000000000000",
|
||||
"locked": false,
|
||||
"unlocked": true,
|
||||
"reason": "ok",
|
||||
"unlockCost": 320,
|
||||
"creditsCharged": 320,
|
||||
"previousCreditBalance": 500,
|
||||
"creditBalance": 180,
|
||||
"images": [
|
||||
{
|
||||
"url": "https://...",
|
||||
"type": "image",
|
||||
"locked": false,
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
前端直接使用响应里的 `images` 替换锁卡,并用 `creditBalance` 刷新余额。
|
||||
|
||||
### 积分不足
|
||||
|
||||
业务失败仍返回 HTTP 200:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"success": true,
|
||||
"data": {
|
||||
"albumId": "a1b2c3d4-0000-0000-0000-000000000000",
|
||||
"locked": true,
|
||||
"unlocked": false,
|
||||
"reason": "insufficient_credits",
|
||||
"unlockCost": 320,
|
||||
"requiredCredits": 320,
|
||||
"creditBalance": 100,
|
||||
"shortfallCredits": 220,
|
||||
"creditsCharged": 0,
|
||||
"images": [
|
||||
{"url": "https://dbapi.banlv-ai.com/storage/v1/object/public/elio-schedules/01.jpg", "type": "image", "locked": true, "index": 0}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
前端可以打开积分充值页,但后端不会创建图片包现金订单。用户充值积分后,再次点击同一个解锁接口。
|
||||
|
||||
### 重复解锁
|
||||
|
||||
```json
|
||||
{
|
||||
"unlocked": true,
|
||||
"locked": false,
|
||||
"reason": "already_unlocked",
|
||||
"creditsCharged": 0,
|
||||
"creditBalance": 180,
|
||||
"images": [{"url": "https://...", "locked": false, "index": 0}]
|
||||
}
|
||||
```
|
||||
|
||||
## 7. `reason` 处理
|
||||
|
||||
| `data.reason` | 含义 | 前端动作 |
|
||||
| --- | --- | --- |
|
||||
| `ok` | 已扣积分并解锁 | 显示真实图片并更新余额。 |
|
||||
| `already_unlocked` | 以前已解锁,本次不扣 | 直接显示返回图片。 |
|
||||
| `insufficient_credits` | 积分不足,未扣分 | 保持锁定,可引导购买积分。 |
|
||||
| `cost_changed` | 价格与 `expectedCost` 不一致 | 刷新列表并重新确认。 |
|
||||
| `unlock_in_progress` | 同一图片包正在并发解锁 | 禁用按钮并稍后刷新。 |
|
||||
| `deduct_failed` | 扣积分失败 | 保持锁定并允许重试。 |
|
||||
| `persist_failed_refunded` | 解锁记录失败,积分已尽力退回 | 保持锁定、刷新余额并告警。 |
|
||||
| `not_found` | 图片包不存在或已停用 | 移除卡片。 |
|
||||
|
||||
## 8. TypeScript 建议
|
||||
|
||||
```ts
|
||||
export interface PrivateAlbumImage {
|
||||
url: string;
|
||||
type: "image";
|
||||
locked: boolean;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface PrivateAlbum {
|
||||
albumId: string;
|
||||
momentId: string;
|
||||
characterId: string;
|
||||
collectionKey: string;
|
||||
title: string;
|
||||
content: string | null;
|
||||
previewText: string;
|
||||
imageCount: 8 | 15;
|
||||
mediaCount: 8 | 15;
|
||||
images: PrivateAlbumImage[];
|
||||
locked: boolean;
|
||||
unlocked: boolean;
|
||||
unlockCost: 320 | 600;
|
||||
requiredCredits: 320 | 600;
|
||||
creditCostPerImage: 40;
|
||||
currency: "credits";
|
||||
canUnlockWithCredits: boolean;
|
||||
publishedAt: string;
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 前端流程
|
||||
|
||||
1. 进入图片包页面,调用 `GET /api/private-room/albums`。
|
||||
2. `locked=true` 时可使用返回 URL 渲染封面/模糊图,但必须覆盖锁层并禁止打开原图。
|
||||
3. 用户确认后调用解锁接口并传当前 `expectedCost`。
|
||||
4. `reason=ok/already_unlocked` 时使用返回的真实图片。
|
||||
5. `reason=insufficient_credits` 时保持锁定并引导购买积分。
|
||||
6. 充值完成后重新调用解锁接口,不需要恢复现金订单。
|
||||
7. 解锁请求进行中禁用按钮,避免重复点击。
|
||||
|
||||
## 10. HTTP 错误
|
||||
|
||||
| HTTP 状态 | 原因 | 处理 |
|
||||
| ---: | --- | --- |
|
||||
| 401 | Token 无效或缺失 | 刷新 Token/重新登录。 |
|
||||
| 422 | `albumId`、query 或 body 格式错误 | 修正请求。 |
|
||||
| 500 | 数据库或服务异常 | 保持当前锁定状态并允许重试。 |
|
||||
|
||||
## 11. 测试方法
|
||||
|
||||
1. 先在 pro 执行 `database/private-albums-migration.sql`。
|
||||
2. 使用 FB 测试导入生成一个 8 张图片包。
|
||||
3. 用少于 320 积分的测试账号请求解锁,确认不扣积分、不出现现金订单。
|
||||
4. 给测试账号补足积分,再请求解锁,确认扣 320 且返回 8 个真实 URL。
|
||||
5. 重复解锁,确认 `creditsCharged=0`。
|
||||
6. 刷新列表,确认仍为 `unlocked=true`。
|
||||
7. 15 张图片包按同样方式验证扣 600。
|
||||
8. 测试结束后删除测试素材、图片包、图片和解锁记录。
|
||||
Reference in New Issue
Block a user