初始化天卡开放平台后端项目
包含完整功能模块: - 设备激活(直销激活码模式 + 代销免激活码模式) - 微信支付 + 天卡订单 - 天卡池管理 + 异步橘视厅SDK激活 - 第三方合作伙伴接口(代销模式) - 签名鉴权(appKey/appSecret + nonce防重放) - 天卡消耗(事务 + 幂等 + 并发控制) - 媒资接口(三级授权过滤) - 审计日志 - 定价策略(3层:门店 > 渠道 > 平台) - 分账(微信分账 + 分润规则) - Docker部署(含Redis容器)
This commit is contained in:
194
第三方合作伙伴接口流程.md
Normal file
194
第三方合作伙伴接口流程.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# 第三方合作伙伴接口完整流程
|
||||
|
||||
## 阶段一:管理后台准备(平台方操作)
|
||||
|
||||
```
|
||||
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/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初始化
|
||||
-> 换取播放地址
|
||||
-> 播放内容
|
||||
```
|
||||
|
||||
## 完整时序图
|
||||
|
||||
```
|
||||
管理后台 第三方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池更新的原子性
|
||||
Reference in New Issue
Block a user