从零构建智能作业批改系统:Spring Boot + Vue3 + RAG 实战
一个基于 AI 的全栈作业批改平台开发实录
项目概述
这是一个面向教育场景的智能作业批改系统,支持多格式文档上传、AI 智能批改、流式对话交互,以及基于 RAG(检索增强生成)的知识库问答功能。
技术栈:
- 后端: Spring Boot 4.x + MyBatis-Plus + PostgreSQL + JWT
- 前端: Vue 3 + Element Plus + Pinia
- AI 层: OpenClaw 网关 + 自研 RAG 引擎
- 文档解析: Apache Tika
一、系统架构设计
1.1 整体架构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| ┌─────────────────────────────────────────────────────────────────┐ │ 前端层 (Vue 3) │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 登录页面 │ │ 聊天页面 │ │ 文件上传 │ │ 历史记录 │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ API 网关层 │ │ Spring Boot + Spring Security + JWT │ └─────────────────────────────────────────────────────────────────┘ │ ┌───────────────────┼───────────────────┐ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 用户认证模块 │ │ 聊天/批改模块 │ │ RAG 知识库 │ │ - 注册/登录 │ │ - 流式对话 │ │ - 文档解析 │ │ - JWT 鉴权 │ │ - 作业批改 │ │ - 智能分块 │ │ - 权限控制 │ │ - 历史记录 │ │ - 向量检索 │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ └───────────────────┼───────────────────┘ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 数据持久层 │ │ PostgreSQL (业务数据) + pgvector (向量存储) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ AI 服务层 (OpenClaw) │ │ 大模型调用 + 智能体编排 + 多模型路由 │ └─────────────────────────────────────────────────────────────────┘
|
1.2 核心模块划分
| 模块 |
职责 |
关键技术 |
auth |
用户认证与授权 |
JWT、Spring Security |
chat |
对话与批改核心 |
SSE 流式、WebFlux |
rag |
知识库与检索 |
向量嵌入、语义搜索 |
document |
文档管理 |
Apache Tika、文件存储 |
storage |
文件上传下载 |
NIO、UUID 命名 |
二、核心技术实现
2.1 RAG 检索增强生成
RAG 是本系统的核心亮点,实现了文档的智能解析、分块、嵌入和检索。
2.1.1 智能文档分块 (SmartChunkService)
不同于简单的固定长度切割,系统实现了多策略智能分块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
public List<DocumentChunk> chunk(String content, ChunkConfig config) { DocType docType = detectDocType(content); List<String> rawChunks; switch (docType) { case MARKDOWN: rawChunks = splitByMarkdownHeaders(content, config); break; case CODE: rawChunks = splitByCodeStructure(content, config); break; case CONVERSATION: rawChunks = splitByConversationTurns(content, config); break; default: rawChunks = splitBySemanticBoundaries(content, config); } return applySlidingWindow(rawChunks, config); }
|
分块策略对比:
| 策略 |
适用场景 |
优势 |
| Markdown 标题分割 |
文档、笔记 |
保持章节完整性 |
| 代码结构分割 |
源代码文件 |
按函数/类边界切割 |
| 对话轮次分割 |
聊天记录 |
保持对话上下文 |
| 语义边界分割 |
通用文本 |
基于句子相似度 |
2.1.2 语义边界检测算法
核心思想:语义相似度低的句子边界 = 主题转换点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private List<String> splitBySemanticBoundaries(String content, ChunkConfig config) { String[] sentences = content.split("(?<=[。!?.!?])\\s+"); List<float[]> embeddings = embeddingService.embedBatch(Arrays.asList(sentences)); for (int i = 0; i < sentences.length; i++) { if (i > 0) { double similarity = cosineSimilarity(embeddings.get(i), embeddings.get(i-1)); if (similarity < SEMANTIC_THRESHOLD) { } } } }
|
2.1.3 轻量级嵌入服务
考虑到国内网络环境,放弃了 HuggingFace 模型下载,实现了基于词频的稀疏向量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Service public class EmbeddingService { public float[] embed(String text) { Map<String, Integer> wordFreq = extractKeywords(text); float[] vector = new float[384]; for (Map.Entry<String, Integer> entry : wordFreq.entrySet()) { int idx = Math.abs(entry.getKey().hashCode()) % 384; vector[idx] += entry.getValue(); } return normalize(vector); } }
|
优势:
- 无需下载大模型,启动即用
- 计算速度快,CPU 即可运行
- 支持中文分词和 2-gram 特征
2.1.4 向量存储与检索
使用 PostgreSQL + pgvector 扩展:
1 2 3 4 5 6 7 8 9
| public List<DocumentChunk> similaritySearch(float[] queryEmbedding, int topK) { String sql = """ SELECT * FROM document_chunk ORDER BY embedding_vec <=> ?::vector LIMIT ? """; return jdbcTemplate.query(sql, rowMapper, vectorStr, topK); }
|
降级策略: 当 pgvector 不可用时,自动回退到全表扫描 + 余弦相似度计算。
2.2 流式对话实现
2.2.1 SSE 流式响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public ResponseEntity<StreamingResponseBody> streamMessage( @RequestParam String message, @RequestParam(required = false) String sessionId) { StreamingResponseBody responseBody = outputStream -> { openClawService.streamChat(message, sessionId, status) .doOnNext(chunk -> { String sseLine = "data: " + chunk + "\n\n"; outputStream.write(sseLine.getBytes()); outputStream.flush(); }) .doOnComplete(() -> { outputStream.write("data: [DONE]\n\n".getBytes()); }) .blockLast(Duration.ofMinutes(5)); }; return ResponseEntity.ok() .header("X-Accel-Buffering", "no") .body(responseBody); }
|
2.2.2 前端流式接收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const reader = response.body.getReader(); const decoder = new TextDecoder();
while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop(); for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); this.messages[msgIndex].content += data; } } }
|
2.3 多格式文档解析
使用 Apache Tika 实现统一的文档解析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Service public class FileStorageServiceImpl implements FileStorageService { private String parseWithTika(Path filePath) { AutoDetectParser parser = new AutoDetectParser(); BodyContentHandler handler = new BodyContentHandler(5 * 1024 * 1024); Metadata metadata = new Metadata(); ParseContext context = new ParseContext(); PDFParserConfig pdfConfig = new PDFParserConfig(); pdfConfig.setExtractInlineImages(false); pdfConfig.setSortByPosition(true); context.set(PDFParserConfig.class, pdfConfig); parser.parse(inputStream, handler, metadata, context); return handler.toString(); } }
|
支持格式: PDF、Word、Excel、PPT、TXT、Markdown 等。
2.4 JWT 认证与权限控制
2.4.1 JWT 工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Component public class JwtUtil { public String generateToken(Long userId, Integer status) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim("status", status) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)) .signWith(key, SignatureAlgorithm.HS256) .compact(); } public Long getUserIdFromToken(String token) { Claims claims = parseToken(token); return Long.valueOf(claims.getSubject()); } }
|
2.4.2 Spring Security 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**", "/api/chat/health").permitAll() .anyRequest().authenticated() ); return http.build(); } }
|
三、关键技术点总结
3.1 知识点梳理
| 技术领域 |
核心知识点 |
应用场景 |
| Spring Boot |
自动配置、Starter 依赖、Actuator |
快速搭建后端服务 |
| Spring Security |
过滤器链、JWT 认证、CORS 配置 |
统一认证授权 |
| MyBatis-Plus |
代码生成、分页插件、逻辑删除 |
数据库 CRUD |
| WebFlux |
Reactive 编程、Mono/Flux、背压 |
流式数据处理 |
| RAG |
文档分块、向量嵌入、相似度检索 |
知识库问答 |
| Vue 3 |
Composition API、响应式系统、Teleport |
前端组件化 |
| SSE |
EventSource、ReadableStream |
实时消息推送 |
3.2 设计模式应用
| 模式 |
应用位置 |
说明 |
| 策略模式 |
SmartChunkService |
不同文档类型使用不同分块策略 |
| 工厂模式 |
EmbeddingService |
根据配置创建不同的嵌入实现 |
| 降级模式 |
VectorStoreService |
pgvector 不可用时回退到全表扫描 |
| 拦截器模式 |
JWT 过滤器 |
统一处理认证逻辑 |
3.3 性能优化点
- 连接池优化: 使用 HikariCP,配置合理的最大连接数
- 向量检索优化: pgvector 索引 + HNSW 算法
- 流式响应: 禁用 Nginx 缓冲,减少内存占用
- 批量嵌入: 一次性处理多个句子,减少 IO 次数
四、项目结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| demo/ ├── src/main/java/com/firedemo/demo/ │ ├── Bean/ # 配置类 │ │ ├── SecurityConfig.java # 安全配置 │ │ ├── OpenClawConfig.java # AI 网关配置 │ │ └── OpenClawProperties.java │ ├── Controller/ # 控制器层 │ │ ├── AuthController.java # 认证接口 │ │ ├── ChatController.java # 聊天/批改接口 │ │ ├── DocumentController.java │ │ └── FileUploadController.java │ ├── Service/ # 服务层 │ │ ├── ChatHistoryService.java │ │ ├── DocumentService.java │ │ ├── FileStorageService.java │ │ ├── OpenClawService.java │ │ └── ServiceImpl/ # 实现类 │ ├── Entity/ # 实体类 │ │ ├── User.java │ │ ├── Document.java │ │ ├── DocumentChunk.java │ │ ├── ChatHistory.java │ │ └── HomeworkEvaluation.java │ ├── mapper/ # MyBatis Mapper │ ├── DTO/ # 数据传输对象 │ ├── VO/ # 视图对象 │ ├── utils/ # 工具类 │ │ ├── JwtUtil.java │ │ └── JwtAuthenticationFilter.java │ └── rag/ # RAG 模块 ⭐ │ ├── SmartChunkService.java # 智能分块 │ ├── EmbeddingService.java # 嵌入服务 │ ├── VectorStoreService.java # 向量存储 │ └── RAGController.java ├── src/main/resources/ │ └── application.yml └── pom.xml
vue-project/ ├── src/ │ ├── api/ │ │ └── request.ts # Axios 封装 │ ├── views/ │ │ ├── LoginView.vue # 登录页 │ │ ├── PageTwo.vue # 聊天页 ⭐ │ │ └── ... │ ├── stores/ │ │ └── auth.ts # Pinia 状态管理 │ ├── router/ │ │ └── index.ts │ └── main.ts └── package.json
|
五、开发过程中的踩坑记录
5.1 Lombok 与 Spring Boot 4.x 兼容问题
现象: 编译时找不到生成的 getter/setter 方法
解决: 确保 maven-compiler-plugin 正确配置 annotationProcessorPaths
1 2 3 4 5 6 7 8 9 10 11 12
| <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </path> </annotationProcessorPaths> </configuration> </plugin>
|
5.2 MyBatis-Plus Spring Boot 4 适配
现象: 启动报错,找不到 Mapper
解决: 使用专为 Spring Boot 4 适配的依赖
1 2 3 4 5
| <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot4-starter</artifactId> <version>3.5.15</version> </dependency>
|
5.3 SSE 连接被 Nginx 缓冲
现象: 流式响应一次性返回,没有实时效果
解决: 添加响应头禁用缓冲
1 2 3 4
| return ResponseEntity.ok() .header("X-Accel-Buffering", "no") .header("Cache-Control", "no-cache") .body(responseBody);
|
5.4 HuggingFace 模型下载失败
现象: DJL 框架无法下载 embedding 模型
解决: 改用自研的基于词频的轻量级嵌入方案
六、未来规划
结语
这个项目从 0 到 1 的过程中,最大的收获不是技术本身,而是工程化思维的培养:
- 先跑通,再优化 - 不要一开始就追求完美架构
- 降级思维 - 每个外部依赖都要有 fallback 方案
- 可观测性 - 日志、监控、健康检查缺一不可
- 用户视角 - 流式响应比等待完整回复体验好 10 倍
希望这篇文章对你有所帮助。如有问题,欢迎交流!
项目地址: (待补充)
作者: 龙空灵
开发周期: 2 周