post-bank-point-mall/docs/需求文档-PRD.md

1944 lines
78 KiB
Markdown
Raw Permalink 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.

# 虚拟商品充值服务商系统 - 产品需求文档
## 接口与任务索引
### 对外提供的接口(被邮储银行调用)
| 接口名称 | 说明 | 文档位置 |
|---------|------|----------|
| 服务预约接口 | 接收预约请求,获取券码链接 | [3.1.1](#311-服务预约接口) |
| 服务取消接口 | 接收取消请求,作废券码 | [3.1.2](#312-服务取消接口) |
| 订单过期积分查询接口 | 查询过期幸福点信息 | [3.1.4](#314-订单过期积分查询接口) |
| 服务详情查询接口 | 获取券码链接展示服务详情 | [3.1.6](#316-服务详情查询接口) |
| 对账文件查询接口 | 查询可用的对账文件列表 | [3.4.3](#343-对账文件查询接口) |
| 对账文件下载接口 | 根据文件ID下载对账文件 | [3.4.4](#344-对账文件下载接口) |
| 批量补推接口 | 手动批量触发服务完成通知 | [3.4.5](#345-批量补推接口) |
### 调用下游的接口调用邮储银行API
| 接口名称 | 说明 | 文档位置 |
|---------|------|----------|
| 权益码有效性验证接口 | 验证权益码是否过期 | [3.1.1](#311-服务预约接口) |
| 服务预约接口 | 发起预约请求 | [3.1.1](#311-服务预约接口) |
| 预约服务状态查询接口 | 查询邮储侧服务状态 | [3.1.2](#312-服务取消接口) |
| 预约取消接口 | 发起取消请求 | [3.1.2](#312-服务取消接口) |
| 服务完成接口 | 通知邮储服务完成 | [3.1.3](#313-服务完成通知接口) |
| 过期积分查询接口 | 查询订单退还时已过期积分 | [3.1.4](#314-订单过期积分查询接口) |
### 调用上游的接口调用蓝色兄弟API
| 接口名称 | 说明 | 文档位置 |
|---------|------|----------|
| 券码获取接口 | 获取虚拟商品券码 | [3.2.1](#321-券码获取功能) |
| 券码查询接口 | 查询券码当前状态 | [3.2.2](#322-券码查询功能) |
| 券码作废接口 | 作废待使用/已过期券码 | [3.2.3](#323-券码作废功能) |
| 券码状态回调 | 接收券码状态变更通知 | [3.2.4](#324-券码状态回调接收) |
### 定时任务
| 任务名称 | 执行频率 | 说明 | 文档位置 |
|---------|----------|------|----------|
| 券码状态同步 | 每小时 | 主动查询券码状态,创建对应处理任务 | [6.2](#62-券码状态同步流程) |
| 通知任务重试 | 每5分钟 | 扫描失败任务,按退避策略重试 | [6.2.1](#621-通知任务执行流程) |
| 失败任务告警 | 每10分钟 | 扫描超过最大重试次数的任务,发送告警(不再本系统实现,外部有监控系统,在监控系统中实现) | [5.2.5](#525-数据一致性监控) |
### 异步通知任务
| 任务类型 | task_type | 触发来源 | 处理逻辑 |
|----------|-----------|----------|----------|
| 服务完成通知 | 1 | 回调/主动查询 | 调用邮储服务完成接口 → 订单状态改为300 |
| 过期处理 | 2 | 主动查询 | 调用邮储预约取消接口 → 订单状态改为500 |
| 作废补偿 | 3 | 主动查询 | 调用邮储预约取消接口 → 订单状态改为400 |
### 订单状态流转
```
100(未预约) → 200(已预约) → 300(已完成)→ 400(已取消) → 500(已过期)
```
---
## 1. 功能模块划分
### 1.1 模块结构
```
服务商系统
├── 接口服务模块(对接邮储银行服务开放平台)
│ ├── 服务预约接口
│ ├── 服务取消接口
│ ├── 服务完成通知接口
│ ├── 订单过期积分查询接口
│ └── 服务状态查询接口
├── 券码管理模块对接蓝色兄弟营销开放API
│ ├── 券码获取功能
│ ├── 券码查询功能
│ ├── 券码作废功能
│ └── 券码状态回调接收
├── 订单管理模块
│ ├── 订单创建
│ ├── 订单状态管理
│ ├── 订单查询
│ └── 订单对账
├── 对账管理模块
│ ├── 对账文件查询
│ ├── 对账文件下载
│ └── 对账数据核对
└── 安全与加密模块
├── SM2/SM4/AES加解密邮储银行
├── RSA/AES加解密蓝色兄弟
└── 签名验签
```
### 1.2 模块关系
| 模块 | 调用方/被调用方 | 内部依赖 | 说明 |
|------|----------------|---------|------|
| 接口服务模块 | 被下游邮储银行调用 | 订单管理模块、券码管理模块 | 接收邮储银行请求,转发至内部处理 |
| 券码管理模块 | 调用上游蓝色兄弟API | 订单管理模块 | 负责券码全生命周期管理 |
| 订单管理模块 | - | 券码管理模块、对账管理模块 | 核心业务数据管理 |
| 对账管理模块 | 调用下游邮储银行API | 订单管理模块 | 月度对账处理 |
| 安全与加密模块 | - | 所有模块 | 提供安全基础能力 |
---
## 2. 公共响应规范
### 2.1 响应结构
所有接口统一使用以下响应结构:
```json
{
"code": 0,
"msg": "成功",
"traceId": "请求追踪ID",
"data": {
// 业务数据
}
}
```
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| code | int | M | 公共响应码0表示成功非0表示失败 |
| msg | String | M | 响应描述信息当code!=0时为错误信息 |
| traceId | String | M | 请求追踪ID用于问题排查。生成规则使用雪花算法或UUID确保全局唯一 |
| data | Object | C | 业务数据,成功时返回 |
### 2.3 时间格式规范
系统内部统一使用 `yyyyMMddHHmmss` 格式存储和传输时间字段。与上游蓝色兄弟系统交互时需进行格式转换(蓝色兄弟使用 `yyyy-MM-dd HH:mm:ss` 格式)。
### 2.2 公共响应码
| 响应码 | 描述 | 说明 |
|-------|------|------|
| 0 | 成功 | 业务处理成功 |
| 1 | 系统异常 | 系统内部错误,请稍后重试 |
| 2 | 参数错误 | 请求参数校验失败 |
| 3 | 签名错误 | 签名验证失败 |
| 4 | 业务错误 | 业务规则校验失败 |
---
## 3. 功能详细说明
### 3.1 接口服务模块
#### 3.1.1 服务预约接口
**功能描述**:接收邮储银行服务开放平台的预约请求,为客户预约虚拟商品充值服务
**业务规则**
1. 查询数据库验证服务编码(type)是否存在(数据库中维护邮储服务编码与营销平台活动的映射关系)
- 如果不存在:返回"暂不支持此服务"
- 如果存在获取对应的AES加密密钥(aes_key)和向量(aes_iv),用于后续解密
2. 使用从数据库查询到的aes_key和aes_iv对request进行AES解密解析出type(服务编码)、code(权益码)、mac(客户id)
- 解密失败:返回"参数错误"
3. 基于权益码(code)获取分布式锁,防止重复请求:
- 获取锁成功:继续执行后续流程
- 获取锁失败:返回"请勿重复提交"
4. 对code(权益码)进行幂等验证:
- 若订单状态为「服务已预约(200)」,直接返回预约成功(与正常请求返回一致)
- 若订单状态为「服务未预约(100)」,继续执行后续流程(更新订单,不新增)
- 若订单状态为「服务已取消(400)」,返回"服务已取消"
- 若订单状态为「服务已完成(300)」,返回"服务已生效"
- 若订单状态为「服务已过期(500)」,返回"服务已过期"
5. 请求邮储服务开放平台「高端客户权益积分预约服务状态查询接口」验证权益码是否过期
如果过期:
1、CRM渠道返回 "二维码20分钟内有效现已超时失效请联系理财经理重新生成二维码
2、手机银行渠道返回 "当前页面停置时间过长,请您重新进入该页面"
6. 创建/更新订单记录(订单状态:服务未预约)
7. 请求邮储服务开放平台接口发起预约:
- 预约成功或返回161010(交易已预约)继续执行步骤8
- 其他情况:订单状态保持「服务未预约(100)」,返回预约失败
8. 请求蓝色兄弟营销平台获取券码链接(使用相同交易号和参数请求):
- 请求成功:更新订单状态为「服务已预约(200)」,返回预约成功响应
- 请求异常:订单状态保持「服务未预约(100)」,返回预约失败
9. 释放分布式锁
**输入**
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| type | String | M | 服务编码用于查询数据库获取AES解密密钥 |
| request | String | M | AES加密字符串解密后格式type=服务编码&code=权益码&mac=加密后的客户id |
| channel | String | M | 渠道标识12-手机银行17-电话银行41-CRM零售 |
| tranChnl | String | M | 渠道标识12-手机银行17-电话银行41-CRM零售 |
| backUrl | String | C | 返回手机银行的地址,可为空 |
| instType | String | M | 客户自营代理属性01-自营02-代理 |
| provinceInstNo | String | M | 客户归属一分机构号 |
**输出**data字段内容
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| url | String | M | 蓝色兄弟营销平台的兑换地址 |
| tranChnl | String | M | 渠道标识 |
| instType | String | M | 客户自营代理属性 |
| backUrl | String | C | 返回手机银行的地址 |
**验收标准**
- [ ] 入参type服务编码为必填字段缺失时返回"参数错误"
- [ ] 根据入参type查询数据库获取对应的aes_key和aes_iv
- [ ] 服务编码(type)不存在时,返回"暂不支持此服务"在AES解密之前校验
- [ ] 使用数据库查询到的aes_key和aes_iv能正确解密AES加密的request参数
- [ ] 能正确解析解密后的参数type、code、mac
- [ ] 幂等验证:相同权益码(code)状态为「服务已预约(200)」时,直接返回预约成功
- [ ] 幂等验证:相同权益码(code)状态为「服务未预约(100)」时,继续执行后续流程
- [ ] 幂等验证:相同权益码(code)状态为「已取消(400)」时,返回"服务已取消"
- [ ] 幂等验证:相同权益码(code)状态为「已完成(300)」时,返回"服务已生效"
- [ ] 幂等验证:相同权益码(code)状态为「已过期(500)」时,返回"服务已过期"
- [ ] 权益码过期且渠道为CRM(41)时,返回"二维码20分钟内有效现已超时失效请联系理财经理重新生成二维码"
- [ ] 权益码过期且渠道为手机银行(12)时,返回"当前页面停置时间过长,请您重新进入该页面"
- [ ] 订单创建/更新时初始状态为「服务未预约(100)」
- [ ] 邮储预约成功或返回161010(交易已预约)时,继续获取券码
- [ ] 邮储预约失败时,订单状态保持「服务未预约(100)」,返回预约失败
- [ ] 蓝色兄弟券码获取成功后,订单状态更新为「服务已预约(200)」
- [ ] 蓝色兄弟券码获取异常时,订单状态保持「服务未预约(100)」,返回预约失败
- [ ] 预约成功后返回蓝色兄弟兑换地址(url)
- [ ] 响应正确透传tranChnl、instType、backUrl字段
- [ ] 并发请求时,基于权益码获取分布式锁,获取失败返回"请勿重复提交"
**测试用例**
| 用例编号 | 测试场景 | 前置条件 | 输入数据 | 预期结果 |
|---------|---------|---------|---------|----------|
| TC-3.1.1-001 | 正常预约成功 | 服务编码存在、权益码有效、订单不存在 | type=有效服务编码, request=有效加密参数, channel=12 | code=0, 返回url、tranChnl、instType、backUrl |
| TC-3.1.1-002 | 必填参数缺失-type | - | 不传type | code=2, msg="参数错误" |
| TC-3.1.1-003 | 服务编码不存在 | 数据库无该服务编码映射 | type=999999 | code=4, msg="暂不支持此服务"在AES解密前返回 |
| TC-3.1.1-004 | AES解密失败-密钥不匹配 | 服务编码存在但request用其他密钥加密 | type=有效服务编码, request=用错误密钥加密 | code=2, msg="参数错误" |
| TC-3.1.1-005 | AES解密失败-格式错误 | 服务编码存在 | type=有效服务编码, request=无效加密字符串 | code=2, msg="参数错误" |
| TC-3.1.1-006 | 参数解析失败-解密后缺少type | 服务编码存在AES解密成功 | request解密后无type字段 | code=2, msg="参数错误" |
| TC-3.1.1-007 | 参数解析失败-解密后缺少code | 服务编码存在AES解密成功 | request解密后无code字段 | code=2, msg="参数错误" |
| TC-3.1.1-008 | 幂等-已预约状态 | 订单状态=200(已预约) | 相同权益码再次请求 | code=0, 返回原预约成功响应 |
| TC-3.1.1-009 | 幂等-未预约状态 | 订单状态=100(未预约) | 相同权益码再次请求 | 继续执行流程,最终返回预约结果 |
| TC-3.1.1-010 | 幂等-已完成状态 | 订单状态=300(已完成) | 相同权益码再次请求 | code=4, msg="服务已生效" |
| TC-3.1.1-011 | 幂等-已取消状态 | 订单状态=400(已取消) | 相同权益码再次请求 | code=4, msg="服务已取消" |
| TC-3.1.1-011a | 幂等-已过期状态 | 订单状态=500(已过期) | 相同权益码再次请求 | code=4, msg="服务已过期" |
| TC-3.1.1-012 | 权益码过期-CRM渠道 | 邮储返回权益码已过期 | channel=41 | code=4, msg含"二维码20分钟内有效" |
| TC-3.1.1-013 | 权益码过期-手机银行 | 邮储返回权益码已过期 | channel=12 | code=4, msg含"当前页面停置时间过长" |
| TC-3.1.1-014 | 权益码过期-电话银行 | 邮储返回权益码已过期 | channel=17 | code=4, msg含"当前页面停置时间过长" |
| TC-3.1.1-015 | 邮储预约成功 | 权益码有效 | 正常请求 | 继续调用蓝色兄弟获取券码 |
| TC-3.1.1-016 | 邮储返回已预约(161010) | 权益码有效 | 正常请求 | 继续调用蓝色兄弟获取券码 |
| TC-3.1.1-017 | 邮储预约失败 | 邮储返回其他错误 | 正常请求 | code!=0, 订单状态=100 |
| TC-3.1.1-018 | 券码获取成功 | 邮储预约成功 | 正常请求 | code=0, 订单状态=200, 返回url |
| TC-3.1.1-019 | 券码获取失败 | 蓝色兄弟返回异常 | 正常请求 | code!=0, 订单状态=100 |
| TC-3.1.1-020 | 券码获取超时 | 蓝色兄弟接口超时 | 正常请求 | code!=0, 订单状态=100 |
| TC-3.1.1-021 | backUrl为空 | backUrl参数未传 | backUrl=空 | code=0, 响应中backUrl为空 |
| TC-3.1.1-022 | 必填参数缺失-channel | - | 不传channel | code=2, msg="参数错误" |
| TC-3.1.1-023 | 必填参数缺失-tranChnl | - | 不传tranChnl | code=2, msg="参数错误" |
| TC-3.1.1-024 | 必填参数缺失-instType | - | 不传instType | code=2, msg="参数错误" |
| TC-3.1.1-025 | 邮储服务不可用 | 邮储平台异常 | 正常请求 | code=1, msg="系统异常" |
| TC-3.1.1-026 | 蓝色兄弟服务不可用 | 蓝色兄弟平台异常 | 正常请求 | code=1, 订单状态=100 |
| TC-3.1.1-027 | 并发幂等测试 | 同一权益码 | 同时发起多个请求 | 仅一个成功创建订单,其他返回幂等结果 |
| TC-3.1.1-028 | 并发锁竞争-获取锁失败 | 同一权益码并发请求 | 第二个请求 | code=1, msg="请勿重复提交" |
| TC-3.1.1-029 | 不同服务编码使用不同密钥 | 数据库存在多个服务编码配置 | type=服务A, request=用服务A密钥加密 | code=0, 使用服务A的aes_key/aes_iv解密成功 |
---
#### 3.1.2 服务取消接口
**功能描述**:接收邮储银行服务开放平台的取消请求,取消已预约的服务
**业务规则**
1. 对request进行AES解密解析出type(服务编码)、code(权益码)、mac(客户id)
2. 基于权益码(code)获取分布式锁,防止重复请求:
- 获取锁成功:继续执行后续流程
- 获取锁失败:返回"请勿重复提交"
3. 检查订单状态必须是已预约状态才能取消:
- 如果是已取消状态,直接返回取消成功
- 如果是已过期状态,返回"服务已过期"
- 其他非已预约状态,提示"服务已生效无法取消"
4. 调用高端客户权益积分预约服务状态查询接口查询状态:
- 如果不是已预约状态且不是已取消状态,提示"服务已生效,无法取消"
- 如果是已取消状态,直接返回取消成功
5. 调用蓝色兄弟作废接口发起作废:
- 只有当业务码返回作废成功才算成功
- 其他情况如HTTP响应超时均视为作废失败
- 作废失败后调用蓝色兄弟券码查询接口查询券码状态,如果是已作废则按作废成功处理,否则提示"服务取消失败,请重试"
6. 调用邮储接口发起预约取消,业务码明确返回取消成功才算成功,否则提示"服务取消失败"
7. 订单状态更新为"已取消"(400)
8. 释放分布式锁
**输入**
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| type | String | M | 服务编码用于查询数据库获取AES解密密钥 |
| request | String | M | AES加密字符串解密后格式type=服务编码&code=权益码&mac=加密后的客户id |
| channel | String | M | 渠道标识12-手机银行17-电话银行41-CRM零售 |
| tranChnl | String | M | 渠道标识12-手机银行17-电话银行41-CRM零售 |
| backUrl | String | C | 返回手机银行的地址,可为空 |
| instType | String | M | 客户自营代理属性01-自营02-代理 |
| provinceInstNo | String | M | 客户归属一分机构号 |
**输出**data字段内容
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| tranChnl | String | M | 渠道标识 |
| instType | String | M | 客户自营代理属性 |
| backUrl | String | C | 返回手机银行的地址 |
**验收标准**
- [ ] 入参type服务编码为必填字段缺失时返回“参数错误”
- [ ] 根据入参type查询数据库获取对应的aes_key和aes_iv
- [ ] 服务编码(type)不存在时返回“暂不支持此服务”在AES解密之前校验
- [ ] 使用数据库查询到的aes_key和aes_iv能正确解密AES加密的request参数
- [ ] 正确解析AES加密的request参数提取type、code、mac字段
- [ ] 本地订单状态为已取消时,直接返回取消成功
- [ ] 本地订单状态为已过期时,返回"服务已过期"
- [ ] 本地订单状态非已预约且非已取消且非已过期时,返回"服务已生效无法取消"
- [ ] 调用邮储预约服务状态查询接口,非已预约且非已取消状态时返回"服务已生效,无法取消"
- [ ] 调用邮储预约服务状态查询接口,已取消状态时直接返回取消成功
- [ ] 蓝色兄弟作废接口返回作废成功时正常处理
- [ ] 蓝色兄弟作废接口超时或失败后,查询券码状态为已作废时按成功处理
- [ ] 蓝色兄弟作废接口失败且券码状态非已作废时返回"服务取消失败,请重试"
- [ ] 邮储预约取消接口返回成功时正常处理
- [ ] 邮储预约取消接口返回失败时提示"服务取消失败"
- [ ] 取消成功后订单状态更新为400
- [ ] 并发请求时,基于权益码获取分布式锁,获取失败返回"请勿重复提交"
**测试用例**
| 用例编号 | 测试场景 | 前置条件 | 输入数据 | 预期结果 |
|---------|---------|---------|---------|----------|
| TC-3.1.2-001 | 正常取消成功 | 订单状态=已预约(200),邮储状态=已预约,蓝色兄弟作废成功,邮储取消成功 | request=有效加密参数 | code=0, 订单状态=400 |
| TC-3.1.2-002 | AES解密失败 | 服务编码存在 | type=有效服务编码, request=无效加密字符串 | code=2, msg="参数错误" |
| TC-3.1.2-002a | 必填参数缺失-type | - | 不传type | code=2, msg="参数错误" |
| TC-3.1.2-002b | 服务编码不存在 | 数据库无该服务编码映射 | type=999999 | code=4, msg="暂不支持此服务" |
| TC-3.1.2-003 | 本地订单状态已取消 | 订单状态=已取消(400) | request=有效加密参数 | code=0, 直接返回取消成功 |
| TC-3.1.2-004 | 本地订单状态非已预约 | 订单状态=已完成(300) | request=有效加密参数 | code=4, msg="服务已生效无法取消" |
| TC-3.1.2-004a | 本地订单状态已过期 | 订单状态=已过期(500) | request=有效加密参数 | code=4, msg="服务已过期" |
| TC-3.1.2-005 | 本地订单不存在 | 订单不存在 | request=有效加密参数 | code=1, msg="订单不存在" |
| TC-3.1.2-006 | 邮储查询状态-已取消 | 订单状态=已预约(200),邮储状态=已取消 | request=有效加密参数 | code=0, 直接返回取消成功 |
| TC-3.1.2-007 | 邮储查询状态-已生效 | 订单状态=已预约(200),邮储状态=已生效 | request=有效加密参数 | code=1, msg="服务已生效,无法取消" |
| TC-3.1.2-008 | 蓝色兄弟作废成功 | 邮储状态=已预约,蓝色兄弟作废返回成功 | request=有效加密参数 | 继续执行邮储取消流程 |
| TC-3.1.2-009 | 蓝色兄弟作废超时后查询已作废 | 蓝色兄弟作废超时,券码查询状态=已作废(3) | request=有效加密参数 | 按作废成功处理,继续执行 |
| TC-3.1.2-010 | 蓝色兄弟作废失败后查询已作废 | 蓝色兄弟作废返回失败,券码查询状态=已作废(3) | request=有效加密参数 | 按作废成功处理,继续执行 |
| TC-3.1.2-011 | 蓝色兄弟作废失败且券码未作废 | 蓝色兄弟作废失败,券码查询状态=未作废 | request=有效加密参数 | code=1, msg="服务取消失败,请重试" |
| TC-3.1.2-012 | 邮储预约取消成功 | 蓝色兄弟作废成功,邮储取消返回成功 | request=有效加密参数 | code=0, 订单状态=400 |
| TC-3.1.2-013 | 邮储预约取消失败 | 蓝色兄弟作废成功,邮储取消返回失败 | request=有效加密参数 | code=1, msg="服务取消失败" |
| TC-3.1.2-014 | 邮储预约取消超时 | 蓝色兄弟作废成功,邮储取消超时 | request=有效加密参数 | code=1, msg="服务取消失败" |
| TC-3.1.2-015 | 幂等性测试-已取消订单 | 订单状态=已取消(400) | 重复发起取消请求 | code=0, 直接返回取消成功 |
| TC-3.1.2-016 | 并发锁竞争-获取锁失败 | 同一权益码并发请求 | 第二个请求 | code=1, msg="请勿重复提交" |
---
#### 3.1.3 服务完成通知接口
**功能描述**:接收蓝色兄弟券码核销回调,主动通知邮储银行服务完成
**业务规则**
1. 接收蓝色兄弟券码状态变更回调
2. 当券码状态变为"已核销"(2)时
3. 调用邮储银行服务完成接口
4. 更新订单状态为"已完成"(300)
5. 服务完成状态更新需在T+2日内完成
**输入**
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| rightsCode | String | M | 兑换预约码 |
| serviceType | String | M | 服务类型编码 |
| providerCode | String | M | 服务厂商编号 |
| completionTime | String | M | 实际完成时间格式yyyyMMddHHmmss |
**输出**data字段内容
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| - | - | - | 无业务数据,仅返回公共响应 |
**验收标准**
- [ ] 能正确接收蓝色兄弟回调通知
- [ ] 回调验签通过后正确响应"ok"
- [ ] 服务完成通知在T+2日内发送
- [ ] 已取消订单不允许完成(161014)
- [ ] 完成成功后订单状态为300
---
#### 3.1.4 订单过期积分查询接口
**功能描述**:查询订单退还时已过期的幸福点信息,用于取消前提醒客户
**业务规则**
1. 验证服务编码(type)查询数据库获取AES解密密钥
2. 服务编码不存在返回“暂不支持此服务”
3. 解密request参数获取服务编码、权益码、客户ID
4. 调用邮储「高端客户权益订单退还时已过期积分查询」接口
5. 返回邮储接口查询结果(总幸福点、过期幸福点、提醒信息)
**输入**
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| type | String | M | 服务编码用于查询数据库获取AES解密密钥 |
| request | String | M | AES加密字符串解密后格式type=服务编码&code=权益码&mac=加密后的客户id |
| channel | String | M | 渠道标识12-手机银行17-电话银行41-CRM零售 |
| tranChnl | String | M | 渠道标识12-手机银行17-电话银行41-CRM零售 |
| backUrl | String | C | 返回手机银行的地址,可为空 |
| instType | String | M | 客户自营代理属性01-自营02-代理 |
| provinceInstNo | String | M | 客户归属一分机构号 |
**输出**data字段内容
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| tranIntglNum | int | C | 交易总幸福点 |
| ovdueIntglNum | int | C | 已过期的幸福点 |
| noticeMsg | String | C | 提醒信息文本 |
**验收标准**
- [ ] 入参type服务编码为必填字段缺失时返回“参数错误”
- [ ] 根据入参type查询数据库获取对应的aes_key和aes_iv
- [ ] 服务编码(type)不存在时返回“暂不支持此服务”在AES解密之前校验
- [ ] 使用数据库查询到的aes_key和aes_iv能正确解密AES加密的request参数
- [ ] 调用邮储过期积分查询接口成功时,正确返回查询结果
- [ ] 调用邮储过期积分查询接口失败时,返回错误提示
- [ ] 调用邮储过期积分查询接口超时时,返回“查询失败,请重试”
**测试用例**
| 用例编号 | 测试场景 | 前置条件 | 输入数据 | 预期结果 |
|---------|---------|---------|---------|----------|
| TC-3.1.4-001 | 正常查询成功 | 邮储接口返回成功 | type=有效服务编码, request=有效加密参数 | code=0, 返回tranIntglNum、ovdueIntglNum、noticeMsg |
| TC-3.1.4-002 | 必填参数缺失-type | - | 不传type | code=2, msg="参数错误" |
| TC-3.1.4-003 | 服务编码不存在 | 数据库无该服务编码映射 | type=999999 | code=4, msg="暂不支持此服务" |
| TC-3.1.4-004 | AES解密失败 | 服务编码存在 | type=有效服务编码, request=无效加密字符串 | code=2, msg="参数错误" |
| TC-3.1.4-005 | 邮储接口返回失败 | AES解密成功邮储接口返回失败 | type=有效服务编码, request=有效加密参数 | code=1, msg=邮储返回的错误信息 |
| TC-3.1.4-006 | 邮储接口调用超时 | AES解密成功邮储接口超时5s | type=有效服务编码, request=有效加密参数 | code=1, msg="查询失败,请重试" |
| TC-3.1.4-007 | 无过期积分 | 邮储接口返回ovdueIntglNum=0 | type=有效服务编码, request=有效加密参数 | code=0, ovdueIntglNum=0 |
---
#### 3.1.5 服务状态查询接口(内部接口)
**功能描述**:调用邮储「高端客户权益积分预约服务状态查询接口」,根据权益码查询邮储侧的服务预约状态。用于服务取消、服务预约等流程中验证邮储侧订单状态。
**说明**:此接口为内部调用邮储接口,非对外提供的接口。
**业务规则**
1. 根据权益码查询订单
2. 返回当前服务状态
**输入**
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| rightsCode | String | M | 兑换权益码 |
| tranChnl | String | M | 渠道标识 |
**输出**data字段内容
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| serviceState | String | M | 预约服务状态码100-未预约200-已预约300-已完成400-已取消500-已过期 |
| serviceStateMsg | String | M | 预约服务状态描述 |
**验收标准**
- [ ] 能正确查询订单状态
- [ ] 状态码与状态描述一致
---
#### 3.1.6 服务详情查询接口
**功能描述**:提供给邮储银行跳转服务详情页面,根据参数获取上游券码链接展示服务详情
**业务规则**
1. 验证服务编码(type)查询数据库获取AES解密密钥
2. 服务编码不存在返回“暂不支持此服务”
3. 解密request参数获取服务编码、权益码、客户ID
4. 根据权益码查询订单获取券码链接
5. 返回券码链接用于页面展示
**输入**
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| type | String | M | 服务编码用于查询数据库获取AES解密密钥 |
| request | String | M | AES加密字符串解密后格式type=服务编码&code=权益码&mac=加密后的客户id |
| channel | String | M | 渠道标识12-手机银行 17-电话银行 41-CRM零售 |
| tranChnl | String | M | 交易渠道12-手机银行 17-电话银行 41-CRM零售 |
| backUrl | String | M | 返回手机银行的地址,用于客户返回操作 |
| instType | String | O | 机构类型01-自营 02-代理积分系统计划2026年0107上线 |
| provinceInstNo | String | O | 客户归属一分机构号积分系统计划2026年0107上线 |
**输出**data字段内容
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| codeUrl | String | M | 券码链接,用于展示服务详情 |
| backUrl | String | M | 返回手机银行的地址 |
**验收标准**
- [ ] 入参type服务编码为必填字段缺失时返回“参数错误”
- [ ] 根据入参type查询数据库获取对应的aes_key和aes_iv
- [ ] 服务编码(type)不存在时返回“暂不支持此服务”在AES解密之前校验
- [ ] 使用数据库查询到的aes_key和aes_iv能正确解密AES加密的request参数
- [ ] 能正确查询订单获取券码链接
- [ ] 权益码不存在时返回错误提示
**测试用例**
| 用例编号 | 测试场景 | 前置条件 | 输入数据 | 预期结果 |
|---------|---------|---------|---------|----------|
| TC-3.1.6-001 | 正常查询成功 | 订单存在且有券码链接 | type=有效服务编码, request=有效加密参数 | code=0, 返回codeUrl和backUrl |
| TC-3.1.6-002 | 必填参数缺失-type | - | 不传type | code=2, msg="参数错误" |
| TC-3.1.6-003 | 服务编码不存在 | 数据库无该服务编码映射 | type=999999 | code=4, msg="暂不支持此服务" |
| TC-3.1.6-004 | AES解密失败 | 服务编码存在 | type=有效服务编码, request=无效加密字符串 | code=2, msg="参数错误" |
| TC-3.1.6-005 | 权益码不存在 | AES解密成功订单不存在 | request解密后code不存在 | code=1, msg="订单不存在" |
---
### 3.2 券码管理模块
#### 3.2.1 券码获取功能
**功能描述**调用蓝色兄弟API获取虚拟商品券码
**业务规则**
1. 生成唯一外部业务号(out_biz_no)
2. 构造请求参数并AES加密
3. 使用RSA私钥签名
4. 调用获取券码接口
5. 保存券码信息与订单关联
**输入**
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| out_biz_no | String | M | 外部业务号长度2~32位幂等 |
| activity_no | String | M | 活动编号长度32位内 |
| number | int | M | 数量只支持1 |
| account | String | N | 账号 |
| notify_url | String | N | 回调通知地址 |
**输出**
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| out_biz_no | String | M | 外部业务号 |
| trade_no | String | M | 交易号 |
| key | String | N | key码 |
| url | String | N | 短链接 |
| status | uint8 | M | 状态1-正常2-已核销3-已作废 |
| valid_begin_time | String | M | key码有效期开始 |
| valid_end_time | String | M | key码有效期结束 |
| usable_num | uint32 | M | 可兑换次数 |
**验收标准**
- [ ] 能正确加密业务参数(AES)
- [ ] 能正确签名请求(RSA)
- [ ] 幂等请求返回相同结果
- [ ] 获取成功后保存券码信息
---
#### 3.2.2 券码查询功能
**功能描述**:查询已获取券码的当前状态
**业务规则**
1. 支持通过外部业务号或交易号查询
2. 返回券码完整状态信息
**输入**
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| out_biz_no | String | C | 外部业务号与trade_no二选一 |
| trade_no | String | C | 交易号,优先使用 |
**输出**
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| status | uint8 | M | 状态1-正常2-已核销3-已作废4-已过期 |
| usage_num | uint32 | M | 已兑换次数 |
| usage_time | String | N | 最后一次核销时间 |
**验收标准**
- [ ] 能正确查询券码状态
- [ ] 状态信息完整准确
---
#### 3.2.3 券码作废功能
**功能描述**:作废待使用或已过期的券码
**业务规则**
1. 只能作废待使用(1)、已过期(4)状态的券码
2. 已核销券码不允许作废
**输入**
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| out_biz_no | String | C | 外部业务号与trade_no二选一 |
| trade_no | String | C | 交易号,优先使用 |
**验收标准**
- [ ] 正常状态券码能成功作废
- [ ] 已核销券码作废返回错误
- [ ] 作废成功后券码状态为3
---
#### 3.2.4 券码状态回调接收
**功能描述**:接收蓝色兄弟的券码状态变更通知
**业务规则**
1. 验证回调签名
2. 解析回调数据
3. 更新本地券码状态
4. 当status=2(已核销)时触发服务完成通知
5. 响应httpCode=200和字符串"ok"
**回调重试策略**
- 首次间隔120s
- 第二次间隔240s
- 第三次间隔360s
- 大于5分钟后间隔5分钟
- 大于10分钟后间隔10分钟
- 超过120分钟后不再重试
**验收标准**
- [ ] 能正确验证回调签名
- [ ] 状态更新及时准确
- [ ] 核销状态触发服务完成通知
- [ ] 响应格式正确(httpCode=200, body="ok")
---
### 3.3 订单管理模块
#### 3.3.1 订单创建
**功能描述**:创建权益服务订单
**业务规则**
1. 生成唯一订单号
2. 关联权益码、券码、客户信息
3. 初始状态为"服务未预约"(100),预约成功后更新为"服务已预约"(200)
**验收标准**
- [ ] 订单号全局唯一
- [ ] 订单信息完整
---
#### 3.3.2 订单状态管理
**功能描述**:管理订单全生命周期状态流转
**状态定义**
| 状态码 | 状态名称 | 说明 |
|-------|---------|------|
| 100 | 服务未预约 | 已兑换但未预约 |
| 200 | 服务已预约 | 已预约待使用 |
| 300 | 服务已完成 | 券码已核销 |
| 400 | 服务已取消 | 用户主动取消 |
| 500 | 服务已过期 | 超过90天未预约 |
**状态流转规则**
- 200(已预约) → 300(已完成):券码核销
- 200(已预约) → 400(已取消):用户取消
- 300(已完成) 和 400(已取消) 为终态,不可变更
**验收标准**
- [ ] 状态流转符合规则
- [ ] 终态不可变更
- [ ] 状态变更有日志记录
---
#### 3.3.3 订单查询
**功能描述**:支持多维度订单查询
**查询维度**
- 按权益码查询
- 按订单号查询
- 按客户信息查询
- 按状态查询
- 按时间范围查询
**验收标准**
- [ ] 支持所有查询维度
- [ ] 查询结果准确
---
### 3.4 对账管理模块
#### 3.4.1 对账文件查询
**功能描述**:查询邮储银行生成的对账文件列表
**业务规则**
1. 每月3号查询上月对账文件
2. 只包含服务完成(300)状态的订单
**输入**
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| accountingDate | String | M | 会计日期YYYYMMDD |
| fileIntfcName | String | M | 文件接口名 |
**验收标准**
- [ ] 能正确查询对账文件列表
- [ ] 返回文件ID、名称等完整信息
---
#### 3.4.2 对账文件下载
**功能描述**:下载对账文件内容
**输入**
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| fileId | String | M | 文件唯一标识 |
| fileIntfcName | String | M | 文件接口名 |
| sm4Key | String | M | 对账文件SM4加密密钥 |
**对账文件内容**
| 字段 | 说明 |
|-----|------|
| 统计月份 | YYYYMMDD |
| 一分机构序号 | - |
| 一分机构名称 | - |
| 网点机构属性 | 01-自营02-代理 |
| 积分客户服务编码 | - |
| 服务内容 | - |
| 预约时间 | - |
| 实际完成时间 | - |
| 权益兑换码 | - |
**验收标准**
- [ ] 能正确解密对账文件
- [ ] 能解析对账文件内容
- [ ] 与本地订单数据核对
---
#### 3.4.3 对账文件查询接口
**功能描述**:查询可用的对账文件列表,用于管理员或运营人员获取本地处理后的对账文件信息
**接口信息**
- 请求方式POST
- 接口路径:/api/v1/reconciliation/files
**业务规则**
1. 每月3号生成上月服务状态为「服务已完成(300)」的权益订单对账文件
2. 对账月份规则:服务完成时间所在月份为对账月份
- 例如:服务完成时间 202512210000对账月份为 2025年12月对账文件在 2026年1月3日生成
3. 对账文件名称格式:`RIGHTS_SERVICE_CHK_{providerCode}`
- 例如:`RIGHTS_SERVICE_CHK_A101`
4. 返回文件ID、文件名称等信息
**输入**
| 字段 | 类型 | 长度 | 必填 | 说明 |
|-----|------|-----|-----|------|
| accountingDate | String | 8 | M | 会计日期格式YYYYMMDD例如20251201 |
| fileIntfcName | String | 64 | M | 文件接口名固定值RIGHTS_SERVICE_CHK_A101 |
**输出**data字段内容
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| fileList | Object[] | M | 对账文件列表 |
| fileList[].fileId | String | M | 文件唯一标识,用于下载接口 |
| fileList[].fileName | String | M | 文件名称 |
| fileList[].fileSize | long | M | 文件大小(字节) |
| fileList[].createTime | String | M | 文件创建时间 yyyyMMddHHmmss |
**验收标准**
- [ ] POST请求能正确查询对账文件列表
- [ ] accountingDate参数校验格式必须为YYYYMMDD
- [ ] fileIntfcName参数校验必须为有效的文件接口名
- [ ] 返回文件ID、文件名称等完整信息
- [ ] 对账月份不存在数据时返回空列表
**测试用例**
| 用例编号 | 测试场景 | 前置条件 | 输入数据 | 预期结果 |
|---------|---------|---------|---------|----------|
| TC-3.4.3-001 | 正常查询对账文件 | 2025年12月有已完成订单 | accountingDate=20251201, fileIntfcName=RIGHTS_SERVICE_CHK_A101 | code=0, 返回文件列表 |
| TC-3.4.3-002 | 会计日期格式错误 | - | accountingDate=2025-12-01 | code=2, msg="参数错误" |
| TC-3.4.3-003 | 文件接口名不存在 | - | fileIntfcName=INVALID_NAME | code=4, msg="文件接口名不存在" |
| TC-3.4.3-004 | 对账月份无数据 | 2025年11月无已完成订单 | accountingDate=20251101 | code=0, fileList=[] |
| TC-3.4.3-005 | 缺少必填参数 | - | 不传accountingDate | code=2, msg="参数错误" |
---
#### 3.4.4 对账文件下载接口
**功能描述**根据文件ID下载对账文件用于管理员或运营人员获取具体的对账文件内容
**接口信息**
- 请求方式POST
- 接口路径:/api/v1/reconciliation/download
**业务规则**
1. 根据文件ID和文件接口名查询对账文件
2. 验证下载密码,密码错误拒绝下载
3. 文件格式为CSV编码UTF-8-BOM兼容Excel中文显示
4. 直接返回文件流
**输入**
| 字段 | 类型 | 长度 | 必填 | 说明 |
|-----|------|-----|-----|------|
| fileId | String | 32 | M | 文件唯一标识,从查询接口获取 |
| fileIntfcName | String | 64 | M | 文件接口名固定值RIGHTS_SERVICE_CHK_A101 |
| password | String | 32 | M | 下载密码,用于验证下载权限 |
**输出**
直接返回文件流Content-Type: text/csvContent-Disposition: attachment; filename="RIGHTS_SERVICE_CHK_A101_202512.csv"
**对账文件内容字段**
| 序号 | 字段名 | 说明 |
|-----|-------|------|
| 1 | 统计月份 | YYYYMM |
| 2 | 一分机构序号 | province_inst_no |
| 3 | 一分机构名称 | 根据机构号映射 |
| 4 | 网点机构属性 | 01-自营02-代理 |
| 5 | 积分客户服务编码 | type字段 |
| 6 | 服务内容 | 服务名称 |
| 7 | 预约时间 | appointment_time格式yyyyMMddHHmmss |
| 8 | 实际完成时间 | key_usage_time格式yyyyMMddHHmmss |
| 9 | 权益兑换码 | code字段 |
| 10 | 订单号 | order_no |
| 11 | 券码状态 | key_status1-正常2-已核销3-已作废4-已过期 |
**验收标准**
- [ ] POST请求能正确下载对账文件
- [ ] fileId、fileIntfcName、password都为必填参数
- [ ] 密码验证失败时返回错误提示
- [ ] 文件ID不存在时返回错误提示
- [ ] 文件名格式正确RIGHTS_SERVICE_CHK_{providerCode}_{reconMonth}.csv
- [ ] 文件内容编码为UTF-8-BOM兼容Excel中文显示
**测试用例**
| 用例编号 | 测试场景 | 前置条件 | 输入数据 | 预期结果 |
|---------|---------|---------|---------|----------|
| TC-3.4.4-001 | 正常下载对账文件 | 文件存在,密码正确 | fileId=有效ID, fileIntfcName=RIGHTS_SERVICE_CHK_A101, password=正确密码 | 返回CSV文件流文件名正确 |
| TC-3.4.4-002 | 密码错误 | 文件存在 | fileId=有效ID, password=错误密码 | code=3, msg="密码错误" |
| TC-3.4.4-003 | 文件ID不存在 | - | fileId=无效ID | code=4, msg="文件不存在" |
| TC-3.4.4-004 | 文件ID为空 | - | fileId= | code=2, msg="参数错误" |
| TC-3.4.4-005 | 缺少password参数 | - | 不传password | code=2, msg="参数错误" |
---
#### 3.4.5 批量补推接口
**功能描述**:手动批量触发服务完成通知,用于处理蓝色兄弟已核销但邮储银行未收到服务完成通知的订单
**接口信息**
- 请求方式POST
- 接口路径:/api/v1/reconciliation/batch-repush
**业务规则**
1. 查询本地订单状态为「服务已预约(200)」且券码状态为「已核销(2)」的订单
2. 批量创建服务完成通知任务(task_type=1)
3. 支持按时间范围、权益码列表筛选
4. 单次补推数量限制最多100笔
5. 幂等处理:已存在相同类型的待处理任务时跳过
**输入**
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| codes | String[] | C | 权益码列表最多100个与时间范围二选一 |
| startTime | String | C | 开始时间格式yyyyMMddHHmmss与codes二选一 |
| endTime | String | C | 结束时间格式yyyyMMddHHmmss与codes二选一 |
| dryRun | boolean | C | 是否试运行默认false。true时只返回待补推订单列表不实际创建任务 |
**输出**data字段内容
| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| totalCount | int | M | 符合条件的订单总数 |
| createdCount | int | M | 新创建的任务数dryRun=true时为0 |
| skippedCount | int | M | 跳过的订单数(已有待处理任务) |
| orders | Object[] | C | 订单列表dryRun=true时返回 |
| orders[].orderNo | String | M | 订单号 |
| orders[].code | String | M | 权益码 |
| orders[].status | int | M | 订单状态 |
| orders[].keyStatus | int | M | 券码状态 |
| orders[].keyUsageTime | String | C | 核销时间 |
| orders[].hasTask | boolean | M | 是否已有待处理任务 |
**验收标准**
- [ ] 只处理本地status=200(已预约)且key_status=2(已核销)的订单
- [ ] 权益码列表和时间范围必须二选一
- [ ] 权益码列表最多100个超过返回"单次补推数量不能超过100笔"
- [ ] 时间范围查询结果超过100笔时返回"符合条件的订单超过100笔请缩小时间范围或使用权益码列表"
- [ ] 幂等处理订单已有task_type=1且status!=2(已完成)的任务时跳过
- [ ] dryRun=true时只查询不创建任务
- [ ] 创建的任务trigger_source=2(主动查询/手动触发)
- [ ] 补推成功后返回创建的任务数和跳过的订单数
**测试用例**
| 用例编号 | 测试场景 | 前置条件 | 输入数据 | 预期结果 |
|---------|---------|---------|---------|----------|
| TC-3.4.4-001 | 按权益码批量补推 | 3笔订单status=200,key_status=2 | codes=["code1","code2","code3"] | code=0, createdCount=3 |
| TC-3.4.4-002 | 按时间范围批量补推 | 2笔订单符合条件 | startTime=20251201000000, endTime=20251231235959 | code=0, createdCount=2 |
| TC-3.4.4-003 | 试运行模式 | 2笔订单符合条件 | codes=["code1","code2"], dryRun=true | code=0, createdCount=0, orders返回2笔 |
| TC-3.4.4-004 | 权益码超过100个 | - | codes=[101个权益码] | code=2, msg="单次补推数量不能超过100笔" |
| TC-3.4.4-005 | 时间范围结果超100笔 | 150笔订单符合条件 | startTime/endTime | code=4, msg="符合条件的订单超过100笔" |
| TC-3.4.4-006 | 参数缺失 | - | 不传codes和时间范围 | code=2, msg="参数错误:权益码列表和时间范围必须二选一" |
| TC-3.4.4-007 | 幂等-已有待处理任务 | code1已有task_type=1,status=0的任务 | codes=["code1"] | code=0, createdCount=0, skippedCount=1 |
| TC-3.4.4-008 | 订单状态不符合 | 订单status=300(已完成) | codes=["code1"] | code=0, totalCount=0 |
| TC-3.4.4-009 | 券码状态不符合 | 订单key_status=1(正常) | codes=["code1"] | code=0, totalCount=0 |
| TC-3.4.4-010 | 混合场景 | 2笔符合1笔已有任务1笔状态不符 | codes=[4个权益码] | code=0, totalCount=2, createdCount=1, skippedCount=1 |
---
### 3.5 安全与加密模块
#### 3.5.1 邮储银行加解密SM2/SM4/AES
**功能描述**:实现与邮储银行通信的加解密
**服务预约请求AES解密规则**
- 算法/模式/填充AES/CBC/PKCS5Padding
- Key长度16位
- IV长度16位
- 编码方式Base64
- 解密后格式type=服务编码&code=权益码&mac=加密后的客户id
**服务开放平台SM4加密规则**
1. 随机生成SM4 key
2. 使用SM4 key对请求报文加密CBC模式
3. 使用邮储银行公钥SM2加密SM4 key
**服务开放平台签名规则**
1. 拼接request + encryptKey + accessToken
2. 使用SM3计算摘要
3. 使用私钥SM2加密摘要
**验收标准**
- [ ] AES解密正确服务预约请求
- [ ] SM4加解密正确
- [ ] SM2加解密正确
- [ ] SM3签名验签正确
---
#### 3.5.2 蓝色兄弟加解密RSA/AES
**功能描述**:实现与蓝色兄弟通信的加解密
**业务参数加密规则**
1. 业务参数去掉"零"值
2. 按字母排序转成JSON字符串
3. 使用应用key进行AES加密
**签名规则**
1. 拼接appId + timestamp + ciphertext
2. 使用私钥RSA签名
**验收标准**
- [ ] AES加解密正确
- [ ] RSA签名验签正确
- [ ] 回调验签正确
---
## 4. 数据需求
### 4.1 数据实体
#### 4.1.1 订单表(ycjfsc_order)
| 字段名 | 类型 | 长度 | 必填 | 说明 |
|-------|------|-----|-----|------|
| id | bigint | 20 | M | 主键 |
| order_no | varchar | 32 | M | 系统内部订单号 |
| code | varchar | 32 | M | 权益码(邮储订单号) |
| out_trade_no | varchar | 32 | N | 请求上游营销平台的交易号 |
| partner_tx_sri_no | varchar | 24 | M | 请求邮储服务平台的流水号 |
| type | varchar | 6 | M | 服务编码 |
| provider_code | varchar | 4 | M | 服务商编号(邮储分配的固定值) |
| status | int | 3 | M | 内部订单状态100未预约 200已预约 300已完成 400已取消 500已过期 |
| appointment_time | datetime | - | N | 预约成功时间 |
| mac | varchar | 40 | N | 加密后的客户id |
| channel | varchar | 4 | M | 渠道标识12-手机银行17-电话银行41-CRM零售 |
| tran_chnl | varchar | 4 | M | 渠道标识12-手机银行17-电话银行41-CRM零售 |
| back_url | varchar | 256 | N | 返回手机银行的地址 |
| inst_type | varchar | 2 | M | 客户自营代理属性01-自营02-代理 |
| province_inst_no | varchar | 20 | M | 客户归属一分机构号 |
| key_code | varchar | 64 | N | 券码 |
| url | varchar | 125 | N | 短链接 |
| key_status | int | 1 | N | 券码状态0-待生成 1-正常2-已核销3-已作废4-已过期 |
| key_issue_time | datetime | - | N | key码发放成功时间 |
| activity_no | varchar | 32 | N | 活动编号 |
| key_valid_begin_time | datetime | - | N | 券码有效期开始 |
| key_valid_end_time | datetime | - | N | 券码有效期结束 |
| key_usage_time | datetime | - | N | 核销时间 |
| key_discard_time | datetime | - | N | 作废时间 |
| update_time | datetime | - | M | 更新时间 |
| create_time | datetime | - | M | 创建时间 |
#### 4.1.3 通知任务表(ycjfsc_notify_task)
| 字段名 | 类型 | 长度 | 必填 | 说明 |
|-------|------|-----|-----|------|
| id | bigint | 20 | M | 主键 |
| order_no | varchar | 32 | M | 关联订单号 |
| code | varchar | 32 | M | 权益码 |
| task_type | int | 1 | M | 任务类型1-服务完成通知 2-过期处理 3-作废补偿 |
| trigger_source | int | 1 | M | 触发来源1-回调通知 2-主动查询 |
| status | int | 1 | M | 任务状态0-待执行 1-执行中 2-已完成 3-失败 |
| retry_count | int | 3 | M | 重试次数默认0 |
| max_retry | int | 3 | M | 最大重试次数默认8 |
| next_retry_time | datetime | - | N | 下次重试时间 |
| error_msg | varchar | 512 | N | 失败原因 |
| create_time | datetime | - | M | 创建时间 |
| update_time | datetime | - | M | 更新时间 |
**任务类型说明:**
| task_type | 说明 | 处理逻辑 |
|-----------|------|----------|
| 1 | 服务完成通知 | 调用邮储服务完成接口 → 订单状态改为300 |
| 2 | 过期处理 | 调用邮储预约取消接口 → 订单状态改为500 |
| 3 | 作废补偿 | 调用邮储预约取消接口 → 订单状态改为400 |
#### 4.1.4 对账记录表(ycjfsc_reconciliation)
| 字段名 | 类型 | 长度 | 必填 | 说明 |
|-------|------|-----|-----|------|
| id | bigint | 20 | M | 主键 |
| recon_month | varchar | 6 | M | 对账月份YYYYMM |
| file_id | varchar | 32 | M | 文件ID |
| file_name | varchar | 128 | M | 文件名 |
| recon_status | int | 1 | M | 对账状态0-待对账1-已对账2-有差异 |
| total_count | int | 10 | M | 总记录数 |
| match_count | int | 10 | M | 匹配记录数 |
| diff_count | int | 10 | M | 差异记录数 |
| create_time | datetime | - | M | 创建时间 |
| update_time | datetime | - | M | 更新时间 |
#### 4.1.5 服务编码映射表(ycjfsc_service_activity_mapping)
| 字段名 | 类型 | 长度 | 必填 | 说明 |
|-------|------|-----|-----|------|
| id | bigint | 20 | M | 主键 |
| service_type | varchar | 6 | M | 邮储服务编码 |
| service_name | varchar | 64 | M | 服务名称 |
| activity_no | varchar | 32 | M | 营销平台活动编号 |
| activity_name | varchar | 64 | N | 活动名称 |
| aes_key | varchar | 32 | M | AES加密密钥16位 |
| aes_iv | varchar | 32 | M | AES加密向量16位 |
| create_time | datetime | - | M | 创建时间 |
| update_time | datetime | - | M | 更新时间 |
### 4.2 数据规则
1. **订单号生成规则(order_no)**
- 格式:`{YYMMDDHHmmss}{machineId}{sequence}`
- 长度20位
- 结构:
- 时间戳(12位)YYMMDDHHmmss例如 `260313142530`
- 机器ID(2位)`01-99`,支持多实例部署
- 序列号(6位)`000001-999999`,每秒重置
- 示例:`26031314253001000001`
- 特点:按时间排序、可读性强、全局唯一
2. **外部业务号生成规则(out_biz_no)**
- 用途:调用蓝色兄弟接口的幂等标识
- 格式:`{order_no}{service_type}`
- 长度26位在蓝色兄弟2~32位限制内
- 结构:
- 订单号(20位)上述order_no
- 服务类型(6位):邮储服务编码,例如 `310001`
- 示例:`26031314253001000001310001`
- 特点:包含业务信息、可追溯、幂等
3. **合作方交易流水号生成规则(partner_tx_sri_no)**
- 用途:调用邮储服务开放平台接口
- 格式:`{tranChnl}{YYYYMMDD}{sequence}`
- 长度18位邮储接口规范要求
- 结构:
- 渠道号(2位)12-手机银行17-电话银行41-CRM零售
- 日期(8位)YYYYMMDD例如 `20260313`
- 序列号(8位)`00000001-99999999`,每日重置
- 示例:`122026031300000001`
- 特点:符合邮储规范、包含渠道信息
3. **状态流转**
- 100(未预约) → 200(已预约):预约成功
- 200(已预约) → 300(已完成):券码核销
- 200(已预约) → 400(已取消):用户取消
- 200(已预约) → 500(已过期):券码过期(根据上游券码状态决定)
4. **数据保留**订单数据保留至少2年
---
## 5. 非功能性需求
### 5.1 性能要求
#### 5.1.1 响应时间要求
| 接口类型 | P95 | P99 | 说明 |
|---------|-----|-----|------|
| 服务预约接口 | < 300ms | < 500ms | 含上游券码获取 |
| 服务取消接口 | < 200ms | < 400ms | 含上游券码作废 |
| 服务状态查询接口 | < 100ms | < 200ms | 优先读缓存 |
| 券码状态回调接口 | < 100ms | < 200ms | 快速响应后异步处理 |
#### 5.1.2 并发处理能力
| 指标 | 要求 | 说明 |
|-----|------|------|
| 日常TPS | 100 | 正常业务峰值 |
| 峰值TPS | 500 | 活动期间峰值 |
| 单机QPS | 200 | 支持水平扩展 |
#### 5.1.3 缓存设计
**缓存策略**
| 缓存对象 | 缓存类型 | 过期策略 | 更新策略 | 说明 |
|---------|---------|---------|---------|------|
| 服务编码映射表 | 本地缓存 + Redis | 本地5分钟Redis 30分钟 | 配置变更主动刷新 | 高频读取变更少 |
| 订单信息 | Redis | 24小时 | 状态变更时更新 | 按权益码缓存 |
| 券码信息 | Redis | 跟随订单 | 状态变更时更新 | 与订单关联存储 |
| 邮储AccessToken | Redis | 有效期前5分钟刷新 | 定时刷新 | 避免过期 |
**缓存Key设计**
```
# 服务编码映射
ycjfsc:service:mapping:{serviceType}
# 订单缓存(按权益码)
ycjfsc:order:code:{rightsCode}
# 订单缓存(按订单号)
ycjfsc:order:no:{orderNo}
# AccessToken
ycjfsc:token:psbc
```
**缓存穿透防护**
- 空值缓存查询不存在的数据时缓存空值过期时间60秒
- 布隆过滤器权益码存在性预判可选
#### 5.1.4 高质量代码实现要求
1. **连接池管理**HTTP客户端数据库Redis均需配置合理的连接池
2. **超时控制**所有外部调用设置合理超时连接超时3s读超时10s
3. **熔断降级**上游服务异常时快速失败避免雪崩
4. **异步处理**非核心链路异步化如日志记录监控上报
5. **批量操作**对账等批量场景使用批量查询/更新
---
### 5.2 数据一致性要求
#### 5.2.1 核心一致性场景
**场景描述**蓝色兄弟券码已核销用户已完成充值兑换必须通知邮储银行服务完成否则会造成资金损失
**风险点**
- 回调通知邮储失败
- 回调成功但本地状态更新失败
- 系统宕机导致通知丢失
#### 5.2.2 一致性保障机制
**机制一:异步消息 + 最终一致**
```
1. 接收蓝色兄弟核销回调
2. 记录核销时间,创建通知任务(task_type=1)
3. 立即响应蓝色兄弟"ok"(快速响应,避免超时)
4. 异步处理:
- 调用邮储服务完成接口
- 成功:更新订单状态为「已完成」(300),任务状态为已完成(2)
- 失败:任务状态为失败(3),等待重试机制处理
```
**说明**不使用本地事务通过重试+补偿+对账三重机制保证最终一致性订单状态统一使用100/200/300/400/500五个状态通知处理过程通过通知任务表(ycjfsc_notify_task)进行管理
**机制二:消息重试策略**
| 重试次数 | 间隔时间 | 累计时间 |
|---------|---------|----------|
| 第1次 | 10秒 | 10秒 |
| 第2次 | 30秒 | 40秒 |
| 第3次 | 1分钟 | 1分40秒 |
| 第4次 | 5分钟 | 6分40秒 |
| 第5次 | 10分钟 | 16分40秒 |
| 第6次 | 30分钟 | 46分40秒 |
| 第7次 | 1小时 | 1小时46分40秒 |
| 第8次 | 2小时 | 3小时46分40秒 |
| 第9次及以后 | 2小时 | 每2小时重试一次 |
超过72小时仍失败任务标记为已放弃状态不再自动重试等待告警通知后人工介入处理
**机制三:定时补偿任务**
| 任务 | 执行频率 | 处理逻辑 |
|-----|---------|----------|
| 券码状态同步 | 每小时 | 扫描状态=200(已预约)的订单,查询蓝色兄弟券码状态:已核销(2)→创建服务完成任务,已作废(3)→创建作废补偿任务,已过期(4)→创建过期处理任务 |
| 通知任务重试 | 每5分钟 | 扫描状态=3(失败)且未超过最大重试次数的任务,按退避策略重试 |
| 失败任务告警 | 每10分钟 | 扫描超过最大重试次数的任务发送告警通知 |
**机制四:对账兜底**
- 每日凌晨对账比对本地已核销订单与邮储已完成状态
- 发现不一致自动重新发起服务完成通知
- 月度对账与邮储对账文件核对确保无遗漏
#### 5.2.3 订单状态说明
| 状态码 | 状态名称 | 说明 |
|-------|---------|------|
| 100 | 服务未预约 | 已兑换但未预约 |
| 200 | 服务已预约 | 已预约待使用 |
| 300 | 服务已完成 | 券码已核销且已通知邮储 |
| 400 | 服务已取消 | 用户主动取消 |
| 500 | 服务已过期 | 超过有效期未使用 |
#### 5.2.4 幂等性设计
| 接口/操作 | 幂等键 | 幂等策略 |
|----------|-------|----------|
| 服务预约 | 权益码(code) | 相同权益码重复请求返回相同结果内部统一使用code字段调用邮储接口时映射为rightsCode |
| 服务取消 | 权益码(rightsCode) | 已取消订单重复请求直接返回成功 |
| 服务完成通知 | 权益码(rightsCode) | 已完成订单重复请求直接返回成功 |
| 券码获取 | 外部业务号(out_biz_no) | 相同业务号返回相同券码 |
| 回调处理 | 交易号(trade_no) | 相同交易号仅处理一次 |
#### 5.2.5 数据一致性监控
**告警指标**
| 指标 | 阈值 | 告警级别 |
|-----|------|----------|
| 待执行任务积压 | > 10笔 | P2 |
| 失败任务超过最大重试 | > 0笔 | P1 |
| 任务执行失败率 | > 1% | P2 |
| 对账差异 | > 0笔 | P1 |
---
### 5.3 可用性要求
| 指标 | 要求 |
|-----|------|
| 系统可用性 | ≥ 99.9% |
| 单点故障 | 无单点,支持多实例部署 |
| 故障恢复 | RTO < 5分钟RPO = 0 |
---
### 5.4 安全要求
1. **传输安全**所有外部通信使用HTTPS
2. **数据脱敏**日志中客户敏感信息脱敏处理
3. **密钥管理**加密密钥通过配置中心管理定期轮换
4. **访问控制**接口签名验证防止非法调用
---
## 6. 流程图
### 6.1 服务预约流程
```plantuml
@startuml 服务预约流程
skinparam ActivityDiamondFontSize 12
skinparam ActivityFontSize 12
start
:根据入参type(服务编码)\n查询数据库;
if (服务编码是否存在?) then (不存在)
:返回"暂不支持此服务";
stop
else (存在)
:获取对应的aes_key和aes_iv;
endif
:使用aes_key和aes_iv\n对request进行AES解密;
if (解密成功?) then (否)
:返回"参数错误";
stop
endif
:解析 type(服务编码)、code(权益码)、mac(客户id);
:基于权益码(code)获取分布式锁;
if (获取锁成功?) then (否)
:返回"请勿重复提交";
stop
endif
:defer 释放分布式锁;
note right: 使用defer机制确保锁在流程结束时释放
:查询订单状态(幂等验证);
if (订单状态?) then (服务已预约 200)
:返回预约成功;
stop
elseif (服务已取消 400) then
:返回"服务已取消";
stop
elseif (服务已完成 300) then
:返回"服务已生效";
stop
elseif (服务已过期 500) then
:返回"服务已过期";
stop
else (服务未预约 100 或 订单不存在)
endif
:请求邮储服务开放平台\n验证权益码有效性;
if (权益码是否过期?) then (过期)
if (渠道类型?) then (CRM渠道)
:返回"二维码20分钟内有效\n现已超时失效请联系理财经理\n重新生成二维码";
else (手机银行渠道)
:返回"当前页面停置时间过长,\n请您重新进入该页面";
endif
stop
else (未过期)
endif
:创建/更新订单记录\n(状态: 服务未预约 100);
:请求邮储服务开放平台\n发起预约;
if (预约结果?) then (成功 或 161010已预约)
:请求蓝色兄弟营销平台\n获取券码链接;
if (获取券码结果?) then (成功)
:更新订单状态为\n「服务已预约 200」;
:返回预约成功\n(包含券码链接url);
stop
else (异常)
:订单状态保持\n「服务未预约 100」;
:返回预约失败;
stop
endif
else (其他情况)
:订单状态保持\n「服务未预约 100」;
:返回预约失败;
stop
endif
@enduml
```
**流程步骤说明:**
| 步骤 | 操作 | 说明 |
|-----|------|------|
| 1 | 验证服务编码 | 根据入参type查询数据库获取对应的aes_key和aes_iv |
| 2 | AES解密 | 使用数据库查询到的aes_key和aes_iv对request进行解密解析出typecodemac |
| 3 | 获取分布式锁 | 基于权益码(code)获取锁防止重复请求 |
| 4 | defer释放锁 | 使用defer机制注册锁释放确保流程结束时自动释放 |
| 5 | 幂等验证 | 根据订单状态决定后续流程已预约返回成功已取消返回"服务已取消"已完成返回"服务已生效"已过期返回"服务已过期" |
| 6 | 验证权益码 | 调用邮储平台验证权益码有效性前置校验5s超时 |
| 7 | 创建/更新订单 | 订单状态设为服务未预约(100)」|
| 8 | 发起预约 | 调用邮储平台发起预约5s超时 |
| 9 | 获取券码 | 调用蓝色兄弟获取券码链接幂等5s超时更新最终状态 |
---
### 6.2 券码状态同步流程
```plantuml
@startuml 券码状态同步流程
start
:定时任务触发(每小时);
:扫描状态=200(已预约)的订单;
while (遍历订单?) is (有)
:请求蓝色兄弟查询券码状态;
note right: 5s超时
if (券码状态?) then (已核销 2)
:检查是否已有通知任务;
if (任务存在?) then (否)
:创建通知任务\n(type=1 服务完成);
endif
elseif (已作废 3) then
:检查是否已有通知任务;
if (任务存在?) then (否)
:创建通知任务\n(type=3 作废补偿);
endif
elseif (已过期 4) then
:检查是否已有通知任务;
if (任务存在?) then (否)
:创建通知任务\n(type=2 过期处理);
endif
else (正常 1)
:跳过,等待用户使用;
endif
endwhile (无)
stop
@enduml
```
**流程步骤说明:**
| 步骤 | 操作 | 说明 |
|-----|------|------|
| 1 | 定时触发 | 每小时执行一次 |
| 2 | 扫描订单 | 查询状态=200(已预约)的订单 |
| 3 | 查询券码状态 | 调用蓝色兄弟券码查询接口5s超时 |
| 4 | 创建任务 | 根据券码状态创建对应类型的通知任务幂等检查 |
**券码状态与任务类型映射:**
| 券码状态 | 任务类型 | 处理逻辑 |
|----------|----------|----------|
| 已核销(2) | 服务完成通知(1) | 调用邮储服务完成接口 订单状态改为300 |
| 已作废(3) | 作废补偿(3) | 调用邮储预约取消接口 订单状态改为400 |
| 已过期(4) | 过期处理(2) | 调用邮储预约取消接口 订单状态改为500 |
| 正常(1) | - | 跳过等待用户使用 |
### 6.2.1 通知任务执行流程
```plantuml
@startuml 通知任务执行流程
start
:定时任务触发每5分钟;
:扫描状态=0(待执行)或\n状态=3(失败)且未超过最大重试次数的任务;
while (遍历任务?) is (有)
:更新任务状态为执行中(1);
if (任务类型?) then (type=1 服务完成)
:调用邮储服务完成接口;
elseif (type=2 过期处理) then
:调用邮储预约取消接口;
else (type=3 作废补偿)
:调用邮储预约取消接口;
endif
note right: 5s超时
if (执行成功?) then (是)
:更新任务状态为已完成(2);
:更新订单状态;
note right: type=1→300, type=2→500, type=3→400
else (否)
:retry_count++;
if (retry_count > max_retry?) then (是)
:任务状态保持失败(3);
:记录错误信息,等待告警;
else (否)
:更新任务状态为失败(3);
:设置下次重试时间(指数退避);
endif
endif
endwhile (无)
stop
@enduml
```
**重试策略(指数退避):**
| 重试次数 | 间隔 | 累计时间 |
|----------|------|----------|
| 第1次 | 10秒 | 10秒 |
| 第2次 | 30秒 | 40秒 |
| 第3次 | 1分钟 | 1分40秒 |
| 第4次 | 5分钟 | 6分40秒 |
| 第5次 | 10分钟 | 16分40秒 |
| 第6次 | 30分钟 | 46分40秒 |
| 第7次 | 1小时 | 1小时46分40秒 |
| 第8次 | 2小时 | 3小时46分40秒 |
超过8次仍失败任务保持失败状态等待告警通知后人工介入处理
---
### 6.3 服务取消流程
```plantuml
@startuml 服务取消流程
start
:接收取消请求;
:AES解密request;
note right: 解析type、code、mac
if (解密成功?) then (否)
:返回参数错误;
stop
endif
:基于权益码(code)获取分布式锁;
if (获取锁成功?) then (否)
:返回"请勿重复提交";
stop
endif
:defer 释放分布式锁;
note right: 使用defer机制确保锁在流程结束时释放
:查询本地订单;
if (订单存在?) then (否)
:返回订单不存在;
stop
endif
if (订单状态=已取消?) then (是)
:返回取消成功;
stop
endif
if (订单状态=已过期?) then (是)
:返回"服务已过期";
stop
endif
if (订单状态=已预约?) then (否)
:返回"服务已生效无法取消";
stop
endif
:调用邮储预约服务状态查询接口;
note right: 5s超时
if (邮储状态=已取消?) then (是)
:更新本地订单状态为已取消;
:返回取消成功;
stop
endif
if (邮储状态=已预约?) then (否)
:返回"服务已生效,无法取消";
stop
endif
:调用蓝色兄弟作废接口;
note right: 5s超时
if (作废成功?) then (否)
:调用蓝色兄弟券码查询接口;
if (券码状态=已作废?) then (否)
:返回"服务取消失败,请重试";
stop
endif
endif
:调用邮储预约取消接口;
note right: 5s超时
if (取消成功?) then (否)
:返回"服务取消失败";
stop
endif
:更新订单状态为已取消(400);
:返回取消成功;
stop
@enduml
```
**流程步骤说明:**
| 步骤 | 操作 | 说明 |
|-----|------|------|
| 1 | AES解密 | 对request进行AES解密解析出typecodemac |
| 2 | 获取分布式锁 | 基于权益码(code)获取锁防止重复请求 |
| 3 | defer释放锁 | 使用defer机制注册锁释放确保流程结束时自动释放 |
| 4 | 本地订单校验 | 查询订单是否存在已取消直接返回成功已过期返回"服务已过期"非已预约返回失败 |
| 5 | 邮储状态查询 | 调用邮储预约服务状态查询接口5s超时确认可取消 |
| 6 | 蓝色兄弟作废 | 调用作废接口5s超时失败时查询券码状态确认 |
| 7 | 邮储预约取消 | 调用邮储预约取消接口5s超时确认取消成功 |
| 8 | 更新订单状态 | 更新本地订单状态为已取消(400) |
---
### 6.4 月度对账流程
```plantuml
@startuml 月度对账流程
start
:定时任务触发每月3号;
:查询上月status=300(已完成)的订单;
note right: 对账月份=服务完成时间所在月
if (有订单数据?) then (无)
:跳过,不生成对账文件;
stop
endif
:生成对账文件;
note right: RIGHTS_SERVICE_CHK_{providerCode}_{reconMonth}.csv
:调用邮储对账文件查询接口;
note right: 5s超时
if (获取文件列表成功?) then (否)
:记录错误,等待重试;
stop
endif
:调用邮储对账文件下载接口;
note right: 5s超时
if (下载成功?) then (否)
:记录错误,等待重试;
stop
endif
:SM4解密对账文件;
:解析对账文件内容;
while (遍历邮储对账记录?) is (有)
:根据权益码查询本地订单;
if (本地订单存在?) then (否)
:记录差异-本地缺失;
elseif (状态一致?) then (否)
:记录差异-状态不一致;
:创建服务完成通知任务;
else (是)
:标记为已对账;
endif
endwhile (无)
:保存对账结果;
if (有差异?) then (是)
:发送告警通知;
endif
:生成对账报告;
stop
@enduml
```
**流程步骤说明**
| 步骤 | 操作 | 说明 |
|-----|------|------|
| 1 | 定时触发 | 每月3号执行 |
| 2 | 查询本地订单 | 查询上月status=300(已完成)的订单 |
| 3 | 生成对账文件 | 生成CSV格式对账文件 |
| 4 | 获取邮储对账文件 | 调用邮储查询和下载接口5s超时 |
| 5 | SM4解密 | 使用SM4密钥解密对账文件 |
| 6 | 数据核对 | 逐笔对比邮储和本地数据 |
| 7 | 差异处理 | 本地缺失或状态不一致时创建补推任务 |
| 8 | 生成报告 | 保存对账结果有差异时发送告警 |
**对账月份规则**
| 服务完成时间 | 对账月份 | 对账文件生成时间 | 对账文件名 |
|--------------|---------|--------------|----------|
| 202512210000 | 202512 | 2026年1月3日 | RIGHTS_SERVICE_CHK_A101_202512.csv |
| 202601010000 | 202601 | 2026年2月3日 | RIGHTS_SERVICE_CHK_A101_202601.csv |
### 6.4.1 批量补推流程
```plantuml
@startuml 批量补推流程
start
:接收补推请求;
if (参数校验?) then (失败)
:返回参数错误;
stop
endif
if (传入codes?) then (是)
if (codes数量>100?) then (是)
:返回"单次补推不能超过100笔";
stop
endif
:按权益码列表查询订单;
else (否)
:按时间范围查询订单;
if (结果数量>100?) then (是)
:返回"符合条件订单超过100笔";
stop
endif
endif
:筛选status=200且key_status=2的订单;
if (dryRun=true?) then (是)
:返回待补推订单列表;
stop
endif
while (遍历订单?) is (有)
:检查是否已有待处理任务;
if (已有任务?) then (是)
:skippedCount++;
else (否)
:创建服务完成通知任务;
note right: task_type=1\ntrigger_source=2
:createdCount++;
endif
endwhile (无)
:返回补推结果;
note right: totalCount, createdCount, skippedCount
stop
@enduml
```
**流程步骤说明**
| 步骤 | 操作 | 说明 |
|-----|------|------|
| 1 | 参数校验 | codes和时间范围必须二选一 |
| 2 | 数量校验 | 单次最多处理100笔 |
| 3 | 查询订单 | 筛选status=200且key_status=2的订单 |
| 4 | 试运行判断 | dryRun=true时只查询不创建任务 |
| 5 | 幂等检查 | 已有待处理任务的订单跳过 |
| 6 | 创建任务 | task_type=1, trigger_source=2 |
| 7 | 返回结果 | 包含创建数量和跳过数量 |
---
### 6.5 错误处理流程
TODO
---
## 附录
### 附录A专业术语说明
#### A.1 下游系统(邮储银行服务开放平台)
| 术语 | 英文 | 说明 |
|-----|------|------|
| 幸福点 | Happiness Points | 邮储银行高端客户积分可兑换权益服务 |
| 权益码 | Rights Code | 客户兑换权益服务后获得的唯一标识码格式如16800G-VN4724-NX874-30VHR |
| 服务开放平台 | Open Platform | 邮储银行对外开放的API服务平台 |
| SM2 | SM2 | 国密非对称加密算法用于密钥交换和数字签名 |
| SM3 | SM3 | 国密哈希算法用于消息摘要 |
| SM4 | SM4 | 国密对称加密算法用于报文加密 |
| 业务主ID | busiMainId | 请求的业务唯一标识规则渠道号(2位)+日期(8位)+序列号(8位) |
| 对账 | Reconciliation | 每月核对服务商与银行之间的交易数据一致性 |
| T+2 | T+2 | 服务完成后2个工作日内需完成状态同步 |
| 高端客户 | VIP Customer | 邮储银行AUM资产管理规模)≥50万的客户 |
| AUM | Assets Under Management | 资产管理规模 |
#### A.2 服务商系统(当前系统)
| 术语 | 英文 | 说明 |
|-----|------|------|
| 合作方交易流水号 | partnerTxSriNo | 服务商生成的交易唯一标识规则渠道号(2位)+日期(8位)+序列号(8位) |
| 订单号 | Order No | 服务商系统内部订单唯一标识 |
| 服务状态 | Service Status | 订单服务状态100-未预约200-已预约300-已完成400-已取消500-已过期 |
#### A.3 上游系统蓝色兄弟营销开放API
| 术语 | 英文 | 说明 |
|-----|------|------|
| 蓝色兄弟 | LSB (Lan Se Xiong Di) | 第三方虚拟商品供应商提供券码服务 |
| 券码 | Coupon Key | 蓝色兄弟平台生成的虚拟商品兑换码 |
| RSA | RSA | 非对称加密算法用于蓝色兄弟接口签名 |
| AES | AES | 对称加密算法用于蓝色兄弟业务参数加密 |
| 外部业务号 | out_biz_no | 调用蓝色兄弟API的幂等标识长度2~32位 |
| 交易号 | trade_no | 蓝色兄弟返回的平台交易唯一标识 |
| 活动编号 | activity_no | 蓝色兄弟平台的营销活动唯一标识 |
| 券码状态 | Key Status | 券码状态1-正常2-已核销3-已作废4-已过期 |
---
### 附录B接口业务响应码
#### B.1 下游系统(邮储银行服务开放平台)响应码
**公共响应码**
| 响应码 | 描述 | 处理建议 |
|-------|------|----------|
| 000000 | 交易成功 | - |
| 900000 | 未知系统异常 | 稍后重试或联系技术支持 |
| 900001 | 非法参数 | 检查请求参数格式 |
| 900002 | 必填参数缺失 | 补充必填参数 |
| 900005 | 签名错误 | 检查签名算法和密钥 |
| 900006 | 请求非法 | 检查请求格式 |
| 900008 | 幂等错误 | 检查流水号是否重复 |
| 900011 | 签名验证失败 | 检查签名和公钥 |
| 990172 | 系统繁忙 | 稍后重试 |
| 990272 | 系统繁忙 | 稍后重试 |
| 990772 | 系统繁忙 | 稍后重试 |
**权益积分业务响应码**
| 响应码 | 描述 | 处理建议 |
|-------|------|----------|
| 161006 | 交易数据不存在 | 检查权益码或流水号 |
| 161008 | 未知渠道错误 | 检查渠道标识 |
| 161014 | 服务已完成或取消不允许再操作 | 业务状态已终结 |
| 161016 | 无权益服务可操作 | 检查订单状态 |
| 161024 | 服务未预约不允许完成或取消操作 | 需先完成预约 |
| 161026 | 兑换权益码已失效 | 权益码超过20分钟有效期 |
| 161027 | 兑换权益码错误 | 检查权益码格式 |
| 161033 | 实际完成时间不满足对账规范 | 检查completionTime格式 |
| 727101 | 系统异常请稍后重试 | 稍后重试 |
| 727102 | 未查询到当前分类的积分信息 | 检查查询条件 |
| 729006 | 未找到原交易 | 检查原交易流水号 |
| 729011 | 客户名称不能为空 | 补充客户名称 |
| 729012 | 客户证件类型不能为空 | 补充证件类型 |
| 729013 | 证件号码不能为空 | 补充证件号码 |
| 729024 | 证件号码格式不正确 | 检查证件号码格式 |
| 729033 | 客户编号格式不正确 | 检查客户编号格式 |
| 720502 | 原交易流水号不能为空 | 补充原交易流水号 |
| 720503 | 原交易流水号格式不正确 | 检查流水号格式 |
| 729809 | 系统繁忙请稍后再试 | 稍后重试 |
| 729909 | 不能为空 | 补充必填字段 |
| 729910 | 格式不正确 | 检查字段格式 |
| 729917 | 长度不符合要求 | 检查字段长度 |
| 729918 | 不能包含特殊字符 | 移除特殊字符 |
| 729922 | 查询结束时间不得小于查询开始时间 | 调整时间范围 |
| 729928 | 查询起始日期必须在12个月内 | 调整查询日期 |
**文件接口响应码**
| 响应码 | 描述 | 处理建议 |
|-------|------|----------|
| 062005 | 查询失败请稍后再试 | 稍后重试 |
| 063001 | 系统繁忙请稍后再试 | 稍后重试 |
---
#### B.2 上游系统蓝色兄弟营销开放API响应码
**公共响应码**
| code | reason | 描述 | 处理建议 |
|------|--------|------|----------|
| 200 | - | 成功 | - |
| 400 | SIGNATURE_FAIL | 签名错误 | 检查签名密钥和算法 |
| 400 | SDK_INI_FAIL | SDK错误 | 检查应用配置信息 |
| 400 | PARAM_FAIL | 参数错误 | 检查业务参数 |
| 400 | PARAM_DECRYPT_FAIL | 参数解密错误 | 检查加密方式 |
| 500 | PANIC | 系统错误 | 联系平台处理 |
**业务响应码**
| code | reason | 描述 | 处理建议 |
|------|--------|------|----------|
| 401 | ACTIVITY_NOT_AUTH | 活动未授权 | 检查活动授权状态 |
| 401 | MERCHANT_NOT_EXIST | 客户不存在 | 检查客户是否存在 |
| 401 | MERCHANT_NOT_AUTH | 客户冻结 | 检查客户授权状态 |
| 401 | MERCHANT_APP_INCOMPLETE | 客户应用配置未完善 | 完善应用配置 |
| 401 | MERCHANT_APP_NOT_AUTH | 应用不存在 | 检查应用授权状态 |
| 403 | ACTIVITY_EXPIRE | 活动已结束 | 检查活动有效期 |
| 403 | ACTIVITY_NOT_AUTH | 活动状态异常 | 检查活动状态作废/待审核/驳回/暂停/失效/编辑中 |
| 403 | ACTIVITY_OUT_OF_STOCK | 活动库存不足 | 联系平台补充库存 |
| 404 | KEY_NOT_EXIST | 券码不存在 | 检查券码或交易号 |
| 404 | ACTIVITY_NOT_EXIST | 活动不存在 | 检查活动编号 |
| 404 | MERCHANT_NOT_EXIST | 客户不存在 | 检查app_id |
| 404 | MERCHANT_APP_NOT_EXIST | 客户应用不存在 | 检查app_id |
| 404 | MERCHANT_ORDER_NOT_EXIST | 订单记录不存在 | 检查交易号/外部业务号 |
| 404 | MERCHANT_APP_INCOMPLETE | 客户应用信息未完善 | 完善应用信息 |
| 503 | KEY_CREATE_FAIL | 券码创建失败 | 稍后重试或联系平台 |
| 503 | MERCHANT_ORDER_CREATE_FAIL | 订单创建失败 | 稍后重试或联系平台 |
| 503 | MERCHANT_ORDER_DISCARD_FAIL | 订单作废失败 | 稍后重试或联系平台 |
| 503 | MERCHANT_ORDER_DISCARD_MQ_FAIL | 订单作废消息失败 | 稍后重试或联系平台 |
---
#### B.3 服务商系统(当前系统)内部错误码
| 错误码 | 描述 | 处理建议 |
|-------|------|----------|
| SYS_001 | 系统内部错误 | 检查系统日志联系技术支持 |
| SYS_002 | 数据库操作异常 | 检查数据库连接和SQL |
| SYS_003 | 外部服务调用超时 | 检查网络连接稍后重试 |
| BIZ_001 | 订单不存在 | 检查订单号是否正确 |
| BIZ_002 | 订单状态不允许此操作 | 检查当前订单状态 |
| BIZ_003 | 券码获取失败 | 检查上游服务状态 |
| BIZ_004 | 券码作废失败 | 检查券码当前状态 |
| BIZ_005 | 服务完成通知失败 | 检查下游服务状态稍后重试 |
---
### 附录C服务类型与厂商编码
#### C.1 服务类型编码
| 服务类型 | 名称 | 类别 |
|---------|------|------|
| 210001 | 口腔护理 | 健康服务 |
| 210002 | 三个月血糖管理 | 健康服务 |
| 210003 | 三个月血压管理 | 健康服务 |
| 210004 | 三个月体重管理 | 健康服务 |
| 220001 | 高铁贵宾厅 | 出行服务 |
| 220002 | 机场贵宾厅 | 出行服务 |
| 220003 | 洗车 | 出行服务 |
| 310001 | 喜马拉雅VIP巅峰会员季卡 | 虚拟会员 |
| 310002 | 知乎会员季卡 | 虚拟会员 |
| 1200 | 机场快速通道 | 出行服务 |
| 1400 | 专车服务舒适型30公里 | 出行服务 |
| 1410 | 专车服务商务型30公里 | 出行服务 |
| 1420 | 专车服务舒适型50公里 | 出行服务 |
| 1430 | 专车服务商务型50公里 | 出行服务 |
| 1500 | 代驾服务20公里及以内 | 出行服务 |
#### C.2 服务厂商编号
| 编号 | 名称 |
|-----|------|
| A101 | 盛大 |
| B101 | 元化 |
#### C.3 渠道标识
| 渠道标识 | 名称 |
|---------|------|
| 12 | 手机银行 |
| 17 | 电话银行 |
| 18 | 微信银行 |
| 36 | you生活 |
| 46 | 北京分行 |