学习计划
# 学习计划(Java 后端转 Java + AI)
本计划面向有 Java 后端经验、但未接触过 Java + AI 联合开发的读者,按依赖顺序拆分阶段,每阶段都依赖前一阶段。建议总学习时长 4-6 周。
# 总体原则
- 按顺序学,别跳:每层都依赖前一层,跳着看会懵
- 先跑通再优化:不要一开始就追求 Advisor 链、分批评估等复杂设计
- 理解"为什么"比"怎么做"重要:每条简历描述都对应一个真实问题,理解问题比理解代码更重要
- AI 部分可以 Mock:API 收费贵,开发时可以 mock LLM 返回,先把工程链路跑通
- 优先看官方文档:Spring AI 2.0 API 变化大,二手博客容易过时
# 阶段零:环境准备(1-2 天)
# 目标
把项目跑起来,能访问 Swagger 页面,能用一个最简单的接口。
# 任务
- [ ] 安装 JDK 25、Gradle 8.14、Node.js、pnpm
- [ ] 安装 PostgreSQL + pgvector 扩展、Redis
- [ ] 配置
.env文件(DashScope API Key、数据库密码等) - [ ] 启动
docker compose -f docker-compose.dev.yml up -d - [ ] 后端
./gradlew :app:bootRun,前端pnpm run dev - [ ] 访问 Swagger 页面,调用一个最简单的健康检查接口
# 验收标准
- 后端启动无报错,前端页面能打开
- Swagger 页面能看到所有接口
- 数据库表自动建好
# 阶段一:Spring AI Hello World(2-3 天)
# 目标
理解 Spring AI 最基础的 ChatClient 调用,跑通"用户问一句、模型答一句"。
# 学习内容
- Spring AI 的核心概念:ChatClient、ChatModel、ChatResponse
- 同步调用 vs 流式调用
- DashScope 接入配置
- System Prompt 与 User Prompt
# 实践任务
- [ ] 阅读
LlmProviderRegistry.java,理解 Provider 注册中心 - [ ] 自己写一个最简单的 Controller,调用 ChatClient 返回一段文本
- [ ] 改 System Prompt,观察模型行为变化
- [ ] 改 Temperature 参数,观察输出随机性变化
# 验收标准
- 能解释 ChatClient 和 ChatModel 的关系
- 能独立写一个调用 LLM 的接口
- 能说清 Temperature 对输出的影响
# 阶段二:结构化输出(2-3 天)
# 目标
理解 LLM 返回 JSON 的不稳定性,掌握项目的 StructuredOutputInvoker 设计。
# 学习内容
- BeanOutputConverter / MapOutputConverter
- LLM 返回 JSON 的常见问题(多余文字、字段缺失、不闭合)
- 修复型重试的设计思路
- Micrometer 指标埋点
# 实践任务
- [ ] 不用封装类,直接让 LLM 返回一个 Java record,观察失败率
- [ ] 阅读
StructuredOutputInvoker.java,理解解析 → 校验 → 重试流程 - [ ] 故意给一个诱导 LLM 输出错误 JSON 的 prompt,观察修复型重试是否生效
- [ ] 查看 Micrometer 指标,理解重试成功率统计
# 验收标准
- 能说出 LLM 输出 JSON 至少 3 种常见失败模式
- 能解释"修复型重试"为什么比"简单重试"更有效
- 能说清 StructuredOutputInvoker 解决了什么问题
# 阶段三:流式输出 SSE(2-3 天)
# 目标
理解流式输出的原理与工程坑点。
# 学习内容
- SSE 协议与 EventSource
- Spring AI 的 stream() 方法
- Reactor 的 Flux/Mono 基础(流式输出返回 Flux)
- SSE 长连接的生命周期
# 实践任务
- [ ] 改造阶段一的接口,用 stream() 实现流式输出
- [ ] 用 curl 或 Postman 观察 SSE 事件流格式
- [ ] 阅读"预创建占位 + 流完成落库"相关代码,理解断线保存策略
- [ ] 阅读"探测窗口算法"代码,理解无结果模板拦截
# 验收标准
- 能解释为什么 LLM 场景必须用流式输出
- 能说清 SSE 与 WebSocket 的取舍(SSE 单向、更轻量)
- 能解释断线丢失问题与项目解法
# 阶段四:Advisor 链(2-3 天)
# 目标
理解 Spring AI 的 Advisor 机制,掌握配置化组合设计。
# 学习内容
- Advisor 接口与 AdvisorChain
- Spring AI 内置 Advisor(如 QuestionAnswerAdvisor、PromptChatMemoryAdvisor)
- 自定义 Advisor 实现
- 通过 yml 配置化组合 Advisor
# 实践任务
- [ ] 阅读项目的 4 种 Advisor 实现
- [ ] 理解不同业务场景的 Advisor 组合配置
- [ ] 自己写一个简单的日志 Advisor,记录每次 LLM 调用的耗时
- [ ] 通过 yml 开关切换 Advisor 组合,观察行为变化
# 验收标准
- 能解释 Advisor 与 Servlet Filter 的相似之处
- 能说出项目 4 种 Advisor 各自的职责
- 能说清"配置化组合"相比"硬编码"的优势
# 阶段五:Embedding 与 pgvector(3-4 天)
# 目标
理解向量化与向量检索的原理,掌握 pgvector 的使用。
# 学习内容
- Embedding 模型与向量维度
- pgvector 安装与扩展启用
- vector 类型与相似度查询(<=> 操作符)
- HNSW 索引 vs IVFFlat 索引
- 向量维度选择(项目用 1024 维)
# 实践任务
- [ ] 在 PG 中手动建一张带 vector 列的表,插入几条向量,用
<=>查询最相似的 - [ ] 阅读知识库入库代码,理解文档分块 → 向量化的完整流程
- [ ] 理解 DashScope batch_size 限制(单次 10 条)与项目的分批处理
- [ ] 查看 HNSW 索引的建索引参数,理解查询快与建索引慢的权衡
# 验收标准
- 能解释 Embedding 与全文检索的区别
- 能写出一条 pgvector 的相似度查询 SQL
- 能说清 HNSW 与 IVFFlat 的取舍
- 能解释为什么向量化要分批
# 阶段六:RAG 完整流程(3-4 天)
# 目标
理解 RAG 的完整链路,掌握幻觉防御手段。
# 学习内容
- RAG 流程:检索 → 拼接 prompt → 生成
- Query Rewrite(查询改写)
- 动态 Top-K 与 minScore 阈值
- 文档分块策略(token-aware)
- 知识库 ID 前置过滤
# 实践任务
- [ ] 阅读知识库问答模块代码,梳理完整 RAG 链路
- [ ] 上传一份文档到知识库,问一个问题,观察检索 + 生成全流程
- [ ] 故意问一个知识库没有的问题,观察是否被阈值拦截
- [ ] 阅读短/中/长 Query 的差异化 Top-K 配置,理解设计意图
- [ ] 阅读 Query Rewrite 代码,理解查询改写的时机与作用
# 验收标准
- 能画出 RAG 完整流程图
- 能解释 Query Rewrite 解决什么问题
- 能说清动态 Top-K 与 minScore 的设计意图
- 能解释 RAG 幻觉的成因与项目防御手段
# 阶段七:异步化 Redis Stream(2-3 天)
# 目标
理解 LLM 调用为什么必须异步化,掌握 Redis Stream 消费模型。
# 学习内容
- LLM 调用耗时问题(10-15s)
- Redis Stream 基础(XADD、XREADGROUP、XACK)
- Consumer Group 与 Pending Entries List
- 死信处理与任务恢复
- 状态流转(PENDING → PROCESSING → COMPLETED)
# 实践任务
- [ ] 阅读
AbstractStreamProducer.java与AbstractStreamConsumer.java - [ ] 理解简历分析的异步链路:用户上传 → 立即返回任务 ID → 异步分析 → 落库
- [ ] 用 redis-cli 手动执行 XADD、XREADGROUP,理解消费模型
- [ ] 模拟 Consumer 中途宕机,观察 PEL 与任务恢复机制
- [ ] 理解为什么 LLM 调用不能放在数据库事务内
# 验收标准
- 能解释 Redis Stream 与 Kafka 的取舍
- 能说清 XACK 的作用与未 ACK 的后果
- 能解释 LLM 调用为什么不能放事务内
- 能画出异步任务的完整时序图
# 阶段八:工程化与基础设施(4-5 天)
# 目标
掌握项目中的工程化设计,这部分与 AI 无关但面试必问。
# 学习内容分组
# 8.1 虚拟线程
- Java 21 虚拟线程 vs 平台线程
- 虚拟线程的 pinning 问题(synchronized 块内会 pin)
- 为什么 SSE + LLM 场景适合虚拟线程
- 虚拟线程的适用与不适用场景
# 8.2 分布式限流
@RateLimit注解 + AOP 切面- Redis + Lua 滑动窗口限流
- GLOBAL / IP / USER 多维度限流
- Hash Tag 兼容 Redis Cluster(
{user:1}让相关 key 落同一槽)
# 8.3 存储抽象层
- S3 协议与 RustFS 对象存储
- 配置化切换后端,业务代码无感知
- 为什么要做抽象层(云迁移)
# 8.4 文件处理
- Apache Tika 多格式解析(PDF/DOCX/TXT)
- PDF 禁用图片提取、DOCX 禁用嵌入资源加载(性能优化)
- 文本清洗(正则清除控制字符与噪声)
- MIME 类型 + 扩展名双重验证 + 路径穿越防护
# 8.5 安全防护
- Prompt 注入的 4 种模式
- 三层纵深防御(输入净化 → 系统提示词加固 → 输出护栏)
- UUID 动态分隔符的作用
- 顺从短语检测
# 8.6 配置管理
- @ConfigurationProperties vs @Value
- 项目从 30+ 个散落 @Value 收拢到 11 个 @ConfigurationProperties 类
- 类型安全校验与 IDE 自动补全
# 8.7 PDF 导出
- iText 8 基础
- 中文字体内嵌(跨平台渲染一致性)
- 分数配色与多维度排版
# 实践任务
- [ ] 阅读限流切面代码,理解 Lua 脚本实现滑动窗口
- [ ] 阅读 Prompt 注入防御代码,列出 4 种注入模式
- [ ] 阅读存储抽象层代码,理解 S3 协议封装
- [ ] 阅读文件解析代码,理解 Tika 性能优化点
- [ ] 查看 @ConfigurationProperties 类,对比散落 @Value 的劣势
- [ ] 阅读 PDF 导出代码,理解中文字体处理
# 验收标准
- 能解释虚拟线程相比线程池的优势与劣势
- 能说清 Lua 滑动窗口限流的实现思路
- 能列出 Prompt 注入的 4 种模式与防御手段
- 能解释 @ConfigurationProperties 相比 @Value 的优势
# 阶段九:面试复盘(贯穿全程)
# 目标
把每条简历描述还原成"它解决了什么问题",准备好面试追问。
# 每条简历描述的自我拷问模板
针对简历中每一条,问自己:
- 问题是什么:不这么做会怎样?
- 方案是什么:核心设计思路?
- 取舍是什么:为什么不用其他方案?
- 细节是什么:关键参数、阈值、边界情况?
- 量化指标:性能提升、成本降低的数据?
# 高频追问点准备
| 简历描述 | 可能的追问 |
|---|---|
| 虚拟线程支撑千级并发 | 虚拟线程什么场景反而更差?pinning 问题? |
| Redis Stream 异步 | 对比 Kafka 的取舍?死信处理? |
| StructuredOutputInvoker | 重试几次?重试失败怎么办? |
| 分批评估(8题/批) | 为什么是 8 题?批次之间怎么归并? |
| RAG 知识库 | 为什么不直接全文检索?HNSW vs IVFFlat? |
| 动态 Top-K | 短查询阈值为什么是 0.25?怎么调出来的? |
| Query Rewrite | 改写本身也是 LLM 调用,成本怎么控制? |
| Lua 限流 | 为什么用 Lua 而不是直接 INCR? |
| Hash Tag 兼容 Cluster | Hash Tag 怎么写?为什么能保证同槽? |
| Prompt 注入 4 种模式 | 具体哪 4 种?怎么净化? |
| 三层防御 | 为什么要三层?单层不够吗? |
| Skill 驱动出题 | 三阶段分配算法细节?为什么这么设计? |
| S3 存储抽象层 | 抽象层接口怎么设计?如何保证不漏特性? |
| 文件哈希去重 | 为什么用 SHA-256?哈希冲突怎么办? |
# 学习节奏建议
| 周次 | 阶段 | 产出 |
|---|---|---|
| 第 1 周 | 阶段 0 + 1 + 2 | 项目跑通 + Spring AI Hello World + 结构化输出 |
| 第 2 周 | 阶段 3 + 4 | 流式输出 + Advisor 链 |
| 第 3 周 | 阶段 5 + 6 | Embedding + RAG 完整流程 |
| 第 4 周 | 阶段 7 + 8 | 异步化 + 工程化基础设施 |
| 第 5-6 周 | 阶段 9 + 查漏补缺 | 面试复盘 + 重看不懂的部分 |
# 学习心法
- 不要试图一次看懂所有代码:先抓主线,细节后补
- 每个模块都问"解决什么问题":理解问题比理解代码更重要
- 自己动手改一改:改参数、改 prompt、改逻辑,观察行为变化
- 记笔记:每个阶段的"我之前不懂、现在懂了"的点记下来
- 找一个人讲一遍:能讲清楚才是真懂了
# 一句话总结
这个项目的难点不在 AI 本身,而在"如何让不稳定的 LLM 在生产环境稳定提供服务"。把每条简历描述都还原成"它解决了什么问题",你就算学透了。
上次更新: 2026/11/4 11:30:46