Files
daycard-app/MediaApp核心业务逻辑文档.md
2026-07-26 12:43:59 +08:00

18 KiB
Raw Permalink Blame History

MediaApp 核心业务逻辑文档

一、系统架构

┌─────────────────────────────────────────────────────────────┐
│                      MediaApp (Android)                      │
│                                                             │
│  HomeActivity ──→ DetailActivity ──→ VideoPlayerActivity    │
│       │               │                    │                 │
│       │               ├─ PaymentViewModel  │                 │
│       │               ├─ JetsenxSdkUtil    │                 │
│       │               └─ DayCardApiService  │                │
└───────┼───────────────┼────────────────────┘                 │
        │               │                                      │
        ▼               ▼                                      ▼
┌──────────────────────────────┐  ┌───────────────────────────┐
│   as-daycard-openapi (后端)   │  │  橘视厅/捷成平台            │
│   端口: 8084                  │  │  api.future.jetsentv.com  │
│                              │  │                           │
│   ├─ 天卡订单管理              │  │  ├─ UMS用户系统            │
│   ├─ 天卡池管理                │  │  ├─ 卡密激活              │
│   ├─ 微信/Mock支付            │  │  ├─ VIP订购关系           │
│   ├─ 异步卡密激活              │──│──→  ├─ 视频地址分发        │
│   ├─ 权益查询(thirdCardNo)    │  │  └─ 媒资管理              │
│   └─ 媒资/播放权限             │  │                           │
│                              │  │                           │
│   数据库: MySQL (dev-0418)    │  │                           │
│   缓存: Redis                 │  │                           │
└──────────────────────────────┘  └───────────────────────────┘

二、完整播放流程

流程图

用户点击播放
    │
    ▼
┌─────────────────────────────────────────────────────────┐
│ Step 1: 检查天卡播放权限                                 │
│ App → GET /openapi/media/play-permission/{mediaId}      │
│       ?deviceId=1                                       │
│ 后端: 查 day_card_pool 表                                │
│   status=USED AND expire_at > NOW()                     │
│                                                         │
│ ┌─ 有权限 ──→ HasPermission ──→ 跳到 Step 3             │
│ └─ 无权限 ──→ 进入支付流程                               │
└─────────────────────────────────────────────────────────┘
    │ (无权限)
    ▼
┌─────────────────────────────────────────────────────────┐
│ Step 2: 天卡支付流程                                     │
│                                                         │
│ 2a. 创建订单                                             │
│     App → POST /openapi/daycard/create                  │
│     后端: Redis防重锁 → SELECT FOR UPDATE SKIP LOCKED    │
│           抢一张UNUSED卡 → 卡状态UNUSED→RESERVED         │
│           → 创建订单(CREATED)                            │
│                                                         │
│ 2b. 支付                                                 │
│     App → POST /openapi/payment/mock-pay/{orderNo}      │
│     后端: 订单→PAID, 卡→USED, expire_at=24h兜底          │
│           → @Async asyncActivateCard()                   │
│                                                         │
│ 2c. 轮询支付状态                                         │
│     App → GET /openapi/daycard/status/{orderNo}         │
│     直到返回 PAID                                        │
└─────────────────────────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────────────────────────┐
│ Step 2d: 后端异步激活卡密 (3步HTTP调用橘视厅API)          │
│                                                         │
│ ① getToken(device, thirdCardNo)                         │
│    POST api.future.jetsentv.com/ums_api/api/v1/         │
│         third/login                                     │
│    参数: uid=thirdCardNo, mac, accesser, businessLine   │
│    签名: RSA(jushitingLoginRsaPublicKey)                 │
│    返回: token + memberCode                              │
│                                                         │
│ ② activateCard(deviceId, cardCode, memberCode, token)   │
│    POST api.future.jetsentv.com/ccms_api/hswj/api/      │
│         card/activate                                   │
│    参数: activateCode=cardCode, memberCode              │
│    签名: MD5(activateCode&memberCode&timestamp&key)      │
│    返回: status=true + expireTime                        │
│    ★ 此时橘视厅平台建立了 thirdCardNo→用户→订购关系       │
│                                                         │
│ ③ queryVipExpireTime(thirdCardNo)  [仅②未返回expireTime] │
│    POST api.future.jetsentv.com/ums_api/member/uid/     │
│         queryVipInfo                                    │
│    返回: vipExpireTime                                   │
│                                                         │
│ 激活完成: day_card_pool.activate_status=SUCCESS          │
│           expire_at = 真实到期时间                       │
└─────────────────────────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────────────────────────┐
│ Step 3: 查询天卡权益获取 thirdCardNo                     │
│ App → GET /openapi/daycard/benefits/{deviceId}          │
│ 后端: 查 day_card_pool                                  │
│   status=USED AND expire_at > NOW()                     │
│ 返回: { thirdCardNo, hasBenefits, cardCode, expireAt }  │
└─────────────────────────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────────────────────────┐
│ Step 4: SDK初始化 (用 thirdCardNo 作为用户身份)          │
│ JetsenxSdkUtil.init(ctx, thirdCardNo, callback)         │
│                                                         │
│ SDK内部: GET api.future.jetsentv.com/ums_api/openapi/   │
│              init?uid=thirdCardNo&utype=2               │
│ 参数: agcode, accesser=JSTASJD, mac, tenant=huashitv    │
│       secret, serviceComboCode                          │
│ 返回: memberCode (用于后续视频地址获取)                   │
└─────────────────────────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────────────────────────┐
│ Step 5: 获取视频地址                                     │
│ JetsenxSdkUtil.getAllVideoUrl(parentContentType,         │
│                               programCode)              │
│                                                         │
│ SDK内部流程:                                             │
│ 5a. getAllAssetPrices → 获取 productCode                │
│ 5b. parentContentType=="8" → getFreeVideoUrls (免费)    │
│     parentContentType!="8" → getVideoUrls (付费)        │
│ 5c. getVideoUrls:                                       │
│     GET api.future.jetsentv.com/bms_api/openapi/        │
│         orderAuth?memberCode&uid&productCode&programCode│
│     有订购→返回VideoInfo[] (1080p/720p地址)              │
│     无订购→-110错误                                      │
│ 5d. -110时回退getFreeVideoUrls                           │
└─────────────────────────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────────────────────────┐
│ Step 6: 播放                                            │
│ 跳转 VideoPlayerActivity                                │
│ ExoPlayer 播放视频URL                                   │
└─────────────────────────────────────────────────────────┘

三、防重复消费机制

三层防护确保不会浪费卡密

防护层 机制 位置 效果
第1层 App端 checkPermission PaymentViewModel Step1 24小时内重复点击不创建订单
第2层 后端 Redis防重锁 createOrder() 第1步 同一设备订单超时时间内只能一个订单
第3层 定时任务释放超时订单 DayCardOrderTimeoutTask 订单超时未支付,卡密回收为UNUSED

权益有效期

  • 天卡支付成功后获得 24小时 播放权益
  • 期间无限次播放不额外消耗卡密
  • 权益过期后再次播放需要重新支付

四、关键配置项

4.1 SDK配置 (JetsenxSdkUtil.kt)

参数 值 说明
AAR包 JetsenxSdk-api-future-release-v1.23.aar 生产域名 api.future.jetsentv.com
AGCODE 1100101010200492358120169 渠道号
TENANT huashitv 租户
ACCESSER JSTASJD 合作方标识(生产环境)
SECRET kMeWsBu0QsTXpnft 秘钥(生产环境)
SERVICE_COMBO_CODE 1100121010200494755850172 业务组合编码

4.2 后端橘视厅平台配置 (sys_config: JST_PLATFORM_CONFIG)

参数 值
jushitingBaseUrl https://api.future.jetsentv.com
vipQueryBaseUrl https://api.future.jetsentv.com
jushitingTenant huashitv
jushitingAccesser JSTASJD
jushitingBusinessLine 4100450011381755998420012
jushitingServiceComboCode 1100121010200494755850172
jushitingLoginRsaPublicKey (RSA公钥)
jushitingActivateSignKey hhDEHY1qC4r
vipQuerySignKey c25svtwbTyCE

4.3 thirdCardNo 生成规则

项目 规则 示例
as-daycard-openapi D + 24位随机(0-9A-Za-z) D00BB8E623D9280D1ACABE803
as-partner-backend 24位随机(0-9A-Za-z) A3f9Kp2m...(无前缀)

D 前缀确保两个项目的 thirdCardNo 空间完全隔离,不会碰撞。


五、关键文件索引

MediaApp (Android端)

文件 作用
ui/DetailActivity.kt 详情页,播放流程入口
ui/VideoPlayerActivity.kt ExoPlayer播放器
ui/HomeActivity.kt 首页,媒资列表
sdk/JetsenxSdkUtil.kt SDK封装:初始化+获取视频地址
network/DayCardApiService.kt 天卡后端API接口定义
network/DayCardRetrofitClient.kt Retrofit客户端
viewmodel/PaymentViewModel.kt 天卡支付流程状态机
viewmodel/DetailViewModel.kt 详情页数据
model/daycard/DayCardModels.kt 天卡数据模型
config/DayCardConfig.kt 天卡配置(设备ID/金额)
config/ApiConfig.kt 媒资API配置

as-daycard-openapi (后端)

文件 作用
daycard/controller/DayCardOrderController.java 订单/权益接口
daycard/service/impl/DayCardOrderServiceImpl.java 订单核心逻辑+异步激活
daycard/service/impl/DayCardPoolServiceImpl.java 天卡池管理+thirdCardNo生成
daycard/task/DayCardOrderTimeoutTask.java 超时订单清理
jst/service/impl/JushitingApiServiceImpl.java 橘视厅API调用
media/service/impl/MediaServiceImpl.java 播放权限检查
openapi/filter/PartnerSignFilter.java 签名鉴权过滤器
openapi/sign/impl/PartnerSignServiceImpl.java HMAC-SHA256签名

六、项目环境信息

6.1 目录结构

/Users/zhaoguodong/Desktop/
├── jiecheng_business/              # 工作目录(本项目)
│   ├── MediaApp/                   # Android客户端 (Kotlin)
│   ├── MediaApp核心业务逻辑文档.md   # 本文档
│   └── README.md
├── as-daycard-openapi/             # 本项目后端 (Spring Boot, 端口8084)
│   └── src/main/resources/mapper/sys/SysConfigDao.xml  # ★手动创建的MyBatis映射
└── as-partner-backend/             # 生产环境后端 (仅参考,端口8083,不在本项目中)

6.2 构建环境

项目 构建命令 说明
MediaApp JAVA_HOME=.../jbr-17.0.14 && cd MediaApp && ./gradlew assembleDebug APK输出: MediaApp/app/build/outputs/apk/debug/app-debug.apk
as-daycard-openapi cd as-daycard-openapi && mvn spring-boot:run -Dspring-boot.run.profiles=dev 端口8084
JAVA_HOME /Users/zhaoguodong/Library/Java/JavaVirtualMachines/jbr-17.0.14/Contents/Home JDK 17
安装到设备 adb install -r app-debug.apk

6.3 后端数据库

项目 值
MySQL 127.0.0.1:3306
数据库 dev-0418
用户/密码 root / r2356510
mysql路径 /opt/homebrew/Cellar/mysql@8.0/8.0.42/bin/mysql
Redis 127.0.0.1:6379 database 0

6.4 API基础地址

项目 地址
天卡后端 (as-daycard-openapi) http://172.16.100.67:3002 (通过nginx代理到8084)
橘视厅平台 https://api.future.jetsentv.com
媒资API 橘视厅SDK内部调用,域名同上

6.5 测试设备

项目 值
deviceId 1 (数据库device表id=1)
MAC CC:8C:DA:C6:E9:9B
测试卡密 cardCode=4009B1C3926DF480, thirdCardNo=D00BB8E623D9280D1ACABE803
卡密状态 USED, activate_status=SUCCESS, expire_at=2026-07-14 18:23:58
合作伙伴 partnerId=1, appKey=71df57e7e47a4a88

七、API签名鉴权机制

App与天卡后端的所有 /openapi/** 接口(除 /openapi/partner/** 和 /openapi/payment/notify)都需要签名鉴权:

签名格式: Base64URL(appKey:timestamp:nonce:HMAC-SHA256(appKey:timestamp:nonce, appSecret))

请求头:

Header 说明
X-App-Key appKey
X-Timestamp 毫秒时间戳
X-Nonce 随机字符串
X-Sign 签名值

校验规则: HMAC比对 + 时间戳5分钟有效期 + Redis nonce防重放


八、PaymentViewModel 状态机

Idle
  ↓ startPaymentFlow()
CheckingPermission  ──→ HasPermission ──→ fetchVideoUrlAndPlay()
  ↓ (无权限)
CreatingOrder  ──→ OrderCreated  ──→ (用户确认支付)
  ↓
Paying  ──→ Polling  ──→ PaymentSuccess ──→ fetchVideoUrlAndPlay()
  ↓ (任意失败)                ↓
Error                       Error

防重复跳转: DetailActivity.isFetchingVideoUrl 标志位,在 HasPermission/PaymentSuccess 时置true,跳转播放器后不会因 repeatOnLifecycle(STARTED) 重新触发。点击播放按钮时重置为false。


九、已知限制与待办

项目 说明
设备ID硬编码 DayCardConfig.DEVICE_ID = 1L,多设备需要改为动态获取
Mock支付 当前用 mock-pay 接口测试,生产需切换微信支付
单内容测试 仅验证了单个媒资播放,未测试列表翻页、搜索等
签名证书 MediaApp未配置release签名,打release包时需要配置
后端签名校验 App端API调用需实现HMAC签名(当前可能在Retrofit拦截器中)

十、踩坑记录

问题 原因 解决方案
详情页闪退 onCreate中调用SDK初始化,native库加载异常 改为点击播放时按需初始化
16KB对齐警告 exoplayer-ffmpeg-extension中的.so未对齐 移除未使用的FFmpeg扩展库
NoClassDefFoundError: LogProducerConfig 缺少火山引擎TLS日志库 添加tls-android-producer依赖
支付对话框闪退 HasPermission时关闭对话框后SDK回调无法显示 保持对话框打开直到视频地址获取完成
SDK返回-110未订购 uid="test_user"无效 从benefits接口获取真实thirdCardNo
后端激活失败: SysConfigDao.queryByKey not found 缺少MyBatis XML映射 创建SysConfigDao.xml
SDK返回604 SDK用external域名(测试),后端用api域名(生产) 换用api-future AAR包
memberCode不一致 两个域名指向不同服务器,用户数据不互通 统一使用api.future.jetsentv.com