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