chore(addon/aikefu): 变更API暴漏的端点

This commit is contained in:
2025-12-06 16:12:12 +08:00
parent 0f76b61152
commit 0b6e6914fd
6 changed files with 1245 additions and 3 deletions

View File

@@ -17,6 +17,15 @@ return [
'KefuGetHistory' => [
'addon\aikefu\event\KefuGetHistory'
],
'KefuClearConversation' => [
'addon\aikefu\event\KefuClearConversation'
],
'KefuHealthCheck' => [
'addon\aikefu\event\KefuHealthCheck'
],
'KefuChatStream' => [
'addon\aikefu\event\KefuChatStream'
],
],
'subscribe' => [

View File

@@ -0,0 +1,235 @@
<?php
namespace addon\aikefu\event;
use addon\aikefu\model\Config as KefuConfigModel;
use addon\aikefu\model\Conversation as KefuConversationModel;
use addon\aikefu\model\Message as KefuMessageModel;
/**
* 智能客服流式聊天
*/
class KefuChatStream
{
/**
* 处理智能客服流式聊天事件
* @param array $data 事件数据
* @return array
*/
public function handle($data)
{
$message = $data['message'] ?? '';
$user_id = $data['user_id'] ?? '';
$conversation_id = $data['conversation_id'] ?? '';
$site_id = $data['site_id'] ?? 0;
$request_id = $data['request_id'] ?? '';
try {
// 验证参数
if (empty($message)) {
return [
[
'type' => 'error',
'message' => '消息内容不能为空'
]
];
}
// 获取智能客服配置
$kefu_config_model = new KefuConfigModel();
$config_info = $kefu_config_model->getConfig($site_id)['data']['value'] ?? [];
if (empty($config_info) || $config_info['status'] != 1) {
return [
[
'type' => 'error',
'message' => '智能客服暂未启用'
]
];
}
$config = $config_info;
$apiKey = $config['api_key'];
$baseUrl = $config['base_url'];
$chatEndpoint = $config['chat_endpoint'];
// 构建请求数据
$requestData = [
'inputs' => [],
'query' => $message,
'response_mode' => 'streaming',
'user' => $user_id,
];
if (!empty($conversation_id)) {
$requestData['conversation_id'] = $conversation_id;
}
// 构建请求头
$headers = [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
'Accept: text/event-stream',
];
// 发送流式请求到Dify API
$url = $baseUrl . $chatEndpoint;
$result = $this->executeStreamRequest($url, $requestData, $headers);
return $result;
} catch (\Exception $e) {
return [
[
'type' => 'error',
'message' => '请求失败:' . $e->getMessage()
]
];
}
}
/**
* 执行流式请求
*/
private function executeStreamRequest($url, $requestData, $headers)
{
$ch = curl_init();
// 设置curl选项
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($requestData));
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_WRITEFUNCTION, [$this, 'streamCallback']);
curl_setopt($ch, CURLOPT_TIMEOUT, 120); // 设置较长的超时时间
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
// 执行请求
$result = [];
$this->stream_buffer = '';
$this->conversation_id = '';
$this->current_message_id = '';
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($response === false || !empty($error)) {
return [
[
'type' => 'error',
'message' => '请求失败:' . $error
]
];
}
if ($http_code >= 400) {
return [
[
'type' => 'error',
'message' => '服务端错误HTTP状态码' . $http_code
]
];
}
return $this->parseStreamResponse($this->stream_buffer);
}
/**
* 流式回调函数
*/
private function streamCallback($ch, $data)
{
$this->stream_buffer .= $data;
return strlen($data);
}
/**
* 解析流式响应
*/
private function parseStreamResponse($response)
{
$result = [];
$lines = explode("\n", $response);
$conversation_id = '';
$message_id = '';
$buffer = '';
foreach ($lines as $line) {
$line = trim($line);
if (empty($line)) {
continue;
}
if (strpos($line, 'data: ') === 0) {
$data_str = substr($line, 6);
if ($data_str === '[DONE]') {
// 流结束,发送完成事件
$result[] = [
'type' => 'complete',
'conversation_id' => $conversation_id,
'message_id' => $message_id,
'content' => $buffer,
'finished' => true
];
break;
}
$data = json_decode($data_str, true);
if (json_last_error() === JSON_ERROR_NONE && isset($data)) {
// 提取会话ID和消息ID
if (isset($data['conversation_id'])) {
$conversation_id = $data['conversation_id'];
}
if (isset($data['message_id'])) {
$message_id = $data['message_id'];
}
// 处理内容块
if (isset($data['answer'])) {
$buffer .= $data['answer'];
$result[] = [
'type' => 'chunk',
'content' => $data['answer'],
'conversation_id' => $conversation_id,
'message_id' => $message_id,
'finished' => false
];
}
// 检查是否结束
if (isset($data['finish_reason']) && $data['finish_reason'] !== 'null') {
$result[] = [
'type' => 'complete',
'conversation_id' => $conversation_id,
'message_id' => $message_id,
'content' => $buffer,
'finish_reason' => $data['finish_reason'],
'usage' => $data['usage'] ?? [],
'finished' => true
];
break;
}
}
}
}
// 如果没有收到[DONE]信号,确保发送完成事件
if (empty($result) || end($result)['type'] !== 'complete') {
$result[] = [
'type' => 'complete',
'conversation_id' => $conversation_id,
'message_id' => $message_id,
'content' => $buffer,
'finished' => true
];
}
return $result;
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace addon\aikefu\event;
use addon\aikefu\model\Conversation as KefuConversationModel;
use addon\aikefu\model\Message as KefuMessageModel;
/**
* 清除客服会话历史
*/
class KefuClearConversation
{
/**
* 处理清除会话历史事件
* @param array $data 事件数据
* @return array
*/
public function handle($data)
{
try {
$conversation_id = $data['conversation_id'] ?? '';
$user_id = $data['user_id'] ?? '';
$site_id = $data['site_id'] ?? 0;
// 验证参数
if (empty($conversation_id) && empty($user_id)) {
return [
'code' => -1,
'message' => '会话ID或用户ID不能为空',
'data' => []
];
}
$conversation_model = new KefuConversationModel();
$message_model = new KefuMessageModel();
$deleted_messages = 0;
$deleted_conversations = 0;
if (!empty($conversation_id)) {
// 删除指定会话的消息和会话记录
// 先删除该会话的所有消息
$message_condition = [
['site_id', '=', $site_id],
['conversation_id', '=', $conversation_id]
];
$message_result = $message_model->deleteMessage($message_condition);
if ($message_result['code'] >= 0) {
$deleted_messages = $message_result['data']['result'] ?? 0;
}
// 再删除会话记录
$conversation_condition = [
['site_id', '=', $site_id],
['conversation_id', '=', $conversation_id]
];
$conversation_result = $conversation_model->deleteConversation($conversation_condition);
if ($conversation_result['code'] >= 0) {
$deleted_conversations = $conversation_result['data']['result'] ?? 0;
}
} else if (!empty($user_id)) {
// 删除指定用户的所有会话和消息
// 先获取该用户的所有会话ID
$conversation_list = $conversation_model->getConversationList([
['site_id', '=', $site_id],
['user_id', '=', $user_id]
], 'conversation_id');
$conversation_ids = array_column($conversation_list['data'], 'conversation_id');
if (!empty($conversation_ids)) {
// 删除所有会话的消息
$message_condition = [
['site_id', '=', $site_id],
['conversation_id', 'in', $conversation_ids]
];
$message_result = $message_model->deleteMessage($message_condition);
if ($message_result['code'] >= 0) {
$deleted_messages = $message_result['data']['result'] ?? 0;
}
// 删除所有会话记录
$conversation_condition = [
['site_id', '=', $site_id],
['user_id', '=', $user_id]
];
$conversation_result = $conversation_model->deleteConversation($conversation_condition);
if ($conversation_result['code'] >= 0) {
$deleted_conversations = $conversation_result['data']['result'] ?? 0;
}
}
}
return [
'code' => 0,
'message' => '清除成功',
'data' => [
'deleted_messages' => $deleted_messages,
'deleted_conversations' => $deleted_conversations
]
];
} catch (\Exception $e) {
return [
'code' => -1,
'message' => '清除失败:' . $e->getMessage(),
'data' => []
];
}
}
}

View File

@@ -0,0 +1,349 @@
<?php
namespace addon\aikefu\event;
use addon\aikefu\model\Config as KefuConfigModel;
use think\facade\Db;
/**
* 智能客服健康检查
*/
class KefuHealthCheck
{
/**
* 处理健康检查事件
* @param array $data 事件数据
* @return array
*/
public function handle($data)
{
$check_results = [];
$site_id = $data['site_id'] ?? 0;
$check_type = $data['check_type'] ?? 'full';
try {
// 1. 数据库连接检查
if (in_array($check_type, ['full', 'basic'])) {
$check_results[] = $this->checkDatabase();
}
// 2. AI服务配置检查
if (in_array($check_type, ['full', 'ai_service'])) {
$check_results[] = $this->checkAIServiceConfig($site_id);
}
// 3. AI服务连接检查
if (in_array($check_type, ['full', 'ai_service'])) {
$check_results[] = $this->checkAIServiceConnection($site_id);
}
// 4. 系统资源检查
if (in_array($check_type, ['full'])) {
$check_results[] = $this->checkSystemResources();
}
} catch (\Exception $e) {
$check_results[] = [
'component' => 'health_check_error',
'status' => 'error',
'message' => '健康检查过程异常:' . $e->getMessage(),
'response_time_ms' => 0
];
}
return $check_results;
}
/**
* 检查数据库连接
*/
private function checkDatabase()
{
$start_time = microtime(true);
try {
// 测试数据库连接
$result = Db::query('SELECT 1 as test');
if (!empty($result) && $result[0]['test'] == 1) {
return [
'component' => 'database',
'status' => 'healthy',
'message' => '数据库连接正常',
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2),
'details' => [
'connection' => 'success',
'query_test' => 'passed'
]
];
} else {
return [
'component' => 'database',
'status' => 'error',
'message' => '数据库查询测试失败',
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2)
];
}
} catch (\Exception $e) {
return [
'component' => 'database',
'status' => 'error',
'message' => '数据库连接失败:' . $e->getMessage(),
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2)
];
}
}
/**
* 检查AI服务配置
*/
private function checkAIServiceConfig($site_id)
{
$start_time = microtime(true);
try {
$config_model = new KefuConfigModel();
$config_info = $config_model->getConfig($site_id);
if (empty($config_info['data']['value'])) {
return [
'component' => 'ai_service_config',
'status' => 'warning',
'message' => '智能客服配置未设置',
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2),
'details' => [
'configured' => false,
'required_fields' => ['api_key', 'base_url', 'chat_endpoint']
]
];
}
$config = $config_info['data']['value'];
$required_fields = ['api_key', 'base_url', 'chat_endpoint'];
$missing_fields = [];
foreach ($required_fields as $field) {
if (empty($config[$field])) {
$missing_fields[] = $field;
}
}
if (!empty($missing_fields)) {
return [
'component' => 'ai_service_config',
'status' => 'warning',
'message' => 'AI服务配置不完整缺少字段' . implode(', ', $missing_fields),
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2),
'details' => [
'configured' => true,
'complete' => false,
'missing_fields' => $missing_fields
]
];
}
if ($config['status'] != 1) {
return [
'component' => 'ai_service_config',
'status' => 'warning',
'message' => '智能服务已禁用',
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2),
'details' => [
'configured' => true,
'complete' => true,
'enabled' => false
]
];
}
return [
'component' => 'ai_service_config',
'status' => 'healthy',
'message' => 'AI服务配置正常',
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2),
'details' => [
'configured' => true,
'complete' => true,
'enabled' => true,
'base_url' => $config['base_url']
]
];
} catch (\Exception $e) {
return [
'component' => 'ai_service_config',
'status' => 'error',
'message' => 'AI服务配置检查失败' . $e->getMessage(),
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2)
];
}
}
/**
* 检查AI服务连接
*/
private function checkAIServiceConnection($site_id)
{
$start_time = microtime(true);
try {
$config_model = new KefuConfigModel();
$config_info = $config_model->getConfig($site_id);
if (empty($config_info['data']['value'])) {
return [
'component' => 'ai_service_connection',
'status' => 'warning',
'message' => 'AI服务未配置跳过连接检查',
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2)
];
}
$config = $config_info['data']['value'];
if ($config['status'] != 1 || empty($config['api_key']) || empty($config['base_url'])) {
return [
'component' => 'ai_service_connection',
'status' => 'warning',
'message' => 'AI服务未启用或配置不完整跳过连接检查',
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2)
];
}
// 测试连接(发送一个简单的健康检查请求)
$url = $config['base_url'];
$headers = [
'Authorization: Bearer ' . $config['api_key'],
'Content-Type: application/json',
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response === false) {
return [
'component' => 'ai_service_connection',
'status' => 'error',
'message' => '无法连接到AI服务',
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2)
];
}
if ($http_code >= 200 && $http_code < 300) {
return [
'component' => 'ai_service_connection',
'status' => 'healthy',
'message' => 'AI服务连接正常',
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2),
'details' => [
'http_status' => $http_code,
'url' => $url
]
];
} else {
return [
'component' => 'ai_service_connection',
'status' => 'warning',
'message' => 'AI服务响应异常HTTP状态码' . $http_code,
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2),
'details' => [
'http_status' => $http_code,
'url' => $url
]
];
}
} catch (\Exception $e) {
return [
'component' => 'ai_service_connection',
'status' => 'error',
'message' => 'AI服务连接检查失败' . $e->getMessage(),
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2)
];
}
}
/**
* 检查系统资源
*/
private function checkSystemResources()
{
$start_time = microtime(true);
try {
$memory_usage = memory_get_usage(true);
$memory_limit = $this->parseMemoryLimit(ini_get('memory_limit'));
$memory_usage_percent = ($memory_usage / $memory_limit) * 100;
$details = [
'php_version' => PHP_VERSION,
'memory_usage' => round($memory_usage / 1024 / 1024, 2) . ' MB',
'memory_limit' => round($memory_limit / 1024 / 1024, 2) . ' MB',
'memory_usage_percent' => round($memory_usage_percent, 2) . '%',
'max_execution_time' => ini_get('max_execution_time') . 's',
'upload_max_filesize' => ini_get('upload_max_filesize'),
'post_max_size' => ini_get('post_max_size')
];
$status = 'healthy';
$message = '系统资源正常';
// 检查内存使用率
if ($memory_usage_percent > 90) {
$status = 'error';
$message = '内存使用率过高';
} elseif ($memory_usage_percent > 80) {
$status = 'warning';
$message = '内存使用率较高';
}
return [
'component' => 'system_resources',
'status' => $status,
'message' => $message,
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2),
'details' => $details
];
} catch (\Exception $e) {
return [
'component' => 'system_resources',
'status' => 'error',
'message' => '系统资源检查失败:' . $e->getMessage(),
'response_time_ms' => round((microtime(true) - $start_time) * 1000, 2)
];
}
}
/**
* 解析内存限制值
*/
private function parseMemoryLimit($val)
{
$val = trim($val);
$last = strtolower($val[strlen($val)-1]);
$val = (int)$val;
switch($last) {
case 'g':
$val *= 1024;
case 'm':
$val *= 1024;
case 'k':
$val *= 1024;
}
return $val;
}
}

View File

@@ -176,19 +176,19 @@ class Pay extends BaseModel
Log::info('华为APP支付响应: ' . json_encode($result));
if (isset($result['code']) && $result['code'] == '0') {
return $this->success([
return success(0, '', [
'type' => 'params',
'data' => $result
]);
} else {
$errorMsg = $result['msg'] ?? ($result['message'] ?? '华为支付请求失败');
Log::error('华为APP支付失败: ' . $errorMsg);
return $this->error('', $errorMsg);
return error(500000, $errorMsg);
}
break;
default:
// 默认返回错误
return $this->error('', '不支持的支付类型: ' . $param["app_type"]);
return error(500000, '不支持的支付类型: ' . $param["app_type"]);
}
} catch (\Exception $e) {
Log::error('华为支付生成支付失败: ' . $e->getMessage() . ',错误堆栈: ' . $e->getTraceAsString());