Files
shop-platform/docs/api_kefu.md

660 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 智能客服API接口文档
## 一、接口说明
本接口用于连接微信小程序与Dify聊天机器人实现智能客服功能。支持流式和非流式两种响应模式具备完整的事务保护、数据一致性和状态管理功能。
## 二、配置说明
### 1. 安装插件
在ThinkPHP后台的插件管理页面中找到智能客服插件aikefu并点击安装按钮。
### 2. 配置插件
1. 进入智能客服配置页面
2. 输入从Dify平台获取的API密钥
3. 配置API基础地址默认https://api.dify.ai/v1
4. 配置聊天接口端点(默认:/chat-messages
5. 启用智能客服功能
### 3. 获取Dify API密钥
1. 登录Dify平台
2. 进入工作台
3. 选择您的聊天机器人项目
4. 点击"发布"按钮
5. 在API访问页面获取API密钥
## 三、接口列表
### 1. 系统健康检查
**接口地址**/api/kefu/health
**请求方式**POST
**请求参数**
| 参数名 | 类型 | 必填 | 说明 |
| ------ | ---- | ---- | ---- |
| uniacid | int | 是 | 站点ID |
| check_type | string | 否 | 检查类型full完整、basic基础、ai_serviceAI服务默认full |
**响应示例**
```json
{
"code": 0,
"message": "success",
"data": {
"status": "healthy",
"check_id": "health_56789",
"timestamp": "2023-12-25 10:30:45",
"total_checks": 2,
"passed_checks": 2,
"failed_checks": 0,
"response_time_ms": 170,
"components": {
"ai_service": {
"status": "healthy",
"message": "AI服务正常",
"response_time_ms": 150
},
"database": {
"status": "healthy",
"message": "数据库连接正常",
"response_time_ms": 20
}
},
"warnings": [],
"errors": []
}
}
```
### 2. 获取服务配置信息
**接口地址**/api/kefu/info
**请求方式**POST
**请求参数**
| 参数名 | 类型 | 必填 | 说明 |
| ------ | ---- | ---- | ---- |
| uniacid | int | 是 | 站点ID |
| member_id | int | 否 | 会员ID |
| token | string | 否 | 访问令牌 |
**响应示例**
```json
{
"code": 0,
"message": "success",
"data": {
"enabled": true,
"status": "enabled"
}
}
```
### 3. 智能客服聊天接口
**接口地址**`/api/kefu/chat`
**请求方式**POST 或 GET流式模式支持EventSource
**请求参数**
| 参数名 | 类型 | 必填 | 说明 |
| ------ | ---- | ---- | ---- |
| query | string | 是 | 用户输入的消息内容 |
| user_id | string | 否 | 用户ID默认使用当前登录会员ID |
| conversation_id | string | 否 | 会话ID第一次聊天可不传系统会自动创建 |
| stream | bool | 否 | 是否使用流式响应默认false |
| response_mode | string | 否 | 响应模式streaming流式、blocking阻塞默认streaming |
| uniacid | int | 是 | 站点ID |
**响应示例**
#### 非流式响应stream=false 或 response_mode=blocking
```json
{
"code": 0,
"message": "success",
"data": {
"conversation_id": "conv_123456789",
"answer": "您好,我是智能客服,有什么可以帮助您的?",
"message_id": "msg_123456789",
"finish_reason": "stop",
"usage": {
"prompt_tokens": 10,
"completion_tokens": 20,
"total_tokens": 30
}
}
}
```
#### 流式响应stream=true 或 response_mode=streaming
**响应格式**Server-Sent Events (SSE)
**响应示例**
```javascript
data: {"event":"message","answer":"您好","conversation_id":"conv_123456789","message_id":"msg_123456789"}
data: {"event":"message","answer":",我是智能客服,","conversation_id":"conv_123456789","message_id":"msg_123456789"}
data: {"event":"message","answer":"有什么可以帮助您的?","conversation_id":"conv_123456789","message_id":"msg_123456789"}
data: {"event":"message_end","conversation_id":"conv_123456789","message_id":"msg_123456789"}
data: {"event":"done","data":{"conversation_id":"conv_123456789","message_id":"msg_123456789","content":"您好,我是智能客服,有什么可以帮助您的?"}}
data: {"event":"close","data":{"conversation_id":"conv_123456789","message_id":"msg_123456789"}}
```
### 4. 获取会话历史
**接口地址**/api/kefu/getHistory
**请求方式**POST
**请求参数**
| 参数名 | 类型 | 必填 | 说明 |
| ------ | ---- | ---- | ---- |
| uniacid | int | 是 | 站点ID |
| conversation_id | string | 是 | 会话ID |
| user_id | string | 否 | 用户ID默认使用当前登录会员ID |
| limit | int | 否 | 每页条数默认20 |
| offset | int | 否 | 偏移量默认0 |
| member_id | int | 否 | 会员ID |
| token | string | 否 | 访问令牌 |
**响应示例**
```json
{
"code": 0,
"message": "success",
"data": {
"messages": [
{
"id": "msg_123456789",
"role": "user",
"content": "您好",
"create_time": 1703505845
},
{
"id": "msg_123456790",
"role": "assistant",
"content": "您好,我是智能客服,有什么可以帮助您的?",
"create_time": 1703505846
}
],
"total": 2,
"page_info": {
"limit": 20,
"offset": 0
}
}
}
```
### 5. 清除会话历史
**接口地址**`/api/kefu/clearConversation`
**请求方式**POST
**请求参数**
| 参数名 | 类型 | 必填 | 说明 |
| ------ | ---- | ---- | ---- |
| uniacid | int | 是 | 站点ID |
| conversation_id | string | 否与user_id二选一 | 会话ID与user_id二选一 |
| user_id | string | 否与conversation_id二选一 | 用户ID默认使用当前登录会员ID与conversation_id二选一 |
| member_id | int | 否 | 会员ID |
| token | string | 否 | 访问令牌 |
**响应示例**
```json
{
"code": 0,
"message": "success",
"data": {}
}
```
## 四、前端调用示例
### 1. 非流式聊天Fetch API
```javascript
// 非流式聊天
async function chatWithAI(message, conversationId = '') {
try {
const formData = new FormData();
formData.append('query', message);
formData.append('uniacid', '1');
formData.append('stream', 'false');
formData.append('response_mode', 'blocking');
if (conversationId) {
formData.append('conversation_id', conversationId);
}
const response = await fetch('/api/kefu/chat', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.code === 0) {
return result.data;
} else {
console.error('聊天失败:', result.message);
return null;
}
} catch (error) {
console.error('聊天请求失败:', error);
return null;
}
}
```
### 2. 流式聊天EventSource
```javascript
// EventSource 流式聊天
function chatWithAIEventSource(message, conversationId = '', onMessage, onComplete, onError) {
// 关闭之前的连接
if (window.currentEventSource) {
window.currentEventSource.close();
}
// 构建请求参数
const params = new URLSearchParams({
uniacid: '1',
user_id: '123456',
query: message,
conversation_id: conversationId || '',
stream: 'true'
});
const url = `/api/kefu/chat?${params.toString()}`;
try {
const eventSource = new EventSource(url);
window.currentEventSource = eventSource;
let aiMessage = '';
// 监听消息事件
eventSource.addEventListener('message', (event) => {
try {
const data = JSON.parse(event.data);
if (data.event === 'message') {
// 更新 AI 消息
aiMessage += data.answer || '';
if (onMessage) onMessage(data.answer || '');
}
if (data.event === 'message_end') {
// 对话完成
if (onComplete) onComplete({
conversation_id: data.conversation_id,
message: aiMessage
});
}
if (data.conversation_id) {
conversationId = data.conversation_id;
}
} catch (error) {
console.error('解析消息失败:', error);
}
});
// 监听完成事件
eventSource.addEventListener('done', (event) => {
try {
const data = JSON.parse(event.data);
if (onComplete) onComplete(data);
} catch (error) {
console.error('解析完成事件失败:', error);
}
});
// 监听关闭事件
eventSource.addEventListener('close', (event) => {
try {
const data = JSON.parse(event.data);
console.log('连接正常结束:', data);
} catch (error) {
console.error('解析关闭事件失败:', error);
}
window.currentEventSource = null;
});
// 监听错误事件
eventSource.addEventListener('error', (error) => {
console.error('EventSource错误:', error);
if (onError) onError({ error: 'EventSource连接错误' });
window.currentEventSource = null;
});
return eventSource;
} catch (error) {
console.error('创建EventSource失败:', error);
if (onError) onError({ error: error.message });
return null;
}
}
```
### 3. 流式聊天Fetch API
```javascript
// Fetch API 流式聊天
async function chatWithAIFetchStream(message, conversationId = '', onMessage, onComplete, onError) {
try {
// 构建请求体
const formData = new FormData();
formData.append('uniacid', '1');
formData.append('user_id', '123456');
formData.append('query', message);
formData.append('conversation_id', conversationId || '');
formData.append('stream', 'true');
const response = await fetch('/api/kefu/chat', {
method: 'POST',
body: formData,
headers: {
'Accept': 'text/event-stream'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
if (!response.body) {
throw new Error('响应体不可用');
}
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let buffer = '';
let aiMessage = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
// 解码新接收的数据
buffer += decoder.decode(value, { stream: true });
// 处理缓冲的数据,按行分割
let lineEnd;
while ((lineEnd = buffer.indexOf('\n')) !== -1) {
const line = buffer.substring(0, lineEnd);
buffer = buffer.substring(lineEnd + 1);
if (line.startsWith('data: ')) {
try {
const data = JSON.parse(line.substring(6));
if (data.event === 'message') {
aiMessage += data.answer || '';
if (onMessage) onMessage(data.answer || '');
} else if (data.event === 'message_end') {
if (onComplete) onComplete({
conversation_id: data.conversation_id,
message: aiMessage
});
} else if (data.event === 'done' && onComplete) {
onComplete(data);
} else if (data.event === 'error' && onError) {
onError(data);
}
if (data.conversation_id) {
conversationId = data.conversation_id;
}
} catch (e) {
console.warn('解析流式数据失败:', e);
}
}
}
}
// 处理剩余的缓冲数据
if (buffer.startsWith('data: ')) {
try {
const data = JSON.parse(buffer.substring(6));
if (data.event === 'message' && onMessage) {
onMessage(data.answer || '');
} else if (data.event === 'done' && onComplete) {
onComplete(data);
}
} catch (e) {
console.warn('解析剩余数据失败:', e);
}
}
} catch (error) {
console.error('Fetch流式聊天请求失败:', error);
if (onError) onError({ error: error.message });
}
}
```
### 4. Uniapp调用示例
```javascript
// Uniapp 非流式调用
async function chatWithAI(message, conversationId = '') {
try {
const res = await uni.request({
url: '/api/kefu/chat',
method: 'POST',
data: {
query: message,
uniacid: 1,
conversation_id: conversationId,
response_mode: 'blocking'
}
});
if (res[1].data.code === 0) {
return res[1].data.data;
} else {
console.error('聊天失败:', res[1].data.message);
return null;
}
} catch (error) {
console.error('聊天请求失败:', error);
return null;
}
}
// Uniapp 获取历史记录
async function getChatHistory(conversationId, limit = 20, offset = 0) {
try {
const res = await uni.request({
url: '/api/kefu/getHistory',
method: 'POST',
data: {
uniacid: 1,
conversation_id: conversationId,
limit: limit,
offset: offset
}
});
if (res[1].data.code === 0) {
return res[1].data.data;
} else {
console.error('获取历史记录失败:', res[1].data.message);
return null;
}
} catch (error) {
console.error('获取历史记录请求失败:', error);
return null;
}
}
// Uniapp 健康检查
async function checkHealth(checkType = 'full') {
try {
const res = await uni.request({
url: '/api/kefu/health',
method: 'POST',
data: {
uniacid: 1,
check_type: checkType
}
});
if (res[1].data.code === 0) {
return res[1].data.data;
} else {
console.error('健康检查失败:', res[1].data.message);
return null;
}
} catch (error) {
console.error('健康检查请求失败:', error);
return null;
}
}
```
## 五、使用流程
1. **初始化检查**:小程序端启动时,调用`health``info`接口检查服务状态
2. **获取会话**:进入客服页面时,系统会自动创建或使用已有会话
3. **发送消息**:用户输入消息后,调用`chat`接口发送消息,获取机器人回复
4. **显示消息**:将用户消息和机器人回复显示在聊天界面
5. **加载历史记录**:需要时调用`getHistory`接口加载历史消息
6. **维护会话**保持会话ID用于后续消息交流
7. **清理数据**:根据用户需求调用`clearConversation`接口清理历史数据
## 六、数据存储机制
### 1. 存储状态
| 状态值 | 含义 | 说明 |
|--------|------|------|
| `streaming` | 流式中 | 正在进行流式输出的临时数据 |
| `completed` | 已完成 | 正常完成的对话数据 |
| `failed` | 失败 | 流式过程中发生失败的数据 |
### 2. 事务保护
- **流式对话**使用临时会话ID机制失败时自动回滚
- **非流式对话**:完整的事务保护,确保数据一致性
- **重复检查**:避免重复存储相同消息
### 3. 数据一致性
- 用户消息和助手消息通过`conversation_id`关联
- 会话状态实时更新,便于管理和监控
- 详细的日志记录,便于问题排查
## 七、注意事项
1. **必填参数**:所有接口都需要`uniacid`站点ID参数
2. **参数更新**:新版本使用`query`替代`message`,使用`uniacid`替代`site_id`
3. **事件驱动**:后端采用事件驱动架构,所有业务逻辑通过事件处理器执行
4. **安全性**请确保Dify API密钥的安全性不要泄露给前端
5. **用户标识**建议对用户ID进行加密处理避免直接使用敏感信息
6. **流式体验**:推荐使用`stream: true`参数获得更好的用户体验
7. **会话管理**:建议实现会话管理机制,定期清理过期会话
8. **频率限制**:建议添加请求频率限制,防止恶意请求
9. **生产环境**在生产环境中建议关闭DEBUG模式
10. **数据完整性**:系统已内置事务保护和重复检查机制
## 八、测试建议
1. **基础检查**:首先调用`health`接口检查系统状态
2. **配置验证**:调用`info`接口验证配置信息
3. **接口测试**使用Postman或类似工具测试各个API接口
4. **流式测试**:测试`chat`接口的流式响应功能
5. **完整流程**:在小程序端集成并测试完整流程
6. **边界测试**:模拟不同场景下的用户输入,测试机器人回复效果
7. **压力测试**:测试接口在高并发情况下的表现
8. **数据验证**:检查非流式对话的存储完整性和一致性
## 九、常见问题
### 1. 接口返回400错误
**原因**:缺少必填参数`uniacid`或参数格式错误
**解决方法**确保请求中包含有效的站点ID参数名已更新为`uniacid`
### 2. 健康检查返回503错误
**原因**AI服务配置不完整或服务异常
**解决方法**检查插件配置和Dify API服务状态
### 3. 接口返回401错误
**原因**Dify API密钥无效或过期
**解决方法**重新获取有效的API密钥并更新插件配置
### 4. 接口返回500错误
**原因**后端服务器错误或Dify API服务异常
**解决方法**查看服务器日志检查Dify API服务状态
### 5. 机器人回复为空
**原因**Dify聊天机器人配置问题或请求参数错误
**解决方法**检查Dify机器人配置验证请求参数是否正确
### 6. 流式响应无法解析
**原因**客户端不支持SSE或解析方式错误
**解决方法**使用正确的方式解析Server-Sent Events格式参考前端示例代码
### 7. 会话ID无效
**原因**:会话已过期或不存在
**解决方法**创建新会话获取新的会话ID
### 8. 参数不匹配
**原因**:使用了旧版本的参数名称
**解决方法**:更新参数:`message``query``site_id``uniacid`
## 十、性能优化建议
1. **缓存配置**:可对`info`接口返回的配置信息进行客户端缓存
2. **连接复用**HTTP请求使用连接池减少建立连接的开销
3. **压缩传输**启用gzip压缩减少传输数据量
4. **分页加载**:历史记录使用分页加载,避免一次性加载大量数据
5. **CDN加速**静态资源使用CDN加速访问
6. **监控告警**:建立接口性能监控和告警机制
7. **数据清理**:定期清理过期和失败状态的垃圾数据
8. **索引优化**:为常用查询字段添加数据库索引
---
**文档更新时间**2025-12-10
**版本**v2.1
**兼容性**:向后兼容,推荐使用标准的`uniacid`参数