feat(addon/aikefu): 新增AI智能客服插件

This commit is contained in:
2025-12-06 10:09:08 +08:00
parent 8da4563435
commit 8ceb252d79
17 changed files with 2043 additions and 0 deletions

View File

@@ -0,0 +1,236 @@
<?php
namespace addon\aikefu\api\controller;
use addon\aikefu\model\Config as KefuConfigModel;
use addon\aikefu\model\Conversation as KefuConversationModel;
use addon\aikefu\model\Message as KefuMessageModel;
use app\api\controller\BaseApi;
use extend\api\HttpClient;
class Kefu extends BaseApi
{
/**
* 智能客服聊天接口
* @return \think\response\Json
*/
public function chat()
{
// 获取请求参数
$message = $this->params['message'] ?? '';
$user_id = $this->params['user_id'] ?? $this->member_id;
$conversation_id = $this->params['conversation_id'] ?? '';
$stream = $this->params['stream'] ?? false;
// 验证参数
if (empty($message)) {
return $this->response($this->error('请输入消息内容'));
}
try {
// 获取智能客服配置
$kefu_config_model = new KefuConfigModel();
$config_info = $kefu_config_model->getConfig($this->site_id);
if (empty($config_info['data']['value']) || $config_info['data']['value']['status'] != 1) {
return $this->response($this->error('智能客服暂未启用'));
}
$config = $config_info['data']['value'];
$apiKey = $config['api_key'];
$baseUrl = $config['base_url'];
$chatEndpoint = $config['chat_endpoint'];
// 构建请求数据
$requestData = [
'inputs' => [],
'query' => $message,
'response_mode' => $stream ? 'streaming' : 'blocking',
'user' => $user_id,
];
// 如果有会话ID添加到请求中
if (!empty($conversation_id)) {
$requestData['conversation_id'] = $conversation_id;
}
// 构建请求头
$headers = [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
];
// 发送请求到Dify API
$url = $baseUrl . $chatEndpoint;
$response = HttpClient::http($url, 'POST', json_encode($requestData), $headers);
// 解析响应
$result = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return $this->response($this->error('解析响应失败'));
}
// 保存消息记录
$kefu_message_model = new KefuMessageModel();
$kefu_conversation_model = new KefuConversationModel();
// 保存用户消息
$kefu_message_model->addMessage([
'site_id' => $this->site_id,
'user_id' => $user_id,
'conversation_id' => $result['conversation_id'] ?? $conversation_id,
'message_id' => $result['message_id'] ?? '',
'role' => 'user',
'content' => $message,
]);
// 保存机器人回复
$kefu_message_model->addMessage([
'site_id' => $this->site_id,
'user_id' => $user_id,
'conversation_id' => $result['conversation_id'] ?? $conversation_id,
'message_id' => $result['id'] ?? '',
'role' => 'assistant',
'content' => $result['answer'] ?? '',
]);
// 更新会话状态或创建新会话
$conversation_info = $kefu_conversation_model->getConversationInfo([
['site_id', '=', $this->site_id],
['conversation_id', '=', $result['conversation_id'] ?? $conversation_id],
]);
if (empty($conversation_info['data'])) {
// 创建新会话
$kefu_conversation_model->addConversation([
'site_id' => $this->site_id,
'user_id' => $user_id,
'conversation_id' => $result['conversation_id'] ?? '',
'name' => '智能客服会话',
]);
} else {
// 更新会话状态
$kefu_conversation_model->updateConversation([
'status' => 1,
], [
['id', '=', $conversation_info['data']['id']],
]);
}
// 返回成功响应
return $this->response($this->success([
'conversation_id' => $result['conversation_id'] ?? '',
'reply' => $result['answer'] ?? '',
'message_id' => $result['message_id'] ?? '',
'finish_reason' => $result['finish_reason'] ?? '',
'usage' => $result['usage'] ?? [],
]));
} catch (\Exception $e) {
return $this->response($this->error('请求失败:' . $e->getMessage()));
}
}
/**
* 获取会话历史
* @return \think\response\Json
*/
public function getHistory()
{
// 获取请求参数
$conversation_id = $this->params['conversation_id'] ?? '';
$user_id = $this->params['user_id'] ?? $this->member_id;
$limit = $this->params['limit'] ?? 20;
$offset = $this->params['offset'] ?? 0;
// 验证参数
if (empty($conversation_id)) {
return $this->response($this->error('会话ID不能为空'));
}
try {
// 获取会话历史记录
$kefu_message_model = new KefuMessageModel();
$message_list = $kefu_message_model->getMessageList([
['site_id', '=', $this->site_id],
['user_id', '=', $user_id],
['conversation_id', '=', $conversation_id],
], 'id, role, content, create_time', 'create_time asc', $limit, $offset);
// 返回成功响应
return $this->response($this->success([
'messages' => $message_list['data'] ?? [],
'total' => $message_list['total'] ?? 0,
'limit' => $limit,
'offset' => $offset,
]));
} catch (\Exception $e) {
return $this->response($this->error('请求失败:' . $e->getMessage()));
}
}
/**
* 创建新会话
* @return \think\response\Json
*/
public function createConversation()
{
// 获取请求参数
$user_id = $this->params['user_id'] ?? $this->member_id;
try {
// 获取智能客服配置
$kefu_config_model = new KefuConfigModel();
$config_info = $kefu_config_model->getConfig($this->site_id);
if (empty($config_info['data']['value']) || $config_info['data']['value']['status'] != 1) {
return $this->response($this->error('智能客服暂未启用'));
}
$config = $config_info['data']['value'];
$apiKey = $config['api_key'];
$baseUrl = $config['base_url'];
// 构建请求数据
$requestData = [
'name' => '智能客服会话',
'user' => $user_id,
];
// 构建请求头
$headers = [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
];
// 发送请求到Dify API
$url = $baseUrl . '/conversations';
$response = HttpClient::http($url, 'POST', json_encode($requestData), $headers);
// 解析响应
$result = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return $this->response($this->error('解析响应失败'));
}
// 保存会话记录
$kefu_conversation_model = new KefuConversationModel();
$kefu_conversation_model->addConversation([
'site_id' => $this->site_id,
'user_id' => $user_id,
'conversation_id' => $result['id'] ?? '',
'name' => $result['name'] ?? '智能客服会话',
]);
// 返回成功响应
return $this->response($this->success([
'conversation_id' => $result['id'] ?? '',
'name' => $result['name'] ?? '',
'created_at' => $result['created_at'] ?? '',
]));
} catch (\Exception $e) {
return $this->response($this->error('请求失败:' . $e->getMessage()));
}
}
}

View File

@@ -0,0 +1,15 @@
<?php
return [
'name' => 'aikefu',
'title' => '智能客服',
'description' => '基于Dify的智能客服系统',
'author' => 'admin',
'version' => '1.0.0',
'scene' => 'web',
'state' => 0,
'category' => 'business',
'need_install' => 1,
'need_cache' => 1,
'hooks' => [],
];

View File

@@ -0,0 +1,36 @@
-- 智能客服插件安装脚本
-- 1. 智能客服插件使用系统配置表存储配置信息,无需创建独立数据表
-- 2. 会话和消息数据存储在独立数据表中
-- 创建智能客服会话表
CREATE TABLE IF NOT EXISTS `lucky_aikefu_conversation` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`site_id` int(11) NOT NULL COMMENT '站点ID',
`user_id` varchar(50) NOT NULL COMMENT '用户ID',
`conversation_id` varchar(100) NOT NULL COMMENT 'Dify会话ID',
`name` varchar(255) NOT NULL COMMENT '会话名称',
`status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态1活跃0结束',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `site_id` (`site_id`),
KEY `user_id` (`user_id`),
KEY `conversation_id` (`conversation_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='智能客服会话表';
-- 创建智能客服消息表
CREATE TABLE IF NOT EXISTS `lucky_aikefu_message` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`site_id` int(11) NOT NULL COMMENT '站点ID',
`user_id` varchar(50) NOT NULL COMMENT '用户ID',
`conversation_id` varchar(100) NOT NULL COMMENT '会话ID',
`message_id` varchar(100) NOT NULL COMMENT '消息ID',
`role` varchar(20) NOT NULL COMMENT '角色user用户assistant助手',
`content` text NOT NULL COMMENT '消息内容',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `site_id` (`site_id`),
KEY `user_id` (`user_id`),
KEY `conversation_id` (`conversation_id`),
KEY `message_id` (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='智能客服消息表';

View File

@@ -0,0 +1,4 @@
-- 智能客服插件卸载脚本
-- 删除智能客服相关表(配置信息存储在系统配置表中,无需单独删除)
DROP TABLE IF EXISTS `lucky_aikefu_message`;
DROP TABLE IF EXISTS `lucky_aikefu_conversation`;

View File

@@ -0,0 +1,50 @@
<?php
namespace addon\aikefu\event;
use app\model\system\Addon as AddonModel;
/**
* 智能客服插件安装
*/
class Install
{
public function handle()
{
$addon_model = new AddonModel();
$info = $addon_model->getAddonInfo(['name' => 'aikefu']);
if (empty($info['data'])) {
// 插件未安装,执行安装逻辑
$addon_model->addAddon([
'name' => 'aikefu',
'title' => '智能客服',
'description' => '基于Dify的智能客服系统',
'author' => 'admin',
'version' => '1.0.0',
'scene' => 'web',
'state' => 0,
'category' => 'business',
'need_install' => 1,
'need_cache' => 1,
'create_time' => time(),
'update_time' => time()
]);
} else {
// 插件已存在,更新插件信息
$addon_model->updateAddon([
'title' => '智能客服',
'description' => '基于Dify的智能客服系统',
'author' => 'admin',
'version' => '1.0.0',
'scene' => 'web',
'category' => 'business',
'need_install' => 1,
'need_cache' => 1,
'update_time' => time()
], ['name' => 'aikefu']);
}
return success(1);
}
}

View File

@@ -0,0 +1,129 @@
<?php
namespace addon\aikefu\event;
use addon\aikefu\api\controller\Kefu as KefuApi;
/**
* 智能客服事件处理类
*/
class Kefu
{
/**
* 处理智能客服聊天事件
* @param array $data 事件数据
* @return array
*/
public function handleKefuChat($data)
{
try {
// 创建addon的KefuApi实例
$kefu_api = new KefuApi();
// 设置必要的属性
$kefu_api->site_id = $data['site_id'] ?? 0;
$kefu_api->member_id = $data['member_id'] ?? 0;
$kefu_api->token = $data['token'] ?? '';
$kefu_api->params = [
'message' => $data['message'] ?? '',
'user_id' => $data['user_id'] ?? '',
'conversation_id' => $data['conversation_id'] ?? '',
'stream' => $data['stream'] ?? false,
];
// 调用addon的chat方法
$response = $kefu_api->chat();
// 返回响应数据
return json_decode($response->getContent(), true);
} catch (\Exception $e) {
return [
'code' => -1,
'message' => '聊天失败:' . $e->getMessage(),
'data' => []
];
}
}
/**
* 处理智能客服创建会话事件
* @param array $data 事件数据
* @return array
*/
public function handleKefuCreateConversation($data)
{
try {
// 创建addon的KefuApi实例
$kefu_api = new KefuApi();
// 设置必要的属性
$kefu_api->site_id = $data['site_id'] ?? 0;
$kefu_api->member_id = $data['member_id'] ?? 0;
$kefu_api->token = $data['token'] ?? '';
$kefu_api->params = [
'user_id' => $data['user_id'] ?? '',
];
// 调用addon的createConversation方法
$response = $kefu_api->createConversation();
// 返回响应数据
return json_decode($response->getContent(), true);
} catch (\Exception $e) {
return [
'code' => -1,
'message' => '创建会话失败:' . $e->getMessage(),
'data' => []
];
}
}
/**
* 处理智能客服获取历史消息事件
* @param array $data 事件数据
* @return array
*/
public function handleKefuGetHistory($data)
{
try {
// 创建addon的KefuApi实例
$kefu_api = new KefuApi();
// 设置必要的属性
$kefu_api->site_id = $data['site_id'] ?? 0;
$kefu_api->member_id = $data['member_id'] ?? 0;
$kefu_api->token = $data['token'] ?? '';
$kefu_api->params = [
'conversation_id' => $data['conversation_id'] ?? '',
'user_id' => $data['user_id'] ?? '',
'limit' => $data['limit'] ?? 20,
'offset' => $data['offset'] ?? 0,
];
// 调用addon的getHistory方法
$response = $kefu_api->getHistory();
// 返回响应数据
return json_decode($response->getContent(), true);
} catch (\Exception $e) {
return [
'code' => -1,
'message' => '获取历史消息失败:' . $e->getMessage(),
'data' => []
];
}
}
/**
* 事件监听映射
* @return array
*/
public function subscribe()
{
return [
'KefuChat' => 'handleKefuChat',
'KefuCreateConversation' => 'handleKefuCreateConversation',
'KefuGetHistory' => 'handleKefuGetHistory',
];
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace addon\aikefu\event;
use app\model\system\Addon as AddonModel;
/**
* 智能客服插件卸载
*/
class UnInstall
{
public function handle()
{
$addon_model = new AddonModel();
// 删除插件信息
$addon_model->deleteAddon(['name' => 'aikefu']);
return success(1);
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,53 @@
<?php
namespace addon\aikefu\model;
use app\model\system\Config as ConfigModel;
use app\model\BaseModel;
/**
* 智能客服配置
*/
class Config extends BaseModel
{
/**
* 设置智能客服配置
* @param array $data
* @param int $site_id
* @param string $app_module
* @return array
*/
public function setConfig($data, $site_id = 0, $app_module = 'shop')
{
$config = new ConfigModel();
$res = $config->setConfig($data, '智能客服配置', 1, [['site_id', '=', $site_id], ['app_module', '=', $app_module], ['config_key', '=', 'AIKEFU_CONFIG']]);
return $res;
}
/**
* 获取智能客服配置
* @param int $site_id
* @param string $app_module
* @return array
*/
public function getConfig($site_id = 0, $app_module = 'shop')
{
$config = new ConfigModel();
$res = $config->getConfig([['site_id', '=', $site_id], ['app_module', '=', $app_module], ['config_key', '=', 'AIKEFU_CONFIG']]);
return $res;
}
/**
* 获取智能客服配置信息
* @param array $condition
* @param string $field
* @return array
*/
public function getConfigInfo($condition = [], $field = '*')
{
// 兼容旧的调用方式
$site_id = $condition[0][1] ?? 0;
$res = $this->getConfig($site_id);
return $res;
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace addon\aikefu\model;
use app\model\BaseModel;
class Conversation extends BaseModel
{
/**
* 表名
* @var string
*/
protected $name = 'aikefu_conversation';
/**
* 主键
* @var string
*/
protected $pk = 'id';
/**
* 获取会话信息
* @param array $condition
* @param string $field
* @return array
*/
public function getConversationInfo($condition = [], $field = '*')
{
$info = $this->where($condition)->field($field)->find();
return empty($info) ? [] : $info->toArray();
}
/**
* 获取会话列表
* @param array $condition
* @param string $field
* @param string $order
* @param int $page
* @param int $limit
* @return array
*/
public function getConversationList($condition = [], $field = '*', $order = 'id desc', $page = 1, $limit = 10)
{
$list = $this->where($condition)->field($field)->order($order)->paginate([
'page' => $page,
'list_rows' => $limit
]);
return $this->pageFormat($list);
}
/**
* 添加会话
* @param array $data
* @return array
*/
public function addConversation($data)
{
$result = $this->insert($data);
return $this->success(['result' => $result]);
}
/**
* 更新会话
* @param array $data
* @param array $condition
* @return array
*/
public function updateConversation($data, $condition)
{
$result = $this->where($condition)->update($data);
return $this->success(['result' => $result]);
}
/**
* 删除会话
* @param array $condition
* @return array
*/
public function deleteConversation($condition)
{
$result = $this->where($condition)->delete();
return $this->success(['result' => $result]);
}
/**
* 获取用户会话列表
* @param int $site_id
* @param string $user_id
* @param int $page
* @param int $limit
* @return array
*/
public function getUserConversationList($site_id, $user_id, $page = 1, $limit = 10)
{
$condition = [
['site_id', '=', $site_id],
['user_id', '=', $user_id]
];
return $this->getConversationList($condition, '*', 'update_time desc', $page, $limit);
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace addon\aikefu\model;
use app\model\BaseModel;
class Message extends BaseModel
{
/**
* 表名
* @var string
*/
protected $name = 'aikefu_message';
/**
* 主键
* @var string
*/
protected $pk = 'id';
/**
* 获取消息信息
* @param array $condition
* @param string $field
* @return array
*/
public function getMessageInfo($condition = [], $field = '*')
{
$info = $this->where($condition)->field($field)->find();
return empty($info) ? [] : $info->toArray();
}
/**
* 获取消息列表
* @param array $condition
* @param string $field
* @param string $order
* @param int $page
* @param int $limit
* @return array
*/
public function getMessageList($condition = [], $field = '*', $order = 'id asc', $page = 1, $limit = 20)
{
$list = $this->where($condition)->field($field)->order($order)->paginate([
'page' => $page,
'list_rows' => $limit
]);
return $this->pageFormat($list);
}
/**
* 添加消息
* @param array $data
* @return array
*/
public function addMessage($data)
{
$result = $this->insert($data);
return $this->success(['result' => $result]);
}
/**
* 更新消息
* @param array $data
* @param array $condition
* @return array
*/
public function updateMessage($data, $condition)
{
$result = $this->where($condition)->update($data);
return $this->success(['result' => $result]);
}
/**
* 删除消息
* @param array $condition
* @return array
*/
public function deleteMessage($condition)
{
$result = $this->where($condition)->delete();
return $this->success(['result' => $result]);
}
/**
* 获取会话消息记录
* @param int $site_id
* @param string $conversation_id
* @param int $limit
* @param int $offset
* @return array
*/
public function getConversationMessages($site_id, $conversation_id, $limit = 20, $offset = 0)
{
$condition = [
['site_id', '=', $site_id],
['conversation_id', '=', $conversation_id]
];
return $this->getMessageList($condition, '*', 'create_time asc', ($offset / $limit) + 1, $limit);
}
/**
* 获取用户消息总数
* @param int $site_id
* @param string $user_id
* @return array
*/
public function getUserMessageCount($site_id, $user_id)
{
$count = $this->where([
['site_id', '=', $site_id],
['user_id', '=', $user_id]
])->count();
return $this->success(['count' => $count]);
}
}

View File

@@ -0,0 +1,222 @@
<?php
namespace addon\aikefu\shop\controller;
use addon\aikefu\model\Config as KefuConfigModel;
use addon\aikefu\model\Conversation as KefuConversationModel;
use addon\aikefu\model\Message as KefuMessageModel;
use app\shop\controller\BaseShop;
use think\facade\View;
class Kefu extends BaseShop
{
/**
* 智能客服配置页
* @return \think\response\View
*/
public function config()
{
$kefu_config_model = new KefuConfigModel();
$config_info = $kefu_config_model->getConfigInfo([['site_id', '=', $this->site_id]]);
View::assign('config_info', $config_info);
return View::fetch('kefu/config');
}
/**
* 保存智能客服配置
* @return \think\response\Json
*/
public function saveConfig()
{
$params = $this->request->post();
$kefu_config_model = new KefuConfigModel();
$data = [
'api_key' => $params['api_key'] ?? '',
'base_url' => $params['base_url'] ?? 'https://api.dify.ai/v1',
'chat_endpoint' => $params['chat_endpoint'] ?? '/chat-messages',
'status' => $params['status'] ?? 0,
];
$result = $kefu_config_model->setConfig($data, $this->site_id);
return $this->success($result);
}
/**
* 会话管理列表
* @return \think\response\View
*/
public function conversation()
{
return View::fetch('kefu/conversation');
}
/**
* 获取会话列表
* @return \think\response\Json
*/
public function getConversationList()
{
$params = $this->request->post();
$page = $params['page'] ?? 1;
$limit = $params['limit'] ?? 10;
$user_id = $params['user_id'] ?? '';
$status = $params['status'] ?? '';
$kefu_conversation_model = new KefuConversationModel();
$condition = [['site_id', '=', $this->site_id]];
if (!empty($user_id)) {
$condition[] = ['user_id', '=', $user_id];
}
if ($status !== '') {
$condition[] = ['status', '=', $status];
}
$conversation_list = $kefu_conversation_model->getConversationList($condition, '*', 'update_time desc', $page, $limit);
return $this->success($conversation_list);
}
/**
* 获取会话信息
* @return \think\response\Json
*/
public function getConversationInfo()
{
$params = $this->request->post();
$conversation_id = $params['conversation_id'] ?? '';
if (empty($conversation_id)) {
return $this->error('会话ID不能为空');
}
$kefu_conversation_model = new KefuConversationModel();
$conversation_info = $kefu_conversation_model->getConversationInfo([
['site_id', '=', $this->site_id],
['conversation_id', '=', $conversation_id]
]);
if (empty($conversation_info)) {
return $this->error('会话不存在');
}
return $this->success($conversation_info);
}
/**
* 结束会话
* @return \think\response\Json
*/
public function endConversation()
{
$params = $this->request->post();
$id = $params['id'] ?? '';
if (empty($id)) {
return $this->error('会话ID不能为空');
}
$kefu_conversation_model = new KefuConversationModel();
$result = $kefu_conversation_model->updateConversation(
['status' => 0],
[
['id', '=', $id],
['site_id', '=', $this->site_id]
]
);
return $this->success($result);
}
/**
* 删除会话
* @return \think\response\Json
*/
public function deleteConversation()
{
$params = $this->request->post();
$id = $params['id'] ?? '';
if (empty($id)) {
return $this->error('会话ID不能为空');
}
$kefu_conversation_model = new KefuConversationModel();
$kefu_message_model = new KefuMessageModel();
// 开启事务
$this->model->startTrans();
try {
// 删除会话关联的消息
$conversation_info = $kefu_conversation_model->getConversationInfo([
['id', '=', $id],
['site_id', '=', $this->site_id]
]);
if (!empty($conversation_info)) {
$kefu_message_model->deleteMessage([
['site_id', '=', $this->site_id],
['conversation_id', '=', $conversation_info['conversation_id']]
]);
}
// 删除会话
$result = $kefu_conversation_model->deleteConversation([
['id', '=', $id],
['site_id', '=', $this->site_id]
]);
// 提交事务
$this->model->commit();
return $this->success($result);
} catch (\Exception $e) {
// 回滚事务
$this->model->rollback();
return $this->error($e->getMessage());
}
}
/**
* 消息管理列表
* @return \think\response\View
*/
public function message()
{
$conversation_id = $this->request->param('conversation_id') ?? '';
View::assign('conversation_id', $conversation_id);
return View::fetch('kefu/message');
}
/**
* 获取消息列表
* @return \think\response\Json
*/
public function getMessageList()
{
$params = $this->request->post();
$page = $params['page'] ?? 1;
$limit = $params['limit'] ?? 50;
$conversation_id = $params['conversation_id'] ?? '';
if (empty($conversation_id)) {
return $this->error('会话ID不能为空');
}
$kefu_message_model = new KefuMessageModel();
$condition = [
['site_id', '=', $this->site_id],
['conversation_id', '=', $conversation_id]
];
$message_list = $kefu_message_model->getMessageList($condition, '*', 'create_time asc', $page, $limit);
return $this->success($message_list);
}
}

View File

@@ -0,0 +1,98 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能客服配置</title>
<link rel="stylesheet" href="/__STATIC__/admin/css/global.css">
<link rel="stylesheet" href="/__STATIC__/admin/css/form.css">
</head>
<body>
<div class="main">
<div class="container">
<div class="page-title">
<h2>智能客服配置</h2>
</div>
<div class="card">
<form id="kefuConfigForm">
<div class="form-group">
<label for="api_key">Dify API密钥</label>
<input type="text" name="api_key" id="api_key" value="{$config_info.api_key ?? ''}" placeholder="请输入Dify API密钥">
<div class="form-tips">
从Dify平台获取的API密钥用于调用Dify聊天机器人API。
<a href="https://dify.ai/" target="_blank">前往Dify平台</a>
</div>
</div>
<div class="form-group">
<label for="base_url">API基础地址</label>
<input type="text" name="base_url" id="base_url" value="{$config_info.base_url ?? 'https://api.dify.ai/v1'}" placeholder="请输入Dify API基础地址">
<div class="form-tips">
Dify API的基础地址默认为https://api.dify.ai/v1
</div>
</div>
<div class="form-group">
<label for="chat_endpoint">聊天接口端点</label>
<input type="text" name="chat_endpoint" id="chat_endpoint" value="{$config_info.chat_endpoint ?? '/chat-messages'}" placeholder="请输入聊天接口端点">
<div class="form-tips">
聊天接口的端点,默认为/chat-messages
</div>
</div>
<div class="form-group">
<label for="status">状态</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="status" value="1" {if isset($config_info.status) && $config_info.status == 1}checked{/if}>
<span>启用</span>
</label>
<label class="radio-item">
<input type="radio" name="status" value="0" {if !isset($config_info.status) || $config_info.status == 0}checked{/if}>
<span>禁用</span>
</label>
</div>
<div class="form-tips">
启用或禁用智能客服功能
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">保存配置</button>
<button type="reset" class="btn btn-default">重置</button>
</div>
</form>
</div>
</div>
</div>
<script src="/__STATIC__/admin/js/jquery.min.js"></script>
<script src="/__STATIC__/admin/js/layer/layer.js"></script>
<script>
$(function() {
$('#kefuConfigForm').on('submit', function(e) {
e.preventDefault();
var formData = $(this).serialize();
$.ajax({
url: '/shop/aikefu/kefu/saveConfig',
type: 'POST',
data: formData,
dataType: 'json',
success: function(res) {
if (res.code === 0) {
layer.msg('保存成功', {icon: 1});
} else {
layer.msg(res.message, {icon: 2});
}
},
error: function() {
layer.msg('请求失败,请稍后重试', {icon: 2});
}
});
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,245 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>会话管理</title>
<link rel="stylesheet" href="/__STATIC__/admin/css/global.css">
<link rel="stylesheet" href="/__STATIC__/admin/css/table.css">
</head>
<body>
<div class="main">
<div class="container">
<div class="page-title">
<h2>会话管理</h2>
</div>
<div class="card">
<div class="search-box">
<div class="search-item">
<label for="user_id">用户ID</label>
<input type="text" name="user_id" id="user_id" placeholder="请输入用户ID">
</div>
<div class="search-item">
<label for="status">状态</label>
<select name="status" id="status">
<option value="">全部</option>
<option value="1">活跃</option>
<option value="0">已结束</option>
</select>
</div>
<div class="search-item">
<button type="button" class="btn btn-primary" id="searchBtn">搜索</button>
<button type="button" class="btn btn-default" id="resetBtn">重置</button>
</div>
</div>
<div class="table-container">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>会话ID</th>
<th>用户ID</th>
<th>会话名称</th>
<th>状态</th>
<th>创建时间</th>
<th>更新时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="conversationList">
<!-- 会话列表将通过JavaScript动态加载 -->
</tbody>
</table>
</div>
<div class="pagination" id="pagination">
<!-- 分页将通过JavaScript动态加载 -->
</div>
</div>
</div>
</div>
<script src="/__STATIC__/admin/js/jquery.min.js"></script>
<script src="/__STATIC__/admin/js/layer/layer.js"></script>
<script>
$(function() {
// 分页参数
var page = 1;
var limit = 10;
var total = 0;
// 加载会话列表
function loadConversationList() {
var user_id = $('#user_id').val();
var status = $('#status').val();
$.ajax({
url: '/shop/aikefu/kefu/getConversationList',
type: 'POST',
data: {
page: page,
limit: limit,
user_id: user_id,
status: status
},
dataType: 'json',
success: function(res) {
if (res.code === 0) {
var list = res.data.list;
total = res.data.total;
var html = '';
if (list.length > 0) {
list.forEach(function(item) {
html += '<tr>';
html += '<td>' + item.id + '</td>';
html += '<td>' + item.conversation_id + '</td>';
html += '<td>' + item.user_id + '</td>';
html += '<td>' + item.name + '</td>';
html += '<td>' + (item.status === 1 ? '<span class="status-active">活跃</span>' : '<span class="status-inactive">已结束</span>') + '</td>';
html += '<td>' + item.create_time + '</td>';
html += '<td>' + item.update_time + '</td>';
html += '<td>';
html += '<a href="javascript:;" class="btn btn-small btn-primary" onclick="viewMessages(' + item.id + ', \'' + item.conversation_id + '\')">查看消息</a>';
html += '<a href="javascript:;" class="btn btn-small btn-warning" onclick="endConversation(' + item.id + ')">结束会话</a>';
html += '<a href="javascript:;" class="btn btn-small btn-danger" onclick="deleteConversation(' + item.id + ')">删除</a>';
html += '</td>';
html += '</tr>';
});
} else {
html += '<tr><td colspan="8" class="text-center">暂无会话数据</td></tr>';
}
$('#conversationList').html(html);
// 渲染分页
renderPagination();
} else {
layer.msg('加载失败:' + res.message, {icon: 2});
}
},
error: function() {
layer.msg('请求失败,请稍后重试', {icon: 2});
}
});
}
// 渲染分页
function renderPagination() {
var pages = Math.ceil(total / limit);
var html = '';
if (pages > 1) {
html += '<ul class="pagination-list">';
// 上一页
if (page > 1) {
html += '<li><a href="javascript:;" onclick="changePage(' + (page - 1) + ')">上一页</a></li>';
} else {
html += '<li class="disabled"><a href="javascript:;">上一页</a></li>';
}
// 页码
for (var i = 1; i <= pages; i++) {
if (i === page) {
html += '<li class="active"><a href="javascript:;">' + i + '</a></li>';
} else {
html += '<li><a href="javascript:;" onclick="changePage(' + i + ')">' + i + '</a></li>';
}
}
// 下一页
if (page < pages) {
html += '<li><a href="javascript:;" onclick="changePage(' + (page + 1) + ')">下一页</a></li>';
} else {
html += '<li class="disabled"><a href="javascript:;">下一页</a></li>';
}
html += '</ul>';
}
$('#pagination').html(html);
}
// 切换页码
window.changePage = function(p) {
page = p;
loadConversationList();
}
// 查看消息
window.viewMessages = function(id, conversation_id) {
layer.open({
type: 2,
title: '消息记录',
content: '/shop/aikefu/kefu/message?conversation_id=' + conversation_id,
area: ['90%', '90%']
});
}
// 结束会话
window.endConversation = function(id) {
layer.confirm('确定要结束该会话吗?', function(index) {
$.ajax({
url: '/shop/aikefu/kefu/endConversation',
type: 'POST',
data: {id: id},
dataType: 'json',
success: function(res) {
if (res.code === 0) {
layer.msg('会话已结束', {icon: 1});
loadConversationList();
} else {
layer.msg('操作失败:' + res.message, {icon: 2});
}
},
error: function() {
layer.msg('请求失败,请稍后重试', {icon: 2});
}
});
layer.close(index);
});
}
// 删除会话
window.deleteConversation = function(id) {
layer.confirm('确定要删除该会话吗?删除后将无法恢复', function(index) {
$.ajax({
url: '/shop/aikefu/kefu/deleteConversation',
type: 'POST',
data: {id: id},
dataType: 'json',
success: function(res) {
if (res.code === 0) {
layer.msg('会话已删除', {icon: 1});
loadConversationList();
} else {
layer.msg('操作失败:' + res.message, {icon: 2});
}
},
error: function() {
layer.msg('请求失败,请稍后重试', {icon: 2});
}
});
layer.close(index);
});
}
// 搜索
$('#searchBtn').click(function() {
page = 1;
loadConversationList();
});
// 重置
$('#resetBtn').click(function() {
$('#user_id').val('');
$('#status').val('');
page = 1;
loadConversationList();
});
// 初始化加载
loadConversationList();
});
</script>
</body>
</html>

View File

@@ -0,0 +1,276 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>消息管理</title>
<link rel="stylesheet" href="/__STATIC__/admin/css/global.css">
<link rel="stylesheet" href="/__STATIC__/admin/css/table.css">
<style>
.message-list {
max-height: 600px;
overflow-y: auto;
padding: 20px;
background-color: #f5f7fa;
border-radius: 8px;
margin-bottom: 20px;
}
.message-item {
margin-bottom: 20px;
display: flex;
align-items: flex-start;
}
.message-item.user {
justify-content: flex-end;
}
.message-item.assistant {
justify-content: flex-start;
}
.message-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
margin: 0 10px;
}
.message-content {
max-width: 70%;
padding: 12px 16px;
border-radius: 18px;
word-wrap: break-word;
}
.message-item.user .message-content {
background-color: #409eff;
color: white;
border-bottom-right-radius: 4px;
}
.message-item.assistant .message-content {
background-color: white;
color: #333;
border-bottom-left-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.message-time {
font-size: 12px;
color: #909399;
margin-top: 5px;
text-align: center;
}
.message-role {
font-size: 12px;
color: #909399;
margin-bottom: 5px;
}
.conversation-info {
margin-bottom: 20px;
padding: 15px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.conversation-info h3 {
margin: 0 0 10px 0;
font-size: 16px;
color: #333;
}
.conversation-info p {
margin: 5px 0;
font-size: 14px;
color: #606266;
}
</style>
</head>
<body>
<div class="main">
<div class="container">
<div class="page-title">
<h2>消息管理</h2>
</div>
<div class="card">
<div id="conversationInfo" class="conversation-info">
<!-- 会话信息将通过JavaScript动态加载 -->
</div>
<div class="search-box">
<div class="search-item">
<label for="conversation_id">会话ID</label>
<input type="text" name="conversation_id" id="conversation_id" placeholder="请输入会话ID" value="{$conversation_id ?? ''}">
</div>
<div class="search-item">
<button type="button" class="btn btn-primary" id="searchBtn">搜索</button>
</div>
</div>
<div class="message-list" id="messageList">
<!-- 消息列表将通过JavaScript动态加载 -->
</div>
<div class="pagination" id="pagination">
<!-- 分页将通过JavaScript动态加载 -->
</div>
</div>
</div>
</div>
<script src="/__STATIC__/admin/js/jquery.min.js"></script>
<script src="/__STATIC__/admin/js/layer/layer.js"></script>
<script>
$(function() {
// 分页参数
var page = 1;
var limit = 50;
var total = 0;
var conversation_id = $('#conversation_id').val();
// 加载会话信息
function loadConversationInfo() {
if (!conversation_id) return;
$.ajax({
url: '/shop/aikefu/kefu/getConversationInfo',
type: 'POST',
data: {
conversation_id: conversation_id
},
dataType: 'json',
success: function(res) {
if (res.code === 0) {
var info = res.data;
var html = '<h3>会话详情</h3>';
html += '<p><strong>会话ID</strong>' + info.conversation_id + '</p>';
html += '<p><strong>用户ID</strong>' + info.user_id + '</p>';
html += '<p><strong>会话名称:</strong>' + info.name + '</p>';
html += '<p><strong>状态:</strong>' + (info.status === 1 ? '<span class="status-active">活跃</span>' : '<span class="status-inactive">已结束</span>') + '</p>';
html += '<p><strong>创建时间:</strong>' + info.create_time + '</p>';
html += '<p><strong>更新时间:</strong>' + info.update_time + '</p>';
$('#conversationInfo').html(html);
} else {
$('#conversationInfo').html('<h3>会话详情</h3><p>未找到会话信息</p>');
}
},
error: function() {
$('#conversationInfo').html('<h3>会话详情</h3><p>加载会话信息失败</p>');
}
});
}
// 加载消息列表
function loadMessageList() {
if (!conversation_id) {
$('#messageList').html('<div style="text-align: center; padding: 50px;">请输入会话ID进行搜索</div>');
return;
}
$.ajax({
url: '/shop/aikefu/kefu/getMessageList',
type: 'POST',
data: {
page: page,
limit: limit,
conversation_id: conversation_id
},
dataType: 'json',
success: function(res) {
if (res.code === 0) {
var list = res.data.list;
total = res.data.total;
var html = '';
if (list.length > 0) {
list.forEach(function(item) {
var role = item.role === 'user' ? '用户' : '机器人';
var roleClass = item.role === 'user' ? 'user' : 'assistant';
var avatar = item.role === 'user' ? '/__STATIC__/admin/img/user.png' : '/__STATIC__/admin/img/robot.png';
html += '<div class="message-item ' + roleClass + '">';
if (item.role === 'assistant') {
html += '<img src="' + avatar + '" class="message-avatar">';
}
html += '<div>';
html += '<div class="message-role">' + role + '</div>';
html += '<div class="message-content">' + item.content + '</div>';
html += '<div class="message-time">' + item.create_time + '</div>';
html += '</div>';
if (item.role === 'user') {
html += '<img src="' + avatar + '" class="message-avatar">';
}
html += '</div>';
});
} else {
html += '<div style="text-align: center; padding: 50px;">暂无消息记录</div>';
}
$('#messageList').html(html);
// 滚动到底部
$('#messageList').scrollTop($('#messageList')[0].scrollHeight);
// 渲染分页
renderPagination();
} else {
layer.msg('加载失败:' + res.message, {icon: 2});
}
},
error: function() {
layer.msg('请求失败,请稍后重试', {icon: 2});
}
});
}
// 渲染分页
function renderPagination() {
var pages = Math.ceil(total / limit);
var html = '';
if (pages > 1) {
html += '<ul class="pagination-list">';
// 上一页
if (page > 1) {
html += '<li><a href="javascript:;" onclick="changePage(' + (page - 1) + ')">上一页</a></li>';
} else {
html += '<li class="disabled"><a href="javascript:;">上一页</a></li>';
}
// 页码
for (var i = 1; i <= pages; i++) {
if (i === page) {
html += '<li class="active"><a href="javascript:;">' + i + '</a></li>';
} else {
html += '<li><a href="javascript:;" onclick="changePage(' + i + ')">' + i + '</a></li>';
}
}
// 下一页
if (page < pages) {
html += '<li><a href="javascript:;" onclick="changePage(' + (page + 1) + ')">下一页</a></li>';
} else {
html += '<li class="disabled"><a href="javascript:;">下一页</a></li>';
}
html += '</ul>';
}
$('#pagination').html(html);
}
// 切换页码
window.changePage = function(p) {
page = p;
loadMessageList();
}
// 搜索
$('#searchBtn').click(function() {
conversation_id = $('#conversation_id').val();
page = 1;
loadConversationInfo();
loadMessageList();
});
// 初始化加载
if (conversation_id) {
loadConversationInfo();
loadMessageList();
}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,172 @@
<?php
namespace app\api\controller;
use app\api\controller\BaseApi;
use think\facade\Event;
/**
* 智能客服API控制器
*/
class Kefu extends BaseApi
{
/**
* 智能客服聊天接口
* @return \think\response\Json
*/
public function chat()
{
// 获取请求参数
$message = $this->params['message'] ?? '';
$user_id = $this->params['user_id'] ?? $this->member_id;
$conversation_id = $this->params['conversation_id'] ?? '';
$stream = $this->params['stream'] ?? false;
// 验证参数
if (empty($message)) {
return $this->response($this->error('请输入消息内容'));
}
try {
// 准备事件数据
$event_data = [
'message' => $message,
'user_id' => $user_id,
'conversation_id' => $conversation_id,
'stream' => $stream,
'site_id' => $this->site_id,
'member_id' => $this->member_id,
'token' => $this->token,
];
// 触发智能客服聊天事件
$result = Event::trigger('KefuChat', $event_data);
// 处理事件结果
$response = [
'code' => 0,
'message' => 'success',
'data' => []
];
if (is_array($result) && !empty($result)) {
foreach ($result as $res) {
if (isset($res['code']) && $res['code'] < 0) {
$response = $res;
break;
}
if (isset($res['data'])) {
$response['data'] = array_merge($response['data'], $res['data']);
}
}
}
return $this->response($response);
} catch (\Exception $e) {
return $this->response($this->error('请求失败:' . $e->getMessage()));
}
}
/**
* 创建新会话
* @return \think\response\Json
*/
public function createConversation()
{
// 获取请求参数
$user_id = $this->params['user_id'] ?? $this->member_id;
try {
// 准备事件数据
$event_data = [
'user_id' => $user_id,
'site_id' => $this->site_id,
'member_id' => $this->member_id,
'token' => $this->token,
];
// 触发创建会话事件
$result = Event::trigger('KefuCreateConversation', $event_data);
// 处理事件结果
$response = [
'code' => 0,
'message' => 'success',
'data' => []
];
if (is_array($result) && !empty($result)) {
foreach ($result as $res) {
if (isset($res['code']) && $res['code'] < 0) {
$response = $res;
break;
}
if (isset($res['data'])) {
$response['data'] = array_merge($response['data'], $res['data']);
}
}
}
return $this->response($response);
} catch (\Exception $e) {
return $this->response($this->error('请求失败:' . $e->getMessage()));
}
}
/**
* 获取会话历史
* @return \think\response\Json
*/
public function getHistory()
{
// 获取请求参数
$conversation_id = $this->params['conversation_id'] ?? '';
$user_id = $this->params['user_id'] ?? $this->member_id;
$limit = $this->params['limit'] ?? 20;
$offset = $this->params['offset'] ?? 0;
// 验证参数
if (empty($conversation_id)) {
return $this->response($this->error('会话ID不能为空'));
}
try {
// 准备事件数据
$event_data = [
'conversation_id' => $conversation_id,
'user_id' => $user_id,
'limit' => $limit,
'offset' => $offset,
'site_id' => $this->site_id,
'member_id' => $this->member_id,
'token' => $this->token,
];
// 触发获取历史消息事件
$result = Event::trigger('KefuGetHistory', $event_data);
// 处理事件结果
$response = [
'code' => 0,
'message' => 'success',
'data' => []
];
if (is_array($result) && !empty($result)) {
foreach ($result as $res) {
if (isset($res['code']) && $res['code'] < 0) {
$response = $res;
break;
}
if (isset($res['data'])) {
$response['data'] = array_merge($response['data'], $res['data']);
}
}
}
return $this->response($response);
} catch (\Exception $e) {
return $this->response($this->error('请求失败:' . $e->getMessage()));
}
}
}

266
src/docs/kefu_api.md Normal file
View File

@@ -0,0 +1,266 @@
# 智能客服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/chat`
**请求方式**POST
**请求参数**
| 参数名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| message | string | 是 | 用户输入的消息内容 |
| user_id | string | 否 | 用户ID默认使用当前登录会员ID |
| conversation_id | string | 否 | 会话ID第一次聊天可不传系统会自动创建 |
| stream | bool | 否 | 是否使用流式响应默认false |
**响应示例**
```json
{
"code": 0,
"message": "success",
"data": {
"conversation_id": "conv_123456789",
"reply": "您好,我是智能客服,有什么可以帮助您的?",
"message_id": "msg_123456789",
"finish_reason": "stop",
"usage": {
"prompt_tokens": 10,
"completion_tokens": 20,
"total_tokens": 30
}
},
"timestamp": 1640995200
}
```
### 2. 获取会话历史
**接口地址**`/api/kefu/getHistory`
**请求方式**POST
**请求参数**
| 参数名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| conversation_id | string | 是 | 会话ID |
| user_id | string | 否 | 用户ID默认使用当前登录会员ID |
| limit | int | 否 | 每页条数默认20 |
| offset | int | 否 | 偏移量默认0 |
**响应示例**
```json
{
"code": 0,
"message": "success",
"data": {
"messages": [
{
"id": "msg_123456789",
"role": "user",
"content": "你好",
"created_at": "2023-01-01T00:00:00Z"
},
{
"id": "msg_987654321",
"role": "assistant",
"content": "您好,我是智能客服,有什么可以帮助您的?",
"created_at": "2023-01-01T00:00:01Z"
}
],
"total": 2,
"limit": 20,
"offset": 0
},
"timestamp": 1640995200
}
```
### 3. 创建新会话
**接口地址**`/api/kefu/createConversation`
**请求方式**POST
**请求参数**
| 参数名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| user_id | string | 否 | 用户ID默认使用当前登录会员ID |
**响应示例**
```json
{
"code": 0,
"message": "success",
"data": {
"conversation_id": "conv_123456789",
"name": "智能客服会话",
"created_at": "2023-01-01T00:00:00Z"
},
"timestamp": 1640995200
}
```
## 四、前端调用示例
### Uniapp调用示例
```javascript
// 引入请求封装(根据项目实际情况调整)
import { request } from '@/utils/request';
// 智能客服聊天
async function chatWithAI(message, conversationId = '') {
try {
const res = await request({
url: '/api/kefu/chat',
method: 'POST',
data: {
message: message,
conversation_id: conversationId,
// user_id: 'your-user-id', // 可选
// stream: false // 可选
}
});
if (res.code === 0) {
return res.data;
} else {
console.error('聊天失败:', res.message);
return null;
}
} catch (error) {
console.error('聊天请求失败:', error);
return null;
}
}
// 获取会话历史
async function getChatHistory(conversationId, limit = 20, offset = 0) {
try {
const res = await request({
url: '/api/kefu/getHistory',
method: 'POST',
data: {
conversation_id: conversationId,
limit: limit,
offset: offset
// user_id: 'your-user-id', // 可选
}
});
if (res.code === 0) {
return res.data;
} else {
console.error('获取历史记录失败:', res.message);
return null;
}
} catch (error) {
console.error('获取历史记录请求失败:', error);
return null;
}
}
// 创建新会话
async function createNewConversation() {
try {
const res = await request({
url: '/api/kefu/createConversation',
method: 'POST',
data: {
// 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;
}
}
```
## 五、使用流程
1. **初始化会话**:小程序端进入客服页面时,调用`createConversation`接口创建新会话或使用本地存储的会话ID
2. **发送消息**:用户输入消息后,调用`chat`接口发送消息,获取机器人回复
3. **显示消息**:将用户消息和机器人回复显示在聊天界面
4. **加载历史记录**:需要时调用`getHistory`接口加载历史消息
5. **维护会话**保持会话ID用于后续消息交流
## 六、注意事项
1. 请确保Dify API密钥的安全性不要泄露给前端
2. 建议对用户ID进行加密处理避免直接使用敏感信息
3. 对于大量用户的场景,建议实现会话管理机制,定期清理过期会话
4. 建议添加请求频率限制,防止恶意请求
5. 在生产环境中建议关闭DEBUG模式
## 七、测试建议
1. 首先在Dify平台测试聊天机器人功能是否正常
2. 在后端配置正确的API密钥
3. 使用Postman或类似工具测试后端API接口
4. 在小程序端集成并测试完整流程
5. 模拟不同场景下的用户输入,测试机器人回复效果
## 八、常见问题
### 1. 接口返回401错误
**原因**Dify API密钥无效或过期
**解决方法**重新获取有效的API密钥并更新插件配置
### 2. 接口返回500错误
**原因**后端服务器错误或Dify API服务异常
**解决方法**查看服务器日志检查Dify API服务状态
### 3. 机器人回复为空
**原因**Dify聊天机器人配置问题或请求参数错误
**解决方法**检查Dify机器人配置验证请求参数是否正确
### 4. 会话ID无效
**原因**:会话已过期或不存在
**解决方法**创建新会话获取新的会话ID