在现代企业应用中,将传统后台管理系统与AI能力(如向量化检索、智能对话)集成是一个常见需求。然而,直接在老旧的后台代码中耦合AI逻辑,会带来版本冲突、代码臃肿和维护噩梦。本文将深入探讨一种优雅的解决方案:利用RabbitMQ实现异步解耦,并结合工厂模式构建一个可灵活扩展的AI向量化处理架构,让业务系统与AI服务各司其职,轻松应对未来变化。
一、核心挑战:业务与AI服务的集成困境
设想一个典型场景:管理员在若依后台对公告(notice)进行增删改查。这些数据不仅需要存入MySQL,还必须被AI“理解”——即转化为向量并存入向量数据库,以供后续的智能对话检索。直接集成AI代码到后台项目面临多重挑战:
- 版本冲突:许多企业后台项目基于较低版本的框架(如Spring Boot 1.8),强行引入较新的AI依赖可能导致兼容性问题。
- 紧耦合:业务逻辑与向量化处理强绑定,任何一方的变更都可能影响另一方,系统难以维护和独立部署。
- 扩展性差:新增一种业务类型(如“失物招领”),就需要修改核心的消息处理链路,违反了开闭原则。
我们的目标是设计一套“懒人架构”,核心处理流程无需改动,仅通过新增实现类即可支持新业务。这背后的数据流转,正如Spring AI读取和更新知识库的过程所示:

公告栏逻辑说明:
- 管理员发公告:若依(后台)生成notice表给Mysql(包括controller,用一个id写入数据库),Mysql数据库表中的数据向量化为AI能识别的数据(知识库,用redis实现向量库)(引入rabbitMQ传递id进行知识库的写入)
- 修改后台生成的Controller,使若依后台在完成MySQL数据操作后,同步发送消息到RabbitMQ。消息中包含业务ID、操作类型和业务类型,由消息消费者(AI应用frontend)根据ID从MySQL查询对应数据,并将数据向量化后存入知识库(向量库vector)。向量库返回的文档ID与业务ID共同记录在中间表,用于后续精准删除操作。
- 管理员删除公告:若依后台删除Mysql表中的相关数据,将id号利用MQ传给消费者AI应用frontend,由frontend用id查找Mysql中的document_ids表(source_id & document_id:notice表的id号 & 向量库中的id号),获取到向量库中的对应id号,frontend再删除向量库中的对应数据。
二、架构全景:消息驱动的异步处理流水线
整个解决方案围绕异步消息和动态路由展开。首先,我们定义标准的消息格式,作为业务系统与AI服务之间的“合同”:
{
"ids": ["123"],
"operation": "ADD|UPDATE|DELETE",
"type": "CAMPUSAI_NOTICE|CAMPUSAI_MATERIALS"
}
完整的架构数据流转链清晰地展示了各组件职责:
若依后台 → MySQL业务库 → RabbitMQ消息 → 向量化服务 → Redis向量库 → Spring AI对话
↑ ↑ ↑ ↑ ↑ ↑
CRUD操作 数据持久化 消息驱动 “工厂派单” 向量存储 智能检索
各核心组件职责如下:
- 业务控制器(如NoticeController):处理前端请求,执行业务逻辑(CRUD)。
- 消息发送服务(RabbitSendService):关键的解耦层,将业务操作封装成消息,异步发送至RabbitMQ。
- 消息接收器(CampusaiMessageReceiver):AI服务端的“前台”,监听队列,接收并解析消息。
- 向量服务工厂(VectorServiceFactory):架构的“智能调度中心”,根据消息类型动态选择对应的向量化处理器。
- 具体向量服务(如NoticeVectorServiceImpl):执行实际的向量化存储、更新与删除操作。
- 文档ID映射表(document_ids):维护业务数据ID与向量库文档ID的对应关系,是实现精准操作的关键。
让我们跟随一条“新增公告”的指令,走完整个流程:
- 业务数据入库:数据首先被写入MySQL的
表。notice - 触发消息发送:业务层通过
调用发送服务。NoticeController.addSave()
INSERT INTO notice (id, title, content, create_time)
VALUES ('1001', '作息与安全', '宿舍楼门禁时间...', NOW());
int rows = noticeService.insertNotice(notice); // 插入MySQL
rabbitSendService.sendAddNotice(notice.getId()); // 发送消息
- 消息进入队列:一条包含操作类型、业务ID和数据的消息被发送。
{
"ids": "1001",
"operation": 1,
"type": "CAMPUSAI_NOTICE"
}
消息由生产者发送到RabbitSendService队列,由消费者CAMPUSAI_NOTICE接收。CampusaiMessageReceiver
- 向量化处理:AI服务端开始核心的向量化工作。
INSERT INTO document_ids (source_id, document_id, type)
VALUES ('1001', 'vec_123456', 'CAMPUSAI_NOTICE');
消费者处理逻辑:
// 1. 解析消息
MessageDto messageDto = JSON.parseObject(message, MessageDto.class);
// 2. 工厂模式选择服务
IVectorService vectorService = vectorServiceFactory.of("CAMPUSAI_NOTICE");
// 3. 执行新增向量化
vectorService.addDocument(messageDto);
具体步骤包括查询业务数据、创建向量文档、存入向量库,并记录ID映射关系。
Notice notice = noticeService.getById("1001")
Document doc = new Document(notice.getContent(),
Map.of("id", "1001", "title", "作息与安全"));
store.add(List.of(doc))
在表中新增映射记录:document_ids
INSERT INTO document_ids (source_id, document_id, type)
VALUES ('1001', 'vec_123456', 'CAMPUSAI_NOTICE');
最终,用户可以通过AI对话界面提问,如“宿舍门禁时间?”,Spring AI会检索向量库并返回相关公告内容,完成智能交互。
[AFFILIATE_SLOT_1]
三、数据流转与RabbitMQ的核心价值
理解表间关系是把握数据一致性的关键。核心表结构如下:
| 表名 | 用途 | 关键字段 |
| notice | 业务数据表 | id, title, content, create_time |
| document_ids | 映射关系表 | source_id, document_id, type |
| Redis向量库 | 向量数据存储 | 向量数据 + 元数据 |
它们之间的数据流转关系为:
notice表 → document_ids表 → Redis向量库
↑ ↑ ↑
业务ID 映射关系 向量化数据
其中,关联notice.id,document_ids.source_id对应向量库中的文档ID,并通过document_ids.document_id区分业务类型。type="CAMPUSAI_NOTICE"
RabbitMQ在此架构中扮演了四大核心角色:
- 异步解耦:将耗时的向量化操作与即时响应的业务操作分离。业务方无需等待AI处理完成。
- 消息缓冲:应对流量峰值,作为缓冲区保护下游的向量化服务不被冲垮。
- 可靠传递:通过持久化、确认机制确保消息不丢失。
- 灵活路由:支持为不同业务(如公告
、资料CAMPUSAI_NOTICE)配置独立队列,实现逻辑隔离。CAMPUSAI_MATERIALS
对比强耦合代码与MQ解耦方案,优劣立判:
// 不优雅的设计
int rows = noticeService.insertNotice(notice);
vectorService.addDocument(notice); // 同步向量化,阻塞业务
int rows = noticeService.insertNotice(notice);
rabbitSendService.sendAddNotice(notice.getId()); // 异步消息
通过以下配置保障可靠性:
// ConfirmCallback:确认消息到达交换机
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (!ack) {
log.info("消息发送失败"); // 可触发重试
}
});
// ReturnCallback:确认消息到达队列
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
log.info("消息丢失"); // 可记录异常
});
四、工厂模式:实现动态路由的“智能调度中心”
这是本架构的精髓所在。工厂模式将“按类型派发逻辑”的复杂性封装起来,让系统易于扩展。
第一步:定义抽象接口
为所有向量化服务制定统一的“契约”,要求它们都必须实现增、改、删这三个核心操作。
public interface IVectorService {
void addDocument(MessageDto messageDto); // 新增向量
void updateDocument(MessageDto messageDto); // 修改向量
void deleteDocument(MessageDto messageDto); // 删除向量
}
第二步:实现工厂类在应用启动时,自动扫描并注册所有实现了VectorServiceFactoryIVectorService的Bean,建立“消息类型”到“具体服务实例”的映射关系。这是一个典型的“服务注册”过程。
@Component
public class VectorServiceFactory {
@Autowired private MaterialsVectorServiceImpl materialsVectorService; // 资料
@Autowired private NoticeVectorServiceImpl noticeVectorService; // 公告
public IVectorService of(String messageType) {
switch(messageType) {
case "CAMPUSAI_MATERIALS": return materialsVectorService; // 派资料
case "CAMPUSAI_NOTICE": return noticeVectorService; // 派公告
default: return null; // 不认识这业务,拒单!
}
}
}
第三步:简化消息接收器
得益于工厂模式,消息接收器的职责变得极其单一和稳定:它只需解析消息,然后根据类型从工厂获取对应的处理器并调用即可。它完全不需要关心公告或资料的具体处理逻辑。CampusaiMessageReceiver
@RabbitListener(queuesToDeclare = {
@Queue("CAMPUSAI_NOTICE"),
@Queue("CAMPUSAI_MATERIALS")})
public void processMessage(String message) {
MessageDto dto = JSON.parseObject(message, MessageDto.class); // 拆包
IVectorService service = vectorServiceFactory.of(dto.getType()); // 派单
// 看操作类型
switch(dto.getOperation()) {
case 1: service.addDocument(dto); break; // 新增
case 2: service.updateDocument(dto); break; // 修改
case 3: service.deleteDocument(dto); break; // 删除
}
}
这种设计哲学实现了完美的职责分离:“前台”只管派单,“后台师傅”各司其职。新增业务类型时,只需编写新的IVectorService实现类并标注类型,工厂会自动将其纳入调度体系,核心链路代码无需任何修改。
五、关键实现:以公告向量化为例
让我们深入,看它如何完成向量化的“三板斧”。NoticeVectorServiceImpl
1. 新增向量
核心流程是从MySQL查询数据,转换为向量文档后存入Redis向量库。这里有一个关键点:向量库(如Redis)会生成自己的唯一文档ID(,例如documentId),这与业务ID(doc-123456,例如notice.id)不同。因此,必须通过1001document_ids表记录映射关系,否则后续无法进行精准的更新或删除。
public void addDocument(MessageDto messageDto) {
// 1. 查MySQL:根据业务ID找出公告内容
Notice notice = noticeService.getById(ids);
// 2. 造“向量原料”:文本+元数据
Document doc = new Document(notice.getContent(),
Map.of("id", notice.getId(), "title", notice.getTitle()));
// 3. 存入Redis向量库
store.add(List.of(doc));
// 4. 记“翻译字典”:业务ID ↔ 向量ID
documentIdsService.save(new DocumentIds()
.setSourceId(ids)
.setDocumentId(doc.getId())
.setType("CAMPUSAI_NOTICE"));
}
2. 修改策略
由于许多向量库(包括Spring AI当前支持的一些)不提供直接的文档更新API,因此修改操作通常采用“先删除旧向量,再新增新向量”的策略。
public void updateDocument(MessageDto messageDto) {
deleteDocument(messageDto); // 先把旧向量“拆了”
addDocument(messageDto); // 再建个新的
}
3. 精准删除
删除操作完全依赖于之前建立的ID映射表。如果没有这张“翻译字典”,业务系统仅凭自己的业务ID,根本无法定位和删除向量库中的对应文档。
public void deleteDocument(MessageDto messageDto) {
// 1. 根据业务ID找向量ID
List documentIds = documentIdsService
.getDocumentIds("CAMPUSAI_NOTICE", messageDto.getIds());
// 2. 删向量数据
store.delete(documentIds);
// 3. 清理
documentIdsService.deleteBySourceIds("CAMPUSAI_NOTICE", messageDto.getIds());
}
不同操作(新增、修改、删除)的完整流程总结如下:
MySQL: notice表新增 → RabbitMQ消息 → 向量库新增 → 中间表记录
MySQL: notice表更新 → RabbitMQ消息 → 向量库删除旧数据 → 向量库新增新数据 → 中间表更新
MySQL: notice表删除 → RabbitMQ消息 → 中间表查询document_id → 向量库删除 → 中间表清理
六、可靠性保障与实战效果
一个健壮的架构必须考虑失败场景。消息可靠性通过RabbitMQ的确认机制保障:
// 开启“签收回执”功能
connectionFactory.setPublisherConfirms(true);
connectionFactory.setPublisherReturns(true);
rabbitTemplate.setMandatory(true);
// 如果快递没送到,要通知我!
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) log.info("消息送到啦!");
else log.info("消息寄丢了!原因:" + cause);
});
// 如果送到地方但没人收(队列不存在),也要通知我!
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
log.info("消息没人收!地址:" + exchange + ",路线:" + routingKey);
});
数据一致性方面,需确保MySQL中的映射表和Redis向量库数据同步持久化,防止服务重启导致数据丢失或映射断裂。
下面通过一系列实战截图验证整个流程:
1. 在若依后台新增公告,触发MQ发送:

2. 在RabbitMQ管理界面查看待消费的消息:
3. 启动AI服务消费消息,日志确认执行成功:
4. 检查Redis向量库,确认新增了向量条目:
5. 验证MySQL的document_ids表写入了正确的映射关系:

6. 最终,通过AI对话界面进行检索,获得准确回答:
[AFFILIATE_SLOT_2]
总结
本文详细剖析了一种结合RabbitMQ异步消息与工厂模式的后端架构,旨在解决传统业务系统与AI向量化服务集成时的耦合与扩展难题。该架构的核心优势在于:
- 彻底解耦:通过消息中间件将业务处理与AI处理分离,双方可独立开发、部署和扩展。
- 灵活扩展:工厂模式的设计使得新增业务类型如同“插拔组件”,符合开闭原则,极大提升了系统的可维护性。
- 职责清晰:每个组件职责单一,降低了代码的复杂度和认知负担。
这套架构不仅适用于文中描述的“公告AI化”场景,更能广泛应用于任何需要将业务数据异步处理并注入AI能力(如内容审核、智能推荐、知识库构建)的系统中,为构建现代化、可伸缩的智能应用提供了坚实的设计范本。
浙公网安备 33010602011771号