1. docs/项目分析报告-发单系统与寄售仓库.md:项目全景(实测文件/行数、测试结果、迁移规模)、后台发单链路与工单状态机、寄售仓库数据模型与匹配规则、按优先级排序的风险总表与改进路线; 2. docs/寄卖子系统技术分析报告.md:寄卖子系统逐项分析,含端到端流程、077-082 数据模型、核心服务清单、库存与分配状态机、API 清单、风险与测试缺口; 3. 两份报告中的资金与安全类结论均已对照源码复核,标注「待确认」的属需要业务口径拍板、而非代码缺陷的项。
27 KiB
order_site 项目分析报告
重点:后台发单系统(进单 → 工单 → 抢单大厅 → 履约 → 验收 → 结算)与 寄售仓库 / 寄卖 子系统。 分析基线:
ef491660(含9298b31b寄卖白名单总开关),工作区干净,949 commits。 说明:本报告中的资金/安全类关键结论均已二次复核源码;标注「待确认」的是需要业务口径拍板、而非代码缺陷的项。 配套明细:寄卖子系统的逐行分析见 寄卖子系统技术分析报告.md。
一、结论速览
| # | 结论 | 影响 | 证据 |
|---|---|---|---|
| 1 | 「发单金额高于用户付款强制确认」只是前端弹窗,后端无任何校验 | 直调 API 可发出亏损单 | apps/frontend/src/pages/admin/panels/work-order-payment.ts:22;apps/backend/src/services/worker-platform/admin-work-order-management-service.ts:60 |
| 2 | 审核入库接口可由请求体改 workerId,货款按改后的归属人结算 |
资金错付风险 | apps/backend/src/services/worker-platform/consignment-admin-service.ts:583;apps/backend/src/repositories/worker-platform/consignment-supplier-repo.ts:67 |
| 3 | 抢单与后台指派的加锁顺序相反(钱包→工单 vs 工单→钱包) | 并发下 40P01 死锁 | 同文件两个函数:work-order-grab-repo.ts:24 → :58(grab)vs :154 → :169(assign) |
| 4 | 寄卖「内部现货履约」已收敛,但后端 assign-worker 接口仍可达 |
可绕过收敛,复现"双重扣押金 + 打手重填资料" | apps/backend/src/routes/admin/worker-platform/consignment.ts:263 |
| 5 | claim token 默认永久有效;/collect/* 与 /files/object 无鉴权 |
买家 UID / 私有文件对外暴露面 | apps/backend/src/config/defaults.ts:64;apps/backend/src/app.ts:106,108 |
| 6 | 模板配置的押金不会带进自动工单:建单硬编码 0 | 自动发单实际零押金 | apps/backend/src/services/worker-platform/sync-worker-orders-service.ts:196 |
| 7 | 迁移在启动时自动执行且无 down 脚本 | 无快速回滚路径,多副本有竞态 | apps/backend/src/startup/bootstrap.ts:32 |
| 8 | 后端测试纪律好(零 SQL 拼接 + check:sql 护栏 + 508 测试全绿),前端与 DB 仓库层是测试真空 |
回归网不均衡 | 实测:后端 16.6% / 前端 1.9% |
二、项目全景(全部实测)
| 指标 | 后端 | 前端 |
|---|---|---|
| 源码文件 / 行数(不含测试) | 327 / 67,089 | 158 / 45,977(另 CSS 7,344) |
| 测试文件 / 行数 | 85 / 11,171 | 17 / 872 |
| 测试/源码比 | 16.6% | 1.9% |
| 测试实测结果(本次复跑) | 508 pass / 3 skipped / 0 fail(511 项) | 66 pass / 0 fail |
| 迁移 | 82 个 SQL / 2,403 行,全库 51 张表 | — |
- 技术栈:Node + Express 5 + TS 6.0 + PostgreSQL +
node-pg-migrate;React 19 + Vite 8 + AntD 6 + TanStack Query 5(updatedAt: Infinity)。 - 运行形态:单实例(内存限流、内存调度器状态、启动即迁移),Compose + Caddy 部署,生产存储走 OSS。
- 代码集中在少数巨型文件:前端
WorkOrdersPanel.tsx3,657 行、后端consignment-repo.ts1,253 /consignment-fulfillment-service.ts1,174。 - 最大模块:
services/worker-platform/15,325 行(占后端 23%),寄卖与工单两部分几乎平分。
flowchart LR
P["91卡券 / 快手电子凭证 / PDD / affiliate-dash"]
P --> IN["services/order<br/>进单·商品匹配·建履约任务"]
IN --> FUL["services/fulfillment<br/>planner → executor"]
FUL --> CLAIM["claim token → 领取页 claimUrl"]
IN --> DISPATCH["发单消费者<br/>syncWorkerOrdersForSourceOrder"]
DISPATCH --> WO["work_orders<br/>pending_material / unassigned"]
WO --> HALL["管理员发布 → open → 抢单大厅"]
HALL --> W["打手抢单/拼单<br/>冻结押金 in_progress"]
W --> ACC["待验收 → 后台或自动验收 → 结算"]
WO -.->|寄卖现货| CONS["寄售仓库<br/>consignment_inventories"]
CONS --> ACC
SCH["scheduler<br/>超时扫描·押金解冻·PDD同步·数据保留"] --> WO
三、后台发单系统
3.1 端到端链路
flowchart TD
A["91 /orders/create<br/>routes/open-91.ts:27"] --> B["upsertOrderFromSource<br/>order-service.ts:98"]
B --> C["规则匹配<br/>work-product-match-mappers.ts:93"]
C -->|未命中/歧义| X1["写匹配日志并跳过<br/>sync-worker-orders-service.ts:119"]
C -->|命中| D["createWorkOrder<br/>sync-worker-orders-service.ts:185"]
D --> E{"必填资料齐?"}
E -->|齐| F["unassigned"]
E -->|缺| G["pending_material + 待办通知:266"]
F --> H["管理员补资料/改价 → unassigned<br/>admin-work-order-material-service.ts:44"]
G --> H
H --> I["publishAdminWorkOrder<br/>校验 reward>0 :219"]
I --> J["open + 核销电子凭证<br/>publish-voucher-service.ts:32"]
J --> K["抢单大厅 worker-hall-service.ts:29"]
K --> L{"拼单?"}
L -->|是| M["joinWorkOrderShare 按份冻押金"]
L -->|否| N["grabWorkOrder open→in_progress<br/>work-order-grab-repo.ts:11"]
M --> O["提交验收 pending_acceptance"]
N --> O
O --> P["acceptWorkOrderAndSettle<br/>work-order-settlement-repo.ts:23"]
P --> Q["释放/延迟解冻押金 + 结算报酬 + 抵扣售后欠款 + 升级"]
N -.->|超时| R["超时扫描<br/>work-order-timeout-service.ts:13"]
关键澄清:工单不会自动上架到大厅。自动建单只到 pending_material / unassigned,必须由后台 publishAdminWorkOrder(admin-work-order-management-service.ts:220)把状态推到 open 才会出现在抢单大厅;reward_amount <= 0 会被拒绝(:228)。另一条路是后台直接指定打手(assign)。
3.2 工单状态机
pending_material ──补资料──> unassigned ──发布──> open ──抢单/指派──> in_progress
│
┌───────────────┴───────────────┐
提交验收 超时/问题单
▼ ▼
pending_acceptance problem / open(重开)
│ │
验收 accepted cancelled(退/扣押金)
- 状态常量:
domain/work-order-status.ts:1-9(8 态 +problem)。 - 履约模式:
standard/friend_gift(赠送冷却默认 72h,上限 30 天,domain/work-order-acceptance-mode.ts:12-13)。 - 拼单母单可长期停
open,份额状态joined → submitted → accepted/cancelled;friend_gift模式强制关闭拼单(sync-worker-orders-service.ts:180)。
3.3 核心模块
| 模块(相对仓库根) | 职责 | 关键函数 |
|---|---|---|
apps/backend/src/services/order/order-service.ts |
来源订单 upsert(advisory lock 去重)、双消费者分流 | upsertOrderFromSource:98,发单调用点 :211,:241 |
.../services/worker-platform/sync-worker-orders-service.ts |
发单核心:匹配 → 建工单 → 事件 → 待办 | syncWorkerOrdersForSourceOrder:37 |
.../sync-work-orders-from-send-code.ts |
快手发码回流补全建单 / 全量重扫 | :90,:176 |
.../work-order-pricing.ts |
按件/固定/拼单定价 | :20,:46 |
.../admin-work-order-material-service.ts |
补资料、改价(含拼单价锁定) | :23,改价守卫 :102 |
.../admin-work-order-management-service.ts |
编辑、发布、下架、置顶 | updateAdminWorkOrder:37、publishAdminWorkOrder:220 |
.../admin-work-order-assignment-service.ts |
后台指派 | :24 |
.../admin-work-order-acceptance-service.ts |
验收 / 份额验收 / 批量验收 | :33,:89,:254 |
.../worker-hall-service.ts |
大厅列表(2s 进程内缓存、分类/延迟/容量) | :29 |
.../worker-hall-action-service.ts |
抢单 / 拼单入口 | :33,:129 |
.../worker-permission-utils.ts |
等级权限、免押额度、冻结额 | :9,:58 |
.../publish-voucher-service.ts |
发布/指派前核销电子凭证 | :32 |
.../repositories/worker-platform/work-order-{grab,share,settlement,issue,cancel}-repo.ts |
事务与资金落库 | grabWorkOrder:11、acceptWorkOrderAndSettle:23 |
3.4 资金与押金规则(已核实的不变量)
- 冻结额 =
max(0, required_deposit_amount − 免押额度)(worker-permission-utils.ts:58);免押 = 等级额度 + 个人追加。 - 抢单原子性:状态条件更新
WHERE status='open' AND assigned_worker_id IS NULL,抢单时在事务内复核大厅容量与可见延迟(work-order-grab-repo.ts:41-73)。 - 结算:
acceptWorkOrderAndSettle用FOR UPDATE锁工单(:34)→ 拼单未全部提交则拒绝(:56)→ 置accepted→ 释放寄卖发布押金(:99)→ 逐份额结算:先抵扣售后退款欠款(:121),再按depositUnfreezeDays(默认 3 天)与rewardUnfreezeEnabled(默认 true)决定即时到账还是进pending_unfreeze_amount(:128-140)。 - 超时三策略:
reopen/cancel_release/cancel_deduct(work-order-timeout-policy.ts);赠送单在material_submitted阶段跳过超时。 - 赠送单押金不可退:确认加好友后
friend_added_at非空 → 取消/脱离一律没收(domain/work-order-acceptance-mode.ts:75)。 - 可编辑边界:仅
pending_material/unassigned/open(无活跃份额)可编辑/改价,已接单不可改(admin-work-order-management-service.ts:44-53)。
3.5 API(后台发单,前缀 /api/v1/admin)
GET/POST /worker-platform/orders、PUT|DELETE /orders/:id、/material、/publish、/unpublish、/assign、/unassign、/cancel、/reopen、/confirm-friend、/complete-gift-cooldown、/reset-gift-material、/sharing、/note、/pin、/events、/accept-batch、/reprocess-matches(routes/admin/worker-platform/orders.ts)。
打手端:routes/worker/hall.ts(大厅、抢单、拼单)、routes/worker/orders.ts(我的订单、提交验收)。
四、寄售仓库(寄卖)
4.1 业务定位与术语
打手把自己《和平精英》的苏尔南手枪挂到平台(寄卖),平台撮合给买家,履约完成后结算货款给卖家。全流程以 SUB… 提报单号为唯一锚点(对外隐藏 INV… 库存号)。
| 术语 | 实体 | 含义 |
|---|---|---|
| 提报 / 上架 | consignment_submissions |
打手端唯一入口,发布即冻结发布押金(默认 ¥20) |
| 现货 / 库存 | consignment_inventories |
审核入库的一件真实货物 |
| 内部现货履约 | internal_stock |
客服/内部仓代交付,无接单打手,免外派佣金 |
| 押金履约 | worker_deposit |
指派打手交付并冻结履约押金(历史路径,已降级为遗留接口) |
| 待加好友 / 冷却 | gift_phase |
占库后停 material_required,确认加好友后进入 72h 冷却 |
| 供货卖家 | getConsignmentSupplierByWorkOrderWithClient |
从工单反查该收货款的人(内部现货履约时工单无接单打手) |
4.2 数据模型(迁移 077–082)
| 表 | 关键字段 | 状态枚举 | 约束/索引 |
|---|---|---|---|
consignment_inventories(077:3 / 078 / 079:49) |
inventory_no UNIQUE、规格五项、cost_price_fen、allow_matching、worker_id(卖家)、assigned_worker_id(履约打手)、bound_order_id/bound_work_order_id、credentials_json |
status ∈ in_stock/consumed/archived;allocation_status ∈ 待安排/已安排(080 CHECK);source_type ∈ manual/worker_portal/recycle |
uniq_consignment_inventories_bound_work_order(bound_work_order_id WHERE status='consumed',079:93)+ 8 个普通索引 |
consignment_submissions |
submission_no UNIQUE、result_inventory_id、content_json、asking_price_fen、worker_id(提报人) |
pending_review/approved/rejected |
idx_status_created;link_id 已删(079:11) |
consignment_collect_links |
— | — | 079:15 已 DROP |
worker_users(082) |
can_consign(默认 FALSE)、consignment_activated_at |
— | idx_worker_users_can_consign |
worker_wallet_ledgers |
payload_json.kind='consignment_publish' + consignmentSubmissionId |
deposit_freeze/release/deduction |
uniq_..._consignment_publish_freeze(079:177,发布冻结幂等) |
079 迁移为何重要:一次性做了三件危险事——删 H5 收集渠道(collection_link 归并为 worker_portal)、拆分卖家 worker_id 与 assigned_worker_id(防止占库覆盖卖家导致「我的寄卖」丢件)、清理历史脏数据(重复绑定工单解绑回库 + 重复冻结押金退还并建幂等索引)。生产上库前必须先跑 寄卖系统迁移079上库前检查.sql,其中 2.1 段明确:若某提报单的押金此前已被释放/没收过,079 的自动退款会重复退款,必须先人工处理(该脚本:43-93)。
4.3 状态机(展示口径)
domain/consignment-item-stage.ts:146-248 把「提报单 + 库存 + 绑定工单」三套状态推导成一个卖家可见阶段:
待审核 → 已通过(在库待匹配) → 已匹配-待加好友 → 加好友赠送冷却中 → 待赠送
→ 已赠送待审核 → 已完成
异常终态:已驳回 / 已下架作废 / 已取消
标准验收(非好友赠送)走 履约中 → 待验收 → 已完成。
库存与分配迁移:
| 迁移 | 触发者 | 代码位置 |
|---|---|---|
in_stock → consumed(内部现货) |
客服 | consignment-fulfillment-service.ts:424-453(锁工单 + 条件占库 + 写绑定 + 停待加好友) |
in_stock → consumed(指派) |
客服 | consignment-repo.ts:511-540(写 assigned_worker_id / 已安排) |
consumed → in_stock(撤销) |
客服 | consignment-fulfillment-service.ts:986-1000(AND bound_work_order_id=... 仲裁) |
consumed → archived |
客服/自动 | 同上 :971-984;已确认加好友时强制作废不回池(:968) |
| 终态解绑回池 | 系统 | consignment-repo.ts:765(唯一出口,被撤单/超时/取消指派等 7 处调用) |
4.4 匹配规则(防错发的核心)
consignment-matching-service.ts:309 matchPeacekeeperOrderWithInventory:
- 品类强隔离门禁(
:319-340):基于生产分类体系识别订单/现货品类,双方可识别且不一致直接拒绝;现货是苏尔南手枪时,订单必须带系列或「苏尔南/手枪/枪皮」信号。 - 三态比对
compareSpec(:292-304):match / mismatch / unknown;通配系统=通用、区服=通用|互通。 - 低配不充高配(
:359):订单明确钻级/璀璨高于库存即拒绝。 - unknown 不得判精确(
:363-374):订单没写清的字段降级为partial并列出缺失项。 - 严格 vs 宽松(
:394-418):默认严格(系统/区服/系列任一非 match 即排除);allowUnspecifiedSpecs只给「人工已选定现货后服务端复检」用。 - 排序:score →
cost_price_fen→inbound_at→ id(:597-611)。
手动匹配(commit f07e4087):工单侧列全部在库现货(含 conflict),仓库侧反查可匹配工单;冲突必须 allowSpecMismatch=true + 必填 matchNote,冲突明细写入工单事件与 material.consignment.specConflicts(:489)。
4.5 白名单与权限
- 总开关:
worker_finance_config.consignmentWhitelistEnabled(配置项,非 DB 列,默认 true)。 - 单人开关:
worker_users.can_consign(082,默认 FALSE)。 - 判定:
canWorkerPublishConsignment = !whitelistEnabled || canConsign(domain/consignment-whitelist.ts:7);后端提报入口 403consignment_permission_required(consignment-worker-service.ts:151)。 - 存量平滑过渡:082 把历史上在
consignment_submissions提报过的打手批量置 TRUE(082:16-24)。 - 后台四个权限:
consignment.view / manage / review / fulfill(077:126-158,080 修正文案)。
4.6 资金规则
| 事件 | 动作 | 代码 |
|---|---|---|
| 提报发布 | 冻结发布押金(默认 2000 分) | consignment-deposit-repo.ts:51 |
| 审核驳回 | 全额释放(幂等) | consignment-admin-service.ts:670 |
| 下架作废 | 释放 | :420 |
| 人工回库 | 重新冻结;已结算则 409 | :500、:262-314 |
| 验收完成 | 释放发布押金 + 补发货款 cost_price_fen || asking_price_fen |
work-order-settlement-repo.ts:99,429 |
| 加好友后违约/取消/超时 | 没收发布押金(deposit_deduction,只扣冻结额) |
work-order-cancel-repo.ts:140,261,385 |
| 撤销履约 | 打手押金 auto/refund/forfeit;卖家押金 auto → 作废 release / 回库 keep_frozen;显式覆盖必须填原因 | consignment-fulfillment-service.ts:1016、domain/consignment-deposit-policy.ts:54-77 |
| 防双付 | 指派对象≠卖家时报酬保持原值;=卖家时改写为结算价 | consignment-fulfillment-service.ts:590,689 |
4.7 API
- 打手端(
/api/v1/worker):POST /consignment/publish、GET /consignment/price-suggestion、GET /consignment/my-items、POST /consignment/items/:submissionId/confirm-friend、.../gift-evidence。 - 后台(
/api/v1/admin):GET /worker-platform/consignment/inventories|stats|inventories/:id(view)、PUT inventories/:id、PATCH inventories/:id/matching、archive、return(manage)、GET submissions(view)、POST submissions/:id/approve|reject(review)、POST fulfill|assign-worker|revoke(fulfill)、POST /worker-platform/workers/:workerId/consignment-permission。
五、风险总表(按优先级,已去重)
P0 · 资金安全
| # | 问题 | 证据 | 建议 |
|---|---|---|---|
| 1 | 发单金额 > 用户付款仅前端确认,后端改价/发布/指派三处均不做金额上限或与付款额比对 | 前端 work-order-payment.ts:22;后端 admin-work-order-management-service.ts:60、admin-work-order-material-service.ts:102(仅拼单价锁定)、发布仅校验 >0(:228) |
后端补齐同一校验并写事件留痕;发布/指派/改价三入口共用 |
| 2 | 审核入库可由请求体 workerId 改归属,货款按 inventory.worker_id 结算 |
consignment-admin-service.ts:587(payload.workerId 优先);consignment-supplier-repo.ts:67(库存优先于提报);consignment-repo.ts:1227(COALESCE($4, worker_id)) |
若改派非刻意玩法:审核接口禁止改 worker_id,或改派必须留痕 + 双人复核 |
| 3 | 同路径仍存在「按手机号自动绑定打手」:审核时若未传 workerId,会用卖家手机号反查并绑定 | consignment-admin-service.ts:597 |
与 079 下线 H5 的初衷一致化:审核不再自动绑定,需显式选择 |
| 4 | 审核接口全量返回 credentialsJson(游戏账号密码),仅需 consignment.view |
consignment-admin-service.ts:179 |
列表脱敏、详情按权限拆分或二次校验 |
| 5 | 模板 required_deposit_amount 可配但建单硬编码 0 |
sync-worker-orders-service.ts:196;规则侧 work-product-rule-repo.ts:375,421 |
明确设计意图:要么带上,要么删字段避免误配 |
| 6 | deposit_threshold_amount 只被读写与展示,无任何冻结/结算逻辑读取 |
全仓 grep 仅 work-order-management-repo.ts / admin-work-order-management-service.ts:68 / work-order-display-mappers.ts:385 |
确认语义后删除或接线 |
P0 · 并发与数据一致性
| # | 问题 | 证据 | 建议 |
|---|---|---|---|
| 7 | 加锁顺序相反可死锁:grabWorkOrder 先锁钱包(:24 FOR UPDATE)再更新工单(:58);assignWorkOrderToWorker 先锁工单(:154 FOR UPDATE)再锁钱包(:169)。同一单并发指派+抢单构成环 |
repositories/worker-platform/work-order-grab-repo.ts:24→:58 与 :154→:169;钱包锁见 repositories/worker-platform/shared.ts:28 |
统一锁序,或抢单先 SELECT ... FOR UPDATE 工单 |
| 8 | 寄卖指派链路跨事务部分成功,补偿三步全部 catch {} 吞错 |
consignment-fulfillment-service.ts:663/680/742/748,补偿 :802-849 |
合并为单事务;补偿失败必须告警而非静默 |
| 9 | 改派会让原卖家「我的寄卖」永远停在「正在入库」,被改派者看到未提报的货 | consignment-worker-service.ts:352-368;consignment-item-stage.ts:158 |
与 #2 一并收敛 |
| 10 | 待加好友阶段可无限期挂起,货与押金同时冻结,无超时策略 | 寄卖库存优化现状与进度追踪.md:93,98 仍标「待业务拍板」 |
加超时预警 + 自动回退/催促 |
P1 · 安全
| # | 问题 | 证据 | 建议 |
|---|---|---|---|
| 11 | claim token 默认永久有效(tokenTtlHours: 0),链接即唯一凭据 |
config/defaults.ts:64;services/claim/claim-service.ts:40;迁移 015 |
生产强制 TTL > 0,并考虑首次访问后才可见 |
| 12 | /collect/* 完全免鉴权,凭订单号可读工单公开投影;限流 key 含攻击者可控的 orderNo → 每个新单号即新桶 |
app.ts:106;routes/collect.ts:13-27;middleware/rate-limit.ts:88 |
一次性 lookup token + 纯 IP 维度限流 |
| 13 | /files/object 无鉴权,仅靠 key 不可枚举 |
app.ts:108;routes/files.ts:12;file-storage-service.ts:233 |
私有 scene 恢复短期签名(需同步改写入侧) |
| 14 | 无安全响应头(无 helmet:CSP/HSTS/X-Frame-Options 缺失) | app.ts:35-45;依赖列表无 helmet |
应用内或 Caddy 统一注入 |
| 15 | 后台 token 与权限列表存 localStorage | apps/frontend/src/utils/admin-auth.ts:1,44 |
XSS 即接管后台,建议 httpOnly Cookie + CSRF |
| 16 | PDD 扩展路由回显任意 Origin | routes/pdd-extension.ts:26-30 |
白名单 chrome-extension://<id> |
| 17 | 备份脚本会把 data/(含 PDD RSA 私钥)打包上传 OSS |
deploy/backup-db.sh:3-6;services/platforms/pdd/pdd-crypto-service.ts:112 |
私钥移出备份范围或备份加密 |
做得好:91 / 快手 / affiliate-dash 三个回调全部强制验签 + 时间戳容差(open-91/order-create-service.ts:32);dev-mock 双重门禁(admin 会话 + isDevMockEnabled,非生产或显式开关,services/dev-mock/dev-mock-service.ts:54);admin 口令 scrypt + 盐;会话 HMAC + DB 会话表 + session_version 三查;请求体 2MB 上限;日志脱敏并有单测。
P2 · 性能与可维护性
| # | 问题 | 证据 |
|---|---|---|
| 18 | 建单对每个 order_item 串行 3 次查询(含循环内重复查同平台单号) | sync-worker-orders-service.ts:73,87,150 |
| 19 | 匹配候选全表入内存、无分页,每次 checkWorkOrderInventoryMatches 都触发 |
consignment-repo.ts:854-887;consignment-fulfillment-service.ts:190,318 |
| 20 | 寄卖统计每次 5 条全表聚合 | consignment-repo.ts:218-303 |
| 21 | 仓库搜索未覆盖前端占位符承诺的 buyer_name/shop_name/seller_phone/worker_name |
consignment-repo.ts:178 vs ConsignmentInventoryPanel.tsx:789 |
| 22 | 「我的寄卖」固定 pageSize: 200,超出静默不可见 |
consignment-worker-service.ts:353 |
| 23 | 6 个 God file:前端 WorkOrdersPanel.tsx 3,657、后端 consignment-repo.ts 1,253、consignment-fulfillment-service.ts 1,174、work-order-query-repo.ts 1,057 |
实测 |
工程与运维
| # | 问题 | 证据 |
|---|---|---|
| 24 | 无 CI(无 .github/workflows),质量门禁只靠本机 pre-push,可 SKIP_CHECKS=1 绕过 |
.githooks/pre-push、scripts/install-git-hooks.sh |
| 25 | 迁移启动时自动执行且无 down;多副本会并发迁移 | startup/bootstrap.ts:32(失败每 5s 重试) |
| 26 | 无指标 / APM / 告警;调度器运行态只在进程内存 | services/scheduler/scheduler-service.ts:11 |
| 27 | 单实例耦合:内存限流 + 内存调度器 | middleware/rate-limit.ts:26 |
| 28 | Caddyfile 硬编码其他项目 vhost | deploy/caddy/Caddyfile:22-30 |
| 29 | bus factor ≈ 1(949 commits,2 作者,其中 704 来自一人) | git shortlog -sn |
测试缺口(真实)
- 无抢单/拼单的并发与押金事务测试(死锁、重复抢单、余额不足回滚无回归保护)。
- 无超时扫描
settleOverdueWorkOrders的测试(三策略 × 赠送单 forfeit 组合)。 - 无
syncWorkerOrdersForSourceOrder端到端分支测试(already_exists/platform_order_exists/auto_create_disabled/ 状态落点)。 - 无「发单金额 vs 用户付款」后端门禁测试(因为后端逻辑不存在)。
- 无结算金额守恒不变量测试(报酬 + 押金 + 欠款抵扣 == 钱包变化)。
- 无
approveAdminConsignmentSubmission端到端测试(改派 + 押金归属 + 货款归属链无回归保护)。 repositories/16,484 行仅约 10 个测试文件——DB 层几乎无回归网,而这正是 SQL 最复杂处。- 前端
pages/45,977 行仅 872 行纯函数测试,无组件渲染测试。
六、改进路线建议(按性价比排序)
- 建 CI(跑两侧
npm run check+ PG service 集成测试,把 pre-push 降级为可选)。当前最大杠杆:102 个测试与check:sql护栏现在只在本机生效。 - 资金门禁后端化:发单金额 vs 付款额(P0-1)、审核改派与手机号自动绑定(P0-2/3)、押金模板漏带(P0-5)三件事都在既有文件内可闭环,且都能补上测试。
- 统一加锁顺序(P0-7)并把寄卖指派链路事务化(P0-8);这两处是唯一可能造成"钱和货同时错"的并发窗口。
- 收紧对外凭据:claim token 生产强制 TTL、
/collect/*一次性 token + IP 限流、私有文件短期签名(P1-11/12/13)。 - 拆分 6 个巨型文件,先动
WorkOrdersPanel.tsx与consignment-repo.ts,同步补测试,把前端测试比从 1.9% 拉到 15% 量级。 - 迁移与发布解耦:部署脚本先
npm run db:migrate再滚动重启,为多副本与蓝绿留出空间;高风险迁移补down段。 - 补最小可观测栈:
/metrics(进程 + 连接池 + 调度运行态)+ 调度失败推送 + helmet。
七、一句话总结
后端是一个分层清晰、DB 纪律与配置治理明显优于同类项目、交付与备份脚本相当完备的成熟业务系统;发单主链路(进单→匹配→建单→发布→抢单→验收→结算)状态机与押金规则闭合度高,寄卖子系统的规格匹配门禁与押金/货款双账设计也相当讲究。 当前最该优先处理的三件事是:① 资金门禁从「前端提醒」下沉到后端强制;② 抢单/指派的加锁顺序与寄卖指派事务化;③ 对外三处凭据薄弱/无鉴权接口。中长期短板集中在前端与 DB 仓库层的测试真空、6 个巨型文件、单实例耦合、以及无 CI/无指标的运维盲区。