从零读懂 AI 智能客服(一):模块职责与 SSE 聊天链路(后端架构)
2026/9/7 10:36:18 网站建设 项目流程

本文主题:先建立后端全局认知,再走通一次 AI 对话从请求入口到 SSE 返回的主链路
适合读者:刚接手 Spring Boot 多模块项目,希望系统理解 AI 客服后端的 Java 开发者
代码基线:当前学习分支源码快照


🐟这里是yurenpai

27届开发者,主要学习 Java 后端与 AI 应用开发。

这里记录真实项目中的代码调用链、Agent/RAG 工程化、问题排查和开发复盘。

个人理念:

阅读大型项目时,先建立地图,再进入街道,比一开始扎进某个类更高效。


写在前面

先说结论:这个后端不是由多个互不相关的服务拼起来的,而是一个以 Maven 多模块组织、由 Spring Boot 统一装配的模块化单体。普通业务遵循 Controller、Service、Mapper 的经典链路;AI 对话则在此基础上增加了上下文解析、场景路由、模型适配、RAG、SSE 和消息持久化等能力。

本文解决四个问题:

  1. 根工程、公共模块、业务模块、启动模块和扩展模块分别负责什么;
  2. 普通 CRUD 与 AI 对话链路为什么复杂度不同;
  3. 一次聊天请求如何从ChatRequest进入ChatServiceFacade
  4. 固定回复和大模型回复最终如何通过 SSE 返回并保存。

说明:内容基于当前学习分支的源码快照进行静态分析,不同分支或后续版本的目录与实现可能发生变化。

代码说明:除明确标注为完整源码外,文中的代码片段均根据当前源码提炼为简化示意,只保留与本节有关的字段、注解或调用关系;文中的“已核对”表示完成静态源码核对,不等同于运行测试通过。


一、先建立后端整体架构认知

后端根目录:

D:\Tools\java\aiwork\tst-pharma-main-backend\ ├─ pom.xml ├─ tst-pharma-admin\ ├─ tst-pharma-common\ ├─ tst-pharma-modules\ ├─ tst-pharma-extend\ ├─ docs\ └─ logs\

这不是四个互不相关的后端项目,而是一个:

Maven 多模块的 Spring Boot 单体应用

可以先记住这张关系图:

根 pom.xml │ ┌──────────────────┼──────────────────┐ │ │ │ ▼ ▼ ▼ tst-pharma-common tst-pharma-modules tst-pharma-extend 公共基础能力 业务模块 独立扩展服务 │ │ └──────────┬───────┘ ▼ tst-pharma-admin 组装并启动主应用

依赖方向大致是:

common ↑ modules ↑ admin

也就是:

业务模块依赖公共模块 启动模块依赖业务模块

不能反过来让:

common 依赖 chat system 依赖 admin

否则很容易出现循环依赖。


二、根pom.xml是干什么的

文件:

D:\Tools\java\aiwork\tst-pharma-main-backend\pom.xml

它的打包方式是:

<packaging>pom</packaging>

说明它自己不直接生成可运行的业务 JAR,主要负责三件事。

1. 聚合模块

<modules><module>tst-pharma-admin</module><module>tst-pharma-common</module><module>tst-pharma-extend</module><module>tst-pharma-modules</module></modules>

在根目录执行 Maven 构建时,会按照模块依赖关系一起编译。

2. 统一版本

当前主要技术版本包括:

Java 17 Spring Boot 3.5.8 MyBatis-Plus 3.5.14 Sa-Token 1.44.0 Redisson 3.51.0 LangChain4j 1.13.0 Weaviate Client 1.19.6

子模块不需要分别写一遍版本号。

3. 统一构建规则

例如:

Maven 编译插件 测试插件 Spring Boot 依赖管理 不同环境的 Maven Profile

因此根pom.xml可以理解为:

整个后端工程的“总目录和统一规则”。

小鱼点睛

根 POM 负责统一规则和聚合构建,tst-pharma-admin才是把业务模块装入同一个 Spring Boot 应用的启动者。聚合构建运行时装配不是一回事。


三、tst-pharma-admin:启动和装配层

目录:

D:\Tools\java\aiwork\tst-pharma-main-backend\tst-pharma-admin\ ├─ pom.xml └─ src\main\ ├─ java\com\tst\pharma\ │ ├─ TstPharmaApplication.java │ ├─ TstPharmaServletInitializer.java │ ├─ config\ │ │ └─ MapperConflictResolver.java │ └─ controller\ │ ├─ AuthController.java │ ├─ CaptchaController.java │ └─ IndexController.java └─ resources\ ├─ application.yml ├─ application-dev.yml ├─ application-prod.yml ├─ logback-plus.xml └─ banner.txt

它为什么业务代码很少

因为它不是主要业务模块,而是:

把其他模块装到一起,然后启动 Spring Boot。

它的pom.xml依赖了:

tst-pharma-system tst-pharma-generator tst-pharma-chat tst-pharma-workflow tst-pharma-aiflow

所以最后运行的是:

tst-pharma-admin

但是实际加载进去的业务包括:

系统管理 AI 聊天 知识库 业务工作流 AI 流程编排 代码生成

启动类

文件:

D:\Tools\java\aiwork\tst-pharma-main-backend\tst-pharma-admin\src\main\java\com\tst\pharma\TstPharmaApplication.java

核心注解:

@SpringBootApplicationpublicclassTstPharmaApplication{}

启动类位于:

com.tst.pharma

而其他模块中的类也基本位于:

com.tst.pharma.xxx

因此 Spring Boot 启动后会扫描依赖中的:

@Controller@RestController@Service@Component@Configuration

这就是为什么ChatController明明不在admin目录中,启动admin后仍然能访问。

当前启动类的特殊行为

当前main()启动之前执行了:

killPortProcess(6039);

含义是:

启动程序 ↓ 检查 6039 端口 ↓ 如果被占用 ↓ 在 Windows 上通过 taskkill 强制结束占用进程 ↓ 再启动 Spring Boot

这不是 Spring Boot 标准行为,而是项目自定义行为。

所以以后发现某个使用 6039 的 Java 进程被结束,需要先想到这里。


四、tst-pharma-common:公共基础能力层

目录:

D:\Tools\java\aiwork\tst-pharma-main-backend\tst-pharma-common\

它不是一个单独业务,而是一组可复用的公共组件。

必须优先掌握

tst-pharma-common-core ├─ 公共返回对象 R ├─ 异常 ├─ 工具类 └─ 基础常量 tst-pharma-common-web ├─ BaseController ├─ Web 配置 ├─ 全局异常处理 └─ Web 过滤器 tst-pharma-common-mybatis ├─ BaseMapperPlus ├─ PageQuery ├─ TableDataInfo ├─ MyBatis-Plus 配置 └─ 数据库公共实体 tst-pharma-common-satoken ├─ 登录用户获取 ├─ 权限认证 └─ LoginHelper tst-pharma-common-redis ├─ RedisUtils ├─ Redisson └─ 缓存公共能力 tst-pharma-common-sse ├─ SseEmitterManager ├─ SseMessageUtils ├─ SSE 事件对象 └─ SSE 连接管理 tst-pharma-common-chat ├─ ChatRequest ├─ ChatModelVo ├─ IChatService ├─ IChatModelService └─ 工作流与聊天共用接口

用到的时候再深入

common-json common-log common-tenant common-doc common-encrypt common-security common-idempotent common-sensitive

当前阶段不用逐个阅读

common-excel common-mail common-sms common-oss common-social common-websocket common-job common-translation

你要注意:

common不是“杂物目录”,而是可以被多个业务模块复用、且不应该依赖具体业务的基础组件。

例如 SSE 连接不只聊天模块能用,所以放在:

tst-pharma-common-sse

而不是直接塞进:

tst-pharma-chat

五、tst-pharma-modules:主要业务代码

目录:

D:\Tools\java\aiwork\tst-pharma-main-backend\tst-pharma-modules\ ├─ tst-pharma-system\ ├─ tst-pharma-chat\ ├─ tst-pharma-aiflow\ ├─ tst-pharma-workflow\ └─ tst-pharma-generator\

1.tst-pharma-system

主要负责:

用户 角色 菜单 部门 岗位 字典 系统参数 租户 登录日志 操作日志

它属于后台系统基础业务。

2.tst-pharma-chat

这是我们现在最需要掌握的模块,负责:

聊天会话 聊天消息 模型配置 模型供应商 模型调用 系统提示词 客服场景分类 医疗风险路由 上下文实体识别 固定回复 SSE 流式响应 知识库 文档切片 Embedding 向量检索 RAG Rerank MCP 智能体 可观测性

因此它不是一个简单的聊天 CRUD 模块。

3.tst-pharma-aiflow

负责:

AI 节点 模型节点 知识库节点 条件节点 节点输入输出 AI 流程编排

它解决的是:

多个 AI 节点怎样按照流程执行。

4.tst-pharma-workflow

负责普通业务流程,例如:

审批 流程节点 流程实例 任务流转 业务办理

注意区分:

aiflow AI 能力和节点的编排 workflow 传统业务审批和流程流转

5.tst-pharma-generator

负责:

读取数据库表结构 生成 Entity 生成 BO、VO 生成 Mapper 生成 Service 生成 Controller 生成前端 CRUD 页面

它主要是开发辅助工具。


六、tst-pharma-extend:独立扩展服务

目录:

D:\Tools\java\aiwork\tst-pharma-main-backend\tst-pharma-extend\ ├─ tst-pharma-monitor-admin\ └─ tst-pharma-snailjob-server\

分别用于:

monitor-admin └─ Spring Boot Admin 服务监控 snailjob-server └─ 定时任务和分布式任务调度

它们与admin不完全相同。

admin是主业务应用入口,而这两个更接近独立辅助服务。你当前学习聊天代码时,只需要知道它们的用途,不需要深入。


七、一个普通业务模块的标准分层

用系统参数配置作为例子。

源码结构:

D:\Tools\java\aiwork\tst-pharma-main-backend\ └─ tst-pharma-modules\tst-pharma-system\src\main\java\com\tst\pharma\system\ ├─ controller\system\ │ └─ SysConfigController.java ├─ service\ │ ├─ ISysConfigService.java │ └─ impl\ │ └─ SysConfigServiceImpl.java ├─ mapper\ │ └─ SysConfigMapper.java └─ domain\ ├─ SysConfig.java ├─ bo\ │ └─ SysConfigBo.java └─ vo\ └─ SysConfigVo.java

标准调用关系:

HTTP 请求 ↓ Controller ↓ Service 接口 ↓ ServiceImpl ↓ Mapper ↓ MyBatis-Plus ↓ MySQL

返回过程:

MySQL 查询结果 ↓ Entity ↓ 对象转换 ↓ VO ↓ Controller ↓ JSON ↓ 前端

每一种类的职责

Controller
SysConfigController.java

负责:

接收 HTTP 请求 校验参数 检查权限 调用 Service 包装返回结果

例如:

@GetMapping("/list")publicTableDataInfo<SysConfigVo>list(SysConfigBoconfig,PageQuerypageQuery){returnconfigService.selectPageConfigList(config,pageQuery);}

Controller 不应该直接写 SQL,也不应该堆很多复杂业务判断。

Service 接口
ISysConfigService.java

负责定义:

系统参数模块能提供哪些业务能力

例如:

查询配置 新增配置 修改配置 删除配置 刷新缓存
ServiceImpl
SysConfigServiceImpl.java

负责真正实现业务逻辑:

检查配置键是否重复 读写数据库 更新缓存 校验内置配置能否删除
Mapper
SysConfigMapper.java

负责数据库访问:

publicinterfaceSysConfigMapperextendsBaseMapperPlus<SysConfig,SysConfigVo>{}

因为继承了BaseMapperPlus,很多基础 CRUD 不用重复编写。

Entity
SysConfig.java

对应数据库表:

@TableName("sys_config")publicclassSysConfigextendsTenantEntity{}

主要用于:

数据库字段映射 MyBatis-Plus 查询 数据库新增修改
BO
SysConfigBo.java

BO 是 Business Object,主要接收业务输入。

例如:

@NotBlank(message="参数名称不能为空")privateStringconfigName;

它可以带:

参数校验 查询条件 业务输入字段
VO
SysConfigVo.java

VO 是 View Object,用于返回前端。

它只暴露前端需要的数据,避免把数据库 Entity 直接返回。


八、为什么tst-pharma-chat比普通 CRUD 复杂

聊天模块顶级结构:

D:\Tools\java\aiwork\tst-pharma-main-backend\ └─ tst-pharma-modules\tst-pharma-chat\src\main\java\com\tst\pharma\ ├─ agent\ ├─ config\ ├─ constant\ ├─ controller\ ├─ domain\ ├─ enums\ ├─ factory\ ├─ mapper\ ├─ mcp\ ├─ observability\ └─ service\

普通 CRUD 只需要

Controller Service Mapper Entity、BO、VO

AI 聊天还要解决

使用哪个模型? 模型是哪家供应商? 请求是否属于医疗高风险? 是否属于客服范围? 是否直接固定回复? 是否需要查询知识库? 是否需要把历史消息交给模型? 怎样流式返回? 什么时候保存用户消息? 什么时候保存助手消息? 模型出错后怎样关闭 SSE? 同一用户开多个窗口怎样隔离?

所以额外出现了:

factory ├─ 根据供应商选择模型适配实现 provider / chat impl ├─ 构建不同供应商的 ChatModel prompt ├─ 系统提示词 scene ├─ 客服场景识别和路由 scene\context ├─ 最近消息和上下文实体解析 retrieval ├─ 检索编排 vector ├─ 向量库操作 embed ├─ 文本向量化 rerank ├─ 检索结果重排序 observability ├─ 模型调用和检索过程观测 mcp ├─ 外部工具能力

小鱼点睛

AI 聊天没有抛弃 Controller、Service 和 Mapper,而是在传统持久化链路之上增加了上下文、路由、模型、RAG 与 SSE 编排。复杂度来自新增的决策和传输职责,而不是目录名称本身。


九、common-chattst-pharma-chat的区别

这两个名字很容易混淆。

tst-pharma-common-chat

公共聊天协议和接口

例如:

ChatRequest ChatModelVo IChatService IChatModelService RoleType

文件:

D:\Tools\java\aiwork\tst-pharma-main-backend\tst-pharma-common\tst-pharma-common-chat\src\main\java\com\tst\pharma\common\chat\domain\dto\request\ChatRequest.java

tst-pharma-chat

聊天业务的真正实现

例如:

ChatController ChatServiceFacade ChatMessageServiceImpl ChatSceneRouter 知识库检索 模型供应商实现

这样设计的主要好处是:

workflow、aiflow 等其他模块 可以只依赖 common-chat 中的接口 而不必直接依赖完整的 chat 实现模块

这是在降低跨模块耦合。


十、开始走完整聊天链路

先看完整地图:

前端发送 POST /chat/send │ ▼ Sa-Token 登录认证、Web 过滤器 │ ▼ ChatRequest 反序列化和参数校验 │ ▼ ChatController.sseChat() │ ▼ ChatServiceFacade.sseChat() │ ├─ 校验图片地址 ├─ 获取服务端登录用户 ├─ 获取登录 Token ├─ 生成当前窗口 SSE 标识 ├─ 构建轻量上下文 ├─ 执行客服场景路由 ├─ 创建 SSE 连接 └─ 保存用户消息 │ ▼ 根据路由结果分流 │ ┌─────────┼─────────────┐ │ │ │ ▼ ▼ ▼ 固定回复 工作流/思考模式 普通模型聊天 │ │ │ ├─ 查询模型配置 │ ├─ 加入系统提示词 │ ├─ 加载历史消息 │ ├─ 可选知识库 RAG │ ├─ 可选图片 │ ├─ 选择模型供应商 │ └─ 调用流式大模型 │ │ ▼ ▼ 发送固定 SSE onPartialResponse 保存助手消息 │ 发送 done ▼ 关闭连接 持续发送 SSE content │ ▼ onCompleteResponse │ 保存完整助手消息 发送 done 关闭 SSE

十一、第一站:ChatRequest

文件:

D:\Tools\java\aiwork\tst-pharma-main-backend\tst-pharma-common\tst-pharma-common-chat\src\main\java\com\tst\pharma\common\chat\domain\dto\request\ChatRequest.java

主要分成两类字段。

前端传入的字段

model ├─ 使用的模型名称 content ├─ 用户输入内容 imageUrls ├─ 用户上传的图片,最多 4 张 sessionId ├─ 会话 ID knowledgeId ├─ 知识库 ID appId ├─ 应用 ID uuid ├─ 当前聊天窗口 ID enableThinking ├─ 是否开启深度思考 enableWorkFlow ├─ 是否使用工作流 workFlowRunner ├─ 工作流参数 isResume、reSumeRunner └─ 工作流人机交互恢复参数

其中:

@NotEmptyprivateStringmodel;@NotEmptyprivateStringcontent;

会经过@Valid校验。

后端运行时补充的字段

chatModelVo ├─ 后端从数据库查到的模型完整配置 emitter ├─ 当前 SSE 连接 userId ├─ 服务端登录用户 ID tokenValue ├─ 服务端 Token sseConnectionId ├─ 当前窗口的 SSE 唯一标识 contextMessages └─ 最终交给模型的完整上下文

尤其要记住:

userId 和 tokenValue 不能信任前端传入值 必须由后端登录状态获取

当前代码确实是通过:

LoginHelper.getUserId(); StpUtil.getTokenValue();

获得。


十二、第二站:ChatController

文件:

D:\Tools\java\aiwork\tst-pharma-main-backend\tst-pharma-modules\tst-pharma-chat\src\main\java\com\tst\pharma\controller\chat\ChatController.java

核心代码非常简单:

@PostMapping("/send")@ResponseBodypublicSseEmittersseChat(@RequestBody@ValidChatRequestchatRequest){returnchatService.sseChat(chatRequest);}

它只做三件事:

1. 接收 POST /chat/send 2. 把 JSON 转成 ChatRequest 并校验 3. 调用 ChatServiceFacade

这里没有写场景判断、模型调用、知识库查询,这是正确的。

因为 Controller 的职责只是:

HTTP 边界,不应该成为业务逻辑中心。


十三、第三站:ChatServiceFacade.sseChat()

文件:

D:\Tools\java\aiwork\tst-pharma-main-backend\tst-pharma-modules\tst-pharma-chat\src\main\java\com\tst\pharma\service\chat\impl\ChatServiceFacade.java

这是聊天主链路的编排中心。

它不是普通的 CRUD Service,而是一个:

Facade 门面服务:把认证、上下文、路由、SSE、知识库、模型调用和消息保存串起来。

核心执行顺序如下。

第一步:校验图片

validateImageUrls(chatRequest.getImageUrls());

检查:

图片数量 URL 长度 URL 协议 不安全地址

第二步:获取登录用户

LonguserId=getCurrentUserId();StringtokenValue=getCurrentTokenValue();

不是从请求体直接取用户身份。

第三步:生成 SSE 连接标识

StringsseConnectionId=buildSseConnectionId(tokenValue,chatRequest);

优先使用:

Token + uuid

没有uuid时使用:

Token + sessionId

如果两者都没有,当前代码会兼容旧调用并退回到仅使用Token。因此,新前端应尽量稳定传入uuidsessionId,避免同一 Token 下的连接相互替换。

这样同一个登录用户可以同时打开多个聊天窗口,互不关闭对方的 SSE 连接。

关系是:

userId └─ sseConnectionId A └─ sseConnectionId B └─ sseConnectionId C

第四步:构建轻量路由上下文

ChatRoutingContext routingContext = chatContextResolver.resolve(chatRequest, userId);

文件:

D:\Tools\java\aiwork\tst-pharma-main-backend\tst-pharma-modules\tst-pharma-chat\src\main\java\com\tst\pharma\service\chat\scene\context\ChatContextResolver.java

它只读取:

当前输入 当前图片 最近最多 20 条文本消息 上一条助手回复 订单号、运单号、处方号等上下文实体 药品名称

它明确不会做:

不调用大模型 不调用 RAG 不调用业务接口 不发送 SSE

产生:

ChatRoutingContext ├─ currentContent ├─ hasCurrentImages ├─ recentMessages ├─ entities └─ lastAssistantMessage

第五步:客服场景路由

ChatRouteResult routeResult = chatSceneRouter.route(routingContext);

路由器:

D:\Tools\java\aiwork\tst-pharma-main-backend\tst-pharma-modules\tst-pharma-chat\src\main\java\com\tst\pharma\service\chat\scene\ChatSceneRouter.java

当前真实顺序是:

1. 医疗风险识别 2. 订单、物流、处方、售后、人工等业务分类 3. 客服服务范围判断 4. 范围外连续追问判断 5. 上下文指代和信息不足判断 6. 决定固定回复还是继续请求模型

路由结果:

ChatRouteResult ├─ sceneType ├─ riskLevel ├─ action ├─ matchedRule └─ replyTemplate

常见action

DIRECT_REPLY ├─ 医疗风险固定回复 SCOPE_GUIDE ├─ 范围外引导 CLARIFY ├─ 信息不足,要求用户补充 BUSINESS_PLACEHOLDER ├─ 业务接口暂未真正接通时的占位回复 HUMAN_GUIDE ├─ 引导人工客服 CONTINUE_CHAT └─ 继续进入系统提示词、RAG 和大模型

第六步:建立 SSE

SseEmitteremitter=sseEmitterManager.connect(userId,sseConnectionId);

连接管理器位于:

D:\Tools\java\aiwork\tst-pharma-main-backend\tst-pharma-common\tst-pharma-common-sse\src\main\java\com\tst\pharma\common\sse\core\SseEmitterManager.java

它维护的结构相当于:

Map<用户ID,Map<SSE连接标识,SseEmitter>>

所以一个用户可以拥有多个窗口连接。

第七步:保存用户消息

chatMessageService.saveChatMessage( userId, chatRequest.getSessionId(), chatRequest.getContent(), RoleType.USER.getName(), chatRequest.getModel() );

无论后面进入:

固定回复 工作流 普通大模型

用户原始消息都先统一保存一次。

小鱼点睛

聊天入口的关键顺序是:先从服务端取得可信身份,再建立用户隔离的 SSE 标识和路由上下文,最后保存原始消息并分流。前端传来的userId或 Token 不能作为认证依据。


十四、两条最重要的后续分支

分支 A:固定回复

例如:

医疗紧急情况 严重不良反应 停药换药 范围外问题 上下文不明确 人工客服 业务接口占位

调用:

FixedReplyService

链路:

replyTemplate ↓ FixedReplyTemplateProvider ↓ 获得固定安全文案 ↓ SSE 发送 content ↓ 保存助手消息 ↓ SSE 发送 done ↓ 关闭连接

这条链路:

不查询模型配置 不调用知识库 不调用大模型

分支 B:继续模型聊天

只有路由动作是:

CONTINUE_CHAT

才进入模型链路:

ChatModelVo chatModelVo = requireChatModel(chatRequest);

然后构建最终上下文:

SystemMessage ↓ 历史对话 ↓ 经过 RAG 和图片增强的当前 UserMessage

具体顺序:

1. 加入全局系统提示词 2. 如果有 knowledgeId,执行知识库 RAG 3. 如果有图片,添加图片内容 4. 从数据库加载会话历史 5. 移除刚保存的重复用户消息 6. 把增强后的当前用户消息放到最后

之后:

AbstractChatService chatService = chatServiceFactory.getOriginalService(providerCode);

工厂根据模型供应商选择实现:

OpenAI 兼容供应商 Ollama 通义 智谱 其他自定义供应商

再构建:

StreamingChatModel streamingChatModel = chatService.buildStreamingChatModel( chatModelVo, chatRequest );

最终调用:

streamingChatModel.chat(contextMessages, handler);

十五、模型返回后的 SSE 链路

模型每返回一个片段,就触发:

onPartialResponse(StringpartialResponse)

处理:

片段追加到 StringBuilder ↓ SSE 发送 content 事件 ↓ 前端逐字显示

模型返回完成后触发:

onCompleteResponse(ChatResponse completeResponse)

处理:

取出完整助手回复 ↓ 保存助手消息到数据库 ↓ 发送 done 事件 ↓ 关闭 SSE 连接

出现异常时:

记录后端异常 ↓ 向前端发送固定安全错误文案 ↓ 关闭 SSE 连接

前端不会直接看到模型供应商的原始异常、密钥或内部接口信息。


十六、阅读时先记住这三张图

后端模块图

common → 公共积木 modules → 业务实现 admin → 组装并启动 extend → 独立辅助服务

普通 CRUD 图

Controller → Service → ServiceImpl → Mapper → MySQL

AI 聊天图

Controller → ChatServiceFacade → 上下文解析 → 场景路由 → 固定回复 / 工作流 / 大模型 → SSE → 消息保存

十七、接下来阅读源码的正确顺序

阅读下一阶段源码时,不建议直接从 872 行的ChatServiceFacade第一行读到最后一行。

建议按下面顺序:

第 1 组:请求入口 ├─ ChatRequest.java ├─ ChatController.java └─ ChatServiceFacade.sseChat() 第 2 组:前置路由 ├─ ChatRoutingContext.java ├─ ChatContextResolver.java ├─ ChatSceneRouter.java ├─ ChatRouteResult.java ├─ ChatRouteAction.java └─ ChatSceneType.java 第 3 组:固定回复 ├─ FixedReplyService.java ├─ FixedReplyTemplateProvider.java ├─ FixedReplySseSender.java └─ DefaultFixedReplySseSender.java 第 4 组:模型调用 ├─ AiCustomerSystemPromptProvider.java ├─ ChatServiceFactory.java ├─ AbstractChatService.java └─ 各供应商 ChatService 实现 第 5 组:知识库 ├─ KnowledgeRetrievalService.java ├─ CustomVectorRetriever.java ├─ VectorStoreService.java └─ KnowledgeInfoService 第 6 组:消息和 SSE ├─ ChatMessageServiceImpl.java ├─ PersistentChatMemoryStore.java ├─ SseEmitterManager.java └─ SseMessageUtils.java

下一步最适合从第一组的三个文件逐行走,重点搞明白:

前端到底传了什么 后端补充了什么 路由发生在什么时候 用户消息为什么在调用模型前保存 SSE 为什么要使用 userId + uuid/sessionId 隔离

这三点理解后,再进入ChatSceneRouter,整个项目的聊天主线就不会乱了。


总结

本文围绕“先建立后端全局认知,再走通一次 AI 对话从请求入口到 SSE 返回的主链路”,主要分析了:

  1. 后端各一级模块的职责与依赖方向;
  2. 普通 CRUD 和 AI 聊天链路的结构差异;
  3. 从 ChatRequest、Controller、Facade 到 SSE 的关键执行顺序;
  4. 固定回复、模型调用和消息落库之间的关系;

整个过程可以概括为:

前端请求 → ChatController → ChatServiceFacade → 上下文与场景路由 → 固定回复 / 模型调用 → SSE → 消息持久化

真正读懂 AI 客服后端,不是记住所有类名,而是先分清“谁接请求、谁做决策、谁执行回复、谁负责传输、谁负责保存”。

当前进度

[OK] 已经完成

  • 已完成根 POM、启动模块、公共模块和聊天主链路的静态源码核对;
  • 已整理普通 CRUD 与 AI 对话两条阅读路线;

[TODO] 后续继续

  • 下一篇继续拆解 Maven 依赖、Spring Bean 装配和跨模块接口桥梁;
  • 后续再逐行分析 ChatRequest、ChatController 与 ChatServiceFacade 的入口代码;

小鱼点睛

真正读懂 AI 客服后端,不是记住所有类名,而是先分清“谁接请求、谁做决策、谁执行回复、谁负责传输、谁负责保存”。

下一篇

下一篇将继续分析“Java 多模块项目如何协作:Maven 依赖、Spring 注入与运行时装配”,把本文建立的结构认知继续落到具体代码和对象流上。


这篇文章是我在真实项目学习过程中的阶段性记录。不同项目的命名和目录可能不同,但判断职责边界、依赖方向和数据生命周期的方法可以复用。如果内容中还有遗漏,欢迎一起交流。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询