Files
as-daycard-openapi/橘视厅酒店版openapi文档.md
zhaogd 73d0d42e8f 更新橘视厅酒店版openapi文档
- 媒资接口补充 /list 多栏目查询、/list-all 全量聚合、/episodes 分集查询接口说明
- curl 示例补充对应接口调用示例,调整章节编号
2026-07-29 12:19:05 +08:00

542 lines
15 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.

# 天卡开放平台 第三方对接文档
**版本**: V2.0
**更新日期**: 2026-07-23
---
## 1. 概述
本文档面向第三方合作伙伴后端开发人员,描述天卡开放平台的接口规范。
### 1.1 业务流程
```
第三方EPG 开放平台 APK
| | |
| 1. 用户在EPG界面完成支付 | |
| | |
| 2. 调用消耗接口(发卡) | |
|---POST /card/consume------->| |
|<---deviceId, expireAt-------| |
| status=ACTIVATING | |
| | 3. 异步激活(橘视厅SDK) |
| | |
| 4. EPG传参启动APK | |
|---设备标识(mac/sn等)-------> | |
| | 5. APK调init获取sign |
| |<---POST /api/jst/init-------|
| |----sign------------------->|
| | 6. APK轮询checkBenefits |
| |<---POST /checkBenefits------|
| |----thirdCardNo------------>|
| | (status=ACTIVE时返回) |
| | 7. APK用thirdCardNo播放 |
```
### 1.2 服务器地址
| 环境 | 地址 |
|------|------|
| 生产环境 | http://58.220.83.58:8084 |
---
## 2. 鉴权方式
### 2.1 凭证
平台分配的 `appKey` 和 `appSecret`,由管理后台创建合作伙伴时自动生成。
### 2.2 签名算法
```
sign = MD5(appKey + timestamp + nonce + appSecret).toUpperCase()
```
- 拼接顺序固定:appKey + timestamp + nonce + appSecret,无分隔符
- MD5 结果转大写
### 2.3 请求头
每个请求必须携带以下 HTTP 请求头:
| 请求头 | 类型 | 说明 |
|--------|------|------|
| appKey | String | 平台分配的应用 Key |
| timestamp | String | 毫秒级时间戳,有效期 5 分钟 |
| nonce | String | 随机字符串,5 分钟内不可重复 |
| sign | String | 签名(大写 MD5) |
### 2.4 鉴权失败响应
HTTP 401:
```json
{"code": 401, "msg": "签名校验失败"}
```
### 2.5 统一响应格式
```json
{"code": 0, "msg": "success", "data": {}}
```
- `code = 0` 表示成功,非 0 表示失败
- 业务数据在 `data` 字段内
---
## 3. 天卡代销接口
### 3.1 消耗天卡
用户在 EPG 完成支付后调用,从合作伙伴天卡池分配 1 张天卡绑定到设备,并异步激活。
```
POST /openapi/partner/card/consume
Content-Type: application/json
```
**请求体**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| mac | String | 否* | 设备 MAC 地址 |
| wifiMac | String | 否* | WiFi MAC 地址 |
| deviceUid | String | 是* | 设备唯一标识 |
| deviceSn | String | 否* | 设备序列号 |
| channelOrgId | Long | 否 | 渠道组织 ID |
\* mac / wifiMac / deviceUid / deviceSn
deviceUid必须传,mac / wifimac /deviceSn
至少传一个,需与 APK 启动时传入的设备标识一致。
**请求示例**:
```json
{
"mac": "AA:BB:CC:DD:EE:01",
"deviceSn": "SN-001",
"deviceUid": "UID-001"
}
```
**响应**:
| 字段 | 类型 | 说明 |
|------|------|------|
| deviceId | Long | 设备 ID |
| expireAt | String | 天卡过期时间(yyyy-MM-dd HH:mm:ss),初始为 24 小时,异步激活成功后更新为真实过期时间 |
| status | String | 天卡状态:ACTIVATING(激活中)|
**响应示例**:
```json
{
"code": 0,
"msg": "success",
"data": {
"deviceId": 123,
"expireAt": "2026-07-24 14:00:00",
"status": "ACTIVATING"
}
}
```
**幂等说明**:同一设备重复调用时不会消耗新天卡,直接返回已有天卡信息。若天卡已激活成功,status 返回 `ACTIVE`。
**错误响应**:
| code | msg | 说明 |
|------|-----|------|
| 400 | mac/wifiMac/deviceUid/deviceSn 至少传一个 | 缺少设备标识 |
| 400 | 天卡池库存不足 | 可用天卡已耗尽 |
| 400 | 操作太频繁,请稍后再试 | 同一设备短时间内重复请求 |
| 500 | 天卡分配失败,请重试 | 系统繁忙,请重试 |
### 3.2 查询天卡库存
```
GET /openapi/partner/card/stock
```
**响应示例**:
```json
{
"code": 0,
"msg": "success",
"data": {
"available": 50
}
}
```
---
## 4. 媒资接口
### 4.1 查看授权范围
```
GET /openapi/partner/media/package
```
**响应**:
| 字段 | 说明 |
|------|------|
| scope | 授权级别:CONTENT(指定内容)/ CATALOG(指定栏目)/ FULL(全量)|
| catalogIds | 授权栏目 ID(逗号分隔,CATALOG 级别有值)|
| contentCount | 授权内容数量 |
### 4.2 栏目树
```
GET /openapi/partner/media/catalog-tree
```
按授权范围过滤返回:
- CONTENT 级别返回空树
- CATALOG 级别只返回授权栏目
- FULL 级别返回完整栏目树
### 4.3 内容列表
```
GET /openapi/partner/media/list?catalogid=472&pageindex=1&pagesize=20
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| catalogid | String | FULL/CATALOG 级别必填 | 栏目 ID,支持逗号分隔传多个(如 `472,492,494`)|
| pageindex | int | 否(默认 1)| 页码 |
| pagesize | int | 否(默认 20)| 每页条数 |
- CONTENT 级别:返回授权内容列表,不需传 catalogid
- CATALOG 级别:必须传授权范围内的 catalogid(多栏目时每个都需在授权范围内)
- FULL 级别:必须传 catalogid
- 单栏目:走橘视厅原生分页,响应快
- 多栏目(逗号分隔):聚合各栏目全量内容后本地分页
### 4.4 全量内容列表
```
GET /openapi/partner/media/list-all?parentCatalogid=471
```
一次请求返回某一级栏目(如 471 电影)下所有二级栏目合并后的全部内容,不分页。适合按天低频全量同步。
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| parentCatalogid | String | 否(默认 471)| 一级栏目 ID |
> 注:各二级栏目内容可能有跨栏目重复(一条内容可属多个栏目),第三方自行按 contentcode 去重。
### 4.5 分集列表
```
GET /openapi/partner/media/episodes?contentcode=23010012026061709581101832&pageindex=1&pagesize=20
```
传入电视剧的父级 contentcode,返回其下各分集列表。分集的 contentcode 可作为 programCode 传入 SDK 获取播放地址。
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| contentcode | String | 是 | 父级内容编码(电视剧的 contentcode)|
| pageindex | int | 否(默认 1)| 页码 |
| pagesize | int | 否(默认 20)| 每页条数 |
- CONTENT 级别:校验 contentcode 在授权范围内
- CATALOG/FULL 级别:直接返回
分集额外字段:
| 字段 | 说明 |
|------|------|
| episodeindex | 集数索引,排序用 |
| parentContentType | 内容类型 |
### 4.6 搜索
```
GET /openapi/partner/media/search?keyword=镖人&pageindex=1&pagesize=20
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| keyword | String | 是 | 搜索关键词 |
| pageindex | int | 否(默认 1)| 页码 |
| pagesize | int | 否(默认 20)| 每页条数 |
### 4.7 内容字段说明
| 字段 | 说明 |
|------|------|
| contentcode | 内容唯一编码 |
| name | 内容名称 |
| posterimg | 海报图片 URL |
| categoryname | 分类名称 |
| score | 评分 |
| isFree | 1 = 免费,0 = 付费 |
| episodecount | 集数 |
| duration | 时长(秒)|
| releaseyear | 上映年份 |
| originalcountry | 地区 |
---
## 5. APK 侧流程说明
以下接口由 APK 调用,第三方无需直接调用,但需了解流程以便 EPG 传参启动 APK。
### 5.1 设备初始化
APK 使用消耗接口传入的相同设备标识调用初始化:
```
POST /api/jst/init
Content-Type: application/json
```
请求体(不传 activationCode,代销模式):
```json
{
"mac": "AA:BB:CC:DD:EE:01",
"wifiMac": "...",
"deviceSn": "SN-001",
"deviceUid": "UID-001"
}
```
返回 `sign`(设备鉴权凭证,24 小时有效)。
### 5.2 查询天卡权益
APK 轮询查询激活状态,仅当 status 为 ACTIVE 时获取播放凭证:
```
POST /api/jst/checkBenefits
Header: sign: <init 返回的 sign>
```
**响应**:
| status | 说明 | 是否返回 thirdCardNo |
|--------|------|---------------------|
| ACTIVATING | 异步激活中,需继续轮询 | 否 |
| ACTIVE | 激活成功,可播放 | 是 |
| ACTIVATION_FAILED | 激活失败 | 否 |
| UNBOUND | 无天卡绑定 | 否 |
**轮询建议**:间隔 2 秒,超时 5 分钟。
### 5.3 播放
APK 使用 `thirdCardNo` 作为播放凭证初始化橘视厅 SDK,获取播放地址后播放。
---
## 6. 签名示例
### Java
```java
String appKey = "平台分配的appKey";
String appSecret = "平台分配的appSecret";
String timestamp = String.valueOf(System.currentTimeMillis());
String nonce = UUID.randomUUID().toString().replace("-", "");
String sign = DigestUtils.md5Hex(appKey + timestamp + nonce + appSecret).toUpperCase();
```
### Python
```python
import hashlib, time, uuid
timestamp = str(int(time.time() * 1000))
nonce = uuid.uuid4().hex
sign = hashlib.md5((app_key + timestamp + nonce + app_secret).encode()).hexdigest().upper()
```
### Node.js
```javascript
const crypto = require('crypto');
const timestamp = Date.now().toString();
const nonce = crypto.randomBytes(16).toString('hex');
const sign = crypto.createHash('md5')
.update(appKey + timestamp + nonce + appSecret)
.digest('hex').toUpperCase();
```
---
## 7. 请求示例(curl)
以下示例中的 `appKey`、`appSecret` 需替换为平台分配的实际值。`timestamp`、`nonce`、`sign` 需按签名算法实时生成。
### 7.1 消耗天卡
```bash
curl -X POST "http://58.220.83.58:8084/openapi/partner/card/consume" \
-H "Content-Type: application/json" \
-H "appKey: 你的appKey" \
-H "timestamp: 1784809605695" \
-H "nonce: 1784809605695aaf094ef" \
-H "sign: A6EBB96858ED2FAD864CB39568E3EABB" \
-d '{"mac":"AA:BB:CC:DD:EE:01","deviceSn":"SN-001","deviceUid":"UID-001"}'
```
响应:
```json
{
"code": 0,
"msg": "success",
"data": {
"deviceId": 123,
"expireAt": "2026-07-24 20:26:46",
"status": "ACTIVATING"
}
}
```
### 7.2 查询天卡库存
```bash
curl -X GET "http://58.220.83.58:8084/openapi/partner/card/stock" \
-H "appKey: 你的appKey" \
-H "timestamp: 1784809605695" \
-H "nonce: 1784809605695aaf094ef" \
-H "sign: A6EBB96858ED2FAD864CB39568E3EABB"
```
### 7.3 查看授权范围
```bash
curl -X GET "http://58.220.83.58:8084/openapi/partner/media/package" \
-H "appKey: 你的appKey" \
-H "timestamp: 1784809605695" \
-H "nonce: 1784809605695aaf094ef" \
-H "sign: A6EBB96858ED2FAD864CB39568E3EABB"
```
### 7.4 栏目树
```bash
curl -X GET "http://58.220.83.58:8084/openapi/partner/media/catalog-tree" \
-H "appKey: 你的appKey" \
-H "timestamp: 1784809605695" \
-H "nonce: 1784809605695aaf094ef" \
-H "sign: A6EBB96858ED2FAD864CB39568E3EABB"
```
### 7.5 内容列表
```bash
curl -X GET "http://58.220.83.58:8084/openapi/partner/media/list?catalogid=472&pageindex=1&pagesize=20" \
-H "appKey: 你的appKey" \
-H "timestamp: 1784809605695" \
-H "nonce: 1784809605695aaf094ef" \
-H "sign: A6EBB96858ED2FAD864CB39568E3EABB"
```
### 7.6 全量内容列表
```bash
curl -X GET "http://58.220.83.58:8084/openapi/partner/media/list-all?parentCatalogid=471" \
-H "appKey: 你的appKey" \
-H "timestamp: 1784809605695" \
-H "nonce: 1784809605695aaf094ef" \
-H "sign: A6EBB96858ED2FAD864CB39568E3EABB"
```
### 7.7 分集列表
```bash
curl -X GET "http://58.220.83.58:8084/openapi/partner/media/episodes?contentcode=23010012026061709581101832&pageindex=1&pagesize=20" \
-H "appKey: 你的appKey" \
-H "timestamp: 1784809605695" \
-H "nonce: 1784809605695aaf094ef" \
-H "sign: A6EBB96858ED2FAD864CB39568E3EABB"
```
### 7.8 搜索
```bash
curl -X GET "http://58.220.83.58:8084/openapi/partner/media/search?keyword=镖人&pageindex=1&pagesize=20" \
-H "appKey: 你的appKey" \
-H "timestamp: 1784809605695" \
-H "nonce: 1784809605695aaf094ef" \
-H "sign: A6EBB96858ED2FAD864CB39568E3EABB"
```
### 7.9 签名生成示例(bash)
```bash
APP_KEY="你的appKey"
APP_SECRET="你的appSecret"
TIMESTAMP=$(date +%s%3N)
NONCE="${TIMESTAMP}$(head -c 8 /dev/urandom | xxd -p)"
SIGN=$(echo -n "${APP_KEY}${TIMESTAMP}${NONCE}${APP_SECRET}" | md5sum | awk '{print toupper($0)}')
echo "appKey: $APP_KEY"
echo "timestamp: $TIMESTAMP"
echo "nonce: $NONCE"
echo "sign: $SIGN"
# 直接用变量发请求
curl -X POST "http://58.220.83.58:8084/openapi/partner/card/consume" \
-H "Content-Type: application/json" \
-H "appKey: $APP_KEY" \
-H "timestamp: $TIMESTAMP" \
-H "nonce: $NONCE" \
-H "sign: $SIGN" \
-d '{"mac":"AA:BB:CC:DD:EE:01","deviceSn":"SN-001"}'
```
---
## 8. 注意事项
1. **nonce 唯一性**:每个请求的 nonce 必须唯一,5 分钟内重复提交会被拒绝。
2. **timestamp 有效期**:与服务器时间差超过 5 分钟会过期。
3. **设备标识一致性**:消耗接口传入的设备标识必须与 APK 启动时传入的一致,否则 APK 无法找到设备。
4. **异步激活**:消耗接口返回后天卡异步激活(通常 2-5 秒),APK 需轮询 checkBenefits 确认激活完成。
5. **库存管理**:消耗接口返回库存不足错误时,需联系平台采购天卡。
---
## 附录:设备标识说明
### deviceUid 生成规则
APK 的 `deviceUid` 按以下优先级生成:
| 优先级 | 来源 | 格式 | 重装不变 | 工厂重置不变 |
|--------|------|------|----------|-------------|
| 1 | Android ID(Settings.Secure.ANDROID_ID) | 64 位 hex(16 字符),如 `f0f179fe199f7a52` | 是 | 否 |
| 2 | UUID(兜底,存 SharedPreferences) | 32 位无横线,如 `a1b2c3d4e5f6...` | 否 | 否 |
Android ID 为空时使用 UUID 兜底。正常情况下 deviceUid 即 Android ID。
### 关键注意点
1. **consume 与 init 的 deviceUid 必须一致**:第三方调用消耗接口时传入的 `deviceUid` 必须与 APK 调用 init 时传入的 `deviceUid` 相同,否则 APK 无法找到设备。
2. **Android ID 按签名隔离**:Android 8+ 系统中,不同签名的 App 在同一设备上获取的 Android ID 不同。因此 EPG 获取 deviceUid 的方式需与 APK 一致,建议由 APK 提供给 EPG,而非 EPG 自行获取。
3. **推荐传参方式**:EPG 启动 APK 时通过 Intent 传递设备标识,APK 使用相同标识调 init 接口。设备标识建议优先使用 `mac`,其次 `deviceUid`。
### 设备标识匹配优先级
消耗接口和 init 接口均按以下优先级匹配设备:
```
mac > wifiMac > deviceUid > deviceSn
```
只要有一个标识匹配即可找到设备。建议 consume 和 init 传递相同的标识组合。