合并本轮全部文档改动(原 5 个 docs 提交压成 1 个)。 口径与索引: - 新增 docs/README.md 文档总索引:三来源口径(91 + 快手 send-code = kuaishou-91)、 「一个订单 = 骨架 + 买家凭证 + 一个执行载体」、成本只改一处、文档状态机 - 过期阶段文档移入 docs/archive/2026-10-08/(保留内容,不删除) 分析报告: - docs/项目全量分析报告-2026-10-17.md:架构、五条业务链路、P0/P1/P2 风险与改进路线 - docs/载体判定与成本中心落地-复核与草案-2026-10-17.md:生产只读克隆量化、上一轮结论复核与 4 处修正 方案文档: - docs/成本中心与三块履约模型.md:三块执行载体、标准商品、成本版本与快照、人工支出登记 - docs/财务系统集成方案.md:复式记账模型、分录表、分期落地 - docs/影子总账说明.md:影子期与转正期的边界、对账与转正标准 SQL 草案(docs/drafts/,未接入迁移): - 载体判定与人工块量化.sql:量化查询、carrier_type 判定与回填、守恒校验 - 成本中心与销售事实-DDL草案.sql:product_catalog / channel_product_mappings / cost_items / cost_versions / cost_snapshots / cost_exceptions / sales_daily_facts - 人工支出登记-DDL草案.sql:manual_fulfillment_cases / manual_expense_records / manual_expense_events 业务口径确认(2026-10-17): - 收入 = 用户实付(orders.total_amount),与快手 paymentFen 数据核验 100% 一致 - 打手报酬 = 支出/成本(最终提现打款),按总额法 - 人工块以「支出可追溯」为先,SLA 监管延后 - 退款/仲裁暂未接入,先预留字段与反向冲正关系 三份草案已在一次性沙箱库按依赖顺序零错误应用(10 张新表、27 个 CHECK 约束)。
21 KiB
order_site 项目全量分析报告(2026-10-17)
状态:现行 代码基线:
main @ 6850bc64(工作区干净) 复核范围:后端、前端、数据库迁移、部署脚本、现行文档 复核方式:源码走查 + 类型检查 + Lint + 格式检查 + 全量测试 + 生产构建 + 2026-10-17 生产库只读聚合(default_transaction_read_only=on) 说明:本次没有做全量数据克隆;历史数据量引用自生产库只读聚合,已在文中标注。
一、结论速览
| # | 结论 | 影响 |
|---|---|---|
| 1 | 系统已是三来源订单履约 + 众包接单 + 寄卖仓库 + 凭证核销平台,不再是单一“快手自动发货”工具 | 文档与新人认知必须先统一来源模型 |
| 2 | 来源只有三个:kuaishou-91、pdd、douyin-mashida;91 回调与快手 send-code 是同一个 kuaishou-91 来源的两条数据通道 |
代码已正确使用 kuaishou-91 适配器;文档曾把二者写成两个来源,现已校正 |
| 3 | 工程纪律处于同类项目上游:严格 TS、99 个渐进迁移、SQL 护栏、回调验签、日志脱敏、生产配置校验、按需部署 | 具备继续承载财务系统的工程基础 |
| 4 | 最大风险集中在四处:资金门禁、Webhook 失败恢复、公开接口凭据、并发锁序 | 这四处会直接影响钱、单、凭证与账号安全 |
| 5 | 财务系统不能直接长在 worker_wallet_ledgers 上;现有表是“业务钱包流水”,不是会计总账 |
需要先建复式记账与对账底座,再接现有资金动作 |
| 6 | 前端与数据库事务层的测试仍偏薄;部分后端测试是源码字符串断言 | 财务系统必须补真实 PostgreSQL 集成测试与金额守恒测试 |
二、来源模型:三个来源,不是一个来源加另一个来源
flowchart LR
A["kuaishou-91<br/>91 回调(订单骨架)"] --> O[("orders / order_items")]
B["kuaishou-91<br/>快手 send-code(补全/兜底)"] --> O
C["pdd"] --> O
D["douyin-mashida"] --> O
O --> M["product-match 引擎<br/>3 个来源适配器"]
M --> W[("work_orders")]
O --> F[("fulfillment_tasks")]
| sourceKey | 数据通道 | 角色 |
|---|---|---|
kuaishou-91 |
91 回调 | 订单骨架权威源 |
kuaishou-91 |
快手 send-code | 商品/SKU/店铺补全源与兜底源 |
pdd |
拼多多同步 | 两段式原始订单暂存后消费 |
douyin-mashida |
抖音码速达推送 | 抖店订单、DYC 券、H5/兑换页 |
代码事实:
- 来源键定义:
apps/backend/src/services/product-match/types.ts:7 - 适配器:
apps/backend/src/services/product-match/adapters/kuaishou-91.ts - 注册表:
apps/backend/src/services/product-match/adapters/registry.ts:69 - 快手 send-code 的权威注释:
apps/backend/src/services/worker-platform/sync-work-orders-from-send-code.ts:5
结论:91 与快手 send-code 必须继续共用一个 kuaishou-91 适配器。 拆开会导致同一店铺键空间、同一订单骨架被重复匹配,直接破坏“一个订单项一个执行载体”的不变式。
三、架构与运行形态
| 层 | 现状 |
|---|---|
| 后端 | Node 22(生产)/ Express 5 / TypeScript 6;routes → services → repositories → pg |
| 前端 | React 19 / Vite 8 / Ant Design 6 / TanStack Query 5 |
| 数据库 | PostgreSQL 16,node-pg-migrate,启动时自动迁移 |
| 网关 | Caddy,统一压缩、静态缓存、反代 |
| 部署 | Docker Compose;ubuntu-deploy.sh 按 Git 改动只重建变化的一端 |
| 运行 | 单实例;限流、调度器、SSE 订阅、列表缓存均在进程内存 |
| 存储 | 生产阿里云 OSS;开发 MinIO |
后端入口与路由挂载:apps/backend/src/app.ts:109。抖音消息推送必须在全局 body parser 之前挂载,以保留原始报文验签:apps/backend/src/app.ts:42。
前端是三个入口共用一套构建产物:
- 后台:
HashRouter - 打手端:独立域名
worker.khhao.com - 买家侧:
/claim、/h5、/redeem独立路径路由
四、当前规模(实测)
| 指标 | 后端 | 前端 |
|---|---|---|
| 源码文件 / 行数 | 371 / 77,811 | 179 / 52,812 |
| CSS | — | 7,448 行 |
| 测试文件 / 行数 | 109 / 16,249 | 26 / 2,221 |
| 测试/源码比 | 约 20.9% | 约 4.2% |
| 接口声明 | 270(后台 197、打手 51) | — |
| 测试结果(本次) | 740 项:737 pass / 3 skipped / 0 fail | 143 pass / 0 fail |
| 生产构建 | 通过 | 通过(主 chunk 524.69 kB,有 >500 kB 警告) |
数据库:99 个迁移、53 张表。
提交历史:1,069 commits,作者高度集中(git shortlog 前两位 824 / 250),bus factor 约等于 1。
五、核心业务链路
5.1 进单与匹配
- 三个来源统一调用
upsertOrderFromSource。 - 按
(provider, platform, platformOrderId)做pg_advisory_xact_lock去重。 resolveOrderItemForFulfillment解析履约载体:affiliate_dash或manual_dispatch。syncWorkerOrdersForSourceOrder用product-match引擎匹配接单模板,生成工单。
关键点:
- 匹配引擎是纯函数,来源差异只在适配器。
- 并列最高分返回
ambiguous,不猜。 - 抖音
sku_code恒为内部 SKU;引流标题只进snapshot.productTitle,永不参与匹配。 - 抖音命中
affiliate_dash后不建工单(apps/backend/src/services/worker-platform/sync-worker-orders-service.ts:65)。
5.2 工单与结算
状态机:
pending_material → unassigned → open → in_progress
→ pending_acceptance → accepted
→ problem → open / cancelled
- 抢单:条件更新
WHERE status='open' AND assigned_worker_id IS NULL。 - 结算:
acceptWorkOrderAndSettle锁工单与份额,释放/延迟解冻押金,抵扣售后欠款,可能升级等级。 - 拼单:母单可长期
open,份额joined → submitted → accepted/cancelled。 - 赠送单:确认加好友后取消/脱离不再退押金。
5.3 寄卖仓库
- 提报:
consignment_submissions,冻结发布押金。 - 入库:
consignment_inventories,审核通过生成INV…。 - 匹配:品类强隔离、三态比对、低配不充高配、unknown 不判精确。
- 履约:
internal_stock内部现货,或worker_deposit指派打手。 - 结算:验收后释放发布押金,补发货款
cost_price_fen || asking_price_fen。 - 撤销:打手押金与卖家押金可
auto / refund / forfeit / keep_frozen,显式覆盖必须填原因。
5.4 凭证与券
| 类型 | 表 | 核销方式 |
|---|---|---|
| 快手行业电子凭证 | kuaishou_industry_vouchers |
快手 OpenAPI |
| 抖音 DYC 券 | redeem_certificates |
coupons.verifyV3 |
两套表、两套状态机,不可混用。发布/指派时按来源分流:publish-voucher-service.ts。
5.5 三块履约与成本(2026-10-17 只读数据增补)
真正决定成本和监管方式的是执行载体,不是订单来源:
| 块 | 载体 | 成本 | 生产数据 |
|---|---|---|---|
| 自动发货 | affiliate_dash 任务 |
固定,按皮肤/SKU | 上游已有 cost_amount,但未落库 |
| 接单平台 | work_orders |
变动,按模板/单价/拼单 | 最近 30 天已验收 62,669 单 |
| 人工客服 | 目前无载体 | 人工工时/按单 | 最近 30 天 44,657 个已支付订单项无任务也无工单 |
关键数据:
- 91 无载体订单 42,391、PDD 2,258、抖音 8;全部
claim_status=active。 manual_dispatch任务全库只有 6 条,说明人工块基本没进任务系统。- 已验收工单的
orders.order_status仍为paid,订单主状态不能代表履约完成。 - 最近 30 天已验收工单:91 报酬/订单金额约 86.0%、PDD 80.0%、抖音 96.5%。口径已确认(2026-10-17):
total_amount即用户付款金额、计收入,打手报酬计支出(总额法);91 的total_amount与快手paymentFen数据核验 100% 一致。详见 载体判定与成本中心落地-复核与草案-2026-10-17.md §九。 - 同一个商品名对应多个
sku_code:「指挥官密钥1个」27 个、「苏尔南钻石100颗」35 个,不能直接按sku_code做销量统计。 - 当前 30 天订单基本是一单一品,但模型必须按订单项设计。
详细设计见 成本中心与三块履约模型.md。
六、工程质量亮点
- 严格类型:
strict、noUncheckedIndexedAccess、exactOptionalPropertyTypes。 - 测试纪律:后端 740 项,覆盖匹配引擎、押金策略、退款、路径判定、日志脱敏、SQL 形状。
- SQL 护栏:
check:sql检查历史迁移不可修改、热点索引存在、禁止COUNT(*) OVER()。 - 生产配置校验:生产强制 session secret 长度、显式 CORS 白名单、存储凭据完整。
- 回调验签:91、快手、affiliate-dash、抖音四条外部链路均有验签;91/affiliate 有时间戳容差。
- 账号安全:后台/打手口令 scrypt + 盐;会话落库 +
session_version;打手设备数限制。 - 日志脱敏:敏感 key、URL 参数、JSON 字段统一脱敏,并有单测。
- 部署与备份:健康检查、优雅关闭、日志轮转、迁移前备份、按需重建。
- 零 TODO/FIXME:源码中没有遗留标记;文档密度高。
七、风险清单(按当前代码复核)
P0 · 资金与一致性
| # | 发现 | 证据 | 影响 |
|---|---|---|---|
| 1 | 发单金额高于用户付款只有前端提醒,后端无强制校验 | 前端 apps/frontend/src/pages/admin/panels/work-order-payment.ts:17;后端发布只校验 >0 apps/backend/src/services/worker-platform/admin-work-order-management-service.ts:228 |
直接调 API 可以发出亏损单;改价、发布、指派三入口没有统一门禁 |
| 2 | 寄卖审核可由请求体改 workerId,并按改后归属结算货款 |
apps/backend/src/services/worker-platform/consignment-admin-service.ts:606;供货方反查优先库存归属 apps/backend/src/repositories/worker-platform/consignment-supplier-repo.ts:67 |
钱可能结给错误的人;还会按手机号自动绑定 |
| 3 | 寄卖审核详情全量返回游戏账号密码 credentialsJson,只需 consignment.view |
apps/backend/src/services/worker-platform/consignment-admin-service.ts:183 |
查看权限即可拿到卖家凭证,权限粒度过粗 |
| 4 | 抢单与指派加锁顺序相反 | 抢单先锁钱包 apps/backend/src/repositories/worker-platform/work-order-grab-repo.ts:25;指派先锁工单 :156,再锁钱包 :171 |
并发下可形成死锁,接口 40P01 / 500 |
| 5 | 自动工单硬编码零押金,模板押金字段不生效 | apps/backend/src/services/worker-platform/sync-worker-orders-service.ts:245 |
自动发单没有押金约束;deposit_threshold_amount 也只读写/展示 |
| 6 | 发布工单时凭证核销失败不回滚;指派却会拦截 | 发布 apps/backend/src/services/worker-platform/admin-work-order-management-service.ts:250;指派 apps/backend/src/services/worker-platform/admin-work-order-assignment-service.ts:75 |
同一条业务规则两套行为;抖音券缺失时工单仍可能进入大厅 |
P1 · 可靠性与公开面
| # | 发现 | 证据 | 影响 |
|---|---|---|---|
| 7 | Webhook 先去重落库、后处理业务;失败后平台重推被永久跳过 | 抖音 apps/backend/src/services/platforms/douyin-mashida/notify-service.ts:95;affiliate-dash apps/backend/src/services/platforms/affiliate-dash/notify-service.ts:70 |
建单/发券/同步任一步失败,事件已写入 webhook_events,重推返回 duplicate,订单可能永久丢失 |
| 8 | 抖音回调同步串行调用上游,单次超时 20 秒 | apps/backend/src/services/platforms/douyin-mashida/source-config-service.ts:136 |
回调响应可能超过平台超时,与 #7 叠加后无法恢复 |
| 9 | /collect/*、/redeem/* 只凭订单号,无第二因子 |
apps/backend/src/routes/collect.ts:22、:32;apps/backend/src/routes/redeem.ts:24、:34 |
可查询/写入待补全订单资料;知道订单号即可触发买家券核销 |
| 10 | /collect/upload 可匿名上传任意场景文件到 OSS |
apps/backend/src/routes/collect.ts:42、:49;场景名只做字符串清洗 apps/backend/src/services/file-storage/file-storage-service.ts:360 |
不校验订单存在,换 orderNo 即可绕过限流桶,存在存储滥用风险 |
| 11 | /files/object 无鉴权;claim token 默认永久有效 |
apps/backend/src/routes/files.ts:12;默认 apps/backend/src/config/defaults.ts:65,生产示例同样为 0 .env.server.example:27 |
链接或对象 key 泄露即长期可访问 |
| 12 | 无 CSP/HSTS/X-Frame-Options;后台 token 存 localStorage | deploy/caddy/Caddyfile 只有缓存头;apps/frontend/src/utils/admin-auth.ts:1、:101 |
任一 XSS 即可接管后台会话 |
| 13 | 先到先匹配,后到的快手补全数据不会纠正既有工单 | 已有工单直接 already_exists 跳过 apps/backend/src/services/worker-platform/sync-worker-orders-service.ts:97 |
价格/模板可能停留在首次粗匹配结果 |
| 14 | 单实例耦合:限流桶、调度器、SSE、列表缓存均在进程内 | apps/backend/src/middleware/rate-limit.ts:26;services/realtime/realtime-event-service.ts |
直接扩容会出现重复定时任务、限流失效、缓存不一致 |
P2 · 可维护性与运维
| # | 发现 | 证据 |
|---|---|---|
| 15 | 巨型文件:WorkOrdersPanel.tsx 3,686 行、AdminTaskDetailPage.tsx 2,015 行、work-order-query-repo.ts 1,499 行、consignment-repo.ts 1,478 行 |
实测 |
| 16 | 前端无组件渲染测试、无 E2E;部分后端测试是源码字符串断言 | 测试目录与用例实现 |
| 17 | 无 CI;质量门禁只靠本机 pre-push,可被 SKIP_CHECKS=1 绕过 |
.githooks/pre-push |
| 18 | dev 分支已完全落后 main,实际是单分支开发 |
git log main..dev 为空 |
| 19 | 迁移启动时自动执行且无 down;多副本会竞争迁移 | apps/backend/src/startup/bootstrap.ts:32 |
| 20 | uncaughtException 只记录不退出 |
apps/backend/src/index.ts:57 |
| 21 | 本地 Node 24,生产镜像 Node 22;前端主 chunk 524.69 kB 警告 | 构建输出、deploy/docker/backend.Dockerfile:11 |
| 22 | 备份会把 apps/backend/data 配置打包上传 OSS,可能含 PDD RSA 私钥/平台 Cookie |
deploy/backup-db.sh:119 |
三块履约与成本增补(2026-10-17)
| # | 发现 | 证据 | 影响 |
|---|---|---|---|
| C1 | 人工块没有载体、没有 case、没有完成状态 | 最近 30 天 44,657 个已支付订单项无任务也无工单;manual_dispatch 仅 6 条;已验收工单的订单仍 order_status=paid |
人工履约不可监管、不可计量、不可对账 |
| C2 | 成本数据散落且无版本/快照 | 自动发货 cost_amount 只映射不落库;工单价格在 work_product_rules;人工无成本表 |
改价无法追溯,历史毛利不可重算,三块成本口径不一致 |
| C3 | 订单金额口径不统一 | 91 totalAmount=maxAmount(platforms/ninetyone/order-service.ts:48);PDD order_amount;抖音 pay_amount |
直接汇总 orders.total_amount 会把不同财务口径相加 |
| C4 | SKU 与数量口径不稳定 | 同一商品名对应 27/35 个 sku_code;order_items.quantity 对「5个」类商品不可靠 |
店铺/日期/皮肤销量统计会漏计、重计或错并 |
| C5 | 无订单项级金额字段 | order_items 仅有 sku_code/sku_name/quantity/spec_json/item_snapshot_json |
多商品订单无法直接分摊收入,需要快照/分摊规则 |
八、文档现状与本次整理
本次已完成:
- 新增 docs/README.md:文档唯一入口、状态约定、三来源口径。
- 新增 docs/archive/README.md:历史文档说明。
- 将 6 份过期阶段性文档移入
docs/archive/2026-10-08/,不删除内容,保留追溯。 - 统一以下文档的来源口径:
README.mddocs/接单模板匹配引擎重构设计.mddocs/抖音-码速达/00-总览.mddocs/抖音-码速达/10-三层分流设计.mddocs/抖音-码速达/12-按SKU分类改造总设计.mddocs/履约配置/多发货平台与电子凭证整体链路.mddocs/91卡券/5.当前项目对接配置.mddocs/91卡券/6.开发设计.md
- 校正“设计稿/未实施”状态:
- 匹配引擎:P0 + P1 已实施,P2/P3 待办。
- 抖音三层分流:抖音侧已实施。
- 校正过期测试数:抖音总览中的 636/115 改为 2026-10-17 复核的 740/143。
- 2026-10-17 增补:新增 成本中心与三块履约模型.md,把自动发货/接单平台/人工客服三块、统一成本中心、销售事实与人工 case 纳入现行设计;本报告同步补入生产只读数据与 C1~C5 风险。
仍保留为“设计/待办”的内容:
work_product_match_entries新表尚未落地(只在设计中),当前仍靠compile.ts编译旧模板/绑定。- 匹配触发点收敛、寄卖建议价/大厅改价统一走
pricing.ts尚未完成。 - 抖音“待人工队列”仍未做。
product_catalog/channel_product_mappings/cost_items/cost_versions/cost_snapshots/sales_daily_facts/manual_fulfillment_cases均未落地。
九、财务系统集成结论(摘要)
现有财务能力是业务钱包 + 流水,不是会计总账:
worker_wallets:可用余额、冻结押金、累计入账、累计结算。worker_wallet_ledgers:冻结/释放/扣减/结算等流水。worker_finance_requests:充值/提现申请。- 售后欠款、寄卖发布押金、工单报酬、货款结算分散在多个 repo。
缺口:
- 没有复式记账(借贷不平无法发现)。
- 没有会计期间/月结/关账。
- 没有平台侧对账(91、快手、PDD、抖音、affiliate-dash)。
- 没有订单级收入/成本/毛利。
- 余额是可变字段,缺少“由流水重算”的校验。
- 资金事件缺少统一的
business_event_id幂等键。
建议:先建影子总账 + 对账,不要一开始替换钱包。成本金额与商品维度单独由成本中心负责,人工块由人工 case 负责;三者关系见 成本中心与三块履约模型.md,总账细节见 财务系统集成方案.md 与 影子总账说明.md。
十、建议路线
立即(1–3 天)
- 后端统一“发单金额 ≤ 用户付款”校验,改价/发布/指派共用。
- 修复 Webhook 去重时序,改为
processing / succeeded / failed或失败删除。 - 统一抢单/指派锁序,或对 40P01 做有限重试。
- 寄卖审核禁止直接改
worker_id;确需改派走独立接口 + 原因 + 复核。
短期(1–2 周)
- 发布与凭证核销统一为“失败即不发布”,或明确异步补偿。
/collect、/redeem增加一次性 token/二次校验;上传前校验订单存在。- 生产强制
CLAIM_TOKEN_TTL_HOURS > 0;私有文件短期签名;补安全响应头。 - 先落商品主数据 + 销售事实:
product_catalog、channel_product_mappings、sales_daily_facts,解决店铺/日期/皮肤销量与数量归一。 - 再落成本中心影子期:
cost_items / cost_versions / cost_snapshots,自动发货接上游固定成本,工单接现有规则金额,人工接按单/按小时成本。
中期(1 个月)
- 人工 case:载体判定、无载体自动建 case、分配/SLA/证据/主管复核、人工成本快照。
- 财务影子总账:
finance_journal_entries / lines+ 业务事件映射。 - CI + PostgreSQL 集成测试;补金额守恒、并发抢单、Webhook 失败恢复测试。
- 财务对账:平台账单/支付流水/钱包流水三方核对。
- 订单级损益:收入、履约成本、打手报酬、人工成本、平台费、退款。
- 拆分巨型文件;补前端组件测试与 1–2 条 E2E。
十一、一句话总结
代码与工程纪律足以支撑下一阶段的财务系统;但财务系统必须建在“商品主数据 + 成本中心 + 不可变复式分录 + 业务事件幂等 + 对账”之上,而不是继续给 worker_wallets 加字段。 先修资金门禁、Webhook 恢复、锁序与公开接口;再把人工块纳入 case 监管,最后接财务总账,风险最低。