Files
as-daycard-openapi/橘视厅酒店版openapi文档.html
zhaogd 12c6fc4db4 feat: 设备查询支持按source筛选,区分自营/第三方设备
- DeviceServiceImpl.queryPage 增加 source 筛选条件
- 自营设备 source=DIRECT(激活码激活,DB默认值)
- 第三方设备 source=PARTNER(OpenAPI注册,显式设置)
2026-08-01 14:41:12 +08:00

705 lines
19 KiB
HTML
Raw Permalink 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.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
font-size: 14px;
line-height: 1.8;
color: #333;
max-width: 900px;
margin: 0 auto;
padding: 40px;
}
h1 {
font-size: 24px;
border-bottom: 2px solid #333;
padding-bottom: 10px;
margin-top: 30px;
}
h2 {
font-size: 20px;
border-bottom: 1px solid #ccc;
padding-bottom: 8px;
margin-top: 25px;
}
h3 {
font-size: 16px;
margin-top: 20px;
}
table {
border-collapse: collapse;
width: 100%;
margin: 15px 0;
font-size: 13px;
}
th, td {
border: 1px solid #ddd;
padding: 8px 12px;
text-align: left;
}
th {
background-color: #f5f5f5;
font-weight: bold;
}
tr:nth-child(even) {
background-color: #fafafa;
}
pre {
background-color: #f5f5f5;
border: 1px solid #ddd;
border-radius: 4px;
padding: 12px;
overflow-x: auto;
font-size: 12px;
white-space: pre-wrap;
word-wrap: break-word;
}
code {
font-family: "SF Mono", "Menlo", "Consolas", monospace;
background-color: #f0f0f0;
padding: 2px 4px;
border-radius: 3px;
font-size: 13px;
}
pre code {
background-color: transparent;
padding: 0;
}
strong {
color: #222;
}
hr {
border: none;
border-top: 1px solid #ddd;
margin: 20px 0;
}
blockquote {
border-left: 4px solid #4A90D9;
margin: 15px 0;
padding: 10px 20px;
background-color: #f9f9f9;
color: #666;
}
</style>
</head>
<body>
<h1>天卡开放平台 第三方对接文档</h1>
<p><strong>版本</strong>: V2.0<br />
<strong>更新日期</strong>: 2026-07-23</p>
<hr />
<h2>1. 概述</h2>
<p>本文档面向第三方合作伙伴后端开发人员,描述天卡开放平台的接口规范。</p>
<h3>1.1 业务流程</h3>
<pre><code>第三方EPG 开放平台 APK
| | |
| 1. 用户在EPG界面完成支付 | |
| | |
| 2. 调用消耗接口(发卡) | |
|---POST /card/consume-------&gt;| |
|&lt;---deviceId, expireAt-------| |
| status=ACTIVATING | |
| | 3. 异步激活(橘视厅SDK) |
| | |
| 4. EPG传参启动APK | |
|---设备标识(mac/sn等)-------&gt; | |
| | 5. APK调init获取sign |
| |&lt;---POST /api/jst/init-------|
| |----sign-------------------&gt;|
| | 6. APK轮询checkBenefits |
| |&lt;---POST /checkBenefits------|
| |----thirdCardNo------------&gt;|
| | (status=ACTIVE时返回) |
| | 7. APK用thirdCardNo播放 |
</code></pre>
<h3>1.2 服务器地址</h3>
<table>
<thead>
<tr>
<th>环境</th>
<th>地址</th>
</tr>
</thead>
<tbody>
<tr>
<td>生产环境</td>
<td>http://58.220.83.58:8084</td>
</tr>
</tbody>
</table>
<hr />
<h2>2. 鉴权方式</h2>
<h3>2.1 凭证</h3>
<p>平台分配的 <code>appKey</code> 和 <code>appSecret</code>,由管理后台创建合作伙伴时自动生成。</p>
<h3>2.2 签名算法</h3>
<pre><code>sign = MD5(appKey + timestamp + nonce + appSecret).toUpperCase()
</code></pre>
<ul>
<li>拼接顺序固定:appKey + timestamp + nonce + appSecret,无分隔符</li>
<li>MD5 结果转大写</li>
</ul>
<h3>2.3 请求头</h3>
<p>每个请求必须携带以下 HTTP 请求头:</p>
<table>
<thead>
<tr>
<th>请求头</th>
<th>类型</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>appKey</td>
<td>String</td>
<td>平台分配的应用 Key</td>
</tr>
<tr>
<td>timestamp</td>
<td>String</td>
<td>毫秒级时间戳,有效期 5 分钟</td>
</tr>
<tr>
<td>nonce</td>
<td>String</td>
<td>随机字符串,5 分钟内不可重复</td>
</tr>
<tr>
<td>sign</td>
<td>String</td>
<td>签名(大写 MD5)</td>
</tr>
</tbody>
</table>
<h3>2.4 鉴权失败响应</h3>
<p>HTTP 401:</p>
<pre><code class="language-json">{&quot;code&quot;: 401, &quot;msg&quot;: &quot;签名校验失败&quot;}
</code></pre>
<h3>2.5 统一响应格式</h3>
<pre><code class="language-json">{&quot;code&quot;: 0, &quot;msg&quot;: &quot;success&quot;, &quot;data&quot;: {}}
</code></pre>
<ul>
<li><code>code = 0</code> 表示成功,非 0 表示失败</li>
<li>业务数据在 <code>data</code> 字段内</li>
</ul>
<hr />
<h2>3. 天卡代销接口</h2>
<h3>3.1 消耗天卡</h3>
<p>用户在 EPG 完成支付后调用,从合作伙伴天卡池分配 1 张天卡绑定到设备,并异步激活。</p>
<pre><code>POST /openapi/partner/card/consume
Content-Type: application/json
</code></pre>
<p><strong>请求体</strong>:</p>
<table>
<thead>
<tr>
<th>参数</th>
<th>类型</th>
<th>必填</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>mac</td>
<td>String</td>
<td>否*</td>
<td>设备 MAC 地址</td>
</tr>
<tr>
<td>wifiMac</td>
<td>String</td>
<td>否*</td>
<td>WiFi MAC 地址</td>
</tr>
<tr>
<td>deviceUid</td>
<td>String</td>
<td>是*</td>
<td>设备唯一标识</td>
</tr>
<tr>
<td>deviceSn</td>
<td>String</td>
<td>否*</td>
<td>设备序列号</td>
</tr>
<tr>
<td>channelOrgId</td>
<td>Long</td>
<td>否</td>
<td>渠道组织 ID</td>
</tr>
</tbody>
</table>
<p>* mac / wifiMac / deviceUid / deviceSn
deviceUid必须传,mac / wifimac /deviceSn
至少传一个,需与 APK 启动时传入的设备标识一致。</p>
<p><strong>请求示例</strong>:</p>
<pre><code class="language-json">{
&quot;mac&quot;: &quot;AA:BB:CC:DD:EE:01&quot;,
&quot;deviceSn&quot;: &quot;SN-001&quot;,
&quot;deviceUid&quot;: &quot;UID-001&quot;
}
</code></pre>
<p><strong>响应</strong>:</p>
<table>
<thead>
<tr>
<th>字段</th>
<th>类型</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>deviceId</td>
<td>Long</td>
<td>设备 ID</td>
</tr>
<tr>
<td>expireAt</td>
<td>String</td>
<td>天卡过期时间(yyyy-MM-dd HH:mm:ss),初始为 24 小时,异步激活成功后更新为真实过期时间</td>
</tr>
<tr>
<td>status</td>
<td>String</td>
<td>天卡状态:ACTIVATING(激活中)</td>
</tr>
</tbody>
</table>
<p><strong>响应示例</strong>:</p>
<pre><code class="language-json">{
&quot;code&quot;: 0,
&quot;msg&quot;: &quot;success&quot;,
&quot;data&quot;: {
&quot;deviceId&quot;: 123,
&quot;expireAt&quot;: &quot;2026-07-24 14:00:00&quot;,
&quot;status&quot;: &quot;ACTIVATING&quot;
}
}
</code></pre>
<p><strong>幂等说明</strong>:同一设备重复调用时不会消耗新天卡,直接返回已有天卡信息。若天卡已激活成功,status 返回 <code>ACTIVE</code>。</p>
<p><strong>错误响应</strong>:</p>
<table>
<thead>
<tr>
<th>code</th>
<th>msg</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>400</td>
<td>mac/wifiMac/deviceUid/deviceSn 至少传一个</td>
<td>缺少设备标识</td>
</tr>
<tr>
<td>400</td>
<td>天卡池库存不足</td>
<td>可用天卡已耗尽</td>
</tr>
<tr>
<td>400</td>
<td>操作太频繁,请稍后再试</td>
<td>同一设备短时间内重复请求</td>
</tr>
<tr>
<td>500</td>
<td>天卡分配失败,请重试</td>
<td>系统繁忙,请重试</td>
</tr>
</tbody>
</table>
<h3>3.2 查询天卡库存</h3>
<pre><code>GET /openapi/partner/card/stock
</code></pre>
<p><strong>响应示例</strong>:</p>
<pre><code class="language-json">{
&quot;code&quot;: 0,
&quot;msg&quot;: &quot;success&quot;,
&quot;data&quot;: {
&quot;available&quot;: 50
}
}
</code></pre>
<hr />
<h2>4. 媒资接口</h2>
<h3>4.1 查看授权范围</h3>
<pre><code>GET /openapi/partner/media/package
</code></pre>
<p><strong>响应</strong>:</p>
<table>
<thead>
<tr>
<th>字段</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>scope</td>
<td>授权级别:CONTENT(指定内容)/ CATALOG(指定栏目)/ FULL(全量)</td>
</tr>
<tr>
<td>catalogIds</td>
<td>授权栏目 ID(逗号分隔,CATALOG 级别有值)</td>
</tr>
<tr>
<td>contentCount</td>
<td>授权内容数量</td>
</tr>
</tbody>
</table>
<h3>4.2 栏目树</h3>
<pre><code>GET /openapi/partner/media/catalog-tree
</code></pre>
<p>按授权范围过滤返回:
- CONTENT 级别返回空树
- CATALOG 级别只返回授权栏目
- FULL 级别返回完整栏目树</p>
<h3>4.3 内容列表</h3>
<pre><code>GET /openapi/partner/media/list?catalogid=472&amp;pageindex=1&amp;pagesize=20
</code></pre>
<table>
<thead>
<tr>
<th>参数</th>
<th>类型</th>
<th>必填</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>catalogid</td>
<td>String</td>
<td>FULL/CATALOG 级别必填</td>
<td>栏目 ID</td>
</tr>
<tr>
<td>pageindex</td>
<td>int</td>
<td>否(默认 1)</td>
<td>页码</td>
</tr>
<tr>
<td>pagesize</td>
<td>int</td>
<td>否(默认 20)</td>
<td>每页条数</td>
</tr>
</tbody>
</table>
<ul>
<li>CONTENT 级别:返回授权内容列表,不需传 catalogid</li>
<li>CATALOG 级别:必须传授权范围内的 catalogid</li>
<li>FULL 级别:必须传 catalogid</li>
</ul>
<h3>4.4 搜索</h3>
<pre><code>GET /openapi/partner/media/search?keyword=镖人&amp;pageindex=1&amp;pagesize=20
</code></pre>
<table>
<thead>
<tr>
<th>参数</th>
<th>类型</th>
<th>必填</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>keyword</td>
<td>String</td>
<td>是</td>
<td>搜索关键词</td>
</tr>
<tr>
<td>pageindex</td>
<td>int</td>
<td>否(默认 1)</td>
<td>页码</td>
</tr>
<tr>
<td>pagesize</td>
<td>int</td>
<td>否(默认 20)</td>
<td>每页条数</td>
</tr>
</tbody>
</table>
<h3>4.5 内容字段说明</h3>
<table>
<thead>
<tr>
<th>字段</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>contentcode</td>
<td>内容唯一编码</td>
</tr>
<tr>
<td>name</td>
<td>内容名称</td>
</tr>
<tr>
<td>posterimg</td>
<td>海报图片 URL</td>
</tr>
<tr>
<td>categoryname</td>
<td>分类名称</td>
</tr>
<tr>
<td>score</td>
<td>评分</td>
</tr>
<tr>
<td>isFree</td>
<td>1 = 免费,0 = 付费</td>
</tr>
<tr>
<td>episodecount</td>
<td>集数</td>
</tr>
<tr>
<td>duration</td>
<td>时长(秒)</td>
</tr>
<tr>
<td>releaseyear</td>
<td>上映年份</td>
</tr>
<tr>
<td>originalcountry</td>
<td>地区</td>
</tr>
</tbody>
</table>
<hr />
<h2>5. APK 侧流程说明</h2>
<p>以下接口由 APK 调用,第三方无需直接调用,但需了解流程以便 EPG 传参启动 APK。</p>
<h3>5.1 设备初始化</h3>
<p>APK 使用消耗接口传入的相同设备标识调用初始化:</p>
<pre><code>POST /api/jst/init
Content-Type: application/json
</code></pre>
<p>请求体(不传 activationCode,代销模式):</p>
<pre><code class="language-json">{
&quot;mac&quot;: &quot;AA:BB:CC:DD:EE:01&quot;,
&quot;wifiMac&quot;: &quot;...&quot;,
&quot;deviceSn&quot;: &quot;SN-001&quot;,
&quot;deviceUid&quot;: &quot;UID-001&quot;
}
</code></pre>
<p>返回 <code>sign</code>(设备鉴权凭证,24 小时有效)。</p>
<h3>5.2 查询天卡权益</h3>
<p>APK 轮询查询激活状态,仅当 status 为 ACTIVE 时获取播放凭证:</p>
<pre><code>POST /api/jst/checkBenefits
Header: sign: &lt;init 返回的 sign&gt;
</code></pre>
<p><strong>响应</strong>:</p>
<table>
<thead>
<tr>
<th>status</th>
<th>说明</th>
<th>是否返回 thirdCardNo</th>
</tr>
</thead>
<tbody>
<tr>
<td>ACTIVATING</td>
<td>异步激活中,需继续轮询</td>
<td>否</td>
</tr>
<tr>
<td>ACTIVE</td>
<td>激活成功,可播放</td>
<td>是</td>
</tr>
<tr>
<td>ACTIVATION_FAILED</td>
<td>激活失败</td>
<td>否</td>
</tr>
<tr>
<td>UNBOUND</td>
<td>无天卡绑定</td>
<td>否</td>
</tr>
</tbody>
</table>
<p><strong>轮询建议</strong>:间隔 2 秒,超时 5 分钟。</p>
<h3>5.3 播放</h3>
<p>APK 使用 <code>thirdCardNo</code> 作为播放凭证初始化橘视厅 SDK,获取播放地址后播放。</p>
<hr />
<h2>6. 签名示例</h2>
<h3>Java</h3>
<pre><code class="language-java">String appKey = &quot;平台分配的appKey&quot;;
String appSecret = &quot;平台分配的appSecret&quot;;
String timestamp = String.valueOf(System.currentTimeMillis());
String nonce = UUID.randomUUID().toString().replace(&quot;-&quot;, &quot;&quot;);
String sign = DigestUtils.md5Hex(appKey + timestamp + nonce + appSecret).toUpperCase();
</code></pre>
<h3>Python</h3>
<pre><code class="language-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()
</code></pre>
<h3>Node.js</h3>
<pre><code class="language-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();
</code></pre>
<hr />
<h2>7. 请求示例(curl)</h2>
<p>以下示例中的 <code>appKey</code>、<code>appSecret</code> 需替换为平台分配的实际值。<code>timestamp</code>、<code>nonce</code>、<code>sign</code> 需按签名算法实时生成。</p>
<h3>7.1 消耗天卡</h3>
<pre><code class="language-bash">curl -X POST &quot;http://58.220.83.58:8084/openapi/partner/card/consume&quot; \
-H &quot;Content-Type: application/json&quot; \
-H &quot;appKey: 你的appKey&quot; \
-H &quot;timestamp: 1784809605695&quot; \
-H &quot;nonce: 1784809605695aaf094ef&quot; \
-H &quot;sign: A6EBB96858ED2FAD864CB39568E3EABB&quot; \
-d '{&quot;mac&quot;:&quot;AA:BB:CC:DD:EE:01&quot;,&quot;deviceSn&quot;:&quot;SN-001&quot;,&quot;deviceUid&quot;:&quot;UID-001&quot;}'
</code></pre>
<p>响应:</p>
<pre><code class="language-json">{
&quot;code&quot;: 0,
&quot;msg&quot;: &quot;success&quot;,
&quot;data&quot;: {
&quot;deviceId&quot;: 123,
&quot;expireAt&quot;: &quot;2026-07-24 20:26:46&quot;,
&quot;status&quot;: &quot;ACTIVATING&quot;
}
}
</code></pre>
<h3>7.2 查询天卡库存</h3>
<pre><code class="language-bash">curl -X GET &quot;http://58.220.83.58:8084/openapi/partner/card/stock&quot; \
-H &quot;appKey: 你的appKey&quot; \
-H &quot;timestamp: 1784809605695&quot; \
-H &quot;nonce: 1784809605695aaf094ef&quot; \
-H &quot;sign: A6EBB96858ED2FAD864CB39568E3EABB&quot;
</code></pre>
<h3>7.3 查看授权范围</h3>
<pre><code class="language-bash">curl -X GET &quot;http://58.220.83.58:8084/openapi/partner/media/package&quot; \
-H &quot;appKey: 你的appKey&quot; \
-H &quot;timestamp: 1784809605695&quot; \
-H &quot;nonce: 1784809605695aaf094ef&quot; \
-H &quot;sign: A6EBB96858ED2FAD864CB39568E3EABB&quot;
</code></pre>
<h3>7.4 栏目树</h3>
<pre><code class="language-bash">curl -X GET &quot;http://58.220.83.58:8084/openapi/partner/media/catalog-tree&quot; \
-H &quot;appKey: 你的appKey&quot; \
-H &quot;timestamp: 1784809605695&quot; \
-H &quot;nonce: 1784809605695aaf094ef&quot; \
-H &quot;sign: A6EBB96858ED2FAD864CB39568E3EABB&quot;
</code></pre>
<h3>7.5 内容列表</h3>
<pre><code class="language-bash">curl -X GET &quot;http://58.220.83.58:8084/openapi/partner/media/list?catalogid=472&amp;pageindex=1&amp;pagesize=20&quot; \
-H &quot;appKey: 你的appKey&quot; \
-H &quot;timestamp: 1784809605695&quot; \
-H &quot;nonce: 1784809605695aaf094ef&quot; \
-H &quot;sign: A6EBB96858ED2FAD864CB39568E3EABB&quot;
</code></pre>
<h3>7.6 搜索</h3>
<pre><code class="language-bash">curl -X GET &quot;http://58.220.83.58:8084/openapi/partner/media/search?keyword=镖人&amp;pageindex=1&amp;pagesize=20&quot; \
-H &quot;appKey: 你的appKey&quot; \
-H &quot;timestamp: 1784809605695&quot; \
-H &quot;nonce: 1784809605695aaf094ef&quot; \
-H &quot;sign: A6EBB96858ED2FAD864CB39568E3EABB&quot;
</code></pre>
<h3>7.7 签名生成示例(bash)</h3>
<pre><code class="language-bash">APP_KEY=&quot;你的appKey&quot;
APP_SECRET=&quot;你的appSecret&quot;
TIMESTAMP=$(date +%s%3N)
NONCE=&quot;${TIMESTAMP}$(head -c 8 /dev/urandom | xxd -p)&quot;
SIGN=$(echo -n &quot;${APP_KEY}${TIMESTAMP}${NONCE}${APP_SECRET}&quot; | md5sum | awk '{print toupper($0)}')
echo &quot;appKey: $APP_KEY&quot;
echo &quot;timestamp: $TIMESTAMP&quot;
echo &quot;nonce: $NONCE&quot;
echo &quot;sign: $SIGN&quot;
# 直接用变量发请求
curl -X POST &quot;http://58.220.83.58:8084/openapi/partner/card/consume&quot; \
-H &quot;Content-Type: application/json&quot; \
-H &quot;appKey: $APP_KEY&quot; \
-H &quot;timestamp: $TIMESTAMP&quot; \
-H &quot;nonce: $NONCE&quot; \
-H &quot;sign: $SIGN&quot; \
-d '{&quot;mac&quot;:&quot;AA:BB:CC:DD:EE:01&quot;,&quot;deviceSn&quot;:&quot;SN-001&quot;}'
</code></pre>
<hr />
<h2>8. 注意事项</h2>
<ol>
<li><strong>nonce 唯一性</strong>:每个请求的 nonce 必须唯一,5 分钟内重复提交会被拒绝。</li>
<li><strong>timestamp 有效期</strong>:与服务器时间差超过 5 分钟会过期。</li>
<li><strong>设备标识一致性</strong>:消耗接口传入的设备标识必须与 APK 启动时传入的一致,否则 APK 无法找到设备。</li>
<li><strong>异步激活</strong>:消耗接口返回后天卡异步激活(通常 2-5 秒),APK 需轮询 checkBenefits 确认激活完成。</li>
<li><strong>库存管理</strong>:消耗接口返回库存不足错误时,需联系平台采购天卡。</li>
</ol>
<hr />
<h2>附录:设备标识说明</h2>
<h3>deviceUid 生成规则</h3>
<p>APK 的 <code>deviceUid</code> 按以下优先级生成:</p>
<table>
<thead>
<tr>
<th>优先级</th>
<th>来源</th>
<th>格式</th>
<th>重装不变</th>
<th>工厂重置不变</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Android ID(Settings.Secure.ANDROID_ID)</td>
<td>64 位 hex(16 字符),如 <code>f0f179fe199f7a52</code></td>
<td>是</td>
<td>否</td>
</tr>
<tr>
<td>2</td>
<td>UUID(兜底,存 SharedPreferences)</td>
<td>32 位无横线,如 <code>a1b2c3d4e5f6...</code></td>
<td>否</td>
<td>否</td>
</tr>
</tbody>
</table>
<p>Android ID 为空时使用 UUID 兜底。正常情况下 deviceUid 即 Android ID。</p>
<h3>关键注意点</h3>
<ol>
<li>
<p><strong>consume 与 init 的 deviceUid 必须一致</strong>:第三方调用消耗接口时传入的 <code>deviceUid</code> 必须与 APK 调用 init 时传入的 <code>deviceUid</code> 相同,否则 APK 无法找到设备。</p>
</li>
<li>
<p><strong>Android ID 按签名隔离</strong>:Android 8+ 系统中,不同签名的 App 在同一设备上获取的 Android ID 不同。因此 EPG 获取 deviceUid 的方式需与 APK 一致,建议由 APK 提供给 EPG,而非 EPG 自行获取。</p>
</li>
<li>
<p><strong>推荐传参方式</strong>:EPG 启动 APK 时通过 Intent 传递设备标识,APK 使用相同标识调 init 接口。设备标识建议优先使用 <code>mac</code>,其次 <code>deviceUid</code>。</p>
</li>
</ol>
<h3>设备标识匹配优先级</h3>
<p>消耗接口和 init 接口均按以下优先级匹配设备:</p>
<pre><code>mac &gt; wifiMac &gt; deviceUid &gt; deviceSn
</code></pre>
<p>只要有一个标识匹配即可找到设备。建议 consume 和 init 传递相同的标识组合。</p>
</body>
</html>