- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
22 KiB
陪练功能API接口规范
一、通用规范
1.1 请求格式
Base URL:
- 开发环境:
http://localhost:8000 - 生产环境:
https://aiedu.ireborn.com.cn
请求头:
Content-Type: application/json; charset=utf-8
Authorization: Bearer <access_token>
1.2 响应格式
统一响应结构:
{
"code": 200,
"message": "success",
"data": {},
"request_id": "uuid"
}
分页响应结构:
{
"code": 200,
"message": "success",
"data": {
"items": [],
"total": 100,
"page": 1,
"page_size": 20,
"pages": 5
}
}
1.3 状态码约定
| HTTP状态码 | code | 说明 |
|---|---|---|
| 200 | 200 | 成功 |
| 400 | 400 | 请求参数错误 |
| 401 | 401 | 未授权(token无效或过期) |
| 403 | 403 | 无权限 |
| 404 | 404 | 资源不存在 |
| 500 | 500 | 服务器错误 |
二、数据模型
2.1 场景类型枚举 (SceneType)
type SceneType = 'phone' | 'face' | 'complaint' | 'after-sales' | 'product-intro'
| 值 | 说明 |
|---|---|
| phone | 电话销售 |
| face | 面对面销售 |
| complaint | 客户投诉 |
| after-sales | 售后服务 |
| product-intro | 产品介绍 |
2.2 难度等级枚举 (Difficulty)
type Difficulty = 'beginner' | 'junior' | 'intermediate' | 'senior' | 'expert'
| 值 | 说明 |
|---|---|
| beginner | 入门 |
| junior | 初级 |
| intermediate | 中级 |
| senior | 高级 |
| expert | 专家 |
2.3 场景状态枚举 (SceneStatus)
type SceneStatus = 'active' | 'inactive'
| 值 | 说明 |
|---|---|
| active | 启用 |
| inactive | 禁用 |
2.4 陪练场景对象 (PracticeScene)
interface PracticeScene {
id: number
name: string
description: string
type: SceneType
difficulty: Difficulty
status: SceneStatus
background: string
ai_role: string
objectives: string[]
keywords: string[]
duration: number
usage_count: number
rating: number
created_by: number
updated_by: number
created_at: string
updated_at: string
}
三、场景管理接口(Manager)
3.1 获取场景列表
接口:GET /api/v1/manager/practice-scenes
权限:manager、admin
请求参数:
| 参数 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
| page | number | 否 | 页码,默认1 | 1 |
| size | number | 否 | 每页数量,默认20 | 20 |
| type | string | 否 | 场景类型筛选 | phone |
| difficulty | string | 否 | 难度筛选 | intermediate |
| status | string | 否 | 状态筛选 | active |
| search | string | 否 | 关键词搜索(名称、描述) | 销售 |
请求示例:
GET /api/v1/manager/practice-scenes?page=1&size=20&type=phone&difficulty=beginner
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
响应示例:
{
"code": 200,
"message": "success",
"data": {
"items": [
{
"id": 1,
"name": "初次电话拜访客户",
"description": "模拟首次通过电话联系潜在客户的场景",
"type": "phone",
"difficulty": "beginner",
"status": "active",
"background": "你是一名销售专员...",
"ai_role": "AI扮演一位忙碌的采购经理...",
"objectives": ["学会专业的电话开场白", "快速建立信任关系"],
"keywords": ["开场白", "需求挖掘"],
"duration": 10,
"usage_count": 256,
"rating": 4.5,
"created_at": "2024-01-15T10:30:00",
"updated_at": "2024-03-18T15:20:00"
}
],
"total": 15,
"page": 1,
"page_size": 20,
"pages": 1
}
}
3.2 获取场景详情
接口:GET /api/v1/manager/practice-scenes/{id}
权限:manager、admin
路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | number | 是 | 场景ID |
请求示例:
GET /api/v1/manager/practice-scenes/1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
响应示例:
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"name": "初次电话拜访客户",
"description": "模拟首次通过电话联系潜在客户的场景",
"type": "phone",
"difficulty": "beginner",
"status": "active",
"background": "你是一名销售专员,需要通过电话联系一位从未接触过的潜在客户。客户是某公司的采购经理,你需要在短时间内引起他的兴趣。",
"ai_role": "AI扮演一位忙碌且略显不耐烦的采购经理,对推销电话比较抵触,但如果销售人员能够快速切入他的需求点,他会愿意继续交谈。",
"objectives": ["学会专业的电话开场白", "快速建立信任关系", "有效探询客户需求", "预约下次沟通时间"],
"keywords": ["开场白", "需求挖掘", "时间管理", "预约技巧"],
"duration": 10,
"usage_count": 256,
"rating": 4.5,
"created_by": 1,
"updated_by": 1,
"created_at": "2024-01-15T10:30:00",
"updated_at": "2024-03-18T15:20:00"
}
}
3.3 创建场景
接口:POST /api/v1/manager/practice-scenes
权限:manager、admin
请求体:
interface CreateSceneRequest {
name: string // 必填,最长200字符
description: string // 必填,场景描述
type: SceneType // 必填
difficulty: Difficulty // 必填
status?: SceneStatus // 可选,默认active
background: string // 必填,场景背景
ai_role: string // 必填,AI角色描述
objectives: string[] // 必填,练习目标数组
keywords: string[] // 必填,关键词数组
duration?: number // 可选,预计时长(分钟),默认10
}
请求示例:
POST /api/v1/manager/practice-scenes
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
{
"name": "产品功能介绍",
"description": "练习向客户详细介绍产品功能特点",
"type": "product-intro",
"difficulty": "junior",
"status": "active",
"background": "客户对你们的产品有一定了解,现在希望你详细介绍产品的核心功能和优势。",
"ai_role": "AI扮演一位专业的采购人员,会提出具体的技术问题和需求,希望了解产品的详细功能。",
"objectives": ["清晰介绍产品功能", "突出产品优势", "回答技术问题"],
"keywords": ["产品介绍", "功能展示", "优势分析"],
"duration": 12
}
响应示例:
{
"code": 200,
"message": "创建场景成功",
"data": {
"id": 6,
"name": "产品功能介绍",
"description": "练习向客户详细介绍产品功能特点",
"type": "product-intro",
"difficulty": "junior",
"status": "active",
"background": "客户对你们的产品有一定了解...",
"ai_role": "AI扮演一位专业的采购人员...",
"objectives": ["清晰介绍产品功能", "突出产品优势", "回答技术问题"],
"keywords": ["产品介绍", "功能展示", "优势分析"],
"duration": 12,
"usage_count": 0,
"rating": 0.0,
"created_at": "2025-10-13T18:30:00"
}
}
3.4 更新场景
接口:PUT /api/v1/manager/practice-scenes/{id}
权限:manager、admin
路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | number | 是 | 场景ID |
请求体:与创建场景相同,所有字段可选
请求示例:
PUT /api/v1/manager/practice-scenes/6
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
{
"name": "产品功能详细介绍",
"duration": 15,
"status": "active"
}
响应示例:
{
"code": 200,
"message": "更新场景成功",
"data": {
"id": 6,
"name": "产品功能详细介绍",
"duration": 15,
"updated_at": "2025-10-13T19:00:00"
}
}
3.5 删除场景
接口:DELETE /api/v1/manager/practice-scenes/{id}
权限:manager、admin
路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | number | 是 | 场景ID |
请求示例:
DELETE /api/v1/manager/practice-scenes/6
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
响应示例:
{
"code": 200,
"message": "删除场景成功",
"data": {
"id": 6
}
}
3.6 切换场景状态
接口:PUT /api/v1/manager/practice-scenes/{id}/toggle-status
权限:manager、admin
路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | number | 是 | 场景ID |
请求示例:
PUT /api/v1/manager/practice-scenes/6/toggle-status
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
响应示例:
{
"code": 200,
"message": "场景状态已切换",
"data": {
"id": 6,
"status": "inactive"
}
}
四、学员查询接口(Trainee)
4.1 获取可用场景列表
接口:GET /api/v1/practice/scenes
权限:trainee、manager、admin
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page | number | 否 | 页码,默认1 |
| size | number | 否 | 每页数量,默认20 |
| type | string | 否 | 场景类型筛选 |
| difficulty | string | 否 | 难度筛选 |
| search | string | 否 | 关键词搜索 |
说明:仅返回status=active的场景
请求示例:
GET /api/v1/practice/scenes?page=1&size=20&type=phone
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
响应示例:同管理接口的列表响应
4.2 获取场景详情
接口:GET /api/v1/practice/scenes/{id}
权限:trainee、manager、admin
路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | number | 是 | 场景ID |
请求示例:
GET /api/v1/practice/scenes/1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
响应示例:同管理接口的详情响应
五、陪练对话接口
5.1 开始陪练对话
接口:POST /api/v1/practice/start
权限:trainee、manager、admin
协议:SSE (Server-Sent Events)
请求体:
interface StartPracticeRequest {
scene_id?: number // 场景ID(陪练中心模式,可选)
scene_name: string // 场景名称(必填)
scene_description?: string // 场景描述(可选)
scene_background: string // 场景背景(必填)
scene_ai_role: string // AI角色(必填)
scene_objectives: string[] // 练习目标(必填)
scene_keywords?: string[] // 关键词(可选)
user_message: string // 用户消息(必填)
conversation_id?: string // 对话ID(续接对话时必填)
is_first: boolean // 是否首次消息(必填)
}
⚠️ 重要说明:
- 首次消息 (is_first=true):后端会将场景信息(background, ai_role, objectives等)拼接到user_message前面,作为完整的系统提示发送给Coze,让AI理解角色设定
- 后续消息 (is_first=false):仅发送user_message,不再重复场景信息
请求示例: 首次消息请求示例:
POST /api/v1/practice/start
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
{
"scene_id": 1,
"scene_name": "初次电话拜访客户",
"scene_description": "模拟首次通过电话联系潜在客户的场景",
"scene_background": "你是一名销售专员,需要通过电话联系一位从未接触过的潜在客户。客户是某公司的采购经理,你需要在短时间内引起他的兴趣。",
"scene_ai_role": "AI扮演一位忙碌且略显不耐烦的采购经理,对推销电话比较抵触,但如果销售人员能够快速切入他的需求点,他会愿意继续交谈。",
"scene_objectives": ["学会专业的电话开场白", "快速建立信任关系", "有效探询客户需求"],
"scene_keywords": ["开场白", "需求挖掘", "时间管理"],
"user_message": "您好,我是XX公司的销售顾问,想占用您几分钟时间",
"is_first": true
}
后端处理:首次消息时,后端会将场景信息构建为完整的场景设定文本,拼接到user_message前面发送给Coze。
实际发送给Coze的内容:
# 陪练场景设定
## 场景名称
初次电话拜访客户
## 场景背景
你是一名销售专员,需要通过电话联系一位从未接触过的潜在客户...
## AI角色要求
AI扮演一位忙碌且略显不耐烦的采购经理...
## 练习目标
1. 学会专业的电话开场白
2. 快速建立信任关系
3. 有效探询客户需求
## 关键词
开场白, 需求挖掘, 时间管理
---
现在开始陪练对话。请你严格按照上述场景设定扮演角色,与学员进行实战对话练习。
学员的第一句话:您好,我是XX公司的销售顾问,想占用您几分钟时间
后续消息请求示例:
POST /api/v1/practice/start
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
{
"user_message": "我们公司提供的是轻医美整体解决方案",
"conversation_id": "conv_abc123",
"is_first": false
}
后端处理:后续消息直接发送user_message,不再拼接场景信息。
SSE事件格式:
事件1:对话创建
event: conversation.chat.created
data: {"conversation_id":"conv_abc123","chat_id":"chat_xyz789"}
事件2:消息增量(实时打字)
event: message.delta
data: {"content":"您"}
event: message.delta
data: {"content":"好"}
event: message.delta
data: {"content":","}
事件3:消息完成
event: message.completed
data: {"content":"您好,我现在很忙,你有什么事吗?"}
事件4:对话完成
event: conversation.completed
data: {"token_count":156}
事件5:结束标记
event: done
data: [DONE]
事件6:错误
event: error
data: {"error":"对话失败: Network Error"}
前端处理示例:
const response = await fetch('/api/v1/practice/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(requestData)
})
const reader = response.body.getReader()
const decoder = new TextDecoder()
while (true) {
const { value, done } = await reader.read()
if (done) break
const chunk = decoder.decode(value)
const lines = chunk.split('\n\n')
for (const line of lines) {
if (!line.trim() || !line.startsWith('event: ')) continue
const [eventLine, dataLine] = line.split('\n')
const event = eventLine.replace('event: ', '')
const data = JSON.parse(dataLine.replace('data: ', ''))
switch (event) {
case 'conversation.chat.created':
conversationId.value = data.conversation_id
break
case 'message.delta':
aiMessage.content += data.content
break
case 'message.completed':
console.log('消息完成')
break
case 'conversation.completed':
console.log('Token用量:', data.token_count)
break
case 'error':
ElMessage.error(data.error)
break
}
}
}
5.2 中断对话
接口:POST /api/v1/practice/interrupt
权限:trainee、manager、admin
请求体:
interface InterruptPracticeRequest {
conversation_id: string
chat_id: string
}
请求示例:
POST /api/v1/practice/interrupt
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
{
"conversation_id": "conv_abc123",
"chat_id": "chat_xyz789"
}
响应示例:
{
"code": 200,
"message": "对话已中断",
"data": {
"conversation_id": "conv_abc123",
"chat_id": "chat_xyz789"
}
}
5.3 获取对话列表
接口:GET /api/v1/practice/conversations
权限:trainee、manager、admin
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page | number | 否 | 页码,默认1 |
| size | number | 否 | 每页数量,默认20 |
请求示例:
GET /api/v1/practice/conversations?page=1&size=20
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
响应示例:
{
"code": 200,
"message": "success",
"data": {
"items": [
{
"id": "conv_abc123",
"name": "初次电话拜访客户 - 2025-10-13",
"created_at": 1697184000000
}
],
"has_more": false,
"page": 1,
"size": 20
}
}
六、Dify场景提取接口
6.1 从课程提取场景
接口:POST /api/v1/practice/extract-scene
权限:trainee、manager、admin
请求体:
interface ExtractSceneRequest {
course_id: number
}
请求示例:
POST /api/v1/practice/extract-scene
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
{
"course_id": 5
}
响应示例:
{
"code": 200,
"message": "场景提取成功",
"data": {
"scene": {
"name": "轻医美产品咨询陪练",
"description": "模拟客户咨询轻医美产品的场景,重点练习产品介绍和价格谈判技巧",
"type": "product-intro",
"difficulty": "intermediate",
"background": "客户是一位30岁女性,对面部抗衰项目感兴趣,之前了解过玻尿酸填充,现在想详细咨询你们的产品和价格。",
"ai_role": "AI扮演一位对轻医美有一定了解的客户,关注产品安全性和性价比,会提出具体的技术问题和价格异议。",
"objectives": [
"了解客户具体需求和预算",
"专业介绍产品成分和效果",
"处理价格异议,强调价值",
"建立客户信任"
],
"keywords": ["抗衰", "玻尿酸", "价格", "安全性"]
},
"workflow_run_id": "wf_run_abc123",
"task_id": "task_xyz789"
}
}
七、错误码说明
| code | message | 说明 | 处理建议 |
|---|---|---|---|
| 200 | success | 成功 | 正常处理 |
| 400 | 参数错误 | 请求参数不合法 | 检查参数格式 |
| 401 | 未授权 | token无效或过期 | 重新登录 |
| 403 | 无权限 | 当前角色无权限 | 提示用户无权限 |
| 404 | 资源不存在 | 场景不存在 | 返回列表页 |
| 409 | 资源冲突 | 场景名称重复 | 提示修改名称 |
| 500 | 服务器错误 | 内部错误 | 提示稍后重试 |
| 503 | 服务不可用 | Coze/Dify服务异常 | 提示稍后重试 |
八、附录
8.1 完整TypeScript类型定义
// 场景类型
type SceneType = 'phone' | 'face' | 'complaint' | 'after-sales' | 'product-intro'
type Difficulty = 'beginner' | 'junior' | 'intermediate' | 'senior' | 'expert'
type SceneStatus = 'active' | 'inactive'
// 场景对象
interface PracticeScene {
id: number
name: string
description: string
type: SceneType
difficulty: Difficulty
status: SceneStatus
background: string
ai_role: string
objectives: string[]
keywords: string[]
duration: number
usage_count: number
rating: number
created_by: number
updated_by: number
created_at: string
updated_at: string
}
// API响应
interface ResponseModel<T = any> {
code: number
message: string
data: T
request_id?: string
}
interface PaginatedResponse<T> {
items: T[]
total: number
page: number
page_size: number
pages: number
}
// 请求对象
interface CreateSceneRequest {
name: string
description: string
type: SceneType
difficulty: Difficulty
status?: SceneStatus
background: string
ai_role: string
objectives: string[]
keywords: string[]
duration?: number
}
interface StartPracticeRequest {
scene_id?: number
scene_name: string
scene_background: string
scene_ai_role: string
scene_objectives: string[]
user_message: string
conversation_id?: string
is_first: boolean
}
interface ExtractSceneRequest {
course_id: number
}
// SSE事件
interface SSEEvent {
event: 'conversation.chat.created' | 'message.delta' | 'message.completed' | 'conversation.completed' | 'error' | 'done'
data: any
}
8.2 API调用示例(完整)
// src/api/practice.ts
import request from '@/utils/request'
export const practiceApi = {
// 获取场景列表
getScenes(params) {
return request.get('/api/v1/practice/scenes', { params })
},
// 获取场景详情
getSceneDetail(id) {
return request.get(`/api/v1/practice/scenes/${id}`)
},
// 开始陪练(SSE流式)
startPractice(data) {
// SSE需要特殊处理,不能用普通的request
return fetch(`${import.meta.env.VITE_API_BASE_URL}/api/v1/practice/start`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}`
},
body: JSON.stringify(data)
})
},
// 中断对话
interruptPractice(data) {
return request.post('/api/v1/practice/interrupt', data)
},
// 获取对话列表
getConversations(params) {
return request.get('/api/v1/practice/conversations', { params })
},
// 提取场景
extractScene(data) {
return request.post('/api/v1/practice/extract-scene', data)
}
}
// 管理接口
export const practiceManagerApi = {
// 获取场景列表
getScenes(params) {
return request.get('/api/v1/manager/practice-scenes', { params })
},
// 创建场景
createScene(data) {
return request.post('/api/v1/manager/practice-scenes', data)
},
// 更新场景
updateScene(id, data) {
return request.put(`/api/v1/manager/practice-scenes/${id}`, data)
},
// 删除场景
deleteScene(id) {
return request.delete(`/api/v1/manager/practice-scenes/${id}`)
},
// 切换状态
toggleStatus(id) {
return request.put(`/api/v1/manager/practice-scenes/${id}/toggle-status`)
}
}
文档版本:v1.0 最后更新:2025-10-13 维护人:考培练系统开发团队