- 阶段二补充 /list 多栏目查询、/list-all 全量聚合、/episodes 分集查询接口说明 - 阶段七补充电视剧分集 contentcode 与 programCode 播放地址的衔接说明
203 lines
8.4 KiB
Markdown
203 lines
8.4 KiB
Markdown
# 第三方合作伙伴接口完整流程
|
||
|
||
## 阶段一:管理后台准备(平台方操作)
|
||
|
||
```
|
||
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池更新的原子性
|