# 第三方合作伙伴接口完整流程 ## 阶段一:管理后台准备(平台方操作) ``` 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池更新的原子性