From f4cc2ce6230fa440dede27d3d223ed68cba78a1c Mon Sep 17 00:00:00 2001 From: yml Date: Mon, 4 May 2026 21:32:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A091=E5=8D=A1=E5=88=B8=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 11.md | 75 ---- apps/backend/data/khhao-sync-state.json | 6 +- .../data/order-fulfillment-bindings.json | 27 -- docs/91卡券/1.说明.md | 6 + docs/91卡券/2.签名规则示例.md | 45 +++ docs/91卡券/3.异步卡密下单.md | 154 ++++++++ docs/91卡券/4.查询订单接口.md | 203 ++++++++++ docs/91卡券/5.当前项目对接配置.md | 315 ++++++++++++++++ 前后端现状分析.md | 350 +++++++++--------- 9 files changed, 909 insertions(+), 272 deletions(-) delete mode 100644 11.md create mode 100644 docs/91卡券/1.说明.md create mode 100644 docs/91卡券/2.签名规则示例.md create mode 100644 docs/91卡券/3.异步卡密下单.md create mode 100644 docs/91卡券/4.查询订单接口.md create mode 100644 docs/91卡券/5.当前项目对接配置.md diff --git a/11.md b/11.md deleted file mode 100644 index bd8a6464..00000000 --- a/11.md +++ /dev/null @@ -1,75 +0,0 @@ -2701835328032037898 2209880145223 海底捞第五人格皮肤 0.01 测试连接 -{ - "item_id": 1038123159888, - "seller_id": 2209880145223, - "MsgTypeDes": "订单已付款", - "biz_order_id": "2701835328032037898", - "order_status": 2, - "_agisoTradeDetail": { - "sku": "6227949975509|类型:海底捞第五人格皮肤", - "item": { - "price": 1, - "title": "测试链接,勿拍", - "item_id": 1038123159888, - "pic_url": "https://gw.alicdn.com/bao/uploaded/i1/2209880145223/O1CN0178ZkcG1oSBmmmr1V8_!!4611686018427387207-53-xy_item.heic" - }, - "payment": 1, - "end_time": 0, - "pay_time": 1776144458000, - "post_fee": 0, - "isEticket": false, - "ship_time": 0, - "buy_amount": 1, - "buyer_nick": "梦里梦", - "isRecharge": false, - "create_time": 1776144455000, - "seller_nick": "大锤号商", - "biz_order_id": "2701835328032037898", - "order_status": 2, - "coupon_eticket_info": [], - "encryption_buyer_id": "enV7xJs5DRbJBzIH8uUUcg==" - } -} - - -SKU: 6227949975509 -ItemId: 1038123159888 - - - ----------------------------- - -(现货秒发)海底捞第五人格联动限定兑换码cdk拉拉队员皮肤+ -SKU: 1038123159888 -ItemId: 1038123159888 - -{ - "item_id": 1038123159888, - "seller_id": 2209880145223, - "MsgTypeDes": "交易成功", - "biz_order_id": "4502265554049011443", - "order_status": 4, - "_agisoTradeDetail": { - "item": { - "price": 4000, - "title": "(现货秒发)海底捞第五人格联动限定兑换码cdk拉拉队员皮肤+", - "item_id": 1038123159888, - "pic_url": "https://gw.alicdn.com/bao/uploaded/i2/2209880145223/O1CN01xGf3721oSBmd1W0DI_!!4611686018427387207-53-xy_item.heic" - }, - "payment": 4000, - "end_time": 1776143786000, - "pay_time": 1775279772000, - "post_fee": 0, - "isEticket": false, - "ship_time": 1775279779000, - "buy_amount": 1, - "buyer_nick": "b***5", - "isRecharge": false, - "create_time": 1775279753000, - "seller_nick": "大锤号商", - "biz_order_id": "4502265554049011443", - "order_status": 4, - "coupon_eticket_info": [], - "encryption_buyer_id": "6iabfI7bB2cCa4y8JR2VQw==" - } -} \ No newline at end of file diff --git a/apps/backend/data/khhao-sync-state.json b/apps/backend/data/khhao-sync-state.json index cdcf4ee9..019628ea 100644 --- a/apps/backend/data/khhao-sync-state.json +++ b/apps/backend/data/khhao-sync-state.json @@ -1,13 +1,13 @@ { "syncFromCreatedAt": "2026-05-02T10:53:28.550Z", - "lastRunStartedAt": "2026-05-04T01:42:53.462Z", - "lastRunFinishedAt": "2026-05-04T01:42:54.077Z", + "lastRunStartedAt": "2026-05-04T13:32:11.217Z", + "lastRunFinishedAt": "2026-05-04T13:32:11.824Z", "lastRunStatus": "success", "lastErrorMessage": "", "fetchedCount": 40, "syncedCount": 0, "ignoredCount": 40, - "lastOrderCreatedAt": "2026-05-04T01:42:50.000Z", + "lastOrderCreatedAt": "2026-05-04T13:32:06.000Z", "watchMode": "normal", "watchOrders": [] } diff --git a/apps/backend/data/order-fulfillment-bindings.json b/apps/backend/data/order-fulfillment-bindings.json index 7e909b15..6fffc22f 100644 --- a/apps/backend/data/order-fulfillment-bindings.json +++ b/apps/backend/data/order-fulfillment-bindings.json @@ -1,31 +1,4 @@ [ - { - "provider": "khhao", - "platform": "kuaishou", - "shopId": "4269276762", - "shopName": "稚嫩游戏交易店", - "khhaoShopId": "10", - "skuCode": "套装-浪漫天命", - "skuName": "套装-浪漫天命", - "profileKey": "kuaishou_ct_assisted", - "enabled": true, - "priority": 100, - "config": { - "kuaishouShop": { - "shopId": "4269276762", - "shopName": "稚嫩游戏交易店", - "khhaoShopId": "10" - } - }, - "match": { - "externalSkuCode": "830", - "externalItemId": "830", - "externalSkuName": "测试1", - "config": { - "resolvedSkuName": "套装-浪漫天命" - } - } - }, { "provider": "agiso", "platform": "xianyu", diff --git a/docs/91卡券/1.说明.md b/docs/91卡券/1.说明.md new file mode 100644 index 00000000..9b1a3f58 --- /dev/null +++ b/docs/91卡券/1.说明.md @@ -0,0 +1,6 @@ +# 本文档仅做示例说明,接口路径、签名规则、参数字段、入参格式等,可自定义。 + +# +# 注意,本业务对接非代码开发级别的对接,内部集成了模块人工参数配置实现的。部分特殊场景有所限制,主要涉及特殊的算法不支持、部分参数不支持传递等。 + +# 可以先编写好接口文档提供给客服,有问题客服会联系你哈 diff --git a/docs/91卡券/2.签名规则示例.md b/docs/91卡券/2.签名规则示例.md new file mode 100644 index 00000000..44b7f8d2 --- /dev/null +++ b/docs/91卡券/2.签名规则示例.md @@ -0,0 +1,45 @@ +# 签名计算规则示例: + +# 以下仅做示例说明,实际的签名规则可自行设计。 + +### 简要描述: +- 除sign字段外,所有参数按照字段名的ascii码从小到大排序后,使用QueryString的格式(即key1=value1&key2=value2…)拼接成字符串后,再在前后追加上 商户密钥 的值,然后转换成32位大写的MD5字符串。 +#### 注意事项:如空值参数是否参与签名需要在提供的文档中进行说明哈 + +**假设 商户密钥 值为:** rste57w8rsubsnxsb384ur3u9kn5fzhr0a091b3aa4324435aab703142518a8f7 + +**假设请求参数为:** +```csharp +{ + "userId":"1001", + "orderNo":"2023061917481700001", + "productNo":"test01", + "buyNum":1, + "attach":"{\"account\":\"13888888888\"}", + "maxAmount":"" + "timestamp":1687168097, + "callbackUrl":"", + "version":"1.0", + "sign":"EF387ED4D401A275498A9FCC6C22116B" +} +``` +**验签步骤:** +1. 转换成字符串如下: +``` +attach={"account":"13888888888"}&buyNum=1&callbackUrl=&maxAmount=&orderNo=2023061917481700001&productNo=test01×tamp=1687168097&userId=1001&version=1.0 +``` + +2. 前后追加上 商户密钥 的MD5源串如下: +``` +rste57w8rsubsnxsb384ur3u9kn5fzhr0a091b3aa4324435aab703142518a8f7attach={"account":"13888888888"}&buyNum=1&callbackUrl=&maxAmount=&orderNo=2023061917481700001&productNo=test01×tamp=1687168097&userId=1001&version=1.0rste57w8rsubsnxsb384ur3u9kn5fzhr0a091b3aa4324435aab703142518a8f7 +``` + +3. MD5后转成32位大写的签名值如下: +``` +EF387ED4D401A275498A9FCC6C22116B +``` + + + + + diff --git a/docs/91卡券/3.异步卡密下单.md b/docs/91卡券/3.异步卡密下单.md new file mode 100644 index 00000000..fcc68668 --- /dev/null +++ b/docs/91卡券/3.异步卡密下单.md @@ -0,0 +1,154 @@ +# 91卡券异步卡密下单接口 + +## 1. 接口说明 + +本文档为当前项目对接 `91卡券` 的正式技术文档,用于提交客服审核。 + +本项目对接场景说明: + +- `91卡券` 负责售卖与自动发货; +- 我方系统负责接收订单、创建任务、生成领取链接; +- 该领取链接会在后续查询订单接口中,作为最终卡密内容返回给 `91卡券`; +- `91卡券` 再将该链接自动发给买家。 + +注意: + +- 本接口为**异步卡密下单接口**; +- 首次下单成功受理后,返回 `orderStatus = 10`; +- **不会**在该接口首次响应中直接返回最终领取链接; +- 最终链接请通过“查询订单接口”获取。 + +## 2. 请求方向 + +- `91卡券平台 -> 接入方系统` + +## 3. 请求 URL + +请按以下地址配置: + +```text +POST https://221329.cc.cd/api/v1/open/91/orders/create +``` + +## 4. 请求方式 + +- `POST` +- `Content-Type: application/json;charset=utf-8` + +## 5. 签名规则 + +签名规则采用 签名规则示例 中的约定: + +- 除 `sign` 外,所有参数按字段名 ASCII 升序排序; +- 使用 `key=value&key=value` 方式拼接; +- 前后拼接商户密钥; +- 取 `MD5`,输出 32 位大写字符串; +- 空值参数参与签名。 + +## 6. 请求参数 + +| 参数名 | 必填 | 类型 | 说明 | +| --- | --- | --- | --- | +| `orderNo` | 是 | string | 商家订单号,唯一,用于幂等处理。 | +| `productNo` | 是 | string | 接入方商品编号。建议与我方内部履约 SKU 一一对应。 | +| `buyNum` | 是 | int | 购买数量。 | +| `maxAmount` | 否 | string | 商家可接受最大成本金额。值为整单金额,非单价。若传值,则我方按该金额校验,超出时返回失败。 | +| `callbackUrl` | 否 | string | 由 `91卡券` 提供的回调地址。当前项目可接收但不依赖该字段完成主流程。 | +| `timestamp` | 是 | long | 10 位秒级 Unix 时间戳,用于请求时效校验。 | +| `version` | 是 | string | 固定传 `1.0`。 | +| `sign` | 是 | string | 签名。 | + +## 7. 请求示例 + +```json +{ + "orderNo": "P91KS202605040001", + "productNo": "KS-CLOUD-SKU-001", + "buyNum": 1, + "maxAmount": "0.0000", + "callbackUrl": "https://cb.example.com/notify/91/order", + "timestamp": 1777867200, + "version": "1.0", + "sign": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +} +``` + +## 8. MD5 源串示例 + +假设: + +- `userId = 1001` +- 商户密钥为:`your_secret_key` + +则源串示例如下: + +```text +your_secret_keybuyNum=1&callbackUrl=https://cb.example.com/notify/91/order&maxAmount=0.0000&orderNo=P91KS202605040001&productNo=KS-CLOUD-SKU-001×tamp=1777867200&userId=1001&version=1.0your_secret_key +``` + +## 9. 业务处理规则 + +我方系统收到请求后,按以下规则处理: + +1. 校验签名与时间戳; +2. 按 `orderNo` 做幂等; +3. 校验 `productNo` 是否已映射到当前项目的快手履约商品; +4. 若传入 `maxAmount`,则校验成本是否超限; +5. 创建内部订单; +6. 创建快手 Cloud 履约任务; +7. 生成当前项目领取链接,例如:`https://221329.cc.cd/#/claim/{token}`; +8. 首次响应返回处理中状态,由 `91卡券` 后续调用查询订单接口获取最终卡密内容。 + +## 10. 响应参数 + +| 参数名 | 类型 | 必须返回 | 说明 | +| --- | --- | --- | --- | +| `orderNo` | string | 必须返回 | 商家订单号,来源 `91卡券` 下单请求。 | +| `outTradeNo` | string | 成功时必须 | 我方系统内部订单号。 | +| `orderStatus` | int | 成功时必须 | 订单状态。`10`:处理中;`30`:失败。
注意:当前接口为异步商品下单接口,首次响应**不会返回 `20`**。 | +| `orderCost` | decimal(14,4) | 成功时可返回 | 订单总成本,单位:元。若当前阶段无法确认,可返回 `0.0000`。 | +| `cards` | string | 非必须 | 当前阶段建议返回空字符串。最终卡密内容请在查询订单接口中返回。 | +| `failCode` | int | 失败时可返回 | 失败代码。 | +| `failReason` | string | 失败时可返回 | 失败原因。 | + +## 11. 响应示例 + +### 11.1 受理成功 + +```json +{ + "code": 200, + "message": "接口调用成功", + "data": { + "orderNo": "P91KS202605040001", + "outTradeNo": "OS202605040001", + "orderStatus": 10, + "orderCost": 0.0000, + "cards": "" + } +} +``` + +### 11.2 下单失败 + +```json +{ + "code": 200, + "message": "接口调用成功", + "data": { + "orderNo": "P91KS202605040001", + "outTradeNo": "", + "orderStatus": 30, + "failCode": 1220, + "failReason": "订单成本超出可接受范围" + } +} +``` + +## 12. 对接说明 + +- `productNo` 请按我方提供的商品编号配置; +- 本接口成功受理后,不代表最终卡密已可交付; +- 最终交付内容为我方系统生成的领取链接,链接域名固定为 `221329.cc.cd`; +- 该链接将在“查询订单接口”中,通过 `cards` 加密串返回; +- 建议 `91卡券` 将此商品配置为:`异步卡密商品`。 diff --git a/docs/91卡券/4.查询订单接口.md b/docs/91卡券/4.查询订单接口.md new file mode 100644 index 00000000..87d755d6 --- /dev/null +++ b/docs/91卡券/4.查询订单接口.md @@ -0,0 +1,203 @@ +# 91卡券查询订单接口 + +## 1. 接口说明 + +本文档为当前项目对接 `91卡券` 的正式技术文档,用于提交客服审核。 + +本接口用于在异步卡密下单后,由 `91卡券` 主动查询订单状态与最终卡密内容。 + +当前项目的最终交付物不是传统卡号密码,而是: + +- 我方系统生成的**领取链接**; +- 该链接将作为最终卡密内容,通过 `cards` 字段返回给 `91卡券`; +- `91卡券` 再使用自动发货能力,将该领取链接展示给买家。 + +## 2. 请求方向 + +- `91卡券平台 -> 接入方系统` + +## 3. 请求 URL + +请按以下地址配置: + +```text +POST https://221329.cc.cd/api/v1/open/91/orders/query +``` + +## 4. 请求方式 + +- `POST` +- `Content-Type: application/json;charset=utf-8` + +## 5. 签名规则 + +签名规则采用 2.签名规则示例 中的约定: + +- 除 `sign` 外,所有参数按字段名 ASCII 升序排序; +- 使用 `key=value&key=value` 方式拼接; +- 前后拼接商户密钥; +- 取 `MD5`,输出 32 位大写字符串; +- 空值参数参与签名。 + +## 6. 请求参数 + +| 参数名 | 必填 | 类型 | 说明 | +| --- | --- | --- | --- | +| `orderNo` | 是 | string | 商家订单号。 | +| `timestamp` | 是 | long | 10 位秒级 Unix 时间戳,用于请求时效校验。 | +| `version` | 是 | string | 固定传 `1.0`。 | +| `sign` | 是 | string | 签名。 | + +## 7. 请求示例 + +```json +{ + "orderNo": "P91KS202605040001", + "timestamp": 1777867260, + "version": "1.0", + "sign": "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" +} +``` + +## 8. MD5 源串示例 + +假设: + +- `userId = 1001` +- 商户密钥为:`your_secret_key` + +则源串示例如下: + +```text +your_secret_keyorderNo=P91KS202605040001×tamp=1777867260&userId=1001&version=1.0your_secret_key +``` + +## 9. 业务处理规则 + +我方系统收到查询请求后,按以下规则返回: + +1. 校验签名与时间戳; +2. 根据 `orderNo` 查询内部订单; +3. 若订单存在但领取链接尚未准备完成,返回 `orderStatus = 10`; +4. 若领取链接已生成,可交付,返回 `orderStatus = 20`; +5. 若订单无法履约,返回 `orderStatus = 30`; +6. 当返回 `20` 时,通过 `cards` 返回最终卡密内容。 + +## 10. 响应参数 + +| 参数名 | 类型 | 必须返回 | 说明 | +| --- | --- | --- | --- | +| `orderNo` | string | 必须返回 | 商家订单号,来源 `91卡券` 下单请求。 | +| `outTradeNo` | string | 必须返回 | 我方系统内部订单号。 | +| `orderStatus` | int | 必须返回 | 订单状态。`10`:处理中;`20`:成功;`30`:失败。 | +| `failCode` | int | 失败时建议返回 | 失败代码。可以不返回该字段,但不要返回 `null`。 | +| `failReason` | string | 失败时建议返回 | 失败原因。 | +| `orderCost` | decimal(14,4) | 成功时建议返回 | 订单总成本,单位:元。 | +| `cards` | string | `orderStatus = 20` 时必须 | 卡密数据加密串。当前项目通过该字段返回领取链接。 | +| `cards[0].cardNo` | string | 成功时必须 | 最终交付的领取链接,格式为 `https://221329.cc.cd/#/claim/{token}`。 | +| `cards[0].cardPwd` | string | 可选 | 当前场景固定为空字符串。 | +| `cards[0].expireTime` | string | 可选 | 领取链接过期时间,支持 `yyyy-MM-dd HH:mm:ss` 或 10 位秒级 Unix 时间戳。 | +| `cards[0].jumpLink` | string | 可选 | 与 `cardNo` 保持一致,用于兼容链接展示场景。 | + +## 11. cards 原始结构约定 + +由于已确认允许将链接作为最终卡密内容展示给买家,当前项目约定: + +- `cardNo`:直接放领取链接; +- `cardPwd`:留空; +- `expireTime`:放领取链接过期时间; +- `jumpLink`:与 `cardNo` 一致。 + +原始结构示例如下: + +```json +[ + { + "cardNo": "https://221329.cc.cd/#/claim/abc123xyz", + "cardPwd": "", + "expireTime": "2026-05-05 12:00:00", + "jumpLink": "https://221329.cc.cd/#/claim/abc123xyz" + } +] +``` + +若 `buyNum > 1`,则 `cards` 原始结构中返回多个卡项,每个卡项对应一个独立领取链接。 + +## 12. 响应示例 + +### 12.1 处理中 + +```json +{ + "code": 200, + "message": "接口调用成功", + "data": { + "orderNo": "P91KS202605040001", + "outTradeNo": "OS202605040001", + "orderStatus": 10, + "failCode": 0, + "failReason": "", + "orderCost": 0.0000, + "cards": "" + } +} +``` + +### 12.2 查询成功 + +```json +{ + "code": 200, + "message": "接口调用成功", + "data": { + "orderNo": "P91KS202605040001", + "outTradeNo": "OS202605040001", + "orderStatus": 20, + "failCode": 0, + "failReason": "", + "orderCost": 0.0000, + "cards": "加密后的cards字符串" + } +} +``` + +`cards` 解密前原始结构示例: + +```json +[ + { + "cardNo": "https://221329.cc.cd/#/claim/abc123xyz", + "cardPwd": "", + "expireTime": "2026-05-05 12:00:00", + "jumpLink": "https://221329.cc.cd/#/claim/abc123xyz" + } +] +``` + +### 12.3 查询失败 + +```json +{ + "code": 200, + "message": "接口调用成功", + "data": { + "orderNo": "P91KS202605040001", + "outTradeNo": "OS202605040001", + "orderStatus": 30, + "failCode": 1204, + "failReason": "商品未配置或订单无法履约" + } +} +``` + +## 13. 对接说明 + +- 本接口是 `91卡券` 自动发货的关键接口; +- 当返回 `orderStatus = 20` 时,表示我方已准备好最终交付内容; +- 最终交付内容是领取链接,不是传统卡号密码; +- 领取链接域名固定为 `221329.cc.cd`; +- 买家收到链接后,会进入我方系统领取页完成后续快手核销与履约流程; +- 建议 `91卡券` 侧确认: + - `cards.cardNo` 可直接展示完整链接; + - `jumpLink` 可按链接字段兼容展示; + - 查询频率按异步商品标准轮询配置。 diff --git a/docs/91卡券/5.当前项目对接配置.md b/docs/91卡券/5.当前项目对接配置.md new file mode 100644 index 00000000..3345bed8 --- /dev/null +++ b/docs/91卡券/5.当前项目对接配置.md @@ -0,0 +1,315 @@ +# 91卡券接入当前项目接口配置 + +## 1. 对接目标 + +- `91卡券` 作为外部售卖与自动发货通道。 +- 当前项目负责: + - 接收 `91卡券` 下单请求; + - 在系统内生成订单与快手履约任务; + - 生成当前项目自己的领取链接; + - 由 `91卡券` 通过自动发货,将该领取链接展示给买家。 +- 已确认:**允许将链接作为最终卡密内容展示给买家**。 + +## 2. 对接结论 + +本次对接不走 `Agiso/咸鱼` 的站内消息链路,而是走: + +1. `91卡券` 调用我方异步下单接口; +2. 我方创建内部订单,并生成领取链接; +3. `91卡券` 调用我方查询订单接口; +4. 我方在查询结果的 `cards` 中返回领取链接; +5. `91卡券` 自动发货给买家。 + +## 3. 接口地址建议 + +基础前缀建议: + +```text +https://你的域名/api/v1/open/91 +``` + +接口列表: + +- 异步卡密下单:`POST /api/v1/open/91/orders/create` +- 查询订单接口:`POST /api/v1/open/91/orders/query` + +## 4. 签名配置 + +签名规则采用当前文档里的示例规则: + +- 除 `sign` 外,所有参数按字段名 ASCII 升序排序; +- 使用 `key=value&key=value` 方式拼接; +- 前后拼接商户密钥; +- 取 `MD5`,输出 32 位大写字符串。 + +建议固定配置: + +- `signType`: `MD5` +- `charset`: `UTF-8` +- `timestamp`: 10 位秒级 Unix 时间戳 +- `version`: `1.0` +- 空值参数是否参与签名:`参与` + +## 5. 业务字段映射 + +### 5.1 下单请求 -> 当前项目 + +| 91字段 | 当前项目用途 | 说明 | +| --- | --- | --- | +| `orderNo` | 外部订单号 / 幂等键 | 建议映射为内部 `platformOrderId` | +| `productNo` | 商品映射 | 映射到内部 SKU 或履约绑定规则 | +| `buyNum` | 购买数量 | 生成对应数量的履约任务 | +| `maxAmount` | 成本上限校验 | 可选,超限返回失败 | +| `callbackUrl` | 备用回调地址 | 先保留,不作为主流程依赖 | +| `timestamp` | 防重放 | 校验请求时效 | +| `version` | 版本 | 固定 `1.0` | +| `sign` | 验签 | 必填 | + +### 5.2 当前项目内部建议映射 + +建议新增一个独立来源: + +- `provider = '91kaquan'` +- `platform = 'kuaishou'` + +商品绑定建议: + +- `productNo` 对应当前项目内部 `skuCode` +- 再由现有快手履约配置,匹配到 `kuaishou_ct_assisted` 履约链路 + +## 6. 异步下单接口配置 + +### 6.1 请求方向 + +- `91卡券 -> 当前项目` + +### 6.2 请求地址 + +```text +POST /api/v1/open/91/orders/create +Content-Type: application/json;charset=utf-8 +``` + +### 6.3 请求参数 + +| 参数名 | 必填 | 类型 | 说明 | +| --- | --- | --- | --- | +| `orderNo` | 是 | string | 91 商家订单号,唯一 | +| `productNo` | 是 | string | 我方商品编号 | +| `buyNum` | 是 | int | 购买数量 | +| `maxAmount` | 否 | string | 可接受最大成本金额 | +| `callbackUrl` | 否 | string | 91 提供的回调地址 | +| `timestamp` | 是 | long | 10 位秒级时间戳 | +| `version` | 是 | string | 固定 `1.0` | +| `sign` | 是 | string | 签名 | + +### 6.4 请求示例 + +```json +{ + "orderNo": "P91KS202605040001", + "productNo": "KS-CLOUD-SKU-001", + "buyNum": 1, + "maxAmount": "0.0000", + "callbackUrl": "https://cb.example.com/notify/91/order", + "timestamp": 1777867200, + "version": "1.0", + "sign": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +} +``` + +### 6.5 下单处理规则 + +- 验签失败:直接返回失败。 +- `orderNo` 已存在:按幂等处理,返回已有订单状态。 +- `productNo` 未配置:返回失败。 +- 成本超限:返回失败,错误码可用 `1220`。 +- 下单成功后: + - 创建内部订单; + - 创建快手履约任务; + - 生成领取链接; + - 异步商品首次响应返回 `10`,表示处理中。 + +### 6.6 响应字段约定 + +| 参数名 | 类型 | 必填 | 说明 | +| --- | --- | --- | --- | +| `orderNo` | string | 是 | 原样返回 | +| `outTradeNo` | string | 是 | 我方内部订单号 | +| `orderStatus` | int | 是 | 异步商品固定返回 `10` 或 `30` | +| `orderCost` | decimal | 否 | 成功受理时可返回 | +| `cards` | string | 否 | 异步下单阶段建议为空 | + +### 6.7 响应示例 + +```json +{ + "code": 200, + "message": "接口调用成功", + "data": { + "orderNo": "P91KS202605040001", + "outTradeNo": "OS202605040001", + "orderStatus": 10, + "orderCost": 0.0000, + "cards": "" + } +} +``` + +## 7. 查询订单接口配置 + +### 7.1 请求方向 + +- `91卡券 -> 当前项目` + +### 7.2 请求地址 + +```text +POST /api/v1/open/91/orders/query +Content-Type: application/json;charset=utf-8 +``` + +### 7.3 请求参数 + +| 参数名 | 必填 | 类型 | 说明 | +| --- | --- | --- | --- | +| `orderNo` | 是 | string | 91 商家订单号 | +| `timestamp` | 是 | long | 10 位秒级时间戳 | +| `version` | 是 | string | 固定 `1.0` | +| `sign` | 是 | string | 签名 | + +### 7.4 查询处理规则 + +- 找不到订单:返回失败。 +- 订单已创建但领取链接未准备好:返回 `orderStatus = 10`。 +- 领取链接已生成,可交付:返回 `orderStatus = 20`。 +- 订单无法履约:返回 `orderStatus = 30`,并带失败原因。 + +### 7.5 响应字段约定 + +| 参数名 | 类型 | 必填 | 说明 | +| --- | --- | --- | --- | +| `orderNo` | string | 是 | 原样返回 | +| `outTradeNo` | string | 是 | 我方内部订单号 | +| `orderStatus` | int | 是 | `10`处理中,`20`成功,`30`失败 | +| `failCode` | int | 否 | 失败时返回 | +| `failReason` | string | 否 | 失败时返回 | +| `orderCost` | decimal | 否 | 成功时返回 | +| `cards` | string | 成功时必须 | 卡密加密串 | + +## 8. cards 字段配置 + +### 8.1 推荐方案 + +既然已确认允许将链接作为最终卡密内容展示给买家,建议: + +- `cardNo` 直接放领取链接; +- `cardPwd` 留空; +- `expireTime` 可放领取链接过期时间; +- `jumpLink` 可与 `cardNo` 保持一致,作为兼容字段。 + +这样做的好处是: + +- `91卡券` 自动发货可直接展示链接; +- 复杂文案、快手操作说明、核销码校验、角色确认,全部放在我方领取页完成; +- 降低 91 展示层格式差异带来的风险。 + +### 8.2 单卡结构建议 + +```json +[ + { + "cardNo": "https://你的域名/claim/abc123xyz", + "cardPwd": "", + "expireTime": "2026-05-05 12:00:00", + "jumpLink": "https://你的域名/claim/abc123xyz" + } +] +``` + +### 8.3 多数量建议 + +如果 `buyNum > 1`: + +- 每个数量生成一个独立领取链接; +- `cards` 中放多个卡项; +- 每个 `cardNo` 对应一个独立链接。 + +## 9. 查询成功响应示例 + +```json +{ + "code": 200, + "message": "接口调用成功", + "data": { + "orderNo": "P91KS202605040001", + "outTradeNo": "OS202605040001", + "orderStatus": 20, + "failCode": 0, + "failReason": "", + "orderCost": 0.0000, + "cards": "加密后的cards字符串" + } +} +``` + +`cards` 解密前原始结构建议为: + +```json +[ + { + "cardNo": "https://你的域名/claim/abc123xyz", + "cardPwd": "", + "expireTime": "2026-05-05 12:00:00", + "jumpLink": "https://你的域名/claim/abc123xyz" + } +] +``` + +## 10. 状态流转建议 + +### 10.1 91侧状态 + +- `10`: 已下单,当前项目正在生成领取链接 +- `20`: 已可交付,91 可自动发货给买家 +- `30`: 无法履约 + +### 10.2 当前项目侧状态建议 + +- 创建订单成功:进入待履约 +- 已生成 `claimUrl`:视为可交付 +- 查询接口检测到 `claimUrl` 已存在:返回 `20` +- 配置缺失、商品未匹配、履约初始化失败:返回 `30` + +## 11. 商品配置建议 + +建议在你方给 91 的商品配置中约定: + +- `productNo`:对应当前项目内部的快手履约 SKU +- 商品名称:明确标注“自动发链接” +- 发货类型:异步卡密 +- 买家收到内容:领取链接 + +## 12. 对接方需确认的固定项 + +发给 `91卡券` 客服/对接方时,建议一次性确认以下配置: + +- 异步卡密下单 URL +- 查询订单 URL +- `userId` +- 商户密钥 +- 签名算法是否按本文档固定 +- `cards` 中 `cardNo` 直接展示链接是否按预期显示 +- `jumpLink` 是否会同步展示或仅作兼容字段 +- 查询频率与超时要求 + +## 13. 最终建议 + +这次对接的最稳方案是: + +- `91` 负责卖货与自动发货; +- 当前项目负责生成 `claimUrl`; +- `claimUrl` 作为最终卡密内容,通过 `cards.cardNo` 返回; +- 所有快手专属引导和后续交互,都留在当前项目领取页中完成。 + +这样对当前项目改动最小,也最符合现在快手 Cloud 履约链路的设计。 diff --git a/前后端现状分析.md b/前后端现状分析.md index 08c7fb87..3daa854e 100644 --- a/前后端现状分析.md +++ b/前后端现状分析.md @@ -2,25 +2,30 @@ ## 当前结论 -整体架构方向没有跑偏,后端主干仍然是 `routes -> services -> repositories`,前端也已经逐步从“巨石页面”往“薄页面 + composable + 子组件”推进。 +整体架构方向没有跑偏。 -现在最需要继续优化的,不是全项目平均整理,而是两类高风险区域: +- 后端主干仍然是 `routes -> services -> repositories` +- 前端后台管理也已经从“单页巨石”逐步转成“薄页面 + composable + 子组件 + 分领域 service” -1. 已经明显长成巨石、后续改动容易连锁的后端运行时模块 -2. 已经完成第一轮拆分、但还需要收口和补测试的前端后台配置模块 +和最初相比,当前最重要的变化有两点: + +1. 平台配置这条前后端竖线,第一阶段拆分已经基本完成 +2. 真正还需要优先处理的,已经转向后端运行时巨石模块 + +所以现在不适合再把精力平均分散到全项目,而应该把已经拆开的区域收口,把还没拆开的大文件优先解决。 ## 已完成 -### 平台配置页前端拆分 +### 1. 平台配置页前端拆分 这部分已经不再是最初判断里的“一个页面承载四套完整平台流”。 - `8e1a4be` 拆分前端后台管理 API 客户端 - `apps/frontend/src/services/admin.ts` 已改成聚合入口 - - 后台 API 已按领域拆到 `services/admin/` 目录 + - 后台 API 已按领域拆到 `apps/frontend/src/services/admin/` - `dff7da7` 拆分平台配置页脚本逻辑 - - `AdminPlatformShopsView.vue` 的平台状态和行为已经拆到多个 composable + - `apps/frontend/src/views/admin/AdminPlatformShopsView.vue` 的平台状态和行为已经拆到 composable - 已有: - `useAdminAgisoPlatform.ts` - `useAdminKhhaoPlatform.ts` @@ -30,9 +35,7 @@ - `4650c3e` 抽离平台配置页共享样式 - 共享样式已统一收口到 `apps/frontend/src/styles/admin-platform-shops.css` -- `60c2d91` 拆分平台配置页 Agiso 与 khhao 区块 -- `254a031` 拆分平台配置页快手核销区块 -- `8f6a72f` 拆分平台配置页履约调试区块 +- `60c2d91`、`254a031`、`8f6a72f` 完成平台区块拆分 - 四个平台区块已经分别下沉为独立组件: - `AdminPlatformAgisoSection.vue` - `AdminPlatformKhhaoSection.vue` @@ -43,134 +46,120 @@ - 新增 `AdminResultCard.vue` - `khhao` / `快手核销` / `cloudtentacles` 的重复结果卡片已统一 -### 现阶段对平台配置页的重新判断 +### 2. `session-proof.js` 已完成第一轮拆分 -`apps/frontend/src/views/admin/AdminPlatformShopsView.vue` 仍然不算小,但问题性质已经变了: +这条线已经不是“下一步待开始”,而是已经落地完成。 -- 现在主要问题不再是“大模板混杂” -- 现在的主要问题是: - - 父页面 `script setup` 仍然偏胖 - - 几个子组件之间还有少量可复用片段 - - 前端缺少测试防线 +当前 `apps/backend/src/services/session/` 下已经拆出: -也就是说,这条线的第一阶段“拆分大页面”已经基本完成,后面不应该继续无限抽象,而应该适时收口。 +- `session-proof-mode.js` +- `session-proof-paths.js` +- `session-proof-result-writer.js` +- `session-proof-beijing-time.js` +- `session-proof-renderer.js` +- `session-proof-html.js` +- `session-proof-constants.js` + +`apps/backend/src/services/session/session-proof.js` 现在已经收敛成门面层,文件体量约 `145` 行,风险比之前明显下降。 + +### 3. 后台平台配置服务已完成按平台拆分 + +这条线也已经不是最初的 `admin-platform-config-service.js` 巨石形态了。 + +最近这波提交已经完成: + +- `d629bc9` 整理后台平台配置服务目录结构 +- `d11879f` 下沉后台履约规则校验编排逻辑 +- `8c9d487` 下沉后台云触手会话结果组装逻辑 +- `dad646b` 抽离后台平台配置写入归一化逻辑 +- `60b84b2` 下沉后台 khhao 平台辅助组装逻辑 +- `3a7c311` 按平台拆分后台平台配置服务 + +当前 `apps/backend/src/services/admin/platform-config/` 已形成按职责拆分的目录: + +- `service.js` + - 只保留统一导出入口 +- `agiso-service.js` +- `khhao-service.js` +- `kuaishou-eticket-service.js` +- `cloudtentacles-service.js` +- `fulfillment-bindings-service.js` +- `kuaishou-cloud-fulfillment-service.js` +- 配套 helper / mapper / validation / test 文件 + +其中 `service.js` 已经从原来的 600+ 行缩到 53 行,平台配置后端竖线的第一阶段拆分已经完成。 ## 已过时的判断 -下面这些结论需要更新,不再按最初版本理解: +下面这些判断不应该再按旧版本理解。 -### `AdminPlatformShopsView.vue` 是前端第一优先级 +### 1. `AdminPlatformShopsView.vue` 是前端第一优先级 -这个判断已经部分过时。 +这个判断已经过时。 原因: -- 该页面的四套平台模板已经完成拆分 -- 平台行为已经移动到 composable -- 剩余问题主要是“收口”和“测试”,不是继续大拆 +- 四个平台模板已经拆开 +- 主要行为已经下沉到 composable +- 剩余问题更多是收口、少量复用和测试,而不是继续大拆页面 新判断: - 这条线仍可继续小步优化 -- 但优先级已经低于后端的 `session.js`、`session-proof.js`、`admin-platform-config-service.js` +- 但优先级已经明显低于后端 `session.js`、`claim-session-service.js`、`admin-write-service.js` -### 继续深挖前端平台配置页 +### 2. `admin-platform-config-service.js` 是后端当前主战场 -这个方向不应该再作为主战场。 +这个判断也已经过时。 -剩余可做项有,但应该控制强度: +原因: -- 可以继续抽 `section-title-row` 或 `meta-card` 这一层公共片段 -- 但不能为了统一而统一 -- 再往下更应该转向后端高风险链路 +- 这条线已经迁到 `apps/backend/src/services/admin/platform-config/` +- 统一入口 `service.js` 已经很薄 +- 大部分公共逻辑也已经拆到 `context.js`、`writes.js`、`validation.js`、`mappers.js`、`fulfillment.js` 等模块 + +新判断: + +- 平台配置后端已经从“继续大拆”阶段,进入“适度收口 + 补测试 + 控制 cloudtentacles 继续膨胀”阶段 + +### 3. 下一刀应该从 `session-proof.js` 开始 + +这个判断也已过时。 + +原因: + +- `session-proof.js` 第一轮拆分已经完成 +- 现在更该处理的是仍然明显偏大的运行时主线文件 ## 仍然优先拆分 / 优先优化 -### 1. `apps/backend/src/services/session/session.js` +### 1. `apps/backend/src/services/admin/admin-write-service.js` -后端第一优先级,判断不变。 +这是现在最胖、最值得优先处理的大文件之一,当前约 `1710` 行。 -它同时承担: +它更像“后台万能动作总线”,跨域过多: -- Playwright 浏览器生命周期 -- 会话内存状态 -- 二维码抓取 -- 登录态判断 -- 角色信息同步 -- 兑换执行 -- 截图产物 -- 自动关闭 +- 库存 +- claim +- webhook +- cloudtentacles +- 快手核销 +- 腾讯 session +- task 手工动作 -而且仍然是 `@ts-nocheck`。这类文件每继续增长一次,后面改动成本都会上升。 +建议方向: -建议目标: +- `admin-inventory-write` +- `admin-task-actions` +- `admin-claim-actions` +- `admin-webhook-actions` +- `admin-manual-redeem-actions` -- `browser-runtime` -- `session-store` -- `session-presentation` -- `session-redeem-orchestrator` -- `session-persistence` +如果继续放大,这类文件会成为后续所有后台动作改动的冲突中心。 -### 2. `apps/backend/src/services/session/session-proof.js` +### 2. `apps/backend/src/services/claim/claim-session-service.js` -这是下一步最合适的切入点。 - -当前职责混在一起: - -- 证明模式判定 -- 产物路径拼接 -- 结果页 DOM 注入 -- 北京时间来源抓取 -- 截图拼接 -- 结果 JSON 落盘 - -这类文件比 `session.js` 更小,适合作为后端拆分的第一刀,风险更低,能先把拆分节奏跑顺。 - -建议拆分方向: - -- `proof-mode` - - 处理 `resolveRedeemProofMode` - - 处理 `shouldCaptureBeijingTimeProof` - -- `proof-paths` - - 处理 `buildArtifactPaths` - -- `proof-result-writer` - - 处理 `writeRedeemResultFile` - -- `proof-beijing-time` - - 处理北京时间页面抓取与截图 - -- `proof-renderer` - - 处理结果弹层、合成图、截图表现逻辑 - -先保留 `session-proof.js` 作为门面导出层,第一阶段不要改公开接口。 - -### 3. `apps/backend/src/services/admin/admin-platform-config-service.js` - -这条线依然偏胖,而且和前端平台配置页是一整条竖线。 - -虽然前端页面已经拆开,但后端这条 service 仍然同时承载: - -- 平台配置读写 -- 登录测试 -- 订单查询 -- SKU 调试 -- 虚拟号调试 -- 履约绑定 - -建议后续按平台拆为: - -- `agiso-admin-service` -- `khhao-admin-service` -- `kuaishou-eticket-admin-service` -- `cloudtentacles-admin-service` - -同时把“配置读写”和“调试接口”分层处理。 - -### 4. `apps/backend/src/services/claim/claim-session-service.js` - -仍然是后续重点。 +当前约 `1255` 行,仍然是后端运行时第二优先级。 问题主要在: @@ -184,29 +173,50 @@ - `claim-task-sync` - `claim-fulfillment-finalizer` -### 5. `apps/backend/src/services/admin/admin-write-service.js` +这条线和下单、兑换、任务状态强相关,越晚拆越难动。 -依然偏胖,但优先级略低于上面三项。 +### 3. `apps/backend/src/services/session/session.js` -它更像“后台万能动作总线”,跨域过多: +当前约 `566` 行,虽然比前两项小,但仍然是高风险运行时核心。 -- 库存 -- claim -- webhook -- cloudtentacles -- 快手核销 -- 腾讯 session +它同时承担: -建议后续至少拆成: +- Playwright 浏览器生命周期 +- 会话内存状态 +- 二维码抓取 +- 登录态判断 +- 角色信息同步 +- 兑换执行 +- 自动关闭 -- `admin-inventory-write` -- `admin-task-actions` -- `admin-webhook-actions` -- `admin-manual-redeem-actions` +好消息是:`session-proof.js`、`session-qq.js`、`session-wx.js`、`session-redeem.js`、`session-page.js`、`session-state.js` 等子模块已经存在,说明这条线并不是没拆过。 -### 6. `apps/backend/src/services/webhook/webhook-service.js` +当前更合适的目标不是“从零拆分”,而是继续把 `session.js` 剩余的编排职责下沉,最终把它收敛成真正的 orchestrator。 -仍然适合改成流水线。 +### 4. 平台配置竖线的收口项 + +虽然平台配置的大拆分已经完成,但这条线还有一些第二优先级问题: + +- `apps/backend/src/services/admin/platform-config/cloudtentacles-service.js` + - 当前约 `250` 行 + - 已经比原来小很多,但仍同时承载登录、会话校验、商品目录、虚拟号调试、完整 flow 调试 + - 如果后面 cloudtentacles 功能继续增加,可以进一步拆成: + - `cloudtentacles-auth-service` + - `cloudtentacles-catalog-service` + - `cloudtentacles-virtual-number-service` + +- `apps/frontend/src/views/admin/AdminPlatformShopsView.vue` + - 当前约 `402` 行 + - 已经不是巨石模板问题,剩下主要是 overview 卡片和页面级加载编排还在父层 + +- `apps/frontend/src/services/admin/platform-config.ts` + - 当前约 `373` 行 + - 还可以进一步按平台拆成更细的 `services/admin/platform-config/` 子文件 + - 但这属于可做项,不是最高优先级 + +### 5. `apps/backend/src/services/webhook/webhook-service.js` + +这条线仍然适合改成流水线。 建议拆成: @@ -217,7 +227,9 @@ - `webhook-process` - `webhook-replay` -### 7. `apps/backend/src/config/runtime.js` +它不是眼下最大的文件,但属于典型“流程很多、分支很多、回归成本高”的区域。 + +### 6. `apps/backend/src/config/runtime.js` 仍需要“配置声明化”优化。 @@ -235,25 +247,26 @@ ## 质量防线现状 -这个判断仍然成立,而且重要性上升了。 +这个判断仍然成立,而且现在重要性更高。 当前风险: - 后端高风险模块仍然存在 `@ts-nocheck` -- 前端后台配置页最近已经连续多次重构 -- 目前主要依靠 `typecheck + build` 防回归 +- 平台配置页和后台平台配置服务最近连续经历了多轮重构 +- 当前主要还是依赖 `typecheck + test + build` 防回归 这能挡住: - 类型问题 -- 组件引用问题 -- 构建问题 +- 引用错误 +- 明显的构建错误 +- 一部分纯函数退化 -但挡不住: +但仍挡不住: -- 交互退化 -- 条件渲染错误 -- 任务状态流转异常 +- 页面条件渲染退化 +- 管理台复杂交互异常 +- 任务状态流转串线 - 外部平台调试链路断裂 建议至少补两层最小防线: @@ -263,8 +276,8 @@ - 关键按钮存在性 / 条件渲染测试 2. 后端 - - `session-proof.js` 纯函数单测 - - 快手 cloud / claim / task 状态流转的集成 smoke 测试 + - `claim-session-service.js` 关键状态流转测试 + - `admin-write-service.js` 高风险动作 smoke 测试 ## 暂时不用急着拆 @@ -272,9 +285,9 @@ - `TencentBrowserView.vue` - `session-qq.js` -- 对应 `wx` 变体 +- `session-wx.js` -这些区域当前模式反而比较健康,后续可以作为其它模块拆分时的参照。 +这些区域当前模式相对健康,仍然可以作为其它模块拆分时的参考。 ## 下一步计划 @@ -285,55 +298,58 @@ 3. 抽离平台配置页共享样式 4. 拆分 Agiso / khhao / 快手核销 / cloudtentacles 四个平台区块 5. 抽离结果信息卡片 +6. 拆分 `session-proof.js` +7. 重构后台 `platform-config` 目录并完成按平台拆分 -### 下一阶段 +### 下一阶段建议 -#### Phase 1:从 `session-proof.js` 开始 +#### Phase 1:先处理 `admin-write-service.js` 目标: -- 先拆一个比 `session.js` 更小、更收敛的后端高风险文件 -- 建立后端拆分模板 -- 不改公开接口,不改产物格式,不改调用方 +- 优先拆掉当前最大的后台动作总线 +- 先按职责切片,不改路由公开接口 +- 把高频变更区域从一个文件拆成多个动作模块 建议步骤: -1. 抽纯函数 - - proof mode - - proof path - - result writer +1. 先按动作域分段 + - inventory + - task + - claim + - webhook + - redeem -2. 抽副作用模块 - - 北京时间截图采集 - - 证明图合成 +2. 把纯组装和副作用编排分开 -3. 保留门面文件 - - `session-proof.js` 暂时继续导出原接口 +3. 保留 `admin-write-service.js` 作为门面导出层 -4. 每个小阶段都跑校验并单独提交 - -#### Phase 2:再处理 `session.js` +#### Phase 2:再处理 `claim-session-service.js` 前提: -- `session-proof.js` 拆分完成 -- 对这条运行时链路已经熟悉 +- `admin-write-service.js` 的动作边界更清楚 +- claim / task / inventory 之间的依赖关系已经更容易梳理 -#### Phase 3:回到平台配置后端竖线 +#### Phase 3:继续收口 `session.js` -重点转向: +重点: -- `admin-platform-config-service.js` -- 相关 route -- 相关调试接口 +- 不是重写 +- 而是继续把剩余运行时编排下沉到已有 session 子模块 + +#### Phase 4:补平台配置页与后台动作的最小 smoke 测试 + +这一步不一定最先做,但应该尽早插入,避免后续继续重构时缺乏防线。 ## 当前建议 -前端平台配置页这条线可以暂时收口,不建议再继续大规模抽模板。 +平台配置这条前后端竖线现在可以先收口,不建议继续把它当主战场。 -下一步最合适的是: +更合适的下一步是: -1. 开始拆 `apps/backend/src/services/session/session-proof.js` -2. 同时补一层最小测试防线 +1. 开始拆 `apps/backend/src/services/admin/admin-write-service.js` +2. 同步梳理 `claim-session-service.js` 的职责边界 +3. 尽快补一层最小 smoke 测试 -如果继续执行,下一刀就从 `session-proof.js` 开始。 +如果继续执行,下一刀最值得从 `admin-write-service.js` 开始。