Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+526
View File
@@ -0,0 +1,526 @@
# 91porn 付费引导 Ping 下发接口文档
> 对接对象:App/H5 前端
>
> 文档状态:已按测试确认的最终规则更新,后端接口与 Web 配置能力已同步实现
>
> 目标:普通付费引导弹窗随 Ping 一次性下发,弹窗触发时不再临时请求接口;会员内容上新仍使用独立接口实时判断。
## 1. 调整概要
付费引导共有 7 个场景编码:
| 场景 | 说明 | 新版前端取值方式 |
|---|---|---|
| `HOME_NEW_USER_FREE_TRIAL` | 首页新用户免费 X 次试看;X 取系统配置 `totalWatchCount` | Ping 下发 |
| `HOME_OLD_USER` | 首页老用户引导;与新用户免费试看场景共用首页开关 | Ping 下发 |
| `VIDEO_PREVIEW_END` | 视频试看 3 秒引导 | Ping 下发 |
| `DISCOUNT_COUNTDOWN` | 优惠倒计时弹窗 | Ping 下发 |
| `VIDEO_BACK` | 退出视频时引导 | Ping 下发 |
| `VIP_CENTER` | 会员中心引导 | Ping 下发 |
| `VIP_CONTENT_UPDATE` | 会员内容上新引导 | 独立接口实时查询 |
新版前端处理规则:
1. App/H5 启动时读取 Ping 中的 `paymentGuide` 并缓存。
2. 前 6 个普通场景在触发时直接读取 Ping 缓存,不再调用付费引导查询接口。
3. `VIP_CONTENT_UPDATE` 必须实时查询最新视频、`contentVersion` 及展示状态,继续调用独立接口。
4. `show` 字段保留。普通场景以 Ping 中的 `show` 为准,上新场景以独立接口的 `show` 为准。
5. 首页新用户免费试看、视频试看 3 秒、App 活跃 3 分钟等触发时机仍由前端控制。
6. `DISCOUNT_COUNTDOWN` 的倒计时由前端在每次 App 冷启动时重新创建,固定为 2 小时;后端不计算或下发倒计时起止时间,也不使用 `durationSeconds` 作为倒计时。
7.`DISCOUNT_COUNTDOWN` 外,普通弹窗实际展示后,前端调用展示回执接口,并在本地立即将该场景的 `show` 设为 `false`,防止同一次启动重复展示。
## 2. 枚举
### 2.1 场景 `scene`
```text
HOME_NEW_USER_FREE_TRIAL
HOME_OLD_USER
VIDEO_PREVIEW_END
DISCOUNT_COUNTDOWN
VIDEO_BACK
VIP_CENTER
VIP_CONTENT_UPDATE
```
### 2.2 用户分层 `segment`
```text
NEW_NEVER_PAID
OLD_NEVER_PAID
PAID_UPGRADE
MAX_VIP
NORMAL
UNREGISTERED
```
未付费用户的新老分层只按注册时间判断:注册不足 24 小时为 `NEW_NEVER_PAID`,达到 24 小时后为 `OLD_NEVER_PAID`。免费观看次数不会改变全局用户分层;次数用完时仅将 `HOME_NEW_USER_FREE_TRIAL.show` 置为 `false`
## 3. Ping 全量接口
### 3.1 App
```http
GET /api/app/ping/domain
```
### 3.2 H5
```http
GET /api/app/ping/domain/h5
```
两个接口的 `data` 均新增 `paymentGuide`,其他字段保持不变。
### 3.3 响应示例
```json
{
"code": 200,
"msg": "success",
"data": {
"paymentGuide": {
"enabled": true,
"segment": "OLD_NEVER_PAID",
"scenes": {
"HOME_NEW_USER_FREE_TRIAL": {
"enabled": false,
"show": false,
"totalWatchCount": 3
},
"HOME_OLD_USER": {
"enabled": true,
"show": true,
"configId": "6889f2014a4fcb5012340001",
"style": "BOTTOM_SHEET",
"title": "开通会员",
"description": "开通会员后可观看完整影片",
"cover": "20260804/payment-guide/home-old-user.png",
"productId": "6889f2014a4fcb5012349999",
"durationSeconds": 0,
"action": {
"type": "VIP_PRODUCT",
"value": "6889f2014a4fcb5012349999"
}
},
"VIDEO_PREVIEW_END": {
"enabled": true,
"show": true,
"configId": "6889f2014a4fcb5012340002",
"style": "CENTER_POPUP",
"title": "试看 3 秒",
"description": "开通会员观看完整内容",
"cover": "20260804/payment-guide/preview-end.png",
"productId": "6889f2014a4fcb5012349999",
"durationSeconds": 0,
"action": {
"type": "VIP_PRODUCT",
"value": "6889f2014a4fcb5012349999"
}
},
"DISCOUNT_COUNTDOWN": {
"enabled": true,
"show": true,
"configId": "6889f2014a4fcb5012340006",
"style": "CENTER_POPUP",
"title": "现在下单获得优惠福利",
"description": "距优惠结束",
"cover": "20260804/payment-guide/discount-countdown.png",
"productId": "6889f2014a4fcb5012349999",
"durationSeconds": 0,
"action": {
"type": "VIP_PRODUCT",
"value": "6889f2014a4fcb5012349999"
}
},
"VIDEO_BACK": {
"enabled": true,
"show": false,
"configId": "6889f2014a4fcb5012340004",
"style": "CENTER_POPUP",
"title": "退出视频引导",
"description": "开通会员观看更多内容",
"cover": "20260804/payment-guide/video-back.png",
"productId": "6889f2014a4fcb5012349999",
"durationSeconds": 0,
"action": {
"type": "VIP_PRODUCT",
"value": "6889f2014a4fcb5012349999"
}
},
"VIP_CENTER": {
"enabled": true,
"show": true,
"configId": "6889f2014a4fcb5012340003",
"style": "CENTER_POPUP",
"title": "限时会员福利",
"description": "立即开通",
"cover": "20260804/payment-guide/vip-center.png",
"productId": "6889f2014a4fcb5012349999",
"durationSeconds": 7200,
"action": {
"type": "VIP_PRODUCT",
"value": "6889f2014a4fcb5012349999"
}
},
"VIP_CONTENT_UPDATE": {
"enabled": true
}
}
}
}
}
```
### 3.4 `paymentGuide` 字段
| 字段 | 类型 | 说明 |
|---|---|---|
| `enabled` | Boolean | 付费引导全局开关;为 `false` 时所有场景均不展示 |
| `segment` | String | 当前用户的付费引导分层 |
| `scenes` | Object | 按场景编码索引的引导配置 |
### 3.5 普通场景字段
| 字段 | 类型 | 必定返回 | 说明 |
|---|---|---|---|
| `enabled` | Boolean | 是 | 当前场景是否有匹配用户分层、在有效期内且已启用的配置 |
| `show` | Boolean | 是 | 当前用户此次是否允许展示;已展示过对应配置时为 `false` |
| `configId` | String | `enabled=true` 时 | 配置 ID,展示回执时原样传回 |
| `style` | String | `enabled=true` 时 | 弹窗样式编码 |
| `title` | String | `enabled=true` 时 | 标题 |
| `description` | String | `enabled=true` 时 | 描述,可为空字符串 |
| `cover` | String | `enabled=true` 时 | 封面图,可为空字符串;相对路径按现有静态资源域名规则拼接 |
| `productId` | String | `enabled=true` 时 | 会员商品 ID,可为空字符串 |
| `durationSeconds` | Number | `enabled=true` 时 | 配置中的兼容时长字段,可为 `0`;不用于 `DISCOUNT_COUNTDOWN` 的前端 2 小时倒计时 |
| `action.type` | String | `enabled=true` 时 | 操作类型:`VIP_PRODUCT`/`INTERNAL`/`EXTERNAL`/`NONE` |
| `action.value` | String | `enabled=true` 时 | 会员商品 ID、内链地址、外链地址或空字符串 |
只要普通场景存在有效配置,以上卡片字段都会完整返回,与 `show``true` 还是 `false` 无关。无有效配置、全局开关关闭或首页共用开关关闭时,对应场景只返回 `enabled=false``show=false`
`enabled``show` 的区别:
| `enabled` | `show` | 含义 |
|---|---|---|
| `false` | `false` | 无有效配置,或全局开关已关闭 |
| `true` | `false` | 配置有效,但当前用户已展示过或不满足该场景的实时展示条件;仍返回完整卡片字段 |
| `true` | `true` | 配置有效且当前用户可展示 |
`DISCOUNT_COUNTDOWN` 不使用后端历史展示记录进行拦截:只要配置有效且相关开关开启,Ping 中始终返回 `enabled=true``show=true`。同一次启动只弹一次由前端本地控制;下次 App 冷启动重新开始本地 2 小时倒计时。
`HOME_NEW_USER_FREE_TRIAL``HOME_OLD_USER` 共用同一个首页引导开关。新用户免费试看场景额外返回 `totalWatchCount`;当新用户剩余免费观看次数为 0 时,该场景保持 `enabled=true` 并返回完整卡片配置,但 `show=false``HOME_NEW_USER` 仅保留后端兼容识别,不再通过 Ping 或 Web 配置列表返回。
`VIP_CONTENT_UPDATE` 在 Ping 中只返回 `enabled`;前端不得使用 Ping 推断该场景的 `show`,必须调用第 5 节的独立接口。
## 4. Ping 增量刷新
```http
GET /api/app/ping/domain/refresh?keys=paymentGuide
```
适用时机:
- 用户登录成功。
- 用户退出登录。
- 会员购买成功或会员状态变化。
- 前端需要主动更新 Ping 缓存时。
不应在准备弹出普通弹窗时调用此接口。
响应示例:
```json
{
"code": 200,
"msg": "success",
"data": {
"paymentGuide": {
"enabled": true,
"segment": "PAID_UPGRADE",
"scenes": {
"HOME_NEW_USER_FREE_TRIAL": {
"enabled": false,
"show": false,
"totalWatchCount": 3
},
"HOME_OLD_USER": {
"enabled": false,
"show": false
},
"VIDEO_PREVIEW_END": {
"enabled": false,
"show": false
},
"DISCOUNT_COUNTDOWN": {
"enabled": false,
"show": false
},
"VIDEO_BACK": {
"enabled": true,
"show": true,
"configId": "6889f2014a4fcb5012340004",
"style": "CENTER_POPUP",
"title": "升级会员",
"description": "升级后可观看更多内容",
"cover": "20260804/payment-guide/upgrade.png",
"productId": "6889f2014a4fcb5012349999",
"durationSeconds": 0,
"action": {
"type": "VIP_PRODUCT",
"value": "6889f2014a4fcb5012349999"
}
},
"VIP_CENTER": {
"enabled": false,
"show": false
},
"VIP_CONTENT_UPDATE": {
"enabled": true
}
}
}
}
}
```
## 5. 会员内容上新引导
### 5.1 获取引导
```http
GET /api/app/payment/guide?scene=VIP_CONTENT_UPDATE
Authorization: {Apptoken}
```
调用前建议先判断:
```text
paymentGuide.enabled == true
&& paymentGuide.scenes.VIP_CONTENT_UPDATE.enabled == true
```
满足后再调用独立接口。最终是否展示必须以该接口返回的 `show` 为准。
### 5.2 `show=true` 响应
```json
{
"code": 200,
"msg": "success",
"data": {
"show": true,
"configId": "6889f2014a4fcb5012340005",
"contentVersion": "4a7cbdb742c44a47",
"segment": "OLD_NEVER_PAID",
"style": "BOTTOM_SHEET",
"title": "查看完整影片",
"description": "开通会员后可观看",
"cover": "20260804/payment-guide/content-update.png",
"videos": [
{
"id": "videoId",
"title": "视频标题",
"cover": "20260804/video/cover.jpg",
"coverThumb": "20260804/video/cover-thumb.jpg",
"playTime": 1384,
"playCount": 12000
}
],
"productId": "6889f2014a4fcb5012349999",
"durationSeconds": 7200,
"action": {
"type": "VIP_PRODUCT",
"value": "6889f2014a4fcb5012349999"
}
}
}
```
### 5.3 `show=false` 响应
```json
{
"code": 200,
"msg": "success",
"data": {
"show": false,
"segment": "OLD_NEVER_PAID"
}
}
```
`show=false` 时不展示弹窗。常见原因:
- 付费引导全局开关已关闭。
- 无匹配当前场景、用户分层和有效期的配置。
- 当前 `contentVersion` 已展示过。
- 当前没有符合条件的最新会员视频。
视频数量由 Web 后台对应配置的 `videoLimit` 控制;未配置或为 `0` 时默认 4 条,最大 20 条。
## 6. 展示回执
```http
POST /api/app/payment/guide/impression
Authorization: {Apptoken}
Content-Type: application/json
```
### 6.1 普通场景(不含优惠倒计时)
```json
{
"configId": "6889f2014a4fcb5012340002",
"scene": "VIDEO_PREVIEW_END",
"contentVersion": "",
"videoId": "videoId",
"requestId": "6f2e26f7-6e86-40eb-9276-6d45f79eae9f"
}
```
### 6.2 会员内容上新场景
```json
{
"configId": "6889f2014a4fcb5012340005",
"scene": "VIP_CONTENT_UPDATE",
"contentVersion": "4a7cbdb742c44a47",
"videoId": "videoId",
"requestId": "e14ae540-a5d2-4a6a-8740-c3e9877545f1"
}
```
### 6.3 请求字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `configId` | String | 是 | Ping 或上新引导接口返回的配置 ID |
| `scene` | String | 是 | 实际展示的场景 |
| `contentVersion` | String | 上新场景必填 | 普通场景传空字符串;`VIP_CONTENT_UPDATE` 必须原样传回 |
| `videoId` | String | 否 | 相关视频 ID,无视频上下文时传空字符串或省略 |
| `requestId` | String | 是 | 本次回执的 UUID;重试必须复用同一个 UUID |
成功响应:
```json
{
"code": 200,
"msg": "success",
"data": ""
}
```
前端注意:
- 只有弹窗真正展示成功后才上报,接口返回 `show=true` 但未展示时不上报。
- 上报发起后立即在本地将对应场景的 `show` 设为 `false`,避免同一次启动重复展示。
- 网络失败可使用相同 `requestId` 有界重试,不要为同一次展示生成多个 `requestId`
- `DISCOUNT_COUNTDOWN` 无需调用展示回执;其同一次启动去重和每次冷启动重置均由前端本地处理。兼容版本即使上报该场景,也不会影响后续 Ping 的 `show`
## 7. Web 后台配置接口
现有付费引导配置接口结构保持不变:
```http
GET /api/web/admin/payment-guide/list?scene={scene}&pageNumber=1&pageSize=20
POST /api/web/admin/payment-guide/add
POST /api/web/admin/payment-guide/edit
DELETE /api/web/admin/payment-guide/delete?id={id}
```
列表响应新增 `sceneOptions`,后台下拉应直接使用该字段,不再硬编码场景名称。示例:
```json
{
"sceneOptions": [
{
"label": "首页新用户免费3次试看",
"value": "HOME_NEW_USER_FREE_TRIAL",
"totalWatchCount": 3
}
]
}
```
`HOME_NEW_USER` 不会出现在 `sceneOptions` 或默认配置列表中,也不能再新增、编辑为该旧场景。
全局开关新增系统配置:
```text
vCode: paymentGuideEnabled
type: bool
```
首页新用户免费试看、老用户共用开关新增系统配置:
```text
vCode: paymentGuideHomeEnabled
type: bool
```
全局开关继续复用现有系统配置查询和更新接口,不新增 Web API。
首页引导只提供一个开关,同时控制 `HOME_NEW_USER_FREE_TRIAL``HOME_OLD_USER`;两个场景的用户分层、素材和弹窗内容仍可分别配置。后台不得再展示旧 `HOME_NEW_USER` 或两个独立的首页开关。
`DISCOUNT_COUNTDOWN` 按普通付费引导场景配置卡片内容、用户分层、启用状态和有效期。倒计时固定 2 小时并由前端每次 App 冷启动重新开始,后端配置中的 `durationSeconds` 不参与该倒计时。
## 8. 兼容性
- `GET /api/app/payment/guide?scene={scene}` 支持当前 7 个可配置场景;旧 `HOME_NEW_USER` 仍可被后端识别但固定不展示。
- 新版前端只在 `VIP_CONTENT_UPDATE` 场景调用该接口。
- 原接口的 `show` 字段保留,不做破坏性删除。
- Ping 新增字段对旧版本属于向后兼容的增量字段。
- 旧的 `paymentStatusPopupConfig` 在旧客户端完成淘汰前继续保留,新客户端不得同时展示旧弹窗和新付费引导弹窗。
## 9. 前端接入流程
### 9.1 启动与缓存
1. 调用全量 Ping。
2. 使用成功响应的 `paymentGuide` 覆盖本地缓存。
3. Ping 网络失败时可使用最近一次成功缓存;首次安装且无缓存时默认不展示,不得自行假定 `show=true`
### 9.2 普通场景(不含优惠倒计时)
1. 前端达到场景触发条件。
2. 检查 `paymentGuide.enabled`
3. 检查 `paymentGuide.scenes[scene].enabled``show`
4. 两者均为 `true` 时展示 Ping 中的弹窗配置。
5. 展示成功后上报回执,并在本地将该场景的 `show` 设为 `false`
### 9.3 会员内容上新
1. 前端达到上新弹窗检查时机。
2. 检查 Ping 的全局开关及 `VIP_CONTENT_UPDATE.enabled`
3. 启用时调用上新引导接口。
4. 只有接口返回 `show=true` 时才展示。
5. 展示成功后携带原始 `contentVersion` 上报回执。
### 9.4 优惠倒计时
1. 读取 `paymentGuide.scenes.DISCOUNT_COUNTDOWN`
2. `enabled=true``show=true` 时展示倒计时弹窗。
3. 每次 App 冷启动在前端本地重新创建固定 2 小时倒计时;不读取 `durationSeconds`、绝对结束时间或旧配置的 `lastDiscountTime`
4. 同一次启动只展示一次由前端本地状态控制;无需调用展示回执,下次冷启动可再次展示。
### 9.5 用户状态变化
登录、退出或会员状态变化后,调用 Ping 增量刷新并替换本地 `paymentGuide` 缓存。
## 10. 联调检查项
- 全局开关关闭时,所有普通场景不展示,上新场景不调用独立接口。
- 场景无有效配置时,返回 `enabled=false``show=false`
- 配置有效但已展示时,返回 `enabled=true``show=false` 和完整卡片字段。
- 注册不足 24 小时且剩余免费观看次数为 0 时,仍返回 `segment=NEW_NEVER_PAID`;仅 `HOME_NEW_USER_FREE_TRIAL.show=false`,不得匹配仅配置给 `OLD_NEVER_PAID` 的其他场景。
- 用户分层改变后,增量刷新返回新分层的配置。
- `HOME_NEW_USER_FREE_TRIAL``HOME_OLD_USER` 由同一个首页开关控制;旧 `HOME_NEW_USER` 不再返回。
- `VIDEO_PREVIEW_END` 的业务含义为“视频试看 3 秒”,不再使用“视频试看结束”文案。
- `DISCOUNT_COUNTDOWN` 无有效配置时不展示;有有效配置且开关开启时 Ping 始终返回完整卡片及 `show=true`
- `DISCOUNT_COUNTDOWN` 每次 App 冷启动由前端重新创建固定 2 小时倒计时,后端不计算倒计时,也不依据展示回执抑制下一次启动。
- 普通场景触发时不发起付费引导查询请求。
- 上新场景仅在 Ping 对应开关开启时调用独立接口。
- 上新场景的 `show=false` 、视频空列表和无新 `contentVersion` 均不展示。
- 除优惠倒计时外,展示回执成功后后续 Ping 返回 `show=false`;优惠倒计时的同一次启动去重由前端本地控制。
- 新客户端不同时使用旧 `paymentStatusPopupConfig` 和新 `paymentGuide`