这是一份双层文档:前半部分供领导在 5 分钟内理解业务、风险和改造顺序;后半部分供研发、测试和运维沿入口、字段、日志和代码证据继续下钻。
一张图看懂当前业务
flowchart LR
classDef source fill:#fff2cc,stroke:#b8860b,color:#222;
classDef dgj fill:#d9eaff,stroke:#3977b8,color:#111;
classDef event fill:#ffd6d6,stroke:#bb3030,color:#111;
classDef fact fill:#dff4df,stroke:#3c8a3c,color:#111;
classDef projection fill:#ffe3bf,stroke:#c66a00,color:#111;
classDef downstream fill:#eadcff,stroke:#7546a8,color:#111;
subgraph CHANGE["业务变化来源"]
PAGE["商品管理页面<br/>新增、编辑、删除、启停、导入"]:::source
ITEM["ItemCenter<br/>价格、限采、销售限价"]:::source
OPS["OpsCenter / GLB OMS<br/>货主销售区域"]:::source
INV["库存事实<br/>inventory_real_time"]:::fact
end
subgraph EXEC["DGJ2 写入入口"]
HTTP["6 个 HTTP<br/>Inventory / Invlocation"]:::dgj
MQ["3 类 MQ 事件<br/>ItemCenterNotify / OpsCenterNotify"]:::event
JOB["1 个后置 Job<br/>ItemCenterJob"]:::dgj
CLI["2 个主同步 CLI<br/>商品快照 / 库存增量"]:::dgj
FIX["6 个修复入口<br/>索引、价格、限采、描述"]:::dgj
WRITER["统一 Mongo 写入层<br/>MaterielCacheStationSer"]:::dgj
end
subgraph DATA["事实与投影"]
FACTS[("MySQL / ItemCenter<br/>商品、库存、价格与规则事实")]:::fact
MONGO[("Mongo<br/>dgj_goods_sid_{sid}<br/>报价商品读模型")]:::projection
DERIVED[("Elasticsearch / Redis<br/>搜索投影与差集基线")]:::projection
end
subgraph QUOTE["报价读取链"]
BASE["Basedata<br/>站点商品"]:::downstream
SALE["Sale Service<br/>报价聚合"]:::downstream
VIN["VinAudit<br/>VIN / 车型候选"]:::downstream
PURCHASE["Purchase<br/>在途数量"]:::downstream
PRICE["Price API<br/>客户价 / 指导价"]:::downstream
UI["商品报价页面<br/>基础信息 + 价格 + 库存"]:::source
end
PAGE --> HTTP
ITEM --> MQ
OPS --> MQ
INV --> CLI
HTTP --> FACTS
MQ -->|"价格 / 限采先落消息表"| JOB
MQ -->|"区域 / 限价直接同步"| WRITER
JOB --> FACTS
CLI --> FACTS
FIX --> WRITER
FACTS --> HTTP
FACTS --> CLI
HTTP --> WRITER
JOB --> WRITER
CLI --> WRITER
WRITER ==>|"按 SID 写商品快照"| MONGO
WRITER -. "同步或重建" .-> DERIVED
MONGO --> BASE --> SALE --> UI
VIN --> SALE
PURCHASE --> SALE
PRICE --> SALE
四个汇报结论
- 商品报价是 DGJ2、Sale、Basedata、VinAudit、Purchase、ItemCenter 和价格接口共同组成的跨微服务聚合链,不是单体页面查询。
dgj_goods_sid_{sid}是为报价读取构建的 Mongo 商品快照,不是最终事实源;MySQL、ItemCenter 和库存事实决定正确结果。- 已从当前代码确认 6 个 HTTP、3 类 MQ、1 个后置 Job、2 个主同步 CLI、6 个修复入口会直接或间接影响商品 Mongo。
- 高可用建设应按“先可观测、再可靠、再解耦、最后治理历史脚本”推进,而不是继续增加无审计的单点修复入口。
当前梳理形成的业务能力
| 过去的问题 | 当前形成的能力 | 价值 |
|---|---|---|
| 商品搜不到时难判断是商品、价格还是库存 | 建立字段生命周期和症状反查路径 | 减少跨团队猜测和无效重跑 |
| 三条 MQ 尾部相似,容易误判为同一业务 | 明确触发原因、Consumer、消息表和最终字段 | 避免错误修改和错误影响面 |
| Mongo 写入口散落在接口、MQ、CLI、修复脚本 | 建立稳定入口清单和公共写入层证据 | 修改前可完整评估写入方 |
| 接口成功被理解为所有投影成功 | 明确 MySQL、Mongo、ES、Redis 的提交边界 | 为补偿和一致性治理提供依据 |
| 历史脚本可直接修改大量数据 | 识别 die、无条件覆盖、只增不减风险 | 降低人工修复导致二次事故的概率 |
报价同步主链
sequenceDiagram
participant UI as "商品报价页面"
participant DGJ as "DGJ2 /sale/Offer/searchList"
participant Sale as "Sale Service /offer/search/list"
participant Vin as "VinAudit"
participant Base as "Basedata"
participant Mongo as "Mongo dgj_goods_sid_{sid}"
participant Inv as "Inventory Center"
participant Pur as "Purchase Service"
participant Price as "Price API"
UI->>DGJ: VIN + customer_id + category_id + page
DGJ->>Sale: 转发并补充登录态
Sale->>Vin: 查询 VIN / 车型候选 SKU
Sale->>Base: 查询 SID 商品与状态
Base->>Mongo: 读取基础字段与状态投影
Sale->>Inv: 查询可用/优良品库存
Sale->>Pur: 查询在途和未交数量
Sale->>Price: 查询客户价、指导价、现金价
Mongo-->>Base: 商品读模型
Vin-->>Sale: 车型候选
Base-->>Sale: 站点商品
Inv-->>Sale: 库存
Pur-->>Sale: 在途
Price-->>Sale: 价格金额
Sale-->>DGJ: 商品 + 库存 + 在途 + 价格 + 标签
DGJ-->>UI: 报价列表
POST /sale/Offer/searchList 本身不写商品 Mongo。它读取已经维护好的商品快照,并聚合多系统结果;因此 Mongo 正确不代表最终价格、库存或在途一定正确。
三条 MQ 的业务差异
flowchart TB
classDef source fill:#fff2cc,stroke:#b8860b,color:#222;
classDef dgj fill:#d9eaff,stroke:#3977b8,color:#111;
classDef mq fill:#ffd6d6,stroke:#bb3030,color:#111;
classDef db fill:#dff4df,stroke:#3c8a3c,color:#111;
classDef projection fill:#ffe3bf,stroke:#c66a00,color:#111;
subgraph P["MQ-01|价格生效 / 失效"]
direction LR
P1["ItemCenter<br/>价格配置变化"]:::source --> P2(("RabbitMQ")):::mq
P2 --> P3["ItemCenterNotify<br/>保存 SID + 消息类型"]:::dgj
P3 --> P4[("t_bs_mq_message<br/>WAIT")]:::db
P4 --> P5["ItemCenterJob<br/>领取并合并 SID"]:::dgj
P5 --> P6["findPriceSkuList<br/>当前有价 SKU"]:::source
P6 --> P7["Mongo / ES / Redis<br/>noPricingFlag"]:::projection
end
subgraph R["MQ-02|货主销售区域变化"]
direction LR
R1["OpsCenter / GLB OMS<br/>区域配置变化"]:::source --> R2(("RabbitMQ")):::mq
R2 --> R3["OpsCenterNotify<br/>Consumer 内直接处理"]:::dgj
R3 --> R4["AdminModel<br/>归一化主站 SID"]:::dgj
R4 --> R5["findPriceSkuList<br/>重新计算适用 SKU"]:::source
R5 --> R6["Mongo / ES / Redis<br/>noPricingFlag"]:::projection
end
subgraph L["MQ-03|指导价 / 销售限价变化"]
direction LR
L1["OpsManager → ItemCenter<br/>限价规则变化"]:::source --> L2(("RabbitMQ")):::mq
L2 --> L3["ItemCenterNotify<br/>Consumer 内直接处理"]:::dgj
L3 --> L4[("MySQL<br/>销售限价关系")]:::db
L4 --> L5["Mongo / ES<br/>is_hot / is_limit_price"]:::projection
end
P7 ~~~ R1
R6 ~~~ L1
| 对比项 | 价格生效/失效 | 货主销售区域 | 销售限价 |
|---|---|---|---|
| 触发原因 | 价格配置变化 | 价格适用区域变化 | 指导价/限价规则变化 |
| DGJ2 入口 | ItemCenterNotify | OpsCenterNotify | ItemCenterNotify |
| 消息表中转 | 有,t_bs_mq_message | 无 | 无 |
| 最终字段 | noPricingFlag | noPricingFlag | is_hot/is_limit_price |
| 页面含义 | 商品是否有适用价格 | 区域变化后是否仍有适用价格 | 是否命中销售限价 |
| 易错认知 | 事件不是最终 SKU 明细 | 不更新价格金额 | is_hot 在此不是热销 |
区域变化最终更新 noPricingFlag,是因为区域属于价格适用条件:DGJ2 重新拉取 ItemCenter 当前有价 SKU,仍有价置 0,已无价置 1;实际价格金额仍由报价价格链获取。
关键字段与页面影响
| 字段 | 回答的问题 | 主要事实来源 | 页面影响 |
|---|---|---|---|
disable | 商品是否被站点停用 | DGJ2 商品扩展事实 | 停用商品退出候选 |
noPricingFlag | 是否存在适用价格 | ItemCenter 当前有价 SKU | 1 时可能不报价或退出候选 |
buyLimit | 是否限制采购 | ItemCenter/MySQL 限采关系 | 采购候选或下单受限 |
is_hot | 是否命中销售限价 | 有效销售限价关系 | 执行/展示限价;不是热销标签 |
qty/fineQty | 总库存/优良品库存 | inventory_real_time 汇总 | 库存展示与商品可用性参考 |
标识不可混用:sid 路由服务站和 Mongo collection;id/invId 是 DGJ2 商品键;skuId 是 ItemCenter 跨系统商品键。
入口清单与计数口径
| 入口组 | 数量 | 代表入口 | 主要影响 |
|---|---|---|---|
| HTTP 接口 | 6 | Inventory::add/update/updateGoodsKz/delete/disable、Invlocation::saveGoods | 完整商品、描述、删除、停用、导入 |
| MQ 业务事件 | 3 | 价格、货主区域、销售限价 | noPricingFlag/is_hot |
| MQ 后置 Job | 1 | ItemCenterJob | 真正处理价格/限采消息表并写投影 |
| 主同步 CLI | 2 | 商品完整快照、库存增量 | 完整商品、qty/fineQty |
| 修复脚本 | 6 | GoodsCache、Import、GoodsDescUpdate | 索引、价格标识、限采、描述 |
| Redis 辅助脚本 | 1,不计 Mongo 写入口 | GoodsCacheFlush | 只读 Mongo、重建 Redis 集合 |
MQ 价格事件与 ItemCenterJob 是同一业务链的前后半段,不应解释成两次独立业务变化。
多入口请求链路
| Caller 与入口 | 请求/上下文 | Controller / Consumer | Service / Provider | 汇合键 | 最终可查询事实 |
|---|---|---|---|---|---|
| 商品维护页面 | 商品表单 + 登录 SID | Inventory/Invlocation | InventoryService | sid + invId | MySQL 商品事实 + Mongo 商品快照 |
| ItemCenter 价格事件 | routing key + SID | ItemCenterNotify | ItemCenterJob + findPriceSkuList | msgId + sid + skuId | noPricingFlag、ES、Redis 有价集合 |
| OpsCenter 区域事件 | eventId + SID | OpsCenterNotify | AdminModel + MaterielNotifySer | eventId + sid + skuId | noPricingFlag、ES、Redis |
| ItemCenter 销售限价 | code/status/SKU/客户 | ItemCenterNotify | MaterielNotifySer | eventId + code + sid + skuId | 限价关系 + is_hot |
| ItemCenter 限采 | routing key + SID | ItemCenterNotify | ItemCenterJob + BuyLimitSer | msgId + sid + skuId | buyLimit、ES、Redis |
| 外部调度 | 商品/库存水位 | Goods CLI | MySQL Models + Mongo writer | watermark + sid + invId | Mongo 商品/库存快照 |
| 运维/研发 | SID、页码或分片 | 修复 tasks Controller | 公共 Mongo 写入层 | sid + invId/skuId | 指定字段或索引 |
当前风险和优先级
| 优先级 | 风险 | 页面/业务影响 | 建议动作 |
|---|---|---|---|
| P0 | Consumer 落中间表失败仍可能 ACK | 价格/限采变化永久丢失 | 提交后 ACK;eventId 唯一键 |
| P0 | Job 单 SID 异常被捕获,整批仍可能 SUCCESS | 部分站点没更新但消息显示成功 | 汇总 SID 结果;部分失败进入重试 |
| P0 | Mongo、ES、Redis 无统一补偿轨迹 | 页面、搜索、差集基线分叉 | 记录每个投影状态,支持单投影重放 |
| P1 | ItemCenter 空集合和异常空响应难区分 | 旧状态不能清理或被误清 | success + snapshotVersion + data=[] |
| P1 | 商品主同步锁非原子且无 TTL | 误清锁后双实例覆盖 | NX + owner token + 续租 + fencing token |
| P1 | 批次异常继续,任务末尾仍推进水位 | Mongo 永久漏页 | 失败 SID/页清单;全部成功后推进源水位 |
| P1 | 报价同步依赖多个服务 | 单下游超时可能拖慢整页 | 基础信息、价格、库存分区加载和降级 |
| P2 | 历史脚本有 die、全量覆盖、只增不减 | 人工修复制造更大不一致 | 下线或增加 dry-run、审批、双向差集 |
建议演进路线
flowchart LR
A["阶段一:可观测<br/>requestId / eventId<br/>结构化日志<br/>一致性巡检"]
B["阶段二:可靠<br/>落表后 ACK<br/>原子领取<br/>幂等版本与重放"]
C["阶段三:解耦<br/>基础商品先返回<br/>价格/库存独立加载<br/>提交时强校验"]
D["阶段四:治理<br/>修复脚本 dry-run<br/>审批与审计<br/>淘汰临时入口"]
A --> B --> C --> D
建议成功标准:
- 每个 MQ 事件可用
eventId + sid + skuId追踪到 Mongo、ES、Redis 的最终状态。 - WAIT、ING、ERROR、部分失败有监控和 owner,超时 ING 可回收。
- 任一投影失败可单独重放,不必重新发送整条业务消息。
- 基础商品链成功时,价格或库存单项故障不再导致整页不可用。
- 人工修复具备预估影响、操作审计、读回验证和幂等重跑能力。
需要跨团队确认的决策
| 决策 | 参与团队 | 要形成的结果 |
|---|---|---|
| 价格/限采快照版本语义 | ItemCenter、DGJ2、Basedata | 空集合、异常和旧版本的处理契约 |
| MQ 唯一键和重放责任 | ItemCenter、OpsCenter、DGJ2、MQ 平台 | eventId 生成、保留周期、补发入口 |
| 商品/价格/库存接口边界 | 前端、Sale、Basedata、Inventory、Price | 首屏契约、局部失败展示、提交校验 |
| Mongo/ES/Redis 一致性 owner | DGJ2、Basedata、运维 | 告警归属、补偿入口、SLA |
| 历史脚本治理 | 研发、DBA、运维 | 权限、审批、dry-run、下线清单 |
15 分钟团队分享建议
| 时间 | 内容 | 目标 |
|---|---|---|
| 2 分钟 | 一张架构全景图 | 理解 Mongo 是读模型,不是事实源 |
| 3 分钟 | 报价同步主链 | 理解 Sale 聚合哪些系统 |
| 4 分钟 | 三条 MQ 对比 | 理解触发原因、执行方式和字段差异 |
| 2 分钟 | 字段和标识 | 不混淆 sid/invId/skuId 与状态字段 |
| 3 分钟 | 风险和演进 | 先可观测,再可靠,再解耦、治理 |
| 1 分钟 | 排查入口 | 按症状查,不直接从修复脚本开始 |
推荐用“货主区域变化后商品突然不报价”串联讲解:区域事件 → 主站 SID 归一化 → ItemCenter 有价快照 → Mongo/ES/Redis noPricingFlag 差集 → Basedata → Sale 聚合 → 页面;异常时按相同顺序逐段找证据。
日志证据矩阵
| 链路 | 日志/数据源 | 搜索锚点 | 成功/失败信号 | 下一段 Join Key |
|---|---|---|---|---|
| HTTP 商品维护 | Web/PHP 日志、商品操作日志 | URL、invId、商品编号 | success / validation / exception | sid + invId |
| 价格 MQ Consumer | itemPriceEnable-*、itemPriceDisable-* | eventId、routing key、SID | 消息表 WAIT 行 / insert 异常 | msgId + sid |
| 价格/限采 Job | itemcenterNotify Job 日志 | messageIds、SID、SKU 数量 | SUCCESS / ERROR / 单 SID 错误 | msgId + sid + skuId |
| 区域 MQ | OpsCenter Consumer 日志 | routing key、eventId、SID | Consumer 完成 / ItemCenter 或投影异常 | eventId + sid |
| 销售限价 | salesPriceLimitNotice-* | code、status、customerCode、skuCode | 关系处理数量 / 写入错误 | code + sid + skuId |
| 商品/库存 CLI | cron/CLI stdout、Redis | SID、水位、更新数 | 任务结束 / 批次异常 / 水位缺失 | watermark + sid + invId |
当前多数 Mongo 写方法没有统一上报 matchedCount/modifiedCount;日志“完成”不能替代 Mongo/ES/Redis 读回。
环节数据变更台账
| 环节 | 事务边界 | 读取事实 | 写入 | 精确变化 | 部分失败补偿 |
|---|---|---|---|---|---|
| HTTP 新增/编辑 | MySQL transaction | 页面表单、商品/库存 | MySQL commit 后写 Mongo/ES/MQ | 商品事实先提交,投影后写 | 商品主同步或字段修复 |
| HTTP 删除/启停 | 无跨存储事务 | 商品及交易校验 | MySQL + Mongo + ES | 删除文档或 disable 0↔1 | 按 invId 重放三处 |
| 价格/区域 | 无跨存储事务 | ItemCenter 快照 + Redis/Mongo 基线 | noPricingFlag + Redis 集合 | remote-local=0;local-remote=1 | 重新拉快照 |
| 限采 | 消息表状态独立 | ItemCenter 限采集合 + Redis | buyLimit + Redis 集合 | remote-local=1;local-remote=0 | 重跑 Job/重建 Redis |
| 销售限价 | MySQL 关系与投影非原子 | 限价关系及有效 code | 关系 state + is_hot | 生效=1;最后规则失效=0 | 按 code/sid/sku 重算 |
| 商品主同步 | 每页 Mongo bulkWrite | MySQL 商品/库存/限价、Redis | 完整 upsert/delete | 覆盖商品快照 | 重跑失败 SID/页 |
| 库存增量 | Redis 锁与水位 | inventory_real_time | qty/fineQty | 仅更新数量;缺文档 upsert | 不推进水位并重跑 |
代码与详细技术底稿
仓库:/Users/zhoujiangbin/code/docker-dev-env/www/dgj2.0
详细文档入口:doc/ai/mongo/README.md
关键代码:
- HTTP:
application/controllers/basedata/Inventory.php、Invlocation.php - MQ:
application/controllers/tasks/ItemCenterNotify.php、OpsCenterNotify.php - 后置任务:
application/controllers/tasks/ItemCenterJob.php - 主同步:
application/controllers/tasks/Goods.php - 公共 Mongo 写入:
application/Services/Materiels/MaterielCacheStationSer.php - 价格/区域快照:
application/Services/Notify/MaterielNotifySer.php - 限采差集:
application/Services/Materiels/BuyLimitSer.php - 修复入口:
GoodsCache.php、Import.php、GoodsDescUpdate.php
当前证据边界
- A 级代码证据:入口、方法、集合、字段、锁和水位逻辑已从当前代码确认。
- B 级运行证据:部分 ItemCenter MQ 日志和消息表状态已有样例。
- C 级待补证:生产 cron/Kubernetes 表达式、Job 实例数、部分上游 Producer 源码、Mongo 复制延迟基线。
- 本文的演进阶段和成功标准是建议方案,不代表已经上线。