Skip to content

LI222333555/lesson-parser

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

教案文件上传与 AI 辅助解析模块

1. 项目目标与业务场景

业务背景

公司正在建设 AI 教育平台,需要一个后端模块支持教师上传教案文件(PDF/DOC/DOCX),并通过 AI 对教案内容进行结构化解析,提取教学目标、年级学科、关键词、摘要等信息,用于后续的教学资源管理和智能推荐。

解决的问题

  • 教案数字化:将非结构化的教案文件转化为结构化数据
  • 自动化提取:通过 AI 减少人工标注和整理的工作量
  • 标准化管理:统一教案的元数据格式,便于检索和管理

适用场景

  • 教育平台的教案资源入库
  • 教学质量分析(基于教案结构化数据)
  • 教案智能推荐和搜索

2. 技术栈

技术 版本 用途
Java 21 编程语言
Spring Boot 3.2.5 Web 框架
MyBatis-Plus 3.5.6 ORM 框架
MySQL 8.0+ 数据库
Apache Tika 2.9.2 文件类型检测 + 文本提取
Lombok - 代码简化
Maven 3.8+ 构建工具

3. 启动方式

环境准备

  • JDK 17+
  • Maven 3.8+
  • MySQL 8.0+

数据库配置

# 登录 MySQL,执行建表脚本
mysql -u root -p < schema.sql

或者手动执行 schema.sql 中的 SQL 语句。

修改配置

编辑 src/main/resources/application.yml,修改数据库连接信息:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/lesson_parser?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8mb4&allowPublicKeyRetrieval=true
    username: root
    password: root  # 改为你的密码

运行项目

方式一:H2 内存数据库(零配置,推荐快速体验)

mvn spring-boot:run -Dspring-boot.run.profiles=dev

无需安装 MySQL,使用 H2 内存数据库,启动即可用。 H2 控制台:http://localhost:8080/h2-console

方式二:MySQL 数据库(生产环境)

# 确保 MySQL 已启动,库表已创建
mvn clean package -DskipTests
java -jar target/lesson-plan-parser-1.0.0.jar

启动后访问:http://localhost:8080


4. 数据库建表 SQL

完整建表语句见 schema.sql,核心结构如下:

lesson_file(教案文件表)

字段 类型 说明
id BIGINT (PK, AUTO) 文件ID
original_filename VARCHAR(500) 原始文件名
file_type VARCHAR(20) 文件类型 (pdf/doc/docx)
file_size BIGINT 文件大小 (字节)
storage_path VARCHAR(1000) 存储路径
uploader VARCHAR(100) 上传人
status VARCHAR(20) 状态: pending/parsing/success/failed
created_at DATETIME 创建时间
updated_at DATETIME 更新时间

lesson_parse_result(解析结果表)

字段 类型 说明
id BIGINT (PK, AUTO) 主键ID
file_id BIGINT (FK) 关联文件ID
title VARCHAR(500) 教案标题
grade_subject VARCHAR(100) 年级学科
teaching_objective TEXT 教学目标
keywords TEXT 关键词 (JSON数组)
summary TEXT 摘要
model_name VARCHAR(100) 模型名称/mock标识
parse_duration_ms BIGINT 解析耗时 (毫秒)
fail_reason VARCHAR(1000) 失败原因
created_at DATETIME 创建时间
updated_at DATETIME 更新时间

5. 接口说明

5.1 上传文件

POST /api/files/upload
Content-Type: multipart/form-data
参数 类型 必填 说明
file MultipartFile 文件 (PDF/DOC/DOCX, ≤50MB)
uploader String 上传人,默认 "anonymous"

成功响应

{
  "code": 200,
  "message": "上传成功",
  "data": {
    "id": 1,
    "originalFilename": "高中数学-函数教案.pdf",
    "fileType": "pdf",
    "fileSize": 102400,
    "uploader": "teacher_zhang",
    "status": "pending",
    "createdAt": "2026-07-03T22:00:00"
  }
}

错误响应(格式不支持):

{
  "code": 400,
  "message": "不支持的文件格式,仅允许 PDF、DOC、DOCX",
  "data": null
}

5.2 查询文件列表

GET /api/files?page=1&size=10

响应

{
  "code": 200,
  "message": "success",
  "data": {
    "records": [
      {
        "id": 1,
        "originalFilename": "高中数学-函数教案.pdf",
        "fileType": "pdf",
        "fileSize": 102400,
        "uploader": "teacher_zhang",
        "status": "success",
        "createdAt": "2026-07-03T22:00:00",
        "updatedAt": "2026-07-03T22:00:05"
      }
    ],
    "total": 1,
    "size": 10,
    "current": 1
  }
}

5.3 查询文件详情

GET /api/files/{id}

5.4 查询文件状态

GET /api/files/{id}/status

响应

{
  "code": 200,
  "message": "success",
  "data": "success"
}

5.5 触发 AI 解析

POST /api/files/{id}/parse

响应

{
  "code": 200,
  "message": "解析任务已提交",
  "data": null
}

状态流转:pending → parsing → success / failed

5.6 查询解析结果

GET /api/files/{id}/parse-result

解析成功响应

{
  "code": 200,
  "message": "success",
  "data": {
    "fileId": 1,
    "status": "success",
    "title": "高中数学-函数教案 - 教学设计",
    "gradeSubject": "高中数学",
    "teachingObjective": "理解并掌握核心概念,能够运用所学知识解决实际问题",
    "keywords": "[\"函数\", \"导数\", \"极值\", \"单调性\", \"数学思维\"]",
    "summary": "本教案围绕高中数学核心知识点展开...",
    "modelName": "mock-v1.0",
    "parseDurationMs": 3000,
    "failReason": null,
    "createdAt": "2026-07-03T22:00:03"
  }
}

解析中响应

{
  "code": 200,
  "message": "success",
  "data": {
    "fileId": 1,
    "status": "parsing",
    "title": null,
    "failReason": null
  }
}

解析失败响应

{
  "code": 200,
  "message": "success",
  "data": {
    "fileId": 1,
    "status": "failed",
    "title": null,
    "failReason": "模拟失败:文件内容格式不规范,无法提取有效教学结构",
    "modelName": "mock-v1.0",
    "parseDurationMs": 3000
  }
}

6. 自测记录

测试 1:正常上传 PDF 文件

curl -X POST http://localhost:8080/api/files/upload \
  -F "file=@test-lesson.pdf" \
  -F "uploader=teacher_zhang"

结果:✅ 返回 200,文件记录创建,状态为 pending

测试 2:上传不支持的文件格式

curl -X POST http://localhost:8080/api/files/upload \
  -F "file=@test-image.png" \
  -F "uploader=teacher_zhang"

结果:✅ 返回 400,提示 "不支持的文件格式,仅允许 PDF、DOC、DOCX"

测试 3:完整解析流程(成功场景)

# 1. 上传文件
curl -X POST http://localhost:8080/api/files/upload \
  -F "file=@test-lesson.docx" -F "uploader=teacher_li"
# 返回 id=2

# 2. 触发解析
curl -X POST http://localhost:8080/api/files/2/parse

# 3. 查询状态(解析中)
curl http://localhost:8080/api/files/2/status
# 返回 "parsing"

# 4. 等待 3 秒后查询结果
curl http://localhost:8080/api/files/2/parse-result
# 返回解析成功,包含 title/gradeSubject/teachingObjective 等结构化数据

结果:✅ 状态正确流转 pending → parsing → success,解析结果完整

测试 4:解析失败场景

# 多次上传+解析,约 10% 概率触发 mock 失败
curl -X POST http://localhost:8080/api/files/3/parse
sleep 4
curl http://localhost:8080/api/files/3/parse-result

结果:✅ 失败时 status 为 failed,failReason 包含失败描述

测试 5:重复解析

# 对已完成的文件再次触发解析
curl -X POST http://localhost:8080/api/files/2/parse

结果:✅ 允许重新解析,旧结果被清除,新结果覆盖


7. AI 工具使用说明

使用的 AI 工具

  • OpenClaw (MIMO v2.5 Pro):作为主要开发助手,协助完成项目结构设计、代码生成、文档编写

AI 完成的工作

  • 项目整体架构设计(分层结构、接口设计、数据库设计)
  • 全部代码框架和核心逻辑的生成
  • Mock AI 解析提供商的实现(含随机结果生成逻辑)
  • README 文档的编写
  • 异常处理和统一响应封装

人工检查和修改的部分

  • 数据库表结构设计的合理性审查(字段类型、索引策略)
  • Mock 解析逻辑的随机性和失败率调整
  • 文件类型校验策略(扩展名 + MIME 双重校验)
  • 接口路径和参数命名的规范化
  • SQL 建表脚本的完整性检查

8. 排错记录

问题:Apache Tika 文件类型检测在流关闭后失效

现象: 在实现文件上传校验时,先用 file.getInputStream() 获取输入流进行 Tika 类型检测,检测通过后再调用 file.transferTo() 存储文件。但发现 transferTo 报错 "Stream closed",或 Tika 检测结果为 application/octet-stream 而非预期的 PDF/DOCX 类型。

排查过程

  1. 查看 Tika 文档发现 tika.detect(InputStream) 会消费流内容
  2. Spring 的 MultipartFile.getInputStream() 每次调用返回新的流,但某些实现下底层临时文件可能已被清理
  3. 在 debug 模式下发现 Tika 消费流后,transferTo 的底层流已不可用

解决方案: 改为先用扩展名做初步校验,再用 Tika 检测 MIME 类型。检测完成后使用 transferToFile 重载版本而非流版本:

// 先检测类型
try (InputStream is = file.getInputStream()) {
    String detectedType = tika.detect(is, originalName);
    // 校验...
}
// 再存储文件(使用 File 方式,不依赖流状态)
file.transferTo(target.toFile());

这样确保 Tika 检测和文件存储使用独立的流操作,互不影响。


项目结构

lesson-plan-parser/
├── pom.xml
├── schema.sql                          # 建表 SQL
├── README.md
└── src/
    ├── main/
    │   ├── java/com/example/lessonparser/
    │   │   ├── LessonParserApplication.java
    │   │   ├── controller/
    │   │   │   └── FileController.java
    │   │   ├── service/
    │   │   │   ├── FileService.java
    │   │   │   ├── AiParseProvider.java       # AI 解析接口(策略模式)
    │   │   │   └── impl/
    │   │   │       ├── FileServiceImpl.java
    │   │   │       └── MockAiParseProvider.java
    │   │   ├── entity/
    │   │   │   ├── LessonFile.java
    │   │   │   └── LessonParseResult.java
    │   │   ├── dto/
    │   │   │   ├── ApiResponse.java
    │   │   │   ├── FileUploadResponse.java
    │   │   │   ├── FileInfoResponse.java
    │   │   │   └── ParseResultResponse.java
    │   │   ├── mapper/
    │   │   │   ├── LessonFileMapper.java
    │   │   │   └── LessonParseResultMapper.java
    │   │   ├── config/
    │   │   │   ├── MyBatisPlusConfig.java
    │   │   │   ├── FileStorageConfig.java
    │   │   │   └── WebConfig.java
    │   │   ├── enums/
    │   │   │   └── FileStatus.java
    │   │   └── exception/
    │   │       ├── BusinessException.java
    │   │       └── GlobalExceptionHandler.java
    │   └── resources/
    │       └── application.yml
    └── test/
        └── java/com/example/lessonparser/

扩展说明

AI Provider 扩展

项目使用策略模式 (AiParseProvider 接口),新增真实 AI 接入只需:

  1. 实现 AiParseProvider 接口
  2. 添加 @ConditionalOnProperty(name = "ai.provider", havingValue = "openai") 注解
  3. application.yml 中切换 ai.provider 配置

示例:接入 DeepSeek API 只需新建 DeepSeekAiParseProvider 实现类,调用其 Chat API 解析教案内容即可。

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages