feat: 全局数据隔离 + 响应结构统一 + 交接文档

1. 新增SysOrgService业务数据隔离工具方法(applyBusinessOrgScope/getUserOrgMpath)
2. 10个Controller接入组织数据隔离(设备/订单/退款/统计/定价/支付配置/激活码/用户)
3. 统一响应结构:写操作从R.ok()改为R.dataSuccess(data)
4. 修复5处data.data双层嵌套
5. 修复SysUserController.info()不一致
6. 修复循环依赖(@Lazy)
7. 新增交接文档HANDOVER.md
This commit is contained in:
zhaogd
2026-08-11 15:50:36 +08:00
parent 8385196b38
commit 03e5f6fbf2
34 changed files with 869 additions and 257 deletions

View File

@@ -1,95 +1,285 @@
# 后端项目交接文档
# as-daycard-openapi 项目交接文档
## 项目信息
- 路径:/Users/zhaoguodong/Desktop/as-daycard-openapi
- 启动:`./start.sh -Dspring-boot.run.profiles=dev`(端口8084,JDK17路径在start.sh里)
- 本地打包:`export JAVA_HOME=/Users/zhaoguodong/Library/Java/JavaVirtualMachines/jbr-17.0.14/Contents/Home && mvn clean package -DskipTests`
- MySQL:/opt/homebrew/Cellar/mysql@8.0/8.0.42/bin/mysql -uroot -pr2356510 dev-0418
- 接口文档:API_DOC.md(完整)+ THIRD_PARTY_API.md(第三方对接)
- DDL:db/full-schema.sql(52个表,完整表结构,从dev-0418导出)
## 一、项目概述
## 云服务器部署
- 服务器:宝塔面板,IP见用户确认
- 数据库:jstopenapi(已建库+导入full-schema.sql+schedule-timeout-task.sql)
- MySQL权限:设为"所有人"(允许Docker容器通过host.docker.internal访问)
- Docker部署:docker-compose.yml已配好(DB_URL/REDIS_HOST用host.docker.internal,JWT/API签名密钥已生成)
- application-prod.yml:已创建(和dev相同配置,环境变量由docker-compose传入)
- Dockerfile:已改为multi-stage(JDK17),但服务器上用的是简单版(本地打包jar上传+COPY)
- 部署方式:本地mvn打包 -> scp上传jar -> docker-compose build/up
- **已知问题**:MySQL的max_connect_errors封锁Docker容器IP,需 `mysql -uroot -p -e "FLUSH HOSTS;"` 解除后重启容器
- **部署状态**:jar已上传,application-prod.yml已包含,正在解决MySQL连接封锁问题
天卡(24小时制视频权益卡)开放API平台,为酒店电视设备提供天卡购买、支付、激活、权益管理的完整能力。设备端通过本平台完成微信支付购买天卡,支付成功后异步调用橘视厅接口激活权益,设备获取 `thirdCardNo` 凭证后直连橘视厅SDK播放内容。
## 已完成的功能
**技术栈**: Java 8 / Spring Boot 2.6.6 / MyBatis-Plus / MySQL / Redis / Shiro + JWT / Quartz / wechatpay-java SDK 0.2.14
### 1. 橘视厅媒资接口(/manage/jetsen-media/**,JWT鉴权)
- catalog-tree/list/search/export/nav/tags
- 栏目树(一级+二级),list必须用二级栏目id(如472=3221条)
- orderinfo:"1"必须带
**部署**: Docker (eclipse-temurin:17-jre-alpine),端口 8084,Spring Profile = prod
### 2. 第三方开放接口(/openapi/partner/**,appKey签名鉴权)
- PartnerSignFilter:MD5(appKey+timestamp+nonce+appSecret).toUpperCase(),5分钟有效+nonce防重放
- 媒资接口(/openapi/partner/media/):package/catalog-tree/list/search
- 三级授权:CONTENT(单部内容)>CATALOG(栏目)>FULL(全量),按open_api_content_package配置判定
- 天卡代销(/openapi/partner/card/):consume(消耗天卡绑定设备)/stock(库存)
- 调用日志:open_api_call_log
---
### 3. 代销模式
- 采购:POST /manage/partner-card/purchase(平台卡池UNUSED->RESERVED,记入partner池子AVAILABLE)
- 消耗:POST /openapi/partner/card/consume(partner池子取1张,绑定设备,RESERVED->USED,异步激活橘视厅)
- 回退:POST /manage/partner-card/revert(AVAILABLE的天卡回退,RESERVED->UNUSED,partner池子DISABLED)
- 免activationCode init:/api/jst/init不传activationCode时按设备标识查找已注册设备(代销模式)
- APK流程:consume消耗天卡 -> APK init(免activationCode) -> checkBenefits拿thirdCardNo -> 播放
## 二、组织架构与数据隔离
### 4. 内容包管理(/manage/content-package/**,JWT鉴权)
- CRUD + fetch-contents(从橘视厅拉内容预览)+ save-from-selection(勾选内容一键保存)
- info/info-by-partner返回contentList(解析后的内容数组)
- save-from-selection支持传id覆盖更新
### 四层组织体系
### 5. 接口风格统一
- 所有接口返回:{code:0, msg:"success", data:{...}}
- APK设备接口原code:200已改0,sign从顶层移入data
- UI接口原retCode:"00000000"已改{code:0,msg,data}
- 管理后台R.ok().put()全部改R.dataSuccess()(数据在data内)
- 路径统一:/sys/管理接口移到/manage/(仅login/refresh/logout保留/sys/)
- 合作伙伴管理从/openapi/partner移到/manage/partner(JWT鉴权)
- 删除冗余DayCardPoolController(和DayCardManageController冲突)
```
平台(Platform) > 渠道(Channel) > 代理商(Agent/SERVICE_PROVIDER) > 门店(Store)
```
### 6. 审计日志
- audit_log表已重建(字段对齐AuditLogEntity)
- 46个@AuditAction审计点(资金/资产/设备/组织写操作)
- AuditLogAspect切面自动记录
- 每个组织节点有 `mpath` 物化路径(如 `1.2.3.`),用于快速判断层级关系
- 用户通过 `sys_user.org_id` 关联到某个组织节点
- `org_id = NULL` 为超级管理员,不限制数据范围
## 数据库关键表
- 合作伙伴:open_api_partner(appKey/appSecret)
- 内容包:open_api_content_package(partnerId/catalogIds/contentCodes)
- partner天卡池:open_api_partner_card_pool(partnerId/poolId/cardCode/status)
- 调用日志:open_api_call_log
- 审计日志:audit_log(已重建)
- 天卡池:day_card_pool(UNUSED->RESERVED->USED->REUSABLE)
- 设备:device(含androidId/manufacturer/model/brand扩展字段)
### 数据隔离机制
## 测试验证状态
- ✅ 媒资接口(4个)+ appKey签名鉴权 + 三级授权过滤
- ✅ 采购天卡 + 消耗天卡 + 回退天卡
- ✅ 内容包配置(fetch-contents + save-from-selection)
- ✅ 禁用合作伙伴 -> 鉴权失败
- ✅ 删除内容包 -> FULL全量fallback
- ✅ 管理后台前端已适配新路径和返回格式
- **隔离工具**: `SysOrgService.applyBusinessOrgScope(wrapper, orgColumn, operatorId)` —— 向业务表 QueryWrapper 注入子查询,按 `sys_org.mpath` 前缀匹配过滤
- **权限校验**: `SysOrgService.isOrgInScope(orgId, operatorId)` —— 判断目标组织是否在操作人范围内
- **已接入隔离的模块**: 设备、订单、退款、统计、定价、支付配置、激活码、用户管理
- **平台级资源不隔离**: 天卡池(全局库存)、系统菜单
## 待处理事项
1. **服务器部署**:解决MySQL连接封锁后重启Docker容器,验证启动成功
2. **直营模式**(方式2):EPG调开放订单接口 -> 微信支付 -> 自动发卡(未开发)
3. **旧代码清理**:DayCardSettleController(旧分账)/MediaController(桩)/ContractServiceImpl(桩)等废弃代码
4. **Nginx反代+HTTPS**:宝塔配反代,第三方用域名调用
5. **前端菜单**:合作伙伴管理/内容包管理/天卡池管理的菜单页面
---
## 关键配置
- docker-compose.yml:DB_URL=jstopenapi, REDIS_HOST=host.docker.internal, JWT_SECRET=2F064BA10037477FBB3A1BF9D508AE96, API_SIGN_SECRET=5F2A2DD8BFBA4DA1B87EFBB405671B3D
- application-prod.yml:和dev相同,环境变量传入
- Druid wall filter:dev和prod都禁用(不兼容SKIP LOCKED)
- Lombok:1.18.24,必须用JDK17编译(JDK21+不兼容)
## 三、核心业务流程
## 合作伙伴测试数据(本地dev-0418)
- 华住酒店:partnerId=0f91ea23961e4983, appKey=b9f9542e1e044466, appSecret=d117d67429b941c1bc0e0f6044e39d62
- 测试合作伙伴:partnerId=1, appKey=71df57e7e47a4a88, appSecret=512f147432124bb48b313365ee31b87b
- 内容包:id=3, name=高分3部, catalogIds=472, contentCount=3, CONTENT级别
### 3.1 设备初始化与鉴权
```
设备 App 启动
-> POST /api/jst/init (body: activationCode + mac/deviceSn/deviceUid)
-> 后端创建/更新设备记录,生成 sign
-> 返回 { sign, deviceId }
-> 后续所有请求在 header 携带 sign
```
- **激活码模式**: 设备通过管理后台生成的门店激活码初始化,绑定到门店组织
- **代销模式**: 第三方合作伙伴的设备免激活码,通过设备标识匹配已注册设备
- **sign 机制**: 后端 `ApiDeviceSignFilter` 验证 sign,解析出 deviceId 放入 request attribute。sign 有时效,可通过 `heartBeat` 续期或 `refreshSign` 刷新
### 3.2 天卡购买流程
```
1. 设备查询权益 -> POST /api/jst/checkBenefits -> UNBOUND(无权益)
2. 设备查询价格 -> POST /api/jst/price -> 返回价格
3. 设备创建订单 -> POST /api/jst/daycard/create
-> Redis 防重锁(按 deviceId)
-> 解析设备组织链路(设备 -> 门店 -> 渠道 -> 代理商)
-> 3层定价匹配(门店 > 渠道 > 平台默认)
-> 插入订单(状态 PENDING)
-> 调微信 Native 下单,获取 code_url
-> SKIP LOCKED 抢卡(UNUSED -> RESERVED)
-> 返回 { orderNo, codeUrl, amount, expireAt }
4. App 用 codeUrl 生成二维码,用户扫码支付
5. 用户取消支付 -> POST /api/jst/daycard/cancel/{orderNo}
-> 校验订单归属 -> 微信关单 -> 释放卡(RESERVED -> UNUSED)-> 订单 CANCELLED -> 清理 Redis 锁
6. 用户支付成功 -> 微信回调 -> 异步激活
```
### 3.3 支付回调与异步激活
```
微信支付回调 -> POST /openapi/payment/wechat/notify
-> 验签(从 header 提取 Wechatpay-Serial/Nonce/Signature/Timestamp,HttpServletRequest.getHeader() 大小写不敏感)
-> 解密获取交易信息
-> 幂等检查(订单已 PAID 则直接返回)
-> 乐观锁更新订单 PAID + 卡状态 RESERVED -> USED
-> 异步激活(@Async):
1. jushitingApiService.getToken(device, thirdCardNo) -> 获取 token + memberCode
2. jushitingApiService.activateCard(deviceId, cardCode, memberCode, token) -> 激活卡密
3. jushitingApiService.queryVipExpireTime(deviceId, thirdCardNo) -> 查询VIP到期时间
4. 更新 day_card_pool: expire_at + activate_status=SUCCESS
-> 清理 Redis 防重锁
```
### 3.4 橘视厅权益映射
```
关键标识 thirdCardNo:
- 卡密导入时自动生成(D + 24位随机字符)
- 存储在 day_card_pool.third_card_no
- 作为橘视厅的 uid 和 mac 参数(mac = thirdCardNo + "_bul_" + businessLine)
- 设备通过 checkBenefits 获取 thirdCardNo
- 设备用 thirdCardNo 直连橘视厅获取 token,激活卡密,获得 VIP 权益
设备与卡的关联:
day_card_pool.device_id -> device.id -> device.mac/device_sn/device_uid
解绑时清除 device_id,卡状态保持 USED(终态不可复用)
```
### 3.5 卡状态流转
```
UNUSED -> RESERVED -> USED(终态)
导入 下单抢卡 支付成功
取消/超时: RESERVED -> UNUSED(释放回池)
退款: USED -> UNUSED(清除 device_id/order_no/expire_at/activate_status)
解绑: USED 保持不变,仅清除 device_id
```
### 3.6 代销模式(合作伙伴)
```
采购: 平台天卡池 UNUSED -> RESERVED,写入 partner_card_pool(status=AVAILABLE)
消耗: POST /openapi/partner/card/consume
-> partner 池取1张 AVAILABLE 卡
-> day_card_pool RESERVED -> USED + device_id
-> partner_card_pool AVAILABLE -> CONSUMED
-> 异步激活(同上)
-> App 后续用同一设备标识调 init + checkBenefits
回退: partner_card_pool AVAILABLE -> DISABLED,平台卡 RESERVED -> UNUSED
```
### 3.7 分账(需微信商户号开通分账权限)
```
支付成功后触发 executeSharing(orderNo):
-> 匹配分润规则(全局/渠道/代理商/门店四级)
-> 计算各方分账金额(比例或固定,非平台方总比例 ≤ 30%)
-> 创建 profit_sharing_order + profit_sharing_detail
-> 调微信 createProfitSharing API(设置 unfreezeUnsplit=true 自动解冻剩余)
-> 调微信 finishProfitSharing 完结分账
注意: 当前分账标记(SettleInfo.profitSharing=true)已注释,需商户号开通分账权限后取消注释
```
---
## 四、接口体系
### 4.1 设备端接口(/api/jst/,sign 鉴权)
| 接口 | 说明 |
|------|------|
| POST /api/jst/init | 设备初始化,返回 sign |
| POST /api/jst/checkBenefits | 查询天卡权益,返回 thirdCardNo |
| POST /api/jst/price | 查询天卡价格 |
| POST /api/jst/daycard/create | 创建天卡订单 |
| GET /api/jst/daycard/status/{orderNo} | 查询支付状态 |
| POST /api/jst/daycard/cancel/{orderNo} | 取消订单 |
| POST /api/jst/heartBeat | 心跳续期 |
| POST /api/jst/refreshSign | 刷新 sign |
### 4.2 管理后台接口(/manage/,JWT 鉴权)
覆盖:系统管理(用户/角色/菜单/组织/门店)、设备管理、天卡池/订单/退款/激活码、定价策略、支付配置、分账规则/记录、统计概览、合作伙伴管理、媒资管理等,共约 100+ 个接口。
### 4.3 合作伙伴接口(/openapi/partner/,appKey/appSecret 签名鉴权)
| 接口 | 说明 |
|------|------|
| POST /openapi/partner/card/consume | 消耗天卡(代销) |
| GET /openapi/partner/card/stock | 查询库存 |
| GET /openapi/partner/media/* | 媒资接口(内容包/栏目树/搜索等) |
### 4.4 微信回调接口(/openapi/payment/,无鉴权)
| 接口 | 说明 |
|------|------|
| POST /openapi/payment/wechat/notify | 支付回调 |
| POST /openapi/payment/refund/notify | 退款回调 |
| GET /openapi/payment/query/{orderNo} | 查询支付状态 |
### 4.5 统一响应结构
```json
{ "code": 0, "msg": "", "data": {} }
```
- code=0 成功,code!=0 失败(500 业务错误,403 权限错误)
- data 始终存在,列表分页用 `data.page`,详情用 `data.{业务字段}`
---
## 五、定价体系
三层维度优先级匹配:门店配置价 > 渠道配置价 > 平台默认价
| 配置层级 | channel_org_id | store_org_id |
|---------|---------------|-------------|
| 平台默认 | NULL | NULL |
| 渠道级 | 具体值 | NULL |
| 门店级 | NULL | 具体值 |
---
## 六、定时任务
| 任务 | 说明 |
|------|------|
| DayCardOrderTimeoutTask | 取消超时未支付订单(15分钟),释放卡 |
| DayCardActivateRetryTask | 重试激活失败的天卡 |
| ProfitSharingQueryTask | 查询分账状态,更新分账单 |
---
## 七、橘视厅平台对接
### 平台级凭证(用于内容/UI接口,非设备级)
```
SP_TOKEN = "9006874392099a89506142c6df9e3314"
KEY = "alltvjs"
PORTAL_CODE = "PCODE-JC"
sign = MD5(sptoken + timeshift + portalcode + key).toUpperCase()
```
App 用此凭证直连橘视厅 `/IPTVPortalInterface/` 下的接口(topButton、gettagslist、getcontentlist、search 等),获取内容和 UI。**不经过我们后端**。
### 设备级权益凭证(thirdCardNo)
- 由后端在卡密导入时自动生成
- 设备通过 `checkBenefits` 获取
- 用于橘视厅免密登录(getToken)和卡密激活(activateCard)
- `getToken` 的 uid 和 mac 参数都基于 thirdCardNo 构造,设备实体不传给橘视厅
### 橘视厅 API 基地址
- 正式环境: `https://api.jetsenx.huashi.tv/`
- 测试环境: `https://external.jetsenx.huashi.tv/`
- 媒资接口: `https://servhub-ep03.future.jetsentv.com/`
---
## 八、微信支付配置
- 支持多商户号配置(DB `wechat_pay_config` 表,四级优先级:门店 > 代理商 > 渠道 > 全局)
- DB 配置优先,回退到 yml 环境变量
- SDK 配置实例按 商户号+证书序列号+密钥+私钥 缓存,配置变更自动重建
- 回调验签支持多商户号(遍历所有 ACTIVE 配置逐个尝试解析)
---
## 九、待办与注意事项
1. **分账功能**: 代码已实现但分账标记(`SettleInfo.profitSharing=true`)已注释,需微信商户号开通分账权限后取消注释(WechatPayServiceImpl 和 DayCardOrderServiceImpl.payCallback 中)
2. **退款SDK**: `WechatPayServiceImpl.refund()` 目前是骨架实现(TODO 接入 wechatpay-java RefundService)
3. **设备标识改造**: 当前卡池用 `device_id`(Long, 自增) 关联设备,计划改为 `device_uid`(String) 关联,已搁置
4. **天卡池不隔离**: 天卡池为平台级库存资源,不按组织隔离,所有管理员可见
5. **媒资接口分页不统一**: 3个媒资相关端点用 `data.list` + `data.totalCount` 平铺式分页,与其他接口的 `data.page` 不同
6. **支付接口路径**: 当前支付接口在 `/openapi/payment/` 下,计划迁移到 `/payment/` 与第三方接口区分(未实施)
7. **密码哈希**: Shiro `Sha256Hash(password, salt)` 哈希顺序为 `salt + password`(不是 password + salt)
---
## 十、数据库与部署
### 数据库
- MySQL 8.0,数据库名 `jstopenapi`
- 完整建表脚本: `db/full-schema.sql`
- 初始化数据: `db/init-sys-data.sql`(菜单 + 角色 + 超管用户 admin/admin)
- 逻辑删除: `delete_flag` 字段(UNDEL/DEL)
### Redis
- 用于:设备 sign 缓存、防重锁、橘视厅 token 缓存、激活锁
- Key 前缀: `daycard:device:`(订单防重)、`api:thirdparty:activate:`(激活相关)
### 部署
```bash
# 构建
mvn clean package -DskipTests
# Docker 部署
docker-compose up -d
# 关键环境变量(docker-compose.yml 中配置)
SPRING_PROFILES_ACTIVE=prod
DB_URL / DB_USERNAME / DB_PASSWORD
REDIS_HOST / REDIS_PORT / REDIS_PASSWORD
JWT_SECRET / API_SIGN_SECRET
WECHAT_PAY_* # 微信支付配置(或通过 DB wechat_pay_config 表配置)
```
### 超管账号
- 用户名: admin / 密码: admin
- 新增超管: wangrui / 密码: Admin@2026.