Files
as-daycard-openapi/第三方合作伙伴接口流程.md
zhaogd 221decc070 更新第三方合作伙伴接口流程文档
- 阶段二补充 /list 多栏目查询、/list-all 全量聚合、/episodes 分集查询接口说明
- 阶段七补充电视剧分集 contentcode 与 programCode 播放地址的衔接说明
2026-07-29 12:17:27 +08:00

203 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.

# 第三方合作伙伴接口完整流程
## 阶段一:管理后台准备(平台方操作)
```
1. 创建合作伙伴
POST /manage/partner/save (JWT鉴权)
-> 自动生成 partnerId / appKey / appSecret,交给第三方
2. 采购天卡到合作伙伴池
POST /manage/partner-card/purchase?partnerId=xxx&quantity=N (JWT鉴权)
-> 平台天卡池 UNUSED -> RESERVED (FOR UPDATE SKIP LOCKED)
-> 写入 partner 天卡池 status=AVAILABLE
3. 配置内容包授权范围(可选)
POST /manage/content-package/... (JWT鉴权)
-> 三级授权:CONTENT(单部) > CATALOG(栏目) > FULL(全量)
```
## 阶段二:第三方EPG界面展示与支付(第三方完成)
```
第三方自行开发EPG界面和支付流程
可选:第三方调媒资接口展示内容
GET /openapi/partner/media/catalog-tree (appKey签名鉴权)
GET /openapi/partner/media/list?catalogid=xxx # 单栏目查询
GET /openapi/partner/media/list?catalogid=472,492,494 # 多栏目聚合查询(逗号分隔)
GET /openapi/partner/media/list-all?parentCatalogid=471 # 全量聚合(按一级栏目,不分页,适合按天同步)
GET /openapi/partner/media/episodes?contentcode=xxx # 电视剧分集列表(分集contentcode即programCode)
GET /openapi/partner/media/search?keyword=xxx
-> 按内容包授权范围过滤返回
支付完成后进入阶段三
```
## 阶段三:第三方调消耗接口发卡(核心)
```
POST /openapi/partner/card/consume (appKey签名鉴权)
Headers: appKey / timestamp / nonce / sign
Body: { mac, wifiMac, deviceUid, deviceSn, channelOrgId }
(mac/wifiMac/deviceUid/deviceSn 至少传一个)
处理流程:
① PartnerSignFilter 校验签名: MD5(appKey+timestamp+nonce+appSecret).toUpperCase()
- timestamp 5分钟有效期 + nonce 5分钟防重放(Redis)
② Redis 防重锁: daycard:partner:consume:{设备标识}, TTL 30s
③ 查找/创建设备 (优先级: mac > wifiMac > deviceUid > deviceSn)
④ 幂等检查: 设备已有有效天卡(status=USED且未过期) -> 直接返回, 不重复消耗
⑤ FOR UPDATE SKIP LOCKED 取partner池AVAILABLE天卡 (防并发超卖)
⑥ 绑定天卡到设备:
- day_card_pool: RESERVED -> USED, device_id, activate_status=PENDING, expire_at=24h兜底
- partner_card_pool: AVAILABLE -> CONSUMED
⑦ @Transactional 事务保护 ②~⑥ 全部数据库写操作
⑧ 异步激活天卡 (线程池 thirdPartyActivateExecutor)
返回: { deviceId, expireAt, status:"ACTIVATING" }
thirdCardNo 不暴露给第三方, App 通过 checkBenefits 自行获取
```
## 阶段四:异步激活天卡(调橘视厅,自动完成)
```
activateCardAsync(poolId, deviceId):
① 激活锁: api:thirdparty:activate:lock:{poolId}, TTL 3600s
防止同一张卡重复激活
② 获取Token (优先缓存):
- 缓存key: api:thirdparty:activate:{thirdCardNo}, TTL 5分钟
- 命中 -> 直接用 token + memberCode
- 未命中 -> 调橘视厅免密登录:
POST {baseUrl}/ums_api/api/v1/third/login (RSA加密签名)
返回 token + memberCode -> 写入缓存
③ 卡密激活:
POST {baseUrl}/ccms_api/hswj/api/card/activate?sign={MD5签名}
- E0000003 + "已激活,重复使用激活码" 视为幂等成功
④ 确定过期时间:
优先 activateCard返回的expireTime -> 其次queryVipExpireTime -> 兜底24h
⑤ 更新 day_card_pool: activate_status=SUCCESS, expire_at=真实过期时间
⑥ finally 释放激活锁
异常: activate_status=FAILED + 错误信息
```
## 阶段五:EPG传参启动App
```
EPG通过app传参启动App, App使用同一设备标识初始化:
POST /api/jst/init (无需sign)
Body: { mac, wifiMac, deviceSn, deviceUid, ...(设备信息) }
不传 activationCode -> 代销模式 doInitWithoutActivationCode
① 按优先级查找已注册设备: mac > wifiMac > deviceSn > deviceUid
(设备由阶段三 consume 接口创建)
② 找不到 -> "设备未注册,请先通过合作伙伴天卡消耗接口激活设备"
③ 找到 -> 更新心跳, 生成 sign
sign = Base64URL(deviceId:timestamp:orgCode:HMAC-SHA256), TTL 24h
返回: { device: {deviceId, roomNo}, sign }
```
## 阶段六:App查询权益拿播放凭证
```
POST /api/jst/checkBenefits (需sign)
① ApiDeviceSignFilter 校验sign, 解析deviceId
② 查询 day_card_pool: device_id + status=USED + expire_at > NOW()
激活完成返回:
{ status:"ACTIVE", thirdCardNo:"xxx", cardNo:"xxx", expireAt:"..." }
-> thirdCardNo 即播放凭证
激活未完成返回:
{ status:"UNBOUND" }
-> App 轮询直到 status 变为 ACTIVE
辅助接口:
POST /api/jst/heartBeat -> 续期sign
POST /api/jst/refreshSign -> 换新sign
```
## 阶段七:App播放
```
App使用 thirdCardNo 作为播放凭证
-> 请求橘视厅SDK初始化
-> 换取播放地址(programCode = 内容contentcode,电视剧取分集contentcode)
-> 播放内容
说明:
- 电影:contentcode 直接作为 programCode
- 电视剧:先调 /openapi/partner/media/episodes?contentcode=父级code 取分集列表
分集的 contentcode 作为 programCode 传入SDK
```
## 完整时序图
```
管理后台 第三方EPG 本系统 橘视厅 App
| | | | |
|--采购天卡------->| | | |
| | | | |
| [用户浏览付费] | | |
| |--consume-------->| | |
| | |--取卡+绑卡 | |
| | |--异步激活-------->| |
| |<--deviceId-------| | |
| | expireAt | | |
| | ACTIVATING | | |
| | |<--激活成功-------| |
| | | | |
| |--传参启动App---->| | |
| | (设备标识) | | |
| | | |<--init--------|
| | | | (免激活码) |
| | | |--sign-------->|
| | | | |
| | | |<--checkBenefits|
| | | | (携带sign) |
| | | |--thirdCardNo->|
| | | | |
| | | | [SDK初始化]
| | | | [播放] |
```
## 关键设计说明
### 安全设计
- **thirdCardNo 不暴露给第三方**:播放凭证仅通过 App 的 `init -> checkBenefits` 链路获取,第三方无法绕过 App 直接播放
- **签名鉴权**:第三方接口通过 `MD5(appKey+timestamp+nonce+appSecret)` 签名,timestamp 5分钟有效期 + nonce 防重放
- **设备 sign 鉴权**:App 接口通过 HMAC-SHA256 sign 鉴权,24小时有效,Redis 保持最新 sign
### 并发控制
- **consume 防重锁**:Redis `daycard:partner:consume:{设备标识}`,TTL 30s,防止并发重复消耗
- **取卡行级锁**:`FOR UPDATE SKIP LOCKED`,防止并发超卖
- **激活锁**:Redis `api:thirdparty:activate:lock:{poolId}`,TTL 3600s,防止重复激活
### 幂等设计
- **consume 幂等**:设备已有有效天卡时直接返回,不重复消耗
- **卡密激活幂等**:橘视厅返回 E0000003 + "已激活,重复使用激活码" 视为成功
### 性能优化
- **Token 缓存**:Redis `api:thirdparty:activate:{thirdCardNo}`,TTL 5分钟,减少橘视厅登录调用
- **异步激活**:consume 接口立即返回 ACTIVATING,激活在后台线程池完成
### 事务保障
- consume 方法 `@Transactional(rollbackFor = Exception.class)`,保证设备创建、卡池状态更新、partner池更新的原子性