本文梳理 DGJ2.0 中与智能对话相关的五个子系统:旧版微信 AI 报价、RobotV2 多平台会话状态机、腾讯 IM 群聊与业务消息、AI 控制台辅助能力、Udesk 账号资料同步。

这五部分共享客户、商品、价格、库存和销售订单数据,但并不是同一个系统。

子系统主要入口主要职责当前事实源
旧版微信机器人微信回调、RobotInquirySerVIN/关键词/图片/语音识别、报价、下单MySQL + Redis 会话
RobotV2RobotFacade多平台适配、Intent、状态机、统一回复平台会话表 + Context KV
腾讯 IMIM Center + MQ群成员、用户、业务消息、机器人回复IM Center + DGJ 关系数据
AI 控制台PC 工作台会话场景下查客户、销售、配送等辅助信息DGJ 业务表 + 外部服务
UdeskMQ/CLI 同步将站管家账号资料同步成 Udesk customerSYS_ADMIN + Udesk
重要边界:仓库内没有找到 RobotV2 会话直接“转 Udesk 人工坐席”的完整实现。能确认的是 Udesk customer 创建/更新。人工接管是否由 Udesk 页面、IM Center 或其他服务完成,需要结合外部系统确认。

1. 业务目标

  1. 接收微信、腾讯 IM 等渠道的文本、VIN、图片和语音消息。
  2. 将不同平台消息转换为统一上下文。
  3. 识别用户意图和当前会话状态。
  4. 查询车型、品类、品牌、商品、价格、库存和报价范围。
  5. 生成文本、图片、卡片或组合回复。
  6. 支持多轮选择、报价、下单和会话超时。
  7. 根据门店和账号权限维护 IM 群成员。
  8. 把站管家账号资料同步到 Udesk,供外部客服系统识别。
  9. 保留日志、搜索记录和会话上下文以支持排查。
flowchart LR
  WX["微信消息"] --> ADAPTER["输入适配器"]
  TIM["腾讯IM消息"] --> MQ["IM Center MQ"] --> ADAPTER
  API["API调用"] --> ADAPTER
  ADAPTER --> INTENT["Handler责任链/Intent"]
  INTENT --> SM["RobotV2状态机"]
  SM --> QUERY["VIN/商品/价格/库存"]
  QUERY --> REPLY["ReplyData"]
  REPLY --> SENDER["平台发送器"]
  SENDER --> WX
  SENDER --> TIM
  ADMIN["站管家账号"] --> UDESK["Udesk customer同步"]

2. 适用场景

问题重点章节
微信机器人不回复10、18、27
腾讯 IM 有消息但机器人不回复11、12、25、28
同一消息重复回复12、25、30
VIN 解析后会话卡住13、14、29
多车型选择无效14、29
图片/语音识别失败15、29
商品有库存但机器人不展示16、29
报价一直等待服务站17、31
回复序号不能下单14、17、29
分仓账号不在客户群20、32
禁用账号仍在群里20、32
Udesk 无账号或资料旧23、33
想接入新聊天平台9、10、34
修改 RobotV2 后评估影响面35、36

3. 系统边界

3.1 哪些数据在 DGJ

数据DGJ 是否主存说明
服务站、账号、客户、门店关系是IM 群成员和 Udesk 同步来源
机器人会话状态和上下文是RobotV2 使用独立会话/Context 表
机器人关键词、报价范围是决定搜索和展示
商品、价格、库存是/缓存与外部组合查询链跨物料缓存、服务站价和库存服务
腾讯 IM 用户、群和消息否主存位于 IM Center/腾讯 IM
微信机器人发送能力否通过 WechatRobot Provider
语音/OCR/意图模型结果否来自 AI Center
Udesk customer否DGJ 只负责推送资料

3.2 数据流责任图

flowchart TB
  subgraph DGJ["DGJ2.0"]
    CONV[("会话/Context")]
    GOODS[("商品/价格/库存")]
    ORG[("账号/门店/客户关系")]
    ROBOT["RobotV2"]
  end
  IMC["IM Center / 腾讯IM"] --> ROBOT
  WXR["微信机器人服务"] --> ROBOT
  AIC["AI Center"] <--> ROBOT
  ROBOT <--> CONV
  ROBOT <--> GOODS
  ROBOT <--> ORG
  ROBOT --> IMC
  ROBOT --> WXR
  ORG --> U["Udesk"]

4. 版本关系:V1、RobotV2 与旧 AI

4.1 三条实现并存

实现关键类会话特点当前用途
旧 AI 智能询价SmartInquirySer、AiSerRedis Hash 保存通知、VIN、sessionKeyAI Center 异步意图和报价
Robot V1RobotInquirySer、RobotInquiryBaseSer旧会话表 + 大量业务方法微信链路和 V2 兼容依赖
RobotV2RobotFacade、Adapters、Handlers、States平台会话 + Context KV + 状态机腾讯 IM 主入口及逐步承接微信

RobotV2 并未彻底摆脱 V1。以下模块仍直接依赖 RobotInquirySer:

  • ProductQueryService
  • OrderService
  • MessageFormatterService
  • 电池、维保、全车件、产品码等 Strategy
  • 多个展示过滤和查询后处理模块

因此不能把 V1 判断为“废代码”后整体删除。

flowchart LR
  V1["Robot V1 大服务"] --> DATA["成熟查询/下单能力"]
  V2["RobotV2 编排层"] --> STATE["Adapter + Intent + State"]
  V2 --> DATA
  OLD["旧 AiSer"] --> AIC["AI Center异步回调"]
  V2 --> AIC

5. 代码入口地图

5.1 消息入口

入口文件用途
腾讯 IM MQapplication/controllers/tasks/ImCenterNotify.php消费 IM 业务消息并调用 RobotV2
微信内部入口application/controllers/inner/moveMall/Robot.php微信消息、异步处理、RobotV2 AI 回调
移动商城入口application/controllers/moveMall/RobotController.php机器人页面/接口入口
老会话任务application/controllers/tasks/RobotConversation.phpRedis 延迟消息和机器人会话任务
旧 AI 回调application/controllers/Ai.phpAI Center 通知和报价展示
IM 工作台application/controllers/moveMall/ImWorkbench.php语音解析等工作台能力
AI 控台配送application/controllers/aiConsole/Courier.php骑手/配送辅助,详见第 38 篇

5.2 RobotV2 目录职责

目录职责
Adapters/Input平台消息转 MessageContext
Handlers按优先级识别 Intent
Intents表达用户意图和解析结果
States多轮会话业务状态
Conversation会话策略、Repository、Context
Services/Query维保、易损、全车件、电池查询
Pipelines/Filters品类、价格、库存过滤
Services/Display分组、展示、过滤原因
Adapters/Output平台回复格式和发送
Strategies复用 V1 的场景查询/下单策略
database会话和 Context 迁移脚本

5.3 外部 Provider

Provider外部能力
ImCenterProviderUserSig、发消息、群用户、同步结果
IMProvider用户批建、群成员变更、群详情/状态
AiCenterProvider文本意图、语音、音频转换、OCR
WechatRobotProvider微信群/私聊发送
Udesk Customers Create/UpdateProvidercustomer 创建和更新

6. 核心表

6.1 旧机器人与报价表

常量/物理表用途
SCM_ROBOT_CONVERSATION / t_scm_robot_conversation旧机器人会话
SCM_ROBOT_PRE_CONVERSATION / t_scm_robot_pre_conversation旧预会话
SCM_ROBOT_CONVERSATION_RECORD对话记录
SCM_ROBOT_SEARCH_LOG搜索日志
SCM_ROBOT_IMAGE_PARSE微信图片解析
SCM_ROBOT_CONVERSATION_KEYWORD会话关键词
SCM_ROBOT_NOCAR_KEYWORD无车关键词
SCM_ROBOT_POSITION_KEYWORD方位关键词
SCM_ROBOT_FRIEND / SCM_ROBOT_FRIEND_BIND好友与真实 wxid 绑定
SCM_ROBOT_OFFER_ORDER_APPLY等待服务站报价的明细
SCM_OFFER_ORDER / SCM_OFFER_ORDER_INFO报价单主/明细
T_SCM_ROBOT_QUOTE_CONFIG*报价范围主/明细

6.2 RobotV2 会话表

仓库 SQL 显示至少有以下目标结构:

表平台主键/查找键主要状态
t_robot_conversation微信id、cv_keystatus、state、expires_at
t_robot_conversation_tencent_im腾讯 IMid、cv_key同上
t_robot_conversation_context通用 Context(conversation_id,key) 唯一JSON value、类型、过期时间
t_robot_conversation_preRobotV2 异步预会话回调关联键pending/processing/completed/cancelled/timeout
erDiagram
  ROBOT_CONVERSATION ||--o{ ROBOT_CONVERSATION_CONTEXT : owns
  ROBOT_CONVERSATION ||--o{ ROBOT_PRE_CONVERSATION : waits_for
  ROBOT_CONVERSATION {
    bigint id
    string cv_key
    int sid
    int contact_id
    string wxid
    string chat_group_id
    string status
    string state
    datetime expires_at
  }
  ROBOT_CONVERSATION_CONTEXT {
    bigint id
    bigint conversation_id
    string key
    mediumtext value
    string type
    datetime expires_at
  }
  ROBOT_PRE_CONVERSATION {
    bigint id
    bigint conversation_id
    string status
    datetime created_at
  }

6.3 表版本警告

application/config/tables.php 仍定义 t_scm_robot_conversation,RobotV2 SQL 则创建 t_robot_conversation 和腾讯 IM 独立表。实际 Repository 使用哪张表必须以运行代码和生产 DDL 为准。

不要只根据 SQL 文件执行迁移;目录中同时存在瘦身、修复、去唯一索引、数据迁移等多版脚本。


7. Redis 数据

7.1 旧会话 Hash

Key/FieldTTL内容
会话 Key + notify默认消息 TTL 600 秒原始微信通知,用于异步回调后回复
vin会话期当前 VIN
compressId会话期已选车型 ID
sessionKey会话期AI Center 多轮 Session
goodsMap会话期回复序号到商品/数量映射
storage_id会话期供货仓上下文
sid/uid/user_name会话期业务身份

7.2 机器人公共 Key

常量用途
ROBOT_MSG_CACHE机器人消息缓存
ROBOT_LINK_CACHE链接缓存,默认 7200 秒
ROBOT_KEYWORD_COMPARISON关键词比对缓存
HASH_ROBOT_WECHAT_INFO机器人微信信息
HASH_ROBOT_REPLY_BLACK_LIST回复黑名单
ROBOT_INQUIRY_MSG询价异步消息
ROBOT_INQUIRY_DELAY_QUEUERedis 延迟队列
ROBOT_INQUIRY_REPLY_FLAG是否已回复标记
flowchart LR
  MSG["微信消息"] --> HASH["会话Redis Hash"]
  HASH --> AI["AI Center异步请求"]
  AI --> CALLBACK["AI回调"]
  CALLBACK --> HASH
  HASH -->|"仍有notify"| REPLY["回原群"]
  HASH -->|"已过期"| DROP["抛会话已过期"]

8. 会话标识与生命周期

8.1 cv_key

RobotV2 SQL 注释给出的微信会话键格式:

{sid}_{contact_id}_{wxid}_{chat_group_id}

不同平台由各自 ConversationPolicy 生成和判定,不应在业务代码手拼字符串。

8.2 会话状态 status

值含义
active活跃会话
closed用户主动关闭或自然结束
expired超时
deleted逻辑删除

8.3 默认过期时间

平台SQL/策略说明
微信默认 1 小时
腾讯 IM默认 1 小时
API默认 5 分钟
旧报价等待10 分钟 CONVERSATION_INQUIRY_TIME

cv_key 在目标 SQL 中是普通索引,不是唯一索引,允许同一个业务键历史上存在多条会话。Repository 查询必须同时限制 active/status/过期时间。


9. RobotV2 总体架构

flowchart TD
  RAW["原始平台消息"] --> AF["MessageAdapterFactory"]
  AF --> CTX["MessageContext"]
  CTX --> HC["Handler Chain"]
  HC --> INTENT["Intent"]
  INTENT --> CF["ConversationFactory"]
  CF --> CM["ConversationManager"]
  CM --> SM["State Machine"]
  SM --> QS["Query/Order/AI Service"]
  QS --> RD["ReplyData"]
  RD --> SF["ResponseSenderFactory"]
  SF --> OUT["微信或腾讯IM"]
  CM <--> DB[("Conversation + Context")]

核心设计原则:

  1. 平台差异只留在 Adapter、Policy、Repository、Formatter、Sender。
  2. 业务输入统一为 MessageContext。
  3. Handler 只负责判断意图,不直接承载完整多轮业务。
  4. State 负责当前阶段行为和状态迁移。
  5. 跨请求业务数据进入 ConversationContext,不继续扩大会话主表。

10. 输入适配器

MessageAdapterFactory 当前注册顺序:

  1. TencentImAdapter
  2. WechatAdapter
  3. 无匹配时 DefaultAdapter

钉钉 Adapter 文件存在,但默认注册被注释,不能认为线上已支持钉钉。

10.1 统一上下文包含什么

类别示例
追踪traceId、原消息 ID
平台wechat、tencent_im、api
用户userId、角色、wxid/IM ID
会话群 ID、服务站、客户、当前会话
消息文本、图片、语音、业务 JSON
业务VIN、Intent、Metadata、Reply 格式

10.2 腾讯 IM 消息示例

{
  "msgContext": {
    "msgId": "im-message-example",
    "msgType": "TIMCustomElem",
    "msgTime": 1784185200
  },
  "fromAccount": {
    "userId": "garage-user-example",
    "role": "garage",
    "bizExtInfo": "{\"sid\":9999,\"contactId\":12345}"
  },
  "toAccount": [
    {
      "userId": "station-user-example",
      "role": "station"
    }
  ],
  "msgContent": {
    "text": "查询前刹车片"
  }
}

11. Handler 责任链与 Intent 优先级

RobotFacade::initHandlerChain() 注释定义的优先级:

顺序Handler/意图示例
1特殊指令重置、诊断类指令
2AI 回调图片/AI 异步结果
3车型选择用户回复车型序号
4图片消息VIN 图片、旧件图片
5VIN 码17 位合法 VIN
6产品码原厂码/产品码
7无 VIN 关键词轮胎、电池、油品等
8序列号从上次列表选商品/下单
9查询条件品类、品牌、方位等
10Fallback无法识别或静默忽略
flowchart TD
  M["MessageContext"] --> S{"特殊指令?"}
  S -->|否| A{"AI回调?"}
  A -->|否| C{"车型选择?"}
  C -->|否| I{"图片?"}
  I -->|否| V{"VIN?"}
  V -->|否| P{"产品码?"}
  P -->|否| N{"无车关键词?"}
  N -->|否| O{"序号下单?"}
  O -->|否| Q{"查询条件?"}
  Q -->|否| F["Fallback"]
  S --> R["IntentResult"]
  A --> R
  C --> R
  I --> R
  V --> R
  P --> R
  N --> R
  O --> R
  Q --> R

优先级的价值是避免同一文本被多个规则同时命中。例如 17 位 VIN 也可能被通用产品码正则误判,必须让 VIN 先处理。


12. 腾讯 IM 消息主流程

Destination:imcenter_notify,Routing Key:im_app_biz_message。

sequenceDiagram
  participant TIM as 腾讯IM
  participant IC as IM Center
  participant MQ as RabbitMQ
  participant C as ImCenterNotify
  participant R as RobotFacade
  participant DB as 会话/业务数据
  TIM->>IC: 用户业务消息
  IC->>MQ: im_app_biz_message
  MQ->>C: messageData
  C->>IC: msgSyncResult(msgId)
  C->>R: handleMessage(tencent_im,json,true)
  R->>DB: 加载/创建会话和上下文
  R->>R: Adapter -> Intent -> State
  R->>IC: TencentImResponseSender发送回复
  R-->>C: 处理结果
  C-->>MQ: ACK

12.1 ACK/NACK 规则

异常结果
正常ACK
InvalidArgumentExceptionACK,不重试
BizErrorExceptionACK,不重试
ParamsErrorExceptionACK,不重试
错误码 400/404/422ACK,不重试
其他系统异常NACK,可重试

12.2 两层异常语义冲突

ImCenterNotify 希望 RobotFacade 抛出系统异常以便 NACK;但 RobotFacade::handleMessage() 自己 catch Exception 后返回 formatError(),不会继续抛出。

这意味着大量内部异常可能被消费者当成“正常返回”并 ACK。只有未被 catch 的 Throwable 才能到达消费者重试逻辑。

12.3 同步确认时点

消费者在 RobotFacade 处理前就调用 msgSyncResult(msgId),而且确认失败只记 warning,不阻断处理。

这个接口表示“已收到”还是“已处理成功”需要与 IM Center 确认。若外部将其理解为处理成功,则后续 Robot 失败不会再补发。


13. RobotV2 状态机

13.1 状态字典

状态类型含义
idle稳定等待新意图
vin_parsing瞬态解析 VIN
vin_parsed稳定已有车型,等待查询条件
car_model_confirming稳定多车型待用户选择
product_querying瞬态查询商品
product_list稳定已展示商品,等待筛选/序号
order_placing瞬态创建订单
image_recognition瞬态图片识别
ai_parsing瞬态AI 异步解析
expired稳定会话过期
stateDiagram-v2
  [*] --> idle
  idle --> vin_parsing: VIN
  vin_parsing --> vin_parsed: 单车型
  vin_parsing --> car_model_confirming: 多车型
  car_model_confirming --> vin_parsed: 选择车型
  vin_parsed --> product_querying: 品类/关键词
  idle --> product_querying: 无车查询/产品码
  product_querying --> product_list: 查询成功
  product_list --> product_querying: 继续筛选
  product_list --> order_placing: 回复序号
  order_placing --> product_list: 下单完成或返回列表
  idle --> image_recognition: 图片
  image_recognition --> ai_parsing: 异步AI
  ai_parsing --> product_querying: 回调得到条件
  idle --> expired: 超时
  vin_parsed --> expired: 超时
  product_list --> expired: 超时

13.2 状态循环

to() 表示立即进入下一状态继续执行,stay() 表示停止并等待下一条用户消息。单条消息最多循环 10 次,防止状态转换死循环。

flowchart TD
  A["确定当前状态"] --> B["handleMessage"]
  B --> C{"Transition"}
  C -->|stay| END["保存最后回复并结束"]
  C -->|to next| D["onExit/onEnter"]
  D --> E["持久化conversation.state"]
  E --> F{"迭代 < 10?"}
  F -->|是| B
  F -->|否| WARN["告警并强制结束"]

13.3 状态持久化失败

saveStateToConversation() 更新失败只记 error,不抛异常。当前请求仍可能回复成功,但下一条消息会从旧状态继续,表现为“机器人忘了上一轮”。


14. 会话 Context

Context 使用 (conversation_id,key) 唯一约束保存 JSON。关键类别:

Context Key内容
cv_ctx.vin_infoVIN、品牌、车型、解析数据
cv_ctx.vin_raw_parse_result原始 VIN 多车型结果
cv_ctx.car_models待选择车型列表
cv_ctx.selected_car_model_index用户选择序号
cv_ctx.confirmed_car_model最终车型
cv_ctx.last_query_context最近查询条件
cv_ctx.query_history查询历史,注释约定最多 10 条
cv_ctx.user_filters用户筛选
cv_ctx.query_result_summary总数、分页摘要
cv_ctx.last_query_result最近商品结果
cv_ctx.history_query_result历史商品,按 invId 去重
cv_ctx.query_products当前展示商品列表

14.1 为什么用 Context 表

flowchart LR
  BEFORE["会话主表不断加VIN/品类/Intent字段"] --> PROBLEM["平台耦合、迁移频繁"]
  PROBLEM --> AFTER["主表只留身份和状态"]
  AFTER --> KV["业务数据进入Context KV"]
  KV --> BENEFIT["按Key演进、统一序列化、独立过期"]

14.2 使用约束

  • 所有 Key 应在 ConversationContextKeys 定义。
  • 禁止业务代码随意写字符串 Key。
  • value 为 JSON,读取时按 type 还原。
  • 大列表进入 MEDIUMTEXT,但仍应控制体积;商品结果过大将增加每轮 DB I/O。
  • 删除会话时是否级联清理 Context 需以生产 DDL/任务确认。

15. VIN、图片和语音

15.1 VIN

旧枚举使用:

/^[A-HJ-NPR-Za-hj-npr-z0-9]{17}$/

同时排除以 XD/XS 开头的业务单号,避免把销售/出库单误识别为 VIN。

VIN 解析可能返回一个车型或多个车型。多车型进入 car_model_confirming,用户回复序号后保存确认车型。

15.2 语音

sequenceDiagram
  participant P as 微信/腾讯IM
  participant A as Input Adapter
  participant AI as AI Center
  participant R as RobotV2
  P->>A: voice URL/消息
  A->>AI: parseVoice
  AI-->>A: translation
  A->>R: 识别文本进入Intent链

AiSer::parseVoice() 直接取 translation,无内容时返回空字符串。腾讯 IM 和微信 Adapter 都会调用它。

15.3 旧件图片

AiSer::parsePart() 调 AI Center OCR,返回品牌、名称、型号、规格、OE 和其他信息。RobotV2 对油品/轮胎旧件识别失败有短路逻辑:

  • 客户配置 old_part_reply=1:回复“识别失败,请用 VIN 或联系人工”。
  • 未开启:静默忽略。

15.4 异步预会话

图片/AI 处理可能先创建 pre_conversation,回调再关联正式 conversation_id。状态通常为:

stateDiagram-v2
  [*] --> pending
  pending --> processing
  processing --> completed
  pending --> cancelled
  processing --> cancelled
  pending --> timeout: 超过约5分钟
  processing --> timeout: 超过约5分钟

TencentImPreConversationRepository 提供 findTimeoutList(300) 和 markTimeout(300);清理任务是否部署需环境确认。


16. 商品查询和展示

RobotV2 查询层按场景分发:

Query Service场景
UpkeepPartsQueryService维保件
WearingPartsQueryService易损件
CarPartsQueryService全车件
BatteryQueryService蓄电池
NoVinKeywordQueryService无车关键词

16.1 典型查询链

flowchart TD
  I["Intent/QueryContext"] --> D["QueryDispatcher"]
  D --> S["场景QueryService"]
  S --> L["旧RobotInquiry能力/商品服务"]
  L --> P["Pipeline"]
  P --> C["CategoryFilter"]
  C --> PR["PriceRangeFilter"]
  PR --> ST["StockFilter"]
  ST --> PP["PostProcessor排序/仓库明细"]
  PP --> DS["DisplayService"]
  DS --> F["平台Formatter"]

16.2 展示不等于原始查询结果

商品会继续经过:

  • 报价范围过滤;
  • 价格范围过滤;
  • 库存过滤;
  • 最小用量、配置项和仓库明细处理;
  • OE/配置/标准分类分组;
  • 平台卡片容量限制。

所以“搜索服务有商品但机器人不展示”应比较每层输入输出,不能只查 ES 或数据库。


17. 旧 AI 报价单流程

17.1 AI 意图回调

AiSer::aiCenterNotify() 根据 intentionBucket:

Bucket动作
PURCHASE构建报价单并进入报价/下单
QUOTE同上
JUSTCHATTING丢弃,不回复
MULTIPLECHATS直接返回 AI 多轮响应
其他交给 OilService

回调先用 sid + chatId + wxId 生成 Redis 会话 Key。若 notify 已过期,抛“会话已过期”,无法回原群。

17.2 报价生成

sequenceDiagram
  participant AI as AI Center
  participant AS as AiSer
  participant P as 价格服务
  participant DB as 报价表
  participant WX as 微信机器人
  AI->>AS: PURCHASE/QUOTE + items
  AS->>P: 查询移动商城价和历史最高报价
  AS->>DB: 开事务
  AS->>DB: 插报价主单/明细/待报价申请
  alt 至少一个商品有价格
    AS->>WX: 报价单已生成 + 小程序链接
  else 全部待报
    AS->>WX: 等待服务站报价
  end
  AS->>DB: 提交

报价单号格式为 BD + sid + 时间 + 随机数。

17.3 服务站补报价

sendOfferSheet():

  1. 前端提交 applyId + salePrice。
  2. 校验申请仍存在、价格大于 0、只提交一张报价单。
  3. 要求保存的 conversation_key 仍能从 Redis 取到 notify。
  4. 更新申请、报价主单和明细。
  5. 生成小程序链接并发回原微信群。

当前该方法没有显式数据库事务。若数据库更新后微信群发送失败,报价已生效但客户未收到链接。


18. 微信机器人主流程

sequenceDiagram
  actor C as 客户
  participant WR as 微信机器人服务
  participant IN as inner/moveMall/Robot
  participant V1 as RobotInquirySer
  participant V2 as RobotFacade
  participant DB as 会话/业务数据
  C->>WR: 文本/图片/语音
  WR->>IN: 机器人通知
  IN->>V1: onMessageAsync/预处理
  V1->>V2: 部分场景进入V2
  V2->>DB: 会话、VIN、查询、报价
  V2-->>WR: 微信格式回复
  WR-->>C: 群/私聊消息

微信入口文件较大,包含旧链、RobotV2 回调、测试/兼容方法。排查时用 requestID/traceId 串联日志,而不是按类名推断唯一流程。


19. ReplyData 与多平台发送

RobotV2 统一回复类型:

类型类用途
文本TextReply提示、错误、普通结果
图片ImageReply图片结果
卡片CardReply商品/车型卡片
自定义CustomReply平台业务消息
组合CompositeReply多段混合回复

ResponseSenderFactory 当前注册:

  • wechat -> WechatResponseSender
  • tencent_im -> TencentImResponseSender

钉钉发送器存在但未注册。

flowchart LR
  RD["ReplyData"] --> FF["平台Formatter"]
  FF -->|wechat| WF["Wechat格式"]
  FF -->|tencent_im| TF["腾讯IM JSON/卡片"]
  WF --> WS["WechatResponseSender"]
  TF --> TS["TencentImResponseSender"]

20. IM 群成员维护

群成员不是静态配置,而是由“客户分仓关系 + 账号门店权限 + 账号启停”共同计算。

20.1 客户切换分仓

IMService::changeRoomUserByStore():

  1. 读取新门店全部账号。
  2. 首次分配时,把这些账号加入客户群。
  3. 切换时读取旧门店账号。
  4. 通过集合差计算入群和退群账号。
  5. 调 IM Center changegroupmember。
flowchart TD
  A["客户分仓关系变更"] --> N["新门店账号集合"]
  A --> O["旧门店账号集合"]
  N --> ADD["N - O = 入群"]
  O --> REMOVE["O - N = 退群"]
  ADD --> IM["IM Center changeGroupMember"]
  REMOVE --> IM

20.2 账号门店权限变更

changeRoomUserByRight() 计算新增/删除门店,再查询这些门店关联客户,将账号加入或移出对应客户群。

总仓账号的特殊规则:若 storeDefault 在变更集合中,会加载服务站全部客户。

20.3 账号启用/禁用

changeRoomUserByAdmin():

  • 启用:加入当前门店权限涉及的客户群;
  • 禁用:退出这些客户群;
  • 总仓账号同样可能涉及全部客户。

20.4 E站 APP 查询群内服务站账号

getRoomUserByContactId() 返回分仓账号 + 总仓账号。

当前代码使用 $userList + $defaultUserList 做数字索引数组合并。PHP 的 + 是键并集,索引相同的右侧元素会被丢弃;这可能导致部分总仓账号缺失。应改为 array_merge() 后 array_unique()。


21. IM Center 接口

接口Provider 方法用途
/imcenter/ext/zgj/user/batchcreateIMProvider::userBatchCreate批量创建 IM 用户
/imcenter/ext/zgj/group/changegroupmemberchangeGroupMember入群/退群
/imcenter/ext/zgj/group/getstatusgroupStatus群状态
/imcenter/ext/zgj/group/getwithmembersgroupInfo群详情和成员
/imcenter/ext/zgj/user/getusersigImCenterProvider::getUserSig腾讯 IM 登录签名
/imcenter/ext/zgj/user/sendimmessagesendImMessage发送业务回复
/imcenter/ext/zgj/user/getGarageUserByImGroupIdgetRepairShopUserByImGroupId反查维修厂成员
/imcenter/ext/zgj/user/msgsyncresultmsgSyncResult消息接收确认

IMProvider 默认对用户批建和群成员变更吞掉异常并返回 null,只有 groupInfo/groupStatus 把 throw=true。调用方如果不检查返回值,可能把外部失败当成功。


22. AI 控制台

当前 application/controllers/aiConsole 只发现 Courier.php,主要是配送辅助:

方法用途
lists查询配送员轨迹列表
pushTmsTransportCreate手工补推 TMS 配送单
getTransportByTmsNo按 TMS 单号查询
getTransportReceiverList查询收货人配送列表

AI IM 工作台页面还组合客户会话、今日销售出库和三方配送组件,但这些属于页面编排,不代表 aiConsole 目录承载完整 AI 机器人后端。

详见 38_配送履约_自提骑手与三方配送.md。


23. Udesk 同步

23.1 实际同步对象

Udesk 代码从 SYS_ADMIN a 读取站管家账号,并连接同 sid 主账号 SYS_ADMIN b(roleid=0) 获取服务站资料。

因此这里的 Udesk customer 实际代表“站管家账号/服务站用户”,不是 t_bs_contact 修理厂客户。

23.2 字段映射

Udesk 字段DGJ 来源
nick_name子账号 name
open_api_tokenmd5(sid + uid)
web_token同上
服务站编码自定义字段sid
服务站名称主账号 name
手机号子账号 mobile
省/市/区主账号区域编码转名称
状态正常/禁用

23.3 异步更新

Destination:dgj_udesk,Routing Key:udesk_customer_async。

sequenceDiagram
  participant DGJ as 账号编辑业务
  participant MQ as RabbitMQ
  participant C as UdeskNotify
  participant S as CustomerService
  participant DB as SYS_ADMIN/区域表
  participant U as Udesk
  DGJ->>MQ: {sids:[...]}
  MQ->>C: udesk_customer_async
  C->>S: update(messageData)
  loop 每批100个账号
    S->>DB: 查询账号、主账号和区域
    S->>U: PUT by token
  end
  S-->>MQ: ACK

23.4 全量 CLI

UdeskAsync 提供:

  • createCustomer():全量创建;
  • updateCustomer():全量更新;
  • updateCustomerByUid($uid):单 UID 修复。

创建/更新逐条 sleep(1),全量任务耗时与账号量线性增长。

23.5 人工接管边界

仓库证据只能确认 Udesk customer 资料同步。未找到:

  • RobotV2 创建 Udesk 工单;
  • 会话状态改为“人工接管”;
  • Udesk 坐席回复回流 RobotV2;
  • 机器人恢复接管的状态转换。

因此人工接管流程必须标为外部待确认,不能在本文中假定已完整实现。


24. Udesk 数据风险

风险代码表现影响
Token 可预测md5(sid . uid) 无盐若外部以此作安全凭据,强度不足
区域可能不存在直接访问 $areaCollection[code]['name']未配置区域时 warning/空字段
单条异常被吞foreach 内 catch 后继续MQ 最终 ACK,失败账号不自动重试
无同步结果表只输出/日志难以统计最后成功时间和失败原因
全量任务慢每账号 sleep 1 秒大量账号时窗口长
仅按 sids 更新一个站点全部账号扫描小改动也可能产生大量外部请求
Udesk 自定义字段 ID 是外部配置的一部分。若外部重建字段,代码常量必须同步更新。

25. 幂等、重复消息和并发

25.1 IM 消息

msgId 被设置为日志 Request ID,但仓库中未看到 DGJ 本地“已处理 msgId”唯一记录。

RabbitMQ NACK 重投时,RobotFacade 可能再次查询、再次回复或再次下单。是否由 IM Center/发送端按 msgId 去重,需要外部确认。

25.2 会话状态

同一会话并发处理两条消息时可能出现:

sequenceDiagram
  participant M1 as 消息1
  participant M2 as 消息2
  participant DB as Conversation
  M1->>DB: 读取 state=product_list
  M2->>DB: 读取 state=product_list
  M1->>DB: 写 state=order_placing
  M2->>DB: 写 state=product_querying
  Note over DB: 后写覆盖前写,Context也可能交叉

目标 SQL 没有 version 字段,Repository 是否通过事务/锁串行化需确认。

25.3 报价和下单

goodsMap 存在 Redis,默认会话短 TTL。并发刷新列表后,用户回复旧序号可能映射到新商品。回复卡片应携带稳定商品 ID 或查询版本,而不是只依赖会话内序号。


26. 可观测性

26.1 追踪标识

标识来源用途
msgIdIM CenterMQ 消息和接收确认
traceIdMessageContextRobotV2 全链追踪
conversation_id会话表状态和 Context
cv_key会话策略按用户/群查活跃会话
requestID微信入口微信链日志
billNo报价/销售单业务结果追踪

26.2 RobotFacade 调试输出

handleMessage() 在主路径中包含大量 echo,包括适配器、用户、状态和异常堆栈。这对 CLI 调试有帮助,但在常驻消费者中会产生大量标准输出,并可能暴露消息元数据。

建议全部改为结构化 Logger,并按日志级别控制。


27. 微信不回复排查

flowchart TD
  A["微信未回复"] --> B{"入口是否收到通知?"}
  B -->|否| C["查微信机器人/长连接入口"]
  B -->|是| D{"回复黑名单/配置?"}
  D -->|禁用| E["符合静默规则"]
  D -->|允许| F{"走V1还是V2?"}
  F -->|V1| G["查RobotInquiry日志/Redis会话"]
  F -->|V2| H["查Adapter/Intent/State/Reply"]
  G --> I{"外部AI异步?"}
  H --> I
  I -->|是| J["查pre_conversation/回调/notify TTL"]
  I -->|否| K["查商品过滤或发送Provider"]

检查顺序:

  1. 原始消息是否进入 DGJ。
  2. 机器人 wxid、群 ID、客户绑定是否正确。
  3. 是否在回复黑名单或配置为静默。
  4. Redis 会话是否仍存在。
  5. 图片/语音/AI 是否处于预会话等待。
  6. Intent 和状态是否符合预期。
  7. 是否产生 ReplyData。
  8. 微信发送 Provider 是否成功。

28. 腾讯 IM 不回复排查

28.1 分层检查

层检查
腾讯 IM消息是否进入 IM Center
MQimcenter_notify.im_app_biz_message 是否有消息
ConsumerImCenterNotify 是否常驻、是否 NACK 重试
AdapterTencentImAdapter::supports/adapt 是否成功
身份bizExtInfo 能否解析 sid/contactId/group
会话腾讯 IM 会话表是否 active、未过期
IntentHandler 命中哪一类
State状态链和最后 ReplyData
SendersendImMessage 返回是否为 true

28.2 常用日志关键词

rg -n "收到IM业务消息|消息同步结果通知|IM业务消息处理失败" application/controllers/tasks/ImCenterNotify.php
rg -n "适配完成|责任链完成|状态转换|状态持久化失败|响应发送完成" application/Services/MoveMall/RobotV2/RobotFacade.php
rg -n "TencentImAdapter|TencentImResponseSender|sendImMessage" application/Services/MoveMall/RobotV2 application/Providers/ImCenter

28.3 “Consumer ACK 但没回复”

重点看 RobotFacade 是否返回了 success=false 的格式化错误。由于异常可能在 Facade 内被吞,Consumer 日志仍可能记录“处理完成”并 ACK。


29. 会话卡住排查

29.1 SQL

实际表名先以环境为准。

SELECT id, cv_key, sid, contact_id, wxid, chat_group_id,
       status, state, type, created_at, updated_at, expires_at
FROM t_robot_conversation_tencent_im
WHERE cv_key = :cv_key
ORDER BY id DESC
LIMIT 20;
SELECT id, conversation_id, `key`, `type`,
       CHAR_LENGTH(`value`) AS value_chars,
       updated_at, expires_at
FROM t_robot_conversation_context
WHERE conversation_id = :conversation_id
ORDER BY `key`;
SELECT id, conversation_id, status, created_at, updated_at
FROM t_robot_conversation_pre
WHERE conversation_id = :conversation_id
ORDER BY id DESC;

29.2 判断方法

状态可能原因
长期 vin_parsingVIN 外部调用异常、状态未持久化
长期 car_model_confirming用户序号没被识别或车型 Context 丢失
长期 product_querying查询异常后失败结果未转换状态
长期 image_recognition/ai_parsingAI 回调未到或预会话未关联
product_list 但无商品 Context状态写成功、Context 写失败
expired 仍被继续使用Repository active 判断或时钟问题

29.3 安全修复原则

不要只改 state。至少一起检查:

  • 会话 status/expires_at;
  • VIN/车型 Context;
  • 最近查询条件和商品列表;
  • 预会话状态;
  • 是否已经创建报价或订单。

30. 重复回复/重复下单排查

flowchart TD
  A["重复回复或下单"] --> B{"msgId相同?"}
  B -->|是| C["MQ重投/消费者并发/无幂等"]
  B -->|否| D["平台重复推送或用户重复发送"]
  C --> E["查同msgId日志次数"]
  D --> F["查消息时间和内容"]
  E --> G{"是否产生业务单?"}
  F --> G
  G -->|是| H["按来源消息/会话/商品查重复订单"]
  G -->|否| I["仅发送重复,查Sender幂等"]

推荐幂等键:

动作建议幂等键
消息消费platform + msgId
AI 回调外部 callback ID 或 preConversation ID
报价单conversation_id + source_msg_id
序号下单conversation_id + list_version + source_msg_id
平台回复source_msg_id + reply_type + business_id

31. 报价排查

31.1 一直“等待服务站报价”

  1. 查询报价主单和明细。
  2. 检查商品价格是否为 0、obsolete=2。
  3. 查 t_scm_robot_offer_order_apply 状态。
  4. 查服务站页面是否提交 salePrice > 0。
  5. 检查 conversation_key 对应 Redis notify 是否已超过 10 分钟。

31.2 SQL

SELECT billNo, sid, buId, uid, createTime
FROM t_scm_offer_order
WHERE billNo = :bill_no;
SELECT billNo, invId, skuId, price, num, obsolete, isDelete
FROM t_scm_offer_order_info
WHERE sid = :sid AND billNo = :bill_no;
SELECT id, sid, bu_id, bill_no, inv_id, num, status,
       source_type, conversation_key
FROM t_scm_robot_offer_order_apply
WHERE bill_no = :bill_no;

31.3 发送失败的一致性

sendOfferSheet() 先更新数据库,再调用微信发送,且没有事务/Outbox。应增加发送状态和补发任务,避免价格已经生效但客户没有收到链接。


32. IM 群成员排查

32.1 期望成员计算

客户当前分仓可用账号
+ 服务站总仓可用账号
- 已禁用/删除账号
= 期望群成员

32.2 查询顺序

  1. 客户与门店关系是否正确。
  2. 账号 storeLever/storeDefault/status。
  3. getAccountByStoreLevel() 返回。
  4. DGJ 计算的 join/quit 请求日志。
  5. IM Center groupInfo() 实际成员。

32.3 已确认的集合风险

位置风险
getRoomUserByContactId数字数组用 + 合并会丢右侧同索引账号
changeRoomUserByStore旧账号为空时差集赋值受 $oldAccounts && 限制,需验证新账号是否未加入
总仓规则storeDefault 与变更集合判断复杂,门店切换需覆盖全客户回归
Provider默认吞异常,调用方未必知道入群失败

33. Udesk 排查

33.1 单账号未同步

  1. SYS_ADMIN 是否存在、未删除。
  2. 同 sid 是否有 roleid=0 主账号。
  3. 主账号省市区编码能否转名称。
  4. MQ 消息是否包含 sids 数组。
  5. dgj_udesk.udesk_customer_async 是否消费。
  6. Udesk 按 md5(sid.uid) token 是否能查到 customer。
  7. 日志中是否有逐账号异常但任务最终 ACK。

33.2 只读 SQL

SELECT a.uid, a.sid, a.username, a.name, a.status, a.mobile,
       a.isDelete, b.name AS station_name,
       b.province, b.eparchy, b.city
FROM t_sys_admin a
LEFT JOIN t_sys_admin b
  ON a.sid = b.sid AND b.roleid = 0
WHERE a.uid = :uid;

33.3 修复入口

单账号可使用 UdeskAsync::updateCustomerByUid($uid),但执行前应确认外部环境和账号,避免本地配置指向非预期 Udesk。


34. 新平台接入方法

接入新平台至少实现:

  1. MessageAdapter:原消息到 MessageContext。
  2. ConversationPolicy:会话 Key、TTL 和活跃规则。
  3. ConversationRepository:平台会话存储。
  4. 平台 Formatter:各类 ReplyData 的表现。
  5. ResponseSender:发送接口。
  6. 在三个 Factory 注册。
  7. 消息入口和认证。
  8. 幂等、重试和监控。
flowchart LR
  NP["新平台"] --> IA["Input Adapter"]
  IA --> CORE["RobotV2 Core"]
  CORE --> CP["Conversation Policy/Repo"]
  CORE --> OF["Output Formatter"]
  OF --> RS["Response Sender"]
  RS --> NP

不要在 State 中写平台 if/else;平台差异应留在适配和输出层。


35. 已确认风险清单

级别风险影响
P0RobotFacade catch 后返回错误,不抛给 Consumer系统异常可能被 ACK 丢失
P0IM 消费未见本地 msgId 幂等表重投可能重复回复/下单
P0并发消息无可见会话 version/锁状态和 Context 相互覆盖
P1状态持久化失败只记日志当前回复成功、下轮从旧状态继续
P1V1/V2 仍深度混用删除旧代码会破坏 V2 查询/下单
P1sendOfferSheet DB 更新和微信发送非原子报价成功但客户未收到
P1IM 用户列表用数组 + 合并总仓账号被丢弃
P1群成员 Provider 默认吞异常业务侧误判同步成功
P1Udesk 逐条异常吞掉并最终 ACK失败账号无自动重试
P1Udesk token 是可预测 MD5不应作为高强度安全令牌
P2RobotFacade 大量 echo/异常堆栈日志污染和元数据暴露
P2Context 可存大商品列表DB I/O 和会话体积持续增长
P2preConversation 清理部署未知pending/processing 长期堆积
P2钉钉类存在但未注册容易误判为已支持
P2Udesk 人工接管链未在仓库闭环业务能力认知偏差

36. 改动影响面

36.1 修改 Adapter

回归:平台身份、群 ID、消息类型、语音/图片、业务扩展 JSON、traceId。

36.2 修改 Handler 顺序

回归:VIN 与产品码冲突、数字序号与普通文本、图片与 AI 回调、特殊命令、Fallback 静默。

36.3 修改 State

回归:新会话、历史会话、瞬态连续转换、最大 10 次循环、状态保存失败、过期恢复。

36.4 修改查询/过滤

回归:维保、易损、全车件、电池、无车、价格范围、库存、门店仓、商品分组。

36.5 修改 Context Key

必须同时处理:旧 Key 迁移、读兼容、写新 Key、过期 Context、历史活跃会话。

36.6 修改 V1

先用 rg 查 RobotV2 的 Strategy/Service 是否调用对应方法。V1 不是一个可独立下线的孤岛。


37. 回归测试清单

37.1 输入与 Intent

  • [ ] 微信文本、腾讯 IM 文本都进入统一 Context。
  • [ ] 合法 17 位 VIN 正确识别。
  • [ ] XD/XS 业务单号不误判为 VIN。
  • [ ] 产品码、无车关键词、品牌、品类、方位正确识别。
  • [ ] 数字序号仅在有商品列表时作为选择/下单。
  • [ ] 未知消息按配置回复或静默。
  • [ ] 图片和语音识别成功/失败分支完整。

37.2 会话状态

  • [ ] 新会话从 idle 开始。
  • [ ] 单车型 VIN 进入 vin_parsed。
  • [ ] 多车型进入 car_model_confirming 并可选中。
  • [ ] 商品查询进入 product_list。
  • [ ] 继续筛选合并上轮 QueryContext。
  • [ ] 序号下单进入 order_placing 且只创建一次。
  • [ ] 会话过期后不复用旧 Context。
  • [ ] 单消息状态循环不超过 10 次。
  • [ ] 状态保存失败能告警且不误导为完整成功。

37.3 平台回复

  • [ ] 文本、图片、卡片、组合回复在微信可用。
  • [ ] 同类回复在腾讯 IM 可用。
  • [ ] autoSend=false 只返回 ReplyData,不发消息。
  • [ ] Sender 失败可被上层识别和重试。
  • [ ] 同 msgId 重投不重复回复。

37.4 商品和订单

  • [ ] 维保、易损、全车件、电池、无车查询正确。
  • [ ] 报价范围、价格、库存过滤有明确原因。
  • [ ] 服务站门店仓上下文正确。
  • [ ] 列表序号与商品稳定映射。
  • [ ] 报价有价/待报价两种分支正确。
  • [ ] 补报价后客户收到链接。
  • [ ] 报价转订单与普通订单状态一致。

37.5 IM 群

  • [ ] 客户首次分仓后正确拉群。
  • [ ] 客户切换分仓只增删差集账号。
  • [ ] 账号新增/删除门店权限更新对应群。
  • [ ] 总仓账号进入所有应有客户群。
  • [ ] 账号禁用退出群、启用重新入群。
  • [ ] 分仓账号和总仓账号合并不丢成员。
  • [ ] Provider 失败不会被当成功。

37.6 Udesk

  • [ ] 新账号创建 customer。
  • [ ] 姓名、手机号、区域、状态更新。
  • [ ] 缺区域编码时有默认值而非 warning。
  • [ ] 单账号失败可记录并重试。
  • [ ] 全量任务可断点续跑。
  • [ ] 禁用/删除账号的外部状态符合业务规则。

38. 常用 SQL 与 Redis

38.1 查旧机器人会话

SELECT *
FROM t_scm_robot_conversation
WHERE sid = :sid
  AND contact_id = :contact_id
ORDER BY id DESC
LIMIT 20;

38.2 查搜索日志

SELECT id, sid, contact_id, create_time
FROM t_scm_robot_search_log
WHERE sid = :sid
  AND contact_id = :contact_id
ORDER BY id DESC
LIMIT 100;

字段以生产 DDL 为准,先 DESC 再补充查询列。

38.3 Redis 只读

redis-cli HGETALL '<conversation-key>'
redis-cli TTL '<conversation-key>'
redis-cli HGET '<prefix>hash_robot_reply_black_list' '<field>'
redis-cli ZCARD '<prefix>robot_inquiry:delay_queue'

会话数据可能含用户消息、手机号、VIN 等信息。排查截图和公开文档必须脱敏。


39. 常用代码定位

rg -n "handleMessage|initHandlerChain|executeStateMachine|saveStateToConversation" application/Services/MoveMall/RobotV2/RobotFacade.php
rg -n "class .*Handler|new .*Handler" application/Services/MoveMall/RobotV2/Handlers application/Services/MoveMall/RobotV2/RobotFacade.php
rg -n "StateNames::|StateTransition::|stay\(|to\(" application/Services/MoveMall/RobotV2/States
rg -n "ConversationContextKeys::" application/Services/MoveMall/RobotV2 | sort
rg -n "RobotInquirySer" application/Services/MoveMall/RobotV2
rg -n "DEST_IMCENTER_NOTIFY|TYPE_IMCENTER_APP_BIZ_MESSAGE|msgSyncResult" application
rg -n "changeRoomUserByStore|changeRoomUserByRight|changeRoomUserByAdmin" application
rg -n "DEST_UDESK|udesk_customer_async|CustomerService" application
rg -n "showOfferSheet|aiCenterNotify|offerSheet|sendOfferSheet|parseVoice|parsePart" application/Services/Ai application/controllers

40. 发布与运行检查

检查方式
IM Consumertasks/imcenternotify/consume 常驻状态和最新日志
Udesk Consumertasks/udesknotify/consume 常驻状态
微信入口测试机器人是否在线并能收消息
AI 回调各环境回调 URL 可达,不公开具体内网地址
会话 DDL表、字段、索引与 Repository 一致
Context 清理过期任务最近运行时间
preConversation 清理pending/processing 超时数量
Redis会话 Key 前缀、TTL 和延迟队列
IM CenterUserSig、群详情和发消息只读/测试账号验证
Udesk测试账号 create/update 验证

41. 监控指标建议

指标建议告警
IM MQ 消费失败/NACK连续增长或超过重试阈值
同 msgId 处理次数>1 记录并检查是否幂等
RobotFacade success=false 但 Consumer ACK>0 立即告警
状态持久化失败>0 立即告警
状态机达到 10 次上限>0 立即告警
preConversation pending 超 5 分钟持续增长告警
会话 Context 平均/最大体积超基线告警
机器人回复成功率和 P95 延迟按平台和 Intent 统计
群成员同步失败率>0 应可重试
Udesk 同步失败账号数>0 进入失败队列
报价生成后未发送链接数>0 补发

42. 证据、推断与待确认

42.1 代码已确认

  • RobotV2 当前输入支持腾讯 IM、微信,Default Adapter 兜底。
  • 会话工厂支持微信、腾讯 IM、API 三种 Policy/Repository。
  • 输出发送器注册微信和腾讯 IM,钉钉未启用。
  • Handler 有明确优先级,状态机最多循环 10 次。
  • 状态分瞬态与稳定态,Context 采用 KV JSON。
  • 腾讯 IM 通过 imcenter_notify.im_app_biz_message 进入。
  • IM 消费者按业务异常 ACK、系统异常 NACK。
  • RobotFacade 自己会格式化捕获的 Exception。
  • IM 群成员由门店关系和账号权限变化驱动。
  • Udesk 同步来源为 SYS_ADMIN,不是修理厂联系人表。

42.2 根据代码推断

  • RobotFacade 内部错误可能被消费者 ACK,需要用真实异常测试确认。
  • 同会话并发消息可能覆盖状态/Context,需要看 Repository 和队列分区。
  • Udesk token 如果参与鉴权则安全强度不足;若只是外部幂等标识,风险较低。
  • V1 仍是 V2 的业务能力库,短期不能整体下线。

42.3 待环境确认

  1. 生产实际会话表名、DDL 和迁移版本。
  2. IM MQ 是否按群/会话保序、NACK 最大重试和 DLQ。
  3. IM Center 是否按 msgId 去重发送。
  4. preConversation 超时任务是否部署。
  5. RobotConversation Redis 延迟任务是否仍在线使用。
  6. Udesk 是否有坐席接管机器人会话的外部能力。
  7. 微信机器人 V1/V2 流量比例和灰度开关。
  8. AI Center 回调 SLA、重试和幂等 ID。
  9. 会话/消息数据保留周期与隐私要求。

43. 一页式排查卡

1. 拿到平台、sid、contactId、群ID、msgId/traceId、发生时间。

2. 确认入口:
   微信 -> inner/moveMall/Robot
   腾讯IM -> imcenter_notify.im_app_biz_message
   旧AI -> AiSer / AI Center callback
   Udesk -> dgj_udesk.udesk_customer_async

3. 确认链路:
   Adapter -> Intent -> Conversation -> State -> Query/Order -> Reply -> Sender

4. 查会话:
   status/state/expires_at
   Context中的VIN、车型、查询条件、商品列表
   preConversation是否超时

5. 查结果:
   有Reply无消息 -> Sender/外部平台
   无Reply且success -> Fallback或静默配置
   success=false仍ACK -> RobotFacade异常吞掉
   重复回复/下单 -> msgId幂等和并发

6. 群成员问题:
   客户分仓 + 账号门店权限 + 启停状态
   对比DGJ期望集合与IM Center实际成员

7. Udesk问题:
   注意同步对象是SYS_ADMIN账号
   查区域、MQ、逐账号异常和外部token记录

44. 相关文档

  • 06_移动商城_E站_智能询价与机器人.md:移动商城总体入口。
  • 13_MQ回调与补偿任务地图.md:IM/Udesk MQ 运行视角。
  • 21_搜索缓存与索引.md:商品搜索、缓存和索引。
  • 24_本地开发联调和日志定位.md:CLI Consumer 与日志。
  • 25_高风险公共文件影响面.md:修改机器人公共服务的影响评估。
  • 38_配送履约_自提骑手与三方配送.md:AI 控台配送能力。
  • 41_报价_智能询价_报价规则专题.md:报价规则深挖。

45. 最终结论

智能对话主链应被理解为:

平台消息
  -> 统一上下文
  -> Intent优先级
  -> 平台隔离会话
  -> 状态机与Context
  -> 商品/价格/库存/订单能力
  -> 统一ReplyData
  -> 平台发送

维护时最重要的四个原则:

  1. 平台差异留在边界层:不要把微信/腾讯 IM 判断散落到业务 State。
  2. 状态与 Context 同步演进:只改状态或只改业务数据都会让下一轮对话失忆。
  3. 消息和业务动作必须幂等:MQ 重试、AI 回调和用户连发都是正常场景。
  4. V1/V2/Udesk 边界要以调用证据为准:存在同名类或目录不代表功能已经迁移或人工接管已经闭环。

请求-日志-数据变更追踪卡

多入口请求链路

场景调用方与入口请求载荷/上下文Controller/ConsumerService/Provider汇合点最终业务事实
IM 入站IM Center MQ/长链路platform、用户、会话、消息、message IDtasks/ImCenterNotify.phpRobot/IM Serviceconversation ID用户消息进入会话记录与状态机
RobotV2/AI/inner/moveMall/Robot/Ai.phpconversation/request ID、场景、上下文Robot/Ai ControllerRobotFacade/AI Providerrequest+conversation IDAI 请求、异步结果和回复记录
人工工作台moveMall/ImWorkbench/AI console会话、客服、接管/转派动作Workbench ControllerConversation/Udesk Providerconversation/udesk ID机器人转人工和客服处理状态
定时会话任务tasks/RobotConversation超时窗、会话状态、批次CLI taskConversation Serviceconversation ID关闭超时会话或补发消息

日志证据矩阵

| 链路段 | 日志来源 | 可检索锚点 | 成功信号 | 失败信号 | 与下一段关联方式 | | --- | --- | --- | --- | --- | --- | --- | | IM 消费 | ImCenterNotify | message ID、platform、user、conversation | ACK 且消息记录一次写入 | 重复消息、用户/会话映射失败 | conversation ID 进入 Robot | | AI 调用/回调 | Robot/Ai/Provider | request ID、conversation ID、model/scenario | 外部受理,回调完成 | timeout、解析失败、旧回调 | request ID 关联记录/状态 | | 人工接管 | Workbench/Udesk Provider | conversation、agent、udesk ticket ID | owner/state 改为人工并收到回执 | 双重接管、外部工单未建 | ticket ID + conversation | | 超时任务 | RobotConversation task | task batch、conversation、last activity | 仅超时会话关闭/提醒 | 活跃会话误关、重复推送 | 批次+会话状态回查 |

环节数据变更台账

步骤代码位置事务读取事实写入表/缓存/MQ字段或数量变化回查证据
接收消息ImCenterNotify/ConversationManager消费事务message 幂等键、用户/活跃会话SCM_ROBOT_CONVERSATION(_RECORD)/预会话record insert;会话 last_time 更新message ID、conversation、记录数
机器人分发RobotFacade/QueryDispatcher会话事务state、scenario、历史消息会话状态、搜索/图片解析日志、AI 请求state old -> waiting;request ID/日志 insertconversation/request ID
AI 结果Robot callback Service回调事务waiting request、回调版本conversation/record、IM MQwaiting -> completed/failed;reply record insertrequest ID、回复条数、MQ ACK
人工接管ImWorkbench/Udesk本地与外部非原子当前 owner/state、客服会话/好友绑定、外部 ticketowner robot -> agent;ticket ID 保存conversation+agent+ticket
超时关闭RobotConversation task每会话事务last activity、当前 stateconversation、通知 MQactive/waiting -> closed/timeout;已关闭不重复通知batch、会话前后态

子模块追踪:im-ingress 腾讯 IM 消息入站

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
消费消息IM Center 投递文本/媒体事件message ID、conversation/user、event timeapplication/controllers/tasks/ImCenterNotify.php消息幂等、用户绑定、活跃/预会话和事件顺序单消息本地事务 insert conversation record,last_time old -> event timemessage ID + conversation/user + record ID重复 0 新记录;旧事件不回退会话时间/状态
分发后置新消息进入机器人/人工路由conversation/record ID、owner/stateapplication/Services/MoveMall/RobotV2/RobotFacade.php当前 owner、scenario、上下文和接管态会话本地事务 state old -> processing/waiting;AI/IM 出站事务外message + conversation + request ID本地已入站只补分发/回复,不重插消息

子模块追踪:robot-context RobotV2 会话 Context 与状态机

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
建立上下文首条/后续用户消息conversation ID、scenario、record/message IDapplication/Services/MoveMall/RobotV2/RobotFacade.php用户站点、历史消息、当前 state/intent 和预会话会话本地事务 context/version old -> new、state -> waitingconversation + context version + request ID并发用版本/锁防覆盖;关闭会话不接受旧消息推进
状态回查会话卡 waiting/状态错conversation、last request、recordsapplication/controllers/tasks/RobotConversation.php状态历史、请求结果、人工 owner 和 last_time查询只读 不写conversation + request IDs + timeline先定位缺失 AI 回调/IM 发送/超时段,再定向补偿

子模块追踪:robot-media VIN、图片与语音解析

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
媒体解析用户发送 VIN/图片/语音message/conversation ID、media ID/typeapplication/Services/MoveMall/RobotV2/RobotFacade.php文件可访问、媒体类型、会话和解析缓存外部 AI/识别事务外;本地 request/log none -> pending/completed/failedmessage + conversation + media/request ID文件/识别失败明确降级,不伪造 VIN/文本
解析回填异步识别结果request ID、result versionapplication/controllers/moveMall/RobotController.phpwaiting request、当前会话和重复回调回调本地事务写解析记录、context old -> parsedrequest + conversation + result version旧结果不覆盖新上下文;敏感 VIN/媒体日志脱敏

子模块追踪:robot-query 机器人商品查询与展示

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
商品查询解析意图后查配件/库存/价格conversation/request、sid、query/SKUapplication/Services/MoveMall/RobotV2/RobotFacade.phpcontext、商品索引、站点价格库存和业务可用性查询只读;保存查询日志/展示快照,不改商品库存request + conversation + query + result IDs搜索无结果与商品不可售分开;缓存旧只补索引/缓存
展示发送结果卡片发 IMconversation、result IDs、out message IDapplication/controllers/inner/moveMall/Robot.php查询快照、会话 owner/state 和发送状态IM 调用事务外;本地 reply record pending -> sent/failedconversation + request/out message IDs发送失败只补回复,不重复查询/下单

子模块追踪:robot-quote 旧 AI 报价与人工报价

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
AI 报价旧 Ai 控制器/机器人请求报价quote/request ID、conversation、itemsapplication/controllers/Ai.php用户、商品、价格规则、旧报价和状态报价本地事务 insert/update status none -> pending/completed;AI 调用事务外request/quote + conversation + item count超时不视为无价;按 request ID 回查后再重试
人工报价客服接管编辑/确认报价quote ID、agent、price/versionapplication/controllers/aiConsole/Courier.php当前报价、权限、版本和客户会话本地事务 owner robot -> agent、price/version old -> newquote + agent + before/after version并发版本冲突不覆盖;发送失败只补消息

子模块追踪:wechat-robot 微信机器人消息链

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
微信入站长连接/网关转发微信消息platform message ID、user/openId hash、conversationapplication/controllers/moveMall/RobotController.php去重、用户绑定、会话和消息类型入站本地事务 insert record、会话激活;后续 Kafka/IM/AI 事务外platform/message + conversation + record ID重复消息 0 新记录;凭证和 openId 脱敏
回复出站机器人/人工结果发微信conversation、reply/message IDapplication/Services/MoveMall/RobotV2/RobotFacade.phpreply record、通道状态和幂等键外部发送事务外;本地 pending -> sent/failedconversation + reply/external message IDs超时查通道送达;只补发送,不重复业务动作

子模块追踪:im-group IM 群成员维护

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
成员变更建群、加人、移除、角色调整group ID、user IDs、action/versionapplication/controllers/moveMall/ImWorkbench.php本地成员、权限、群状态和外部映射本地关系事务 old member set -> new;IM 调用事务外request ID + group + user/action + external code外部 timeout 按 group/user 查实际成员,避免重复添加
一致性回查本地/腾讯 IM 群成员不一致group ID、both member setsapplication/controllers/tasks/ImCenterNotify.php两端集合、事件版本和最后同步时间查询只读;确认后幂等 upsert/delete 本地关系group + diff + message/version外部为通信事实,本地权限仍需校验;小批修复

子模块追踪:udesk-sync Udesk 同步与人工接管

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
人工接管会话转 Udesk 客服conversation、agent/ticket ID、reasonapplication/controllers/moveMall/ImWorkbench.php当前 robot owner/state、客服和既有 ticket本地会话事务 owner robot -> agent/pending;Udesk 调用事务外request ID + conversation + local/external tickettimeout 按 conversation 查 ticket,禁止重复工单
回调同步Udesk 工单/消息/结束回调message ID、ticket/conversation、statusapplication/controllers/tasks/ImCenterNotify.php映射、当前 owner/state 和回调版本单回调本地事务 pending/agent -> active/closed、写人工消息message + ticket/conversation + status乱序不回退 closed;只补消息/状态,不重建会话

子模块追踪:robot-timeout 重复消息、并发与超时任务

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
并发幂等同 message/request 多次到达message/request/conversation IDsapplication/Services/MoveMall/RobotV2/RobotFacade.php已有 record/request、context version 和当前 state单消息本地事务命中已处理则 0 新增;版本条件更新 contextall IDs + affected rows随机回调 ID 不替代原业务键;旧结果不覆盖新轮次
超时关闭定时扫描 active/waiting 会话task batch、last_time、conversationapplication/controllers/tasks/RobotConversation.php当前 state、最后活动、人工接管和 pending request每会话本地事务 active/waiting -> timeout/closed,通知 commit 后task + conversation + old/new state已关闭重跑 0 通知;AI 结果不确定先查 request 终态