山东大学软件学院2026项目实训个人博客(四)
项目名称:基于AI大模型 的智能考研社区
撰写日期:2026年5月6日
本周主要实现了部分错题功能的接口,包括做题记录、获取每日推题、标签管理等,完成了RabbitMQ 消息队列的异步通信链路联调,引入Mock服务,重点处理了 AI 图片分析功能中的异步等待难题。
一、实现部分TODO接口
1. 做题( /topic/doTopic )
TopicDoDTO 中的 correctFlag 是 Integer 类型,Service 层需要的是:boolean 类型,故转换逻辑:1 → true,0 → false
(节选代码)

测试结果为:

2. 获取每日推题接口 (/topic/getDailyTopic)
这个接口的功能是,根据当前用户ID,从 topic_daily 表中查询今日推荐题目。每天自动更新推荐题目。
题目来源有两种:
type=0:从用户自己的错题本中挑选(薄弱知识点优先)
type=1:AI 生成的新题目

(1)getDailyCount() 方法:
从 topic_daily_count 表查询用户设置的推题数量,如果没有设置,返回默认值 10。优点是用户可以根据自己情况调整每日练习量
(2)refreshDailyTopics() 方法:
清空旧的推荐题目,重新生成新的推荐,作用是可以配合定时任务,每天凌晨自动刷新
(3)getTodayDailyTopics() 方法:
查询今日推荐题目,如果为空,自动触发刷新。
测试结果:

3. 标签管理( /label/list , /label/add , /label/delete)
标签是错题分类的核心,用户可以通过标签快速筛选题目,支持按知识点、科目、难度等多维度分类。
type=0:错题标签(用于题目标记),这里强制设置为 0,因为这是错题本模块。
type=1:帖子标签(用于社区帖子分类)
此外,每个用户有自己的标签体系,用户 A 的"高等数学"标签和用户 B 的"高等数学"标签是不同的,通过 userId 实现标签隔离。
@Data
@Schema(description = "题目标签绑定参数")
public class TopicLabelDTO {
@Schema(description = "题目ID")
private Long topicId;
@Schema(description = "标签名称列表")
private List<String> labelNames;
}
获取标签列表:
@Operation(summary = "获取标签列表")
@PostMapping("/list")
public Result<List<Label>> list() {
log.info("获取标签列表");
List<Label> labels = labelService.getUserLabels(); //增_0430
return Result.success(labels); //新增返回数据_0508
}
添加标签:
@Operation(summary = "添加标签")
@PostMapping("/add")
public Result add(@RequestBody Label label) {
log.info("添加标签: {}", label);
// 参数校验
if (label.getContent() == null || label.getContent().trim().isEmpty()) {
return Result.error("标签名称不能为空");
}
// 设置标签类型为错题标签(type=0)
label.setType(0);
// 设置当前用户ID(从登录上下文获取)
Long userId = BaseContext.getCurrentId();
label.setUserId(userId);
// 保存标签
labelService.save(label);
log.info("标签添加成功 - LabelId: {}, UserId: {}, Content: {}",
label.getId(), userId, label.getContent());
return Result.success();
}
删除标签:
@Operation(summary = "删除标签")
@PostMapping("/delete")
public Result delete(@RequestBody Long labelId) {
log.info("删除标签: {}", labelId);
labelService.deleteLabel(labelId);
return Result.success();
}
二、RabbitMQ 消息队列的架构搭建
1. 核心设计思路
消息队列的核心作用是“解耦”与“异步”。考虑到 AI 模型分析题目需要数秒时间,直接通过 HTTP 同步等待会导致连接超时。因此,设计了基于 RabbitMQ 的异步流程: Java 后端将图片 URL 发送到 picture-queue 后立即返回请求 ID,同时挂起等待;Python AI(或 Mock)处理完成后,将结果发往 ai-result-queue,Java 后端接收到结果后唤醒请求并返回给前端。
2. 队列配置与路由键分离
在 RabbitMQConfig.java 中,严格区分了“队列名称”与“路由键”,遵循了 Spring AMQP 的最佳实践。
public static final String PICTURE_QUEUE = "picture-queue"; // 队列实体
public static final String PICTURE_ROUTING_KEY = "picture-routing-key"; // 路由键
@Bean
public Binding pictureBinding(Queue pictureQueue, DirectExchange directExchange) {
// 将队列通过路由键绑定到交换机,确保消息能准确投递
return BindingBuilder.bind(pictureQueue).to(directExchange).with(PICTURE_ROUTING_KEY);
}
3.消费者服务设计
新建了 RabbitMQConsumerService.java,负责监听 AI 返回的结果队列。为了保证数据不丢失,配置了手动确认模式(Manual Acknowledge)。
@RabbitListener(queues = "ai-result-queue", containerFactory = "rabbitListenerContainerFactory")
public void consumeAIResult(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
try {
// 解析 JSON 结果
TopicAnalysePackage result = JSON.parseObject(message, TopicAnalysePackage.class);
// 通知正在等待的请求(通过 requestId 关联)
aiResultManager.completeRequest(result.getRequestId(), result);
// 手动确认消息,通知 RabbitMQ 该消息已处理完毕
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
// 异常时拒绝消息,防止数据丢失
channel.basicNack(deliveryTag, false, false);
}
}
三、AI 异步分析链路的开发与调试
1. CompletableFuture 异步等待机制
在 TopicServiceImpl.uploadAI 中,利用 CompletableFuture 实现了同步阻塞等待异步结果的功能。
发送前:调用 aiResultManager.createPendingRequest(requestId) 创建一个 Future 对象,并将其存入并发 Map 中。
等待中:调用 future.get(30, TimeUnit.SECONDS),主线程在此阻塞最多 30 秒。
接收后:消费者收到 MQ 消息后,通过 requestId 找到对应的 Future,调用 future.complete(result),主线程随即被唤醒。
(节选代码段)

2. Mock AI 服务的实现
为了模拟 对接Python 后端的服务,我编写了 MockAIResponder.java 来模拟 AI 行为。
模拟延迟:使用 Thread.sleep(3000) 模拟 AI 思考过程。
构造数据:生成包含题干、解析、错点分析、推荐题目列表的完整 JSON 数据。
result.setQuestionStem("已知函数f(x) = x² + 2x + 1,求f(2)的值");
result.setAnalyseQuestion("这是一道基础的函数求值题,考察二次函数的基本运算能力");
result.setAnalyseWrong("常见错误:代入时符号错误,或者计算顺序混乱");
result.setSuggestLabels(Arrays.asList("函数", "二次函数", "代数运算", "求值"));
TopicVO recommendTopic1 = TopicVO.builder()
.id(1001L)
.title("二次函数求值练习1")
.questionStem("求g(x) = 2x² - 3x + 1在x=3时的值")
.answer("g(3) = 2×9 - 3×3 + 1 = 18 - 9 + 1 = 10")
.createTime(LocalDateTime.now())
.build();
回传消息:将构造好的结果发送到 ai-result-queue,触发消费者的逻辑。
String jsonMessage = JSON.toJSONString(result);
rabbitTemplate.convertAndSend(
RabbitMQConfig.EXCHANGE_NAME,
"ai-result-routing-key",
jsonMessage
);
3. 关键问题解决
在开发过程中遇到了两个核心阻碍,并逐一攻克:
问题一:FastJSON 反序列化报错
原因:TopicVO 使用了 Lombok 的 @Builder 注解,导致没有无参构造函数,FastJSON 无法实例化对象。
解决:为 TopicVO 补充了 @NoArgsConstructor 注解。
问题二:消息发送成功但消费者无响应(超时)
现象:日志显示消息已发送,但 30 秒后报 TimeoutException。
解决:取消消费者的 @RabbitListener 注释并修正路由键为 picture-routing-key,确保消费者能收到消息。
四、Swagger测试
启动项目后端,在Swagger测试界面中正常注册、登录。
找到 /topic/uploadAI 测试接口,输入模拟url,进行测试:


测试成功,异步通讯正常。
五、总结
本周完成了异步通信架构的进一步搭建,并进行了RabbitMQ 消息队列的异步通信链路联调,重点处理了 AI 图片分析功能中的异步等待难题。通过引入Mock服务,成功打通了从“前端上传 -> 消息队列 -> 模拟 AI 处理 -> 返回结果”的完整流程。同时,修复了消息路由键匹配错误、JSON 反序列化失败等问题,为后续与 AI 模块进行联调打下基础。
在基础设施方面,搭建并完善了 RabbitMQ 的生产者、消费者、交换机绑定配置,解决了路由键匹配和 JSON 序列化等关键技术难题。 在业务逻辑方面,通过 CompletableFuture 解决了 HTTP 请求与异步消息处理之间的同步问题,保证了前端用户体验(无需轮询即可获取结果)。 同时,通过编写Mock 服务,将后端开发和其他模块的开发隔离开,加快了项目总体并行推进的进度。
下一步开发计划:
真实联调:获取远程 RabbitMQ 地址,与 Python AI 团队进行真实接口对接。
数据落库:实现 /topic/save-analysis 接口,将 AI 分析后的题目详情、标签和推荐题一键保存到错题本数据库中。
优化体验:增加 WebSocket 实时推送功能,让用户在前端无感知的情况下收到 AI 分析结果。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)