实现后台及前台通过API访问UV埋点,所有代码全部保存

This commit is contained in:
2025-11-08 18:15:26 +08:00
parent 6bad32d9b1
commit e440631275
43 changed files with 5960 additions and 1105 deletions

View File

@@ -0,0 +1,505 @@
<?php
/**
* AI API服务, 主要用来与终端进行AI相关的交互
* 1. 与微信小程序/H5的智能客服进行交互
* 2. 连接Dify平台或RAGFlow平台的智能体
*/
namespace app\api\controller;
use Exception;
use think\facade\Cache;
use think\facade\Db;
use think\facade\Request;
use think\response\Json;
use app\exception\ApiException;
use app\model\ai\AiChatSession;
use app\model\ai\AiChatHistory;
use app\model\web\Config;
class AI extends BaseApi
{
/**
* 日志文件名称
* @var string
*/
private $log_file = 'ai.log';
/**
* 配置模型实例
* @var Config
*/
protected $configModel;
/**
* AiChatSession模型实例
* @var AiChatSession
*/
protected $aiChatSessionModel;
/**
* AiChatHistory模型实例
* @var AiChatHistory
*/
protected $aiChatHistoryModel;
/**
* 构造函数
*/
public function __construct()
{
parent::__construct();
$this->aiChatSessionModel = new AiChatSession();
$this->aiChatHistoryModel = new AiChatHistory();
$this->configModel = new Config();
}
/**
* 平台类型常量
*/
const PLATFORM_DIFY = 'dify';
const PLATFORM_RAGFLOW = 'ragflow';
/**
* AI对话接口
* 发送对话或者创建会话
* 1. 支持与Dify平台或RAGFlow平台的智能体进行交互
* 2. 支持会话管理,每个用户在每个平台上的会话是独立的
* 3. 支持上下文管理,每个会话都维护一个上下文信息,用于持续对话
*
* @return Json
*/
public function chat()
{
log_write('AI chat request: ' . json_encode($this->params), 'info', $this->log_file);
try {
// 获取请求参数
$message = $this->params['message'] ?? ''; // 用户消息
$session_id = $this->params['session_id'] ?? ''; // 会话ID
$user_id = $this->params['user_id'] ?? $this->member_id; // 用户ID
$context = $this->params['context'] ?? []; // 上下文信息
$site_id = $this->params['uniacid'] ?? $this->site_id; // 站点ID
$app_module = $this->params['app_module'] ?? $this->app_module; // 应用模块
// 参数验证
if (empty($message)) {
return $this->response($this->error('', 'MESSAGE_EMPTY'));
}
// 获取平台配置
$config = $this->getPlatformConfig($site_id, $app_module);
if (!$config['status']) {
return $this->response($this->error('', $config['message']));
}
$platform = $config['data']['default']['type'] ?? self::PLATFORM_DIFY; // 平台类型
// 生成或使用现有会话ID
$session_id = $session_id ?: $this->generateSessionId($user_id, $platform);
// 根据平台类型调用不同的方法
$result = [];
if ($platform === self::PLATFORM_DIFY) {
$result = $this->callDifyApi($config['data'], $message, $session_id, $user_id, $context);
} else if ($platform === self::PLATFORM_RAGFLOW) {
$result = $this->callRagflowApi($config['data'], $message, $session_id, $user_id, $context);
}
if (!$result['status']) {
return $this->response($this->error('', $result['message']));
}
// 保存会话记录
$this->saveChatHistory($user_id, $session_id, $platform, $message, $result['data']['content']);
return $this->response($this->success([
'session_id' => $session_id,
'content' => $result['data']['content'],
'tokens' => $result['data']['tokens'] ?? null,
'response_time' => $result['data']['response_time'] ?? null
]));
} catch (Exception $e) {
// 记录错误日志
log_write('AI chat error: ' . $e->getMessage(), 'error', $this->log_file);
return $this->response($this->error('', 'AI_SERVICE_ERROR'));
}
}
/**
* 获取平台配置
* @param int $site_id 站点ID 默认为1, 业务站点uniacid值与site_id保持一致
* @param string $app_module 应用模块 默认为shop
* @return array
*/
private function getPlatformConfig($site_id, $app_module)
{
$config = [];
try {
// 从配置模型获取平台配置
$config = $this->configModel->getAIPlatformConfig($site_id, $app_module);
throw new \Exception('Get AI platform config error: ' . json_encode($config));
if (!$config || !$config['status']) {
return ['status' => false, 'message' => 'PLATFORM_CONFIG_NOT_FOUND'];
}
$config_data = json_decode($config['config'], true);
if (!$config_data || empty($config_data['api_key']) || empty($config_data['base_url']) || empty($config_data['app_id'])) {
return ['status' => false, 'message' => 'PLATFORM_CONFIG_INVALID'];
}
return ['status' => true, 'data' => $config_data];
} catch (Exception $e) {
return ['status' => false, 'message' => 'GET_CONFIG_ERROR' . $e->getMessage()];
}
}
/**
* 生成会话ID
* @param string $user_id
* @param string $platform
* @return string
*/
private function generateSessionId($user_id, $platform)
{
return md5($user_id . '_' . $platform . '_' . time() . '_' . rand(1000, 9999));
}
/**
* 调用Dify API
* @param array $config
* @param string $message
* @param string $session_id
* @param string $user_id
* @param array $context
* @return array
*/
private function callDifyApi($config, $message, $session_id, $user_id, $context = [])
{
try {
$start_time = microtime(true);
$headers = [
'Authorization: Bearer ' . $config['api_key'],
'Content-Type: application/json',
'Accept: application/json',
];
$data = [
'inputs' => $context,
'query' => $message,
'response_mode' => $config['response_mode'] ?? 'streaming',
'user' => $user_id,
'conversation_id' => $session_id
];
$url = rtrim($config['base_url'], '/') . '/v1/chat-messages';
// 发送请求
$result = $this->httpRequest($url, $data, $headers);
if (!$result['status']) {
return ['status' => false, 'message' => 'DIFY_API_ERROR'];
}
$response_time = round((microtime(true) - $start_time) * 1000, 2);
return [
'status' => true,
'data' => [
'content' => $result['data']['answer'] ?? $result['data']['choices'][0]['message']['content'] ?? '',
'tokens' => [
'prompt' => $result['data']['usage']['prompt_tokens'] ?? 0,
'completion' => $result['data']['usage']['completion_tokens'] ?? 0,
'total' => $result['data']['usage']['total_tokens'] ?? 0
],
'response_time' => $response_time
]
];
} catch (Exception $e) {
log_write('Dify API error: ' . $e->getMessage(), 'error', $this->log_file);
return ['status' => false, 'message' => 'DIFY_CALL_ERROR'];
}
}
/**
* 调用RAGFlow API
* @param array $config
* @param string $message
* @param string $session_id
* @param string $user_id
* @param array $context
* @return array
*/
private function callRagflowApi($config, $message, $session_id, $user_id, $context = [])
{
try {
$start_time = microtime(true);
$headers = [
'Authorization: Bearer ' . $config['api_key'],
'Content-Type: application/json',
'Accept: application/json',
];
$data = [
'query' => $message,
'conversation_id' => $session_id,
'user_id' => $user_id,
'agent_id' => $config['app_id'],
'stream' => $config['stream'] ?? false
];
$url = rtrim($config['base_url'], '/') . '/api/v1/chat/completions';
// 发送请求
$result = $this->httpRequest($url, $data, $headers);
if (!$result['status']) {
return ['status' => false, 'message' => 'RAGFLOW_API_ERROR'];
}
$response_time = round((microtime(true) - $start_time) * 1000, 2);
return [
'status' => true,
'data' => [
'content' => $result['data']['choices'][0]['message']['content'] ?? $result['data']['answer'] ?? '',
'tokens' => [
'prompt' => $result['data']['usage']['prompt_tokens'] ?? 0,
'completion' => $result['data']['usage']['completion_tokens'] ?? 0,
'total' => $result['data']['usage']['total_tokens'] ?? 0
],
'response_time' => $response_time
]
];
} catch (Exception $e) {
log_write('RAGFlow API error: ' . $e->getMessage(), 'error', $this->log_file);
return ['status' => false, 'message' => 'RAGFLOW_CALL_ERROR'];
}
}
/**
* 保存聊天记录
* @param string $user_id
* @param string $session_id
* @param string $platform
* @param string $user_message
* @param string $ai_message
*/
private function saveChatHistory($user_id, $session_id, $platform, $user_message, $ai_message)
{
try {
$data = [
'site_id' => $this->site_id,
'user_id' => $user_id,
'session_id' => $session_id,
'platform' => $platform,
'user_message' => $user_message,
'ai_message' => $ai_message,
'create_time' => time(),
'ip' => Request::ip()
];
// 使用事务保存聊天记录
Db::startTrans();
try {
// 使用模型保存聊天记录
$save_result = $this->aiChatHistoryModel->saveHistory($data);
if (!$save_result['success']) {
log_write('Save chat history failed: ' . $save_result['msg'], 'error', $this->log_file);
}
// 更新会话最后活动时间
$update_result = $this->aiChatSessionModel->updateLastActiveTime($session_id);
if (!$update_result['success']) {
log_write('Update session active time failed: ' . $update_result['msg'], 'error', $this->log_file);
}
// 如果会话不存在,创建新会话
$session_info = $this->aiChatSessionModel->getSessionInfo(['session_id' => $session_id]);
if (!$session_info['success']) {
$session_data = [
'site_id' => $this->site_id,
'user_id' => $user_id,
'session_id' => $session_id,
'platform' => $platform,
'create_time' => time(),
'last_active_time' => time()
];
$create_result = $this->aiChatSessionModel->createSession($session_data);
if (!$create_result['success']) {
log_write('Create session failed: ' . $create_result['msg'], 'error', $this->log_file);
}
}
Db::commit();
} catch (Exception $e) {
Db::rollback();
log_write('Save chat history error: ' . $e->getMessage(), 'error', $this->log_file);
}
} catch (Exception $e) {
// 记录错误但不影响主流程
log_write('Save chat history exception: ' . $e->getMessage(), 'error', $this->log_file);
}
}
/**
* HTTP请求方法
* @param string $url
* @param array $data
* @param array $headers
* @return array
*/
private function httpRequest($url, $data = [], $headers = [])
{
try {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
if (!empty($data)) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
if (!empty($headers)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code != 200) {
log_write('HTTP request failed: URL=' . $url . ', Code=' . $http_code . ', Response=' . $response, 'error', $this->log_file);
return ['status' => false, 'message' => 'HTTP_REQUEST_FAILED', 'code' => $http_code];
}
$result = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
log_write('JSON decode error: ' . json_last_error_msg(), 'error', $this->log_file);
return ['status' => false, 'message' => 'JSON_DECODE_ERROR'];
}
return ['status' => true, 'data' => $result];
} catch (Exception $e) {
log_write('HTTP request exception: ' . $e->getMessage(), 'error', $this->log_file);
return ['status' => false, 'message' => 'HTTP_REQUEST_EXCEPTION'];
}
}
/**
* 获取会话历史
* @return Json
*/
public function getHistory()
{
try {
$session_id = $this->params['session_id'] ?? '';
$user_id = $this->params['user_id'] ?? $this->member_id;
$page = $this->params['page'] ?? 1;
$page_size = $this->params['page_size'] ?? 20;
if (empty($session_id)) {
return $this->response($this->error('', 'SESSION_ID_EMPTY'));
}
// 使用模型获取会话历史
$where = [
'site_id' => $this->site_id,
'user_id' => $user_id
];
$result = $this->aiChatHistoryModel->getHistoryBySessionId($session_id, $where, $page, $page_size);
if (!$result['success']) {
log_write('Get history failed: ' . $result['msg'], 'error', $this->log_file);
return $this->response($this->error('', 'GET_HISTORY_ERROR'));
}
return $this->response($this->success($result['data']));
} catch (Exception $e) {
log_write('Get history error: ' . $e->getMessage(), 'error', $this->log_file);
return $this->response($this->error('', 'GET_HISTORY_ERROR'));
}
}
/**
* 获取用户的会话列表
* @return Json
*/
public function getSessions()
{
try {
$user_id = $this->params['user_id'] ?? $this->member_id;
$page = $this->params['page'] ?? 1;
$page_size = $this->params['page_size'] ?? 20;
// 使用模型获取会话列表
$where = [
'site_id' => $this->site_id,
'user_id' => $user_id
];
$result = $this->aiChatSessionModel->getSessionList($where, ['*'], 'last_active_time DESC', $page, $page_size);
if (!$result['success']) {
return $this->response($this->error('', 'GET_SESSIONS_ERROR'));
}
return $this->response($this->success($result['data']));
} catch (Exception $e) {
log_write('Get sessions error: ' . $e->getMessage(), 'error', $this->log_file);
return $this->response($this->error('', 'GET_SESSIONS_ERROR'));
}
}
/**
* 删除会话
* @return Json
*/
public function deleteSession()
{
try {
$session_id = $this->params['session_id'] ?? '';
$user_id = $this->params['user_id'] ?? $this->member_id;
if (empty($session_id)) {
return $this->response($this->error('', 'SESSION_ID_EMPTY'));
}
// 使用模型删除会话
$where = [
'site_id' => $this->site_id,
'session_id' => $session_id,
'user_id' => $user_id
];
$result = $this->aiChatSessionModel->deleteSession($where);
if (!$result['success']) {
return $this->response($this->error('', 'DELETE_SESSION_ERROR'));
}
return $this->response($this->success());
} catch (Exception $e) {
log_write('Delete session exception: ' . $e->getMessage(), 'error', $this->log_file);
return $this->response($this->error('', 'DELETE_SESSION_EXCEPTION'));
}
}
}

View File

@@ -18,6 +18,7 @@ use think\facade\Cache;
use addon\store\model\Config as StoreConfig;
use app\model\store\Store;
use think\Response;
use app\tracking\VisitorTracker;
class BaseApi
{
@@ -97,6 +98,15 @@ class BaseApi
}
$this->store_id = $this->params[ 'store_id' ] ?? 0;
// ------------ 埋点:新增用户访问来源统计数据 ------------
$visitorTracker = new VisitorTracker();
$visitorTracker->shop_visit(['site_id' => $this->site_id,]);
if (!empty($this->params[ 'store_id' ])) {
$visitorTracker->store_visit(['site_id' => $this->site_id, 'store_id' => $this->store_id]);
}
// ------------------------------------------- ------------
}
/**

View File

@@ -11,6 +11,7 @@ use app\model\web\DiyView as DiyViewModel;
use app\model\shop\Shop as ShopModel;
use app\model\member\Config as ConfigMemberModel;
class Config extends BaseApi
{
@@ -107,7 +108,6 @@ class Config extends BaseApi
*/
public function init()
{
$diy_view = new DiyViewModel();
$diy_style = $diy_view->getStyleConfig($this->site_id)[ 'data' ][ 'value' ];