Files
012-kaopeilian/知识库/问题修复/文件上传和AI分析问题修复报告.md
111 998211c483 feat: 初始化考培练系统项目
- 从服务器拉取完整代码
- 按框架规范整理项目结构
- 配置 Drone CI 测试环境部署
- 包含后端(FastAPI)、前端(Vue3)、管理端

技术栈: Vue3 + TypeScript + FastAPI + MySQL
2026-01-24 19:33:28 +08:00

10 KiB
Raw Permalink Blame History

文件上传和AI分析问题修复报告

修复时间

2025-10-16 22:40 - 2025-10-17 06:54

问题描述

用户报告了两个关键问题:

问题1文件上传后刷新页面消失

  • 现象:上传文件后显示成功,也看得到文件,但刷新页面后文件就消失了
  • 根因数据库记录没有真正创建数据库中没有对应的course_materials记录

问题2AI分析知识点未正确启动Dify工作流

  • 现象文件上传后AI分析未自动启动
  • 根因前端代码只改变UI状态没有真正调用AI分析API

根本原因分析

原因1http.ts的upload方法双重数据提取

文件/root/aiedu/kaopeilian-frontend/src/utils/http.ts

问题代码第392行

upload<T = any>(url: string, file: File, config?: RequestConfig): Promise<ResponseData<T>> {
  // ... FormData构建 ...
  return this.instance.post(url, formData, config)
    .then((response) => response.data as ResponseData<T>)  // ❌ 多余的数据提取
}

数据流转过程

  1. 后端返回:{ code: 200, data: { file_url: '...', file_type: '...', file_size: ... } }
  2. 响应拦截器处理后返回:{ code: 200, data: { file_url: '...', ... } }
  3. upload方法又提取.data,最终返回:{ file_url: '...', ... } 丢失code字段
  4. 前端判断 uploadRes.code === 200 失败 → 抛出"文件上传失败"错误

修复措施

upload<T = any>(url: string, file: File, config?: RequestConfig): Promise<ResponseData<T>> {
  // ... FormData构建 ...
  return this.instance.post(url, formData, config)  // ✅ 直接返回
}

原因2上传成功后未启动AI分析

文件/root/aiedu/kaopeilian-frontend/src/views/manager/edit-course.vue

问题代码第1158-1165行

// 上传成功后,设置状态为分析中
setTimeout(() => {
  const material = materialList.value.find(m => m.id === newMaterial.id)
  if (material) {
    material.status = 'analyzing'  // ❌ 只改变UI状态
    ElMessage.info(`正在分析资料"${material.name}"的知识点...`)
  }
}, 1000)

问题只修改了UI状态没有调用API启动AI分析

修复措施

// 上传成功后自动启动AI分析
setTimeout(async () => {
  const material = materialList.value.find(m => m.id === newMaterial.id)
  if (material) {
    console.log('自动启动AI知识点分析:', material.id, material.name)
    await analyzeWithAI(material)  // ✅ 真正调用AI分析
  }
}, 1000)

原因3AI分析API返回404

现象POST /api/v1/courses/1/materials/30/analyze 返回404

根因前端传递的material_id=30在数据库中不存在

  • 数据库最新的material_id是26
  • material_id=30是前端临时添加到列表的但数据库记录创建失败
  • 因为之前的http.ts bug导致创建资料记录的API调用失败

修复详情

修复1http.ts的upload方法

文件/root/aiedu/kaopeilian-frontend/src/utils/http.ts第392行

修改前

}).then((response) => response.data as ResponseData<T>)

修改后

})  // 直接返回响应拦截器处理后的结果

修复2自动启动AI分析

文件/root/aiedu/kaopeilian-frontend/src/views/manager/edit-course.vue第1158-1165行

修改前

setTimeout(() => {
  const material = materialList.value.find(m => m.id === newMaterial.id)
  if (material) {
    material.status = 'analyzing'
    ElMessage.info(`正在分析资料"${material.name}"的知识点...`)
  }
}, 1000)

修改后

setTimeout(async () => {
  const material = materialList.value.find(m => m.id === newMaterial.id)
  if (material) {
    console.log('自动启动AI知识点分析:', material.id, material.name)
    await analyzeWithAI(material)
  }
}, 1000)

验证结果

验证1upload方法返回正确结构

修复后,upload() 方法正确返回 { code: 200, data: {...} } 结构

验证2数据库记录成功创建

调用 courseApi.addMaterial() 时能正确判断 res.code === 200 数据库中成功创建 course_materials 记录 刷新页面后文件不再消失

验证3AI分析自动启动

文件上传成功后自动调用 analyzeWithAI(material) 正确调用 /api/v1/courses/{course_id}/materials/{material_id}/analyze API Dify工作流成功启动

完整的数据流程

修复后的正确流程

  1. 用户选择文件并点击"确认上传"

  2. 前端上传文件到服务器

    const uploadRes = await request.upload(
      `/api/v1/upload/course/${courseId}/materials`,
      file.raw
    )
    // 返回:{ code: 200, data: { file_url: '...', file_type: '...', file_size: ... } }
    
  3. 判断上传成功

    if (uploadRes.code === 200 && uploadRes.data) {  // ✅ 判断通过
    
  4. 创建数据库记录

    const res = await courseApi.addMaterial(courseId, {
      name: file.name,
      file_url: uploadRes.data.file_url,
      file_type: uploadRes.data.file_type,
      file_size: uploadRes.data.file_size
    })
    // 返回:{ code: 200, data: { id: 27, name: '...', ... } }
    
  5. 添加到前端列表

    const newMaterial = {
      id: res.data.id,  // ✅ 真实的数据库ID
      name: res.data.name,
      status: 'pending',
      knowledgePoints: []
    }
    materialList.value.push(newMaterial)
    
  6. 自动启动AI分析

    setTimeout(async () => {
      await analyzeWithAI(material)
      // 调用POST /api/v1/courses/{course_id}/materials/{material.id}/analyze
    }, 1000)
    
  7. Dify工作流启动

    • 后端接收分析请求
    • 调用Dify API上传文件并启动知识点分析工作流
    • 返回workflow_run_id给前端
    • 工作流完成后自动保存知识点到数据库
  8. 刷新页面

    • 从数据库加载 course_materials
    • 文件和知识点都正常显示

技术要点总结

1. 响应拦截器与方法封装的关系

核心原则

  • 响应拦截器负责统一处理所有响应,返回标准的 {code, message, data} 结构
  • 具体方法get/post/upload等只负责构造请求不应再次处理响应结构
  • 保持"单一职责":拦截器处理响应,方法构造请求

常见错误

// ❌ 错误:双重数据提取
upload() {
  return this.instance.post(url, data).then(res => res.data)
  // 响应拦截器已返回 {code, data},再次.data会丢失code
}

// ✅ 正确:直接返回
upload() {
  return this.instance.post(url, data)
}

2. 异步操作的完整性

原则

  • UI状态更新必须基于实际的后端操作结果
  • 不能"假装成功"——只改UI不调API
  • 异步任务必须真正启动,不能只显示"进行中"

修复前

material.status = 'analyzing'  // 假装在分析
ElMessage.info('正在分析...')  // 但实际没调用API

修复后

await analyzeWithAI(material)  // 真正调用API
// API内部会
// 1. 设置状态为analyzing
// 2. 调用后端API
// 3. 根据结果更新状态为completed/failed

3. 数据库事务的正确性

虽然这次不是数据库事务的问题,但需要注意:

  • FastAPI的 get_db 依赖会自动commit
  • 但前提是API调用成功执行到结束
  • 如果API调用失败如前端判断错误没有调用数据库操作根本不会发生

相关文件

修改的文件

  1. /root/aiedu/kaopeilian-frontend/src/utils/http.ts - 修复upload方法
  2. /root/aiedu/kaopeilian-frontend/src/views/manager/edit-course.vue - 修复AI分析启动

文档更新

  1. /root/aiedu/考培练系统规划/全链路联调/联调经验汇总.md - 记录问题和修复
  2. /root/aiedu/考培练系统规划/全链路联调/规范与约定-团队基线.md - 更新团队规范

后端相关文件(已验证)

  1. /root/aiedu/kaopeilian-backend/app/api/v1/upload.py - 文件上传API ✓
  2. /root/aiedu/kaopeilian-backend/app/api/v1/courses.py - 资料管理API ✓
  3. /root/aiedu/kaopeilian-backend/app/api/v1/knowledge_analysis.py - 知识点分析API ✓
  4. /root/aiedu/kaopeilian-backend/app/services/course_service.py - 课程服务 ✓

测试建议

测试步骤

  1. 打开编辑课程页面
  2. 点击"上传资料"选择一个PDF文件
  3. 点击"确认上传"
  4. 验证点1:上传成功提示,文件出现在列表中
  5. 验证点21秒后显示"AI正在分析文件内容提取知识点..."
  6. 验证点3等待30-180秒知识点分析完成
  7. 验证点4:刷新页面,文件和知识点仍然存在

数据库验证

-- 查询最新的资料记录
SELECT id, course_id, name, created_at 
FROM course_materials 
ORDER BY created_at DESC 
LIMIT 5;

-- 查询知识点
SELECT id, course_id, material_id, name, type, source
FROM knowledge_points
WHERE material_id = {新资料ID}
ORDER BY created_at DESC;

日志验证

# 查看后端日志
docker compose logs backend --tail 100 | grep -E "(上传成功|添加课程资料|知识点分析)"

# 查看前端日志  
# 浏览器控制台应显示:
# - 开始上传文件: xxx.pdf
# - 文件上传响应: { code: 200, data: {...} }
# - 创建资料记录响应: { code: 200, data: { id: 27, ... } }
# - 自动启动AI知识点分析: 27 xxx.pdf
# - AI分析API响应: { code: 200, data: { status: 'succeeded', ... } }

后续改进建议

  1. 添加单元测试

    • 测试 http.tsupload 方法返回结构
    • 测试 courseApi.addMaterial 的完整流程
  2. 增强错误处理

    • 文件上传失败时显示详细错误信息
    • AI分析失败时允许手动重试
  3. 优化用户体验

    • 显示上传进度条
    • 显示AI分析进度轮询任务状态
    • 支持批量上传文件
  4. 监控和告警

    • 记录文件上传失败率
    • 记录AI分析成功率
    • 异常时发送告警

总结

本次修复解决了两个关键问题:

  1. http.ts双重数据提取 - 导致文件上传后无法创建数据库记录
  2. AI分析未真正启动 - 导致知识点无法自动提取

核心教训:

  • 响应拦截器和方法封装要保持单一职责,避免重复处理
  • UI状态必须基于真实的API调用结果不能"假装成功"
  • 异步操作的完整性至关重要,每一步都要真正执行

修复后的完整流程已验证通过文件上传、数据库记录创建、AI知识点分析都能正常工作。