🧩 背景与需求
在现代企业协作中,员工常常需要快速获取制度文档、项目复盘记录或操作手册等信息。典型问题如:
- "我们公司的请假制度是什么?"
- "上次项目复盘提到哪些问题?"
传统方式依赖人工搜索或翻阅 Confluence/Notion,效率低下。为此,构建一个基于内部知识库的智能问答系统成为提升组织效能的关键一步。
本文将带你从零开始,使用 Java + Spring Boot 实现一个轻量级但可扩展的知识库问答系统,并配套一个简洁的博客前端用于演示。整个方案支持本地文档、Notion 或内部 Wiki(通过 FireCrawl)作为数据源,满足 70% 以上的企业基础问答场景。
🏗️ 系统架构设计
我们采用"检索增强生成"(Retrieval-Augmented Generation, RAG)的核心思想,但为降低复杂度,初期以关键词检索 + 原文返回为主,后续可无缝升级至语义向量检索与大模型生成。
在线问答流程
离线数据摄入
提供索引
本地文件系统
构建 Lucene 索引
Notion API
FireCrawl 爬取内部 Wiki
用户提问
Spring Boot 后端
查询解析
检索 Lucene 索引
返回相关段落
前端博客展示
💡 为什么选择 Lucene?
对于中文关键词匹配(如"请假制度"),Lucene 的倒排索引高效、稳定,且无需 GPU 或外部 API,非常适合 Java 技术栈的轻量部署。
🛠️ 技术选型
| 模块 | 技术 |
|---|---|
| 后端框架 | Spring Boot 3.x |
| 全文检索 | Apache Lucene 9.8 |
| 前端展示 | 静态 HTML + JavaScript |
| 数据源 | 本地 .txt 文件(可扩展至 Notion/FireCrawl) |
| 部署 | Jar 包 / Docker |
⚠️ 注:若需语义理解(如"年假怎么算?"匹配"入职满一年享5天年假"),可后续集成 Jina Embeddings 或本地 Sentence-BERT 模型。
📁 项目结构
knowledge-qa-blog/
├── src/main/java/com/example/kbqa/
│ ├── KbQaApplication.java # 启动类
│ ├── controller/QaController.java # 问答 API
│ ├── service/QaService.java # 检索逻辑
│ └── service/DocumentIngestService.java # 文档索引构建
├── src/main/resources/
│ ├── static/index.html # 博客前端
│ └── documents/policy.txt # 示例知识库
└── pom.xml
🔧 核心代码实现
1. 文档索引构建(DocumentIngestService)
系统启动时自动扫描 resources/documents/ 目录,为每个文本文件建立 Lucene 索引。
java
@Service
public class DocumentIngestService {
@PostConstruct
public void ingestLocalDocuments() throws IOException {
Path indexPath = Paths.get("index");
try (Directory dir = FSDirectory.open(indexPath);
IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(new StandardAnalyzer()))) {
Files.list(Paths.get("src/main/resources/documents"))
.filter(p -> p.toString().endsWith(".txt"))
.forEach(path -> {
try {
String content = Files.readString(path, StandardCharsets.UTF_8);
Document doc = new Document();
doc.add(new TextField("content", content, Field.Store.YES));
writer.addDocument(doc);
} catch (IOException e) {
throw new RuntimeException("索引文档失败: " + path, e);
}
});
}
}
}
2. 问答服务(QaService)
接收用户问题,通过 Lucene 查询返回最相关的文档片段。
java
@Service
public class QaService {
private IndexSearcher searcher;
private final Analyzer analyzer = new StandardAnalyzer();
@PostConstruct
public void initSearcher() throws IOException {
Directory dir = FSDirectory.open(Paths.get("index"));
searcher = new IndexSearcher(DirectoryReader.open(dir));
}
public String answerQuestion(String question) {
try {
Query query = new QueryParser("content", analyzer).parse(question);
TopDocs results = searcher.search(query, 2); // 返回前2个结果
if (results.scoreDocs.length == 0) {
return "未在知识库中找到相关信息。";
}
StringBuilder answer = new StringBuilder();
for (ScoreDoc sd : results.scoreDocs) {
Document doc = searcher.doc(sd.doc);
answer.append(doc.get("content")).append("\n\n");
}
return answer.toString().trim();
} catch (Exception e) {
return "系统处理出错:" + e.getMessage();
}
}
}
3. REST API 接口(QaController)
java
@RestController
@RequestMapping("/api")
public class QaController {
@Autowired
private QaService qaService;
@PostMapping("/ask")
public ResponseEntity<Map<String, String>> ask(@RequestBody Map<String, String> payload) {
String question = payload.get("question");
String answer = qaService.answerQuestion(question);
return ResponseEntity.ok(Map.of("answer", answer));
}
}
🌐 前端博客页面(static/index.html)
一个极简但功能完整的交互界面,便于演示和测试:
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>企业知识库问答系统</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; max-width: 720px; margin: 40px auto; padding: 0 20px; }
h1 { text-align: center; color: #2c3e50; }
input { width: 65%; padding: 12px; border: 1px solid #ccc; border-radius: 4px; }
button { padding: 12px 20px; background: #3498db; color: white; border: none; border-radius: 4px; cursor: pointer; }
#answer { margin-top: 24px; padding: 16px; background: #f8f9fa; border-left: 4px solid #3498db; border-radius: 4px; }
</style>
</head>
<body>
<h1>🔍 企业知识库智能问答</h1>
<p>示例问题:<code>"请假制度是什么?"</code> 或 <code>"项目复盘有哪些问题?"</code></p>
<input type="text" id="question" placeholder="请输入您的问题..." />
<button onclick="ask()">提问</button>
<div id="answer"></div>
<script>
async function ask() {
const q = document.getElementById("question").value.trim();
if (!q) return;
const res = await fetch("/api/ask", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ question: q })
});
const data = await res.json();
document.getElementById("answer").innerText = data.answer;
}
</script>
</body>
</html>
📂 示例知识库内容
resources/documents/policy.txt:
公司请假制度说明:
1. 年假:员工入职满1年可享受5天带薪年假,之后每增加1年工龄加1天,上限15天。
2. 病假:需提供二级以上医院证明,每月不超过2天带薪病假。
3. 事假:需提前3个工作日申请,事假期间无薪资。
4. 丧假:直系亲属(父母、配偶、子女)去世,可享3天全薪丧假。
▶️ 运行与测试
-
将上述文件放入对应目录
-
启动应用:
bash./mvnw spring-boot:run -
访问
http://localhost:8080 -
输入问题:"请假制度是什么?" → 系统返回匹配段落
🚀 扩展方向(⭐⭐⭐ 高阶能力)
当前实现覆盖约 70% 的常见问答场景。为进一步提升智能化水平,可逐步引入:
| 能力 | 实现方式 |
|---|---|
| 语义搜索 | 集成 Jina AI Embeddings API,将文本转为向量,使用 FAISS 或 Milvus 存储检索 |
| Notion 同步 | 定期调用 Notion Database API 拉取最新内容 |
| 内部 Wiki 抓取 | 使用 FireCrawl SDK 自动爬取 Confluence 页面 |
| 答案生成 | 调用本地 Ollama(如 llama3)对检索结果进行总结润色 |
| 权限控制 | 结合企业 OAuth2/LDAP,限制敏感文档访问 |
✅ 总结
本文展示了如何用 纯 Java 技术栈 构建一个实用的企业知识库问答系统。其优势在于:
- 轻量:无需 GPU、大模型或复杂依赖
- 可控:所有数据保留在内网,符合安全合规要求
- 可扩展:模块化设计,便于未来升级至语义 RAG 架构
该方案特别适合中小团队快速落地"智能知识助手",显著减少重复咨询,提升信息获取效率。
延伸阅读: