面向 AI 学习场景的后端服务,提供学习窗口管理、知识库文档解析、AI 出题与测评、学习指南生成、聊天问答、自学诊断、课程看板分析等能力。
- 前端:ai-tech-vue
- 后端(当前仓库):ai-tech
- Agent:ai-tech-agent
- 技术定位:
Spring Boot 4 + MyBatis-Plus + RabbitMQ + Redis + MySQL + 腾讯云 COS + 外部 AI Agent 服务 - 核心形态:
REST API + SSE 流式响应 + MQ 异步任务 - 统一返回:除 SSE 接口外,统一使用
Result<T>结构 - 鉴权机制:JWT(
Authorization: Bearer <token>)
- 用户认证
- 邮箱验证码登录(自动注册)
- 学习窗口管理
- 创建/更新/删除窗口,课程邀请码加入
- 课程章节管理
- 新增/更新/删除/排序查看章节
- 知识库管理
- 预签名上传、上传确认、异步解析、列表与删除
- 测验系统
- AI 异步生成试题、提交评分、错题补救测验、测验分析
- 学习指南
- 按章节与文件生成学习指南(异步)
- 聊天问答
- 聊天历史查询,SSE 流式回复,支持工具态分享通道
- 自学诊断
- 基于近期对话生成诊断报告与掌握度结构化数据
- Dashboard
- 课程整体数据、学员画像、学员分析报告(异步)
- 异步任务中心
- 汇总文档解析与 AI 生成任务状态
- 语言与运行时:
Java 17 - 框架:
Spring Boot 4.0.1 - Web:
spring-boot-starter-web、spring-boot-starter-webflux(SSE/Reactive Client) - ORM:
MyBatis-Plus 3.5.15 - 数据库:
MySQL(测试使用 H2) - 缓存:
Redis - 消息队列:
RabbitMQ - 对象存储:
Tencent COS - 鉴权:
jjwt 0.12.5 - 工具库:
Hutool
- 前端调用 Java API。
- 需要异步处理的任务(出题、分析、诊断、文档解析、学习指南)写入数据库占位记录,并投递到 RabbitMQ。
- Listener 消费消息后调用外部 AI 服务。
- 回写业务记录状态,并同步更新
t_async_task任务卡片。 - 聊天接口通过 SSE 将 AI 流式内容实时回推前端,并异步落库对话。
ai-tech/
├─ src/main/java/com/ai/tech
│ ├─ controller/ # HTTP 接口层
│ ├─ service/ # 业务接口
│ ├─ service/impl/ # 业务实现
│ ├─ listener/ # RabbitMQ 消费者
│ ├─ mapper/ # MyBatis Mapper
│ ├─ model/
│ │ ├─ dto/ # 入参模型
│ │ ├─ entity/ # 实体模型
│ │ ├─ message/ # MQ 消息模型
│ │ └─ vo/ # 出参模型
│ ├─ prompt/ # Prompt 模板加载与渲染
│ ├─ config/ # Spring 配置
│ ├─ interceptor/ # 鉴权与内网访问控制
│ └─ common/ # 常量/统一返回/异常处理
├─ src/main/resources
│ ├─ application.yml
│ ├─ mapper/*.xml
│ └─ prompts/**
└─ src/test/java
启动前需准备:
- JDK
17 - Maven(或直接使用项目内
mvnw/mvnw.cmd) - MySQL(生产/开发)
- Redis
- RabbitMQ
- 可用 SMTP 邮件服务
- 腾讯云 COS 凭据
- 外部 AI Agent 服务(HTTP API)
仓库中仅提交了:
src/main/resources/application.yml(通用配置与默认值)src/test/resources/application-test.yml(测试配置)
以下文件被 .gitignore 忽略,需自行创建:
src/main/resources/application-local.ymlsrc/main/resources/application-prod.yml
推荐通过环境变量或 profile 文件注入敏感项。
application.yml 中的默认项(可由环境变量覆盖):
| 配置键 | 环境变量 | 默认值 |
|---|---|---|
spring.profiles.active |
SPRING_PROFILES_ACTIVE |
prod |
spring.transaction.default-timeout |
SPRING_TX_TIMEOUT_SECONDS |
60 |
spring.rabbitmq.listener.simple.concurrency |
RABBITMQ_LISTENER_CONCURRENCY |
2 |
spring.rabbitmq.listener.simple.max-concurrency |
RABBITMQ_LISTENER_MAX_CONCURRENCY |
5 |
spring.rabbitmq.listener.simple.prefetch |
RABBITMQ_LISTENER_PREFETCH |
1 |
jwt.expiration |
JWT_EXPIRATION_MS |
86400000 |
ai.service.* |
AI_SERVICE_* |
http://localhost:8000/... |
app.cors.allowed-origin-patterns |
APP_CORS_ALLOWED_ORIGIN_PATTERNS |
* |
必须提供(无合理默认值)的配置:
jwt.secret(长度至少 32 字节)spring.datasource.*spring.data.redis.*spring.rabbitmq.*spring.mail.*tencent.cos.secretIdtencent.cos.secretKeytencent.cos.regiontencent.cos.bucketNametencent.cos.cdnUrl(可空,但建议配置)
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/ai_tech?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
data:
redis:
host: 127.0.0.1
port: 6379
password:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
mail:
host: smtp.example.com
port: 587
username: no-reply@example.com
password: your_mail_password
properties:
mail.smtp.auth: true
mail.smtp.starttls.enable: true
jwt:
secret: replace-with-at-least-32-bytes-secret
tencent:
cos:
secretId: xxx
secretKey: xxx
region: ap-beijing
bucketName: your-bucket
cdnUrl: https://cdn.example.comWindows:
.\mvnw.cmd spring-boot:run -Dspring-boot.run.profiles=localmacOS/Linux:
./mvnw spring-boot:run -Dspring-boot.run.profiles=localWindows:
.\mvnw.cmd -q test当前测试覆盖重点:
- Spring 容器加载测试
- Prompt 模板渲染与变量缺失校验
- 全局登录拦截:
LoginInterceptor - 放行接口:
POST /api/v1/auth/send-codePOST /api/v1/auth/login/api/v1/tool/**- 内网拦截:
InternalAccessInterceptor - 作用范围:
/api/v1/tool/** - 允许地址:
127.0.0.1、localhost、10.x.x.x、172.16-31.x.x、192.168.x.x
说明:除特别声明外,均返回 Result<T>。
POST /api/v1/auth/send-code发送邮箱验证码POST /api/v1/auth/login邮箱+验证码登录PUT /api/v1/auth/info更新用户信息
GET /api/v1/window/listPOST /api/v1/window/createPUT /api/v1/window/updateDELETE /api/v1/window/batchPOST /api/v1/window/joinGET /api/v1/window/detail/{windowId}
POST /api/v1/course/chapter/createPUT /api/v1/course/chapter/updateDELETE /api/v1/course/chapter/delete/{id}GET /api/v1/course/chapter/list/{windowId}
GET /api/v1/knowledge/presign-uploadPOST /api/v1/knowledge/confirmGET /api/v1/knowledge/listDELETE /api/v1/knowledge/{fileId}
POST /api/v1/quiz/generateGET /api/v1/quiz/{quizId}POST /api/v1/quiz/submitDELETE /api/v1/quiz/{quizId}GET /api/v1/quiz/recordGET /api/v1/quiz/analysis/{recordId}DELETE /api/v1/quiz/analysis/{recordId}POST /api/v1/guide/generateGET /api/v1/guide/{guideId}DELETE /api/v1/guide/{guideId}POST /api/v1/feedback/submitGET /api/v1/feedback/list
POST /api/v1/chat/history历史记录POST /api/v1/chat/sendSSE 对话流POST /api/v1/chat/send/toolSSE 工具通道流
GET /api/v1/self-study/mastery/{windowId}GET /api/v1/self-study/diagnose/{windowId}GET /api/v1/self-study/diagnosis/{diagnosisId}DELETE /api/v1/self-study/diagnosis/{reportId}
GET /api/v1/dashboard/course/{windowId}GET /api/v1/dashboard/course/member/{windowId}DELETE /api/v1/dashboard/course/member/{memberId}GET /api/v1/dashboard/learner/detailPOST /api/v1/dashboard/learner/analyzeDELETE /api/v1/dashboard/learner/analyze/{reportId}
GET /api/v1/tasks/active/all/{windowId}GET /api/v1/tasks/activePUT /api/v1/tasks/{id}/title
POST /api/v1/tool/usersPOST /api/v1/tool/window-membersPOST /api/v1/tool/chaptersPOST /api/v1/tool/knowledgePOST /api/v1/tool/quizzesPOST /api/v1/tool/guidesPOST /api/v1/tool/student-quiz-recordsPOST /api/v1/tool/student-chatsPOST /api/v1/tool/feedbacks
| 业务 | Exchange | Queue | Routing Key |
|---|---|---|---|
| 文档解析 | rag.exchange |
queue.doc.parse |
doc.parse |
| 测验生成 | quiz.exchange |
quiz.generate.queue |
quiz.generate |
| 学员分析 | analysis.exchange |
queue.analysis.generate |
analysis.generate |
| 自学诊断 | diagnosis.exchange |
queue.diagnosis.generate |
diagnosis.generate |
| 测验分析 | quiz.analysis.exchange |
queue.quiz.analysis.generate |
quiz.analysis.generate |
| 学习指南 | guide.exchange |
queue.guide.generate |
guide.generate |
所有主队列均配置了死信交换机 dlx.exchange(队列 queue.dlx)。
StatusConstants.AsyncTask:
0进行中(RUNNING)1成功(SUCCESS)2失败(FAILED)
- 生成状态(测验/分析/指南):
0 生成中,1 成功,2 失败 - 知识库解析状态:
0 上传中,1 等待解析,2 成功,3 失败 - 测验记录状态:
1 完成,2 需补救 - 逻辑删除字段:
is_deleted(0 未删,1 已删)
模板目录:src/main/resources/prompts/**
由 PromptTemplateService 在启动时加载并缓存,支持 {{variable}} 占位变量渲染,变量缺失会抛出异常。
- 采用 MyBatis-Plus + 部分 XML 自定义 SQL
- 主要表:
sys_usert_learning_windowt_window_membert_course_chaptert_knowledge_baset_quizt_user_quiz_recordt_learning_guidet_chat_messaget_student_analysis_reportt_course_feedbackt_async_task
注意:仓库当前未包含建表 SQL / migration 脚本,需在部署环境自行维护数据库 schema。
401 未授权- 检查是否携带
Authorization: Bearer <token>。 tool接口访问被拒绝- 仅允许内网来源地址访问。
- 邮件发送失败
- 检查
spring.mail.*配置、SMTP 白名单与账号授权码。 - 文档解析一直等待
- 检查 RabbitMQ 消费是否正常、AI 解析服务是否可达、COS 下载 URL 是否可访问。
- 应用启动报
jwt.secret错误 - 确认
jwt.secret已配置且不少于 32 字节。
- 新增异步能力时,遵循“先落库占位 + 投递 MQ + Listener 回写状态 + 同步任务卡片”模式。
- 新增 AI Prompt 时,统一放在
prompts/并通过PromptId枚举注册。 - 对外接口优先复用统一返回结构和全局异常处理。
当前仓库未声明开源许可证;如需开源发布,请补充 LICENSE 文件。