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

8.4 KiB
Raw Blame History

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

请求示例

curl 'https://api.banlv-ai.com/api/private-room/albums?character=elio&limit=20' \
  -H 'Authorization: Bearer <TOKEN>'

锁定图片包响应

{
  "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 是否为空判断解锁状态。

已解锁图片包

同一个用户解锁后,再次请求列表会返回:

{
  "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 原样传回:

GET /api/private-room/albums?cursor=<nextCursor>&limit=20

6. POST /api/private-room/albums/{albumId}/unlock

请求格式

Content-Type: application/json
Authorization: Bearer <TOKEN>

Body

{
  "expectedCost": 320
}
字段 类型 必填 示例 说明
expectedCost integer 否但建议 320 用户确认时看到的价格;价格变化则拒绝扣分。

请求示例

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}'

解锁成功

{
  "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

{
  "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}
    ]
  }
}

前端可以打开积分充值页,但后端不会创建图片包现金订单。用户充值积分后,再次点击同一个解锁接口。

重复解锁

{
  "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 建议

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. 测试结束后删除测试素材、图片包、图片和解锁记录。