fix(addon/aikefu): 修复代码错误,缺少 think\facade\Db
This commit is contained in:
709
docs/api_kefu.md
709
docs/api_kefu.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## 一、接口说明
|
## 一、接口说明
|
||||||
|
|
||||||
本接口用于连接微信小程序与Dify聊天机器人,实现智能客服功能。所有接口都采用事件驱动架构,支持高并发和模块化扩展。
|
本接口用于连接微信小程序与Dify聊天机器人,实现智能客服功能。支持流式和非流式两种响应模式,具备完整的事务保护、数据一致性和状态管理功能。
|
||||||
|
|
||||||
## 二、配置说明
|
## 二、配置说明
|
||||||
|
|
||||||
@@ -30,14 +30,14 @@
|
|||||||
|
|
||||||
### 1. 系统健康检查
|
### 1. 系统健康检查
|
||||||
|
|
||||||
**接口地址**:`/api/kefu/health`
|
**接口地址**:/api/kefu/health
|
||||||
|
|
||||||
**请求方式**:GET
|
**请求方式**:POST
|
||||||
|
|
||||||
**请求参数**:
|
**请求参数**:
|
||||||
|
|
||||||
| 参数名 | 类型 | 必填 | 说明 |
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
| --- | --- | --- | --- |
|
| ------ | ---- | ---- | ---- |
|
||||||
| uniacid | int | 是 | 站点ID |
|
| uniacid | int | 是 | 站点ID |
|
||||||
| check_type | string | 否 | 检查类型:full(完整)、basic(基础)、ai_service(AI服务),默认full |
|
| check_type | string | 否 | 检查类型:full(完整)、basic(基础)、ai_service(AI服务),默认full |
|
||||||
|
|
||||||
@@ -46,56 +46,25 @@
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"message": "healthy",
|
"message": "success",
|
||||||
"data": {
|
"data": {
|
||||||
"status": "healthy",
|
"status": "healthy",
|
||||||
"check_id": "health_63a8f9c1234abcd",
|
"check_id": "health_56789",
|
||||||
"timestamp": "2023-12-25 10:30:45",
|
"timestamp": "2023-12-25 10:30:45",
|
||||||
"total_checks": 4,
|
"total_checks": 2,
|
||||||
"passed_checks": 4,
|
"passed_checks": 2,
|
||||||
"failed_checks": 0,
|
"failed_checks": 0,
|
||||||
"response_time_ms": 156.78,
|
"response_time_ms": 170,
|
||||||
"components": {
|
"components": {
|
||||||
|
"ai_service": {
|
||||||
|
"status": "healthy",
|
||||||
|
"message": "AI服务正常",
|
||||||
|
"response_time_ms": 150
|
||||||
|
},
|
||||||
"database": {
|
"database": {
|
||||||
"status": "healthy",
|
"status": "healthy",
|
||||||
"message": "数据库连接正常",
|
"message": "数据库连接正常",
|
||||||
"response_time_ms": 12.34,
|
"response_time_ms": 20
|
||||||
"details": {
|
|
||||||
"connection": "success",
|
|
||||||
"query_test": "passed"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ai_service_config": {
|
|
||||||
"status": "healthy",
|
|
||||||
"message": "AI服务配置正常",
|
|
||||||
"response_time_ms": 8.56,
|
|
||||||
"details": {
|
|
||||||
"configured": true,
|
|
||||||
"complete": true,
|
|
||||||
"enabled": true,
|
|
||||||
"base_url": "https://api.example.com"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ai_service_connection": {
|
|
||||||
"status": "healthy",
|
|
||||||
"message": "AI服务连接正常",
|
|
||||||
"response_time_ms": 45.67,
|
|
||||||
"details": {
|
|
||||||
"http_status": 200,
|
|
||||||
"url": "https://api.example.com"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"system_resources": {
|
|
||||||
"status": "healthy",
|
|
||||||
"message": "系统资源正常",
|
|
||||||
"response_time_ms": 2.34,
|
|
||||||
"details": {
|
|
||||||
"php_version": "8.1.0",
|
|
||||||
"memory_usage": "45.67 MB",
|
|
||||||
"memory_limit": "512.00 MB",
|
|
||||||
"memory_usage_percent": "8.92%",
|
|
||||||
"max_execution_time": "30s"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"warnings": [],
|
"warnings": [],
|
||||||
@@ -106,15 +75,15 @@
|
|||||||
|
|
||||||
### 2. 获取服务配置信息
|
### 2. 获取服务配置信息
|
||||||
|
|
||||||
**接口地址**:`/api/kefu/info`
|
**接口地址**:/api/kefu/info
|
||||||
|
|
||||||
**请求方式**:GET
|
**请求方式**:POST
|
||||||
|
|
||||||
**请求参数**:
|
**请求参数**:
|
||||||
|
|
||||||
| 参数名 | 类型 | 必填 | 说明 |
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
| --- | --- | --- | --- |
|
| ------ | ---- | ---- | ---- |
|
||||||
| uniacid | int | 否 | 站点ID |
|
| uniacid | int | 是 | 站点ID |
|
||||||
| member_id | int | 否 | 会员ID |
|
| member_id | int | 否 | 会员ID |
|
||||||
| token | string | 否 | 访问令牌 |
|
| token | string | 否 | 访问令牌 |
|
||||||
|
|
||||||
@@ -125,56 +94,8 @@
|
|||||||
"code": 0,
|
"code": 0,
|
||||||
"message": "success",
|
"message": "success",
|
||||||
"data": {
|
"data": {
|
||||||
"service_info": {
|
"enabled": true,
|
||||||
"name": "智能客服",
|
"status": "enabled"
|
||||||
"version": "1.0.0",
|
|
||||||
"enabled": true,
|
|
||||||
"status": "enabled"
|
|
||||||
},
|
|
||||||
"features": {
|
|
||||||
"chat": true,
|
|
||||||
"chat_stream": true,
|
|
||||||
"conversation_management": true,
|
|
||||||
"history_management": true
|
|
||||||
},
|
|
||||||
"limits": {
|
|
||||||
"max_message_length": 4000,
|
|
||||||
"max_conversation_history": 100,
|
|
||||||
"rate_limit": {
|
|
||||||
"requests_per_minute": 60,
|
|
||||||
"requests_per_hour": 1000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"endpoints": {
|
|
||||||
"chat": "/api/kefu/chat",
|
|
||||||
"chat_stream": "/api/kefu/chatStream",
|
|
||||||
"create_conversation": "/api/kefu/createConversation",
|
|
||||||
"get_history": "/api/kefu/getHistory",
|
|
||||||
"clear_conversation": "/api/kefu/clearConversation",
|
|
||||||
"health": "/api/kefu/health",
|
|
||||||
"info": "/api/kefu/info"
|
|
||||||
},
|
|
||||||
"api_config": {
|
|
||||||
"base_url": "https://api.dify.ai/v1",
|
|
||||||
"chat_endpoint": "/chat-messages",
|
|
||||||
"supports_streaming": true,
|
|
||||||
"authentication": "bearer_token"
|
|
||||||
},
|
|
||||||
"client_info": {
|
|
||||||
"user_agent": "Mozilla/5.0...",
|
|
||||||
"ip": "192.168.1.100",
|
|
||||||
"timestamp": 1703505845
|
|
||||||
},
|
|
||||||
"server_info": {
|
|
||||||
"php_version": "8.1.0",
|
|
||||||
"server_time": "2023-12-25 10:30:45",
|
|
||||||
"timezone": "Asia/Shanghai"
|
|
||||||
},
|
|
||||||
"user_stats": {
|
|
||||||
"can_use_service": true,
|
|
||||||
"member_id": 123,
|
|
||||||
"site_id": 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -183,29 +104,30 @@
|
|||||||
|
|
||||||
**接口地址**:`/api/kefu/chat`
|
**接口地址**:`/api/kefu/chat`
|
||||||
|
|
||||||
**请求方式**:POST
|
**请求方式**:POST 或 GET(流式模式支持EventSource)
|
||||||
|
|
||||||
**请求参数**:
|
**请求参数**:
|
||||||
|
|
||||||
| 参数名 | 类型 | 必填 | 说明 |
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
| --- | --- | --- | --- |
|
| ------ | ---- | ---- | ---- |
|
||||||
| uniacid | int | 是 | 站点ID |
|
| query | string | 是 | 用户输入的消息内容 |
|
||||||
| message | string | 是 | 用户输入的消息内容 |
|
|
||||||
| user_id | string | 否 | 用户ID,默认使用当前登录会员ID |
|
| user_id | string | 否 | 用户ID,默认使用当前登录会员ID |
|
||||||
| conversation_id | string | 否 | 会话ID,第一次聊天可不传,系统会自动创建 |
|
| conversation_id | string | 否 | 会话ID,第一次聊天可不传,系统会自动创建 |
|
||||||
| stream | bool | 否 | 是否使用流式响应,默认false |
|
| stream | bool | 否 | 是否使用流式响应,默认false |
|
||||||
| member_id | int | 否 | 会员ID |
|
| response_mode | string | 否 | 响应模式:streaming(流式)、blocking(阻塞),默认streaming |
|
||||||
| token | string | 否 | 访问令牌 |
|
| uniacid | int | 是 | 站点ID |
|
||||||
|
|
||||||
**响应示例**:
|
**响应示例**:
|
||||||
|
|
||||||
|
#### 非流式响应(stream=false 或 response_mode=blocking)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"message": "success",
|
"message": "success",
|
||||||
"data": {
|
"data": {
|
||||||
"conversation_id": "conv_123456789",
|
"conversation_id": "conv_123456789",
|
||||||
"reply": "您好,我是智能客服,有什么可以帮助您的?",
|
"answer": "您好,我是智能客服,有什么可以帮助您的?",
|
||||||
"message_id": "msg_123456789",
|
"message_id": "msg_123456789",
|
||||||
"finish_reason": "stop",
|
"finish_reason": "stop",
|
||||||
"usage": {
|
"usage": {
|
||||||
@@ -217,110 +139,36 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. 智能客服流式聊天接口
|
#### 流式响应(stream=true 或 response_mode=streaming)
|
||||||
|
|
||||||
**接口地址**:`/api/kefu/chatStream`
|
|
||||||
|
|
||||||
**请求方式**:POST
|
|
||||||
|
|
||||||
**请求参数**:同 `/api/kefu/chat` 接口
|
|
||||||
|
|
||||||
**响应格式**:Server-Sent Events (SSE)
|
**响应格式**:Server-Sent Events (SSE)
|
||||||
|
|
||||||
**响应示例**:
|
**响应示例**:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// 开始事件
|
data: {"event":"message","answer":"您好","conversation_id":"conv_123456789","message_id":"msg_123456789"}
|
||||||
event: start
|
|
||||||
data: {
|
|
||||||
"id": "unique_id",
|
|
||||||
"event": "start",
|
|
||||||
"timestamp": 1703505845,
|
|
||||||
"data": {
|
|
||||||
"request_id": "stream_123",
|
|
||||||
"message": "开始处理请求"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 内容块事件
|
data: {"event":"message","answer":",我是智能客服,","conversation_id":"conv_123456789","message_id":"msg_123456789"}
|
||||||
event: message
|
|
||||||
data: {
|
|
||||||
"id": "unique_id",
|
|
||||||
"event": "message",
|
|
||||||
"timestamp": 1703505845,
|
|
||||||
"data": {
|
|
||||||
"content": "您",
|
|
||||||
"conversation_id": "conv_123",
|
|
||||||
"finished": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 完成事件
|
data: {"event":"message","answer":"有什么可以帮助您的?","conversation_id":"conv_123456789","message_id":"msg_123456789"}
|
||||||
event: complete
|
|
||||||
data: {
|
|
||||||
"id": "unique_id",
|
|
||||||
"event": "complete",
|
|
||||||
"timestamp": 1703505845,
|
|
||||||
"data": {
|
|
||||||
"conversation_id": "conv_123",
|
|
||||||
"message_id": "msg_456",
|
|
||||||
"usage": {},
|
|
||||||
"finish_reason": "stop"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 结束事件
|
data: {"event":"message_end","conversation_id":"conv_123456789","message_id":"msg_123456789"}
|
||||||
event: end
|
|
||||||
data: {
|
data: {"event":"done","data":{"conversation_id":"conv_123456789","message_id":"msg_123456789","content":"您好,我是智能客服,有什么可以帮助您的?"}}
|
||||||
"id": "unique_id",
|
|
||||||
"event": "end",
|
data: {"event":"close","data":{"conversation_id":"conv_123456789","message_id":"msg_123456789"}}
|
||||||
"timestamp": 1703505845,
|
|
||||||
"data": {
|
|
||||||
"request_id": "stream_123",
|
|
||||||
"status": "completed"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. 创建新会话
|
### 4. 获取会话历史
|
||||||
|
|
||||||
**接口地址**:`/api/kefu/createConversation`
|
**接口地址**:/api/kefu/getHistory
|
||||||
|
|
||||||
**请求方式**:POST
|
**请求方式**:POST
|
||||||
|
|
||||||
**请求参数**:
|
**请求参数**:
|
||||||
|
|
||||||
| 参数名 | 类型 | 必填 | 说明 |
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
| --- | --- | --- | --- |
|
| ------ | ---- | ---- | ---- |
|
||||||
| uniacid | int | 是 | 站点ID |
|
|
||||||
| user_id | string | 否 | 用户ID,默认使用当前登录会员ID |
|
|
||||||
| member_id | int | 否 | 会员ID |
|
|
||||||
| token | string | 否 | 访问令牌 |
|
|
||||||
|
|
||||||
**响应示例**:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 0,
|
|
||||||
"message": "success",
|
|
||||||
"data": {
|
|
||||||
"conversation_id": "conv_123456789",
|
|
||||||
"name": "智能客服会话",
|
|
||||||
"created_at": "2023-12-25 10:30:45"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. 获取会话历史
|
|
||||||
|
|
||||||
**接口地址**:`/api/kefu/getHistory`
|
|
||||||
|
|
||||||
**请求方式**:POST
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 必填 | 说明 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| uniacid | int | 是 | 站点ID |
|
| uniacid | int | 是 | 站点ID |
|
||||||
| conversation_id | string | 是 | 会话ID |
|
| conversation_id | string | 是 | 会话ID |
|
||||||
| user_id | string | 否 | 用户ID,默认使用当前登录会员ID |
|
| user_id | string | 否 | 用户ID,默认使用当前登录会员ID |
|
||||||
@@ -338,26 +186,28 @@ data: {
|
|||||||
"data": {
|
"data": {
|
||||||
"messages": [
|
"messages": [
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": "msg_123456789",
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": "你好",
|
"content": "您好",
|
||||||
"create_time": "2023-12-25 10:30:45"
|
"create_time": 1703505845
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": "msg_123456790",
|
||||||
"role": "assistant",
|
"role": "assistant",
|
||||||
"content": "您好,我是智能客服,有什么可以帮助您的?",
|
"content": "您好,我是智能客服,有什么可以帮助您的?",
|
||||||
"create_time": "2023-12-25 10:30:46"
|
"create_time": 1703505846
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"total": 2,
|
"total": 2,
|
||||||
"limit": 20,
|
"page_info": {
|
||||||
"offset": 0
|
"limit": 20,
|
||||||
|
"offset": 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7. 清除会话历史
|
### 5. 清除会话历史
|
||||||
|
|
||||||
**接口地址**:`/api/kefu/clearConversation`
|
**接口地址**:`/api/kefu/clearConversation`
|
||||||
|
|
||||||
@@ -366,10 +216,10 @@ data: {
|
|||||||
**请求参数**:
|
**请求参数**:
|
||||||
|
|
||||||
| 参数名 | 类型 | 必填 | 说明 |
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
| --- | --- | --- | --- |
|
| ------ | ---- | ---- | ---- |
|
||||||
| uniacid | int | 是 | 站点ID |
|
| uniacid | int | 是 | 站点ID |
|
||||||
| conversation_id | string | 否 | 会话ID(与user_id二选一) |
|
| conversation_id | string | 否(与user_id二选一) | 会话ID(与user_id二选一) |
|
||||||
| user_id | string | 否 | 用户ID,用于清除该用户所有会话(与conversation_id二选一) |
|
| user_id | string | 否(与conversation_id二选一) | 用户ID,默认使用当前登录会员ID(与conversation_id二选一) |
|
||||||
| member_id | int | 否 | 会员ID |
|
| member_id | int | 否 | 会员ID |
|
||||||
| token | string | 否 | 访问令牌 |
|
| token | string | 否 | 访问令牌 |
|
||||||
|
|
||||||
@@ -379,65 +229,262 @@ data: {
|
|||||||
{
|
{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"message": "success",
|
"message": "success",
|
||||||
"data": {
|
"data": {}
|
||||||
"deleted_messages": 15,
|
|
||||||
"deleted_conversations": 3
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 四、前端调用示例
|
## 四、前端调用示例
|
||||||
|
|
||||||
### Uniapp调用示例
|
### 1. 非流式聊天(Fetch API)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// 引入请求封装(根据项目实际情况调整)
|
// 非流式聊天
|
||||||
import { request } from '@/utils/request';
|
async function chatWithAI(message, conversationId = '') {
|
||||||
|
|
||||||
// 1. 获取服务配置信息
|
|
||||||
async function getAIInfo() {
|
|
||||||
try {
|
try {
|
||||||
const res = await request({
|
const formData = new FormData();
|
||||||
url: '/api/kefu/info',
|
formData.append('query', message);
|
||||||
method: 'GET',
|
formData.append('uniacid', '1');
|
||||||
data: {
|
formData.append('stream', 'false');
|
||||||
uniacid: 1
|
formData.append('response_mode', 'blocking');
|
||||||
}
|
|
||||||
|
if (conversationId) {
|
||||||
|
formData.append('conversation_id', conversationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/api/kefu/chat', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.code === 0) {
|
if (!response.ok) {
|
||||||
console.log('AI服务状态:', res.data.service_info);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
console.log('可用功能:', res.data.features);
|
}
|
||||||
return res.data;
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.code === 0) {
|
||||||
|
return result.data;
|
||||||
} else {
|
} else {
|
||||||
console.error('获取配置失败:', res.message);
|
console.error('聊天失败:', result.message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取配置请求失败:', error);
|
console.error('聊天请求失败:', error);
|
||||||
return null;
|
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()}`;
|
||||||
|
|
||||||
// 2. 智能客服聊天(普通模式)
|
|
||||||
async function chatWithAI(message, conversationId = '') {
|
|
||||||
try {
|
try {
|
||||||
const res = await request({
|
const eventSource = new EventSource(url);
|
||||||
url: '/api/kefu/chat',
|
window.currentEventSource = eventSource;
|
||||||
method: 'POST',
|
|
||||||
data: {
|
let aiMessage = '';
|
||||||
uniacid: 1,
|
|
||||||
message: message,
|
// 监听消息事件
|
||||||
conversation_id: conversationId
|
eventSource.addEventListener('message', (event) => {
|
||||||
// user_id: 'your-user-id', // 可选
|
try {
|
||||||
// stream: false // 可选
|
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.code === 0) {
|
// 监听完成事件
|
||||||
return res.data;
|
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 {
|
} else {
|
||||||
console.error('聊天失败:', res.message);
|
console.error('聊天失败:', res[1].data.message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -446,74 +493,10 @@ async function chatWithAI(message, conversationId = '') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 智能客服聊天(流式模式)
|
// Uniapp 获取历史记录
|
||||||
async function chatWithAIStream(message, conversationId = '', onMessage, onComplete, onError) {
|
|
||||||
try {
|
|
||||||
const response = await uni.request({
|
|
||||||
url: '/api/kefu/chatStream',
|
|
||||||
method: 'POST',
|
|
||||||
data: {
|
|
||||||
uniacid: 1,
|
|
||||||
message: message,
|
|
||||||
conversation_id: conversationId
|
|
||||||
},
|
|
||||||
responseType: 'text'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 处理流式响应
|
|
||||||
if (response.statusCode === 200) {
|
|
||||||
const lines = response.data.split('\n');
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.startsWith('data: ')) {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(line.substring(6));
|
|
||||||
if (data.event === 'message' && onMessage) {
|
|
||||||
onMessage(data.data);
|
|
||||||
} else if (data.event === 'complete' && onComplete) {
|
|
||||||
onComplete(data.data);
|
|
||||||
} else if (data.event === 'error' && onError) {
|
|
||||||
onError(data.data);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('解析流式数据失败:', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('流式聊天请求失败:', error);
|
|
||||||
if (onError) onError({ error: error.message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 创建新会话
|
|
||||||
async function createNewConversation() {
|
|
||||||
try {
|
|
||||||
const res = await request({
|
|
||||||
url: '/api/kefu/createConversation',
|
|
||||||
method: 'POST',
|
|
||||||
data: {
|
|
||||||
uniacid: 1
|
|
||||||
// user_id: 'your-user-id', // 可选
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.code === 0) {
|
|
||||||
return res.data.conversation_id;
|
|
||||||
} else {
|
|
||||||
console.error('创建会话失败:', res.message);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建会话请求失败:', error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 获取会话历史
|
|
||||||
async function getChatHistory(conversationId, limit = 20, offset = 0) {
|
async function getChatHistory(conversationId, limit = 20, offset = 0) {
|
||||||
try {
|
try {
|
||||||
const res = await request({
|
const res = await uni.request({
|
||||||
url: '/api/kefu/getHistory',
|
url: '/api/kefu/getHistory',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
@@ -521,14 +504,13 @@ async function getChatHistory(conversationId, limit = 20, offset = 0) {
|
|||||||
conversation_id: conversationId,
|
conversation_id: conversationId,
|
||||||
limit: limit,
|
limit: limit,
|
||||||
offset: offset
|
offset: offset
|
||||||
// user_id: 'your-user-id', // 可选
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.code === 0) {
|
if (res[1].data.code === 0) {
|
||||||
return res.data;
|
return res[1].data.data;
|
||||||
} else {
|
} else {
|
||||||
console.error('获取历史记录失败:', res.message);
|
console.error('获取历史记录失败:', res[1].data.message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -537,47 +519,22 @@ async function getChatHistory(conversationId, limit = 20, offset = 0) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 清除会话历史
|
// Uniapp 健康检查
|
||||||
async function clearConversation(conversationId = '', userId = '') {
|
|
||||||
try {
|
|
||||||
const res = await request({
|
|
||||||
url: '/api/kefu/clearConversation',
|
|
||||||
method: 'POST',
|
|
||||||
data: {
|
|
||||||
uniacid: 1,
|
|
||||||
conversation_id: conversationId,
|
|
||||||
user_id: userId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.code === 0) {
|
|
||||||
return res.data;
|
|
||||||
} else {
|
|
||||||
console.error('清除会话失败:', res.message);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('清除会话请求失败:', error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. 健康检查
|
|
||||||
async function checkHealth(checkType = 'full') {
|
async function checkHealth(checkType = 'full') {
|
||||||
try {
|
try {
|
||||||
const res = await request({
|
const res = await uni.request({
|
||||||
url: '/api/kefu/health',
|
url: '/api/kefu/health',
|
||||||
method: 'GET',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
uniacid: 1,
|
uniacid: 1,
|
||||||
check_type: checkType
|
check_type: checkType
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.code === 0) {
|
if (res[1].data.code === 0) {
|
||||||
return res.data;
|
return res[1].data.data;
|
||||||
} else {
|
} else {
|
||||||
console.error('健康检查失败:', res.message);
|
console.error('健康检查失败:', res[1].data.message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -590,40 +547,65 @@ async function checkHealth(checkType = 'full') {
|
|||||||
## 五、使用流程
|
## 五、使用流程
|
||||||
|
|
||||||
1. **初始化检查**:小程序端启动时,调用`health`和`info`接口检查服务状态
|
1. **初始化检查**:小程序端启动时,调用`health`和`info`接口检查服务状态
|
||||||
2. **创建会话**:进入客服页面时,调用`createConversation`接口创建新会话,或使用本地存储的会话ID
|
2. **获取会话**:进入客服页面时,系统会自动创建或使用已有会话
|
||||||
3. **发送消息**:用户输入消息后,调用`chat`或`chatStream`接口发送消息,获取机器人回复
|
3. **发送消息**:用户输入消息后,调用`chat`接口发送消息,获取机器人回复
|
||||||
4. **显示消息**:将用户消息和机器人回复显示在聊天界面
|
4. **显示消息**:将用户消息和机器人回复显示在聊天界面
|
||||||
5. **加载历史记录**:需要时调用`getHistory`接口加载历史消息
|
5. **加载历史记录**:需要时调用`getHistory`接口加载历史消息
|
||||||
6. **维护会话**:保持会话ID,用于后续消息交流
|
6. **维护会话**:保持会话ID,用于后续消息交流
|
||||||
7. **清理数据**:根据用户需求调用`clearConversation`接口清理历史数据
|
7. **清理数据**:根据用户需求调用`clearConversation`接口清理历史数据
|
||||||
|
|
||||||
## 六、注意事项
|
## 六、数据存储机制
|
||||||
|
|
||||||
|
### 1. 存储状态
|
||||||
|
|
||||||
|
| 状态值 | 含义 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| `streaming` | 流式中 | 正在进行流式输出的临时数据 |
|
||||||
|
| `completed` | 已完成 | 正常完成的对话数据 |
|
||||||
|
| `failed` | 失败 | 流式过程中发生失败的数据 |
|
||||||
|
|
||||||
|
### 2. 事务保护
|
||||||
|
|
||||||
|
- **流式对话**:使用临时会话ID机制,失败时自动回滚
|
||||||
|
- **非流式对话**:完整的事务保护,确保数据一致性
|
||||||
|
- **重复检查**:避免重复存储相同消息
|
||||||
|
|
||||||
|
### 3. 数据一致性
|
||||||
|
|
||||||
|
- 用户消息和助手消息通过`conversation_id`关联
|
||||||
|
- 会话状态实时更新,便于管理和监控
|
||||||
|
- 详细的日志记录,便于问题排查
|
||||||
|
|
||||||
|
## 七、注意事项
|
||||||
|
|
||||||
1. **必填参数**:所有接口都需要`uniacid`(站点ID)参数
|
1. **必填参数**:所有接口都需要`uniacid`(站点ID)参数
|
||||||
2. **事件驱动**:后端采用事件驱动架构,所有业务逻辑通过事件处理器执行
|
2. **参数更新**:新版本使用`query`替代`message`,使用`uniacid`替代`site_id`
|
||||||
3. **安全性**:请确保Dify API密钥的安全性,不要泄露给前端
|
3. **事件驱动**:后端采用事件驱动架构,所有业务逻辑通过事件处理器执行
|
||||||
4. **用户标识**:建议对用户ID进行加密处理,避免直接使用敏感信息
|
4. **安全性**:请确保Dify API密钥的安全性,不要泄露给前端
|
||||||
5. **流式支持**:推荐使用`chatStream`接口获得更好的用户体验
|
5. **用户标识**:建议对用户ID进行加密处理,避免直接使用敏感信息
|
||||||
6. **会话管理**:建议实现会话管理机制,定期清理过期会话
|
6. **流式体验**:推荐使用`stream: true`参数获得更好的用户体验
|
||||||
7. **频率限制**:建议添加请求频率限制,防止恶意请求
|
7. **会话管理**:建议实现会话管理机制,定期清理过期会话
|
||||||
8. **生产环境**:在生产环境中,建议关闭DEBUG模式
|
8. **频率限制**:建议添加请求频率限制,防止恶意请求
|
||||||
|
9. **生产环境**:在生产环境中,建议关闭DEBUG模式
|
||||||
|
10. **数据完整性**:系统已内置事务保护和重复检查机制
|
||||||
|
|
||||||
## 七、测试建议
|
## 八、测试建议
|
||||||
|
|
||||||
1. **基础检查**:首先调用`health`接口检查系统状态
|
1. **基础检查**:首先调用`health`接口检查系统状态
|
||||||
2. **配置验证**:调用`info`接口验证配置信息
|
2. **配置验证**:调用`info`接口验证配置信息
|
||||||
3. **接口测试**:使用Postman或类似工具测试各个API接口
|
3. **接口测试**:使用Postman或类似工具测试各个API接口
|
||||||
4. **流式测试**:测试`chatStream`接口的流式响应
|
4. **流式测试**:测试`chat`接口的流式响应功能
|
||||||
5. **完整流程**:在小程序端集成并测试完整流程
|
5. **完整流程**:在小程序端集成并测试完整流程
|
||||||
6. **边界测试**:模拟不同场景下的用户输入,测试机器人回复效果
|
6. **边界测试**:模拟不同场景下的用户输入,测试机器人回复效果
|
||||||
7. **压力测试**:测试接口在高并发情况下的表现
|
7. **压力测试**:测试接口在高并发情况下的表现
|
||||||
|
8. **数据验证**:检查非流式对话的存储完整性和一致性
|
||||||
|
|
||||||
## 八、常见问题
|
## 九、常见问题
|
||||||
|
|
||||||
### 1. 接口返回400错误
|
### 1. 接口返回400错误
|
||||||
|
|
||||||
**原因**:缺少必填参数`uniacid`或参数格式错误
|
**原因**:缺少必填参数`uniacid`或参数格式错误
|
||||||
**解决方法**:确保请求中包含有效的站点ID
|
**解决方法**:确保请求中包含有效的站点ID,参数名已更新为`uniacid`
|
||||||
|
|
||||||
### 2. 健康检查返回503错误
|
### 2. 健康检查返回503错误
|
||||||
|
|
||||||
@@ -648,14 +630,19 @@ async function checkHealth(checkType = 'full') {
|
|||||||
### 6. 流式响应无法解析
|
### 6. 流式响应无法解析
|
||||||
|
|
||||||
**原因**:客户端不支持SSE或解析方式错误
|
**原因**:客户端不支持SSE或解析方式错误
|
||||||
**解决方法**:使用正确的方式解析Server-Sent Events格式
|
**解决方法**:使用正确的方式解析Server-Sent Events格式,参考前端示例代码
|
||||||
|
|
||||||
### 7. 会话ID无效
|
### 7. 会话ID无效
|
||||||
|
|
||||||
**原因**:会话已过期或不存在
|
**原因**:会话已过期或不存在
|
||||||
**解决方法**:创建新会话,获取新的会话ID
|
**解决方法**:创建新会话,获取新的会话ID
|
||||||
|
|
||||||
## 九、性能优化建议
|
### 8. 参数不匹配
|
||||||
|
|
||||||
|
**原因**:使用了旧版本的参数名称
|
||||||
|
**解决方法**:更新参数:`message` → `query`,`site_id` → `uniacid`
|
||||||
|
|
||||||
|
## 十、性能优化建议
|
||||||
|
|
||||||
1. **缓存配置**:可对`info`接口返回的配置信息进行客户端缓存
|
1. **缓存配置**:可对`info`接口返回的配置信息进行客户端缓存
|
||||||
2. **连接复用**:HTTP请求使用连接池,减少建立连接的开销
|
2. **连接复用**:HTTP请求使用连接池,减少建立连接的开销
|
||||||
@@ -663,3 +650,11 @@ async function checkHealth(checkType = 'full') {
|
|||||||
4. **分页加载**:历史记录使用分页加载,避免一次性加载大量数据
|
4. **分页加载**:历史记录使用分页加载,避免一次性加载大量数据
|
||||||
5. **CDN加速**:静态资源使用CDN加速访问
|
5. **CDN加速**:静态资源使用CDN加速访问
|
||||||
6. **监控告警**:建立接口性能监控和告警机制
|
6. **监控告警**:建立接口性能监控和告警机制
|
||||||
|
7. **数据清理**:定期清理过期和失败状态的垃圾数据
|
||||||
|
8. **索引优化**:为常用查询字段添加数据库索引
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档更新时间**:2025-12-10
|
||||||
|
**版本**:v2.1
|
||||||
|
**兼容性**:向后兼容,推荐使用标准的`uniacid`参数
|
||||||
@@ -6,6 +6,7 @@ use addon\aikefu\model\Config as KefuConfigModel;
|
|||||||
use addon\aikefu\model\Conversation as KefuConversationModel;
|
use addon\aikefu\model\Conversation as KefuConversationModel;
|
||||||
use addon\aikefu\model\Message as KefuMessageModel;
|
use addon\aikefu\model\Message as KefuMessageModel;
|
||||||
use app\api\controller\BaseApi;
|
use app\api\controller\BaseApi;
|
||||||
|
use think\facade\Db as Db;
|
||||||
|
|
||||||
|
|
||||||
class Kefu extends BaseApi
|
class Kefu extends BaseApi
|
||||||
|
|||||||
Reference in New Issue
Block a user