Files
daycard-app/卡密导入激活鉴权流程.md

243 lines
11 KiB
Markdown
Raw Normal View History

# 卡密-导入-激活-鉴权 完整流程文档
## 一、整体流程概览
```
┌─────────────────────────────────────────────────────────────────────┐
│ 一次性前置准备 │
│ ① 导入卡密 ② 配置橘视厅平台参数 ③ 注册设备 │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ 用户运行时流程 │
│ ④ 点击播放 → ⑤ 创建订单 → ⑥ 支付 → ⑦ 支付回调触发异步激活 │
│ ↓ │
│ ⑧ 橘视厅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基础地址) |