# 抖音码速达 · 总览(进度 / 代码 / 配置 / 上线) > 更新:2026-10-15 **状态:最小接入已跑通,且按 SKU 的三层分流已上线**(真实网关签名验证 / 真实推送订阅 / 真实 orderDetail 建单 / 真实 coupons.syncV2 同步成功 / 工单发布顺带核销抖音券),两端自检全绿 > 本文是这套内容的**入口**,具体细节在 04~12,最新增量见 [§九点六](#九点六2026-10-14--10-15-增量)。 --- ## 一、一句话现状 按 [05-最小接入方案分析.md](05-最小接入方案分析.md) 落地的**最小闭环六步全部完成**;2026-10-17 复核:后端 740 项测试(737 pass / 3 skipped)、前端 143 项测试全绿,迁移 `094` / `095` 已在本地临时库实测通过。 **没做的**:兑换后的实际交付物(卡密/充值)、`order.searchList` 对账、兑换页二次校验(手机后 4 位)。 > ✅ **原「按 SKU 的三层分类」已上线**(2026-10-14):抖音与 `kuaishou-91` 来源(91 回调 + 快手 send-code)同构(命中映射 → `affiliate_dash` 自动发货;未命中 → 接单平台建工单;都不中 → 无载体=待人工),载体唯一有硬守卫。总设计 [12-按SKU分类改造总设计.md](12-按SKU分类改造总设计.md)、分层实测 [10-三层分流设计.md](10-三层分流设计.md)、运营动作 [11-接单模板待补清单.md](11-接单模板待补清单.md)。 **只走真实公网联调**(离线模拟已按要求删除): 1. `ngrok http 80` 起隧道拿公网地址 2. `douyin-mashida:set-public-url` 一键把 `certLink` 切过去 + 打印平台侧待配项 + 自检隧道 3. `douyin-mashida:subscribe` **订阅推送**(`message.config`)—— 不做这步平台一条消息都不会推 4. `douyin-mashida:preflight` 前置自检(配置就绪度 / **真实网关连通性** / 公网回调可达 / 域名一致性) 详见 [08-公网联调.md](08-公网联调.md)。 > ⚠️ localhost 永远收不到平台推送、也发不出买家能打开的链接 —— **必须挂隧道**。 --- ## 二、闭环与落点 ```text ① trade_TradePaid 推送 POST /api/v1/open/douyin-mashida/message └─ 验签(MD5 小写)→ webhook_events 幂等 → order.orderDetail 拉详情 └─ upsertOrderFromSource 建单(provider=douyin_mashida / platform=doudian) └─ 每个订单项生成 DYC 券码 → coupons.syncV2 交给平台 ② 买家打开 https://<域名>/redeem(路径式,不带 #) 输订单号 → POST /api/v1/redeem/lookup(状态分流) ③ 确认兑换 → POST /api/v1/redeem/confirm └─ coupons.verifyV3 上报核销 └─ order.message 把「去使用链接」发给买家(best-effort) ④ refund_RefundSuccess / trade_TradeCanceled 推送 └─ 该订单未核销的券码作废 ``` | 环节 | 入口 | 核心实现 | | --- | --- | --- | | 消息接收 | `routes/douyin-mashida.ts`(挂在全局 body parser **之前**) | `notify-service.ts` | | 签名 | — | `sign.ts`(出网 + 入站两套,含 golden 向量单测) | | 拉详情/发券/核销/通知 | — | `gateway-client.ts` + `http-client.ts` | | 建单 | 复用 `services/order/order-service.ts:upsertOrderFromSource` | `order-mapper.ts` | | 券码 | 迁移 `094` | `redeem-certificate-repo.ts` + `certificate-service.ts` | | 兑换 | `routes/redeem.ts` | `redeem-service.ts` | | 后台配置 | `routes/admin/platform-config/douyin-mashida.ts` | `douyin-mashida-service.ts`(凭据脱敏)+ 前端面板 `DouyinMashidaPanel.tsx` | | 本地联调 | `scripts/douyin-mashida-gateway-stub.ts` / `douyin-mashida-push.ts` | 网关桩 + 推送模拟,见文档 08 | | 公网联调 | `set-public-url` + `preflight` + `subscribe` 三个脚本 + ngrok | 一键切地址 / 自检隧道 / **推送订阅**,见文档 08 | | 前端页面 | `pages/redeem/RedeemPage.tsx` | `App.tsx` 的独立 `BrowserRouter` 分支 | --- ## 三、代码清单(23 个文件 / 3107 行) ### 新增 · 后端 | 文件 | 行 | 作用 | | --- | ---: | --- | | `services/platforms/douyin-mashida/sign.ts` | 130 | 两套签名:出网 `md5(secret+Σ(名+值)+secret)`、推送 `md5(secret+data+pub_time+topic+secret)` | | `services/platforms/douyin-mashida/source-config-service.ts` | 154 | 配置读写 + 默认值 + `assertDouyinMashidaConfig` | | `services/platforms/douyin-mashida/http-client.ts` | 165 | 网关 POST、超时、报文日志、`code !== 10000` 抛 502 | | `services/platforms/douyin-mashida/gateway-client.ts` | 109 | 4 个出网方法:`orderDetail` / `syncV2` / `verifyV3` / `order.message`(+`cancelVerify`) | | `services/platforms/douyin-mashida/order-mapper.ts` | 214 | 消息+详情 → `SourceOrderEvent`;抖店状态映射;子单号入 item 快照 | | `services/platforms/douyin-mashida/notify-service.ts` | 384 | 验签 → 幂等 → 分派(付款建单发券 / 退款作废) | | `services/platforms/douyin-mashida/*.test.ts` | 407 | 3 个测试文件 / 24 用例 | | `services/redeem/certificate-service.ts` | 312 | 发券、syncV2、核销上报、核销后通知、作废 | | `services/redeem/redeem-service.ts` | 269 | 兑换状态机(7 态)+ 券码掩码 + 订单号校验 | | `services/redeem/redeem-service.test.ts` | 69 | 6 用例 | | `repositories/redeem-certificate-repo.ts` | 233 | DYC 券码生成(Crockford Base32)、发券幂等、状态流转 | | `routes/douyin-mashida.ts` | 67 | 消息入口(`express.text` 收原文,兼容 `data=` 表单) | | `routes/redeem.ts` | 43 | `/lookup` + `/confirm`,按 `orderNo`+IP 限流 | | `routes/admin/platform-config/douyin-mashida.ts` | 49 | 后台 GET/POST + 审计 | | `services/admin/platform-config/douyin-mashida-service.ts` | 75 | 配置读写,secret 出参置空 + 掩码,掩码回写不覆盖 | | `db/migrations/094_redeem_certificates.sql` | 57 | `redeem_certificates` 表 | | `db/migrations/095_redeem_certificates_platform_cert.sql` | 30 | 记录**平台侧实际券码**(平台会改名,核销必须用它) | | `scripts/douyin-mashida-gateway-stub.ts` | 320 | **本地网关桩**:假装码速达,校验签名并回 `code=10000` | | `scripts/douyin-mashida-push.ts` | 300 | **推送模拟器**:造签名消息打本地后端 | | `scripts/douyin-mashida-set-public-url.ts` | 290 | **公网联调切换**:改 `certLink` 为隧道地址 + 打印平台侧配置 + 自检隧道 | | `scripts/douyin-mashida-preflight.ts` | 330 | **前置自检**:配置就绪度 + 真实网关连通性(可选验签名)+ 公网回调 + 域名一致性 | | `scripts/douyin-mashida-subscribe.ts` | 250 | **推送订阅**:调 `message.config` 配置推送地址与 topics + `messagetest` 自测 | ### 新增 · 前端 | 文件 | 行 | 作用 | | --- | ---: | --- | | `pages/redeem/RedeemPage.tsx` | 166 | 兑换页(同 CollectPage 骨架,按 status 渲染) | | `utils/redeem.ts` | 82 | 纯函数:`isRedeemPath` / 金额时间格式化 / 状态→文案色调 | | `types/redeem.ts` | 39 | 与后端 `RedeemOrderView` 对齐 | | `services/redeem.ts` | 16 | `lookupRedeemOrder` / `confirmRedeemOrder` | | `tests/redeem.test.ts` | 67 | 7 用例 | | `pages/admin/platform/DouyinMashidaPanel.tsx` | 346 | 后台「抖音码速达」配置面板(进度/脱敏密钥/本地桩提示) | | `components/admin/SecretFieldControl.tsx` | 158 | 脱敏密钥输入组件(从 `AdminPlatformShopsPage` 抽出,两侧共用) | | `utils/douyin-mashida.ts` | 64 | 纯函数:本地地址判定 / 推送地址拼接 / 缺失项提示 | | `types` + `services/admin/platform-config/douyin-mashida.ts` | 49 | 配置读写服务 | | `tests/douyin-mashida.test.ts` | 86 | 6 用例 | ### 修改 | 文件 | 改了什么 | | --- | --- | | `apps/backend/src/app.ts` | 挂载 `/api/v1/open/douyin-mashida`(**必须在 `express.json` 之前**)+ `/api/v1/redeem` | | `apps/backend/src/config/app-config-keys.ts` | 新增 key `douyin_mashida` | | `apps/backend/src/routes/admin/platform-config.ts` | 注册后台配置路由(自动继承 admin 守卫) | | `apps/backend/package.json` | 新增 `douyin-mashida:set-public-url` / `douyin-mashida:preflight` / `douyin-mashida:subscribe` | | `apps/frontend/vite.config.ts` | dev server `allowedHosts` 放行常见隧道域名;**接上了此前从未被读取的 `VITE_ALLOWED_HOSTS`**(`.env`/compose 早就配了,白名单却是硬编码的) | | `apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx` | 新增「抖音码速达」Tab;本地 `SecretFieldControl` 改为共享组件 import | | `apps/frontend/src/App.tsx` | 新增 `isRedeemPath` 分支(早于 `isWorkerSite()`)+ `RedeemApplication` | | `apps/frontend/src/styles/client.css` | `.redeem-page` / `.redeem-card` / `.redeem-order-input` | --- ## 四、配置清单 存在 `app_config_entries.douyin_mashida`(JSONB)。后台接口:`POST /api/v1/admin/platform-config/douyin-mashida` | 字段 | 默认 | 说明 | | --- | --- | --- | | `enabled` | `true` | 关掉后推送接口返回 409 | | `baseUrl` | `https://dy-openplatform-api.wanmahui.com` | 网关地址 | | `uid` | 空 | 抖店商户号(必填) | | `secret` | 空 | 用户秘钥(必填);**出参永不明文回显** | | `certLink` | 空 | 卡券里的兑换链接,固定 `https://<域名>/redeem`(必填) | | `messageType` | `2` | `order.message` 的类型:1-订单状态 / 2-订单发货 / 4-图片 / 5-视频 | | `afterVerifyLink` | 空 | 兑换成功后发给买家的「去使用链接」 | | `afterVerifyMessage` | `您的订单已兑换成功` | 通知正文 | | `timeoutMs` | `10000` | 出网超时 | | `handledTopics` | `trade_TradePaid` / `trade_TradeCanceled` / `refund_RefundSuccess` | 白名单外的 topic 只留痕 | `GET` 会返回 `ready` 自查位(`enabled + uid + secret + certLink` 齐全才为 `true`)。 --- ## 五、文档索引 | 文档 | 内容 | 状态 | | --- | --- | --- | | [README.md](README.md) | 抓取说明 + 目录 | ✅ 已入库 | | [01-计费规则.md](01-计费规则.md) | 平台计费 | ✅ | | [02-接口调用规范与签名.md](02-接口调用规范与签名.md) | 出网签名规则 | ✅ | | [03-消息推送规范与签名.md](03-消息推送规范与签名.md) | 推送与验签 | ✅ | | `接口API/`、`消息推送/`、`data/` | 全量抓取快照(46 个文件 / 488K) | ✅ | | [04-结合本项目的深度分析.md](04-结合本项目的深度分析.md) | 深读平台后结合仓库的分析 | ✅ 已入库(历史分析) | | [05-最小接入方案分析.md](05-最小接入方案分析.md) | **实施方案**(决策与取舍都在这) | ✅ 已入库(历史方案,已实施) | | [06-待确认问题清单.md](06-待确认问题清单.md) | 问题清单(Q1~Q4 已答,其余边做边测) | ✅ 已入库(历史清单) | | [07-最小接入实现说明.md](07-最小接入实现说明.md) | 实现说明 + 联调步骤 + 坑 | ✅ 已入库(现行) | | [08-公网联调.md](08-公网联调.md) | **真实公网联调**(ngrok + 一键切地址 + 前置自检 + checklist + 坑) | ✅ 已入库(现行) | | 本文 `00-总览.md` | 进度索引 | ✅ 已入库(现行) | --- ## 六、落地提交记录 | commit | 内容 | | --- | --- | | `5a73fb74` | 抖音券核销后台界面(列表/核销/冲正/同步/对账/作废) | | `319e0822` | 领取消息只发给有自动发货领取单元的单,人工单不再假发货 | | `4b081d88` | 抖音按 SKU 三层分类接入,对齐 `kuaishou-91` | | `10464fea` | 抽离接单模板匹配引擎(来源适配器/引擎/价格收口) | | `6850bc64` | 工单发布并核销支持抖音券(按来源分流) | > 本文档与 04~12 均已入库;历史分析文档按状态标注,不再使用“仅本地/不入库”的口径。 --- ## 七、上线步骤 1. **发版**:`bash deploy/ubuntu-deploy.sh`(启动时自动 up 迁移 `094`,建 `redeem_certificates` 表) 2. **配平台**:后台 `POST /api/v1/admin/platform-config/douyin-mashida` 填 `uid` / `secret` / `certLink=https://<域名>/redeem`;确认 `GET` 返回 `ready: true` 3. **配推送**:平台侧把消息地址设为 `https://<域名>/api/v1/open/douyin-mashida/message`,订阅 `trade_TradePaid`(建议加 `trade_TradeCanceled`、`refund_RefundSuccess`) **Caddy 不用改**:已有 `try_files {path} /index.html`,`/redeem` 直接可用(这也是当初选路径式而非 `#` 的原因)。 --- ## 八、缺口与待联调 ### 没做的(按优先级) | # | 缺口 | 影响 | 备注 | | --- | --- | --- | --- | | ~~1~~ | ~~后台前端配置面板~~ | **已完成**:`/admin/platform-shops?tab=douyinMashida` | — | | ~~2~~ | ~~履约任务分支~~ | **已完成且已修正**:命中映射 → `affiliate_dash` 自动发货;未命中**不再写死** preferred、**不再兜底 `manual_dispatch`**(否则会出现「人工任务 + 工单」双载体) | `product-resolution-service.ts:286-350`(commit `4b081d88`) | | ~~2b~~ | ~~**接单平台发单层(层②)**~~ | **已完成**:抖音按内部 SKU 三层分类(`snapshot.classification`),发单层有「载体唯一」硬守卫(命中 `affiliate_dash` 即跳过建工单);生产库接单模板 `platform` 已全部清空(354 条),抖音单从此可命中模板 | [10-三层分流设计.md](10-三层分流设计.md) / [11-接单模板待补清单.md](11-接单模板待补清单.md) | | ~~2c~~ | ~~工单「发布并核销」只核销快手电子凭证~~ | **已完成**(2026-10-15):按来源分流,抖音工单核销 DYC 券(`redeem_certificates`),见 [§九点六](#九点六2026-10-14--10-15-增量) | `worker-platform/publish-douyin-voucher-service.ts` | | 3 | 兑换后的实际交付物 | 当前只「上报核销 + 展示掩码券码 + 发链接」 | 等业务定(B2) | | 4 | `order.searchList` 对账任务 | 消息丢了就漏单 | 二期 | | ~~5~~ | ~~`cancelVerify` 后台按钮~~ | **已完成**:后台「抖音券核销」含核销/冲正/重新同步/对账/重发/作废(每个写操作带审计) | `routes/admin/douyin-mashida.ts`(commit `5a73fb74`) | | 6 | 兑换页二次校验(手机后 4 位) | 知道订单号即可兑换 | 看业务是否接受(B1) | | 7 | `message.config` 自动订阅 | 需平台侧手配 | 可选做「一键订阅」 | | 8 | 隧道域名变化需手工同步平台侧 | `set-public-url` 会打印待配项;ngrok 免费版建议保留静态域名 | 固定域名后基本不是问题 | | 9 | ngrok 免费版对浏览器插警示页 | 买家点 `cert_link` 会先过一页英文提示 | 正式环境用正式域名;联调期间点一下即可 | | — | 工单里的抖音券前端不渲染 | **有意为之**(2026-10-15 用户口径):打手/客服**不需要看到券码**,快照只落 `material_json` 供追溯 | `material_json.douyinMashidaVoucher` | ### 联调要实测的点 1. `message_type=2` 发出去**会不会改变抖店订单的发货状态**(我们只想发链接) 2. 平台期望的**成功响应体**(当前 `{"code":0,"message":"success"}`) 3. 平台**重推策略**(失败我们返回 4xx,是否真重推) 4. `coupons.syncV2` 的 `order_id` 认**子订单号还是父订单号**(代码按子单优先、父单兜底,联调时看日志) 5. 同步成功后**买家在哪看到卡券**(决定用户能不能走到 `/redeem`) 6. `verifyV3` 重复核销的返回、能否 `cancelVerify` 冲正 7. `order.orderDetail` 的 `p_id`/`order_id` 实际是字符串还是数字 --- ## 九、两个必须记住的坑 ### 1)19 位订单号会被 `JSON.parse` 抹精度 ★ 抖店订单号超过 JS 安全整数,`4837328765348567001` 经 `JSON.parse` 会变成 `...000`。 处理:`notify-service.ts:parseMessageData()` 解析后用正则从**原始 JSON 文本**把长数字原样抓回(字符串), `applyRawBigIntegerOverrides` 覆盖 `p_id`/`order_id`/`s_id`/`s_ids`/`aftersale_id` 等。单测同时证明了"不修会错"和"修完精确"。 ### 2)验签必须用 body 原文 → 路由挂在全局 parser 之前 `app.ts:42` 把 `/api/v1/open/douyin-mashida` 挂在 `express.json()` / `express.urlencoded()` **之前**, 路由内部用 `express.text({ type: () => true })` 自己收原文(并兼容 `data=` 的表单形态)。 任何全局 body parser 都会重排/转义 JSON,签名原串就对不上。 --- ## 九点五、真实平台实测结论(2026-10-09) | 验证项 | 结果 | | --- | --- | | 出网签名(用真实 uid/secret 调 `order.orderDetail`) | ✅ `code=10000`,签名规则与文档一致 | | 推送订阅 `message.config` | ✅ `code=10000 成功`(配置前平台一条都不推 —— 这是"下单没进 dev"的根因) | | 进站推送(真密钥签名 → 公网地址 → 验签 → 建单) | ✅ `action=paid-order-upserted` | | 19 位订单号精度 | ✅ `6930310175251856483` 精确保留 | | `coupons.syncV2` 同步到真平台 | ✅ `sync_status=success` | | 兑换页状态 | ✅ `redeemable`,展示真实商品名与真实支付时间 | | **平台自动推送**(订阅后真单付款) | ✅ 6 秒到达,来源 IP `118.31.56.230`,端到端约 7 秒完成建单+发券+同步 | | `pay_time` 字符串形态 | ⚠️ 实测发现并已修复(见 07 §4.8) | | **核销(verifyV3)** | ✅ 真单核销成功,平台侧 `status=3 已使用 / verify_num=1` | | **平台会改写券码** | ⚠️ 3 笔里 2 笔被平台改名 → 已修(核销改用平台侧券码 + 失败自愈,迁移 095) | | `order.message` 发消息 | ✅ 兑换成功后已调用,无错误日志(成功日志默认被 integration 阈值 warn 挡住) | | `messagetest` 自测入口 | ⚠️ 始终 `20006 data参数内容解析异常`,未探明其 data 要求;不影响真实链路 | ## 九点六、2026-10-14 / 10-15 增量 ### 1)按 SKU 三层分流上线(`4b081d88`) 抖音与 `kuaishou-91` 同构,**只动抖音,kuaishou-91 不变**: | 层 | 判据 | 载体 | | --- | --- | --- | | ① 自动发货 | 内部 SKU 命中 `AFFILIATE_DASH_SKU_MAPPING`(常量表 + 后台 `skuMapping` 增量) | `affiliate_dash` 履约任务(买家输 UID → 扫码绑定 → 自动核销) | | ② 接单平台 | 命中接单模板(`work_product_rules`,按**内部 SKU**) | `work_orders` 工单 | | ③ 人工 | 两者都不中 | **无载体**(无任务无工单)=待人工 | - `sku_code` 恒为**内部 SKU**(不再被英文 sku 覆写);引流长标题只进 `snapshot.productTitle`,**永不参与匹配**。 - 未命中映射时**不设 preferred、强制覆盖 `unmatchedExecutorKey:''`**——否则会出现「人工任务 + 工单」双载体。 - 载体唯一守卫:`sync-worker-orders-service.ts:65` 命中 `classification.channel==='affiliate_dash'` 的订单项**不建工单**(reason `affiliate_dash_carrier_taken`)。 - 券 → 工单快照:`material_json.douyinMashidaVoucher`(对齐快手 `kuaishouIndustryVoucher`)。**前端有意不渲染**(打手/客服不需要看到券码)。 ### 2)接单模板匹配引擎重构(`10464fea`,P0+P1) 按 [接单模板匹配引擎重构设计.md](../接单模板匹配引擎重构设计.md) 抽出 `services/product-match/`(适配器按**来源**划分:`kuaishou-91` / `pdd` / `douyin-mashida`,其中 `kuaishou-91` 同时覆盖 91 回调与快手 send-code 两条数据通道)。生产回放 6000 条订单项:`kuaishou-91`/PDD 决策 **0 差异**,抖音 5 条全为修正。旧 `work-product-match-mappers.ts` 变为门面,签名不变。 ### 3)工单「发布并核销」支持抖音券(本次) **问题**:抖音单走到接单平台后有工单,但「发布并核销」只查 `kuaishou_industry_vouchers`(抖音订单在那张表里永远是空的)→ **抖音券从未被核销**。三个入口(发布 / 指派 / 寄售履约)共用同一个函数,全部受影响。 **改法**:在唯一入口按**来源**分流,核销本身**不重复实现**。 ```text publishAdminWorkOrder / assignAdminWorkOrder / 寄售履约 └─ consumeIndustryVouchersBeforeWorkOrderPublish (publish-voucher-service.ts) ├─ 抖音(orders.provider === 'douyin_mashida') │ └─ verifyDouyinMashidaCertificateForWorkOrderPublish (publish-douyin-voucher-service.ts) │ ├─ 按 platform_order_id(= redeem_certificates.order_id,子单优先/父单兜底)查券 │ ├─ 委托 verifyCertificate(redeem/certificate-service.ts)→ coupons.verifyV3 │ │ (幂等 / invalid 拒核 / 未同步拒核 / 平台券码优先 / 失败对账自愈) │ └─ 券快照 merge 进工单 material_json.douyinMashidaVoucher + 事件 publish_voucher_consumed └─ 其它来源(kuaishou-91) └─ 原快手电子凭证路径(listKuaishouIndustryVouchersByOid + consumeKuaishouIndustryVoucher) ``` | 文件 | 作用 | | --- | --- | | `worker-platform/publish-douyin-voucher-service.ts` | 抖音券核销分支(新增) | | `worker-platform/publish-voucher-service.ts` | 顶部按来源分流(`isDouyinMashidaWorkOrder`,查 `orders.provider`) | | `utils/voucher-consume.ts` | 前端失败提示改为「请到「抖音券核销」或快手电子凭证后台人工核销后再发货」 | 口径要点: 1. **查不到券 → 抛 409**(`work_order_publish_douyin_certificate_missing`)而不是静默成功——抖音口径是「任何订单都发券」,查不到说明发券环节没走完,需要运营去「抖音券核销」重发。 2. **券状态落库由 `verifyCertificate` 负责**,因此后台「抖音券核销」界面的状态、重复发布的幂等行为与买家兑换页完全一致(不会有「本地说没核销」的分叉)。 3. **回填工单是 best-effort**:失败只留 warn,不影响发布结果。 4. 单测用 `setOrderProviderResolverForTest` / `setDouyinVoucherDepsForTest` 注入(生产恒为 undefined,直连真实仓储与网关)。 **验证**(2026-10-15):后端 `npm run check` ✅ 740 例 0 失败(本次新增 13 例:核销成功回填/保留 material 其它字段/无券报错/真源报错转失败结果且不回填/已核销幂等/指派路径/无平台单号跳过 + 来源分流回归 3 例);前端 `npm run check` ✅ 143 例 0 失败。 ### 4)其它 - `584b6f52`:问题单处置存在活跃拼单份额时,禁止取消 / 退回大厅 / 重开。 --- ## 十、自检结果(2026-10-15 当时) | 项 | 结果 | | --- | --- | | 后端 `npm run typecheck` | EXIT=0 | | 新增单测(douyin-mashida + redeem) | **30 / 30 通过** | | 后端 `npm run check`(format+sql+lint+typecheck+test+build) | EXIT=0,636 测试 / 633 通过 / 0 失败 | | 前端 `npm run check` | EXIT=0,115 / 115 通过 | | 迁移 `094` | 在本地 dev Postgres 临时库实测通过;重复 `(provider, order_id)` 插入返回 `INSERT 0 0`、重复券码触发唯一键 |