Files
daycard-app/卡密导入激活鉴权流程.md
2026-07-26 12:43:59 +08:00

243 lines
11 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.

# 卡密-导入-激活-鉴权 完整流程文档
## 一、整体流程概览
```
┌─────────────────────────────────────────────────────────────────────┐
│ 一次性前置准备 │
│ ① 导入卡密 ② 配置橘视厅平台参数 ③ 注册设备 │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ 用户运行时流程 │
│ ④ 点击播放 → ⑤ 创建订单 → ⑥ 支付 → ⑦ 支付回调触发异步激活 │
│ ↓ │
│ ⑧ 橘视厅API激活卡密(3步HTTP调用) │
│ ↓ │
│ ⑨ 查询权益获取thirdCardNo → ⑩ SDK初始化 → ⑪ 获取视频地址 → ⑫ 播放 │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 二、各环节详解
### ① 卡密导入
| 项目 | 内容 |
|------|------|
| **执行位置** | `as-daycard-openapi` 后端 |
| **触发方式** | 管理员调用 `POST /openapi/daycard/pool/import` |
| **代码位置** | `DayCardPoolServiceImpl.importCards()` |
| **输入** | 橘视厅真实激活码(cardCode),如 `ASXXXXXX` |
| **处理** | 为每个cardCode自动生成 `thirdCardNo`(随机数字+字母串),写入 `day_card_pool` 表,status=UNUSED |
| **产出** | `day_card_pool` 表新增记录:cardCode + thirdCardNo + status=UNUSED |
| **关键** | cardCode必须是橘视厅平台真实有效的激活码,否则后续激活会失败 |
### ② 配置橘视厅平台参数
| 项目 | 内容 |
|------|------|
| **执行位置** | `as-daycard-openapi` 后端数据库 |
| **存储位置** | `sys_config` 表,`param_key=JST_PLATFORM_CONFIG` |
| **配置内容** | jushitingBaseUrl / jushitingAccesser / jushitingBusinessLine / jushitingLoginRsaPublicKey / jushitingActivateSignKey / vipQueryBaseUrl |
| **来源** | 从生产环境 `as-partner-backend` 的 `sys_config` 表复制 |
| **作用** | 后端调用橘视厅API时的鉴权参数 |
### ③ 设备注册
| 项目 | 内容 |
|------|------|
| **执行位置** | `as-daycard-openapi` 后端 + MediaApp |
| **触发方式** | 管理后台 `POST /manage/device/save` 或自动创建 |
| **代码位置** | `DeviceController.save()` → `DeviceServiceImpl` |
| **产出** | `device` 表新增记录,deviceId=1(当前测试设备) |
| **App引用** | `DayCardConfig.DEVICE_ID = 1L`(硬编码) |
---
### ④ 点击播放
| 项目 | 内容 |
|------|------|
| **执行位置** | MediaApp(Android端) |
| **代码位置** | `DetailActivity.btnPlay.onClick` → `startPaymentFlow()` |
| **动作** | 调用天卡后端 `GET /openapi/media/play-permission/{mediaId}?deviceId=1` |
| **目的** | 检查设备是否已有播放权限(是否已支付过且未过期) |
### ⑤ 创建订单
| 项目 | 内容 |
|------|------|
| **执行位置** | `as-daycard-openapi` 后端 |
| **触发方式** | App调用 `POST /openapi/daycard/create` |
| **代码位置** | `DayCardOrderServiceImpl.createOrder()` |
| **处理** | 1. Redis防重锁 2. 从 `day_card_pool` 中 `SELECT...FOR UPDATE SKIP LOCKED` 抢一张UNUSED的卡 3. 卡状态 UNUSED→RESERVED 4. 创建订单 status=CREATED |
| **产出** | `day_card_order` 表新增订单 + 卡状态变为RESERVED |
### ⑥ 支付
| 项目 | 内容 |
|------|------|
| **执行位置** | MediaApp + `as-daycard-openapi` 后端 |
| **测试方式** | App调用 `POST /openapi/payment/mock-pay/{orderNo}` |
| **生产方式** | 微信扫码支付 `POST /openapi/payment/native/create` |
| **代码位置** | `DayCardOrderServiceImpl.mockPay()` 或微信支付回调 |
---
### ⑦ 支付回调触发异步激活
| 项目 | 内容 |
|------|------|
| **执行位置** | `as-daycard-openapi` 后端 |
| **代码位置** | `DayCardOrderServiceImpl.payCallback()` |
| **处理** | 1. 订单 status→PAID 2. 卡状态 RESERVED→USED 3. 设置初始expire_at(24h兜底) 4. 触发 `@Async asyncActivateCard(order)` |
| **关键** | 异步激活不阻塞支付回调返回,App此时已收到支付成功 |
### ⑧ 橘视厅API激活卡密(3步HTTP调用)
| 项目 | 内容 |
|------|------|
| **执行位置** | `as-daycard-openapi` 后端(异步线程池 `thirdPartyActivateExecutor`) |
| **代码位置** | `DayCardOrderServiceImpl.asyncActivateCard()` → `JushitingApiServiceImpl` |
#### Step 1: 免密登录获取Token
```
JushitingApiServiceImpl.getToken(device, thirdCardNo)
→ POST {jushitingBaseUrl}/ums_api/api/v1/third/login
→ 参数: uid=thirdCardNo, mac={thirdCardNo}_bul_{businessLine}, accesser, businessLine, nonce, timestamp, serviceComboCode
→ 签名: RSA加密(用jushitingLoginRsaPublicKey)
→ 返回: token + memberCode
```
#### Step 2: 卡密激活
```
JushitingApiServiceImpl.activateCard(deviceId, cardCode, memberCode, token)
→ POST {jushitingBaseUrl}/ccms_api/hswj/api/card/activate?sign=xxx
→ 参数: activateCode=cardCode, memberCode, timestamp, serviceComboCode
→ 签名: MD5(activateCode&memberCode&timestamp&key={jushitingActivateSignKey}).toUpperCase()
→ 返回: status=true + expireTime
→ 此时橘视厅平台建立了 thirdCardNo → 用户 → 订购关系
```
#### Step 3: 查询VIP到期时间(仅Step2未返回expireTime时)
```
JushitingApiServiceImpl.queryVipExpireTime(deviceId, thirdCardNo)
→ POST {vipQueryBaseUrl}/ums_api/member/uid/queryVipInfo
→ 参数: uid=thirdCardNo, businessLine, requestTime, sign
→ 签名: MD5(businessLine + requestTime + uid + key).toUpperCase(), key="c25svtwbTyCE"
→ 返回: vipExpireTime
```
#### 激活完成后更新数据库
```
day_card_pool: activate_status=SUCCESS, activated_at=now, expire_at=真实到期时间
```
---
### ⑨ 查询权益获取thirdCardNo
| 项目 | 内容 |
|------|------|
| **执行位置** | `as-daycard-openapi` 后端 |
| **触发方式** | App调用 `GET /openapi/daycard/benefits/{deviceId}` |
| **代码位置** | `DayCardOrderController.benefits()` → `DayCardOrderServiceImpl.getDeviceBenefits()` |
| **SQL** | `SELECT...FROM day_card_pool WHERE device_id=? AND status='USED' AND expire_at > NOW() ORDER BY used_at DESC LIMIT 1` |
| **返回** | `{ hasBenefits: true, thirdCardNo: "xxx", cardCode: "xxx", expireAt: "xxx" }` |
### ⑩ SDK初始化
| 项目 | 内容 |
|------|------|
| **执行位置** | MediaApp(Android端) |
| **代码位置** | `DetailActivity.fetchVideoUrlAndPlay()` → `JetsenxSdkUtil.init(ctx, thirdCardNo, ...)` |
| **参数** | uid=thirdCardNo, utype=2(第三方ID), agcode, tenant, secret, accesser |
| **SDK内部** | JetsenxSdk向捷成平台注册用户身份,获取memberCode |
| **关键** | 此时橘视厅平台已有该thirdCardNo的订购关系(⑧步已建立) |
### ⑪ 获取视频地址
| 项目 | 内容 |
|------|------|
| **执行位置** | MediaApp(Android端)→ JetsenxSdk → 橘视厅平台 |
| **代码位置** | `JetsenxSdkUtil.getAllVideoUrl()` |
| **流程** | 1. `getAllAssetPrices()` 获取productCode 2. 根据parentContentType判断免费/付费 3. `getVideoUrls()` 或 `getFreeVideoUrls()` |
| **返回** | VideoInfo[]: videoUrl(1080p/720p), format, movieCode |
| **前提** | SDK已用有效thirdCardNo初始化 + 平台有订购关系 |
### ⑫ 播放
| 项目 | 内容 |
|------|------|
| **执行位置** | MediaApp(Android端) |
| **代码位置** | `VideoPlayerActivity` → ExoPlayer |
| **动作** | 用获取到的视频URL播放 |
---
## 三、当前阻滞点
```
① 卡密导入 → ❌ 导入的是mock卡密 DAYCARD-TEST-003,不是橘视厅真实激活码
② 平台配置 → ❌ sys_config中JST_PLATFORM_CONFIG可能未配置
↓
⑧ 激活卡密 → ❌ getToken()返回null(baseUrl为空) 或 activateCard()失败(卡密无效)
↓
橘视厅平台无 thirdCardNo 的订购关系
↓
⑩ SDK初始化 → ✅ 成功(允许任意uid登录)
⑪ 获取视频地址 → ❌ -110未订购 / 604身份无效
```
---
## 四、打通链路的前置条件
1. **从生产环境获取真实的橘视厅卡密(cardCode)**
2. **从生产环境 `sys_config` 表复制 `JST_PLATFORM_CONFIG` 配置值**
3. **清除当前mock卡密,用真实卡密重新导入天卡池**
---
## 五、关键代码文件索引
### MediaApp(Android端)
| 文件 | 作用 |
|------|------|
| `MediaApp/.../ui/DetailActivity.kt` | 详情页,播放流程入口 |
| `MediaApp/.../sdk/JetsenxSdkUtil.kt` | JetsenxSdk封装,SDK初始化+获取视频地址 |
| `MediaApp/.../ui/VideoPlayerActivity.kt` | ExoPlayer播放器 |
| `MediaApp/.../network/DayCardApiService.kt` | 天卡API接口定义 |
| `MediaApp/.../viewmodel/PaymentViewModel.kt` | 天卡支付流程 |
| `MediaApp/.../config/DayCardConfig.kt` | 天卡配置(设备ID、金额等) |
| `MediaApp/.../config/ApiConfig.kt` | 媒资API配置 |
### as-daycard-openapi(本项目后端)
| 文件 | 作用 |
|------|------|
| `.../daycard/controller/DayCardOrderController.java` | 天卡订单Controller(创建/状态/权益) |
| `.../daycard/service/impl/DayCardOrderServiceImpl.java` | 订单核心逻辑(createOrder/payCallback/asyncActivateCard/getDeviceBenefits) |
| `.../daycard/service/impl/DayCardPoolServiceImpl.java` | 天卡池管理(导入/抢卡) |
| `.../jst/service/impl/JushitingApiServiceImpl.java` | 橘视厅API调用(getToken/activateCard/queryVipExpireTime) |
| `.../jst/service/JstActivateCardResult.java` | 卡密激活结果 |
| `.../device/controller/DeviceController.java` | 设备管理 |
| `.../media/controller/MediaController.java` | 媒资/播放权限 |
### 生产环境参考代码
| 文件 | 作用 |
|------|------|
| `app/.../request/AsApiRequester.java` | 生产环境AS接口(init/check/checkBenefits/activate) |
| `app/.../utils/JetsenxSdkUtil.java` | 生产环境SDK工具类 |
| `app/.../video/VideoDetailActivity.java` | 生产环境详情页(完整播放流程) |
| `app/.../MainActivity.java` | 生产环境主页(SDK初始化入口) |
| `app/.../config/AppConfiguration.java` | 生产环境配置(AS基础地址) |