docs: 清理过时文档(以代码为准)
删除(整篇已被代码淘汰 / 重复副本 / 与仓库无关):
- cloudflared_tunnel.txt:全仓无 cloudflared 配置,个人终端记录,无任何引用
- 履约配置/工程债-lewan拆分与短链.md:文中 services/kuaishou-cloud 与 kuaishou-cloud-executor 已删除
- 履约配置/快手Cloud领取mock.md:mock:claim / mock:feifei 与 dev-mock 卡片已移除(只剩 mock:open91)
- 行业电子凭证/解决方案/技术方案/1.基础技术.md(+ 其孤立 image.png):0.基础技术.md 的 75 行截断副本
- 寄卖库存功能设计与实施方案.md:手工入库已 409 停用、收集链接表已随 079 删除、统计看板未实现
- 服务器图片迁移到阿里云OSS.md:MinIO→OSS 迁移已完成(生产 STORAGE_MODE=oss)
- pgBackRest备份方案分析.md:全仓无 pgBackRest 落地痕迹,文档自述暂缓实施
- finance-implementation-plan.md:财务模块已落地(072 迁移 / RBAC / 页面接口),已实现部分与代码重复
(未落地的二期三期路线图可从本提交的历史版本取回)
改写为与代码一致(已下线通道不再当作现行能力):
- 履约配置/多发货平台与电子凭证整体链路.md:kuaishou-cloud / kuaishou-feifei 标注为已下线 legacy stub,
现行自动通道 affiliate-dash、人工 manual_dispatch;核销时机与券表按实现校正
- 履约配置/{UID领取与旧单兼容, open91请求, TypeScript类型收紧}.md:lewan/feifei/cloudtentacles 表述改为
现行 affiliate-dash 链路;normalize 具名类型 KuaishouCloudFlow/FeifeiFlow → AffiliateDashFlow
- affiliate_dash_发货平台/affiliate-dash对接.md:§13/§14/§19/§21/§24/§25 的已删文件与已下线通道引用校正,
§21/§22 加「当时快照 / 落地后现状」横幅,回调去重表按 013_affiliate_dash_webhook_events.sql 改写
- 91卡券/{3.异步卡密下单, 5.当前项目对接配置, 6.开发设计}.md:删掉不存在的后台入口、
cloudtentacles/kuaishou_ct_assisted 依赖、cardsEncoding 与 .js 代码落点校正
- 行业电子凭证/技术方案.md:checklist 指向不存在的 测试用例.md → gen-test-cases.ts + 5.测试指南.md
- 履约配置/cloudtentacles发货平台.md:补「本仓库未接入该通道」说明(保持外部参考)
- 部署启动与数据库重建.md:补齐生产必需变量 APP_DOMAIN / CORS_ALLOWED_ORIGINS / STORAGE_MODE / STORAGE_OSS_*
其它:
- README.md:迁移序号范围 002~009 → 002~080
- 寄卖系统迁移079上库前检查.sql:第 2 段注释按批次 Y 的自动去重更新;新增 2.1「重复退款风险」只读排查
(已在一次性 PostgreSQL 16 上跑通 001→080 迁移:整份脚本 exit=0,脏数据下 2/2.1 均按预期报警)
- 寄卖系统优化方案.md:删掉链路图里已下线的「客服手工录入」,去掉指向已删设计稿的引用
This commit is contained in:
@@ -119,7 +119,7 @@ SKIP_CHECKS=1 git push
|
||||
| 文件 | 含义 |
|
||||
| --- | --- |
|
||||
| `001_init.sql` | 空库基线,建立完整业务结构 |
|
||||
| `002_xxx.sql` ~ `009_xxx.sql` | 后续结构变更的增量 SQL(含接单平台 004~009) |
|
||||
| `002_xxx.sql` ~ `080_xxx.sql` | 后续结构变更的增量 SQL(接单平台 004~009,寄卖相关 077~080) |
|
||||
|
||||
约定:
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ order_site 在 affiliate_dash 开通一个商户账号 + API 客户端,下单
|
||||
| **order_site** | 91 进单、商品匹配、履约任务、统一领取页、玩家信息收集、回调接收、电子凭证核销、人工兜底 | 不直接对接皮肤源头;不管理 affiliate_dash 钱包 |
|
||||
| **affiliate_dash** | 商户建单扣款、发货数据查询、绑定、对源头履约、回调推送 | 不接触 91 卡券,不生成 order_site 的 claimUrl |
|
||||
|
||||
> 对接方式与现有 `kuaishou-feifei` 执行器同构:order_site 新增一个 `affiliate_dash` executor,复用一个 `preparePaidTask` + `resolveDeliveryLink`,对外仍然只返回本站统一领取链接。
|
||||
> 对接方式沿用既有 executor 模式(`preparePaidTask` + `resolveDeliveryLink`):order_site 新增一个 `affiliate_dash` executor,对外仍然只返回本站统一领取链接。历史上 `kuaishou-feifei` / `kuaishou-cloud` 也走该模式,现已下线,只保留只读 legacy stub(`services/fulfillment/executors/legacy-retired-executor.ts`)。
|
||||
|
||||
---
|
||||
|
||||
@@ -343,8 +343,8 @@ order_site 处理要求:
|
||||
|
||||
## 13. 参考代码位置
|
||||
|
||||
- order_site executor 模式:`apps/backend/src/services/fulfillment/executors/kuaishou-feifei-executor.ts`(最接近,同为「建单 + 统一领取链接」)、`registry.ts`(注册)、`types.ts`(接口定义)
|
||||
- order_site 平台对接样例:`apps/backend/src/services/platforms/kuaishou-feifei/`
|
||||
- order_site executor 模式:`apps/backend/src/services/fulfillment/executors/affiliate-dash-executor.ts`(现行唯一自动履约 executor,「建单 + 统一领取链接」)、`registry.ts`(注册)、`types.ts`(接口定义);`kuaishou-feifei-executor.ts` 已随通道下线删除,历史通道在 `registry.ts:18-19` 映射到 `legacy-retired-executor.ts`(只读 stub)
|
||||
- order_site 平台对接样例:`apps/backend/src/services/platforms/affiliate-dash/`(旧 `services/platforms/kuaishou-feifei/` 已删除)
|
||||
- order_site 履约配置:`apps/backend/src/repositories/fulfillment-profile-repo.ts`、`apps/backend/src/routes/admin/platform-config/fulfillment-routing.ts`
|
||||
- affiliate_dash 接口契约(在线文档/字段定义):`frontend/src/openapi/endpoints.ts`;鉴权实现:`backend/internal/middleware/open_auth.go`(商户侧)、`backend/internal/service/callback.go`(回调签名 `BuildCallbackSign`)
|
||||
|
||||
@@ -372,9 +372,9 @@ order_site 处理要求:
|
||||
### 14.2 order_site 落地要点
|
||||
|
||||
- **client_order_no 优先用平台订单号**:单笔任务直接使用订单号;拆单使用 `订单号-序号`,均在 affiliate-dash 的 64 字符限制内。订单和任务集合创建后保持稳定,因此 `preparePaidTask` 失败重试会重放同一个幂等单号。
|
||||
- **preparePaidTask 同步拿到 order_no**(201 响应即含),写入 `context_json.affiliateDash.orderNo`;无需等回调。失败必须降级 `MANUAL_REVIEW` + `deps.notifyTaskAutoManualReview`(范式:kuaishou-feifei-executor.ts:36-51)。
|
||||
- **preparePaidTask 同步拿到 order_no**(201 响应即含),写入 `context_json.affiliateDash.orderNo`;无需等回调。失败必须降级 `MANUAL_REVIEW` + `deps.notifyTaskAutoManualReview`(范式:`services/fulfillment/executors/affiliate-dash-executor.ts:18-53`)。
|
||||
- **状态迁移路径**:见 §8 注 —— 领取页打开 `link_generated → claimed`,绑定 `→ waiting_binding`,submit 成功/回调 delivering `→ redeeming`,delivered `→ redeemed`(+核销),ship_failed `→ retry_pending`,cancelled `→ manual_review → closed`。
|
||||
- **回调幂等**:affiliate_dash 投递带 `X-Event-ID`,order_site 现无独立去重表 —— 建议 migration `013_affiliate_dash.sql` 新建 `webhook_events(event_id UNIQUE)` 去重表;事件处理用**状态合并式更新**(仿 `syncKuaishouFeifeiTaskStatus`,重复通知可重入)。接收方注意 `app.ts:31-38` 的 `express.json` 已保存 `req.rawBody`(验签必需),挂载回调路由时确认 rawBody 可用。
|
||||
- **回调幂等**:affiliate_dash 投递带 `X-Event-ID`,去重表已落地——migration `013_affiliate_dash_webhook_events.sql` 建 `webhook_events` 表并加 `(provider, event_id)` 唯一索引,写入见 `apps/backend/src/repositories/webhook-event-repo.ts:20`,过期清理见 `services/scheduler/data-retention-job.ts:42`;事件处理用**状态合并式更新**(`syncAffiliateDashTaskStatus`,重复通知可重入)。接收方注意 `app.ts:31-38` 的 `express.json` 已保存 `req.rawBody`(验签必需),挂载回调路由时确认 rawBody 可用。
|
||||
- **核销联动**:`delivered` 回调 → 复用 `consumeKuaishouIndustryVouchersForTask`(`platforms/kuaishou-industry/voucher-service.ts:143`),成功 → `redeemed/completed`,失败 → `manual_review`。
|
||||
- **领取页分发**:`flowType` 扩展 `'affiliate_dash'`;`buildClaimDetailPayload`(`services/claim/kuaishou-cloud-claim-context.ts:89-92`)按 executor_key 增加分支,返回 affiliate_dash 专用 payload(商品/预期账号/当前状态/二维码相关);前端 `ClaimPage.tsx:337-373` 增加渲染分支。
|
||||
|
||||
@@ -385,7 +385,7 @@ order_site 处理要求:
|
||||
| # | 文件 | 改动 |
|
||||
|---|------|------|
|
||||
| 1 | `services/fulfillment/executors/types.ts` | `FULFILLMENT_EXECUTOR_KEYS` 加 `AFFILIATE_DASH`;按需加 `isAffiliateDashExecutor` 守卫 |
|
||||
| 2 | `services/fulfillment/executors/affiliate-dash-executor.ts`(新) | 仿 kuaishou-feifei-executor.ts:`{ key, preparePaidTask, resolveDeliveryLink }` |
|
||||
| 2 | `services/fulfillment/executors/affiliate-dash-executor.ts`(新) | 实现 `{ key, preparePaidTask, resolveDeliveryLink }`(`kuaishou-feifei-executor.ts` 已随通道下线删除) |
|
||||
| 3 | `services/fulfillment/executors/registry.ts` | `EXECUTORS` Map 注册 |
|
||||
| 4 | `services/fulfillment/affiliate-dash/index.ts`(新) | 业务实现:prepare / sync 状态合并 + flow normalize |
|
||||
| 5 | `services/platforms/affiliate-dash/`(新) | `config.ts` + `http-client.ts`(签名 §3.1 + 请求封装 + 超时)+ `order-service.ts` + `notify-service.ts`(验签,`timingSafeEqual` 模式) |
|
||||
@@ -528,7 +528,7 @@ order_site 处理要求:
|
||||
|
||||
## 19. 核销时机优化(v2.4.1 · 已完成)
|
||||
|
||||
**需求**:核销时机从「delivered 回调」提前到「给用户链接(91 卡信息到手、建单成功)」——参考 kuaishou-feifei 的「给用户链接即核销」模式。
|
||||
**需求**:核销时机从「delivered 回调」提前到「给用户链接(91 卡信息到手、建单成功)」——affiliate-dash 的现行实现即按此模式(kuaishou-feifei 的「给用户链接即核销」模式已由 affiliate-dash 承接)。
|
||||
|
||||
**改动**(`apps/backend/src/services/fulfillment/affiliate-dash/index.ts`):
|
||||
|
||||
@@ -564,6 +564,8 @@ order_site 处理要求:
|
||||
|
||||
## 21. 履约匹配逻辑深度分析(v2.6)
|
||||
|
||||
> ⚠ **本节是 2026-07 当时的分析快照**:文中「cloudtentacles / feifei / affiliate_dash 三平台按商品名匹配、按全局优先级选择」已不成立——cloudtentacles 与 kuaishou-cloud / kuaishou-feifei 通道均已下线,现行可路由通道只有 `affiliate_dash` 与 `manual_dispatch`,`DEFAULT_EXECUTOR_PRIORITY = [affiliate_dash]`(`services/fulfillment/routing-config-service.ts:64-69`)。保留本节作为当时的决策记录。
|
||||
|
||||
### 21.1 匹配链路全貌
|
||||
|
||||
```
|
||||
@@ -617,6 +619,8 @@ order_site 处理要求:
|
||||
|
||||
## 22. 履约匹配优化实施(v2.7 · 已完成)
|
||||
|
||||
> ⚠ **落地后的现状(2026-09)**:§22.1 第 2 条写的「默认优先级 cloud → feifei → affiliate_dash」已过时。现行可路由通道只有 `affiliate_dash` 与 `manual_dispatch`,`DEFAULT_EXECUTOR_PRIORITY = [affiliate_dash]`(`apps/backend/src/services/fulfillment/routing-config-service.ts:64-69`);cloudtentacles 与 kuaishou-cloud/feifei 通道均已下线(`executors/registry.ts:18-19` 映射到 `legacy-retired-executor.ts` 只读 stub)。
|
||||
|
||||
**背景确认**:91 商品编码全部为**中文名**(如「幸运币90个」「星际漫游服装礼包」),无独立编码体系;运营按「中文名 → affiliate_dash sku」配置显式映射表(33 条,见 §22.3)。
|
||||
|
||||
### 22.1 匹配策略(两级)
|
||||
@@ -707,8 +711,8 @@ order_site 处理要求:
|
||||
| `AdminPlatformFulfillmentPage.tsx` | 移除 affiliate-dash tab、`AffiliateDashFulfillmentPanel` 组件及全部 affiliate 相关 import/类型 |
|
||||
|
||||
**页面归属**:
|
||||
- **平台配置**(`AdminPlatformShopsPage`):affiliate-dash、kuaishou-feifei、kuaishou-lewan、行业电子凭证、内部通知 —— 平台接入凭据
|
||||
- **履约配置**(`AdminPlatformFulfillmentPage`):履约路由、kuaishou-feifei 履约、kuaishou-lewan 履约 —— 通道路由与履约参数
|
||||
- **平台配置**(`AdminPlatformShopsPage`):内部通知、行业电子凭证、affiliate-dash、拼多多 (PDD) —— 平台接入凭据
|
||||
- **履约配置**(`AdminPlatformFulfillmentPage`):履约路由(affiliate_dash / manual_dispatch 通道开关 + 优先级 + 商品规则) —— 通道路由与履约参数
|
||||
|
||||
**验证**:前端 `tsc -b --noEmit` 通过;后端未改动。
|
||||
|
||||
@@ -749,7 +753,7 @@ order_site 处理要求:
|
||||
| 第 3 步(确认并提交发货) | 移除信息区(含三项),仅保留已绑定提示 + 提交按钮 |
|
||||
| 第 4 步(结果页) | 移除信息区(领取商品/单号/订单状态/发货状态/金额全部移除),仅保留结果标题 + 描述 |
|
||||
|
||||
**实现**:`ClaimAffiliateDashSteps.tsx` —— `AffiliateDashClaimPanel` 移除 `onOpenBindUrl` prop 与按钮、info grid 精简为三项且仅在未绑定(第 2 步)显示;`AffiliateDashResultStep` 移除 info grid;`ClaimPage.tsx` 同步移除 `onOpenBindUrl` 传参(`openBindUrl` 函数保留,lewan 仍用)。
|
||||
**实现**:`ClaimAffiliateDashSteps.tsx` —— `AffiliateDashClaimPanel` 移除 `onOpenBindUrl` prop 与按钮、info grid 精简为三项且仅在未绑定(第 2 步)显示;`AffiliateDashResultStep` 移除 info grid;`ClaimPage.tsx` 同步移除 `onOpenBindUrl` 传参(`openBindUrl` 相关代码已随后续通道下线一并删除,全库已无该符号)。
|
||||
|
||||
**附带修复**:`product-resolution-service.ts` 透传拉取失败 warn 日志补 `logIntegration` import(上一轮遗漏导致后端 typecheck 报错)。
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ your_secret_keybuyNum=1&callbackUrl=https://cb.example.com/notify/91/order&maxAm
|
||||
1. 校验签名与时间戳;
|
||||
2. 按 `orderNo` 做幂等;
|
||||
3. 将订单作为 `91卡券` 来源订单写入当前项目;
|
||||
4. 若 `productNo` 已映射到快手 Cloud 履约商品,则创建履约任务;
|
||||
4. 若 `productNo` 已映射到 affiliate-dash 履约商品,则创建履约任务;
|
||||
5. 若 `productNo` 尚未配置,则订单进入后台“待补全”队列,由运营补齐履约配置后重试生成任务;
|
||||
6. 若传入 `maxAmount`,则校验成本是否超限;
|
||||
7. 履约任务生成后,生成当前项目领取链接,例如:`https://你的域名/#/claim/{token}`;
|
||||
|
||||
+15
-13
@@ -59,7 +59,7 @@ https://你的域名/api/v1/open/91
|
||||
| 91字段 | 当前项目用途 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `orderNo` | 外部订单号 / 幂等键 | 建议映射为内部 `platformOrderId` |
|
||||
| `productNo` | 商品和店铺映射 | 默认按商品名匹配 cloudtentacles 商品;可用 `商品名----店铺编号` 携带快手小店 `shopId` |
|
||||
| `productNo` | 商品和店铺映射 | 默认按商品名匹配 affiliate-dash 商品;可用 `商品名----店铺编号` 携带快手小店 `shopId` |
|
||||
| `buyNum` | 购买数量 | 生成对应数量的履约任务 |
|
||||
| `maxAmount` | 成本上限校验 | 可选,超限返回失败 |
|
||||
| `callbackUrl` | 备用回调地址 | 先保留,不作为主流程依赖 |
|
||||
@@ -67,18 +67,18 @@ https://你的域名/api/v1/open/91
|
||||
| `version` | 版本 | 固定 `1.0` |
|
||||
| `sign` | 验签 | 必填 |
|
||||
|
||||
### 5.2 当前项目内部建议映射
|
||||
### 5.2 当前项目内部映射(现状)
|
||||
|
||||
建议新增一个独立来源:
|
||||
代码中已按独立来源落地:
|
||||
|
||||
- `provider = '91kaquan'`
|
||||
- `platform = 'kuaishou'`
|
||||
- `shopId = '91kaquan'`、`shopName = '91卡券'`(默认取 `platforms.ninetyone.shopId / shopName`)
|
||||
|
||||
商品绑定建议:
|
||||
商品绑定现状:
|
||||
|
||||
- `productNo` 对应当前项目内部 `skuCode`
|
||||
- 再由现有快手履约配置,匹配到 `kuaishou_ct_assisted` 履约链路
|
||||
- 若 `productNo` 暂未配置,订单会先进入后台“91卡券待补全订单”队列,不直接丢弃。
|
||||
- `productNo`(`商品名----店铺编号` 时取前半段作商品名)经 affiliate-dash 的 SKU 映射表(中文商品名 -> 英文 sku)匹配,未命中时再做 sku 透传兜底,命中后走 affiliate-dash 履约链路;
|
||||
- 若 `productNo` 暂未命中映射,订单先以 `order_status = 'pending_config'` 落库为待补全订单,不直接丢弃;可通过后台管理接口 `/api/v1/admin/platform-config/ninetyone/orders` 查看与重试。
|
||||
|
||||
## 6. 异步下单接口配置
|
||||
|
||||
@@ -289,18 +289,20 @@ Content-Type: application/json;charset=utf-8
|
||||
|
||||
建议在你方给 91 的商品配置中约定:
|
||||
|
||||
- `productNo`:对应当前项目内部的快手履约 SKU
|
||||
- `productNo`:对应当前项目内部的快手履约商品名(会经 affiliate-dash SKU 映射表转成英文 sku)
|
||||
- 商品名称:明确标注“自动发链接”
|
||||
- 发货类型:异步卡密
|
||||
- 买家收到内容:领取链接
|
||||
|
||||
后台补全路径:
|
||||
后台补全路径(当前实现):
|
||||
|
||||
1. 进入“平台配置 -> 91卡券接入”查看待补全订单;
|
||||
2. 根据订单中的 `productNo` 到“履约配置中心 -> 快手 Cloud 新履约”新增或补齐规则;
|
||||
3. 规则保存后回到“91卡券接入”,点击“重试生成任务”;
|
||||
1. 未命中 affiliate-dash SKU 映射的订单会以 `order_status = 'pending_config'` 落库;
|
||||
2. 查询待补全订单:`GET /api/v1/admin/platform-config/ninetyone/orders`;
|
||||
3. 补齐该 `productNo` 的 affiliate-dash SKU 映射后,调用 `POST /api/v1/admin/platform-config/ninetyone/orders/:id/retry` 重试生成任务(也可调用 `.../:id/fail` 标记无法履约);
|
||||
4. 任务生成后,查询接口会在领取链接准备好时返回 `orderStatus = 20`。
|
||||
|
||||
说明:前端「平台配置」页当前只有 `notifications / kuaishouIndustry / affiliateDash / pdd` 四个 tab,尚无 91 卡券专属页面;上述接口需直接调用。
|
||||
|
||||
## 12. 对接方需确认的固定项
|
||||
|
||||
发给 `91卡券` 客服/对接方时,建议一次性确认以下配置:
|
||||
@@ -322,4 +324,4 @@ Content-Type: application/json;charset=utf-8
|
||||
- `claimUrl` 作为最终卡密内容,通过 `cards.cardNo` 返回;
|
||||
- 所有快手专属引导和后续交互,都留在当前项目领取页中完成。
|
||||
|
||||
这样对当前项目改动最小,也最符合现在快手 Cloud 履约链路的设计。
|
||||
这样对当前项目改动最小,也最符合现在 affiliate-dash 履约链路的设计。
|
||||
|
||||
+32
-23
@@ -11,7 +11,7 @@
|
||||
|
||||
1. `91卡券` 调用我方异步下单接口;
|
||||
2. 我方将订单作为独立接入平台订单写入当前项目;
|
||||
3. 已配置商品自动走现有快手 Cloud 履约链路;
|
||||
3. 已配置商品自动走现有 affiliate-dash 履约链路;
|
||||
4. 未配置商品进入后台待补全队列,可手动补齐履约信息后重试;
|
||||
5. 我方生成领取链接 `https://你的域名/#/claim/{token}`;
|
||||
6. `91卡券` 调用查询订单接口;
|
||||
@@ -93,7 +93,7 @@ platforms: {
|
||||
shopId: '91kaquan',
|
||||
shopName: '91卡券',
|
||||
timestampToleranceSeconds: 600,
|
||||
cardsEncoding: 'base64json'
|
||||
cardsEncoding: 'aes-256-ecb-base64'
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -155,10 +155,10 @@ platforms: {
|
||||
|
||||
这样可直接复用现有:
|
||||
|
||||
- 91 商品名到 cloudtentacles 商品名的匹配;
|
||||
- cloudtentacles 覆盖规则(套装、多数量发货);
|
||||
- 快手 Cloud task 创建;
|
||||
- claim token 生成。
|
||||
- 91 商品名到 affiliate-dash sku 的匹配(`resolveAffiliateDashSkuByProductNo`,含 sku 透传兜底);
|
||||
- affiliate-dash 履约 task 创建;
|
||||
- 按数量拆 task(`delivery-task-service.ts` 按 `unit_index` 为每个数量各建一个 task);
|
||||
- claim token / 领取链接生成。
|
||||
|
||||
### 7.4 成功判定
|
||||
|
||||
@@ -185,12 +185,13 @@ platforms: {
|
||||
|
||||
### 8.2 claimUrl 判定
|
||||
|
||||
对订单下的每个 task:
|
||||
对订单下的每个 task,调用 `resolveTaskDeliveryLink(task)`(`apps/backend/src/services/fulfillment/delivery-link-service.ts`),由该 task 对应的履约执行器解析可交付链接:
|
||||
|
||||
1. 优先读取 `primary_claim_token`;
|
||||
2. 若没有,则读取 `claim_token`;
|
||||
3. 若仍没有,且是 `kuaishou_ct_assisted`,调用现有 `ensureTaskClaimLink(task)` 补生成;
|
||||
4. 用 `buildClaimUrl(token)` 组装最终链接。
|
||||
1. 读取 `primary_claim_token`,为空则回退 `claim_token`;
|
||||
2. 同时读取 `primary_claim_expires_at` / `claim_expires_at` 作为过期时间;
|
||||
3. 有 token 才返回 `claimUrl = buildClaimUrl(token)`,否则返回 `null`(视为暂不可交付)。
|
||||
|
||||
说明:`kuaishou_ct_assisted`(乐玩)/ kuaishou-feifei 通道已下线,其历史 task 由只读 stub 处理,不再生成领取链接;领取链接现由 affiliate-dash 执行器的 `resolveDeliveryLink` 提供。
|
||||
|
||||
### 8.3 查询状态判定
|
||||
|
||||
@@ -249,11 +250,12 @@ Node 侧实现等价为:
|
||||
|
||||
当前项目的 `91` 下单流程直接使用 `productNo` 做履约匹配:
|
||||
|
||||
- 默认把 `productNo` 当作商品名,匹配 cloudtentacles 商品列表里的同名商品;
|
||||
- 如果 `productNo` 使用 `商品名----店铺编号` 格式,前半段用于商品匹配,后半段作为快手小店 `shopId` 用于核销;
|
||||
- 特殊商品通过 cloudtentacles 覆盖规则处理,例如一个 91 商品发多个 cloudtentacles 商品,或某个商品发多次。
|
||||
- 默认把 `productNo` 当作商品名(中文),经 affiliate-dash 的 SKU 映射表(`apps/backend/src/services/platforms/affiliate-dash/sku-mapping.ts`)转成 affiliate-dash 商品的英文 sku;
|
||||
- 若映射表未命中,再用 `productNo` 与 affiliate-dash 商品列表的 sku 做透传兜底匹配(`matchAffiliateDashSkuPassthrough`);
|
||||
- 如果 `productNo` 使用 `商品名----店铺编号` 格式,前半段作为商品名匹配、后半段作为快手小店 `shopId`(`parseOpen91ProductNo`);
|
||||
- 匹配成功后由履约路由选中 affiliate-dash 执行器,多数量在 `delivery-task-service.ts` 里按数量拆成多个 task。
|
||||
|
||||
最终目标是让 91 卡券只传商品名,系统按商品名和覆盖规则自动创建快手 Cloud 履约任务。
|
||||
最终目标是让 91 卡券只传商品名,系统按 SKU 映射与透传兜底自动创建 affiliate-dash 履约任务。
|
||||
|
||||
## 11. 日志与排障
|
||||
|
||||
@@ -273,17 +275,24 @@ Node 侧实现等价为:
|
||||
|
||||
建议新增:
|
||||
|
||||
- `apps/backend/src/routes/open-91.js`
|
||||
- `apps/backend/src/services/open-91/shared.js`
|
||||
- `apps/backend/src/services/open-91/order-create-service.js`
|
||||
- `apps/backend/src/services/open-91/order-query-service.js`
|
||||
- `apps/backend/src/routes/open-91.ts`(挂载于 `apps/backend/src/app.ts:103`,前缀 `/api/v1/open/91`)
|
||||
- `apps/backend/src/services/open-91/config.ts`
|
||||
- `apps/backend/src/services/open-91/payload.ts`
|
||||
- `apps/backend/src/services/open-91/crypto.ts`
|
||||
- `apps/backend/src/services/open-91/response.ts`
|
||||
- `apps/backend/src/services/open-91/order-create-service.ts`
|
||||
- `apps/backend/src/services/open-91/order-query-service.ts`
|
||||
- `apps/backend/src/services/open-91/consume-on-deliver.ts`
|
||||
|
||||
需要修改:
|
||||
|
||||
- `apps/backend/src/config/defaults.ts`
|
||||
- `apps/backend/src/config/runtime.js`
|
||||
- `apps/backend/src/types/runtime-config.js`
|
||||
- `apps/backend/src/index.js`
|
||||
- `apps/backend/src/config/runtime.ts`
|
||||
- `apps/backend/src/config/env-overrides.ts`
|
||||
- `apps/backend/src/types/runtime-config.ts`
|
||||
- `apps/backend/src/services/platforms/ninetyone/order-service.ts`
|
||||
- `apps/backend/src/services/fulfillment/product-resolution-service.ts`
|
||||
- `apps/backend/src/index.ts`
|
||||
|
||||
## 13. 测试范围
|
||||
|
||||
@@ -309,4 +318,4 @@ Node 侧实现等价为:
|
||||
- 不实现 `callbackUrl` 主动回调;
|
||||
- 不在 `91` 查询成功后回写快手侧最终兑换结果;
|
||||
- 不实现除 `aes-256-ecb-base64` 之外的卡密加密算法;
|
||||
- 不新增独立顶级菜单,后台入口复用“平台配置 -> 91卡券接入”。
|
||||
- 不新增独立顶级菜单;91 待补全订单目前只有后端管理接口(`/api/v1/admin/platform-config/ninetyone/orders` 查询、`.../:id/retry` 重试、`.../:id/fail` 标记失败),前端暂无专属页面。
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
|
||||
最快解决(强制使用 HTTP/2 协议)防止clash干扰
|
||||
cloudflared tunnel --protocol http2 run --url http://localhost:80 mac-mini-tunnel
|
||||
|
||||
|
||||
|
||||
cloudflared tunnel create mac-mini-tunnel
|
||||
|
||||
yml@ymlMacBook-Air ~ % cloudflared tunnel create mac-mini-tunnel
|
||||
Tunnel credentials written to /Users/yml/.cloudflared/562f9639-71f2-44e0-a5ef-b34bd4e215e5.json. cloudflared chose this file based on where your origin certificate was found. Keep this file secret. To revoke these credentials, delete the tunnel.
|
||||
|
||||
Created tunnel mac-mini-tunnel with id 562f9639-71f2-44e0-a5ef-b34bd4e215e5
|
||||
yml@ymlMacBook-Air ~ %
|
||||
|
||||
|
||||
221329.cc.cd
|
||||
|
||||
4. # 例子:把 web.yourdomain.com 指向刚才创建的隧道
|
||||
cloudflared tunnel route dns mac-mini-tunnel 221329.cc.cd
|
||||
|
||||
|
||||
5. 启动隧道(临时/测试)
|
||||
如果你只是临时跑一个服务(比如你 OrbStack 里的容器映射到了本地 80 端口):
|
||||
|
||||
cloudflared tunnel run --url http://localhost:80 mac-mini-tunnel
|
||||
|
||||
-----
|
||||
如果两台电脑**不会同时运行**(例如一台是公司的 iMac,一台是家里的 MacBook),那么处理起来就简单得多了。
|
||||
|
||||
在这种情况下,你可以直接采用 **“共用同一个 Tunnel ID”** 的方案。这在 Cloudflare 的术语中叫做多副本(Replicas),虽然你不是为了负载均衡,但它能完美解决你的需求。
|
||||
|
||||
### 实现方案:共用 Tunnel
|
||||
你只需要确保两台电脑拥有完全一致的“身份令牌”和“配置文件”。
|
||||
|
||||
1. **迁移证书与密钥:**
|
||||
* 在第一台电脑(已配置好)上,找到这两个文件:
|
||||
* `~/.cloudflared/cert.pem`(登录证书)
|
||||
* `~/.cloudflared/<Tunnel-ID>.json`(隧道的身份密钥)
|
||||
* 将它们拷贝到第二台电脑的 `~/.cloudflared/` 目录下。
|
||||
|
||||
2. **同步配置文件:**
|
||||
* 将 `config.yml` 也拷贝过去。
|
||||
* **注意:** 确保两台电脑上运行的本地服务端口是一致的(比如都是 `localhost:8080`)。
|
||||
|
||||
3. **直接运行:**
|
||||
* 在任意一台电脑上运行 `cloudflared tunnel run <隧道名>` 即可。
|
||||
|
||||
---
|
||||
|
||||
### 这样做的优势
|
||||
* **域名无缝切换:** 你不需要在 Cloudflare 后台修改任何 DNS 记录。只要其中一台电脑开启了隧道,`example.yourdomain.com` 就会立即指向那台电脑。
|
||||
* **无需重复配置:** 所有的路由规则(Ingress rules)都是一致的,维护起来非常省心。
|
||||
|
||||
### 注意事项
|
||||
* **如果真的“不小心”同时运行了:**
|
||||
Cloudflare 会把它们当成一个隧道的两个节点。流量会以类似“轮询”的方式随机分配到两台电脑上。如果这时你的两台电脑跑的服务内容不一样,访问者可能会看到一会儿是 A 电脑的内容,一会儿是 B 电脑的内容。
|
||||
* **路径一致性:**
|
||||
在 `config.yml` 中,`credentials-file` 的路径如果写的是绝对路径(如 `/Users/jack/...`),请确保两台电脑的用户名一致;如果不一致,记得修改配置文件里的路径。
|
||||
|
||||
---
|
||||
|
||||
**总结:**
|
||||
既然不同时运行,那就把第二台电脑伪装成第一台电脑的“克隆体”。只需拷贝那几个关键文件,你就能实现在家、在公司用同一个域名访问不同的机器了。
|
||||
@@ -1,75 +0,0 @@
|
||||
# 财务功能实施计划
|
||||
|
||||
## 目标
|
||||
|
||||
在后台左侧主导航增加“财务”,提供按中国时区日期筛选的全站财务总览、每日账目和资金明细。首版复用现有订单、打手资金申请、钱包流水和售后数据,不改变接单、充值、提现的现有业务流程。
|
||||
|
||||
## 已实现范围
|
||||
|
||||
- `/admin/finance` 页面和主导航入口。
|
||||
- `/api/v1/admin/finance/summary`:返回日期范围内的日报和汇总指标。
|
||||
- `/api/v1/admin/finance/transactions`:返回分页资金明细,并支持账目类型、渠道筛选。
|
||||
- 日期范围支持 `YYYY-MM-DD HH:mm`,精确到分钟;日报仍按 `Asia/Shanghai` 自然日切桶。
|
||||
- 日界线固定为 `Asia/Shanghai`,前端金额展示为元,数据库金额仍使用分。
|
||||
- 总览指标:订单收款、订单退款、打手报酬、已确认提现、充值入账、售后追缴退还、净现金流、当前钱包余额/冻结/待解冻/待提现。
|
||||
- 渠道日报按“日期 + 渠道 + 账目类型”汇总笔数和金额,订单渠道显示为 `provider/platform`,打手资金显示为提现/充值渠道。
|
||||
- 原“接单平台 → 资金”页签继续用于打手资金申请审核和资金配置。
|
||||
- 新增独立 `finance` 后台角色:仅显示财务和资金审核入口,可访问财务报表、资金申请列表/审核,不可访问订单、任务和系统概览。
|
||||
- 管理员拥有全部财务权限,普通运营保留查看/审核权限,客服不获得财务权限。
|
||||
|
||||
## 财务口径
|
||||
|
||||
1. 订单收款按 `paid_at`(缺失时回退 `created_at`)归属日期。
|
||||
2. 退款首版按订单 `pay_status=refunded` 的订单金额统计;部分退款明细需要后续接入独立退款流水。
|
||||
3. 打手报酬取 `reward_settlement`、`sharing_reward` 钱包流水。
|
||||
4. 已确认提现取资金申请 `status=approved` 且有 `reviewed_at` 的金额,避免和钱包 `withdraw_paid` 零金额流水重复计算。
|
||||
5. 充值入账取已通过的充值申请;押金冻结/解冻只作为资金占用变化,不计入收入。
|
||||
6. 净现金流 = 收款 + 充值 + 打手资金回收 - 订单退款 - 打手提现 - 售后追缴退还。
|
||||
|
||||
## 后续迭代
|
||||
|
||||
### 二期:人工打款可追踪
|
||||
|
||||
- 将提现审核和实际打款拆分。
|
||||
- 增加打款时间、操作人、流水号、付款凭证和备注。
|
||||
- 增加“审核通过待打款”“已打款”状态,历史 `approved` 数据保持兼容。
|
||||
|
||||
### 三期:统一财务总账
|
||||
|
||||
- 新增 append-only `finance_transactions` 表。
|
||||
- 对订单收款、退款、充值、提现、结算和售后资金动作双写统一流水。
|
||||
- 增加对账、CSV/Excel 导出、幂等键和异常金额检查。
|
||||
|
||||
## RBAC 设计
|
||||
|
||||
权限键:
|
||||
|
||||
- `finance.view`:财务总览、日报和明细
|
||||
- `finance.review`:充值/提现申请审核
|
||||
- `finance.config`:财务规则配置
|
||||
- `finance.export`:导出财务数据
|
||||
|
||||
角色矩阵:
|
||||
|
||||
| 角色 | 财务报表 | 资金审核 | 财务配置 | 订单/任务 |
|
||||
|---|---|---|---|---|
|
||||
| 管理员 | 是 | 是 | 是 | 是 |
|
||||
| 普通运营 | 是 | 是 | 否 | 是 |
|
||||
| 财务 | 是 | 是 | 否 | 否 |
|
||||
| 客服 | 否 | 否 | 否 | 是(客服范围) |
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 日期范围包含起止两天,日报为空时仍返回完整日期行。
|
||||
- 日报各列合计与顶部总览一致。
|
||||
- 提现不被申请表和钱包流水重复计入。
|
||||
- 每笔明细包含来源类型、来源编号、发生时间和关联单号/渠道。
|
||||
- 选择具体时间段时只统计起止时间内的事件;渠道日报能分别展示同一渠道不同日期的金额。
|
||||
- 刷新、分页和账目类型筛选保持当前日期范围。
|
||||
- 管理员、普通运营可访问;客服访问接口返回角色错误且不显示主导航。
|
||||
|
||||
## 主要文件
|
||||
|
||||
- 前端:`apps/frontend/src/pages/admin/AdminFinancePage.tsx`、`apps/frontend/src/services/admin/finance.ts`、`apps/frontend/src/types/admin/finance.ts`、`apps/frontend/src/layouts/AdminLayout.tsx`、`apps/frontend/src/App.tsx`
|
||||
- 后端:`apps/backend/src/services/admin/admin-finance-service.ts`、`apps/backend/src/routes/admin/finance.ts`、`apps/backend/src/routes/admin.ts`
|
||||
- 测试:`apps/backend/src/services/admin/admin-finance-service.test.ts`
|
||||
@@ -1,192 +0,0 @@
|
||||
# pgBackRest 生产备份方案分析
|
||||
|
||||
> 状态:暂缓实施。当前继续使用 `pg_dump -Fc` + OSS manifest 备份方案,待安排维护窗口后再启用 WAL 归档。
|
||||
>
|
||||
> 记录日期:2026-08-23
|
||||
|
||||
## 结论
|
||||
|
||||
PostgreSQL 没有 MySQL binlog 的同名机制,但有等价能力:
|
||||
|
||||
```text
|
||||
基础物理备份 + WAL 归档 = 按时间点恢复(PITR)
|
||||
```
|
||||
|
||||
当前 Compose 是单节点 PostgreSQL。启用 `archive_mode` 和 `wal_level` 通常需要重启 PostgreSQL,因此不能承诺绝对零停机。
|
||||
|
||||
预计生产维护窗口:
|
||||
|
||||
| 阶段 | 是否停机 | 预计耗时 |
|
||||
| --- | --- | --- |
|
||||
| 构建 pgBackRest 镜像 | 否 | 几分钟 |
|
||||
| 创建 stanza、校验 OSS | 否 | 1~3 分钟 |
|
||||
| 重启 PostgreSQL 启用 WAL 归档 | 是 | 5~30 秒 |
|
||||
| backend/web 等待恢复 | 是 | 20~90 秒 |
|
||||
| 首次全量备份 | 否 | 几分钟到几十分钟 |
|
||||
| 建议预留维护窗口 | 是 | 2~5 分钟 |
|
||||
|
||||
实际时间取决于数据库大小、磁盘恢复速度、容器启动时间和 OSS 网络质量。当前 `pg_dump` 文件约 161 MB,首次物理备份预计不会很大,但仍应按实际 PostgreSQL 数据目录大小估算。
|
||||
|
||||
## 当前方案
|
||||
|
||||
目前生产使用:
|
||||
|
||||
- `pg_dump -Fc` 全库逻辑备份。
|
||||
- 备份文件上传 OSS。
|
||||
- manifest 保存大小、SHA-256、迁移记录和创建时间。
|
||||
- `apps/backend/data` 配置单独打包上传。
|
||||
- `restore-db.sh --verify` 在临时 PostgreSQL 容器中演练恢复。
|
||||
|
||||
该方案可以恢复到某个 dump 的时间点,但不能恢复最近一次 dump 之后的几分钟变更。
|
||||
|
||||
pgBackRest 不能替代当前逻辑备份,建议两套并存:
|
||||
|
||||
```text
|
||||
pgBackRest:生产灾难恢复、PITR、分钟级 RPO
|
||||
pg_dump:逻辑迁移、单表恢复、跨版本恢复、第二套保险
|
||||
```
|
||||
|
||||
## 需要增加的组件
|
||||
|
||||
当前 PostgreSQL 镜像没有 pgBackRest,需要构建自定义镜像:
|
||||
|
||||
```dockerfile
|
||||
FROM docker.m.daocloud.io/library/postgres:16-bookworm
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends pgbackrest ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
```
|
||||
|
||||
需要增加或挂载:
|
||||
|
||||
- `/etc/pgbackrest/pgbackrest.conf`
|
||||
- `/var/spool/pgbackrest/`
|
||||
- pgBackRest 日志目录
|
||||
- OSS 备份凭证
|
||||
- pgBackRest repository 配置
|
||||
|
||||
不能只在正在运行的容器里执行 `apt install pgbackrest`,因为容器重建后会丢失。
|
||||
|
||||
## OSS 规划
|
||||
|
||||
可以复用当前图片 OSS 桶,但必须使用独立前缀:
|
||||
|
||||
```text
|
||||
backups/postgres/ # 当前 pg_dump
|
||||
backups/configs/ # 应用配置
|
||||
backups/pgbackrest/ # 基础备份和 WAL
|
||||
```
|
||||
|
||||
生产更推荐新建独立备份桶,并创建独立 RAM 用户。原因:数据库备份包含订单、用户和后台数据,不能与图片读写权限混用。
|
||||
|
||||
建议 OSS 配置:
|
||||
|
||||
- 使用 ECS 同地域内网端点,避免公网流量费用。
|
||||
- 开启版本控制。
|
||||
- 配置生命周期规则。
|
||||
- 备份桶保持私有。
|
||||
- 必要时启用 pgBackRest 仓库加密,并安全保存加密密码。
|
||||
|
||||
pgBackRest 使用 OSS S3 兼容接口,最终配置必须通过 `pgbackrest check` 和一次完整备份实测,不能只凭配置文件判断兼容性。
|
||||
|
||||
## PostgreSQL 配置
|
||||
|
||||
需要启用:
|
||||
|
||||
```text
|
||||
wal_level=replica
|
||||
archive_mode=on
|
||||
archive_command=pgbackrest --stanza=order-site archive-push %p
|
||||
archive_timeout=60
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `wal_level=replica` 通常需要重启。
|
||||
- `archive_mode=on` 必须重启。
|
||||
- `archive_command` 可以 reload,但建议统一写入 Compose 配置。
|
||||
- `archive_timeout=60` 让低写入量时 WAL 最多约 60 秒切换归档一次。
|
||||
- OSS 不可用时 WAL 会在本地积压,不能手动删除 `pg_wal` 文件。
|
||||
|
||||
启用前先检查当前状态:
|
||||
|
||||
```bash
|
||||
docker compose exec postgres psql \
|
||||
-U postgres -d order_site \
|
||||
-c "SHOW wal_level; SHOW archive_mode; SHOW archive_command;"
|
||||
```
|
||||
|
||||
如果 `archive_mode` 已经是 `on`,后续可能只需要配置 pgBackRest 和 reload;如果是 `off`,需要完整重启 PostgreSQL。
|
||||
|
||||
## 计划中的切换步骤
|
||||
|
||||
当前暂不执行。正式实施时按以下顺序:
|
||||
|
||||
1. 继续保留并执行现有 `bash deploy/backup-db.sh`。
|
||||
2. 创建独立 OSS 备份桶或规划独立 `backups/pgbackrest/` 前缀。
|
||||
3. 创建只允许备份前缀的 RAM 用户。
|
||||
4. 构建包含 pgBackRest 的 PostgreSQL 镜像。
|
||||
5. 准备 `pgbackrest.conf`,不要把密钥提交到 Git。
|
||||
6. 在 PostgreSQL 仍在线时执行 `stanza-create`。
|
||||
7. 选择低峰期停止 backend/web。
|
||||
8. 重启 PostgreSQL,启用 `archive_mode` 和 `archive_command`。
|
||||
9. 等待 PostgreSQL 健康检查通过。
|
||||
10. 执行 `pgbackrest check`。
|
||||
11. 执行第一次 full backup。
|
||||
12. 检查 OSS 中是否出现基础备份和 WAL 文件。
|
||||
13. 在临时 PostgreSQL 或测试服务器执行一次 PITR 恢复。
|
||||
14. 恢复 backend/web,并抽查后台、订单和图片访问。
|
||||
|
||||
不要执行以下命令,避免误删当前数据库:
|
||||
|
||||
```bash
|
||||
docker compose down -v
|
||||
docker volume rm order_site_postgres_data
|
||||
```
|
||||
|
||||
增加 pgBackRest 只需要替换镜像、挂载配置并重启,不需要删除 `postgres_data` 数据卷。
|
||||
|
||||
## 备份策略建议
|
||||
|
||||
```text
|
||||
每周:full 基础备份
|
||||
每天:diff 差异备份
|
||||
持续:WAL 归档到 OSS
|
||||
每天:保留当前 pg_dump 逻辑备份
|
||||
```
|
||||
|
||||
WAL 归档不依赖 cron,由 PostgreSQL 的 `archive_command` 持续触发。需要监控:
|
||||
|
||||
- WAL 归档失败次数。
|
||||
- `/var/lib/postgresql/data/pg_wal` 占用空间。
|
||||
- OSS 上传延迟和失败率。
|
||||
- 最近一次 full/diff 备份时间。
|
||||
- 最近一段 WAL 是否已经出现在 OSS。
|
||||
|
||||
## 恢复时间预期
|
||||
|
||||
pgBackRest PITR 恢复也不是零停机操作。当前单节点架构下,恢复生产数据库需要停止 backend/web,并替换或恢复 PostgreSQL 数据目录。
|
||||
|
||||
恢复耗时主要取决于:
|
||||
|
||||
- 基础备份大小。
|
||||
- WAL 数量。
|
||||
- 磁盘读写速度。
|
||||
- WAL 回放量。
|
||||
- PostgreSQL 启动和健康检查时间。
|
||||
|
||||
如果基础备份较小、目标时间接近当前时间,预计几分钟;数据库规模扩大后需要重新压测,不能按当前 161 MB dump 固定估计。
|
||||
|
||||
## 后续验收清单
|
||||
|
||||
- `pgbackrest stanza-create` 成功。
|
||||
- `pgbackrest check` 成功。
|
||||
- 第一次 full backup 成功。
|
||||
- OSS 中能看到基础备份和 WAL。
|
||||
- PostgreSQL 重启后 `archive_mode=on`。
|
||||
- `pg_stat_archiver` 中归档成功数持续增加。
|
||||
- OSS 暂时不可用时,WAL 会积压但不会静默丢失。
|
||||
- 临时环境 PITR 到指定时间成功。
|
||||
- 恢复后的订单、后台用户、迁移记录和图片引用可用。
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
# 和平精英寄售仓库 · 系统设计与实施方案
|
||||
|
||||
> **文档定位**:功能技术与产品设计方案(推荐实现标准)
|
||||
> **核心游戏**:和平精英(地铁逃生模式 / 账号与物资寄售)
|
||||
> **更新时间**:2026-09-13
|
||||
|
||||
---
|
||||
|
||||
## 一、 业务定位与设计原则
|
||||
|
||||
本模块针对 **《和平精英》**(重点针对地铁逃生模式的苏尔南手枪、极品附魔武器与排位账号)建设专属的 **「寄售仓库」**,取代原有的线下手工 Excel 台账,并深度融入订单与接单履约链路:
|
||||
|
||||
### 核心设计原则:
|
||||
1. **去繁就简、数据结构化**:吸取线下台账对“收购成本、收购人、卖家账号/付款凭证、排号/出库店铺”的核心关注点,将杂乱的手工记录转化为规范清晰的系统实体。
|
||||
2. **高度自动化,告别手工录入**:
|
||||
* 履约出库时,系统**自动提取并反向回填**订单号、快手/拼多多店铺名、出库时间,无需人工再去登记“安排到哪张单”;
|
||||
* 客服创建/审核时,系统**自动绑定当前登录人员**为收购人,自动记录操作时间。
|
||||
3. **闭环降本**:在工单发布到打手大厅前,系统通过规则引擎智能比对内部在库物品,支持一键直接履约结单,**直接省去外部打手赏金(¥15~¥80/单)**。
|
||||
4. **风控自主可控**:每一件入库物品提供独立的 **【允许匹配开关】**,随手一关即可排除在自动匹配推荐之外,便于自用、高价单卖或线下保留。
|
||||
5. **逆向容错(支持撤单与回库重选)**:全链路支持买家退款、客服选错或账号异常时的**一键撤单解绑**,库存安全退回复库,工单可随时重新匹配或转推大厅。
|
||||
|
||||
---
|
||||
|
||||
## 二、 核心数据模型设计
|
||||
|
||||
将原本 Excel 的离散字段归纳为清晰的三层架构:**基础规格属性**、**收货与卖家资料**、**流转履约与匹配控制**。
|
||||
|
||||
### 数据表:`consignment_inventories`(寄售仓库主表)
|
||||
|
||||
| 分类 | 字段名 | 类型 | 默认值 | 业务含义与说明 |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| **主键标识** | `id` | `BIGSERIAL` | 自增 | 内部唯一自增 ID |
|
||||
| | `inventory_no` | `TEXT` | 唯一 | 系统自动生成的业务编号(如 `INV26091312345`) |
|
||||
| **规格属性** | `game_name` | `TEXT` | `和平精英` | 游戏名称 |
|
||||
| (多维匹配) | `category_id` | `BIGINT` | `NULL` | 关联的大类(如 苏尔南手枪) |
|
||||
| | `item_name` | `TEXT` | 必填 | 规范物品标题(如 `安卓微信区 暗潮 1钻`) |
|
||||
| | `system` | `TEXT` | `""` | 操作系统:`苹果` / `安卓`(硬约束) |
|
||||
| | `server_zone` | `TEXT` | `""` | 游戏大区:`微信区` / `QQ区`(硬约束) |
|
||||
| | `series` | `TEXT` | `""` | 附魔系列:`暗潮` / `怒嚎` / `血宴` 等(硬约束) |
|
||||
| | `diamond_level` | `INTEGER` | `1` | 钻级:`1` ~ `6` 钻 |
|
||||
| | `brilliance_count` | `INTEGER` | `0` | 璀璨数:`0`(无)、`1`、`2` 等 |
|
||||
| **收货采购信息** | `cost_price_fen` | `INTEGER` | `0` | **收货成本价**(单位分,如 10500 代表 105 元) |
|
||||
| (财务与人效) | `buyer_name` | `TEXT` | `""` | **收购人**(如 王斌、周杰雄、周顺,默认取当前登录客服) |
|
||||
| | `inbound_batch` | `TEXT` | `""` | **收货批次/班次代码**(如 `8.30-1`、`8.30-4`,便于交接对账) |
|
||||
| | `inbound_at` | `TIMESTAMPTZ`| `NOW()` | 正式入库时间 |
|
||||
| **卖家与凭据** | `seller_contact` | `TEXT` | `""` | 卖家微信号 / 联系电话 |
|
||||
| (留痕防纠纷) | `seller_game_id` | `TEXT` | `""` | 卖家游戏角色名 / UID |
|
||||
| | `seller_payment_info`| `TEXT` | `""` | 卖家收款抬头与卡号(如 `王大锤商行 331682...`) |
|
||||
| | `credentials_json` | `JSONB` | `{}` | 敏感交付信息(游戏账号、密码、换绑辅助),权限保护 |
|
||||
| | `screenshots_json` | `JSONB` | `[]` | 截图凭证数组(卖家ID主页图、词条属性图、名片图等) |
|
||||
| | `remark` | `TEXT` | `""` | 运营其他备注说明 |
|
||||
| **履约与状态** | `allow_matching` | `BOOLEAN` | `TRUE` | **★ 允许匹配开关**(开:参与匹配;关:暂停匹配) |
|
||||
| (出库流转) | `status` | `TEXT` | `in_stock` | 资产状态:`in_stock`(在库待匹配) / `consumed`(已出库) / `archived`(已作废) |
|
||||
| | `bound_order_id` | `BIGINT` | `NULL` | 履约绑定的内部系统订单 ID(出库时系统自动写入) |
|
||||
| | `bound_work_order_id`| `BIGINT`| `NULL` | 履约绑定的接单工单 ID(出库时系统自动写入) |
|
||||
| | `bound_platform_order_id`| `TEXT`| `""` | 外部订单号(如快手/拼多多单号 `260830-085784...`,出库自动回填) |
|
||||
| | `shop_name` | `TEXT` | `""` | 售出店铺名称(如 `拼多多向日葵`、`稚嫩游戏`,出库自动回填) |
|
||||
| | `consumed_at` | `TIMESTAMPTZ`| `NULL` | 出库履约结单时间 |
|
||||
|
||||
---
|
||||
|
||||
## 三、 三大核心业务场景与操作流
|
||||
|
||||
### 1. ~~定向收集链接生成与用户填报(自动化收货渠道)~~ —— **已下线**
|
||||
> 2026-09 起:寄卖提报统一走**接单平台打手端提报**(`/api/v1/worker/consignment/publish`),
|
||||
> 对外收集链接(H5 填报页、`consignment_collect_links`、`consignment_submissions.link_id`)
|
||||
> 已整条移除,详见迁移 `079_drop_consignment_collect_links.sql`。本节保留仅作历史记录。
|
||||
|
||||
```
|
||||
[客服后台] ──► 点击【新建收集链接】
|
||||
├── 勾选目标:分类=苏尔南手枪 / 系统=安卓 / 区服=微信
|
||||
├── 勾选品质:系列=暗潮 / 钻级=1钻 / 璀璨=0
|
||||
├── 输入协商收货价:如 105 元,输入批次号:如 8.30-1
|
||||
└── 点击【生成链接】 ──► 复制专属 H5 链接发给卖家
|
||||
│
|
||||
[卖家手机] ◄────────────────┘
|
||||
- 打开 H5:顶部显示《和平精英 安卓微信区 暗潮 1钻 回收单》(规格锁定只读)
|
||||
- 填入:游戏账号、密码、角色ID
|
||||
- 上传:角色主页图、暗潮与1钻属性截图
|
||||
- 填入:收款卡号/抬头(如 王大锤商行...)、卖家联系电话
|
||||
- 点击提交
|
||||
│
|
||||
[后台审核工作台] ◄──────────┘ (状态:pending_review)
|
||||
- 客服打开「寄卖审核」弹窗,核查截图无误
|
||||
- 可修正实收价格与字段
|
||||
- 点击【审核通过并入库】 ──► 正式存入寄售仓库(in_stock,允许匹配=开)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 线下人工快速录入(直接收货登记)
|
||||
|
||||
对于客服在线下直接谈好的现货:
|
||||
* 在「寄售仓库」面板点击 **【快速手工录入】**。
|
||||
* 录入表单模块化聚合:
|
||||
* **商品规格**:点选 系统、区服、系列(暗潮/怒嚎/血宴)、钻级(1~6)、璀璨数。
|
||||
* **采购成本与批次**:输入收购价、当前收购人(默认填入当前操作员)、批次代码(如 8.30-1)。
|
||||
* **卖家与凭据**:输入卖家联系方式、收款方式、上传品质截图、填入账号密码。
|
||||
* 保存后直接进入在库列表,即刻就绪,参与工单匹配。
|
||||
|
||||
---
|
||||
|
||||
### 3. 订单发布前拦截与一键内部履约(降本核心)
|
||||
|
||||
```
|
||||
[买家下单] ──► 拼多多向日葵 店铺进单,工单状态为 unassigned (待发布大厅)
|
||||
│
|
||||
▼ 匹配引擎实时计算 (仅扫描 allow_matching = true 的在库库存)
|
||||
[工单列表行] ──► 醒目标记 Tag:🟢 匹配内部库存(2件)
|
||||
│
|
||||
▼ 客服点击「发布并核销」
|
||||
[发布拦截弹窗] ──► 拦截并弹出《检测到可用寄售库存》推荐卡片:
|
||||
├── 卡片 A:【全部匹配】安卓微信 暗潮 1钻 0璀璨(成本 ¥105,收购人:周杰雄)
|
||||
└── 卡片 B:【部分匹配】安卓微信 暗潮 1钻 1璀璨(成本 ¥128,多出【1璀璨】,高配充低配)
|
||||
│
|
||||
┌───────────┴───────────┐
|
||||
▼ ▼
|
||||
【选用内部库存直接履约】 【忽略并继续发布到大厅】
|
||||
1. 选中卡片 A 点击确认 维持原有逻辑,推送到大厅由打手抢单
|
||||
2. 系统原子事务操作:
|
||||
- 工单直接 completed(不发大厅,0 赏金支出!)
|
||||
- 自动顺带核销快手电子凭证
|
||||
- 库存状态变为 consumed
|
||||
- 自动回填:
|
||||
bound_platform_order_id = 买家平台订单号
|
||||
shop_name = 拼多多向日葵
|
||||
consumed_at = 当前时间
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、 智能匹配引擎逻辑(全部匹配 vs 部分匹配)
|
||||
|
||||
### 1. 过滤基准条件(必须满足,否则不匹配)
|
||||
* 资产状态必须为 `in_stock`(在库可用);
|
||||
* **【允许匹配开关】必须为 `TRUE`**(若为 `FALSE` 则完全跳过,绝不打扰);
|
||||
* 系统与区服必须完全一致(苹果不能配安卓,微信不能配QQ);
|
||||
* 词条系列必须完全一致(暗潮 只能配 暗潮,不能拿 怒嚎 充数)。
|
||||
|
||||
### 2. 钻级与璀璨的匹配算法
|
||||
* **全部匹配(Exact Match,得分 100 分)**:
|
||||
* 钻级完全相同(如 订单=1钻,库存=1钻);
|
||||
* 璀璨完全相同(如 订单=0璀璨,库存=0璀璨)。
|
||||
* 推荐标为绿色,无降级风险。
|
||||
* **部分匹配 / 超集匹配(Partial Match,得分 80 分)**:
|
||||
* 核心特征全部满足,但库存规格更高(如 订单=1钻无璀璨,库存=1钻1璀璨,或钻级更高)。
|
||||
* 推荐标为黄色,并提示 **“⚠️ 该库存品质更高(多出 1 璀璨),属于高配充低配,请确认是否愿意降级使用”**,由客服做最终决断。
|
||||
|
||||
### 3. 排序策略
|
||||
1. 优先全部匹配(100分) $\gt$ 部分匹配(80分)。
|
||||
2. 同等匹配度下,收购成本最低优先(毛利最大化)。
|
||||
3. 同等成本下,入库时间最早优先(FIFO 快速清库存)。
|
||||
|
||||
---
|
||||
|
||||
## 五、 异常撤单与库存回退重选机制(退款 / 选错 / 无法发货)
|
||||
|
||||
在实际运营中,若发生买家退款、客服选错或账号异常,系统提供完整的**逆向撤销与回库重选闭环**:
|
||||
|
||||
```
|
||||
【 已寄售履约工单 (completed) 】
|
||||
│
|
||||
点击【撤销寄售履约】
|
||||
│
|
||||
┌────────────────────────┼────────────────────────┐
|
||||
▼ ▼ ▼
|
||||
【场景 1:选错库存重新选】 【场景 2:买家退款取消工单】 【场景 3:物资账号异常作废】
|
||||
- 原库存解绑 ──► in_stock - 原库存解绑 ──► in_stock - 原库存变更为 ──► archived
|
||||
- 工单回退 ────► unassigned - 工单状态 ────► cancelled - 工单回退 ────► unassigned
|
||||
- 客服可重新选用新库存 - 终止发货,库存回库继续卖 - 客服选用其他正常库存发货
|
||||
或直接点击发布到大厅
|
||||
```
|
||||
|
||||
### 1. 工单视角:【撤销寄售履约 / 重新选择】
|
||||
在工单列表或工单详情页,对「已通过内部库存履约、且**尚未验收**」的工单(状态为 `in_progress` / `pending_acceptance` / `problem`)提供 **【撤销履约】** 操作:
|
||||
* **前置约束(2027-02-21 加固)**:已验收(`accepted`)与已取消(`cancelled`)的工单**一律禁止撤销** —— 钱已结算、货已交付,撤销会造成同一件现货二次出货并重新冻结已退还的押金;买家退款/纠纷改走售后流程。服务端在事务内 `FOR UPDATE` 重新读单并校验状态白名单,撤销原因必填,前端按钮同样按状态 + `consignment.fulfill` 权限渲染。
|
||||
* **分支 A:【回退为未分配(重新选库存 或 发大厅)】**
|
||||
* **原库存处理**:状态从 `consumed` 自动恢复为 `in_stock`,清除绑定的订单号与售出店铺,重新进入待匹配池(**仅当该行仍绑定本工单时**才允许改写,否则 409)。
|
||||
* **工单处理**:状态安全回退为 `unassigned`(未分配),清空指派人、时限、验收草稿与本次发货记录。
|
||||
* **操作结果**:客服可以立即再次点击「发布并核销」选择其他匹配库存,或者直接发布到大厅由打手抢单!
|
||||
* **分支 B:【买家申请退款(取消工单并退库)】**
|
||||
* **原库存处理**:安全解绑并自动回退为在库 `in_stock`,避免资产报废损失。
|
||||
* **工单处理**:状态流转为 `cancelled`(已取消),不再继续履约。
|
||||
* **分支 C:【物资账号异常(作废该库存并重新选)】**
|
||||
* **原库存处理**:该把手枪/账号密码失效或被封,直接变更为 `archived`(已作废),记录作废原因,不再参与匹配,并释放发布押金。
|
||||
* **工单处理**:工单回退为 `unassigned`,供客服挑选另一把正常手枪继续履约。
|
||||
|
||||
### 2. 仓库视角:【退回复库 / 重新上架】
|
||||
在「寄售仓库」管理列表中,对状态为 `consumed`(已出库)的物品行,提供 **【退回复库】** 按钮:
|
||||
* 客服可手动将某件出库的物品解绑原订单,直接重置为 `in_stock` 在库待匹配状态;适合线下沟通中买家临时更换需求、跑单或线下对账异常时的人工修正。
|
||||
* **前置约束(2027-02-21 加固)**:若该物品仍被一张「进行中的工单」占用(`bound_work_order_id` 指向的工单状态不是 `accepted`/`cancelled`),【退回复库】与【下架作废】都会被拒绝,并提示先撤销履约或撤单 —— 避免把别的工单正在交付的现货抢走。
|
||||
* **匹配开关语义**:回库**不再**强制打开 `allow_matching`,而是保留运营的手动设置(该开关是"暂停匹配"的运营闸门,自动回库不应替运营做决定)。编辑接口也不再接受直接改 `status`,状态只能经由带守卫的动作变更。
|
||||
|
||||
### 2.1 工单终止时的自动解绑(2027-02-21 新增)
|
||||
工单进入「不再交付」的状态时,会在**同一事务内**把占用的现货放回在库池(`releaseConsignmentInventoryBindingForWorkOrderWithClient`),不再依赖运营手工退库:
|
||||
* 覆盖路径:打手撤单(`cancelled_by_worker`)、后台取消指派(`unassigned_by_admin`)、后台退回大厅(`returned_to_hall_by_admin`)、后台撤单取消订单(`cancelled_by_admin`)、打手退单申请审核通过、超时处置(`timeout_reopen` / `timeout_cancel_release` / `timeout_cancel_deduct`)、已取消工单重新启用(兜底清理历史绑定)。
|
||||
* **售后处置不在其列**:售后(`resolveAdminAfterSalesCase`)只对**已验收**工单做资金追缴/退赔,工单状态保持 `accepted`,货物已交付,无需解绑。若买家退回实物,由运营在寄售仓库执行【退回复库】(已验收工单不再是"进行中",守卫会放行)并填写原因。
|
||||
* 发布押金保持冻结:现货回到在库池等同于"重新挂卖",符合「发布冻结、驳回/验收释放」的既有口径;卖家要彻底取回物品,由运营执行【下架作废】后释放押金。
|
||||
|
||||
### 3. 数据安全与事务保障
|
||||
* 撤单过程全部置于数据库事务(`withTransaction`)中,确保库存状态与工单状态原子同步,绝不会出现“工单回退了但库存未恢复”的错乱。
|
||||
* 撤销时事务内对工单行 `SELECT ... FOR UPDATE`,库存 UPDATE 带 `status = 'consumed' AND bound_work_order_id = $工单` 条件,命中 0 行即 409,防止与「验收」并发时丢失更新或抢占其他工单的绑定。
|
||||
* 一件现货同一时刻最多绑定一张工单:迁移 `079` 增加部分唯一索引 `uniq_consignment_inventories_bound_work_order` 兜底。
|
||||
* 每次撤单操作均在工单事件(`work_order_events`)中留下完整审计轨迹(记录撤销人、撤回时间、原绑定单号与原因)。
|
||||
|
||||
---
|
||||
|
||||
## 六、 自动化统计报表规划(告别手工 Excel 汇总)
|
||||
|
||||
在后台提供清晰的统计看板,解决运营与财务的核心诉求:
|
||||
|
||||
1. **收购人人效统计(Buyer Stats)**:
|
||||
* 维度:王斌、周杰雄、周顺 等。
|
||||
* 指标:收货总量(件)、收货总花费(元)、已售出件数、库存去化率(%)、平均单价。
|
||||
2. **店铺出库贡献统计(Shop Stats)**:
|
||||
* 维度:拼多多向日葵、稚嫩游戏、佳佳游戏、小鱼儿 等。
|
||||
* 指标:各店铺消耗寄售库存单数、**直接节省的打手赏金总额(元)**、平均履约时效。
|
||||
3. **批次核算统计(Batch Stats)**:
|
||||
* 维度:按 `8.30-1`、`8.30-4` 等批次代码汇总。
|
||||
* 指标:批次总支出、已回款金额、剩余未售资产成本,便于每天下班对账。
|
||||
4. **品类规格周转(Spec Stats)**:
|
||||
* 统计 暗潮、怒嚎、血宴 各钻级手枪的在库均价、库存留存天数与断货预警。
|
||||
|
||||
---
|
||||
|
||||
## 七、 界面交互与原型设计
|
||||
|
||||
### 1. 接单平台 · 寄售仓库管理页(`ConsignmentInventoryPanel`)
|
||||
* **顶部数据指标卡**:
|
||||
* 【在库总数】 / 【在库总成本】 / 【累计节省赏金】 / 【本月出库单数】。
|
||||
* **高级筛选工具条**:
|
||||
* 下拉筛选:收购人(王斌/周杰雄等)、售出店铺(拼多多向日葵等)、批次(如 8.30-1)。
|
||||
* 点选切换:系统(全部/苹果/安卓)、区服(全部/微信/QQ)、系列(全部/暗潮/怒嚎/血宴)、钻级(全部/1钻~6钻)。
|
||||
* 状态标签切换:【全部】/【在库待匹配】/【已出库】/【已作废】。
|
||||
* **表格列表**:
|
||||
* 支持彩色 Badge:收购人(王斌-紫色、周杰雄-粉色等)、店铺名(拼多多向日葵-橙色等)。
|
||||
* **行内 Switch 开关**:【允许匹配】随手可切,切换即刻生效。
|
||||
* 凭据与截图:支持点击缩略图查看大图(包含角色主页图、手枪词条图)。
|
||||
* 敏感交付信息:点击【查看账号密码】,弹窗显示并带有一键复制。
|
||||
* 操作:【编辑】、【作废】、针对已出库物品提供【退回复库】。
|
||||
|
||||
### 2. 工单列表与撤销重选(`WorkOrdersPanel`)
|
||||
* **发布前拦截**:点击「发布并核销」时,弹窗推荐匹配库存,支持一键选用内部履约结单。
|
||||
* **撤销履约入口**:对已完成内部履约的工单,提供操作按钮【撤销寄售履约】:
|
||||
* 弹出确认弹窗,选择“选错重选 / 买家退款 / 物资作废”;
|
||||
* 点击确认后原子回退状态,工单即刻变回未分配状态,支持重新选或发大厅。
|
||||
|
||||
### 3. ~~客服创建收集链接弹窗(`CreateCollectLinkModal`)~~ —— **已下线**
|
||||
> 随收集链接渠道一并移除,客服不再生成对外链接;提报入口为接单平台打手端页面。
|
||||
|
||||
### 4. 寄卖提报审核面板(`ConsignmentSubmissionsPanel`)
|
||||
* 展示卖家提交的待审单据。
|
||||
* 审核弹窗:双栏布局,左边看截图(支持放大看词条),右边核对资料,一键【审核入库】或【驳回】。
|
||||
|
||||
---
|
||||
|
||||
## 八、 技术落地与阶段划分
|
||||
|
||||
1. **阶段一:数据库与模型层**
|
||||
* 创建数据表 `consignment_inventories`、`consignment_collect_links`、`consignment_submissions`。
|
||||
* 配置对应 RBAC 权限(`consignment.view`, `consignment.manage`, `consignment.review`, `consignment.fulfill`)。
|
||||
2. **阶段二:后端核心服务与匹配引擎**
|
||||
* 实现库存 CRUD、多维筛选及统计报表查询;
|
||||
* 实现和平精英规格匹配算法(暗潮/怒嚎/血宴 + 1~6钻 + 1~6璀璨 + `allow_matching` 开关判断);
|
||||
* 实现发布拦截检测接口与一键内部履约原子事务;
|
||||
* **实现工单撤销履约与库存回退重选服务(`revokeConsignmentFulfillment`)**。
|
||||
3. **阶段三:前端管理后台**
|
||||
* 接单平台新增「寄售仓库」面板(包含多维筛选、彩色标签、Switch 开关、卡密查看、退回复库);
|
||||
* 新增「寄售审核」面板(提报来源为接单平台打手端);
|
||||
* 改造工单发布拦截与履约弹窗;
|
||||
* 增加工单列表/详情中的「撤销寄售履约」弹窗与重选逻辑。
|
||||
4. **阶段四:~~移动端 H5 填报端~~ —— 已下线**
|
||||
* 原设计为移动端极简收集页;实际改为接单平台打手端提报,H5 页面已删除。
|
||||
+2
-2
@@ -51,7 +51,6 @@
|
||||
|
||||
```
|
||||
[打手接单平台提报] ── 唯一入口(H5 定向收集链接已整体下线)
|
||||
[客服手工录入] ── 凭据/截图字段名已对齐(本轮修复)
|
||||
│
|
||||
▼
|
||||
提报审核 → consignment_inventories(in_stock, allow_matching)
|
||||
@@ -72,7 +71,8 @@
|
||||
|
||||
> **状态说明(2027-02-21 更新)**:下列清单为第一轮审计结论。本轮已落地批次 A/B 的核心内容,
|
||||
> 逐条状态见每项标题后的标记;未完成项保留原文,作为下一轮输入。
|
||||
> 已交付的改动见 `docs/寄卖库存功能设计与实施方案.md` §五(状态机语义)与迁移 `079_drop_consignment_collect_links.sql`。
|
||||
> 已交付的改动见 `consignment-repo.ts` / `consignment-fulfillment-service.ts` / `consignment-deposit-repo.ts` 与迁移 `079_drop_consignment_collect_links.sql`。
|
||||
> (原设计稿《寄卖库存功能设计与实施方案》已随代码淘汰删除,历史全文见 git 记录。)
|
||||
|
||||
### ✅ 本轮已落地(2027-02-21)
|
||||
|
||||
|
||||
+57
-1
@@ -25,7 +25,11 @@ HAVING COUNT(*) > 1;
|
||||
|
||||
-- 2. 同一提报单重复冻结了发布押金?
|
||||
-- 会让唯一索引 uniq_worker_wallet_ledgers_consignment_publish_freeze 创建失败。
|
||||
-- 期望:0 行。若有行,需人工决定保留哪笔冻结(多冻的部分按 deposit_release 冲正)。
|
||||
-- 期望:0 行。注意 079 自身会先去重再建索引——每组(打手+提报单)保留最早一条冻结流水,
|
||||
-- 多余行改 payload 标记 kind=consignment_publish_superseded(移出押金口径与索引范围,
|
||||
-- 原行留审计)并按 LEAST(多冻总额, 冻结池实有) 退还押金、写 consignment_publish_dedup_refund
|
||||
-- 修正流水。所以有行也能建起索引;但若该提报单的押金此前已被释放/没收过,
|
||||
-- 自动退款会重复退(见 2.1),必须先人工确认。
|
||||
SELECT
|
||||
payload_json ->> 'consignmentSubmissionId' AS submission_id,
|
||||
COUNT(*) AS freeze_rows,
|
||||
@@ -36,6 +40,58 @@ WHERE ledger_type = 'deposit_freeze'
|
||||
GROUP BY 1
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
-- 2.1(只读)重复退款风险:同提报单有多条冻结流水,且该提报单的押金已经被释放/没收过。
|
||||
-- 判据:already_settled_fen > 0 时,079 的自动退款会把「已经退过的钱」再退一次——
|
||||
-- 钱从同一打手其它提报单的冻结额里出,之后那张单释放/没收会因为
|
||||
-- frozen < outstanding 抛 consignment_deposit_balance_inconsistent(接口 500)。
|
||||
-- 命中就必须先人工处理,不要直接上库。safe_refund_should_be_fen = 应当退还的金额。
|
||||
-- 期望:0 行。
|
||||
WITH fr AS (
|
||||
SELECT worker_id, payload_json ->> 'consignmentSubmissionId' AS sub, id, ABS(amount) AS amt
|
||||
FROM worker_wallet_ledgers
|
||||
WHERE ledger_type = 'deposit_freeze'
|
||||
AND COALESCE(payload_json ->> 'kind', '') = 'consignment_publish'
|
||||
AND COALESCE(payload_json ->> 'consignmentSubmissionId', '') <> ''
|
||||
),
|
||||
g AS (
|
||||
SELECT
|
||||
worker_id,
|
||||
sub,
|
||||
COUNT(*) AS freeze_rows,
|
||||
SUM(amt) AS freeze_total_fen,
|
||||
(ARRAY_AGG(amt ORDER BY id ASC))[1] AS kept_fen
|
||||
FROM fr
|
||||
GROUP BY 1, 2
|
||||
),
|
||||
s AS (
|
||||
SELECT
|
||||
worker_id,
|
||||
payload_json ->> 'consignmentSubmissionId' AS sub,
|
||||
COALESCE(SUM(CASE WHEN ledger_type IN ('deposit_release', 'deposit_deduction')
|
||||
THEN ABS(amount) ELSE 0 END), 0) AS settled_fen
|
||||
FROM worker_wallet_ledgers
|
||||
WHERE COALESCE(payload_json ->> 'kind', '') = 'consignment_publish'
|
||||
AND COALESCE(payload_json ->> 'consignmentSubmissionId', '') <> ''
|
||||
GROUP BY 1, 2
|
||||
)
|
||||
SELECT
|
||||
g.worker_id,
|
||||
g.sub AS submission_id,
|
||||
g.freeze_rows,
|
||||
g.freeze_total_fen - g.kept_fen AS dup_fen,
|
||||
COALESCE(s.settled_fen, 0) AS already_settled_fen,
|
||||
COALESCE(w.frozen_deposit_amount, 0) AS wallet_frozen_fen,
|
||||
LEAST(g.freeze_total_fen - g.kept_fen, COALESCE(w.frozen_deposit_amount, 0))
|
||||
AS migration_will_refund_fen,
|
||||
GREATEST(0, (g.freeze_total_fen - COALESCE(s.settled_fen, 0)) - g.kept_fen)
|
||||
AS safe_refund_should_be_fen
|
||||
FROM g
|
||||
LEFT JOIN s ON s.worker_id = g.worker_id AND s.sub = g.sub
|
||||
LEFT JOIN worker_wallets w ON w.worker_id = g.worker_id
|
||||
WHERE g.freeze_rows > 1
|
||||
AND COALESCE(s.settled_fen, 0) > 0
|
||||
ORDER BY 1, 2;
|
||||
|
||||
-- 3. 待删除对象的影响面(收集链接渠道下线)。
|
||||
-- 用 DO 块 + 动态 SQL,保证"表/列已被删掉"时脚本也能跑(否则会直接报错中断)。
|
||||
DO $$
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
4. **TaskContext**:`types/task-context.ts`,`parseTaskContext` 返回 `TaskContext`
|
||||
5. **ClaimIdentity 契约**:`types/claim-identity.ts`(`ClaimIdentity` / `ClaimIdentityPayload` / `ClaimIdentityAdminSummary`)
|
||||
6. **Admin 任务详情契约**:`types/admin/task-detail.ts`,`getAdminTaskDetail(): Promise<AdminTaskDetailView>`
|
||||
7. **normalize 出口具名类型**:`KuaishouCloudFlow`、`KuaishouFeifeiFlow`
|
||||
7. **normalize 出口具名类型**:`AffiliateDashFlow`(`apps/backend/src/services/fulfillment/affiliate-dash/index.ts:304`,由 `normalizeAffiliateDashFlow` 返回);原 `KuaishouCloudFlow` / `KuaishouFeifeiFlow` 已随通道下线移除(全库 grep 0 命中)
|
||||
8. **前后端 claimIdentity 字段对齐**:前端 `ClaimIdentityInfo` / `AdminClaimIdentitySummary`
|
||||
9. **测试 fixture**:`delivery-task-service.test.ts` 减少 `as any`;生产路径去掉 `as any` 状态列表
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
## 规则(现行)
|
||||
|
||||
1. **所有 lewan 自动发货**必须存在 `context.claimIdentity.expectedUid`。
|
||||
1. **所有自动履约(affiliate-dash)发货**必须存在 `context.claimIdentity.expectedUid`。
|
||||
2. 绑定角色 ID(`rid`)必须与 expectedUid 一致,否则禁止 confirm / redeem / dispatch。
|
||||
3. **旧单无 UID**:用户打开领取页补填 UID 后继续;后台发货也会被拦截并提示补 UID。
|
||||
4. **不再**使用「仅确认角色、无 UID」的静默兼容路径。
|
||||
5. feifei:用户填 UID 后拼入 H5;对外仍只返回本站 claimUrl。
|
||||
5. affiliate-dash:用户填 UID 后作为 `data.game_account` 传给 affiliate-dash(`services/fulfillment/affiliate-dash/index.ts:446-447`);对外仍只返回本站 claimUrl。
|
||||
|
||||
## 运营可见
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
- Step1:填 UID
|
||||
- Step2:绑定;匹配成功后可 **一键确认兑换**(无需再点「下一步」)
|
||||
- mock 任务仍可不调真实 CT
|
||||
- dev-mock 任务可不调真实 affiliate-dash 平台(`flow.mock.enabled` 短路建单 / 绑定 / 提交 / 同步)
|
||||
|
||||
## 短链
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
> ⚠ 本仓库当前**未接入** cloudtentacles 通道:该履约通道已下线,`apps/backend/src/services/platforms/` 下已无对应适配器,`fulfillment/executors/registry.ts:18-19` 对历史通道只保留只读 legacy stub(`legacy-retired-executor.ts`)。本文是第三方(cloudtentacles)官方接口样例,仅作外部参考。
|
||||
> 现行自动履约通道是 affiliate-dash(`apps/backend/src/services/platforms/affiliate-dash/`,走 `/api/client/v1`);通道现状见 `docs/ affiliate_dash_发货平台/affiliate-dash对接.md`。
|
||||
|
||||
查询SKU列表
|
||||
```
|
||||
{
|
||||
|
||||
@@ -22,6 +22,6 @@
|
||||
|
||||
`测试-关联商品1----3676797936`
|
||||
|
||||
系统会把 `测试-关联商品1` 当作商品名字匹配 cloudtentacles,把 `3676797936` 当作快手小店 shopId 用于核销。
|
||||
系统会把 `测试-关联商品1` 当作商品名字,走 affiliate-dash 的匹配链路(skuMapping 指定匹配 → 商品路由规则 → 全局优先级,默认仅 `affiliate_dash`,未命中落 `manual_dispatch`);把 `3676797936` 当作快手小店 shopId 用于核销。
|
||||
|
||||
这是最重要的信息
|
||||
|
||||
+90
-95
@@ -7,8 +7,8 @@
|
||||
| 模块 | 定位 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 91 卡券 | 订单来源 + 发链接通道 | 91 调用我方下单接口,我方生成领取链接;91 查询订单时拿到领取链接,并发送给用户。 |
|
||||
| kuaishou-cloud | 发货平台 1 | 已对接,用于发放它支持的皮肤、道具。 |
|
||||
| kuaishou-feifei | 发货平台 2 | 新增发货平台,作为 kuaishou-cloud 的补充,覆盖 kuaishou-cloud 没有的皮肤、道具。 |
|
||||
| affiliate-dash | 现行发货平台(自动履约) | 当前唯一自动履约通道,order_site 作为其商户走开放接口 `/api/client/v1`,发放皮肤、道具。 |
|
||||
| kuaishou-cloud / kuaishou-feifei | 已下线发货平台 | 历史乐玩 / 飞飞通道已下线,executor 仅保留只读 legacy stub(`legacy-retired-executor.ts`),不再发货。 |
|
||||
| 快手电子凭证 | 领取流程里的自动核销能力 | 用来替代用户在领取页手动输入核销码的步骤,不替代 91,也不替代发货平台。 |
|
||||
|
||||
因此整体设计不是“新增订单来源”,而是:
|
||||
@@ -16,7 +16,7 @@
|
||||
```text
|
||||
91 负责进单和把领取链接发给用户
|
||||
我方领取页负责统一承接用户
|
||||
kuaishou-cloud / kuaishou-feifei 负责真实发货
|
||||
kuaishou-cloud / kuaishou-feifei 通道已下线,affiliate-dash 负责真实发货
|
||||
快手电子凭证负责把领取页里的核销码输入步骤自动化
|
||||
```
|
||||
|
||||
@@ -26,8 +26,8 @@ kuaishou-cloud / kuaishou-feifei 负责真实发货
|
||||
91 卡券下单
|
||||
-> 我方创建内部订单和履约任务
|
||||
-> 按商品选择发货平台
|
||||
-> kuaishou-cloud
|
||||
-> kuaishou-feifei
|
||||
-> affiliate-dash(自动履约)
|
||||
-> manual_dispatch(人工兜底)
|
||||
-> 91 卡券查询订单
|
||||
-> 我方返回统一 claimUrl
|
||||
-> 91 卡券把 claimUrl 发给用户
|
||||
@@ -35,7 +35,7 @@ kuaishou-cloud / kuaishou-feifei 负责真实发货
|
||||
-> 系统检查快手电子凭证状态
|
||||
-> 已有关联电子凭证:跳过手动输入核销码
|
||||
-> 未关联电子凭证:保留兜底处理
|
||||
-> 用户继续完成绑定 / 跳转发货平台 H5
|
||||
-> 用户继续完成绑定 / 提交发货
|
||||
-> 发货平台完成履约
|
||||
-> 我方更新任务状态
|
||||
-> 我方完成快手电子凭证核销闭环
|
||||
@@ -54,7 +54,7 @@ kuaishou-cloud / kuaishou-feifei 负责真实发货
|
||||
claimUrl = https://你的域名/#/claim/{token}
|
||||
```
|
||||
|
||||
不要直接把 kuaishou-cloud 绑定链接或 kuaishou-feifei 的 H5 链接返回给 91。原因是领取页需要统一处理:
|
||||
不要直接把 affiliate-dash 的绑定链接返回给 91。原因是领取页需要统一处理:
|
||||
|
||||
- 商品最终走哪个发货平台;
|
||||
- 是否已经通过快手电子凭证完成自动核销;
|
||||
@@ -67,48 +67,51 @@ claimUrl = https://你的域名/#/claim/{token}
|
||||
|
||||
```text
|
||||
91 productNo
|
||||
-> 商品匹配
|
||||
-> 命中 kuaishou-cloud 商品
|
||||
-> executor_key = kuaishou_ct_assisted
|
||||
-> 命中 kuaishou-feifei 商品
|
||||
-> executor_key = kuaishou_feifei
|
||||
-> 商品匹配(skuMapping 指定匹配:91 编码 / 商品名 -> affiliate-dash sku)
|
||||
-> 命中且 affiliate-dash 通道可用
|
||||
-> executor_key = affiliate_dash
|
||||
-> 未命中
|
||||
-> 进入待补全 / 人工处理
|
||||
-> 按履约路由配置:商品路由规则 -> 全局优先级
|
||||
-> 仍未命中 -> 待补全 / 人工履约(manual_dispatch)
|
||||
```
|
||||
|
||||
优先级建议先做成可配置规则,不在代码里硬编码。第一阶段可以采用:
|
||||
优先级由可配置规则决定(`services/fulfillment/routing-config-service.ts`),当前顺序:
|
||||
|
||||
1. 显式覆盖规则优先;
|
||||
2. 再按商品名匹配 kuaishou-cloud;
|
||||
3. 再按商品名或 `product_code` 匹配 kuaishou-feifei;
|
||||
4. 都未命中则进入待补全队列。
|
||||
1. 指定匹配(显式映射)优先;
|
||||
2. 再按商品路由规则(商品名 exact / contains);
|
||||
3. 再按全局优先级 `defaultExecutorPriority`(当前默认仅 `affiliate_dash`);
|
||||
4. 都未命中则回落人工履约(`manual_dispatch`)或保持未配置。
|
||||
|
||||
## 5. kuaishou-feifei 接入方式
|
||||
## 5. kuaishou-feifei 接入方式(已下线)
|
||||
|
||||
kuaishou-feifei 的主要交付物是 `h5.recharge_url`。
|
||||
kuaishou-feifei 通道已下线:历史交付物是 `h5.recharge_url`,相关 executor / API client / H5 对接均已移除,历史任务由只读 legacy stub(`legacy-retired-executor.ts`)承接,不再发货。
|
||||
|
||||
创建任务后,系统应向 kuaishou-feifei 创建订单,并把关键字段保存到任务上下文:
|
||||
现行自动履约通道是 affiliate-dash:order_site 作为其商户走 `/api/client/v1`,关键字段保存到任务上下文 `context_json.affiliateDash`(具名类型 `AffiliateDashFlow`):
|
||||
|
||||
```json
|
||||
{
|
||||
"kuaishouFeifei": {
|
||||
"platformOrderNo": "内部幂等单号",
|
||||
"orderNo": "发货平台内部单号",
|
||||
"productCode": "商品编码",
|
||||
"rechargeStatus": 15,
|
||||
"statusLabel": "待绑定",
|
||||
"h5": {
|
||||
"rechargeUrl": "http://..."
|
||||
},
|
||||
"affiliateDash": {
|
||||
"flowType": "affiliate_dash",
|
||||
"sku": "affiliate-dash sku",
|
||||
"productName": "商品名",
|
||||
"orderNo": "affiliate-dash 平台单号",
|
||||
"clientOrderNo": "内部幂等单号",
|
||||
"orderStatus": "paid / delivering / delivered / ship_failed / cancelled",
|
||||
"canShip": true,
|
||||
"bindUrl": "http://...",
|
||||
"qrUrl": "http://...",
|
||||
"bound": false,
|
||||
"submitStatus": "",
|
||||
"consumeStatus": "",
|
||||
"lastSyncedAt": "2026-07-07T00:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
领取页根据当前任务的 executor 展示不同内容:
|
||||
领取页根据当前任务的 executor 展示不同内容(`services/claim/kuaishou-cloud-claim-context.ts`):
|
||||
|
||||
- `kuaishou_ct_assisted`:展示现有 kuaishou-cloud 绑定流程;
|
||||
- `kuaishou_feifei`:展示或跳转 kuaishou-feifei 的 `h5.recharge_url`;
|
||||
- `affiliate_dash`:本站领取页自建流程(查 delivery -> 填 UID -> bind -> 轮询 bind-result -> submit);
|
||||
- `kuaishou_ct_assisted` / `kuaishou_feifei`:通道已下线,仅展示历史信息与「该履约渠道已下线」提示;
|
||||
- `manual_dispatch`:进入人工兜底。
|
||||
|
||||
## 6. 快手电子凭证的嵌入点
|
||||
@@ -154,25 +157,28 @@ kuaishou-feifei 的主要交付物是 `h5.recharge_url`。
|
||||
|
||||
## 7. 核销完成时机
|
||||
|
||||
电子凭证不应该在用户刚打开领取页时就立刻标记为已消费。
|
||||
电子凭证的核销时机与领取页打开解耦:不因用户打开领取页就标记已消费。
|
||||
|
||||
推荐时机:
|
||||
当前实现(`services/fulfillment/affiliate-dash/index.ts:87-117,212-241`)在**建单成功、91 侧可取到领取链接时**即核销(91 卡信息一经消耗),不再等发货完成:
|
||||
|
||||
```text
|
||||
发货平台实际履约成功
|
||||
准备履约任务成功(拿到领取链接)
|
||||
-> 我方调用快手电子凭证核销
|
||||
-> 凭证状态更新为 CONSUMED(consumeStatus = success)
|
||||
发货回调 delivered
|
||||
-> 只做状态收敛:consumeStatus 已 success / not_required 则短路
|
||||
-> failed / 空才补核销(兜底)
|
||||
-> 我方更新 task 为 completed / redeemed
|
||||
-> 我方触发快手电子凭证核销回调
|
||||
-> 电子凭证状态更新为 CONSUMED
|
||||
```
|
||||
|
||||
这样可以避免用户打开链接但未完成绑定时,快手侧已经显示核销成功。
|
||||
核销失败不阻塞下单与链接发放(`consumeStatus = failed` + warn 日志),由 delivered 回调兜底。
|
||||
|
||||
不同发货平台的成功判定:
|
||||
|
||||
| 发货平台 | 成功判定 |
|
||||
| --- | --- |
|
||||
| kuaishou-cloud | 当前发货流程完成,任务进入完成态。 |
|
||||
| kuaishou-feifei | 查询或异步通知返回 `recharge_status = 30`。 |
|
||||
| affiliate-dash | `submit` 成功 / 回调 `order.shipping.updated` 且 `order_status = delivered`,任务进入完成态。 |
|
||||
| kuaishou-cloud / kuaishou-feifei | 已下线,不再产生新的成功判定;历史任务只读。 |
|
||||
|
||||
失败时:
|
||||
|
||||
@@ -185,18 +191,18 @@ kuaishou-feifei 的主要交付物是 `h5.recharge_url`。
|
||||
当前项目已经有以下基础能力:
|
||||
|
||||
- 91 卡券下单和查询;
|
||||
- kuaishou-cloud 发货流程;
|
||||
- 快手电子凭证 `send-code / query-code / destroy-code / consume-code` 接口骨架;
|
||||
- 领取页手动输入核销码流程。
|
||||
- affiliate-dash 自动发货流程(唯一自动履约通道);
|
||||
- 快手电子凭证 `send-code / query-code / destroy-code / consume-code` 接口;
|
||||
- 领取页手动输入核销码流程(异常兜底)。
|
||||
|
||||
后续需要把它们串起来:
|
||||
后续调整方向(当前状态):
|
||||
|
||||
1. 91 查询仍返回统一 `claimUrl`。
|
||||
2. 商品匹配支持多发货平台。
|
||||
3. kuaishou-feifei 新增独立 executor 和 API client。
|
||||
4. 领取页根据 task executor 展示对应发货流程。
|
||||
5. 快手电子凭证状态进入 task context,用于跳过手动输入核销码。
|
||||
6. 发货成功后再触发快手电子凭证核销。
|
||||
1. 91 查询仍返回统一 `claimUrl`(已完成)。
|
||||
2. 商品匹配:skuMapping 指定匹配 -> 商品路由规则 -> 全局优先级(默认仅 `affiliate_dash`),未命中回落人工履约(已完成)。
|
||||
3. kuaishou-feifei / kuaishou-cloud 已下线,executor 仅保留只读 legacy stub,不再新增独立 executor 和 API client(已处理)。
|
||||
4. 领取页根据 task executor 展示对应发货流程(affiliate_dash 自建流程;下线通道展示历史信息)(已完成)。
|
||||
5. 快手电子凭证状态进入 task context,用于跳过手动输入核销码(已完成)。
|
||||
6. 建单成功(拿到领取链接)即触发快手电子凭证核销,发货回调只做状态收敛(已完成)。
|
||||
7. 保留手动输入核销码作为异常兜底,而不是主路径。
|
||||
|
||||
## 9. 数据关联
|
||||
@@ -323,26 +329,18 @@ send-code.oid
|
||||
|
||||
### 11.1 当前逻辑
|
||||
|
||||
当前实现中,`send-code` 返回的 `eticket.id` 来自本地 task id:
|
||||
当前实现中,`send-code` 返回的 `eticket.id` 是独立券表的业务券号 `voucher_code`,不再依赖 task id:
|
||||
|
||||
```text
|
||||
eticket.id = fulfillment_tasks.id
|
||||
eticket.id = kuaishou_industry_vouchers.voucher_code
|
||||
voucher_code = 'KSV' + Crockford Base32(10 字节随机数)
|
||||
```
|
||||
|
||||
真实测试单中:
|
||||
来源:`services/platforms/kuaishou-industry/send-code-service.ts:86-116`(按 `oid + unit_index` upsert 券记录后返回)、`services/platforms/kuaishou-industry/voucher-service.ts:95`(`id / code = voucher_code`)、`repositories/kuaishou-industry-voucher-repo.ts:26-53`(`KSV` 前缀 + Crockford Base32 生成,唯一冲突自动重试)。
|
||||
|
||||
```text
|
||||
eticket.id = 2038
|
||||
```
|
||||
历史测试单(2026-07-07)里 `eticket.id = 2038`,那是早期把 task id 当券号的实现,已废弃。
|
||||
|
||||
这说明当前“核销码/券号”不是快手生成的,而是我方创建 task 后,把 task id 当作电子凭证 id 返回给快手。
|
||||
|
||||
这个方式有两个问题:
|
||||
|
||||
1. task 属于当前实现里的 `kuaishou-industry` 孤立订单链,尚未绑定到 91 订单 task。
|
||||
2. task id 虽然全局唯一,但它和任务生命周期耦合太强;后续如果任务重建、补配置、切换发货平台,会影响电子凭证反查和幂等。
|
||||
|
||||
### 11.2 推荐目标
|
||||
### 11.2 券号要求
|
||||
|
||||
电子凭证券号应该满足:
|
||||
|
||||
@@ -354,58 +352,55 @@ eticket.id = 2038
|
||||
| 可延迟绑定 | 如果快手 `send-code` 早于 91 task 准备完成,券号也能先落库,后续再绑定 task。 |
|
||||
| 可核销 | 发货完成后,能用同一张券更新为 `CONSUMED`,并记录核销流水。 |
|
||||
|
||||
### 11.3 推荐数据模型
|
||||
### 11.3 已落地数据模型
|
||||
|
||||
建议新增独立的电子凭证券表,而不是继续只依赖 task id:
|
||||
已新增独立的电子凭证券表 `kuaishou_industry_vouchers`(`db/migrations/001_init.sql:272-297`,`eticket_type` 见 `db/migrations/003_kuaishou_industry_voucher_eticket_type.sql`),不再只依赖 task id:
|
||||
|
||||
```text
|
||||
kuaishou_industry_vouchers
|
||||
id BIGSERIAL PRIMARY KEY
|
||||
voucher_code TEXT UNIQUE NOT NULL
|
||||
voucher_code TEXT NOT NULL UNIQUE
|
||||
oid TEXT NOT NULL
|
||||
order_id BIGINT NULL
|
||||
task_id BIGINT NULL
|
||||
order_id BIGINT NULL REFERENCES orders(id) ON DELETE SET NULL
|
||||
task_id BIGINT NULL REFERENCES fulfillment_tasks(id) ON DELETE SET NULL
|
||||
unit_index INTEGER NOT NULL
|
||||
token TEXT NOT NULL
|
||||
status TEXT NOT NULL -- UNUSED / CONSUMED / DESTROYED
|
||||
valid_start_time BIGINT NOT NULL
|
||||
valid_end_time BIGINT NOT NULL
|
||||
seller_id TEXT NOT NULL DEFAULT ''
|
||||
token TEXT NOT NULL DEFAULT ''
|
||||
eticket_type TEXT NOT NULL DEFAULT ''
|
||||
status TEXT NOT NULL DEFAULT 'UNUSED' -- UNUSED / CONSUMED / DESTROYED
|
||||
valid_start_time BIGINT NOT NULL DEFAULT 0
|
||||
valid_end_time BIGINT NOT NULL DEFAULT 0
|
||||
consume_serial_num TEXT NOT NULL DEFAULT ''
|
||||
consume_details_json JSONB NOT NULL DEFAULT '[]'::jsonb
|
||||
consumed_at TIMESTAMPTZ NULL
|
||||
destroyed_at TIMESTAMPTZ NULL
|
||||
raw_payload_json JSONB NOT NULL DEFAULT '{}'::jsonb
|
||||
send_callback_status TEXT NOT NULL DEFAULT 'success'
|
||||
send_callback_attempt_count / send_callback_last_error / send_callback_response_json / send_callback_sent_at
|
||||
created_at TIMESTAMPTZ NOT NULL
|
||||
updated_at TIMESTAMPTZ NOT NULL
|
||||
|
||||
唯一约束:
|
||||
UNIQUE(oid, unit_index)
|
||||
UNIQUE(voucher_code)
|
||||
UNIQUE(oid, unit_index)
|
||||
```
|
||||
|
||||
券号生成建议:
|
||||
券号生成(`repositories/kuaishou-industry-voucher-repo.ts:26-53`):
|
||||
|
||||
```text
|
||||
voucher_code = String(kuaishou_industry_vouchers.id)
|
||||
voucher_code = 'KSV' + Crockford Base32(10 字节随机数)
|
||||
```
|
||||
|
||||
理由:
|
||||
|
||||
- 数字字符串兼容性最好;
|
||||
- 数据库主键保证不重复;
|
||||
- 重复 `send-code` 时通过 `UNIQUE(oid, unit_index)` 找回原记录,返回原 `voucher_code`;
|
||||
- 业务券号与 task 生命周期解耦,任务重建 / 换发货通道不影响反查;
|
||||
- 数据库 `UNIQUE(voucher_code)` 保证不重复,生成冲突自动重试(最多 5 次);
|
||||
- 重复 `send-code` 时通过 `UNIQUE(oid, unit_index)` + `ON CONFLICT ... DO UPDATE` 找回原记录,返回原 `voucher_code`;
|
||||
- 不依赖 task 是否已经创建。
|
||||
|
||||
如果后续确认快手完全支持任意字符串,也可以升级为带前缀的业务券号,例如:
|
||||
|
||||
```text
|
||||
voucher_code = KSEV-{oid}-{unitIndex}
|
||||
```
|
||||
|
||||
但第一阶段建议优先使用数字字符串,降低快手侧兼容风险。
|
||||
|
||||
### 11.4 send-code 幂等规则
|
||||
|
||||
`send-code` 处理流程应改为:
|
||||
`send-code` 处理流程(已实现):
|
||||
|
||||
```text
|
||||
收到 oid + num
|
||||
@@ -424,17 +419,17 @@ voucher_code = KSEV-{oid}-{unitIndex}
|
||||
第一次 send-code:
|
||||
oid = 2618800083429561
|
||||
num = 1
|
||||
eticket.id = 2038
|
||||
eticket.id = KSVASXT6Q955WT4SS36
|
||||
|
||||
第二次 send-code:
|
||||
oid = 2618800083429561
|
||||
num = 1
|
||||
eticket.id 仍然 = 2038
|
||||
eticket.id 仍然 = KSVASXT6Q955WT4SS36
|
||||
```
|
||||
|
||||
### 11.5 有效期生成规则
|
||||
|
||||
真实测试单里快手没有传 `certActualStartTime / certActualEndTime`,并且 `certStartTime / certEndTime` 也可能都是 0。当前系统返回了:
|
||||
真实测试单里快手没有传 `certActualStartTime / certActualEndTime`,并且 `certStartTime / certEndTime` 也可能都是 0。当时系统返回了:
|
||||
|
||||
```text
|
||||
validStartTime = 0
|
||||
@@ -476,12 +471,12 @@ validStartTime = send-code 处理时的当前毫秒时间戳
|
||||
validEndTime = validStartTime + 3 * 86400000
|
||||
```
|
||||
|
||||
### 11.6 后续核销规则
|
||||
### 11.6 核销规则(已实现)
|
||||
|
||||
发货完成后,不再要求用户输入快手核销码,而是系统根据 voucher 主动完成核销闭环:
|
||||
不再要求用户输入快手核销码,而是系统根据 voucher 主动完成核销闭环。affiliate-dash 在**建单成功(拿到领取链接)**时即核销,`delivered` 回调只做状态收敛与兜底补核销(`services/fulfillment/affiliate-dash/index.ts:87-117,212-241` / `services/platforms/kuaishou-industry/voucher-service.ts:241-291`):
|
||||
|
||||
```text
|
||||
发货平台履约成功
|
||||
建单成功 / affiliate-dash 已消耗(91 卡信息到手)
|
||||
-> 找到 task 绑定的 voucher
|
||||
-> 检查 voucher.status = UNUSED
|
||||
-> 生成稳定 consume_serial_num
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
# 工程债:lewan 拆分、默认角色、短链
|
||||
|
||||
## 命名约定
|
||||
|
||||
| 叫法 | 用途 |
|
||||
| --- | --- |
|
||||
| lewan / kuaishou-lewan | 业务/文案 |
|
||||
| `kuaishou_ct_assisted` | `executor_key` |
|
||||
| `kuaishou-cloud/` | 履约领域模块路径 |
|
||||
| `cloudtentacles` | 外部发货平台 API |
|
||||
|
||||
## lewan 模块拆分(已做)
|
||||
|
||||
```text
|
||||
kuaishou-cloud/
|
||||
index.ts # barrel 导出
|
||||
prepare-fulfillment.ts # prepare + 刷新 bindUrl
|
||||
rebind-role.ts
|
||||
probe-bind-url.ts
|
||||
refresh-role-info.ts
|
||||
role-state.ts # 默认角色快照 + ready 状态(UID 主闸)
|
||||
delivery-plan.ts
|
||||
ensure-claim-link.ts
|
||||
dispatch-role-sync.ts
|
||||
redeem-fulfillment.ts # 一键兑换用例
|
||||
confirm-role.ts # 确认角色用例
|
||||
dispatch-fulfillment.ts # 发货编排
|
||||
dispatch-stock.ts # 库存/采购/下发
|
||||
dispatch-failure.ts # 发货失败落库
|
||||
return-fulfillment.ts # 退号 + 电子凭证核销收尾
|
||||
mock-helpers.ts
|
||||
task-finalization.ts # 兼容 re-export
|
||||
domain.ts
|
||||
|
||||
executors/
|
||||
kuaishou-cloud-executor # preparePaid + prepareBinding + rebind/refresh/confirm + redeem + dispatch + return
|
||||
registry # prepare/rebind/refresh/confirm/redeem/dispatch/return 统一入口
|
||||
```
|
||||
|
||||
对外 import 路径仍为 `.../kuaishou-cloud/index.js`。
|
||||
claim / admin 应优先走 `executors/registry`,不要直连子模块动作。
|
||||
|
||||
## 默认角色
|
||||
|
||||
- 主闸:`claimIdentity.expectedUid` 与绑定 `rid` 一致。
|
||||
- `isDefaultRole` 仅诊断文案;有角色信息即 `ready` 并落库 rid。
|
||||
- `hasKuaishouCloudCustomerRole` 改为「是否已有绑定角色」,不再要求不同于默认角色。
|
||||
|
||||
## 短链
|
||||
|
||||
- `/s/:code` **只读解析**历史链接。
|
||||
- feifei claim **不再**依赖 shortLink 交付;若仍调用 `ensureShortLinkForTarget` 且 source 为 feifei claim,会打 warn。
|
||||
- 新业务:本站 claimUrl + H5 拼 `uid`。
|
||||
@@ -1,61 +0,0 @@
|
||||
# 开发 Mock(领取 / 91 查单)
|
||||
|
||||
优先使用 **后台 GUI**:登录运营后台 → 左侧 **开发 / 开发 Mock**(仅非 production 环境显示)。
|
||||
|
||||
生产镜像默认 `NODE_ENV=production`,接口会 403。本地 docker-compose.dev 默认可用;特殊环境可设 `ENABLE_DEV_MOCK=1`。
|
||||
|
||||
## GUI 能做什么
|
||||
|
||||
| 卡片 | 作用 |
|
||||
| --- | --- |
|
||||
| lewan 领取 | 写库生成 91 订单 + claim,强制 UID 匹配流(不调 CloudTentacles) |
|
||||
| feifei 领取 | 写库生成 feifei 任务 + 本站 claim;H5 带 uid(不调真实 feifei) |
|
||||
| 91 查单 | 生成带签名的 `POST /api/v1/open/91/orders/query`,可本机执行 |
|
||||
|
||||
推荐路径:
|
||||
|
||||
1. lewan 选 `binding` + UID `10001` → 生成 → 打开领取链接 → 确认兑换
|
||||
2. 用同一 orderNo 点「模拟 91 查单」验证 cards / claimUrl
|
||||
|
||||
## CLI(可选)
|
||||
|
||||
```bash
|
||||
# lewan
|
||||
docker compose -f docker-compose.dev.yml exec -T backend \
|
||||
npm run mock:claim -- --step=binding --uid=10001
|
||||
|
||||
# feifei
|
||||
docker compose -f docker-compose.dev.yml exec -T backend \
|
||||
npm run mock:feifei -- --step=ready --uid=166909256
|
||||
|
||||
# 91 查单(需 KAQUAN91_SECRET)
|
||||
docker compose -f docker-compose.dev.yml exec -T backend \
|
||||
npm run mock:open91 -- --baseUrl=http://127.0.0.1:3000 --mode=query --orderNo=MOCK91...
|
||||
```
|
||||
|
||||
## 步骤说明
|
||||
|
||||
### lewan `--step`
|
||||
|
||||
| step | 含义 |
|
||||
| --- | --- |
|
||||
| `uid` | 待填 UID(Step1) |
|
||||
| `binding` | 已预填 UID,角色 ID 已匹配(推荐) |
|
||||
| `confirm` | 待确认兑换 |
|
||||
| `result` | 已模拟完成 |
|
||||
|
||||
兑换走 `context.mock.enabled=true`,不会真实发货。
|
||||
|
||||
### feifei `--step`
|
||||
|
||||
| step | 含义 |
|
||||
| --- | --- |
|
||||
| `uid` | 待填 UID |
|
||||
| `ready` | 已填 UID,可打开带 uid 的 H5 |
|
||||
| `completed` / `failed` | 模拟终态 |
|
||||
|
||||
## 与真实环境边界
|
||||
|
||||
- Mock **不**替代真实 CloudTentacles / feifei 联调
|
||||
- 91 `create` CLI 仍会走真实商品匹配;造单测领取请用 GUI / `mock:claim` / `mock:feifei`
|
||||
- 写入的是开发库真实行,注意别在生产 volume 上开 Mock
|
||||
@@ -1,71 +0,0 @@
|
||||
# 服务器图片迁移到阿里云 OSS
|
||||
|
||||
本流程只操作服务器上的 MinIO。Mac 本地继续使用 `.env.mac-docker.example` 的 MinIO 配置,不会把本地图片迁走。
|
||||
|
||||
## 1. 准备 OSS
|
||||
|
||||
在阿里云 OSS 创建与现有 MinIO bucket 对应的 bucket,并创建只允许该 bucket 的 RAM 账号。建议 OSS 使用 S3 兼容端点,例如 `https://oss-cn-hangzhou.aliyuncs.com`,不要把 bucket 名写进 endpoint。
|
||||
|
||||
编辑服务器 `.env`,先保留 MinIO 为主存储,并填好 OSS 目标配置:
|
||||
|
||||
```dotenv
|
||||
STORAGE_MODE=dual
|
||||
STORAGE_ENDPOINT=http://minio:9000
|
||||
STORAGE_BUCKET=order-site
|
||||
STORAGE_ACCESS_KEY_ID=原 MinIO 账号
|
||||
STORAGE_SECRET_ACCESS_KEY=原 MinIO 密钥
|
||||
STORAGE_REGION=us-east-1
|
||||
STORAGE_OSS_ENDPOINT=https://oss-cn-hangzhou.aliyuncs.com
|
||||
STORAGE_OSS_BUCKET=OSS bucket 名
|
||||
STORAGE_OSS_ACCESS_KEY_ID=OSS RAM AccessKey ID
|
||||
STORAGE_OSS_SECRET_ACCESS_KEY=OSS RAM AccessKey Secret
|
||||
STORAGE_OSS_REGION=oss-cn-hangzhou
|
||||
```
|
||||
|
||||
重建后端容器使配置生效。`dual` 模式下新上传的原图和缩略图会同时写入 MinIO、OSS;读取仍优先 MinIO,失败时回退 OSS,所以迁移期间无需停机。
|
||||
|
||||
## 2. 增量复制与校验
|
||||
|
||||
在服务器项目目录执行。容器镜像已内置迁移程序,下面命令不会停止现有后端:
|
||||
|
||||
```bash
|
||||
docker compose exec backend node dist/storage-migrate.js copy
|
||||
docker compose exec backend node dist/storage-migrate.js verify
|
||||
```
|
||||
|
||||
复制可以重复执行,目标中已有且大小、ETag 一致的对象会跳过。程序默认 8 路并发,可通过 `docker compose exec -e STORAGE_MIGRATION_CONCURRENCY=16 ...` 调整(最大 32)。校验会比较源、目标每个对象的 SHA-256、大小和 Content-Type;复制阶段的 ETag 仅用于幂等跳过,最终仍会对所有对象做完整内容校验。复制期间仍有新上传时,复制完成后再次执行 `copy` 和 `verify`,直到校验通过且没有遗漏。
|
||||
|
||||
## 3. 切换到 OSS
|
||||
|
||||
确认校验通过后,把服务器 `.env` 的 `STORAGE_MODE` 改为 `oss`,重新创建 backend:
|
||||
|
||||
```bash
|
||||
docker compose up -d --no-deps backend
|
||||
curl -fsS https://你的域名/health/ready
|
||||
```
|
||||
|
||||
此时新旧图片都只从 OSS 读取,数据库中的对象 key 和访问 URL 不需要改动。
|
||||
|
||||
## 4. MinIO 退场
|
||||
|
||||
先观察 OSS 模式运行一段时间,确认图片上传、后台预览、打手端验收图片均正常,再停止并移除 MinIO 容器(若仍由旧版编排管理):
|
||||
|
||||
```bash
|
||||
docker compose --profile storage stop minio
|
||||
docker compose --profile storage rm -f minio
|
||||
```
|
||||
|
||||
生产编排(`docker-compose.yml` 与 `deploy/ubuntu-deploy.sh`)已移除 MinIO 服务与 `storage` profile,此后部署不会再启动 MinIO。
|
||||
|
||||
不要删除 `minio_data` 卷(实际名称 `order_site_minio_data`),至少保留一个回滚窗口。若需要回滚,把 `.env` 的 `STORAGE_MODE` 改回 `dual`,并手动用旧卷拉起一个 MinIO 容器接入 compose 网络(`--network-alias minio` 保证 backend 仍可用 `http://minio:9000` 访问它):
|
||||
|
||||
```bash
|
||||
docker run -d --name minio-rollback \
|
||||
--network order_site_default \
|
||||
--network-alias minio \
|
||||
-v order_site_minio_data:/data \
|
||||
docker.m.daocloud.io/minio/minio:latest \
|
||||
server /data --address ":9000" --console-address ":9001"
|
||||
```
|
||||
|
||||
确认 OSS 已稳定且完成密钥轮换后,再单独评估删除 `minio_data` 卷释放磁盘。
|
||||
+1
-1
@@ -243,7 +243,7 @@ npm run test:industry:http -- --baseUrl=https://ks.khhao.com --load --endpoint=q
|
||||
- [ ] 三个出站回调(发码/核销/销毁)已实现并配置 `KUASHOU_INDUSTRY_SEND_CALLBACK_ENABLED=true`
|
||||
- [ ] access_token 已通过店主主账号 OAuth 获取,48h 自动刷新
|
||||
- [ ] 生产环境 appKey / signSecret 与测试环境独立配置
|
||||
- [ ] 测试用例文档(`测试用例.md`)已填写完整提交快手审核
|
||||
- [ ] 测试用例已用 `apps/backend/scripts/gen-test-cases.ts`(`npm run test:industry:gen`)生成并整理完整,可提交快手审核(测试步骤见 `docs/行业电子凭证/5.测试指南.md`)
|
||||
- [ ] 压力测试通过:发码 100 TPS、查询 500 QPS、销毁 100 TPS,平均耗时 < 100ms
|
||||
- [ ] 所有错误场景(签名错/参数缺失/appkey 错)均能正确处理
|
||||
- [ ] signSecret 未提交到 git 仓库,仅通过环境变量注入
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
基础技术
|
||||
更新时间:2025-03-26
|
||||
阅读数:5722
|
||||
本处介绍开放平台的基础协议和通用接入方式
|
||||
|
||||
1.环境说明
|
||||
两套环境相互独立,数据不可相互使用
|
||||
|
||||
环境 环境地址 说明
|
||||
线上环境 https://open.kwaixiaodian.com 线上环境可直接使用
|
||||
线下环境 https://gw-merchant-staging.test.gifshow.com 测试环境调用需要提供出口IP,并联系快手对接人添加ip白名单
|
||||
2.授权流程
|
||||
2.1 如何获取token
|
||||
首先需要拿到appkey、appsecret、signSecret信息,线下环境找快手提供,线上环境到“开放平台-控制台-应用中心-应用详情”查看,请求示例如下
|
||||
https://gw-merchant-staging.test.gifshow.com/integration/virtual/topup/mobile/order/callback?access_token=ChFvYXV0aC5hY2Nlc3NUb2tlbhJAErnBVtjx5FPdE0AITaSq4xlW6XaTOaV_McGinj-hFivkyxAw1SZ3i28bLEa6xP-C4UuKlRz6uh3lukYRLDcAdRoSeCxQKnQhAPTTL4YL0_NqmqD-IiApwDDKEL1wBW7EbWH0ZRDO3UUNSCxMwkuF7nb57Fn7AygFMAE&appkey=ks683702719562282620&method=integration.virtual.topup.mobile.order.callback¶m=%7B%22orderId%22%3A%22100%22%2C%22businessTime%22%3A%222021-06-08T14%3A16%3A01%2B0800%22%2C%22mobile%22%3A%2215990013607%22%2C%22status%22%3A%22FAILED%22%2C%22amount%22%3A%225000%22%2C%22bizType%22%3A20%7D&signMethod=MD5×tamp=1623132961782&version=1&sign=c091ca3414a9d6429a0fda47c383d202
|
||||
|
||||
注:如果回调失败需要以指数周期(1m、2m、4m、8m...)重试
|
||||
|
||||
注意:
|
||||
|
||||
测试环境需要把自己的出口ip给到快手,配置白名单,否则无权限访问。配置对接人:屈国庆,王哲,杨天问
|
||||
测试环境和线上环境,是两套token,不可混用。测试token,如下方式获取。线上token通过上面sdk调用,有效期48h。
|
||||
Token授权码:参考文档(2.2节 商家开发者角色)
|
||||
授权一定要用店主的主账号来授权,否则token换取到的sellerId就是子账号的id
|
||||
线上token验证:https://open.kwaixiaodian.com/commonTool/tokenAuth/accessToken?cateId=0&typeIndex=1,需要快手app扫描登录
|
||||
测试环境获取token的步骤(参考文档(2.2节 商家开发者角色)):
|
||||
|
||||
测试环境:优先使用集成中心自己调试
|
||||
线上环境:需要按照开放平台的授权文档,通过你的店铺由店主授权(不要用子账号授权),然后用你的redirect_uri来接收code。
|
||||
用授权码code换取长时令牌refreshToken以及访问令牌accessToken
|
||||
示例:https://gw-merchant-staging.test.gifshow.com/oauth2/access_token?app_id=ks683702719562282620&app_secret=zSCiWmGOk_diMCU9k3zHcg&grant_type=code&code=bab4f0378269db7a1603de9d5a381615d7d2c451ffbb32f8ede78ac8f05b6171f7d4eb8c
|
||||
|
||||
用长时令牌refreshToken刷新访问令牌accessToken
|
||||
示例:
|
||||
https://gw-merchant-staging.test.gifshow.com/oauth2/refresh_token?app_id=ks683702719562282620&app_secret=zSCiWmGOk_diMCU9k3zHcg&grant_type=refresh_token&refresh_token=ChJvYXV0aC5yZWZyZXNoVG9rZW4SkAGpBE83BKP9TeiwbEP1-IIQL2G08s8rU-OETIzfs5wFN2HeZgUaR_mfGjKHjHzfV-sicHG4IZmxVKlxQhdAZIdf01AR51ZtPN7sHY3eeW6RMnx8LPKo92V1yk-GnkXiiPMWpn9Vmhq0dk_U_aKh5Jwg7mMySO8IEDoz-fPk9Rc2rjUK3inqxNd7rqTQ9fz316oaEsFIUEDt4EyD090nGWRnwQ5g3SIgY-ctBM_4jrFXxBA9EF1jMaP6as57lNYIyOZf9Qlqq64oBTAB
|
||||
|
||||
后面就可以一直用第三步来刷新accessToken
|
||||
|
||||
3.签名算法
|
||||
3.1 协议
|
||||
https协议,支持GET/POST,调用地址、appkey、signSecret由快手提供,返回结果为json字符串。
|
||||
|
||||
3.2 签名
|
||||
官方签名说明
|
||||
|
||||
快手电商开放平台的所有开放API调用都需要进行加签,服务端会根据请求参数,对签名进行验证,签名不合法的请求将会被拒绝。当前平台支持的签名算法为MD5(signMethod=MD5)
|
||||
|
||||
下面将详细介绍签名流程:
|
||||
|
||||
对所有API系统参数和请求参数(不包括sign参数和byte[]类型的参数),根据参数名称进行字典顺序排序;
|
||||
|
||||
排序前的顺序是:appkey=ks123,version=1,method=open.xxx.xxx,signMethod=MD5,access_token=xxxx,timestamp=1583271919000,param={"title":"短袖", "relItemId":123456, "categoryId":12}
|
||||
|
||||
排序后的顺序是:appkey=ks123¶m={"categoryId":12,"relItemId":123456,"title":"短袖"}&signMethod=MD5×tamp=1583271919000&version=1&signSecret=abc
|
||||
|
||||
|
||||
|
||||
保留=符号,用&符号将多个参数及其值组装在一起,根据上面的示例得到的排序结果为:access_token=xxx&appkey=ks123&method=open.xxx.xxx¶m={"title":"短袖", "relItemId":123456, "categoryId":12}&signMethod=MD5×tamp=1583271919000&version=1
|
||||
|
||||
排序好参数后,在末尾加入signSecret(在应用创建审核通过后由平台分配,在“应用中心-应用列表-应用详情”中可见)进行对应算法的签名计算
|
||||
|
||||
MD5(access_token=xxx&appkey=ks123&method=open.xxx.xxx.xxx¶m={"title":"短袖", "relItemId":123456, "categoryId":12}&signMethod=MD5×tamp=1583271919000&version=1&signSecret=xxxxxx)=sign
|
||||
|
||||
|
||||
|
||||
签名加好后,将sign添加到请求参数中,并对param内容进行encode(双引号"和冒号:也需要encode),这里注意请求参数里面是不包括signSecret的,signSecret只是作为加签因子用于签名sign的计算,
|
||||
|
||||
所以千万不要将signSecret当作请求参数传输,请求参数内容见第3点API调用参数说明的表格内容,请求url样例:
|
||||
|
||||
https://open.kwaixiaodian.com/open/xxx/xxx?access_token=xxx&appkey=ks123&method=open.xxx.xxx.xxx¶m=%7B%22title%22%3A%22%E7%9F%AD%E8%A2%96%22%2C%20%22relItemId%22%3A123456%2C%20%22categoryId%22%3A12%7D&version=1&signMethod=MD5×tamp=1583271919000&sign=af2d80958e77e17f1d973003b7b7aec2
|
||||
说明:param是json对象,需要排序
|
||||
|
||||
4.API调用
|
||||
确认完成了授权流程后进行API的调用测试,内容详情可见《API调用说明》,也可使用平台提供的API测试工具测试
|
||||
|
||||

|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 292 KiB |
@@ -145,6 +145,7 @@ cp .env.server.example .env
|
||||
至少配置:
|
||||
|
||||
```text
|
||||
APP_DOMAIN
|
||||
CADDY_SITE_ADDR
|
||||
WORKER_SITE_ADDR
|
||||
CLAIM_BASE_URL
|
||||
@@ -152,8 +153,17 @@ POSTGRES_PASSWORD
|
||||
DATABASE_URL
|
||||
ADMIN_SESSION_SECRET
|
||||
ADMIN_DEFAULT_USERS_JSON
|
||||
CORS_ALLOWED_ORIGINS
|
||||
STORAGE_MODE
|
||||
STORAGE_OSS_ENDPOINT
|
||||
STORAGE_OSS_BUCKET
|
||||
STORAGE_OSS_ACCESS_KEY_ID
|
||||
STORAGE_OSS_SECRET_ACCESS_KEY
|
||||
STORAGE_OSS_REGION
|
||||
```
|
||||
|
||||
其中 `APP_DOMAIN`、`CORS_ALLOWED_ORIGINS` 与 `STORAGE_MODE=oss` / `STORAGE_OSS_*` 为生产必需:`docker-compose.yml:95` 的 `STORAGE_MODE` 生产默认即 `oss`,`apps/backend/src/config/runtime-validation.ts:107-114` 在生产环境强制校验 `STORAGE_OSS_*` 凭据并使用显式 CORS 域名白名单;`.env.server.example` 已给出这些变量的模板值,其中带 `change-me` 的占位会被 `deploy/ubuntu-deploy.sh` 的占位值检查拦住,需替换为真实值。
|
||||
|
||||
容器内连接 Compose PostgreSQL 时,主机名用 `postgres`:
|
||||
|
||||
```text
|
||||
|
||||
Reference in New Issue
Block a user