feat(api-docs): add VIP and credits frontend API documentation
This commit is contained in:
@@ -0,0 +1,309 @@
|
|||||||
|
# VIP / Credits Frontend API
|
||||||
|
|
||||||
|
本文档是前端接入会员、积分、权益和历史解锁的推荐接口说明。新版接入原则:
|
||||||
|
|
||||||
|
- 前端权益状态只认 `GET /api/user/entitlements`。
|
||||||
|
- 游客和登录非会员权益一致,只按 `isVip` 区分会员/非会员。
|
||||||
|
- 支付完成后只刷新一次 `GET /api/user/entitlements`。
|
||||||
|
- `dolBalance` 是旧字段名,等价于 `creditBalance`,新代码优先使用 `creditBalance`。
|
||||||
|
|
||||||
|
## 推荐调用链
|
||||||
|
|
||||||
|
```text
|
||||||
|
打开充值/VIP 弹窗
|
||||||
|
-> GET /api/payment/plans
|
||||||
|
-> POST /api/payment/create-order
|
||||||
|
-> 打开 data.payParams.url / checkout_url / payment_url
|
||||||
|
-> GET /api/payment/order-status 轮询,或等待 WebSocket payment_success
|
||||||
|
-> GET /api/user/entitlements 刷新权益
|
||||||
|
```
|
||||||
|
|
||||||
|
不要再按订单类型分别调用 `/api/payment/vip-status` 和 `/api/user/credits` 刷新状态。
|
||||||
|
|
||||||
|
## GET /api/user/entitlements
|
||||||
|
|
||||||
|
用途:当前用户 VIP、积分和权益快照。支持游客 token 和登录 token。
|
||||||
|
|
||||||
|
认证:需要 `Authorization: Bearer <token>`,游客 token 也可以。
|
||||||
|
|
||||||
|
响应示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"userId": "user-1",
|
||||||
|
"isGuest": false,
|
||||||
|
"isVip": false,
|
||||||
|
"vipExpiresAt": null,
|
||||||
|
"creditBalance": 120,
|
||||||
|
"dolBalance": 120,
|
||||||
|
"policy": {
|
||||||
|
"membershipState": "non_vip",
|
||||||
|
"nonVipEntitlementsShared": true,
|
||||||
|
"guestSameAsLoggedInNonVip": true,
|
||||||
|
"refreshAfterPayment": "GET /api/user/entitlements"
|
||||||
|
},
|
||||||
|
"costs": {
|
||||||
|
"normal_message": 2,
|
||||||
|
"private_message": 10,
|
||||||
|
"voice_message": 20,
|
||||||
|
"photo": 40,
|
||||||
|
"voice_call_minute": 50,
|
||||||
|
"private_album_10": 300,
|
||||||
|
"private_album_20": 600
|
||||||
|
},
|
||||||
|
"quotas": {
|
||||||
|
"normalChatFreeDaily": 30,
|
||||||
|
"privateUnlockFreeDaily": 1,
|
||||||
|
"voiceMessageFreeDaily": 0,
|
||||||
|
"photoFreeDaily": 0
|
||||||
|
},
|
||||||
|
"historyUnlock": {
|
||||||
|
"enabled": true,
|
||||||
|
"order": "oldest_first",
|
||||||
|
"chargeMode": "highest_cost_per_locked_message",
|
||||||
|
"insufficientBalanceBehavior": "no_deduction",
|
||||||
|
"costs": {
|
||||||
|
"private_message": 10,
|
||||||
|
"voice_message": 20,
|
||||||
|
"photo": 40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
前端使用:
|
||||||
|
|
||||||
|
- App 启动、登录成功、游客 token 创建成功后调用。
|
||||||
|
- 支付成功、充值成功、历史解锁成功后调用。
|
||||||
|
- `isGuest` 只用于 UI 身份展示,不用于权益判断。
|
||||||
|
|
||||||
|
## GET /api/payment/plans
|
||||||
|
|
||||||
|
用途:获取当前国家对应的会员/积分套餐。
|
||||||
|
|
||||||
|
认证:不需要登录。后端通过 `CF-IPCountry` 判断国家;登录后创建订单会再次按用户国家校验价格。
|
||||||
|
|
||||||
|
响应重点字段:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plan_id": "vip_monthly",
|
||||||
|
"plan_name": "月度会员",
|
||||||
|
"order_type": "vip_monthly",
|
||||||
|
"amount_cents": 1999,
|
||||||
|
"original_amount_cents": 1999,
|
||||||
|
"daily_price_cents": 66,
|
||||||
|
"currency": "USD",
|
||||||
|
"pricing_tier": "T1",
|
||||||
|
"vip_days": 30,
|
||||||
|
"dol_amount": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
|
||||||
|
- `amount_cents`: 实付金额,传给支付服务。
|
||||||
|
- `original_amount_cents`: 划线价;无划线价时为 `null`,字段一定存在。
|
||||||
|
- `currency`: 支付币种。
|
||||||
|
- `plan_id`: 创建订单时传回后端。
|
||||||
|
|
||||||
|
## POST /api/payment/create-order
|
||||||
|
|
||||||
|
用途:创建支付订单。
|
||||||
|
|
||||||
|
认证:需要登录 token。
|
||||||
|
|
||||||
|
请求:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"planId": "vip_monthly",
|
||||||
|
"payChannel": "stripe",
|
||||||
|
"autoRenew": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"orderId": "ord-1",
|
||||||
|
"payParams": {
|
||||||
|
"url": "https://pay.example/ord-1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
前端使用:
|
||||||
|
|
||||||
|
- `planId` 必须来自 `/api/payment/plans`。
|
||||||
|
- `payParams` 原样交给支付 SDK 或打开其中的支付 URL。
|
||||||
|
- 后端创建订单时使用 plans 里的 `amount_cents/currency`,不会再固定覆盖成 CNY。
|
||||||
|
|
||||||
|
## GET /api/payment/order-status
|
||||||
|
|
||||||
|
用途:轮询支付订单状态。
|
||||||
|
|
||||||
|
认证:需要登录 token,只能查自己的订单。
|
||||||
|
|
||||||
|
请求:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /api/payment/order-status?order_id=ord-1
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"orderId": "ord-1",
|
||||||
|
"status": "paid",
|
||||||
|
"orderType": "vip",
|
||||||
|
"planId": "vip_monthly"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
状态:
|
||||||
|
|
||||||
|
- `pending`: 支付处理中,继续轮询。
|
||||||
|
- `paid`: 支付成功,停止轮询并调用 `/api/user/entitlements`。
|
||||||
|
- `failed`: 支付失败,停止轮询并提示用户。
|
||||||
|
|
||||||
|
## WebSocket payment_success / payment_failed
|
||||||
|
|
||||||
|
用途:支付服务回调后,后端主动推送支付结果。
|
||||||
|
|
||||||
|
成功事件示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "payment_success",
|
||||||
|
"orderId": "ord-1",
|
||||||
|
"payType": "vip",
|
||||||
|
"planName": "月度会员",
|
||||||
|
"vipExpiresAt": "2026-07-26T00:00:00+00:00"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
积分充值成功示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "payment_success",
|
||||||
|
"orderId": "ord-2",
|
||||||
|
"payType": "dol",
|
||||||
|
"planName": "1000 Credits",
|
||||||
|
"dolAmount": 1000,
|
||||||
|
"dolBalance": 1120
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
前端收到 `payment_success` 后:
|
||||||
|
|
||||||
|
```text
|
||||||
|
停止 order-status 轮询
|
||||||
|
-> GET /api/user/entitlements
|
||||||
|
-> 用 creditBalance/isVip 更新页面
|
||||||
|
```
|
||||||
|
|
||||||
|
## POST /api/chat/unlock-history
|
||||||
|
|
||||||
|
用途:一键解锁历史锁定消息,按创建时间从旧到新处理。
|
||||||
|
|
||||||
|
认证:需要登录 token。
|
||||||
|
|
||||||
|
请求:无 body。
|
||||||
|
|
||||||
|
余额不足响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"unlocked": false,
|
||||||
|
"reason": "insufficient_balance",
|
||||||
|
"totalLocked": 2,
|
||||||
|
"unlockedCount": 0,
|
||||||
|
"privateCount": 1,
|
||||||
|
"imageCount": 1,
|
||||||
|
"voiceCount": 0,
|
||||||
|
"requiredCredits": 50,
|
||||||
|
"currentCredits": 20,
|
||||||
|
"remainingCredits": 20,
|
||||||
|
"shortfallCredits": 30,
|
||||||
|
"costsByMessage": {
|
||||||
|
"private-1": 10,
|
||||||
|
"photo-1": 40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
余额充足响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"unlocked": true,
|
||||||
|
"reason": "ok",
|
||||||
|
"requiredCredits": 60,
|
||||||
|
"currentCredits": 100,
|
||||||
|
"remainingCredits": 40,
|
||||||
|
"shortfallCredits": 0,
|
||||||
|
"messageIds": ["voice-1", "photo-1"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
计费规则:
|
||||||
|
|
||||||
|
- 私密消息:10 credits。
|
||||||
|
- 语音消息:20 credits。
|
||||||
|
- 照片:40 credits。
|
||||||
|
- 同一条消息命中多个类型时,按最高成本计一次。
|
||||||
|
- 余额不足时不扣费、不解锁,只返回差额。
|
||||||
|
- 已解锁过的消息会跳过。
|
||||||
|
|
||||||
|
## 兼容接口
|
||||||
|
|
||||||
|
以下接口保留给旧前端,但新前端不推荐作为权益状态来源:
|
||||||
|
|
||||||
|
- `GET /api/payment/vip-status`: 只返回 `isVip/vipExpiresAt`。
|
||||||
|
- `GET /api/user/credits`: 只返回 `dolBalance`。
|
||||||
|
- `GET /api/user/stats`: 用户统计、亲密度、记忆等,不再用于支付后刷新权益。
|
||||||
|
|
||||||
|
## 测试证明
|
||||||
|
|
||||||
|
本地测试命令:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe -m pytest -q tests/test_vip_credit_entitlements.py
|
||||||
|
.\.venv\Scripts\python.exe -m pytest -q tests/test_private_unlock.py tests/test_elio_image_paywall.py
|
||||||
|
```
|
||||||
|
|
||||||
|
已通过结果:
|
||||||
|
|
||||||
|
```text
|
||||||
|
tests/test_vip_credit_entitlements.py: 13 passed
|
||||||
|
tests/test_private_unlock.py tests/test_elio_image_paywall.py: 11 passed
|
||||||
|
```
|
||||||
|
|
||||||
|
覆盖证明:
|
||||||
|
|
||||||
|
- `/api/user/entitlements`: 已测游客、登录非会员、VIP;游客和登录非会员的 `policy/costs/quotas` 一致。
|
||||||
|
- `/api/payment/plans`: 已测 T1/T2 国家;所有计划都有 `original_amount_cents`。
|
||||||
|
- `/api/payment/create-order`: 已测国家价格计划会传入创建订单;支付服务收到的金额和币种来自 plan。
|
||||||
|
- `/api/payment/order-status`: 已测 `pending/paid/failed` 和禁止查询他人订单。
|
||||||
|
- WebSocket `payment_success`: 已测 VIP 和积分发放事件 payload。
|
||||||
|
- `/api/chat/unlock-history`: 已测无锁定消息、余额不足不扣费、余额充足扣费、已解锁跳过。
|
||||||
|
- 兼容接口:已测 `/api/payment/vip-status` 和 `/api/user/credits` 仍可用。
|
||||||
-123
@@ -1,123 +0,0 @@
|
|||||||
图片 URL 长相
|
|
||||||
真实图片 URL 大概是这种公开 Supabase Storage 地址:
|
|
||||||
https://lehwkihwnlqkavhcspel.supabase.co/storage/v1/object/public/elio-schedules/schedules/42/a1b2c3d4.jpg
|
|
||||||
前端不用拼 URL,也不用鉴权,后端会直接返回完整 URL。前端只读:
|
|
||||||
data.image?.url
|
|
||||||
消息接口
|
|
||||||
POST https://proapi.banlv-ai.com/api/chat/send
|
|
||||||
响应 data 固定字段:
|
|
||||||
{
|
|
||||||
"reply": "这是我昨天傍晚的照片,那会儿在外面走了走。",
|
|
||||||
"audioUrl": "",
|
|
||||||
"messageId": "msg_123",
|
|
||||||
"timestamp": 1782271808947,
|
|
||||||
"isGuest": false,
|
|
||||||
"image": {
|
|
||||||
"type": "elio_schedule",
|
|
||||||
"url": "https://lehwkihwnlqkavhcspel.supabase.co/storage/v1/object/public/elio-schedules/schedules/42/a1b2c3d4.jpg"
|
|
||||||
},
|
|
||||||
"lockDetail": {
|
|
||||||
"locked": false,
|
|
||||||
"showContent": true,
|
|
||||||
"showUpgrade": false,
|
|
||||||
"reason": null,
|
|
||||||
"hint": null,
|
|
||||||
"detail": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
前端用法:
|
|
||||||
const d = res.data
|
|
||||||
|
|
||||||
if (d.lockDetail?.showContent !== false && d.reply) {
|
|
||||||
renderText(d.reply)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (d.image?.url) {
|
|
||||||
renderImage(d.image.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (d.audioUrl) {
|
|
||||||
renderAudio(d.audioUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (d.lockDetail?.showUpgrade) {
|
|
||||||
showVipPaywall(d.lockDetail.hint)
|
|
||||||
}
|
|
||||||
image.type 目前主要是 elio_schedule,无图时:
|
|
||||||
"image": { "type": null, "url": null }
|
|
||||||
被限额/需要充值时:
|
|
||||||
{
|
|
||||||
"reply": "",
|
|
||||||
"image": { "type": null, "url": null },
|
|
||||||
"lockDetail": {
|
|
||||||
"locked": true,
|
|
||||||
"showContent": false,
|
|
||||||
"showUpgrade": true,
|
|
||||||
"reason": "daily_limit",
|
|
||||||
"hint": "免费消息额度已用完,开通会员后可以继续聊天。",
|
|
||||||
"detail": {
|
|
||||||
"type": "daily_msg_limit",
|
|
||||||
"usedToday": 3,
|
|
||||||
"limit": 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
历史接口
|
|
||||||
GET https://proapi.banlv-ai.com/api/chat/history?limit=50&offset=0
|
|
||||||
响应结构:
|
|
||||||
{
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"type": "text",
|
|
||||||
"content": "你最近在干嘛",
|
|
||||||
"id": "msg_1",
|
|
||||||
"created_at": "2026-06-24T10:00:00Z",
|
|
||||||
"audioUrl": null,
|
|
||||||
"image": { "type": null, "url": null },
|
|
||||||
"lockDetail": {
|
|
||||||
"locked": false,
|
|
||||||
"showContent": true,
|
|
||||||
"showUpgrade": false,
|
|
||||||
"reason": null,
|
|
||||||
"hint": null,
|
|
||||||
"detail": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "assistant",
|
|
||||||
"type": "text",
|
|
||||||
"content": "",
|
|
||||||
"id": "msg_2",
|
|
||||||
"created_at": "2026-06-24T10:00:03Z",
|
|
||||||
"audioUrl": null,
|
|
||||||
"image": { "type": null, "url": null },
|
|
||||||
"lockDetail": {
|
|
||||||
"locked": true,
|
|
||||||
"showContent": false,
|
|
||||||
"showUpgrade": true,
|
|
||||||
"reason": "private_message",
|
|
||||||
"hint": "他好像想说什么悄悄话,解锁会员才能看到…",
|
|
||||||
"detail": { "messageId": "msg_2" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"total": 200,
|
|
||||||
"limit": 50,
|
|
||||||
"offset": 0,
|
|
||||||
"isVip": false
|
|
||||||
}
|
|
||||||
历史前端用法:
|
|
||||||
for (const m of data.messages) {
|
|
||||||
if (m.lockDetail?.showContent === false) {
|
|
||||||
renderLockedCard(m.lockDetail.hint)
|
|
||||||
if (m.lockDetail?.showUpgrade) showVipEntry()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
renderMessage(m.role, m.content)
|
|
||||||
|
|
||||||
if (m.image?.url) renderImage(m.image.url)
|
|
||||||
if (m.audioUrl) renderAudio(m.audioUrl)
|
|
||||||
}
|
|
||||||
前端不要再读这些旧字段:mode、relationshipStage、currentMood、intimacyChange、newIntimacy、voiceUrl、imageUrl、imageType、privateLocked、privateHint。统一是 audioUrl、image.url、lockDetail。
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
发消息
|
|
||||||
前端调:
|
|
||||||
POST /api/chat/send
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
Content-Type: application/json
|
|
||||||
请求体最常用就是:
|
|
||||||
{
|
|
||||||
"message": "给我发图片",
|
|
||||||
"image": null,
|
|
||||||
"useWebSocket": false
|
|
||||||
}
|
|
||||||
如果是“用户发自己的图片给 AI 看”,先上传图片,再把这些字段带进来:
|
|
||||||
{
|
|
||||||
"message": "你看看这张图",
|
|
||||||
"imageId": "...",
|
|
||||||
"imageThumbUrl": "...",
|
|
||||||
"imageMediumUrl": "...",
|
|
||||||
"imageOriginalUrl": "...",
|
|
||||||
"imageWidth": 1080,
|
|
||||||
"imageHeight": 720,
|
|
||||||
"useWebSocket": false
|
|
||||||
}
|
|
||||||
返回给前端,重点只看这几个字段:
|
|
||||||
{
|
|
||||||
"reply": "回复文本",
|
|
||||||
"audioUrl": "",
|
|
||||||
"messageId": "uuid",
|
|
||||||
"isGuest": true,
|
|
||||||
"timestamp": 1782281463683,
|
|
||||||
"image": {
|
|
||||||
"type": "elio_schedule",
|
|
||||||
"url": "https://..."
|
|
||||||
},
|
|
||||||
"lockDetail": {
|
|
||||||
"locked": true,
|
|
||||||
"showContent": false,
|
|
||||||
"showUpgrade": true,
|
|
||||||
"reason": "image_paywall",
|
|
||||||
"hint": "嗯,你想看我……那就给你看一张。这是我06月15日的照片,你喜欢吗?",
|
|
||||||
"detail": {
|
|
||||||
"image": {
|
|
||||||
"type": "elio_schedule",
|
|
||||||
"url": "https://..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
前端规则很简单:
|
|
||||||
lockDetail.showContent=true:正常展示 reply、image.url、audioUrl
|
|
||||||
lockDetail.showContent=false:不要展示真实内容,改成会员引导卡
|
|
||||||
lockDetail.showUpgrade=true:显示开会员入口
|
|
||||||
也就是说,后端会正常生成内容,前端只靠 lockDetail 决定“显示还是锁住”。
|
|
||||||
历史消息
|
|
||||||
前端调:
|
|
||||||
GET /api/chat/history?limit=50&offset=0
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
返回结构:
|
|
||||||
{
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"type": "text",
|
|
||||||
"content": "给我发图片",
|
|
||||||
"id": "uuid",
|
|
||||||
"created_at": "2026-06-24T06:13:27.000Z",
|
|
||||||
"audioUrl": null,
|
|
||||||
"image": { "type": null, "url": null },
|
|
||||||
"lockDetail": {
|
|
||||||
"locked": false,
|
|
||||||
"showContent": true,
|
|
||||||
"showUpgrade": false,
|
|
||||||
"reason": null,
|
|
||||||
"hint": null,
|
|
||||||
"detail": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "assistant",
|
|
||||||
"type": "text",
|
|
||||||
"content": "嗯,你想看我……那就给你看一张。这是我06月15日的照片,你喜欢吗?",
|
|
||||||
"id": "uuid",
|
|
||||||
"created_at": "2026-06-24T06:13:27.000Z",
|
|
||||||
"audioUrl": null,
|
|
||||||
"image": {
|
|
||||||
"type": "elio_schedule",
|
|
||||||
"url": "https://..."
|
|
||||||
},
|
|
||||||
"lockDetail": {
|
|
||||||
"locked": true,
|
|
||||||
"showContent": false,
|
|
||||||
"showUpgrade": true,
|
|
||||||
"reason": "image_paywall",
|
|
||||||
"hint": "嗯,你想看我……那就给你看一张。这是我06月15日的照片,你喜欢吗?",
|
|
||||||
"detail": {
|
|
||||||
"image": {
|
|
||||||
"type": "elio_schedule",
|
|
||||||
"url": "https://..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"total": 2,
|
|
||||||
"limit": 50,
|
|
||||||
"offset": 0,
|
|
||||||
"isVip": false
|
|
||||||
}
|
|
||||||
历史列表也完全按同一套 lockDetail 渲染,不要自己猜 VIP 逻辑。
|
|
||||||
看图片
|
|
||||||
图片消息统一看这两个位置:
|
|
||||||
message.image.url
|
|
||||||
如果被锁,message.lockDetail.detail.image.url
|
|
||||||
前端建议处理:
|
|
||||||
先看 lockDetail.showContent
|
|
||||||
如果 true,直接展示 image.url
|
|
||||||
如果 false,不要展示真实图,只显示“图片已锁定”的会员引导卡
|
|
||||||
也就是:
|
|
||||||
后端会返回真实图片
|
|
||||||
非 VIP 前端不展示
|
|
||||||
VIP 或解锁后前端正常展示
|
|
||||||
|
|
||||||
私密图片 / 私密消息
|
|
||||||
现在前端不用区分很多旧字段,只看 lockDetail.reason:
|
|
||||||
image_paywall:图片锁
|
|
||||||
private_message:私密消息锁
|
|
||||||
voice_message:语音锁
|
|
||||||
daily_limit:免费消息额度用完
|
|
||||||
其中“私密图片”本质上也是同一套路子:
|
|
||||||
后端正常产出内容
|
|
||||||
前端收到 lockDetail.showContent=false
|
|
||||||
前端不展示真实内容
|
|
||||||
前端展示会员引导
|
|
||||||
如果是 private_message,通常读:
|
|
||||||
content / reply:后端可能仍会返回
|
|
||||||
但前端必须以 lockDetail.showContent 为准
|
|
||||||
不要因为拿到了文本就直接渲染
|
|
||||||
|
|
||||||
前端最简对接结论
|
|
||||||
你们只需要统一渲染这几个字段:
|
|
||||||
文本:content 或 reply
|
|
||||||
图片:image.url
|
|
||||||
语音:audioUrl
|
|
||||||
是否展示:lockDetail.showContent
|
|
||||||
是否引导会员:lockDetail.showUpgrade
|
|
||||||
锁类型:lockDetail.reason
|
|
||||||
引导文案:lockDetail.hint
|
|
||||||
Reference in New Issue
Block a user