项目名称:基于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 分析结果。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐