Files
cozsweet-frontend-nextjs/docs/backend/FRONTEND_PRIVATE_ALBUM_API.md
T

308 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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. 测试结束后删除测试素材、图片包、图片和解锁记录。