yml2213
ccbcb7cbae
fix(deploy): --backend-only/--all 不该连带跳过"改动范围"的计算
...
真机验证时发现:显式指定 --backend-only 会直接 return,没算 git diff,
于是 DIFF_KNOWN 一直是 false —— "无 migrations 改动就自动跳过备份"的判断随之失效,
一次只想重建后端的部署照样会跑 pg_dump + 传 OSS(生产上约 12 分钟)。
修复:拆成两步
- compute_changed_files:只回答"自上次成功部署以来改了什么"(CHANGED_FILES / DIFF_KNOWN /
ENV_CHANGED / LAST_DEPLOYED_SHA),在任何分支下都先算;
- detect_changed_services:只决定"重建哪一端",显式参数(--all/--backend-only/--web-only/
--reset-db)只覆盖重建范围,不覆盖改动范围。
- print_plan 改用 LAST_DEPLOYED_SHA,不再重复读状态文件。
补一条顺序守卫用例:compute_changed_files 必须在 detect_changed_services 之前调用。
2026-10-03 16:42:55 +08:00
yml2213
9b6aa8e2fd
fix(deploy): 中文路径被 git 转义 → 按需重建误判为"两端全建"
...
首次真机验证按需重建时踩到:`git diff --name-only` 默认 core.quotePath=true,
docs/部署说明.md 会输出成 "docs/\351\203\250...",匹配不到 docs/* 规则,
一路掉进"拿不准就两端都重建"的兜底分支——只改文档却重建了 backend + web。
修复:
- 新增 classify-changes.sh --git-diff <from> <to> [repo],内部用
`git -c core.quotePath=false diff --name-only`,部署脚本改走它;
- 分类时顺带剥掉可能残留的首尾引号;
- 补回归用例:临时 git 仓库里提交中文文件名,断言改动列表保留中文原文、
且最终分类为空(不加 --git-diff 时该用例会失败,已实测复现)。
2026-10-03 16:34:18 +08:00
yml2213
0b48313617
feat(deploy): 部署改成按需重建,无 schema 变更自动跳过备份
...
每次部署都无条件重建 backend + web 两个镜像(实测后端 3m35s、前端 2m54s),
并且每次都在迁移前 pg_dump + 传 OSS(生产 492MB dump 那一步约 12 分钟);
只改了后端也要把前端整条链重跑一遍。
改动:
- 新增 deploy/lib/classify-changes.sh:把改动文件映射成"要重建哪一端",
规则保守——apps/backend/** → backend、apps/frontend/**|deploy/caddy/**|前端 Dockerfile → web、
文档/部署脚本/测试文件 → 不重建,compose 文件与未知路径 → 两端都重建。
- ubuntu-deploy.sh 用 .deploy-state(上次成功部署的 commit + .env 指纹)算 git diff,
未变化的一端不 build、也不 --force-recreate,容器保持运行;若它没在跑则用现有镜像拉起。
两端都要建时合并为一次 compose build(bake 并行)。
- 备份:改动不含 apps/backend/src/db/migrations/** 时自动跳过(--backup 强制、
--skip-backup 无条件跳过);算不出改动范围(首次使用、force push)时不自动跳过。
- compose pull 改 --policy missing:不再每次重新校验 postgres tag,避免顺手静默升级数据库镜像。
- 新增 --dry-run(打印计划后退出,含基础镜像 digest)、--all、--backend-only/--web-only;
部署结束打印各阶段耗时,回答"这次怎么比上次慢"不用靠推算。
- .deploy-state 进 .gitignore;README 与 docs/部署启动与数据库重建.md 同步。
验证:新增 7 个用例直接执行分类脚本断言"改动→重建范围",并守住"状态只在健康检查后才落盘"
等关键顺序;全量 595 个单测、typecheck、lint、check:sql 全通过。
2026-10-03 16:29:05 +08:00
yml2213
30ae35d3a5
fix(consignment): 撤销寄售履约回库分支必 500 —— SQL 参数比占位符多一个
...
线上客服 kf_13 撤销 WO26092609475746 时返回 500 internal_error,
真实库报:bind message supplies 5 parameters, but prepared statement "" requires 4。
根因:解绑现货的两条分支共用一个 5 元参数数组,但回库/取消分支的 SQL 只用到
$1..$4(第 5 个「备注前缀」只有作废分支才用)。于是运营只要选
「退单回库并恢复工单为未指派」或「退单回库并取消工单」就必定 500,
而假 client 的单测与类型检查都看不到这类错。
修复:
- 解绑 SQL 与参数抽到 consignment-revoke-inventory-sql.ts 一起构造,
回库/取消分支只传 4 个参数,作废分支才多带备注前缀($5);
- 单测锁定「占位符个数 == 参数个数」,覆盖 reopen / archive_inventory /
已确认加好友自动作废三条分支;
- db 层加参数个数自检(db/sql-arity.ts + db/client.ts):参数比占位符多时
提前抛出带 SQL 片段的错误,不再让 pg 的 bind 报错裸奔,
并跳过字符串/注释/dollar-quote 里的 $n,避免误杀。
验证:本地影子库端到端跑通三条分支(工单 100140 + 现货 INV26091917439822),
并复现修复前的 bind 报错;全量单测 588 通过,typecheck/lint/build 均通过。
2026-10-03 16:03:14 +08:00
yml2213
8ecb8a879a
fix(backup): 修复本地备份保留修剪静默失效,本地默认保留降到 2 份
...
bug:prune_local 里 ls 的 cd 只发生在命令替换的子 shell 中,
rm 却拿裸文件名在 PROJECT_ROOT 下执行——目标文件不存在,
而 rm -f 对不存在的文件静默成功。结果是"清理成功"日志照打、
本地备份数量却一直超出 KEEP(生产 configs 目录累积到 43 份,
dump 10 份,均超出 KEEP=7)。
修复:rm 改用 ${dir}/${file} 绝对路径,并补源码级守卫测试锁定。
顺带把 .env.server.example 的默认保留从 7 降到 2:
本地备份只是断网应急,OSS 已有完整备份;而部署脚本的磁盘预检
要求 可用 ≥ 备份目录×2,本地留太多会直接把部署卡死
(这次就是 3.8G 备份触发预检失败 → 被迫清构建缓存 → 全量构建变慢)。
2026-10-03 15:51:01 +08:00
yml2213
69e3d9a396
perf(worker): 「我的订单」缓存改为事件驱动失效,TTL 2s/5s → 30s
...
这是当前最大瓶颈的根治:打手端三件套(列表/计数/概览)占全库约 75% 缓冲读,
单查询成本是 O(打手单量) 的固有成本(重打手 8,586 单逐行探测),
索引层面已无可抠,只能把"计算次数"和"访问次数"解耦。
原先 TTL 只有 2s/5s,是因为文档担心"再长会让分页总数滞后"。
但该数据只在该打手自己的工单流转时才变,而项目已有 realtime 快照广播
精确感知每次流转——把同一事件流接到缓存失效上,长 TTL 与新鲜度可以兼得,
正确性反而优于固定 5s(那同样存在滞后窗口)。
- WorkerListCache 新增 evictByPrefix:按键前缀整体逐出;
- 缓存实例抽到中立模块 worker-my-order-cache.ts:realtime 需要引用失效入口,
而 my-order-service 已 import realtime,实例放任何一方都会循环依赖;
- 缓存键统一为 ["w",workerId,scope,detail],前缀失效不会误伤其它打手;
- 失效挂在 publishWorkOrderSnapshots:那里恰好已有工单 + 全部拼单人,
受影响集合取 当前指派 + 历史保留 + 全部拼单人 + 调用方指定,
21 处工单写入路径(含取消申请/反馈处理)都会经过;
工单已删除的分支同样失效,避免打手继续看到已删的单。
最坏情况(漏失效)是打手最多 30s 看到旧状态,与原 5s TTL 的滞后窗口同性质。
守卫测试锁定:失效入口存在、键前缀格式、受影响集合三要素齐全。
2026-10-03 15:23:11 +08:00
yml2213
fbdef6ecab
perf(worker): 「仅问题单」检索短路,从 pending 表反查而非枚举可见集
...
问题单页签原先走完整派生表达式:为该打手枚举全部可见单并逐行求值
嵌套 CASE + pending EXISTS(重打手 8,586 单 × 逐行探测 ≈ 233MB 缓冲读),
而问题单结果通常为空——生产实测 worker_order_cancel_requests 与
worker_order_feedbacks 的 pending 行数当前均为 0。
短路形状:
wo.status = 'problem'
OR (基础状态 = 'in_progress' AND wo.id IN (两张 pending 表反查))
- 左臂必须写成 sargable 的 wo.status = 'problem':基础状态等于 'problem'
当且仅当 wo.status = 'problem'(派生分支没有 problem 映射);
- 反查走迁移 058 的两张 pending 专用索引,实测 8 个块;
- 仅当请求状态"只含 problem"时短路,混合状态仍走完整表达式(结果集完整性);
- 顺带把 workerOrderStatusExpression 拆出 workerOrderBaseStatusExpression 复用。
生产 EXPLAIN(worker=7):29,851 块 / 267ms → 13,387 块 / 118ms。
2026-10-03 15:22:57 +08:00
yml2213
253dc2061e
perf(hall): 接单排行榜加 30s 短缓存,消除每次刷新的全表聚合
...
排行榜每次都要对全量 work_orders + work_order_shares 做跨源去重聚合
(生产实测 742ms / 35,483 块 ≈ 277MB),而结果只在工单验收完成时才变化。
大厅每次刷新/窗口聚焦都会触发前端重新拉取,不缓存时该查询独占全库约 8% 的缓冲读。
- TTL 取 30s:名次变化以分钟计,30s 足够新鲜;按观测到的调用量,命中率接近 100%。
- 缓存键必须带 workerId:SQL 是 WHERE rank <= 10 OR worker_id = $2,
结果包含"当前打手自己的名次",键漏了 workerId 会把 A 的名次错发给 B
(正确性问题),守卫测试专门锁定这一点。
2026-10-03 15:22:46 +08:00
yml2213
c8a4b7e945
fix(worker): 删除依赖残留字段 task_id 的死 JOIN,店铺名兜底改读 material_json
...
核查结论:work_orders.task_id 是残留字段,不是"数据没同步上来"。
1. 唯一写入点 createWorkOrder 写的是 input.taskId || null,而两个调用方
一个硬编码 taskId: null、一个根本不传;updateWorkOrder 的白名单里也没有它;
git 全历史检索 `taskId:` 只出现过 null 一种取值。
2. 即便强写也无从关联:工单关联 100,231 个订单、履约任务关联 87,603 个订单,
交集为 0;按 order_item_id 比对 100,229 条同样 0 命中。而两者时间范围
(2026-07~10)与 orders.id 区间(1~280k)都重叠 —— 说明是按规则表分流的
两条并行履约通道,一单只走一条。
3. 依赖它的 idx_work_orders_task_unique 为空(8KB)。
改动:
- 三处 `LEFT JOIN fulfillment_tasks ft ON ft.id = wo.task_id`
(取消申请 / 售后案 / 工单反馈)恒为空,删除;
- 店铺名兜底 `ft.shop_name/shop_id` 改为读工单自己的
material_json->'source'->>'shopName'/'shopId';
- 取消申请里的 `ft.platform_order_id` 一并删除(实测 needs_ft_pid_fallback = 0,纯 no-op);
- 迁移 091 给列与索引打上注释,避免后续再被当成数据问题重复排查。
生产逐行回归(旧逻辑 vs 新逻辑取差集):
取消申请 715/715 行、103 条店铺名从空变有值、回归 0;
售后案 21/21 行、1 条、回归 0;工单反馈 348/348 行、15 条、回归 0;
合计 119 条修复、0 条回归、行数与 platform_order_id 零变化。
验证:新增源码级守卫(禁止再 JOIN fulfillment_tasks、兜底必须读 material_json)。
2026-10-03 14:27:46 +08:00
yml2213
0173efb277
feat(pdd): 店铺连续同步失败 3 次后熔断,停止自动重试
...
背景:生产上「木塔网游专营店」Cookie 已过期,每约 24 秒重试一次、每轮刷一条 ERROR 日志,
持续数天。Cookie 失效不会自愈,继续重试只刷日志、白白占用同步周期。
行为:
- 失败 1~2 次:累计计数,继续重试;
- 第 3 次:熔断,打一条显式 ERROR(含"更新 Cookie 会自动恢复"提示);
- 熔断后:自动同步直接跳过,不再刷错误日志;
- 恢复:更新 Cookie(后台编辑或浏览器插件)自动解除;手动同步成功一次亦可。
熔断状态持久化在店铺配置里,容器重启后依然生效。
改动:
- pdd-source-config-service:阈值常量 + consecutiveFailures/syncSuspendedAt/
syncSuspendedReason 持久化字段 + 纯函数 resolvePddStoreFailureState /
applyPddStoreBreakerState / isPddStoreSyncSuspended / shouldResetPddSyncBreaker。
- pdd-sync-service:syncAllPddStores 跳过熔断店铺并上报 suspendedCount;
syncStoreOrders 失败记录、成功清零(健康店铺不产生额外配置写)。
- pdd-order-sync-job:上报熔断数,且存在熔断店铺时强制留在 INFO 级
(否则"全部店铺已停同步"会因新单为 0 被降级成 debug 而不可见)。
- 前端加「已熔断 · 停止重试」标签与类型字段。
顺带修掉一个隐患:saveAdminPddStore 收的是 Partial 载荷,normalizePddStoreItem
会为缺失字段产出 undefined,导致一次「只改店铺名」的保存就把熔断洗掉。
由 applyPddStoreBreakerState 回填既有状态解决。
验证:9 项熔断单测(含局部保存不洗状态、Cookie 变更解除熔断)。
2026-10-03 14:27:38 +08:00
yml2213
90de6b9419
perf(worker): 单号检索收敛为可走索引的等值/前缀,并支持关联单号反查
...
问题:后台「按单号检索」在平台单号/工单号分支里 OR 了一支
`work_order_no ILIKE '%'||$n||'%'`。前置通配符非 sargable,任何 B-Tree 都用不上,
整条查询退化成 work_orders 的 Seq Scan。
生产实测(10.3 万行 / 572MB,pg_stat_statements 46 小时窗口):
- 列表查询 + 统计查询用同一份 filters 并行执行,一次检索打两遍全表;
两者合计占全库 66% 的磁盘读(1.1TB/46h),单次读 26,442 个块(约 211MB)、398ms、返回 1 行。
- 收敛后同库同参数:等值 0.19ms,前缀 30ms(约 2000 倍)。
- 真实统计查询端到端 760ms → 61.6ms。
改动:
1. 平台单号/工单号分支收敛为「等值 + 前缀」,与 order-repo / task-repo 既有写法一致;
工单号含随机小写段(10.2 万行里 7.4 万行非全大写),保留 LOWER() 比较。
2. 迁移 090 补 text_pattern_ops 前缀索引:当前库 collation 为 en_US.utf8,
前缀 LIKE 在默认 B-Tree 上会退化成全索引扫(实测 438ms)。
3. 新增关联单号反查分支:SUB/INV/AS/DT 不在 work_orders 上,
经关联表反查工单 id,四条链路均命中唯一索引。
4. 前端 isExplicitOrderNoSearch 同步补齐(否则搜到的单会被状态页签挡掉、
实时补丁还会把行删掉)。
验证:生产 EXPLAIN 逐条比对 + 14 项仓储单测 + 6 项分类器单测。
2026-10-03 14:27:32 +08:00
yml2213
f0d745ea50
fix(worker-risk): 修掉自动封禁被对账撤销的竞态,以及账号不存在时写假留痕
...
## 一、刚下的封禁会被对账撤销(生产实测)
Tier 3 是「先把 tracker.banned 置 true,再 await 写库」。在写库提交前的窗口里,
并发的另一个请求会走对账分支、读到 status 仍是 active,于是把刚下的封禁撤销掉。
生产日志(worker 28):
17:20:11.361 WARN 封禁标记与数据库不一致,已自动解除并放行 ← 对账把刚下的封禁撤了
17:20:11.388 ERROR 打手账号已被系统自动永久封禁! count=30 ← 27ms 后才写出封禁
后果:刷子能漏进来一个请求;日志还会让运营以为"数据库里已经解封了"。
修法:新增 bannedAt,刚下的封禁在 BAN_GRACE_MS(60s) 内不与数据库对账。
人工解封走 releaseWorkerRiskTracker() 直接删 tracker,不受宽限期影响,处置不会变慢。
## 二、账号不存在时会写下"已永久封禁"的假记录
updateWorkerUser 在账号不存在时返回 null 而**不抛错**,原代码直接把返回值丢弃,
于是照样打出"已被系统自动永久封禁"的日志并写 tier3_autofrozen 留痕。
外挂伪造成随机 uid 时就会命中这条路径,留下一批查不到的假封禁记录。
修法:显式判断返回值,为空则记明确错误并跳过留痕。
## 测试
新增 2 个用例:
- 刚下的封禁在宽限期内不得被对账撤销(锁住上面的竞态)
- 账号不存在时不写"已封禁"的假日志
并更新 2 个原有用例:宽限期内本就不发起对账,原断言(bannedVerifiedAt > 0)已过时。
风控测试 11/11;后端全量 559 测试 556 通过 0 失败。
2026-10-02 17:59:06 +08:00
yml2213
2982819548
fix(worker-risk): 伪造 uid 时风控事件因外键约束写不进去,改为查不到就存 NULL
...
worker_risk_events.worker_id 有指向 worker_users(id) 的外键。
外挂可以伪造 token 里的 uid(中间件只 base64 解 payload、不验签就以该 uid 建跟踪维度),
那种 uid 在 worker_users 里并不存在,直接写入会触发外键约束失败 ——
结果是**最该留痕的伪造流量反而记不下来**,而且因为留痕是 fire-and-forget,
失败只在日志里留一条,界面上完全看不出来。
生产实测(真实库事务内回滚验证):
worker_id=41(真实账号) -> 插入成功
worker_id=99999999(伪造 uid) -> ERROR: violates foreign key constraint
改为用子查询解析 worker_id,查不到即写 NULL;target_key 仍保留原始 uid,
追溯能力不受影响。修复后实测两种情形均写入成功,伪造者 worker_id 为 NULL。
顺带确认:留痕链路本身是通的(后台 release 接口已实际写入过一条 manual_unbanned)。
2026-10-02 17:09:33 +08:00
yml2213
78c676d2b9
feat(worker-risk): 风控后台页面(可调阈值/实时状态/事件留痕/人工封禁解封),并修复解封后仍被 403 的 bug
...
## 修复的 bug(生产已发生)
风控三级封号把 tracker.banned 置为 true 后,banned 检查发生在鉴权之前,
而它只存在于进程内存。后台把账号改回 active 时数据库变了、内存没变,
于是账号在库里已经正常、请求却永远 403,只能等容器重启才恢复。
修法两道:
- **数据库是唯一事实来源**:banned 改为带 30 秒 TTL 的缓存,到点回查
worker_users.status;已解封(或账号已删)则清除标记并放行。
对被判 banned 的 key 最多每 30 秒查一次,即便对方仍以 65 QPS 刷也只是
每 30 秒一次查询。查询失败时**保守维持封禁**,不因数据库抖动放行刷子。
- **后台改状态时主动释放**:review 只允许设 active/rejected/disabled/pending_review
(没有 frozen),所以走到那里就意味着不再由风控冻结,直接释放内存状态。
顺带修掉一个连带缺陷:解封时只清了 violationCount,没清滑动窗口计数,
账号刚恢复就会被上一轮残留的 windowCount 立刻限流、并在 60 秒内二次封禁。
现在整轮窗口状态一并清零。
## 新增后台页面
接单平台 → 风控(/admin/worker-platform?tab=risk-control),三块:
- 阈值配置:6 个阈值可视化编辑,落 app_config_entries,保存即热生效,无需重启
- 当前实时风控:进程内存状态(已封禁/惩罚中/仅记录),支持人工解封、封禁、仅释放内存状态
- 风控事件留痕:worker_risk_events 分页查询,可按类型/IP 筛选
## 关键取舍
只记**显著事件**(二级惩罚、三级封号、人工处置),不记每一次被拒请求。
被刷时实测 45~65 QPS,按请求写库正是最初要解决的问题。实时计数仍走进程内存。
事件写入为 fire-and-forget 并吞异常,留痕失败绝不影响大厅请求。
## 验证
- 新增 6 个用例覆盖解封立即生效、对账自愈、TTL 不重复查库、对账失败保守维持、阈值可配置
- 迁移 089 已在临时库实测:表结构/4 索引/CHECK 约束/外键均正确,非法 event_type 被拒
- 后端 557 测试 554 通过 0 失败;前后端 typecheck/lint/format/build 全绿
2026-10-02 17:05:25 +08:00
yml2213
adaf4ed240
feat(worker-risk): 新增大厅阶梯式风控中间件与鉴权短缓存,惩罚外挂死循环狂刷并支持恶意封号
...
1. 新增 worker-risk-control 阶梯式风控:单打手/IP 限流 3次/秒(超限返 429);1 分钟累计违规 10 次升级为二级惩罚(锁定 5 分钟秒拒并带 Retry-After);惩罚期内持续暴力刷接口累计 30 次自动判定恶意不改,直接封禁打手账号(status='frozen')并作废所有 session;
2. 风控限流前置挂载在 requireWorkerSession 之前:命中限流或处于风控期时,微秒级在内存中基于 JWT payload 与 IP 拦截,彻底杜绝外挂流量穿透到 PostgreSQL 数据库;
3. verifyWorkerSessionToken 增加 3 秒内存校验缓存:并发请求直接复用内存鉴权对象,同一页面并发发出的多个接口请求仅查 1 次数据库,极大减轻连接池争抢压力;
4. 全套新增风控阶梯机制与自动封号自动化测试,后端 549 项测试全绿通过。
2026-10-02 16:30:53 +08:00
yml2213
5c13599dff
perf(file-storage): 限制 libvips 并发、收紧操作缓存,源图只解码一次
...
后端 RSS 在 23 小时内从 194MB 涨到 832MB(限额 1GiB,已两次 OOM kill),
而同期 heapUsed 稳定在 71~101MB,增长全在 V8 堆外:libvips 用 glibc malloc,
每个开发出来的 worker 线程都会催生一个只涨不缩的 arena。
sharp 自带保护——检测到 "glibc 且未设置 MALLOC_ARENA_MAX" 时自动把并发降到 1;
但 .env 已显式设置 MALLOC_ARENA_MAX=2 给 arena 封顶,该保护不再触发(实测
concurrency 由 1 变 4、libvips worker 达 8 个)。故此处必须显式限制并发:
- sharp.concurrency(2):把可能催生 arena 的线程数压到最低
- sharp.cache({memory:16, files:0, items:20}):默认 50MB 收紧到 16MB
(注意 sharp.cache() 不做部分合并,三项必须全给)
- 源图用 clone() 复用,原实现把 sharp(data) 写在循环内导致整图解两遍
输出经比对与改动前完全等价(尺寸/格式/内容类型/字节数一致)。
2026-10-02 15:57:13 +08:00
yml2213
142c4ee141
perf(realtime): 一次广播只序列化一次,不再按订阅者重复 stringify
...
抓包发现单个后台客户端 5 秒收到 981,776 字节 / 336 条事件(约 196KB/s、67 条/秒、
每条平均 2680 字节,最大 6418 字节)。事件构成:work_order.changed 34 条/秒、
order.changed 23 条/秒、admin_notification.changed 7.6 条/秒。
原广播实现在订阅者循环里逐个 `JSON.stringify(payload)` 并重建 SSE 模板串:
客户端越多,同一份载荷就被序列化越多次(后台/打手端都在线时更明显)。
改成先 `buildEventFrame()` 构造一次,循环里只写同一帧:
- broadcast() 只调用一次 buildEventFrame
- writeEvent()(ready 事件与断线补发)复用同一构造函数
- 行为不变,仅去掉重复序列化
受众过滤经核对本来就是正确的(admin 事件只发后台、worker 事件按 workerIds 过滤),
realtime_events 也有自我清理(每 100 条保留最近 500 条,实测 555 行/2MB),
故本次只动序列化这一处。
验证:后端 548 项单测全绿(新增 1 项守卫,禁止 broadcast 内再出现 JSON.stringify)。
2026-10-01 15:46:43 +08:00
yml2213
5092e88618
perf(admin): 打手等级筛选用 UNION 形式、概览加短缓存、静态配置接口加缓存头
...
三处都出自审计清单,改动互不重叠。
1) 打手等级筛选(生产实测 1119ms)
原写法是对 worker_users 的两个相关 EXISTS(接单人 / 拼单参与人),规划器无法用它
驱动索引,只能顺序扫完整张 work_orders。改成先把等级解析成打手 id
(worker_users(level_id) 有索引,等级只有 6 个、解析出几百个 id),再走索引。
注意:**不能**写成 `assigned_worker_id = ANY(...) OR id IN (...)` —— 实测 1852ms,
比原写法还慢(OR 右支要逐行回表取 id)。用 UNION 形式:
wo.id IN (SELECT id FROM work_orders WHERE assigned_worker_id = ANY(...) UNION
SELECT work_order_id FROM work_order_shares WHERE worker_id = ANY(...) AND status != 'cancelled')
实测 61ms(count),语义与原来完全一致(含"空数组=过滤掉全部")。
三个入口 listWorkOrders / countWorkOrders / getWorkOrderStatistics 都先解析;
未解析时保留原相关 EXISTS 兜底。
2) 平台概览短缓存(5 秒)
一次并发 5 条 count(4 条打 work_orders,一条是运营态 CASE),而池只有 8 条连接,
后台刷新时与其他请求互相争抢(抓包显示整批 9 个请求同时发出、它自身 167ms)。
看板计数滞后几秒无感,用 WorkerListCache 缓存并合并并发。
3) 三个静态配置接口加 HTTP 缓存头(private, max-age=300)
接单分类、快手行业店铺、自定义店铺此前没有任何 Cache-Control,每次刷新都真发一次
请求(抓包里 4 个这类请求)。带 private 是因为这些是带鉴权的后台接口。
**dev-mock/status 故意不加**:它是可切换状态,缓存会让"切了开关界面没变"。
验证:后端 547 项单测全绿(新增 4 项守卫,分别锁住 UNION 形式、三个入口都解析、
概览缓存、以及 dev-mock 不得加缓存)。
2026-10-01 15:36:46 +08:00
yml2213
21cd9135a2
perf(worker): 「我的订单」列表改成先取 id 再取详情,排序阶段不再物化宽列
...
打手端我的订单的排序是表达式:
COALESCE(CASE WHEN wo.assigned_worker_id = $1 THEN wo.assigned_at END,
CASE WHEN worker_share.status != 'cancelled' THEN worker_share.created_at END)
DESC NULLS LAST, wo.id DESC
它依赖打手参数与拼单表,**没有任何索引能支撑**,规划器只能把该打手全部可见单
连同宽列(每行约 1.2KB material_json)物化后排序。生产实测(真实 service 路径):
名下 6609 单的打手首访 1112ms、5198 单的 808ms、3140 单的 772ms,每次读约 60MB
(列表缓存 2 秒一失效,所以这个代价每天要重复付很多次)。
改法:该排序走"延迟分页"——第一阶段只 SELECT wo.id(窄投影 + 同一 ORDER BY +
同一 LIMIT/OFFSET),第二阶段用 `WHERE wo.id = ANY($1::bigint[])` 取这一页的详情,
再按 id 顺序还原(ANY 不保证顺序)。行集、顺序、总数语义完全不变。
验证:后端 542 项单测全绿(新增 1 项守卫);生产上先用部署前的 id 顺序留底,
部署后用同一批打手比对首 20 条 id 与总数,并对比首访耗时。
2026-10-01 15:29:49 +08:00
yml2213
987553909d
perf(db): 统计的「状态+日期范围」组合补 created_at 键覆盖索引
...
087 以 id 为键覆盖了统计要读的窄列,但日期条件无法用它驱动扫描
(created_at 不是前导列),规划器因此对 work_orders 做整表 Hash Join:
生产实测该变体每次读 400~700MB —— 缓存热时 283~694ms,缓存有压力时 926ms+
并触发慢查询告警。而"状态 + 近 7/30 天"正是运营最常用的筛选组合。
迁移 088 补一条 created_at 键、覆盖同一批窄列的索引,让规划器改为
Bitmap Index Scan 只取日期范围内的行。生产实测(真实 service 路径):
取 13,974 行 / 执行 75.7ms / 端到端 283ms(含列表查询),慢查询告警消失。
用"单次调用前后块差值"验证:51,521 块,与我 EXPLAIN 的 shared hit 完全一致。
与 087 的分工:无日期筛选走 id 键(index-only),有日期范围走本索引(bitmap)。
Bitmap Index Scan 无法 index-only,INCLUDE 的作用是让 heap 访问只落在日期范围内。
刻意不含 acceptance_json(勾 VIP 待补图时本就有意回表)。
验证:后端 541 项单测全绿(新增 1 项守卫)。
2026-10-01 15:23:57 +08:00
yml2213 and Cursor
b6c0e06f45
style: 补齐筛选统计守卫测试的格式
...
Co-authored-by: Cursor <cursoragent@cursor.com >
2026-10-01 15:05:56 +08:00
yml2213 and Cursor
1239393fc2
perf: 筛选统计去掉 JSON 兜底,并补上 orders 覆盖索引
...
去掉 work_orders 堆访问后,JOIN 会把 orders 整表扫进 Hash Right Join。
领取页在快照新鲜时不再打上游;快手出网统一 10 秒超时。倒计时改为自适应间隔。
Co-authored-by: Cursor <cursoragent@cursor.com >
2026-10-01 15:05:38 +08:00
yml2213
2d75be702d
perf(db): 补齐三处"只差索引"的热查询缺口
...
审计方式:pg_stat_statements 按"每次调用读取块数"排序 + EXPLAIN (ANALYZE, BUFFERS) 复现,
每条都先在线上并发建索引验证有效再固化(生产上索引已存在,IF NOT EXISTS 跳过)。
① 后台订单列表:COALESCE(paid_at, created_at) 同时出现在 WHERE 与 ORDER BY,
函数包裹列非 sargable —— 默认"近 7 天"视角本身就会全表扫 orders 再排序。
实测 26 万行/443MB:Parallel Seq Scan 读 256MB、577ms
→ Index Scan 读 0.3MB、2.05ms(约 280 倍)。
② 重跑快手补单:NOT EXISTS 打在 work_orders.order_id 上,而该列此前**没有索引**,
反连接只能 Hash Anti Join —— 为几十个候选 id 顺序扫完 500MB 宽表,LIMIT 100 拦不住。
实测 188MB、120ms → Index Only Scan 1.2ms(约 100 倍)。同时补 orders(order_status)。
③ 快手凭证"同步源"列表的 (oid, updated_at DESC, id DESC):29.6 万行/678MB,实测 568MB、5.5 秒。
仅建索引不足以改变计划(查询写法要求先物化全部 oid,见审计报告),但它是后续
改写查询的前提,先建好避免再等一次大表建索引。
验证:后端 534 项单测全绿(新增 1 项守卫)。
2026-10-01 13:48:05 +08:00
yml2213
92d7887108
perf(db): 运营态计数/统计加覆盖索引,消除 248MB/次的全表扫
...
pg_stat_statements 重置后 25 分钟窗口显示,work_orders 相关查询里每次调用读取量最大的
全是"按运营态筛选"的形状:
SELECT COUNT(*) FROM work_orders wo LEFT JOIN (<拼单汇总>) WHERE <运营态 CASE>
87 次调用、每次约 248MB、均 347ms
筛选统计的同形查询多次,248~545MB/次
根因:运营态不是存储列而是算出来的 CASE(依赖 status/sharing_enabled/sharing_total_quantity
+ 拼单汇总),规划器只能逐行求值,EXPLAIN 显示 "Rows Removed by Filter: 95923" ——
把 9.6 万行、约 533MB 的宽表整张读出来再丢掉。
迁移 085 给 CASE 用到的三列建覆盖索引(约 4MB,堆表的 1/130):
同一查询实测 248MB → 22MB、184ms → 75ms(IO 降 11 倍)。
status 本来就有索引、状态流转本来也走不了 HOT 更新,故不额外增加写放大。
验证:后端 533 项单测全绿(新增 1 项守卫);生产真机 EXPLAIN (ANALYZE, BUFFERS) 前后对比。
2026-10-01 13:30:06 +08:00
yml2213
3ab897963d
perf(db): 打手端「我的订单」概览加覆盖索引与短缓存
...
这是数据库累计耗时第一的热路径:32 天 130 万次调用(0.5 次/秒),平均 99.8ms,
但名下订单多的打手会退化到 4.5~9 秒。EXPLAIN (ANALYZE) 定位到根因:
查询只用到 8126 个工单 id,规划器却选了「work_orders 全表顺序扫 + Hash Join」——
为几千个 id 扫 9.6 万行宽表(500MB),单这一步 4.1 秒。
1. 迁移 084:给查询真正需要的列建覆盖索引(id + INCLUDE status/assigned_worker_id/
reward_amount/acceptance_json)。把那次全表扫变成 index-only scan,代价从 31015 降到约 5k,
规划器自然改选索引;索引 34MB,是堆表的十五分之一。生产实测同一查询 14.5 秒 → 1.5 秒(冷缓存)。
试过用 LATERAL 强制回表,规划器会把它拉平成同样的 Hash Join,所以只能从索引侧解决。
迁移在事务内执行,故用普通 CREATE INDEX(不用 CONCURRENTLY)。
2. 概览读取加 5 秒短缓存:把"计算次数"与"页面轮询次数"解耦。
只缓存读取——会话校验与 settleOverdueWorkOrders(写副作用)每次照常执行,否则会漏结算。
验证:后端 532 项单测全绿(新增 1 项守卫,覆盖索引列与缓存位置都纳入断言)。
2026-10-01 13:12:42 +08:00
yml2213
2f2d44f601
perf(admin-worker): 筛选统计加 10 秒短缓存,把成本增长与访问量解耦
...
金额落到派生列后,统计查询从生产实测 3.5~4.4 秒降到 ~400ms(约 9 倍),
但仍要扫全部命中工单(9.6 万行宽表 + 关联 orders + 拼单汇总),成本随工单量线性增长,
数据再翻几倍会重新撞上 500ms 慢查询线。
因此按"筛选指纹"缓存 10 秒并合并并发请求(WorkerListCache.getOrSet):
单位时间内的计算次数被封顶在 TTL 上,不再跟着刷新次数走;多人同时刷列表只会算一次。
代价是统计与分页总数最多滞后 10 秒,期间前端已由实时事件快照就地修正 statistics,
刷新后自动对齐。
指纹覆盖 statuses/status/workOrderId/keyword/workerSharingId/workerId/workerLevelIds/
categoryId/acceptanceMode/giftPhase/vipEvidencePending/useOperationalStatus/创建日期,
并加静态守卫防止后续漏字段导致不同筛选互相污染。
验证:后端 531 项单测全绿(新增 1 项守卫)。
2026-10-01 13:02:09 +08:00
yml2213
1ca15e9025
perf(db): 统计金额落到列上、会话刷新节流、补齐保留与内存治理
...
背景:生产 32 天内 >500ms 慢查询从 2.4k/天涨到 1.3 万/天(09-26 尖峰 14.3 万),
pg_stat_statements 显示后台工单列表族占 57%、打手端 visible_orders 占 27%;
同时 backend 容器十天从 194MB 涨到 754MB(768MB 限额的 98%),贴顶后整体变慢。
1. 新增迁移 083:work_orders 增加派生列 source_order_amount_fen,并从
material_json.source.orderAmountFen 回填(只认 15 位以内整数,与 JS/SQL 口径一致)。
统计查询原来每行要解析 1.2KB JSON(全表约 132MB)且同一表达式求值两次,
生产实测 3.5~4.4 秒;改走派生列后 COALESCE 短路,未命中列的行才回退 JSON/orders.total_amount,
口径与改造前完全一致。写入点:createWorkOrder / updateWorkOrder 各一处,并加静态守卫。
2. worker_sessions.last_seen_at 从"每个请求都写"改为"5 分钟才写一次":
32 天被写了 3300 万次(占 DB 总时间 2.8%,并堆出 14% 死元组)。仍用一条语句返回鉴权有效性
(EXISTS target AS valid),不能因为不写就让上层 401。
3. 保留策略补上 work_order_events(71.7 万行 / 269MB,此前完全没纳入清理,只增不减),
新增配置 retention.workOrderEventDays(默认 180 天)与 WORK_ORDER_EVENT_RETENTION_DAYS 出口。
4. 内存治理:compose 后端限额 768m→1g 并显式设置 V8 老生代上限(--max-old-space-size=768),
避免贴 cgroup 上限后 GC 抖动;新增每 10 分钟的进程内存快照日志(rss/heapUsed/heapLimit),
用于区分"正常 GC 曲线"与"真泄漏"。
5. 限流桶加 5 万条硬上限:key 里含请求体字段(collect 的 orderNo)时可被构造成高基数,
只按过期清理挡不住内存增长。
验证:后端 530 项单测全绿(新增 11 项,含派生列/会话/保留的静态守卫与解析函数单测)。
2026-10-01 12:55:09 +08:00
yml2213
9573acc59d
fix(consignment): 问题单「退回大厅」也要解绑寄卖现货
...
问题单处置的「退回大厅」只把订单置回 open,没有解绑被本工单占用的寄卖现货,也没摘 material 上的
寄售标记。后果是卖家端「我的寄卖」把这张单显示成「履约中 + 关联订单状态已变化,请联系客服核对」
(状态机只认 in_progress / problem / pending_acceptance,open 落到兜底分支),而现货永久停在
consumed 占库不还、既不再参与匹配也没有释放入口。
其余同义路径(后台退回大厅、超时重开、打手退单通过)本来都会解绑,只有问题单这条漏了,属口径不一致。
现在把解绑条件从「仅取消」扩到「取消或退回大厅」;「退回打手重做」保持占用(货还在这一单上履约)。
调用位置仍在状态 UPDATE 之前——该 UPDATE 在 open/cancelled 时会清空 friend_added_at,
清完再解绑会把已确认加好友的货误判成可回池(二次出货)。
验证:真实库跑通两条路径——退回打手重做(现货仍 consumed+绑定的、标记保留、打手端仍「已匹配-待加好友」)
与退回大厅(现货回 in_stock、解绑、标记摘除、打手端变「已通过(在库待匹配)」);后端 520 项单测全绿。
2026-10-01 12:22:56 +08:00
yml2213
40ee36d72a
fix(consignment): 修复寄卖取消解绑、SUB 单号写空、拼单门禁与后台工单列表实时乱跳
...
1. 问题单处置「取消」后打手信息不消失:resolveProblemWorkOrder 的取消分支从未解绑寄卖现货,现货永久停在 consumed 占库不还,工单 material 上的寄售标记残留又让列表继续显示「供货打手」。取消时补上解绑;调用必须排在状态 UPDATE 之前——那条 UPDATE 会清空 friend_added_at,清完再解绑会把已确认加好友的货误判成可回池(二次出货);
2. 解绑函数在没有现货可解绑时直接 return,跳过了摘标记,于是「库存行早已不存在」的老帖永远摘不掉寄售标记(列表一直显示供货打手)。现在无论是否解绑到现货都摘标记,痕迹保留在 material.consignmentHistory 的 detachReason 里;
3. 内部现货履约写入的 sourceSubmissionNo 恒为空:取的是占库 `UPDATE ... RETURNING *` 的行,只带回本表列,没有 join 出来的 submission_no,导致工单列表把所有寄卖单都显示成「老单无提报单号」,SUB 单号这个唯一锚点丢失。改取带 join 的预读库存(内部现货资料、指派工单资料、指派事件留痕三处)。存量老单按业务口径不回填;
4. 后台接单工单列表实时乱跳:SSE 快照补丁按数组下标读 queryKey,面板 key 加了字段后下标全错(page 被读成 targetWorkOrderId → 任何页都被当成第 1 页),且完全没判「内部寄售匹配」等筛选,于是任意一条不在当前页的老单只要发生变更就被插到列表顶部。改为结构化 queryKey + 三态判定(match 才允许插顶、mismatch 才允许移除、其余只原地更新),插入还要求 page===1 且工单 id 大于当前首行;状态比对改用 rawStatus(快照的 status 是运营态,拼单母单会被显示成 in_progress),分类筛选因快照不带 parentCategoryId 归入不可证伪;
5. 新增「寄卖只做整单」硬门禁:已有人参与的拼单工单不参与现货匹配与库存提醒,履约入口一律返回 work_order_sharing_not_consignable,前端「手动匹配寄卖」入口按同一口径收敛;开了拼单但无人参与的整单不受影响,避免过度拦截。
验证:后端 520 项单测(517 pass / 3 skip)、前端 78 项全绿;并对真实开发库跑通问题单取消三类场景(老帖无库存行 / 回库 / 已加好友作废)、退回打手与退回大厅的反向用例(不得解绑)、履约写入真实 SUB 单号、拼单门禁四入口 + 未过度拦截反向用例。
2026-10-01 12:17:00 +08:00
yml2213
1636616ec1
feat(worker-platform): 寄卖价格改由接单模板只读定价,我的寄卖支持服务端搜索与分页
...
1. 寄售价不再由卖家填写:打手端改为只读展示模板价(与履约押金同款只读框),服务端在发布时按同一套规则复算金额,保证展示口径与落库口径一致,直连接口也无法随意定价;
2. 模板缺档按「最接近的已配置档位」报价、距离相同取更低档:后台只配了 1/3/7 钻时,号主出 2 钻报 1 钻价、出 6 钻报 7 钻价,不再因为中间档没配价而拿不到价格;
3. 我的寄卖列表把分组、关键字、翻页全部下沉到 SQL:新增 listWorkerConsignmentPage 用 UNION ALL 合成「提报单行 + 无提报单库存兜底行」,CASE 推导 待审核/履约中/已完成 分组,返回当前分组 total 与三个分组的角标计数;
4. 关键字覆盖寄卖单号、规格名、系统/区服/系列/钻级、卖家角色名与数字 ID、联系方式、买家昵称与游戏 ID(读取 work_orders.material_json.collect.fields 的多种历史键名);
5. 前端搜索框 300ms 防抖后才请求服务端、换分组或换关键字自动回到第 1 页、列表底部增加分页器,并移除原来固定 pageSize 200 的隐形截断;
6. 新增仓库层与匹配层单测,其中包含 SQL 分组 CASE 与 domain/consignment-item-stage.ts 的 group 映射交叉断言,防止两处状态机漂移;后端 516 项、前端 66 项测试全部通过。
2026-10-01 11:07:28 +08:00
yml2213 and Cursor
9298b31b9b
feat(worker-platform): 增加寄卖白名单总开关,关闭后全员可发布
...
资金配置与打手列表可一键切换白名单模式;发布校验、打手端入口按总开关放行。同时收紧未开通判定、关权限确认和开通弹窗文案。
Co-authored-by: Cursor <cursoragent@cursor.com >
2026-09-21 14:23:00 +08:00
yml2213
cb8b249e57
feat(worker-platform): 实现打手寄卖白名单权限控制与存量发布打手平滑过渡
...
1. 新增数据库迁移 082_worker_consignment_whitelist.sql,worker_users 表增加 can_consign 与 consignment_activated_at 字段,存量在 consignment_submissions 提报过的打手默认自动开启;
2. 打手端提报接口 publishWorkerConsignment 增加白名单权限服务端强校验拦截(403 consignment_permission_required);
3. 管理后台打手管理表格增加「寄卖权限」实时 Switch 开关列与打手详情抽屉权限展示,支持客服即时开通或关闭打手寄卖权限;
4. 打手端我要寄卖页面在未开通权限时展示专属白名单引导卡片,一键呼起联系客服开通寄卖弹窗;
5. 补充寄卖权限拦截与路由单测,前端59项单测与后端507项单测全部通过。
2026-09-21 13:30:33 +08:00
yml2213
c80bd0e337
fix(worker-platform): 修复寄售匹配工单详情中卖家上架截图丢失问题,并收敛手动匹配为内部现货直接履约
...
1. 修复文件归一化器 normalizeUploadedFiles 遇到纯字符串 URL 数组被误过滤为空的问题,自动提取 objectKey 并构造规范 UploadedFile 结构;
2. 优化工单展示映射器 resolveWorkOrderEffectiveBoosterMaterial:当工单资料中截图为空时,自动回退并加载寄售卖家上架/主页截图;
3. 前端管理后台详情抽屉与打手端详情抽屉支持读取 detailOrder.gift.boosterMaterial.screenshots 与 consignmentInfo.screenshots 降级兜底渲染;
4. 手动匹配弹窗与发布拦截弹窗全面收敛为单一标准的【内部现货直接履约】,废除旧版指派打手带来的重复扣押金与二次填资料问题;
5. 新增 consignment-booster-material 单元测试,更新寄卖库存优化现状与进度追踪文档,前后端全套测试通过。
2026-09-21 11:35:38 +08:00
yml2213 and Cursor
040b3db84c
fix(finance): 移除资金审核取消按钮,避免提现冻结资金未退回
...
Co-authored-by: Cursor <cursoragent@cursor.com >
2026-09-20 21:59:29 +08:00
yml2213
c115f5e944
fix(claim): 修复后台手动关闭任务后领取链接未失效的缺陷
...
1. 鉴权层激活 CLAIM_TERMINAL_STATUSES:在 getClaimContext 中增加 task 状态校验,对 closed 与 expired 终态任务实施 410 拦截并自愈更新关联 Token;
2. 仓储层新增 closeClaimTokensByTaskId():支持按 taskId 批量更新所有非 closed 状态的 Token 为 closed,彻底杜绝多 Token 或重发链接导致的历史活跃残留;
3. 后台关闭任务流程闭环:closeAdminTask 显式触发 closeClaimTokensByTaskId,确保任务关闭时关联领取链接立即失效;
4. 补充 CLAIM_TERMINAL_STATUSES 单元测试与完整排查分析方案文档。
2026-09-20 14:06:48 +08:00
yml2213
f07e4087ad
feat(consignment): 优化寄售仓库与接单工单双向手动匹配,支持工单号与提报单号检索与强制匹配
...
1. 寄售仓库手动匹配订单扩充支持 open 状态工单,增强 WO 工单号与纯数字的模糊/前缀搜索,修复工单搜不到的问题并在卡片高亮展示内部工单号与状态;
2. 接单工单操作列全面支持【手动匹配寄售】,允许从接单工单主动调起寄售匹配弹窗;
3. 寄售现货筛选支持通过 SUB 提报单号、INV 仓库号、供货打手等快速搜索;
4. 完善规格冲突下的强制匹配确认与必填原因提交机制,确保履约全流程留痕合规。
2026-09-19 17:59:05 +08:00
yml2213
e5cc7cadef
style: 格式化后端工单查询与展示映射模块代码风格
2026-09-19 14:41:16 +08:00
yml2213
2c120bd3e5
fix(worker-platform): 接单平台工单详情抽屉完整展示寄售手动匹配与指派订单的卖家资料
...
1. 修复管理后台「接单平台 -> 接单工单」抽屉中,手动匹配或指派寄售库存履约的工单因 booster_material_json 为空导致卖家资料全部显示 '-' 的问题;
2. 后端在履约及指派时将卖家角色昵称、数字ID、联系方式、手机号、备注及上架凭证截图完整写入工单 material.consignment;
3. 后端工单查询及展示映射层增加自动回退与批量库存补充解析机制,确保历史与新增的寄售工单均能准确解析出卖家角色名、ID、区服、系统与截图;
4. 前端工单抽屉卡片优化寄售订单的资料展示与阶段标签,清晰展示卖家角色名、游戏ID、联系方式(微信/QQ/手机)、提报/仓库单号及上架截图。
2026-09-19 14:33:56 +08:00
yml2213
d40b5a4c1c
feat(worker-platform): 优化打手我的寄卖卡片资料展示,前置展示打手自身上架账号资料
...
1. 后端 consignment-worker-service 在我的寄卖列表中解析并返回打手上架时填写的 sellerInfo(角色名、数字ID、区服系统、微信号/QQ、寄卖留言等);
2. 前端新增 ConsignmentSellerInfoBlock 卖家资料区块,提供角色名/ID高亮与一键复制功能;
3. 寄卖卡片布局升级为前置展示【我的寄卖账号资料】、后置展示【买家加好友资料】,多号上架时号主与买家清晰对应,彻底杜绝登错号加错人。
2026-09-19 14:19:35 +08:00
yml2213
0b4ee964ed
feat(pdd): 引入拼多多原始订单暂存仓 pdd_raw_orders 与两段式解耦流水线
...
1. 数据库新增 081_pdd_raw_orders.sql 迁移,创建 pdd_raw_orders 原始暂存表及索引;
2. 新增 pdd-raw-order-repo 仓储,支持原汁原味批量暂存、pending 单据消费、状态回填与重置;
3. 重构 pdd-sync-service 为采集暂存(Staging)与业务进单(Ingestion)两段式解耦流水线,实现断点续传;
4. 管理后台路由接入 raw-orders 原始流水查询及 replay 一键历史单重放 API。
2026-09-19 11:58:28 +08:00
yml2213
5e59a5fdb1
fix(order-service): 修复包含 Emoji 和孤立代理字符的订单在 PostgreSQL jsonb 下报错导致死循环重试进单的问题
...
1. 在 utils/json.ts 新增 sanitizeWellFormedUnicode、deepSanitizeUnicode 和 safeJsonStringify,自动清理空字节 (\0) 并将畸变孤立 UTF-16 代理字符 (Lone Surrogates) 安全转换为规范字符,杜绝 PostgreSQL jsonb 解析错误;
2. 拼多多订单映射器与通用进单服务 (order-service) 全面接入 Unicode 深度清洗与安全序列化,确保买家昵称/留言中的各类特殊 Emoji 和生僻字符平滑落库;
3. 数据库仓储层 (order-repo / order-item-repo) 针对 raw_payload_json、spec_json、item_snapshot_json 等 jsonb 字段增加兜底安全序列化保护;
4. 补充 utils/json.test.ts 与 pdd-order-mapper.test.ts 单元测试,覆盖畸变字符与空字节场景。
2026-09-19 11:40:02 +08:00
yml2213
67d68d37f6
feat(worker-platform): 对齐生产环境分类树并增强寄卖匹配多品类硬隔离门禁
...
1. 接入生产分类树体系:将 ItemCategoryType 对齐生产 work_categories 真实分类(苏尔南手枪、苏尔南钻石、特种兵密钥、地铁逃生、大转盘车皮、蛋仔派对、王者荣耀、第五人格、人气礼物等);
2. 强化跨游戏与跨子类隔离:精准拆分地铁逃生下的苏尔南钻石与苏尔南手枪,杜绝钻石充值单误匹配手枪现货;
3. 严格品类硬隔离门禁:对齐 matchPeacekeeperOrderWithInventory 与 describeConsignmentSpecDifferences,品类冲突时彻底拒绝匹配并在非精确列表中置为 conflict 沉底;
4. 补充全品类匹配与隔离单元测试,覆盖手枪、密钥、车皮、钻石、跨游戏等测试用例。
2026-09-19 10:46:18 +08:00
yml2213
8631100d07
style: 统一代码格式化与代码规范
2026-09-19 10:41:15 +08:00
yml2213
c62c8c2cad
feat(worker-platform): 增加寄卖匹配品类隔离门禁,杜绝手枪跨品类误匹配密钥礼盒
...
1. 新增 detectItemCategory 与 formatItemCategoryLabel 品类主体识别器(覆盖苏尔南手枪/枪皮、特种兵密钥、爆破型肩炮、专属礼盒、改名卡、点券等类目);
2. 寄卖匹配比对中引入强品类隔离门禁,在宽松模式下亦严禁跨品类误判为非精确匹配;
3. describeConsignmentSpecDifferences 补充品类级差异冲突说明,非同品类订单自动沉底;
4. 补充专项单测并保证全量测试与构建通过。
2026-09-19 10:40:02 +08:00
yml2213
439888631f
fix(worker-platform): 内部寄售现货履约发单金额对齐寄卖成本价并补充留痕追溯
...
1. 内部现货履约时原子更新工单 reward_amount 为寄卖结算成本价,解决工单列表显示旧规则价与寄卖成本冲突问题;
2. 工单资料 material.consignment 与工单事件 flow 补充 previousRewardAmountFen/updatedRewardAmountFen/costPriceFen 等价格变更留痕;
3. 撤销寄售履约且退回未指派状态时支持自动还原原工单发单金额并在撤销事件中记录还原明细;
4. 新增 consignment-fulfillment-price-trace.test.ts 专项单测并通过全量测试回归。
2026-09-19 10:22:09 +08:00
yml2213
f816f57eb0
fix(worker-platform): 补齐寄售凭证类型导入与平台单号测试格式化
...
1. 修复 consignment-worker-service.ts 缺少 WorkOrderRow 类型导入的问题;
2. 规范化 platform-order-id.test.ts 与 worker-platform-service.test.ts 代码风格。
2026-09-18 19:40:29 +08:00
yml2213
d6523635ea
fix(worker-platform): 优化工单号精准检索与跨状态流转展示,修复状态更新后工单消失问题
...
1. 后端查询重构:新增 isWorkOrderNoLookupKeyword 工单号前缀与忽略大小写精准索引匹配,优化后端 work-order-query-repo 的工单号检索路径;
2. 前端单号智能穿透:管理员输入 WO... 等精准单号查询时,自动放宽细粒度排他状态/阶段过滤,避免加好友/验收等状态流转后工单因旧状态筛选脱靶消失;
3. 搜索栏交互升级:输入框清空即时重置关键词与页码,支持回车/按钮/清除全方位无缝触发;
4. 详情抽屉与多面板联动:增加 ordersQuery 数据更新对 detailOrder 的自动同步,并将 refreshAll 广播范围覆盖至寄售仓库与统计指标。
2026-09-18 19:36:17 +08:00
yml2213
ace4791005
feat(consignment): 寄卖物品已验收后隐藏退单回库按钮并拦截退库
...
1. 寄卖库存管理面板中,已验收通过的现货记录不再展示「退单回库」按钮;
2. 后端 returnAdminConsignmentInventory 接口增加校验,若关联工单已验收结算则拒绝退库,后续问题引导走售后流程。
2026-09-18 18:43:22 +08:00
yml2213
c17b8a78d5
fix(worker-consignment): 修复卖家提交赠送截图后在接单平台无法回显的问题
...
1. 后端 listWorkerConsignments 接口从关联工单验收资料中提取 giftEvidenceImages、giftEvidenceNote、giftEvidenceSubmittedAt 并在打手端寄卖列表中下发;
2. 接单平台「我的寄卖」卡片新增已提交赠送凭证展示模块,支持展示缩略图、张数统计、大图预览及赠送备注;
3. 优化提报单与库存、工单的 SQL 关联兼容双向绑定,确保后台与前台均能稳定查到赠送凭证。
2026-09-18 18:35:15 +08:00
yml2213
8228776b8c
fix(files): 修复 304 缓存未销毁流导致的 S3 连接池泄漏与图片挂起卡死
2026-09-17 21:03:20 +08:00