feat: 初始化考培练系统项目

- 从服务器拉取完整代码
- 按框架规范整理项目结构
- 配置 Drone CI 测试环境部署
- 包含后端(FastAPI)、前端(Vue3)、管理端

技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
111
2026-01-24 19:33:28 +08:00
commit 998211c483
1197 changed files with 228429 additions and 0 deletions

View File

@@ -0,0 +1,256 @@
# Dify API Keys 配置管理经验
## 📅 更新时间
2025-10-16
## 🎯 经验背景
在全链路联调阶段,发现代码中存在多处硬编码的 Dify API Keys不利于维护和安全管理。本文档总结了统一配置管理的实施经验。
## ✅ 问题发现
### 硬编码问题
**发现位置**`app/api/v1/exam.py`
```python
# ❌ 硬编码的 API Key
headers = {
"Authorization": "Bearer app-tDlrmXyS9NtWCShsOx5FH49L",
"Content-Type": "application/json",
}
```
**问题影响**
1. 更换 API Key 需要搜索全部代码
2. 敏感信息暴露在代码中
3. 不同环境无法灵活配置
4. 版本控制中包含敏感信息
## 🔧 解决方案
### 1. 配置文件集中管理
**文件**`app/core/config.py`
```python
class Settings(BaseSettings):
# Dify API 基础配置
DIFY_API_BASE: Optional[str] = Field(default="http://dify.ireborn.com.cn/v1")
# 各工作流 API Keys
DIFY_API_KEY: Optional[str] = Field(default="app-LZhZcMO6CiriLMOLB2PwUGHx") # 上传知识库
DIFY_EXAM_GENERATOR_API_KEY: str = Field(default="app-tDlrmXyS9NtWCShsOx5FH49L") # 试题生成器
DIFY_ANSWER_JUDGE_API_KEY: str = Field(default="app-FvMdrvbRBz547DVZEorgO1WT") # 答案判断器
DIFY_PRACTICE_API_KEY: Optional[str] = Field(default="app-rYP6LNM4iPmNjIHns12zFeJp") # 陪练场景提取
DIFY_PRACTICE_ANALYSIS_API_KEY: str = Field(default="app-9MWaCEiRegpYGQLov4S9oQjh") # 陪练分析报告
DIFY_COURSE_CHAT_API_KEY: str = Field(default="app-lJzD6COkL8z7Eez8t6ZrYoJS") # 与课程对话
DIFY_YANJI_ANALYSIS_API_KEY: str = Field(default="app-g0I5UT8lBB0fvuxGDOqrG8Zj") # 智能工牌分析
```
### 2. 代码重构
**重构前**
```python
headers = {
"Authorization": "Bearer app-tDlrmXyS9NtWCShsOx5FH49L",
"Content-Type": "application/json",
}
```
**重构后**
```python
from app.core.config import settings
headers = {
"Authorization": f"Bearer {settings.DIFY_EXAM_GENERATOR_API_KEY}",
"Content-Type": "application/json",
}
```
### 3. 配置验证脚本
创建验证脚本 `verify_dify_config.py`
```python
from app.core.config import settings
def verify_dify_config():
"""验证所有 Dify 配置"""
configs = [
("API Base URL", "DIFY_API_BASE", settings.DIFY_API_BASE),
("上传知识库", "DIFY_API_KEY", settings.DIFY_API_KEY),
("试题生成器", "DIFY_EXAM_GENERATOR_API_KEY", settings.DIFY_EXAM_GENERATOR_API_KEY),
# ... 其他配置项
]
all_valid = True
for name, var_name, value in configs:
if not value:
print(f"{name}: 配置缺失")
all_valid = False
else:
print(f"{name}: 已配置")
return all_valid
```
## 📋 完整工作流清单
| 工作流名称 | 配置变量 | API Key |
|-----------|---------|---------|
| 上传知识库 | `DIFY_API_KEY` | `app-LZhZcMO6CiriLMOLB2PwUGHx` |
| 试题生成器 | `DIFY_EXAM_GENERATOR_API_KEY` | `app-tDlrmXyS9NtWCShsOx5FH49L` |
| 答案判断器 | `DIFY_ANSWER_JUDGE_API_KEY` | `app-FvMdrvbRBz547DVZEorgO1WT` |
| 陪练场景提取 | `DIFY_PRACTICE_API_KEY` | `app-rYP6LNM4iPmNjIHns12zFeJp` |
| 陪练分析报告 | `DIFY_PRACTICE_ANALYSIS_API_KEY` | `app-9MWaCEiRegpYGQLov4S9oQjh` |
| 与课程对话 | `DIFY_COURSE_CHAT_API_KEY` | `app-lJzD6COkL8z7Eez8t6ZrYoJS` |
| 智能工牌分析 | `DIFY_YANJI_ANALYSIS_API_KEY` | `app-g0I5UT8lBB0fvuxGDOqrG8Zj` |
## 💡 最佳实践
### 1. 命名规范
- 统一前缀:`DIFY_`
- 功能描述:`EXAM_GENERATOR``ANSWER_JUDGE`
- 后缀:`_API_KEY`
- 示例:`DIFY_EXAM_GENERATOR_API_KEY`
### 2. 类型注解
```python
# ✅ 推荐:使用明确的类型注解
DIFY_EXAM_GENERATOR_API_KEY: str = Field(default="app-xxx")
# ⚠️ 可选配置使用 Optional
DIFY_API_KEY: Optional[str] = Field(default="app-xxx")
```
### 3. 环境变量覆盖
```bash
# .env 文件
DIFY_EXAM_GENERATOR_API_KEY=app-new-key-for-dev
DIFY_ANSWER_JUDGE_API_KEY=app-new-key-for-dev
```
### 4. 安全管理
```gitignore
# .gitignore 必须包含
.env
.env.local
.env.*.local
```
## 🔍 排查经验
### 1. 查找硬编码 API Keys
```bash
# 搜索所有 app- 开头的字符串
grep -r "app-[A-Za-z0-9]" --include="*.py" app/
# 搜索 Bearer 后跟 app-
grep -r "Bearer app-" --include="*.py" app/
```
### 2. 验证配置生效
```bash
# 运行验证脚本
python verify_dify_config.py
# 预期输出
✅ 所有 Dify 配置验证通过!
```
### 3. 调试配置加载
```python
# 临时添加调试日志
import logging
logger = logging.getLogger(__name__)
logger.debug(f"DIFY_EXAM_GENERATOR_API_KEY: {settings.DIFY_EXAM_GENERATOR_API_KEY[:20]}...")
```
## ⚠️ 注意事项
### 1. 不要提交敏感信息
```bash
# 检查暂存区
git diff --cached | grep -i "app-"
# 如果误提交,使用 git filter-branch 清理历史
```
### 2. 环境隔离
- 开发环境:使用测试 API Keys
- 生产环境:使用正式 API Keys
- 通过环境变量区分
### 3. API Key 轮换
定期轮换 API Keys步骤
1. 在 Dify 平台生成新 Key
2. 更新配置文件
3. 重启服务验证
4. 废弃旧 Key
### 4. 权限最小化
- 每个工作流使用独立 API Key
- 便于权限管理和问题追踪
- 避免一个 Key 的泄露影响所有功能
## 📊 实施效果
### 改进前
- ❌ 3 处硬编码 API Keys
- ❌ 修改需要搜索全部代码
- ❌ 无法按环境区分配置
- ❌ 安全风险高
### 改进后
- ✅ 统一在配置文件管理
- ✅ 一处修改全局生效
- ✅ 支持环境变量覆盖
- ✅ 提供验证脚本
- ✅ 完善的文档说明
## 🔗 相关文档
- [Dify API Keys 配置说明](/root/aiedu/kaopeilian-backend/docs/dify_api_keys.md)
- [Dify 系统对接分析报告](/root/aiedu/Dify系统对接分析报告.md)
- [配置更新总结](/root/aiedu/DIFY_API_KEYS_UPDATE_SUMMARY.md)
## 📝 更新日志
| 日期 | 变更内容 | 影响范围 |
|------|---------|---------|
| 2025-10-16 | 添加试题生成器和答案判断器 API Keys | `app/api/v1/exam.py` |
| 2025-10-16 | 移除硬编码,统一使用配置变量 | `app/api/v1/exam.py` |
| 2025-10-16 | 创建配置验证脚本 | 新增 `verify_dify_config.py` |
| 2025-10-16 | 创建配置文档 | 新增 `docs/dify_api_keys.md` |
## 🎓 经验总结
1. **早期规划很重要**:在项目初期就应该统一配置管理规范
2. **代码审查必不可少**:定期检查是否有新的硬编码出现
3. **自动化验证**:使用脚本自动验证配置完整性
4. **文档同步更新**:配置变更时必须同步更新文档
5. **安全意识**:敏感信息绝不提交到版本控制
## 🚀 后续优化建议
1. **CI/CD 集成**
- 在部署流程中添加配置验证步骤
- 配置缺失时阻止部署
2. **密钥管理服务**
- 考虑使用 AWS Secrets Manager 或 HashiCorp Vault
- 实现动态密钥轮换
3. **监控告警**
- API Key 即将过期时发送告警
- API 调用失败时检查 Key 是否有效
4. **权限审计**
- 定期审计 API Key 使用情况
- 发现异常调用及时处理

View File

@@ -0,0 +1,623 @@
# 考培练系统与Dify平台对接深度分析报告
## 目录
1. [系统概述](#系统概述)
2. [Dify API接口分析](#dify-api接口分析)
3. [前端页面对接实现](#前端页面对接实现)
4. [业务流程分析](#业务流程分析)
5. [技术架构图](#技术架构图)
6. [配置参数详解](#配置参数详解)
7. [数据流向分析](#数据流向分析)
8. [错误处理机制](#错误处理机制)
9. [性能优化建议](#性能优化建议)
## 系统概述
本考培练系统是一个基于 **Python + Vue3 + MySQL + FastAPI** 架构的智能教育平台与两个主要的AI平台进行深度对接
- **Dify平台**:用于动态题目生成和知识提取
- **Coze平台**用于AI陪练和智能对话
### 核心功能模块
- 动态考试题目生成基于Dify工作流
- 知识点提取与分析基于Dify工作流
- AI智能陪练基于Coze智能体
- 三轮考试机制(错题重练)
## Dify API接口分析
### 1. 主要接口端点
系统中使用了 **1个核心Dify API端点**
```
POST https://aiedu.ireborn.com.cn/v1/workflows/run
```
### 2. 使用的工作流Token
系统中发现了 **2个不同的工作流Token**
#### 2.1 动态题目生成工作流
- **Token**: `app-tDlrmXyS9NtWCShsOx5FH49L`
- **用途**: 根据考试ID和错题信息生成动态题目
- **文件位置**: `ExamsSystem/frontend/src/views/system/exams/start_exams.vue`
#### 2.2 知识提取工作流
- **Token**: `app-LZhZcMO6CiriLMOLB2PwUGHx`
- **用途**: 从考试附件中提取知识点
- **文件位置**: `ExamsSystem/frontend/src/views/system/exams/index.vue`
### 3. API请求参数详解
#### 3.1 动态题目生成API参数
```javascript
const payload = {
inputs: {
examsId: examId, // 考试ID必需
error: errorNums // 错题编号(可选,用于第二轮、第三轮)
},
response_mode: "blocking", // 同步模式
user: "abc-123" // 用户标识
};
```
**参数说明:**
- `examsId`: 当前考试的唯一标识符,用于工作流识别要生成哪个考试的题目
- `error`: 错题编号字符串,格式为逗号分隔的知识点编号,用于生成针对性的错题练习
- `response_mode`: 固定为"blocking",表示同步等待工作流执行完成
- `user`: 用户标识,固定为"abc-123"
#### 3.2 知识提取API参数
```javascript
const payload = {
inputs: {
examsTitle: exams_title, // 考试标题
file: [file], // 文件信息数组
examsId: row.id // 考试ID
},
response_mode: "blocking",
user: "abc-123"
};
// 文件对象结构
const file = {
transfer_method: "remote_url",
url: fileUrl, // 完整的文件URL
type: "document" // 文件类型
};
```
**参数说明:**
- `examsTitle`: 考试名称,帮助工作流理解文档内容的上下文
- `file`: 文件信息数组支持PDF等文档格式的知识提取
- `transfer_method`: 固定为"remote_url"表示通过URL方式传递文件
- `url`: 文件的完整访问URL支持相对路径自动补全为绝对路径
- `type`: 固定为"document",表示文档类型
### 4. API响应数据结构
#### 4.1 成功响应结构
```javascript
{
data: {
status: "succeeded", // 执行状态
outputs: {
result: [...] // 工作流输出结果
}
}
}
```
#### 4.2 题目数据结构
动态题目生成的响应数据中,`result`字段包含题目数组:
```javascript
[
{
topic: {
title: "题目内容", // 题目文本
options: {
opt1: "选项A",
opt2: "选项B",
opt3: "选项C",
opt4: "选项D"
}
},
correct: "A", // 正确答案
analysis: "解析内容", // 题目解析
know_title: "知识点编号" // 知识点标识
}
]
```
## 前端页面对接实现
### 1. 核心页面文件
#### 1.1 考试开始页面 (`start_exams.vue`)
- **路径**: `ExamsSystem/frontend/src/views/system/exams/start_exams.vue`
- **功能**: 动态题目生成、三轮考试机制、错题统计
- **关键函数**: `callDifyWorkflow()`
#### 1.2 考试管理页面 (`index.vue`)
- **路径**: `ExamsSystem/frontend/src/views/system/exams/index.vue`
- **功能**: 知识提取、考试管理
- **关键函数**: `update_know()`
#### 1.3 AI陪练页面 (`training.vue`)
- **路径**: `ExamsSystem/frontend/src/views/system/exams/training.vue`
- **功能**: 嵌入Coze聊天界面
- **实现方式**: iframe嵌入
### 2. 前端调用实现
#### 2.1 动态题目生成调用
```javascript
async function callDifyWorkflow(error = '') {
loading.value = true;
const url = "https://aiedu.ireborn.com.cn/v1/workflows/run";
const token = 'app-tDlrmXyS9NtWCShsOx5FH49L';
const payload = {
inputs: { examsId: examId },
response_mode: "blocking",
user: "abc-123"
};
// 错题重练时添加错题参数
if (error) {
payload.inputs.error = error;
}
try {
const res = await fetch(url, {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
});
const data = await res.json();
if (data.data.status != 'succeeded') throw new Error("请求失败");
questions.value = data.data.outputs.result;
loading.value = false;
return data;
} catch (err) {
console.error("Dify 工作流调用异常:", err);
loading.value = false;
return null;
}
}
```
#### 2.2 知识提取调用
```javascript
async function update_know(row) {
// 获取考试附件信息
const exams_title = row.title || '';
const fileList = Array.isArray(row.attachmentList) ? row.attachmentList : [];
if (!fileList.length) {
proxy.$modal.msgWarning("该考试没有附件,无法提取知识!");
return;
}
// 构建文件对象
const fileUrl = fileList[0].fileUrl || fileList[0].url || '';
const file = {
transfer_method: "remote_url",
url: fileUrl.startsWith('http') ? fileUrl : `https://aiedu.ireborn.com.cn${fileUrl}`,
type: "document"
};
const payload = {
inputs: {
examsTitle: exams_title,
file: [file],
examsId: row.id
},
response_mode: "blocking",
user: "abc-123"
};
const token = "app-LZhZcMO6CiriLMOLB2PwUGHx";
const url = "https://aiedu.ireborn.com.cn/v1/workflows/run";
proxy.$modal.loading("正在提取知识,请稍候...");
try {
const res = await fetch(url, {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
});
if (!res.ok) {
proxy.$modal.msgError("知识提取失败!");
return;
}
const data = await res.json();
proxy.$modal.msgSuccess("知识提取成功!");
console.log("Dify知识提取结果:", data);
} catch (err) {
proxy.$modal.closeLoading();
proxy.$modal.msgError("知识提取异常!");
console.error("Dify知识提取异常:", err);
}
}
```
### 3. 前端状态管理
#### 3.1 考试状态管理
```javascript
// 核心状态变量
const questions = ref([]); // 题目数组
const loading = ref(true); // 加载状态
const currentIndex = ref(0); // 当前题目索引
const selected = ref(''); // 选中答案
const answered = ref(false); // 是否已答题
const score = ref(0); // 当前分数
const wrongQuestions = ref([]); // 错题记录
const round = ref(1); // 当前轮次 (1,2,3)
// 三轮成绩记录
const firstRoundScore = ref(0);
const firstRoundTime = ref(null);
const secondRoundScore = ref(0);
const secondRoundTime = ref(null);
const thirdRoundScore = ref(0);
const thirdRoundTime = ref(null);
```
#### 3.2 轮次流转逻辑
```javascript
// 第二轮:基于第一轮错题
function restartWithWrongQuestions() {
const errorNums = wrongQuestions.value.map(item => item.title).join(',');
// 重置状态
currentIndex.value = 0;
score.value = 0;
wrongQuestions.value = [];
round.value = 2;
// 调用Dify生成针对性题目
callDifyWorkflow(errorNums);
}
// 第三轮:基于第二轮错题
function restartWithThirdQuestions() {
const errorNums = wrongQuestions.value.map(item => item.title).join(',');
// 重置状态
currentIndex.value = 0;
score.value = 0;
thirdWrongQuestions.value = [...wrongQuestions.value];
wrongQuestions.value = [];
round.value = 3;
// 调用Dify生成针对性题目
callDifyWorkflow(errorNums);
}
```
## 业务流程分析
### 1. 动态考试流程
```mermaid
graph TD
A[用户选择考试] --> B[获取考试ID]
B --> C[调用Dify工作流]
C --> D[生成第一轮题目]
D --> E[用户答题]
E --> F[记录错题]
F --> G{是否完成所有题目}
G -->|否| E
G -->|是| H[显示第一轮成绩]
H --> I{用户选择第二轮}
I -->|是| J[传递错题信息给Dify]
J --> K[生成第二轮针对性题目]
K --> L[用户答题]
L --> M[记录错题]
M --> N{是否完成所有题目}
N -->|否| L
N -->|是| O[显示第二轮成绩]
O --> P{用户选择第三轮}
P -->|是| Q[传递第二轮错题给Dify]
Q --> R[生成第三轮题目]
R --> S[完成三轮考试]
S --> T[保存最终成绩]
```
### 2. 知识提取流程
```mermaid
graph TD
A[管理员上传考试附件] --> B[点击知识提取按钮]
B --> C[获取附件URL]
C --> D[构建文件对象]
D --> E[调用Dify知识提取工作流]
E --> F[Dify处理PDF文档]
F --> G[提取知识点]
G --> H[返回提取结果]
H --> I[前端显示成功消息]
```
### 3. AI陪练流程
```mermaid
graph TD
A[用户进入陪练页面] --> B[iframe加载Coze聊天界面]
B --> C[用户发送语音/文本]
C --> D[Coze智能体处理]
D --> E[返回AI回复]
E --> F[支持语音合成]
F --> G[用户继续对话]
G --> C
```
## 技术架构图
### 1. 整体架构
```mermaid
graph TB
subgraph "前端层 (Vue3)"
A1[考试管理页面]
A2[动态考试页面]
A3[AI陪练页面]
end
subgraph "后端层 (FastAPI)"
B1[考试管理API]
B2[成绩管理API]
B3[文件管理API]
end
subgraph "AI平台层"
C1[Dify工作流]
C2[Coze智能体]
end
subgraph "数据层"
D1[MySQL数据库]
D2[文件存储]
end
A1 --> B1
A2 --> C1
A3 --> C2
A2 --> B2
B1 --> D1
B2 --> D1
B3 --> D2
C1 --> D2
```
### 2. Dify集成架构
```mermaid
graph LR
subgraph "前端"
A[Vue组件]
end
subgraph "Dify平台"
B[工作流引擎]
C[题目生成工作流]
D[知识提取工作流]
end
A -->|HTTP POST| B
B --> C
B --> D
C -->|题目数据| A
D -->|知识点数据| A
```
## 配置参数详解
### 1. 系统配置
#### 1.1 域名配置
- **主域名**: `https://aiedu.ireborn.com.cn`
- **API端点**: `/v1/workflows/run`
- **文件服务**: `/dev-api/profile/upload/`
#### 1.2 工作流配置
| 功能 | Token | Bot ID | 用途 |
|------|-------|--------|------|
| 题目生成 | app-tDlrmXyS9NtWCShsOx5FH49L | - | 根据考试ID和错题生成动态题目 |
| 知识提取 | app-LZhZcMO6CiriLMOLB2PwUGHx | - | 从PDF文档中提取知识点 |
| 高情商回复 | - | 7509380917472280617 | AI智能回复 |
| 咨询师陪练 | - | 7509379008556089379 | 语音陪练 |
| 动态考题 | - | 7509379046204162074 | 动态题目生成 |
### 2. 环境配置
#### 2.1 前端配置 (`vite.config.js`)
```javascript
server: {
host: '0.0.0.0',
port: 80,
proxy: {
'/dev-api': {
target: 'https://aiedu.ireborn.com.cn',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '')
}
}
}
```
#### 2.2 后端配置 (`config/env.py`)
```python
app_host: str = 'https://aiedu.ireborn.com.cn/'
```
## 数据流向分析
### 1. 题目生成数据流
```
用户操作 → Vue组件状态 → Dify API调用 → 工作流处理 → 题目数据返回 → 前端渲染
```
**详细流程:**
1. 用户点击开始考试
2. 获取URL参数中的`examId`
3. 调用`callDifyWorkflow(examId)`
4. 发送POST请求到Dify工作流
5. 工作流根据`examsId`生成题目
6. 返回JSON格式的题目数组
7. 前端解析数据并渲染题目界面
### 2. 错题重练数据流
```
错题收集 → 错题编号拼接 → Dify API调用(带error参数) → 针对性题目生成 → 前端渲染
```
**详细流程:**
1. 第一轮答题过程中收集错题
2. 将错题的`know_title`字段拼接成字符串
3. 调用`callDifyWorkflow(errorNums)`
4. Dify工作流根据错题信息生成针对性题目
5. 返回专门针对薄弱知识点的题目
6. 前端进入第二轮/第三轮答题模式
### 3. 知识提取数据流
```
文件上传 → 附件URL获取 → Dify API调用 → PDF解析 → 知识点提取 → 结果返回
```
**详细流程:**
1. 管理员在考试管理页面上传PDF附件
2. 系统生成文件访问URL
3. 点击"知识提取"按钮触发`update_know()`
4. 构建包含文件URL的请求参数
5. 调用Dify知识提取工作流
6. 工作流下载并解析PDF文档
7. 提取关键知识点并返回结果
## 错误处理机制
### 1. API调用错误处理
#### 1.1 网络错误处理
```javascript
try {
const res = await fetch(url, options);
const data = await res.json();
// 处理成功响应
} catch (err) {
console.error("Dify 工作流调用异常:", err);
loading.value = false;
return null;
}
```
#### 1.2 业务错误处理
```javascript
if (data.data.status != 'succeeded') {
throw new Error("请求失败");
}
```
#### 1.3 用户提示机制
```javascript
// 成功提示
proxy.$modal.msgSuccess("知识提取成功!");
// 警告提示
proxy.$modal.msgWarning("该考试没有附件,无法提取知识!");
// 错误提示
proxy.$modal.msgError("知识提取失败!");
// 加载提示
proxy.$modal.loading("正在提取知识,请稍候...");
proxy.$modal.closeLoading();
```
### 2. 数据验证机制
#### 2.1 前端验证
- 检查考试ID是否存在
- 验证附件列表是否为空
- 确认URL格式正确性
#### 2.2 响应数据验证
- 检查`data.data.status`是否为"succeeded"
- 验证`data.data.outputs.result`是否存在
- 确保题目数据结构完整
## 性能优化建议
### 1. 前端优化
#### 1.1 请求优化
- **缓存机制**: 对相同考试ID的题目进行本地缓存
- **请求去重**: 防止用户快速点击导致的重复请求
- **超时处理**: 设置合理的请求超时时间
#### 1.2 用户体验优化
- **加载状态**: 显示详细的加载进度和状态
- **错误重试**: 提供手动重试机制
- **离线支持**: 缓存已生成的题目支持离线答题
### 2. 后端优化
#### 2.1 API性能
- **连接池**: 使用HTTP连接池减少连接开销
- **异步处理**: 对于知识提取等耗时操作使用异步处理
- **结果缓存**: 缓存Dify工作流的执行结果
#### 2.2 监控告警
- **API监控**: 监控Dify API的响应时间和成功率
- **错误日志**: 记录详细的错误日志便于问题排查
- **性能指标**: 统计题目生成时间和知识提取效率
### 3. Dify工作流优化
#### 3.1 工作流设计
- **参数验证**: 在工作流中添加输入参数验证
- **错误处理**: 完善工作流内部的错误处理逻辑
- **性能调优**: 优化工作流的执行效率
#### 3.2 资源管理
- **并发控制**: 控制同时执行的工作流数量
- **资源限制**: 设置合理的内存和CPU使用限制
- **成本优化**: 监控和优化AI模型的调用成本
## 总结
本考培练系统通过与Dify平台的深度对接实现了智能化的题目生成和知识提取功能。系统采用了成熟的技术架构具备良好的扩展性和可维护性。主要特点包括
1. **智能题目生成**: 基于考试内容和学员错题情况动态生成个性化题目
2. **三轮考试机制**: 通过多轮练习帮助学员巩固薄弱知识点
3. **知识自动提取**: 从PDF文档中自动提取关键知识点
4. **AI智能陪练**: 结合Coze平台提供语音陪练功能
系统在实现上注重用户体验和错误处理,具备较强的实用性和稳定性。建议在后续开发中进一步优化性能和扩展功能模块。
---
**文档版本**: v1.0
**生成时间**: 2025年9月20日
**分析范围**: ExamsSystem、coze-chat-backend、coze-chat-frontend模块

View File

@@ -0,0 +1,69 @@
# Dify工作流集成文档
**版本:** v2.0
**状态:** ✅ 已完成并验证
**最后更新:** 2025-10-12
---
## 文件说明
### 核心文档
| 文件 | 说明 |
|------|------|
| `考试工作流-最终版.md` | 考试工作流完整实现(推荐阅读) |
| `知识拆解工作流.md` | 知识点分析工作流配置 |
| `试题生成器的核心提示词与输出示例.md` | Dify提示词和返回格式参考 |
| `考试工作流联调文档.md` | 详细联调文档(备查) |
| `考试工作流联调-原版.md` | 原始需求(禁止修改) |
### 数据库API服务
| 文件 | 说明 |
|------|------|
| `数据库api 服务/README.md` | 快速配置指南 |
| `数据库api 服务/openapi_sql_executor.json` | OpenAPI Schema文件 |
---
## 快速开始
### 1. 考试工作流
阅读:`考试工作流-最终版.md`
**关键信息:**
- 试题生成器Token`app-tDlrmXyS9NtWCShsOx5FH49L`
- 答案判断器Token`app-FvMdrvbRBz547DVZEorgO1WT`
- 后端接口:`/api/v1/exams/*`
- 测试页面:`http://localhost:3001/trainee/exam?courseId=1`
### 2. 知识拆解工作流
阅读:`知识拆解工作流.md`
**关键信息:**
- API Key`app-LZhZcMO6CiriLMOLB2PwUGHx`
- 响应模式streaming无需轮询
- 状态映射succeeded→分析完成failed→分析失败
### 3. 数据库API服务
阅读:`数据库api 服务/README.md`
**关键信息:**
- API Key`dify-2025-kaopeilian`
- 端点:`/sql/execute-simple`
- 服务器:`http://120.79.247.16:8000/api/v1`
---
## 验证状态
- ✅ 考试工作流:三轮考试流程完整可用
- ✅ 知识拆解工作流:单个/批量分析正常
- ✅ 数据库API服务SQL执行器正常工作
---
**文档维护:** 开发团队
**技术支持:** 参考 `考培练系统规划/全链路联调/联调经验汇总.md`

View File

@@ -0,0 +1,500 @@
# Dify 对话流 API 官方文档
> 文档来源https://dify.ireborn.com.cn/app/4bea851a-7f24-47bd-9d0b-1d74f69ba603/develop
> 导出时间2025-10-14
## 工作流编排对话型应用 API
对话应用支持会话持久化,可将之前的聊天记录作为上下文进行回答,可适用于聊天/客服 AI 等。
### 基础 URL
```
http://dify.ireborn.com.cn/v1
```
### 鉴权
Service API 使用 `API-Key` 进行鉴权。
**强烈建议开发者把 `API-Key` 放在后端存储,而非分享或者放在客户端存储,以免 `API-Key` 泄露,导致财产损失。**
所有 API 请求都应在 `Authorization` HTTP Header 中包含您的 `API-Key`,如下所示:
```
Authorization: Bearer {API_KEY}
```
---
## POST /chat-messages - 发送对话消息
创建会话消息。
### Request Body
| Name | Type | Description |
|------|------|-------------|
| `query` | string | 用户输入/提问内容。 |
| `inputs` | object | 允许传入 App 定义的各变量值。inputs 参数包含了多组键值对Key/Value pairs每组的键对应一个特定变量每组的值则是该变量的具体值。如果变量是文件类型请指定一个包含以下 `files` 中所述键的对象。默认 `{}` |
| `response_mode` | string | `streaming` 流式模式(推荐)。基于 SSE[Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events))实现类似打字机输出方式的流式返回。<br>`blocking` 阻塞模式,等待执行完毕后返回结果。(请求若流程较长可能会被中断)。由于 Cloudflare 限制,请求会在 100 秒超时无返回后中断。 |
| `user` | string | 用户标识,用于定义终端用户的身份,方便检索、统计。由开发者定义规则,需保证用户标识在应用内唯一。服务 API 不会共享 WebApp 创建的对话。 |
| `conversation_id` | string | (选填)会话 ID需要基于之前的聊天记录继续对话必须传之前消息的 conversation_id。 |
| `files` | array[object] | 文件列表,适用于传入文件结合文本理解并回答问题,仅当模型支持 Vision/Video 能力时可用。<br><br>**文件类型:**<br>- `document`: TXT, MD, MARKDOWN, MDX, PDF, HTML, XLSX, XLS, VTT, PROPERTIES, DOC, DOCX, CSV, EML, MSG, PPTX, PPT, XML, EPUB<br>- `image`: JPG, JPEG, PNG, GIF, WEBP, SVG<br>- `audio`: MP3, M4A, WAV, WEBM, MPGA<br>- `video`: MP4, MOV, MPEG, WEBM<br>- `custom`: 其他文件类型<br><br>**transfer_method (string)** 传递方式:<br>- `remote_url`: 文件地址<br>- `local_file`: 上传文件<br><br>- `url`: 文件地址(仅当传递方式为 remote_url 时)<br>- `upload_file_id`: 上传文件 ID仅当传递方式为 local_file 时) |
| `auto_generate_name` | bool | (选填)自动生成标题,默认 true。若设置为 false则可通过调用会话重命名接口并设置 auto_generate 为 true 实现异步生成标题。 |
| `workflow_id` | string | 选填工作流ID用于指定特定版本如果不提供则使用默认的已发布版本。 |
| `trace_id` | string | 选填链路追踪ID。适用于与业务系统已有的trace组件打通实现端到端分布式追踪等场景。如果未指定系统会自动生成trace_id。支持以下三种方式传递具体优先级依次为<br>1. Header通过 HTTP Header X-Trace-Id 传递,优先级最高。<br>2. Query 参数:通过 URL 查询参数 trace_id 传递。<br>3. Request Body通过请求体字段 trace_id 传递(即本字段)。 |
### Response
`response_mode``blocking` 时,返回 **ChatCompletionResponse** object。
`response_mode``streaming` 时,返回 **ChunkChatCompletionResponse** object 流式序列。
#### ChatCompletionResponse阻塞模式
返回完整的 App 结果Content-Type 为 `application/json`
| 字段 | 类型 | 描述 |
|------|------|------|
| `event` | string | 事件类型,固定为 `message` |
| `task_id` | string | 任务 ID用于请求跟踪和下方的停止响应接口 |
| `id` | string | 唯一ID |
| `message_id` | string | 消息唯一 ID |
| `conversation_id` | string | 会话 ID |
| `mode` | string | App 模式,固定为 `chat` |
| `answer` | string | 完整回复内容 |
| `metadata` | object | 元数据 |
| `usage` | Usage | 模型用量信息 |
| `retriever_resources` | array[RetrieverResource] | 引用和归属分段列表 |
| `created_at` | int | 消息创建时间戳1705395332 |
#### ChunkChatCompletionResponse流式模式
返回 App 输出的流式块Content-Type 为 `text/event-stream`
每个流式块均为 `data:` 开头,块之间以 `\n\n` 即两个换行符分隔,如下所示:
```
data: {"event": "message", "task_id": "900bbd43-dc0b-4383-a372-aa6e6c414227", "id": "663c5084-a254-4040-8ad3-51f2a3c1a77c", "answer": "Hi", "created_at": 1705398420}\n\n
```
**流式块中根据 `event` 不同,结构也不同:**
##### event: workflow_started
workflow 开始执行
| 字段 | 类型 | 描述 |
|------|------|------|
| `task_id` | string | 任务 ID用于请求跟踪和下方的停止响应接口 |
| `workflow_run_id` | string | workflow 执行 ID |
| `event` | string | 固定为 `workflow_started` |
| `data` | object | 详细内容 |
| `data.id` | string | workflow 执行 ID |
| `data.workflow_id` | string | 关联 Workflow ID |
| `data.created_at` | timestamp | 开始时间 |
##### event: node_started
node 开始执行
| 字段 | 类型 | 描述 |
|------|------|------|
| `task_id` | string | 任务 ID用于请求跟踪和下方的停止响应接口 |
| `workflow_run_id` | string | workflow 执行 ID |
| `event` | string | 固定为 `node_started` |
| `data` | object | 详细内容 |
| `data.id` | string | workflow 执行 ID |
| `data.node_id` | string | 节点 ID |
| `data.node_type` | string | 节点类型 |
| `data.title` | string | 节点名称 |
| `data.index` | int | 执行序号,用于展示 Tracing Node 顺序 |
| `data.predecessor_node_id` | string | 前置节点 ID用于画布展示执行路径 |
| `data.inputs` | object | 节点中所有使用到的前置节点变量内容 |
| `data.created_at` | timestamp | 开始时间 |
##### event: node_finished
node 执行结束,成功失败同一事件中不同状态
| 字段 | 类型 | 描述 |
|------|------|------|
| `task_id` | string | 任务 ID用于请求跟踪和下方的停止响应接口 |
| `workflow_run_id` | string | workflow 执行 ID |
| `event` | string | 固定为 `node_finished` |
| `data` | object | 详细内容 |
| `data.id` | string | node 执行 ID |
| `data.node_id` | string | 节点 ID |
| `data.index` | int | 执行序号,用于展示 Tracing Node 顺序 |
| `data.predecessor_node_id` | string | optional 前置节点 ID用于画布展示执行路径 |
| `data.inputs` | object | 节点中所有使用到的前置节点变量内容 |
| `data.process_data` | json | Optional 节点过程数据 |
| `data.outputs` | json | Optional 输出内容 |
| `data.status` | string | 执行状态 running / succeeded / failed / stopped |
| `data.error` | string | Optional 错误原因 |
| `data.elapsed_time` | float | Optional 耗时(s) |
| `data.execution_metadata` | json | 元数据 |
| `data.execution_metadata.total_tokens` | int | optional 总使用 tokens |
| `data.execution_metadata.total_price` | decimal | optional 总费用 |
| `data.execution_metadata.currency` | string | optional 货币,如 USD / RMB |
| `data.created_at` | timestamp | 开始时间 |
##### event: workflow_finished
workflow 执行结束,成功失败同一事件中不同状态
| 字段 | 类型 | 描述 |
|------|------|------|
| `task_id` | string | 任务 ID用于请求跟踪和下方的停止响应接口 |
| `workflow_run_id` | string | workflow 执行 ID |
| `event` | string | 固定为 `workflow_finished` |
| `data` | object | 详细内容 |
| `data.id` | string | workflow 执行 ID |
| `data.workflow_id` | string | 关联 Workflow ID |
| `data.status` | string | 执行状态 running / succeeded / failed / stopped |
| `data.outputs` | json | Optional 输出内容 |
| `data.error` | string | Optional 错误原因 |
| `data.elapsed_time` | float | Optional 耗时(s) |
| `data.total_tokens` | int | Optional 总使用 tokens |
| `data.total_steps` | int | 总步数(冗余),默认 0 |
| `data.created_at` | timestamp | 开始时间 |
| `data.finished_at` | timestamp | 结束时间 |
##### event: message
LLM 返回文本块事件,即:完整的文本以分块的方式输出。
| 字段 | 类型 | 描述 |
|------|------|------|
| `task_id` | string | 任务 ID用于请求跟踪和下方的停止响应接口 |
| `message_id` | string | 消息唯一 ID |
| `conversation_id` | string | 会话 ID |
| `answer` | string | LLM 返回文本块内容 |
| `created_at` | int | 创建时间戳1705395332 |
##### event: message_file
文件事件,表示有新文件需要展示
| 字段 | 类型 | 描述 |
|------|------|------|
| `id` | string | 文件唯一ID |
| `type` | string | 文件类型目前仅为image |
| `belongs_to` | string | 文件归属user或assistant该接口返回仅为 assistant |
| `url` | string | 文件访问地址 |
| `conversation_id` | string | 会话ID |
##### event: message_end
消息结束事件,收到此事件则代表流式返回结束。
| 字段 | 类型 | 描述 |
|------|------|------|
| `task_id` | string | 任务 ID用于请求跟踪和下方的停止响应接口 |
| `message_id` | string | 消息唯一 ID |
| `conversation_id` | string | 会话 ID |
| `metadata` | object | 元数据 |
| `usage` | Usage | 模型用量信息 |
| `retriever_resources` | array[RetrieverResource] | 引用和归属分段列表 |
##### event: tts_message
TTS 音频流事件语音合成输出。内容是Mp3格式的音频块使用 base64 编码后的字符串,播放的时候直接解码即可。(开启自动播放才有此消息)
| 字段 | 类型 | 描述 |
|------|------|------|
| `task_id` | string | 任务 ID用于请求跟踪和下方的停止响应接口 |
| `message_id` | string | 消息唯一 ID |
| `audio` | string | 语音合成之后的音频块使用 Base64 编码之后的文本内容,播放的时候直接 base64 解码送入播放器即可 |
| `created_at` | int | 创建时间戳1705395332 |
##### event: tts_message_end
TTS 音频流结束事件,收到这个事件表示音频流返回结束。
| 字段 | 类型 | 描述 |
|------|------|------|
| `task_id` | string | 任务 ID用于请求跟踪和下方的停止响应接口 |
| `message_id` | string | 消息唯一 ID |
| `audio` | string | 结束事件是没有音频的,所以这里是空字符串 |
| `created_at` | int | 创建时间戳1705395332 |
##### event: message_replace
消息内容替换事件。开启内容审查和审查输出内容时,若命中了审查条件,则会通过此事件替换消息内容为预设回复。
| 字段 | 类型 | 描述 |
|------|------|------|
| `task_id` | string | 任务 ID用于请求跟踪和下方的停止响应接口 |
| `message_id` | string | 消息唯一 ID |
| `conversation_id` | string | 会话 ID |
| `answer` | string | 替换内容(直接替换 LLM 所有回复文本) |
| `created_at` | int | 创建时间戳1705395332 |
##### event: error
流式输出过程中出现的异常会以 stream event 形式输出,收到异常事件后即结束。
| 字段 | 类型 | 描述 |
|------|------|------|
| `task_id` | string | 任务 ID用于请求跟踪和下方的停止响应接口 |
| `message_id` | string | 消息唯一 ID |
| `status` | int | HTTP 状态码 |
| `code` | string | 错误码 |
| `message` | string | 错误消息 |
##### event: ping
每 10s 一次的 ping 事件,保持连接存活。
### Errors
- `404` - 对话不存在
- `400, invalid_param` - 传入参数异常
- `400, app_unavailable` - App 配置不可用
- `400, provider_not_initialize` - 无可用模型凭据配置
- `400, provider_quota_exceeded` - 模型调用额度不足
- `400, model_currently_not_support` - 当前模型不可用
- `400, workflow_not_found` - 指定的工作流版本未找到
- `400, draft_workflow_error` - 无法使用草稿工作流版本
- `400, workflow_id_format_error` - 工作流ID格式错误需要UUID格式
- `400, completion_request_error` - 文本生成失败
- `500` - 服务内部异常
### Request 示例
```bash
curl -X POST 'http://dify.ireborn.com.cn/v1/chat-messages' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{
"inputs": {},
"query": "What are the specs of the iPhone 13 Pro Max?",
"response_mode": "streaming",
"conversation_id": "",
"user": "abc-123",
"files": [
{
"type": "image",
"transfer_method": "remote_url",
"url": "https://cloud.dify.ai/logo/logo-site.png"
}
]
}'
```
### Response 示例
#### 阻塞模式
```json
{
"event": "message",
"task_id": "c3800678-a077-43df-a102-53f23ed20b88",
"id": "9da23599-e713-473b-982c-4328d4f5c78a",
"message_id": "9da23599-e713-473b-982c-4328d4f5c78a",
"conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2",
"mode": "chat",
"answer": "iPhone 13 Pro Max specs are listed here:...",
"metadata": {
"usage": {
"prompt_tokens": 1033,
"prompt_unit_price": "0.001",
"prompt_price_unit": "0.001",
"prompt_price": "0.0010330",
"completion_tokens": 128,
"completion_unit_price": "0.002",
"completion_price_unit": "0.001",
"completion_price": "0.0002560",
"total_tokens": 1161,
"total_price": "0.0012890",
"currency": "USD",
"latency": 0.7682376249867957
},
"retriever_resources": [
{
"position": 1,
"dataset_id": "101b4c97-fc2e-463c-90b1-5261a4cdcafb",
"dataset_name": "iPhone",
"document_id": "8dd1ad74-0b5f-4175-b735-7d98bbbb4e00",
"document_name": "iPhone List",
"segment_id": "ed599c7f-2766-4294-9d1d-e5235a61270a",
"score": 0.98457545,
"content": "\"Model\",\"Release Date\",\"Display Size\",\"Resolution\",\"Processor\",\"RAM\",\"Storage\",\"Camera\",\"Battery\",\"Operating System\"\n\"iPhone 13 Pro Max\",\"September 24, 2021\",\"6.7 inch\",\"1284 x 2778\",\"Hexa-core (2x3.23 GHz Avalanche + 4x1.82 GHz Blizzard)\",\"6 GB\",\"128, 256, 512 GB, 1TB\",\"12 MP\",\"4352 mAh\",\"iOS 15\""
}
]
},
"created_at": 1705407629
}
```
#### 流式模式
```
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}}
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}
data: {"event": "message", "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": " I", "created_at": 1679586595}
data: {"event": "message", "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": "'m", "created_at": 1679586595}
data: {"event": "message", "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": " glad", "created_at": 1679586595}
data: {"event": "message", "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": " to", "created_at": 1679586595}
data: {"event": "message", "message_id" : "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": " meet", "created_at": 1679586595}
data: {"event": "message", "message_id" : "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": " you", "created_at": 1679586595}
data: {"event": "message_end", "id": "5e52ce04-874b-4d27-9045-b3bc80def685", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "metadata": {"usage": {"prompt_tokens": 1033, "prompt_unit_price": "0.001", "prompt_price_unit": "0.001", "prompt_price": "0.0010330", "completion_tokens": 135, "completion_unit_price": "0.002", "completion_price_unit": "0.001", "completion_price": "0.0002700", "total_tokens": 1168, "total_price": "0.0013030", "currency": "USD", "latency": 1.381760165997548}, "retriever_resources": [{"position": 1, "dataset_id": "101b4c97-fc2e-463c-90b1-5261a4cdcafb", "dataset_name": "iPhone", "document_id": "8dd1ad74-0b5f-4175-b735-7d98bbbb4e00", "document_name": "iPhone List", "segment_id": "ed599c7f-2766-4294-9d1d-e5235a61270a", "score": 0.98457545, "content": "\"Model\",\"Release Date\",\"Display Size\",\"Resolution\",\"Processor\",\"RAM\",\"Storage\",\"Camera\",\"Battery\",\"Operating System\"\n\"iPhone 13 Pro Max\",\"September 24, 2021\",\"6.7 inch\",\"1284 x 2778\",\"Hexa-core (2x3.23 GHz Avalanche + 4x1.82 GHz Blizzard)\",\"6 GB\",\"128, 256, 512 GB, 1TB\",\"12 MP\",\"4352 mAh\",\"iOS 15\""}]}}
data: {"event": "tts_message", "conversation_id": "23dd85f3-1a41-4ea0-b7a9-062734ccfaf9", "message_id": "a8bdc41c-13b2-4c18-bfd9-054b9803038c", "created_at": 1721205487, "task_id": "3bf8a0bb-e73b-4690-9e66-4e429bad8ee7", "audio": "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"}
data: {"event": "tts_message_end", "conversation_id": "23dd85f3-1a41-4ea0-b7a9-062734ccfaf9", "message_id": "a8bdc41c-13b2-4c18-bfd9-054b9803038c", "created_at": 1721205487, "task_id": "3bf8a0bb-e73b-4690-9e66-4e429bad8ee7", "audio": ""}
```
---
## POST /files/upload - 上传文件
上传文件并在发送消息时使用,可实现图文多模态理解。支持您的应用程序所支持的所有格式。上传的文件仅供当前终端用户使用。
该接口需使用 `multipart/form-data` 进行请求。
### Request Body
| Name | Type | Description |
|------|------|-------------|
| `file` | file | 要上传的文件。 |
| `user` | string | 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。 |
### Response
成功上传后,服务器会返回文件的 ID 和相关信息。
| 字段 | 类型 | 描述 |
|------|------|------|
| `id` | uuid | ID |
| `name` | string | 文件名 |
| `size` | int | 文件大小byte |
| `extension` | string | 文件后缀 |
| `mime_type` | string | 文件 mime-type |
| `created_by` | uuid | 上传人 ID |
| `created_at` | timestamp | 上传时间 |
### Errors
- `400, no_file_uploaded` - 必须提供文件
- `400, too_many_files` - 目前只接受一个文件
- `400, unsupported_preview` - 该文件不支持预览
- `400, unsupported_estimate` - 该文件不支持估算
- `413, file_too_large` - 文件太大
- `415, unsupported_file_type` - 不支持的扩展名,当前只接受文档类文件
- `503, s3_connection_failed` - 无法连接到 S3 服务
- `503, s3_permission_denied` - 无权限上传文件到 S3
- `503, s3_file_too_large` - 文件超出 S3 大小限制
### Request 示例
```bash
curl -X POST 'http://dify.ireborn.com.cn/v1/files/upload' \
--header 'Authorization: Bearer {api_key}' \
--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif]' \
--form 'user=abc-123'
```
### Response 示例
```json
{
"id": "72fa9618-8f89-4a37-9b33-7e1178a24a67",
"name": "example.png",
"size": 1024,
"extension": "png",
"mime_type": "image/png",
"created_by": 123,
"created_at": 1577836800
}
```
---
## POST /chat-messages/:task_id/stop - 停止响应
仅支持流式模式。
### Path
| 参数 | 类型 | 描述 |
|------|------|------|
| `task_id` | string | 任务 ID可在流式返回 Chunk 中获取 |
### Request Body
| Name | Type | Description |
|------|------|-------------|
| `user` | string | Required 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。API 无法访问 WebApp 创建的会话。 |
### Response
| 字段 | 类型 | 描述 |
|------|------|------|
| `result` | string | 固定返回 success |
### Request 示例
```bash
curl -X POST 'http://dify.ireborn.com.cn/v1/chat-messages/:task_id/stop' \
-H 'Authorization: Bearer {api_key}' \
-H 'Content-Type: application/json' \
--data-raw '{
"user": "abc-123"
}'
```
### Response 示例
```json
{
"result": "success"
}
```
---
## 其他接口
文档还包含以下接口(此处仅列出标题,详细内容请查看原始文档):
- POST /messages/:message_id/feedbacks - 消息反馈(点赞)
- GET /app/feedbacks - 获取APP的消息点赞和反馈
- GET /messages/{message_id}/suggested - 获取下一轮建议问题列表
- GET /messages - 获取会话历史消息
- GET /conversations - 获取会话列表
- DELETE /conversations/:conversation_id - 删除会话
- POST /conversations/:conversation_id/name - 会话重命名
- GET /conversations/:conversation_id/variables - 获取对话变量
- PUT /conversations/:conversation_id/variables/:variable_id - 更新对话变量
- POST /audio-to-text - 语音转文字
- POST /text-to-audio - 文字转语音
- GET /info - 获取应用基本信息
- GET /parameters - 获取应用参数
- GET /meta - 获取应用Meta信息
- GET /site - 获取应用 WebApp 设置
- GET /apps/annotations - 获取标注列表
- POST /apps/annotations - 创建标注
- PUT /apps/annotations/{annotation_id} - 更新标注
- DELETE /apps/annotations/{annotation_id} - 删除标注
- POST /apps/annotation-reply/{action} - 标注回复初始设置
- GET /apps/annotation-reply/{action}/status/{job_id} - 查询标注回复初始设置任务状态
---
## 关键说明
### 流式模式事件流程
对于工作流编排的对话应用,典型的事件流程如下:
1. `workflow_started` - 工作流开始(包含 conversation_id
2. `node_started` - 节点开始执行
3. `node_finished` - 节点执行完成(可能包含输出数据)
4. `workflow_finished` - 工作流完成(包含最终输出)
5. `message` - LLM 文本块(逐字返回,可能有多个)
6. `message_end` - 消息结束
### conversation_id 管理
- **首次对话**:不传 `conversation_id`,系统会在 `workflow_started` 事件中返回新的 `conversation_id`
- **续接对话**:传入之前获取的 `conversation_id`,保持上下文连续性
### 重要注意事项
1. API Key 必须放在后端,不要暴露在客户端
2. 流式模式使用 SSEServer-Sent Events协议
3. 每个事件块以 `data:` 开头,块之间用 `\n\n` 分隔
4. Cloudflare 有 100 秒超时限制(阻塞模式)
5. 流式模式每 10 秒发送一次 ping 事件保持连接

View File

@@ -0,0 +1,283 @@
# 与课程对话功能实施总结
> 完成时间2025-10-14
> 功能状态:✅ 已完成实施
## 📋 功能概述
基于 Dify 对话流实现了与课程的智能对话功能,用户可以在课程中心点击"对话"按钮,与课程内容进行智能问答互动。
## 🎯 技术方案
### 架构设计
```
前端 chat-course.vue
↓ (SSE)
后端 /api/v1/course/chat
↓ (HTTP Stream)
Dify 对话流 API
```
### 核心特性
1. **流式响应**:使用 SSEServer-Sent Events实现实时对话
2. **会话管理**conversation_id 由前端管理,支持多轮对话
3. **无持久化**:对话历史由 Dify 托管,系统不存储
4. **纯文本**:当前版本仅支持文本对话
## 📁 已修改文件
### 后端
1. **配置文件**`kaopeilian-backend/app/core/config.py`
- 添加 `DIFY_COURSE_CHAT_API_KEY = "app-lJzD6COkL8z7Eez8t6ZrYoJS"`
2. **API 接口**`kaopeilian-backend/app/api/v1/course_chat.py`(新建)
- `POST /api/v1/course/chat` - 与课程对话接口
- 实现 SSE 流式代理
- 事件转换Dify → 前端友好格式
3. **路由注册**`kaopeilian-backend/app/api/v1/__init__.py`
- 注册 `course_chat_router``/course` 前缀
### 前端
1. **API 封装**`kaopeilian-frontend/src/api/courseChat.ts`(新建)
- `courseChatApi.sendMessage()` - 发送消息并返回 ReadableStream
- TypeScript 类型定义:`CourseChatEvent`
2. **对话页面**`kaopeilian-frontend/src/views/trainee/chat-course.vue`
- 删除 Coze 集成代码
- 改用 Dify 对话流
- 前端管理 `conversationId`
- SSE 事件处理conversation_started / message_content / message_end
### 测试
1. **测试脚本**`test_course_chat.py`(新建)
- 测试登录 → 首次对话 → 续接对话
- 验证 SSE 事件流
- 验证会话管理
## 🔄 SSE 事件流程
### Dify 原始事件
```
workflow_started → node_finished → workflow_finished → message_end
```
### 后端转换后的事件
```json
// 1. 会话开始(首次对话)
{"event": "conversation_started", "conversation_id": "xxx"}
// 2. 消息块(逐字返回,实现打字机效果)
{"event": "message_chunk", "chunk": "这"}
{"event": "message_chunk", "chunk": "门"}
{"event": "message_chunk", "chunk": "课"}
...
// 3. 消息结束
{"event": "message_end"}
// 4. 错误(如有)
{"event": "error", "message": "错误信息"}
```
## 📊 数据流
### 首次对话
```
用户输入问题
前端调用 courseChatApi.sendMessage({course_id, query})
后端转发到 Dify (无 conversation_id)
Dify 创建新会话
SSE: conversation_started → 前端保存 conversation_id
SSE: message_content → 前端显示答案
SSE: message_end → 对话完成
```
### 续接对话
```
用户输入问题
前端调用 courseChatApi.sendMessage({course_id, query, conversation_id})
后端转发到 Dify (带 conversation_id)
Dify 基于上下文回答
SSE: message_content → 前端显示答案
SSE: message_end → 对话完成
```
## 🧪 测试步骤
### 1. 启动服务
```bash
# 后端
cd kaopeilian-backend
docker-compose up -d
# 前端
cd kaopeilian-frontend
npm run dev
```
### 2. 运行测试脚本
```bash
python test_course_chat.py
```
### 3. 手动测试
1. 登录系统
2. 进入课程中心http://localhost:3001/trainee/course-center
3. 点击课程卡片的"对话"按钮
4. 输入问题并发送
5. 验证:
- AI 回复显示正常
- 可以进行多轮对话
- 点击"清空对话"后会话重置
## 🎨 UI 特性
- ✅ 欢迎界面(首次进入时显示)
- ✅ 快速提问(预设问题点击发送)
- ✅ 消息加载动画(三个点跳动)
- ✅ 消息复制功能
- ✅ 消息收藏功能
- ✅ 侧边栏(知识要点、对话历史)
- ✅ 响应式设计(移动端适配)
## ⚠️ 注意事项
### 1. conversation_id 管理
- 前端使用 `ref<string>` 保存
- 页面刷新后丢失(符合"每次进入对话页面都创建新会话"的需求)
- 点击"清空对话"时重置
### 2. 流式打字机效果
- ✅ 已实现Dify streaming 模式支持 `event: message` 逐字返回
- 前端通过 `message_chunk` 事件逐字追加文本
- 实现类似 ChatGPT 的实时打字效果
### 3. 超时设置
- 后端180 秒httpx.Timeout
- 前端:依赖浏览器默认(通常无限制)
### 4. 错误处理
- 网络错误:显示友好提示
- Dify API 错误:记录日志并返回错误事件
- 解析错误:跳过当前行,继续处理
## 📝 API 接口文档
### POST /api/v1/course/chat
**请求体:**
```json
{
"course_id": 1,
"query": "这门课程讲什么?",
"conversation_id": "可选,续接对话时传入"
}
```
**响应SSE**
```
data: {"event":"conversation_started","conversation_id":"xxx"}
data: {"event":"message_content","answer":"这门课程..."}
data: {"event":"message_end"}
```
## 🔍 关键代码片段
### 后端 SSE 生成
```python
async def generate_stream():
async with client.stream("POST", url, headers=headers, json=payload) as response:
async for line in response.aiter_lines():
if line.startswith("data: "):
event_data = json.loads(line[6:])
# 处理事件...
yield f"data: {json.dumps(frontend_event)}\n\n"
```
### 前端 SSE 消费
```typescript
const stream = await courseChatApi.sendMessage({...})
const reader = stream.getReader()
const decoder = new TextDecoder()
while (true) {
const { done, value } = await reader.read()
if (done) break
const text = decoder.decode(value)
// 解析 SSE 事件...
}
```
## ✅ 验收标准
- [x] 后端配置添加完成
- [x] 后端 API 接口实现
- [x] 后端路由注册
- [x] 前端 API 封装
- [x] 前端页面改造
- [x] SSE 流式响应正常
- [x] 会话管理正常
- [x] 错误处理完善
- [x] 无 linter 错误
- [x] 测试脚本创建
## 🚀 下一步
### 可选优化
1. **Markdown 渲染**:如果 Dify 返回 Markdown 格式,前端可添加 Markdown 渲染器
2. **会话持久化**:如需要持久化历史,可在后端存储 conversation_id 与用户/课程的映射
3. **实时打字效果**:如 Dify 支持逐字返回,可修改事件处理逻辑
4. **语音对话**:未来可集成语音输入/输出
## 📚 相关文档
- [Dify 对话流 API 文档](./Dify对话流API文档.md)
- [实施计划](../../../------dify---.plan.md)
- [规范与约定-团队基线](../../规范与约定-团队基线.md)
- [联调经验汇总](../../联调经验汇总.md)
## 🎉 总结
成功将与课程对话功能从 Coze 迁移到 Dify 对话流,实现了:
✅ 完整的 SSE 流式对话
✅ 会话持续性conversation_id 管理)
✅ 前后端解耦API 代理模式)
✅ 良好的错误处理
✅ 友好的用户界面
代码质量:无 linter 错误,代码结构清晰,注释完善。

View File

@@ -0,0 +1,76 @@
# 数据库API服务配置指南
**用途:** Dify工作流访问考培练系统数据库
---
## 快速配置
### 服务器信息
- **地址:** http://120.79.247.16:8000/api/v1
- **备用:** http://aiedu.ireborn.com.cn/api/v1
### Dify配置步骤
1. **导入OpenAPI Schema**
- 文件:`openapi_sql_executor.json`
- 位置:工具 → 导入OpenAPI
2. **配置认证**
- 鉴权类型:请求头
- 头部前缀Custom
- 键:`X-API-Key`
- 值:`dify-2025-kaopeilian`
3. **选择端点**
- `/sql/execute-simple`
---
## 常用SQL语句
### 查询知识点
```sql
SELECT kp.id, kp.name, kp.description, kp.topic_relation
FROM knowledge_points kp
INNER JOIN course_materials cm ON kp.material_id = cm.id
WHERE kp.course_id = ? AND kp.is_deleted = FALSE AND cm.is_deleted = FALSE
ORDER BY RAND() LIMIT 10
```
### 查询岗位信息
```sql
SELECT id, name, description, skills, level
FROM positions
WHERE id = ? AND is_deleted = FALSE
```
---
## 测试验证
```bash
curl -X POST http://120.79.247.16:8000/api/v1/sql/execute-simple \
-H "X-API-Key: dify-2025-kaopeilian" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT COUNT(*) as total FROM users"}'
```
**预期结果:**
```json
{
"code": 200,
"message": "SQL 执行成功",
"data": {
"type": "query",
"columns": ["total"],
"rows": [{"total": 8}],
"row_count": 1
}
}
```
---
**最后更新:** 2025-10-12

View File

@@ -0,0 +1,664 @@
{
"openapi": "3.1.0",
"info": {
"title": "KaoPeiLian SQL Executor API",
"description": "SQL 执行器 API专门为 Dify 平台集成设计,支持对考陪练系统数据库执行查询和写入操作。\n\n## 主要功能\n- 执行 SQL 查询和写入操作\n- 支持参数化查询防止 SQL 注入\n- 获取数据库表列表和表结构\n- SQL 语句验证\n\n## 安全说明\n所有接口都需要 JWT Bearer Token 认证。请先通过登录接口获取访问令牌。",
"version": "1.0.0",
"contact": {
"name": "KaoPeiLian Tech Support",
"email": "support@kaopeilian.com"
}
},
"servers": [
{
"url": "http://120.79.247.16:8000/api/v1",
"description": "考陪练系统服务器"
},
{
"url": "http://aiedu.ireborn.com.cn/api/v1",
"description": "域名访问"
}
],
"security": [
{
"bearerAuth": []
}
],
"paths": {
"/auth/login": {
"post": {
"tags": ["认证"],
"summary": "用户登录",
"description": "获取访问令牌,用于后续 API 调用",
"security": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoginRequest"
},
"examples": {
"admin": {
"summary": "管理员登录",
"value": {
"username": "admin",
"password": "admin123"
}
}
}
}
}
},
"responses": {
"200": {
"description": "登录成功",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoginResponse"
}
}
}
},
"401": {
"description": "用户名或密码错误",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/sql/execute": {
"post": {
"tags": ["SQL执行器"],
"summary": "执行 SQL 语句",
"description": "执行查询或写入 SQL 语句。\n\n**查询操作**: SELECT, SHOW, DESCRIBE\n**写入操作**: INSERT, UPDATE, DELETE, CREATE, ALTER, DROP\n\n支持参数化查询使用 `:param_name` 格式定义参数。",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SqlExecuteRequest"
},
"examples": {
"simpleQuery": {
"summary": "简单查询",
"value": {
"sql": "SELECT id, username, role FROM users LIMIT 5"
}
},
"parameterizedQuery": {
"summary": "参数化查询",
"value": {
"sql": "SELECT * FROM courses WHERE category = :category AND status = :status",
"params": {
"category": "护肤",
"status": "active"
}
}
},
"insertData": {
"summary": "插入数据",
"value": {
"sql": "INSERT INTO knowledge_points (title, content, course_id) VALUES (:title, :content, :course_id)",
"params": {
"title": "面部护理基础",
"content": "面部护理的基本步骤...",
"course_id": 1
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "SQL 执行成功",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/QueryResponse"
},
{
"$ref": "#/components/schemas/ExecuteResponse"
}
]
},
"examples": {
"queryResult": {
"summary": "查询结果",
"value": {
"code": 200,
"message": "SQL 执行成功",
"data": {
"type": "query",
"columns": ["id", "username", "role"],
"rows": [
{
"id": 1,
"username": "admin",
"role": "admin"
},
{
"id": 2,
"username": "user1",
"role": "trainee"
}
],
"row_count": 2
}
}
},
"executeResult": {
"summary": "写入结果",
"value": {
"code": 200,
"message": "SQL 执行成功",
"data": {
"type": "execute",
"affected_rows": 1,
"success": true
}
}
}
}
}
}
},
"400": {
"description": "请求参数错误",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "未认证或认证失败",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "SQL 执行错误",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/sql/validate": {
"post": {
"tags": ["SQL执行器"],
"summary": "验证 SQL 语法",
"description": "验证 SQL 语句的语法正确性,不执行实际操作",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SqlValidateRequest"
}
}
}
},
"responses": {
"200": {
"description": "验证完成",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidateResponse"
}
}
}
}
}
}
},
"/sql/tables": {
"get": {
"tags": ["SQL执行器"],
"summary": "获取表列表",
"description": "获取数据库中所有表的列表",
"responses": {
"200": {
"description": "成功获取表列表",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TablesResponse"
}
}
}
},
"401": {
"description": "未认证",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/sql/table/{table_name}/schema": {
"get": {
"tags": ["SQL执行器"],
"summary": "获取表结构",
"description": "获取指定表的结构信息,包括字段名、类型、约束等",
"parameters": [
{
"name": "table_name",
"in": "path",
"required": true,
"description": "表名",
"schema": {
"type": "string",
"pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
},
"example": "users"
}
],
"responses": {
"200": {
"description": "成功获取表结构",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TableSchemaResponse"
}
}
}
},
"400": {
"description": "无效的表名",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "未认证",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": "使用登录接口返回的 access_token。\n格式: Bearer {access_token}"
}
},
"schemas": {
"LoginRequest": {
"type": "object",
"required": ["username", "password"],
"properties": {
"username": {
"type": "string",
"description": "用户名",
"example": "admin"
},
"password": {
"type": "string",
"format": "password",
"description": "密码",
"example": "admin123"
}
}
},
"LoginResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 200
},
"message": {
"type": "string",
"example": "登录成功"
},
"data": {
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"example": 1
},
"username": {
"type": "string",
"example": "admin"
},
"role": {
"type": "string",
"example": "admin"
}
}
},
"token": {
"type": "object",
"properties": {
"access_token": {
"type": "string",
"description": "JWT 访问令牌",
"example": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
},
"token_type": {
"type": "string",
"example": "bearer"
},
"expires_in": {
"type": "integer",
"description": "过期时间(秒)",
"example": 1800
}
}
}
}
}
}
},
"SqlExecuteRequest": {
"type": "object",
"required": ["sql"],
"properties": {
"sql": {
"type": "string",
"description": "要执行的 SQL 语句",
"example": "SELECT * FROM users WHERE role = :role"
},
"params": {
"type": "object",
"description": "SQL 参数字典,键为参数名,值为参数值",
"additionalProperties": true,
"example": {
"role": "admin"
}
}
}
},
"SqlValidateRequest": {
"type": "object",
"required": ["sql"],
"properties": {
"sql": {
"type": "string",
"description": "要验证的 SQL 语句",
"example": "SELECT * FROM users"
}
}
},
"QueryResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 200
},
"message": {
"type": "string",
"example": "SQL 执行成功"
},
"data": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["query"],
"example": "query"
},
"columns": {
"type": "array",
"items": {
"type": "string"
},
"description": "列名数组",
"example": ["id", "username", "role"]
},
"rows": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
},
"description": "查询结果行"
},
"row_count": {
"type": "integer",
"description": "返回的行数",
"example": 5
}
}
}
}
},
"ExecuteResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 200
},
"message": {
"type": "string",
"example": "SQL 执行成功"
},
"data": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["execute"],
"example": "execute"
},
"affected_rows": {
"type": "integer",
"description": "影响的行数",
"example": 1
},
"success": {
"type": "boolean",
"example": true
}
}
}
}
},
"ValidateResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 200
},
"message": {
"type": "string",
"example": "SQL 验证完成"
},
"data": {
"type": "object",
"properties": {
"valid": {
"type": "boolean",
"description": "SQL 是否有效",
"example": true
},
"warnings": {
"type": "array",
"items": {
"type": "string"
},
"description": "警告信息列表",
"example": ["包含危险操作: DROP"]
},
"sql_type": {
"type": "string",
"description": "SQL 类型",
"example": "SELECT"
}
}
}
}
},
"TablesResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 200
},
"message": {
"type": "string",
"example": "获取表列表成功"
},
"data": {
"type": "object",
"properties": {
"tables": {
"type": "array",
"items": {
"type": "string"
},
"description": "表名列表",
"example": ["users", "courses", "exams"]
},
"count": {
"type": "integer",
"description": "表的数量",
"example": 20
}
}
}
}
},
"TableSchemaResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 200
},
"message": {
"type": "string",
"example": "获取表结构成功"
},
"data": {
"type": "object",
"properties": {
"table_name": {
"type": "string",
"example": "users"
},
"columns": {
"type": "array",
"items": {
"type": "object",
"properties": {
"field": {
"type": "string",
"description": "字段名",
"example": "id"
},
"type": {
"type": "string",
"description": "字段类型",
"example": "int(11)"
},
"null": {
"type": "string",
"enum": ["YES", "NO"],
"description": "是否可为空",
"example": "NO"
},
"key": {
"type": "string",
"description": "键类型PRI, UNI, MUL",
"example": "PRI"
},
"default": {
"type": "string",
"nullable": true,
"description": "默认值",
"example": null
},
"extra": {
"type": "string",
"description": "额外信息",
"example": "auto_increment"
}
}
}
},
"column_count": {
"type": "integer",
"description": "列的数量",
"example": 10
}
}
}
}
},
"ErrorResponse": {
"type": "object",
"properties": {
"detail": {
"type": "string",
"description": "错误详情",
"example": "SQL 执行失败: You have an error in your SQL syntax"
}
}
}
}
},
"tags": [
{
"name": "认证",
"description": "用户认证相关接口"
},
{
"name": "SQL执行器",
"description": "SQL 执行和管理相关接口"
}
]
}

View File

@@ -0,0 +1,52 @@
# 知识拆解工作流
**工作流名称:** upload_knowldge
**功能:** 上传资料并提炼知识点
---
## 配置信息
**API端点**
```
URL: http://dify.ireborn.com.cn/v1/workflows/run
API Key: app-LZhZcMO6CiriLMOLB2PwUGHx
```
**请求参数:**
```json
{
"inputs": {
"file": {
"type": "document",
"transfer_method": "local_file",
"upload_file_id": "dify_file_id"
},
"course_name": "课程名称",
"course_id": "1",
"material_id": "16"
},
"response_mode": "streaming",
"user": "kaopeilian"
}
```
---
## 实现方式
### Streaming模式直接完成
- 使用 `response_mode: "streaming"`
- 后端完整处理SSE流至 `workflow_finished`
- 前端180秒超时等待最终状态
- 无需轮询
### 状态映射
- `running` → 分析中
- `succeeded` → 分析完成(刷新知识点)
- `failed` → 分析失败
- `stopped` → 分析已停止
---
**最后更新:** 2025-10-12

View File

@@ -0,0 +1,224 @@
# 考试工作流-最终版
**版本:** v2.0
**状态:** ✅ 已完成并验证
**最后更新:** 2025-10-12
---
## 一、功能概述
### 考试页面
- **URL**`http://localhost:3001/trainee/exam?courseId=1`
- **流程**:三轮考试(正式考试 + 两次错题重考)
- **试题生成**动态调用Dify工作流预计1-3分钟
### 支持的题型
1. 单选题 - 点击立即判断
2. 多选题 - 点击提交判断
3. 判断题 - 点击立即判断
4. 填空题 - AI语义判断
5. 问答题 - AI语义判断
---
## 二、Dify工作流配置
### 工作流1试题生成器
**API配置**
```
URL: http://dify.ireborn.com.cn/v1/workflows/run
Token: app-tDlrmXyS9NtWCShsOx5FH49L
User: kaopeilian
Mode: streaming
```
**输入参数:**
```json
{
"course_id": 1,
"position_id": 3,
"single_choice_count": 4,
"multiple_choice_count": 2,
"true_false_count": 1,
"fill_blank_count": 2,
"essay_count": 1,
"difficulty_level": 3
}
```
**第二、三轮增加:**
```json
{
"mistake_records": "[{\"question_id\":null,\"knowledge_point_id\":456,...}]"
}
```
⚠️ **重要**:第一轮不传`mistake_records`参数第二三轮传入JSON字符串格式
### 工作流2答案判断器
**API配置**
```
URL: http://dify.ireborn.com.cn/v1/workflows/run
Token: app-FvMdrvbRBz547DVZEorgO1WT
User: kaopeilian
Mode: streaming
```
**输入参数:**
```json
{
"question": "题目内容",
"correct_answer": "正确答案",
"user_answer": "用户答案",
"analysis": "答案解析"
}
```
**返回字段:** `result``is_correct`(值为"正确"/"错误"或true/false
---
## 三、后端API接口
### 接口列表
| 接口 | 方法 | 功能 |
|------|------|------|
| `/api/v1/exams/generate` | POST | 生成试题 |
| `/api/v1/exams/judge-answer` | POST | 判断主观题答案 |
| `/api/v1/exams/record-mistake` | POST | 记录错题 |
| `/api/v1/exams/mistakes` | GET | 获取错题记录 |
**文件位置:** `kaopeilian-backend/app/api/v1/exam.py`
### 关键实现
**获取岗位ID**
```python
# 从用户岗位分配信息中自动获取
position_member = await db.execute(
select(PositionMember).where(
PositionMember.user_id == current_user.id,
PositionMember.is_deleted == False
)
)
position_id = position_member.position_id if position_member else 1
```
**创建Exam记录**
```python
exam = Exam(
user_id=current_user.id,
course_id=request.course_id,
exam_name=f"课程{request.course_id}考试",
status="started"
)
await db.commit()
return exam.id # 使用真实自增ID
```
---
## 四、前端实现要点
### 数据格式转换
**Dify格式 → 前端格式:**
```typescript
{
type: "single_choice" "single",
topic.title title,
topic.options options[],
correct correctAnswer,
analysis explanation,
knowledge_point_id parseInt()
}
```
### 三轮考试流程
```typescript
// 第一轮不传mistake_records
await generateExam({
course_id: 1,
// 不包含 mistake_records
})
// 第二、三轮:传入上一轮错题
const mistakes = await getMistakes(lastExamId)
await generateExam({
course_id: 1,
mistake_records: JSON.stringify(mistakes.data.data.mistakes)
})
```
---
## 五、关键配置
### HTTP超时设置
```typescript
// 前端
generateExam: timeout 300000 // 5分钟
judgeAnswer: timeout 60000 // 1分钟
// 后端
httpx.AsyncClient(timeout=300.0) // 试题生成
httpx.AsyncClient(timeout=60.0) // 答案判断
```
### 数据库表
**exam_mistakes表**
- user_id, exam_id必填
- question_id, knowledge_point_id可空
- question_content, correct_answer, user_answer
---
## 六、重要经验
### FastAPI路由顺序
```python
# ✅ 正确顺序
@router.get("/mistakes") # 具体路由在前
@router.get("/{exam_id}") # 动态路由在后
```
### ResponseModel使用
```python
# ✅ 统一使用ResponseModel
@router.get("/mistakes", response_model=ResponseModel[GetMistakesResponse])
async def get_mistakes(...):
return ResponseModel(code=200, data=GetMistakesResponse(...))
```
### 数据库ID生成
- ❌ 不使用时间戳13位数字超出INT范围
- ✅ 使用数据库自增ID
### Axios响应访问
```typescript
// ✅ 正确路径
response.data.code
response.data.data.result
response.data.data.exam_id
```
---
## 七、测试账号
- **用户名:** admin
- **密码:** admin123
- **测试课程:** courseId=1
---
**文档维护:** AI助手
**最后验证:** 2025-10-12三轮考试流程验证成功

View File

@@ -0,0 +1,164 @@
# 考试工作流联调文档
**版本:** v2.0
**状态:** ✅ 已完成
**最后更新:** 2025-10-12
---
## 一、基本信息
### 考试页面
- **URL** `http://localhost:3001/trainee/exam?courseId=1`
- **流程:** 三轮考试(正式考试 + 两次错题重考)
### 两个Dify工作流
#### 工作流1试题生成器
- **API** `http://dify.ireborn.com.cn/v1/workflows/run`
- **Token** `app-tDlrmXyS9NtWCShsOx5FH49L`
- **功能:** 根据课程知识点生成试题
#### 工作流2答案判断器
- **API** `http://dify.ireborn.com.cn/v1/workflows/run`
- **Token** `app-FvMdrvbRBz547DVZEorgO1WT`
- **功能:** 判断填空题和问答题答案
---
## 二、API接口
### 后端接口列表
| 接口 | 方法 | 功能 |
|------|------|------|
| `/api/v1/exams/generate` | POST | 生成试题 |
| `/api/v1/exams/judge-answer` | POST | 判断主观题答案 |
| `/api/v1/exams/record-mistake` | POST | 记录错题 |
| `/api/v1/exams/mistakes` | GET | 获取错题记录 |
**文件:** `kaopeilian-backend/app/api/v1/exam.py`
---
## 三、参数说明
### 生成试题参数
**第一轮不传mistake_records**
```json
{
"course_id": 1,
"single_choice_count": 4,
"multiple_choice_count": 2,
"true_false_count": 1,
"fill_blank_count": 2,
"essay_count": 1,
"difficulty_level": 3
}
```
**第二、三轮(传入错题记录):**
```json
{
"course_id": 1,
"mistake_records": "[{\"question_id\":null,\"knowledge_point_id\":456,...}]",
"single_choice_count": 2,
...
}
```
⚠️ **关键**
- 第一轮:完全不传`mistake_records`参数
- 第二三轮传入JSON字符串格式
### 判断答案参数
```json
{
"question": "题目内容",
"correct_answer": "正确答案",
"user_answer": "用户答案",
"analysis": "答案解析"
}
```
**返回:** `result: "正确"/"错误"``is_correct: true/false`
---
## 四、数据库表
### exam_mistakes错题记录表
**核心字段:**
- user_id, exam_id必填外键CASCADE
- question_id, knowledge_point_id可空SET NULL
- question_content, correct_answer, user_answer
**索引:** user_id, exam_id, knowledge_point_id
---
## 五、三轮考试流程
```
第一轮
↓ 答错N题
├─ 记录错题到数据库
└─ 获取错题记录 → 第二轮
第二轮(针对第一轮错题)
↓ 答错M题
├─ 记录错题到数据库
└─ 获取错题记录 → 第三轮
第三轮(针对第二轮错题)
↓ 完成
└─ 显示最终成绩
```
---
## 六、关键技术点
### 1. 路由顺序
```python
# ✅ 正确
@router.get("/mistakes") # 具体路由在前
@router.get("/{exam_id}") # 动态路由在后
```
### 2. 数据格式转换
**Dify → 前端:**
```
single_choice → single
multiple_choice → multiple
true_false → judge
fill_blank → blank
essay → essay
```
### 3. 超时配置
- 试题生成300秒5分钟
- 答案判断60秒1分钟
---
## 七、测试验证
**测试账号:** admin / admin123
**测试课程:** courseId=1
**验证要点:**
- ✅ 试题成功生成
- ✅ 所有题型正常答题
- ✅ 错题正确记录
- ✅ AI判断正常工作
- ✅ 三轮流程完整
---
**文档维护:** 开发团队
**参考:** `试题生成器的核心提示词与输出示例.md`

File diff suppressed because one or more lines are too long