存客宝应用接口初始化

This commit is contained in:
Ghost
2026-01-05 10:25:32 +08:00
parent fa37227954
commit 02797ba50a
414 changed files with 85530 additions and 75 deletions

View File

@@ -0,0 +1,214 @@
<?php
// +----------------------------------------------------------------------
// | 设备管理模块路由配置
// +----------------------------------------------------------------------
use think\facade\Route;
// 定义RESTful风格的API路由
Route::group('v1/', function () {
Route::group('kefu/', function () {
//好友相关
Route::group('wechatFriend/', function () {
Route::get('list', 'app\chukebao\controller\WechatFriendController@getList'); // 获取好友列表
Route::get('detail', 'app\chukebao\controller\WechatFriendController@getDetail'); // 获取好友详情
Route::post('updateInfo', 'app\chukebao\controller\WechatFriendController@updateFriendInfo'); // 更新好友资料
// 添加好友任务记录相关接口
Route::get('addTaskList', 'app\chukebao\controller\WechatFriendController@getAddTaskList'); // 获取添加好友任务记录列表包含添加者信息、状态、时间等支持状态筛选无需传好友ID
});
//群相关
Route::group('wechatChatroom/', function () {
Route::get('list', 'app\chukebao\controller\WechatChatroomController@getList'); // 获取好友列表
Route::get('detail', 'app\chukebao\controller\WechatChatroomController@getDetail'); // 获取群详情
Route::get('members', 'app\chukebao\controller\WechatChatroomController@getMembers'); // 获取群成员列表
Route::post('aiAnnouncement', 'app\chukebao\controller\WechatChatroomController@aiAnnouncement'); // AI群公告
});
//客服相关
Route::group('customerService/', function () {
Route::get('list', 'app\chukebao\controller\CustomerServiceController@getList'); // 获取好友列表
});
//账号管理
Route::group('accounts/', function () {
Route::get('list', 'app\chukebao\controller\AccountsController@getList'); // 获取账号列表
});
//消息相关
Route::group('message/', function () {
Route::get('list', 'app\chukebao\controller\MessageController@getList'); // 获取好友列表
Route::get('readMessage', 'app\chukebao\controller\MessageController@readMessage'); // 读取消息
Route::get('details', 'app\chukebao\controller\MessageController@details'); // 消息详情
Route::get('getMessageStatus', 'app\chukebao\controller\MessageController@getMessageStatus'); // 获取单条消息发送状态
});
//微信分组
Route::group('wechatGroup/', function () {
Route::get('list', 'app\chukebao\controller\WechatGroupController@getList'); // 获取分组列表
Route::post('add', 'app\chukebao\controller\WechatGroupController@create'); // 新增分组
Route::post('update', 'app\chukebao\controller\WechatGroupController@update'); // 更新分组
Route::delete('delete', 'app\chukebao\controller\WechatGroupController@delete'); // 删除分组(假删除)
Route::post('move', 'app\chukebao\controller\WechatGroupController@move'); // 移动分组(好友/群移动到指定分组)
});
//AI相关
Route::group('ai/', function () {
//问答
Route::group('questions/', function () {
Route::get('list', 'app\chukebao\controller\QuestionsController@getList'); // 问答列表
Route::post('add', 'app\chukebao\controller\QuestionsController@create'); // 问答添加
Route::post('update', 'app\chukebao\controller\QuestionsController@update'); // 问答更新
Route::delete('delete', 'app\chukebao\controller\QuestionsController@delete'); // 问答删除
Route::get('detail', 'app\chukebao\controller\QuestionsController@detail'); // 问答详情
});
//全局配置
Route::group('settings/', function () {
Route::get('get', 'app\chukebao\controller\AiSettingsController@getSetting');
Route::post('set', 'app\chukebao\controller\AiSettingsController@setSetting');
});
//好友配置
Route::group('friend/', function () {
Route::post('set', 'app\chukebao\controller\AiSettingsController@setFriend');
Route::get('get', 'app\chukebao\controller\AiSettingsController@getFriend');
Route::post('setAll', 'app\chukebao\controller\AiSettingsController@setAllFriend');
});
//ai对话
Route::get('getUserTokens', 'app\chukebao\controller\AiSettingsController@getUserTokens');
Route::post('chat', 'app\chukebao\controller\AiChatController@index');
});
//代办事项
Route::group('todo/', function () {
Route::get('list', 'app\chukebao\controller\ToDoController@getList');
Route::post('add', 'app\chukebao\controller\ToDoController@create');
Route::get('process', 'app\chukebao\controller\ToDoController@process');
});
//跟进提醒
Route::group('followUp/', function () {
Route::get('list', 'app\chukebao\controller\FollowUpController@getList');
Route::post('add', 'app\chukebao\controller\FollowUpController@create');
Route::get('process', 'app\chukebao\controller\FollowUpController@process');
});
//算力相关
Route::group('tokensRecord/', function () {
Route::get('list', 'app\chukebao\controller\TokensRecordController@getList');
});
//内容管理
Route::group('content/', function () {
//素材管理
Route::group('material/', function () {
Route::get('all', 'app\chukebao\controller\ContentController@getAllMaterial');
Route::get('list', 'app\chukebao\controller\ContentController@getMaterial');
Route::post('add', 'app\chukebao\controller\ContentController@createMaterial');
Route::get('details', 'app\chukebao\controller\ContentController@detailsMaterial');
Route::delete('del', 'app\chukebao\controller\ContentController@delMaterial');
Route::post('update', 'app\chukebao\controller\ContentController@updateMaterial');
});
//违禁词管理
Route::group('sensitiveWord/', function () {
Route::get('list', 'app\chukebao\controller\ContentController@getSensitiveWord');
Route::post('add', 'app\chukebao\controller\ContentController@createSensitiveWord');
Route::get('details', 'app\chukebao\controller\ContentController@detailsSensitiveWord');
Route::delete('del', 'app\chukebao\controller\ContentController@delSensitiveWord');
Route::post('update', 'app\chukebao\controller\ContentController@updateSensitiveWord');
Route::get('setStatus', 'app\chukebao\controller\ContentController@setSensitiveWordStatus');
});
//关键词管理
Route::group('keywords/', function () {
Route::get('list', 'app\chukebao\controller\ContentController@getKeywords');
Route::post('add', 'app\chukebao\controller\ContentController@createKeywords');
Route::get('details', 'app\chukebao\controller\ContentController@detailsKeywords');
Route::delete('del', 'app\chukebao\controller\ContentController@delKeywords');
Route::post('update', 'app\chukebao\controller\ContentController@updateKeywords');
Route::get('setStatus', 'app\chukebao\controller\ContentController@setKeywordStatus');
});
});
//自动问候
Route::group('autoGreetings/', function () {
Route::get('list', 'app\chukebao\controller\AutoGreetingsController@getList');
Route::post('add', 'app\chukebao\controller\AutoGreetingsController@create');
Route::get('details', 'app\chukebao\controller\AutoGreetingsController@details');
Route::delete('del', 'app\chukebao\controller\AutoGreetingsController@del');
Route::post('update', 'app\chukebao\controller\AutoGreetingsController@update');
Route::get('setStatus', 'app\chukebao\controller\AutoGreetingsController@setStatus');
Route::get('copy', 'app\chukebao\controller\AutoGreetingsController@copy');
Route::get('stats', 'app\chukebao\controller\AutoGreetingsController@stats');
});
//AI智能推送
Route::group('aiPush/', function () {
Route::get('list', 'app\chukebao\controller\AiPushController@getList'); // 获取推送列表
Route::post('add', 'app\chukebao\controller\AiPushController@add'); // 添加推送
Route::get('details', 'app\chukebao\controller\AiPushController@details'); // 推送详情
Route::delete('del', 'app\chukebao\controller\AiPushController@del'); // 删除推送
Route::post('update', 'app\chukebao\controller\AiPushController@update'); // 更新推送
Route::get('setStatus', 'app\chukebao\controller\AiPushController@setStatus'); // 修改状态
Route::get('stats', 'app\chukebao\controller\AiPushController@stats'); // 统计概览
});
Route::group('notice/', function () {
Route::get('list', 'app\chukebao\controller\NoticeController@getList');
Route::put('readMessage', 'app\chukebao\controller\NoticeController@readMessage');
Route::put('readAll', 'app\chukebao\controller\NoticeController@readAll');
});
Route::group('reply/', function () {
Route::get('list', 'app\chukebao\controller\ReplyController@getList');
Route::post('addGroup', 'app\chukebao\controller\ReplyController@addGroup');
Route::post('addReply', 'app\chukebao\controller\ReplyController@addReply');
Route::post('updateGroup', 'app\chukebao\controller\ReplyController@updateGroup');
Route::post('updateReply', 'app\chukebao\controller\ReplyController@updateReply');
Route::delete('deleteGroup', 'app\chukebao\controller\ReplyController@deleteGroup');
Route::delete('deleteReply', 'app\chukebao\controller\ReplyController@deleteReply');
});
Route::group('moments/', function () {
Route::post('add', 'app\chukebao\controller\MomentsController@create');
Route::post('update', 'app\chukebao\controller\MomentsController@update');
Route::delete('delete', 'app\chukebao\controller\MomentsController@delete');
Route::get('list', 'app\chukebao\controller\MomentsController@getList');
});
Route::post('dataProcessing', 'app\chukebao\controller\DataProcessing@index'); // 修改数据
});
})->middleware(['jwt']);
// 客服登录
Route::group('v1/kefu', function () {
Route::post('login', 'app\chukebao\controller\LoginController@index'); // 登录
});
return [];

View File

@@ -0,0 +1,71 @@
<?php
namespace app\chukebao\controller;
use library\ResponseHelper;
use think\Db;
class AccountsController extends BaseController
{
/**
* 获取账号列表(过滤掉后缀为 _offline 与 _delete 的账号)
* @return \think\response\Json
*/
public function getList()
{
try {
$companyId = $this->getUserInfo('companyId');
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 401);
}
if (empty($companyId)) {
return ResponseHelper::error('请先登录', 401);
}
$page = max(1, intval($this->request->param('page', 1)));
$limit = max(1, intval($this->request->param('limit', 10)));
$keyword = trim((string)$this->request->param('keyword', ''));
$query = Db::table('s2_company_account')
->alias('a')
->join('users u', 'a.id = u.s2_accountId')
->where([
['a.departmentId', '=', $companyId],
['a.status', '=', 0],
])
->whereNotLike('a.userName', '%_offline')
->whereNotLike('a.userName', '%_delete');
if ($keyword !== '') {
$query->where(function ($subQuery) use ($keyword) {
$likeKeyword = '%' . $keyword . '%';
$subQuery->whereLike('a.userName', $likeKeyword)
->whereOrLike('a.realName', $likeKeyword)
->whereOrLike('a.nickname', $likeKeyword);
});
}
$total = (clone $query)->count();
$list = $query->field([
'a.id',
'u.id as uid',
'a.userName',
'a.realName',
'a.nickname',
'a.departmentId',
'a.departmentName',
'a.avatar'
])
->order('a.id', 'desc')
->page($page, $limit)
->select();
return ResponseHelper::success([
'total' => $total,
'list' => $list,
]);
}
}

View File

@@ -0,0 +1,832 @@
<?php
namespace app\chukebao\controller;
use app\ai\controller\CozeAI;
use app\ai\controller\DouBaoAI;
use app\api\model\WechatFriendModel;
use app\chukebao\controller\TokensRecordController as tokensRecord;
use app\chukebao\model\AiSettings;
use app\chukebao\model\FriendSettings;
use app\chukebao\model\TokensCompany;
use library\ResponseHelper;
use think\Db;
use think\facade\Cache;
use think\facade\Log;
/**
* AI聊天控制器
* 负责处理与好友的AI对话功能
*/
class AiChatController extends BaseController
{
// 对话状态常量
const STATUS_CREATED = 'created'; // 对话已创建
const STATUS_IN_PROGRESS = 'in_progress'; // 智能体正在处理中
const STATUS_COMPLETED = 'completed'; // 智能体已完成处理
const STATUS_FAILED = 'failed'; // 对话失败
const STATUS_REQUIRES_ACTION = 'requires_action'; // 对话中断,需要进一步处理
const STATUS_CANCELED = 'canceled'; // 对话已取消
// 轮询配置
const MAX_RETRY_TIMES = 1000; // 最大重试次数
const RETRY_INTERVAL = 500000; // 重试间隔微秒即500毫秒
// 并发控制
const CACHE_EXPIRE = 30; // 缓存过期时间(秒)
// 请求唯一标识符
private $requestKey = '';
private $requestId = '';
private $currentStep = 0;
/**
* AI聊天主入口
*
* @return \think\response\Json
*/
public function index()
{
try {
// 1. 参数验证和初始化
$params = $this->validateAndInitParams();
if ($params === false) {
return ResponseHelper::error('参数验证失败');
}
// 并发控制:检查并处理同一用户的重复请求
$this->requestKey = "aichat_{$params['friendId']}_{$params['wechatAccountId']}";
$this->requestId = uniqid('req_', true);
$concurrentCheck = $this->handleConcurrentRequest($params);
if ($concurrentCheck !== true) {
return $concurrentCheck; // 返回错误响应
}
$this->currentStep = 1;
// 2. 验证Tokens余额
$this->updateRequestStep(2);
if ($this->isRequestCanceled()) {
return ResponseHelper::error('该好友有新的AI对话请求正在处理中当前请求已被取消');
}
$hasBalance = $this->checkTokensBalance($params['companyId']);
if (!$hasBalance) {
$this->clearRequestCache();
return ResponseHelper::error('Tokens余额不足请充值后再试');
}
// 3. 获取AI配置
$this->updateRequestStep(3);
if ($this->isRequestCanceled()) {
return ResponseHelper::error('该好友有新的AI对话请求正在处理中当前请求已被取消');
}
$setting = $this->getAiSettings($params['companyId']);
if (!$setting) {
$this->clearRequestCache();
return ResponseHelper::error('未找到AI配置信息请先配置AI策略');
}
// 4. 获取好友AI设置
$this->updateRequestStep(4);
if ($this->isRequestCanceled()) {
return ResponseHelper::error('该好友有新的AI对话请求正在处理中当前请求已被取消');
}
$friendSettings = $this->getFriendSettings($params['companyId'], $params['friendId']);
if (!$friendSettings) {
$this->clearRequestCache();
return ResponseHelper::error('该好友未配置或未开启AI功能');
}
// 5. 确保会话存在
$this->updateRequestStep(5);
if ($this->isRequestCanceled()) {
return ResponseHelper::error('该好友有新的AI对话请求正在处理中当前请求已被取消');
}
$conversationId = $this->ensureConversation($friendSettings, $setting, $params);
if (empty($conversationId)) {
$this->clearRequestCache();
return ResponseHelper::error('创建会话失败');
}
// 6. 获取历史消息
$this->updateRequestStep(6);
if ($this->isRequestCanceled()) {
return ResponseHelper::error('该好友有新的AI对话请求正在处理中当前请求已被取消');
}
$msgData = $this->getHistoryMessages($params['friendId'], $friendSettings);
// 7. 创建AI对话从这步开始需要保存对话ID以便取消
$this->updateRequestStep(7);
if ($this->isRequestCanceled($conversationId, null)) {
return ResponseHelper::error('该好友有新的AI对话请求正在处理中当前请求已被取消');
}
$chatId = $this->createAiChat($setting, $friendSettings, $msgData);
if (empty($chatId)) {
$this->clearRequestCache();
return ResponseHelper::error('创建对话失败');
}
// 保存对话ID到缓存以便新请求可以取消
$this->updateRequestStep(7, $conversationId, $chatId);
// 8. 等待AI处理完成轮询
$this->updateRequestStep(8, $conversationId, $chatId);
if ($this->isRequestCanceled($conversationId, $chatId)) {
return ResponseHelper::error('该好友有新的AI对话请求正在处理中当前请求已被取消');
}
$chatResult = $this->waitForChatCompletion($conversationId, $chatId);
if (!$chatResult['success']) {
$this->clearRequestCache();
return ResponseHelper::error($chatResult['error']);
}
$chatResult = $chatResult['data'];
// 9. 扣除Tokens
$this->updateRequestStep(9, $conversationId, $chatId);
if ($this->isRequestCanceled($conversationId, $chatId)) {
return ResponseHelper::error('该好友有新的AI对话请求正在处理中当前请求已被取消');
}
$this->consumeTokens($chatResult, $params, $friendSettings);
// 10. 获取对话消息
$this->updateRequestStep(10, $conversationId, $chatId);
if ($this->isRequestCanceled($conversationId, $chatId)) {
return ResponseHelper::error('该好友有新的AI对话请求正在处理中当前请求已被取消');
}
$messages = $this->getChatMessages($conversationId, $chatId);
if (!$messages) {
return ResponseHelper::error('获取对话消息失败');
}
// 筛选type为answer的消息AI回复的内容
$answerContent = '';
foreach ($messages as $msg) {
if (isset($msg['type']) && $msg['type'] === 'answer') {
$answerContent = $msg['content'] ?? '';
break;
}
}
if (empty($answerContent)) {
Log::warning('未找到AI回复内容messages: ' . json_encode($messages));
return ResponseHelper::error('未获取到AI回复内容');
}
// 清理请求缓存
$this->clearRequestCache();
// 返回结果
return ResponseHelper::success(['content' => $answerContent], '对话成功');
} catch (\Exception $e) {
Log::error('AI聊天异常' . $e->getMessage());
// 清理请求缓存
$this->clearRequestCache();
return ResponseHelper::error('系统异常:' . $e->getMessage());
}
}
/**
* 取消AI对话
* 取消当前正在进行的AI对话请求
*
* @return \think\response\Json
*/
public function cancel()
{
try {
// 获取参数
$friendId = $this->request->param('friendId', '');
$wechatAccountId = $this->request->param('wechatAccountId', '');
if (empty($wechatAccountId) || empty($friendId)) {
return ResponseHelper::error('参数缺失');
}
// 生成缓存键
$requestKey = "aichat_{$friendId}_{$wechatAccountId}";
// 获取缓存数据
$cacheData = Cache::get($requestKey);
if (!$cacheData) {
return ResponseHelper::error('当前没有正在进行的AI对话');
}
$requestId = $cacheData['request_id'] ?? '';
$step = $cacheData['step'] ?? 0;
$conversationId = $cacheData['conversation_id'] ?? '';
$chatId = $cacheData['chat_id'] ?? '';
Log::info("手动取消AI对话 - 请求ID: {$requestId}, 步骤: {$step}");
// 如果已经到达步骤7或之后需要调用取消API
if ($step >= 7 && !empty($conversationId) && !empty($chatId)) {
try {
$cozeAI = new CozeAI();
$cancelResult = $cozeAI->cancelConversationChat([
'conversation_id' => $conversationId,
'chat_id' => $chatId,
]);
$result = json_decode($cancelResult, true);
if ($result['code'] != 200) {
Log::error("调用取消API失败 - conversation_id: {$conversationId}, chat_id: {$chatId}, 错误: " . ($result['msg'] ?? '未知错误'));
} else {
Log::info("成功调用取消API - conversation_id: {$conversationId}, chat_id: {$chatId}");
}
} catch (\Exception $e) {
Log::error("调用取消API异常" . $e->getMessage());
}
}
// 清理缓存
Cache::rm($requestKey);
Log::info("已清理AI对话缓存 - 请求ID: {$requestId}");
return ResponseHelper::success([
'canceled_request_id' => $requestId,
'step' => $step
], 'AI对话已取消');
} catch (\Exception $e) {
Log::error('取消AI对话异常' . $e->getMessage());
return ResponseHelper::error('系统异常:' . $e->getMessage());
}
}
/**
* 验证和初始化参数
*
* @return array|false
*/
private function validateAndInitParams()
{
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$friendId = $this->request->param('friendId', '');
$wechatAccountId = $this->request->param('wechatAccountId', '');
if (empty($wechatAccountId) || empty($friendId)) {
return false;
}
return [
'userId' => $userId,
'companyId' => $companyId,
'friendId' => $friendId,
'wechatAccountId' => $wechatAccountId
];
}
/**
* 检查Tokens余额
*
* @param int $companyId 公司ID
* @return bool
*/
private function checkTokensBalance($companyId)
{
$tokens = TokensCompany::where(['companyId' => $companyId])->value('tokens');
return !empty($tokens) && $tokens > 1000;
}
/**
* 获取AI配置
*
* @param int $companyId 公司ID
* @return AiSettings|null
*/
private function getAiSettings($companyId)
{
return AiSettings::where(['companyId' => $companyId])->find();
}
/**
* 获取好友AI设置
*
* @param int $companyId 公司ID
* @param string $friendId 好友ID
* @return FriendSettings|null
*/
private function getFriendSettings($companyId, $friendId)
{
$friendSettings = FriendSettings::where([
'companyId' => $companyId,
'friendId' => $friendId
])->find();
if (empty($friendSettings) || $friendSettings->type == 0) {
return null;
}
return $friendSettings;
}
/**
* 确保会话存在
*
* @param FriendSettings $friendSettings 好友设置
* @param AiSettings $setting AI设置
* @param array $params 参数
* @return string|null 会话ID
*/
private function ensureConversation($friendSettings, $setting, $params)
{
if (!empty($friendSettings->conversationId)) {
return $friendSettings->conversationId;
}
// 创建新会话
$cozeAI = new CozeAI();
$data = [
'bot_id' => $setting->botId,
'name' => '与好友' . $params['friendId'] . '的对话',
'meta_data' => [
'friendId' => (string)$friendSettings->friendId,
'wechatAccountId' => (string)$params['wechatAccountId'],
],
];
$res = $cozeAI->createConversation($data);
$res = json_decode($res, true);
if ($res['code'] != 200) {
Log::error('创建会话失败:' . ($res['msg'] ?? '未知错误'));
return null;
}
// 保存会话ID
$conversationId = $res['data']['id'];
$friendSettings->conversationId = $conversationId;
$friendSettings->conversationTime = time();
$friendSettings->save();
return $conversationId;
}
/**
* 获取历史消息
*
* @param string $friendId 好友ID
* @param FriendSettings $friendSettings 好友设置
* @return array
*/
private function getHistoryMessages($friendId, $friendSettings)
{
$msgData = [];
// 会话创建时间小于1分钟加载最近10条消息
if ($friendSettings->conversationTime >= time() - 60) {
$messages = Db::table('s2_wechat_message')
->where('wechatFriendId', $friendId)
->where('msgType', '<', 50)
->order('wechatTime desc')
->field('id,content,msgType,isSend,wechatTime')
->limit(10)
->select();
// 按时间正序排列
usort($messages, function ($a, $b) {
return $a['wechatTime'] <=> $b['wechatTime'];
});
// 处理聊天数据
foreach ($messages as $val) {
if (empty($val['content'])) {
continue;
}
$msg = [
'role' => empty($val['isSend']) ? 'user' : 'assistant',
'content' => $val['content'],
'type' => empty($val['isSend']) ? 'question' : 'answer',
'content_type' => 'text'
];
$msgData[] = $msg;
}
} else {
// 只加载最新一条用户消息
$message = Db::table('s2_wechat_message')
->where('wechatFriendId', $friendId)
->where('msgType', '<', 50)
->where('isSend', 0)
->order('wechatTime desc')
->field('id,content,msgType,isSend,wechatTime')
->find();
if (!empty($message) && !empty($message['content'])) {
$msgData[] = [
'role' => 'user',
'content' => $message['content'],
'type' => 'question',
'content_type' => 'text'
];
}
}
return $msgData;
}
/**
* 创建AI对话
*
* @param AiSettings $setting AI设置
* @param FriendSettings $friendSettings 好友设置
* @param array $msgData 消息数据
* @return string|null 对话ID
*/
private function createAiChat($setting, $friendSettings, $msgData)
{
$cozeAI = new CozeAI();
$data = [
'bot_id' => $setting->botId,
'uid' => $friendSettings->friendId,
'conversation_id' => $friendSettings->conversationId,
'question' => $msgData,
];
$res = $cozeAI->createChat($data);
$res = json_decode($res, true);
if ($res['code'] != 200) {
Log::error('创建对话失败:' . ($res['msg'] ?? '未知错误'));
return null;
}
return $res['data']['id'];
}
/**
* 等待AI处理完成轮询机制
*
* @param string $conversationId 会话ID
* @param string $chatId 对话ID
* @return array ['success' => bool, 'data' => array|null, 'error' => string]
*/
private function waitForChatCompletion($conversationId, $chatId)
{
$cozeAI = new CozeAI();
$retryCount = 0;
while ($retryCount < self::MAX_RETRY_TIMES) {
// 获取对话状态
$res = $cozeAI->getConversationChat([
'conversation_id' => $conversationId,
'chat_id' => $chatId,
]);
$res = json_decode($res, true);
if ($res['code'] != 200) {
$errorMsg = 'AI接口调用失败' . ($res['msg'] ?? '未知错误');
Log::error($errorMsg);
return ['success' => false, 'data' => null, 'error' => $errorMsg];
}
$status = $res['data']['status'] ?? '';
// 处理不同的状态
switch ($status) {
case self::STATUS_COMPLETED:
// 对话完成,返回结果
return ['success' => true, 'data' => $res['data'], 'error' => ''];
case self::STATUS_IN_PROGRESS:
case self::STATUS_CREATED:
// 继续等待
$retryCount++;
usleep(self::RETRY_INTERVAL);
break;
case self::STATUS_FAILED:
$errorMsg = 'AI对话处理失败';
Log::error($errorMsg . 'chat_id: ' . $chatId);
return ['success' => false, 'data' => null, 'error' => $errorMsg];
case self::STATUS_CANCELED:
$errorMsg = 'AI对话已被取消';
Log::error($errorMsg . 'chat_id: ' . $chatId);
return ['success' => false, 'data' => null, 'error' => $errorMsg];
case self::STATUS_REQUIRES_ACTION:
$errorMsg = 'AI对话需要进一步处理';
Log::warning($errorMsg . 'chat_id: ' . $chatId);
return ['success' => false, 'data' => null, 'error' => $errorMsg];
default:
$errorMsg = 'AI返回未知状态' . $status;
Log::error($errorMsg);
return ['success' => false, 'data' => null, 'error' => $errorMsg];
}
}
// 超时
$errorMsg = 'AI对话处理超时已等待' . (self::MAX_RETRY_TIMES * self::RETRY_INTERVAL / 1000000) . '秒';
Log::error($errorMsg . 'chat_id: ' . $chatId);
return ['success' => false, 'data' => null, 'error' => $errorMsg];
}
/**
* 扣除Tokens
*
* @param array $chatResult 对话结果
* @param array $params 参数
* @param FriendSettings $friendSettings 好友设置
*/
private function consumeTokens($chatResult, $params, $friendSettings)
{
$tokenCount = $chatResult['usage']['token_count'] ?? 0;
if (empty($tokenCount)) {
return;
}
// 获取好友昵称
$nickname = WechatFriendModel::where('id', $friendSettings->friendId)->value('nickname');
$remarks = !empty($nickname) ? '与好友【' . $nickname . '】聊天' : '与好友聊天';
// 扣除Tokens
$tokensRecord = new tokensRecord();
$data = [
'tokens' => $tokenCount * 20,
'type' => 0,
'form' => 13,
'wechatAccountId' => $params['wechatAccountId'],
'friendIdOrGroupId' => $params['friendId'],
'remarks' => $remarks,
];
$tokensRecord->consumeTokens($data);
}
/**
* 获取对话消息
*
* @param string $conversationId 会话ID
* @param string $chatId 对话ID
* @return array|null
*/
private function getChatMessages($conversationId, $chatId)
{
$cozeAI = new CozeAI();
$res = $cozeAI->listConversationMessage([
'conversation_id' => $conversationId,
'chat_id' => $chatId,
]);
$res = json_decode($res, true);
if ($res['code'] != 200) {
Log::error('获取对话消息失败:' . ($res['msg'] ?? '未知错误'));
return null;
}
return $res['data'] ?? [];
}
/**
* 处理并发请求
* 检查是否有同一用户的旧请求正在处理,如果有则取消旧请求
*
* @param array $params 请求参数
* @return true|\think\response\Json true表示可以继续否则返回错误响应
*/
private function handleConcurrentRequest($params)
{
$cacheData = Cache::get($this->requestKey);
if ($cacheData) {
// 有旧请求正在处理
$oldRequestId = $cacheData['request_id'] ?? '';
$oldStep = $cacheData['step'] ?? 0;
$oldConversationId = $cacheData['conversation_id'] ?? '';
$oldChatId = $cacheData['chat_id'] ?? '';
Log::info("检测到并发请求 - 旧请求: {$oldRequestId} (步骤{$oldStep}), 新请求: {$this->requestId}");
// 如果旧请求已经到达步骤7或之后需要调用取消API
if ($oldStep >= 7 && !empty($oldConversationId) && !empty($oldChatId)) {
try {
$cozeAI = new CozeAI();
$cancelResult = $cozeAI->cancelConversationChat([
'conversation_id' => $oldConversationId,
'chat_id' => $oldChatId,
]);
Log::info("已调用取消API取消旧请求的对话 - conversation_id: {$oldConversationId}, chat_id: {$oldChatId}");
} catch (\Exception $e) {
Log::error("取消旧请求对话失败:" . $e->getMessage());
}
}
// 标记旧请求为已取消(通过更新缓存的 canceled 标志)
$cacheData['canceled'] = true;
$cacheData['canceled_by'] = $this->requestId;
Cache::set($this->requestKey, $cacheData, self::CACHE_EXPIRE);
}
// 设置当前请求为活动请求
$newCacheData = [
'request_id' => $this->requestId,
'step' => 1,
'start_time' => time(),
'canceled' => false,
'conversation_id' => '',
'chat_id' => '',
];
Cache::set($this->requestKey, $newCacheData, self::CACHE_EXPIRE);
return true;
}
/**
* 检查当前请求是否被新请求取消
*
* @param string $conversationId 会话ID可选用于取消对话
* @param string $chatId 对话ID可选用于取消对话
* @return bool
*/
private function isRequestCanceled($conversationId = '', $chatId = '')
{
$cacheData = Cache::get($this->requestKey);
if (!$cacheData) {
// 缓存不存在,说明被清理或过期,视为被取消
return true;
}
$currentRequestId = $cacheData['request_id'] ?? '';
$isCanceled = $cacheData['canceled'] ?? false;
// 如果缓存中的请求ID与当前请求ID不一致或者被标记为取消
if ($currentRequestId !== $this->requestId || $isCanceled) {
Log::info("当前请求已被取消 - 请求ID: {$this->requestId}, 缓存请求ID: {$currentRequestId}, 取消标志: " . ($isCanceled ? 'true' : 'false'));
// 如果提供了对话ID尝试取消对话
if (!empty($conversationId) && !empty($chatId) && $this->currentStep >= 7) {
try {
$cozeAI = new CozeAI();
$cancelResult = $cozeAI->cancelConversationChat([
'conversation_id' => $conversationId,
'chat_id' => $chatId,
]);
Log::info("已取消当前请求的对话 - conversation_id: {$conversationId}, chat_id: {$chatId}");
} catch (\Exception $e) {
Log::error("取消当前请求对话失败:" . $e->getMessage());
}
}
return true;
}
return false;
}
/**
* 更新请求步骤
*
* @param int $step 当前步骤
* @param string $conversationId 会话ID可选
* @param string $chatId 对话ID可选
*/
private function updateRequestStep($step, $conversationId = '', $chatId = '')
{
$this->currentStep = $step;
$cacheData = Cache::get($this->requestKey);
if ($cacheData && $cacheData['request_id'] === $this->requestId) {
$cacheData['step'] = $step;
$cacheData['update_time'] = time();
if (!empty($conversationId)) {
$cacheData['conversation_id'] = $conversationId;
}
if (!empty($chatId)) {
$cacheData['chat_id'] = $chatId;
}
Cache::set($this->requestKey, $cacheData, self::CACHE_EXPIRE);
}
}
/**
* 清理请求缓存
*/
private function clearRequestCache()
{
if (!empty($this->requestKey)) {
$cacheData = Cache::get($this->requestKey);
// 只有当前请求才能清理自己的缓存
if ($cacheData && isset($cacheData['request_id']) && $cacheData['request_id'] === $this->requestId) {
Cache::rm($this->requestKey);
Log::info("已清理请求缓存 - 请求ID: {$this->requestId}");
}
}
}
public function index2222()
{
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$friendId = $this->request->param('friendId', '');
$wechatAccountId = $this->request->param('wechatAccountId', '');
$content = $this->request->param('content', '');
if (empty($wechatAccountId) || empty($friendId)) {
return ResponseHelper::error('参数缺失');
}
$tokens = TokensCompany::where(['companyId' => $companyId])->value('tokens');
if (empty($tokens) || $tokens <= 0) {
return ResponseHelper::error('用户Tokens余额不足');
}
//读取AI配置
$setting = Db::name('ai_settings')->where(['companyId' => $companyId, 'userId' => $userId])->find();
if (empty($setting)) {
return ResponseHelper::error('未找到配置信息请先配置AI策略');
}
$config = json_decode($setting['config'], true);
$modelSetting = $config['modelSetting'];
$round = isset($config['round']) ? $config['round'] : 10;
// 导出聊天
$messages = Db::table('s2_wechat_message')
->where('wechatFriendId', $friendId)
->order('wechatTime desc')
->field('id,content,msgType,isSend,wechatTime')
->limit($round)
->select();
usort($messages, function ($a, $b) {
return $a['wechatTime'] <=> $b['wechatTime'];
});
//处理聊天数据
$msg = [];
foreach ($messages as $val) {
if (empty($val['content'])) {
continue;
}
if (!empty($val['isSend'])) {
$msg[] = '客服:' . $val['content'];
} else {
$msg[] = '用户:' . $val['content'];
}
}
$content = implode("\n", $msg);
$params = [
'model' => 'doubao-1-5-pro-32k-250115',
'messages' => [
// ['role' => 'system', 'content' => '请完成跟客户的对话'],
['role' => 'system', 'content' => '角色设定:' . $modelSetting['role']],
['role' => 'system', 'content' => '公司背景:' . $modelSetting['businessBackground']],
['role' => 'system', 'content' => '对话风格:' . $modelSetting['dialogueStyle']],
['role' => 'user', 'content' => $content],
],
];
//AI处理
$ai = new DouBaoAI();
$res = $ai->text($params);
$res = json_decode($res, true);
if ($res['code'] == 200) {
//扣除Tokens
$tokensRecord = new tokensRecord();
$nickname = Db::table('s2_wechat_friend')->where(['id' => $friendId])->value('nickname');
$remarks = !empty($nickname) ? '与好友【' . $nickname . '】聊天' : '与好友聊天';
$data = [
'tokens' => $res['data']['token'],
'type' => 0,
'form' => 13,
'wechatAccountId' => $wechatAccountId,
'friendIdOrGroupId' => $friendId,
'remarks' => $remarks,
];
$tokensRecord->consumeTokens($data);
return ResponseHelper::success($res['data']['content']);
} else {
return ResponseHelper::error($res['msg']);
}
}
}

View File

@@ -0,0 +1,505 @@
<?php
namespace app\chukebao\controller;
use app\chukebao\model\AiPush;
use app\chukebao\model\AiPushRecord;
use app\chukebao\model\AutoGreetings;
use library\ResponseHelper;
use think\Db;
class AiPushController extends BaseController
{
/**
* 获取推送列表
* @return \think\response\Json
* @throws \Exception
*/
public function getList()
{
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$where = [
['companyId', '=', $companyId],
['userId', '=', $userId],
['isDel', '=', 0],
];
if (!empty($keyword)) {
$where[] = ['name', 'like', '%' . $keyword . '%'];
}
$query = AiPush::where($where);
$total = $query->count();
$list = $query->where($where)->page($page, $limit)->order('id desc')->select();
// 处理数据
$list = is_array($list) ? $list : $list->toArray();
foreach ($list as &$item) {
// 解析标签数组
$item['tags'] = json_decode($item['tags'], true);
if (!is_array($item['tags'])) {
$item['tags'] = [];
}
// 格式化推送时机显示文本
$timingTypes = [
1 => '立即推送',
2 => 'AI最佳时机',
3 => '定时推送'
];
$item['timingText'] = $timingTypes[$item['pushTiming']] ?? '未知';
// 处理定时推送时间
if ($item['pushTiming'] == 3 && !empty($item['scheduledTime'])) {
$item['scheduledTime'] = date('Y-m-d H:i:s', $item['scheduledTime']);
} else {
$item['scheduledTime'] = '';
}
// 从记录表计算实际成功率
$pushId = $item['id'];
$totalCount = Db::name('kf_ai_push_record')
->where('pushId', $pushId)
->count();
$sendCount = Db::name('kf_ai_push_record')
->where('pushId', $pushId)
->where('isSend', 1)
->count();
$item['successRate'] = $totalCount > 0 ? round(($sendCount * 100) / $totalCount, 1) : 0;
$item['totalPushCount'] = $totalCount; // 推送总数
$item['sendCount'] = $sendCount; // 成功发送数
}
unset($item);
return ResponseHelper::success(['list' => $list, 'total' => $total]);
}
/**
* 添加
* @return \think\response\Json
* @throws \Exception
*/
public function add()
{
$name = $this->request->param('name', '');
$tags = $this->request->param('tags', ''); // 标签,支持逗号分隔的字符串或数组
$content = $this->request->param('content', '');
$pushTiming = $this->request->param('pushTiming', 1); // 1=立即推送2=最佳时机(AI决定)3=定时推送
$scheduledTime = $this->request->param('scheduledTime', ''); // 定时推送的时间
$status = $this->request->param('status', 1);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($name) || empty($content)) {
return ResponseHelper::error('推送名称和推送内容不能为空');
}
// 验证推送时机
if (!in_array($pushTiming, [1, 2, 3])) {
return ResponseHelper::error('无效的推送时机类型');
}
// 如果是定时推送,需要验证时间
if ($pushTiming == 3) {
if (empty($scheduledTime)) {
return ResponseHelper::error('定时推送需要设置推送时间');
}
// 验证时间格式
$timestamp = strtotime($scheduledTime);
if ($timestamp === false || $timestamp <= time()) {
return ResponseHelper::error('定时推送时间格式不正确或必须大于当前时间');
}
} else {
$scheduledTime = '';
}
// 处理标签
$tagsArray = [];
if (!empty($tags)) {
if (is_string($tags)) {
// 如果是字符串,按逗号分割
$tagsArray = array_filter(array_map('trim', explode(',', $tags)));
} elseif (is_array($tags)) {
$tagsArray = array_filter(array_map('trim', $tags));
}
}
if (empty($tagsArray)) {
return ResponseHelper::error('目标用户标签不能为空');
}
Db::startTrans();
try {
$aiPush = new AiPush();
$aiPush->name = $name;
$aiPush->tags = json_encode($tagsArray, JSON_UNESCAPED_UNICODE);
$aiPush->content = $content;
$aiPush->pushTiming = $pushTiming;
$aiPush->scheduledTime = $pushTiming == 3 && !empty($scheduledTime) ? strtotime($scheduledTime) : 0;
$aiPush->status = $status;
$aiPush->successRate = 0; // 初始成功率为0
$aiPush->userId = $userId;
$aiPush->companyId = $companyId;
$aiPush->createTime = time();
$aiPush->updateTime = time();
$aiPush->save();
Db::commit();
return ResponseHelper::success(['id' => $aiPush->id], '创建成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('创建失败:' . $e->getMessage());
}
}
/**
* 详情
* @return \think\response\Json
* @throws \Exception
*/
public function details()
{
$id = $this->request->param('id', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)) {
return ResponseHelper::error('参数缺失');
}
$data = AiPush::where(['id' => $id, 'isDel' => 0, 'userId' => $userId, 'companyId' => $companyId])->find();
if (empty($data)) {
return ResponseHelper::error('该推送已被删除或者不存在');
}
$data = $data->toArray();
// 解析标签数组
$data['tags'] = json_decode($data['tags'], true);
if (!is_array($data['tags'])) {
$data['tags'] = [];
}
// 标签转为逗号分隔的字符串(用于编辑时回显)
$data['tagsString'] = implode(',', $data['tags']);
// 处理定时推送时间
if ($data['pushTiming'] == 3 && !empty($data['scheduledTime'])) {
$data['scheduledTime'] = date('Y-m-d H:i:s', $data['scheduledTime']);
} else {
$data['scheduledTime'] = '';
}
// 成功率保留一位小数
$data['successRate'] = isset($data['successRate']) ? round($data['successRate'], 1) : 0;
return ResponseHelper::success($data, '获取成功');
}
/**
* 删除
* @return \think\response\Json
* @throws \Exception
*/
public function del()
{
$id = $this->request->param('id', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)) {
return ResponseHelper::error('参数缺失');
}
$data = AiPush::where(['id' => $id, 'isDel' => 0, 'userId' => $userId, 'companyId' => $companyId])->find();
if (empty($data)) {
return ResponseHelper::error('该推送已被删除或者不存在');
}
Db::startTrans();
try {
$data->isDel = 1;
$data->delTime = time();
$data->save();
Db::commit();
return ResponseHelper::success('', '删除成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('删除失败:' . $e->getMessage());
}
}
/**
* 更新
* @return \think\response\Json
* @throws \Exception
*/
public function update()
{
$id = $this->request->param('id', '');
$name = $this->request->param('name', '');
$tags = $this->request->param('tags', '');
$content = $this->request->param('content', '');
$pushTiming = $this->request->param('pushTiming', 1);
$scheduledTime = $this->request->param('scheduledTime', '');
$status = $this->request->param('status', 1);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id) || empty($name) || empty($content)) {
return ResponseHelper::error('参数缺失');
}
// 验证推送时机
if (!in_array($pushTiming, [1, 2, 3])) {
return ResponseHelper::error('无效的推送时机类型');
}
// 如果是定时推送,需要验证时间
if ($pushTiming == 3) {
if (empty($scheduledTime)) {
return ResponseHelper::error('定时推送需要设置推送时间');
}
// 验证时间格式
$timestamp = strtotime($scheduledTime);
if ($timestamp === false || $timestamp <= time()) {
return ResponseHelper::error('定时推送时间格式不正确或必须大于当前时间');
}
} else {
$scheduledTime = '';
}
// 处理标签
$tagsArray = [];
if (!empty($tags)) {
if (is_string($tags)) {
$tagsArray = array_filter(array_map('trim', explode(',', $tags)));
} elseif (is_array($tags)) {
$tagsArray = array_filter(array_map('trim', $tags));
}
}
if (empty($tagsArray)) {
return ResponseHelper::error('目标用户标签不能为空');
}
$query = AiPush::where(['id' => $id, 'isDel' => 0, 'userId' => $userId, 'companyId' => $companyId])->find();
if (empty($query)) {
return ResponseHelper::error('该推送已被删除或者不存在');
}
Db::startTrans();
try {
$query->name = $name;
$query->tags = json_encode($tagsArray, JSON_UNESCAPED_UNICODE);
$query->content = $content;
$query->pushTiming = $pushTiming;
$query->scheduledTime = $pushTiming == 3 && !empty($scheduledTime) ? strtotime($scheduledTime) : 0;
$query->status = $status;
$query->updateTime = time();
$query->save();
Db::commit();
return ResponseHelper::success('', '修改成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('修改失败:' . $e->getMessage());
}
}
/**
* 修改状态
* @return \think\response\Json
* @throws \Exception
*/
public function setStatus()
{
$id = $this->request->param('id', '');
$status = $this->request->param('status', 1);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)) {
return ResponseHelper::error('参数缺失');
}
if (!in_array($status, [0, 1])) {
return ResponseHelper::error('状态值无效');
}
$data = AiPush::where(['id' => $id, 'isDel' => 0, 'userId' => $userId, 'companyId' => $companyId])->find();
if (empty($data)) {
return ResponseHelper::error('该推送已被删除或者不存在');
}
Db::startTrans();
try {
$data->status = $status;
$data->updateTime = time();
$data->save();
Db::commit();
return ResponseHelper::success('', $status == 1 ? '启用成功' : '禁用成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('操作失败:' . $e->getMessage());
}
}
/**
* 统计概览整合自动问候和AI推送
* - 活跃规则自动问候规则近30天
* - 总触发次数(自动问候记录总数)
* - AI推送成功率AI推送的成功率
* - AI智能推送AI推送规则近30天活跃
* - 规则效果排行(自动问候规则,按使用次数排序)
* @return \think\response\Json
*/
public function stats()
{
$companyId = $this->getUserInfo('companyId');
$userId = $this->getUserInfo('id');
$start30d = time() - 30 * 24 * 3600;
try {
// 公司维度(用于除排行外的统计)
$companyWhere = [
['companyId', '=', $companyId],
];
// 排行维度(限定个人)
$rankingWhere = [
['companyId', '=', $companyId],
['userId', '=', $userId],
];
// ========== 自动问候统计 ==========
// 1) 活跃规则自动问候规则近30天有记录的
$activeRules = Db::name('kf_auto_greetings_record')
->where($companyWhere)
->where('createTime', '>=', $start30d)
->distinct(true)
->count('autoId');
// 2) 总触发次数(自动问候记录总数)
$totalTriggers = Db::name('kf_auto_greetings_record')
->where($companyWhere)
->count();
// ========== AI推送统计 ==========
// 3) AI推送成功率
$totalPushes = Db::name('kf_ai_push_record')
->where($companyWhere)
->count();
$sendCount = Db::name('kf_ai_push_record')
->where($companyWhere)
->where('isSend', '=', 1)
->count();
// 成功率百分比保留整数75%
$aiPushSuccessRate = $totalPushes > 0 ? round(($sendCount * 100) / $totalPushes, 0) : 0;
// 4) AI智能推送AI推送规则近30天活跃的
$aiPushCount = Db::name('kf_ai_push_record')
->where($companyWhere)
->where('createTime', '>=', $start30d)
->distinct(true)
->count('pushId');
// ========== 规则效果排行(自动问候规则,按使用次数排序)==========
$ruleRanking = Db::name('kf_auto_greetings_record')
->where($rankingWhere)
->field([
'autoId AS id',
'COUNT(*) AS usageCount'
])
->group('autoId')
->order('usageCount DESC')
->limit(20)
->select();
// 附加规则名称和触发类型
$autoIds = array_values(array_unique(array_column($ruleRanking, 'id')));
$autoIdToRule = [];
if (!empty($autoIds)) {
$rules = AutoGreetings::where([['id', 'in', $autoIds]])
->field('id,name,trigger')
->select();
foreach ($rules as $rule) {
$triggerTypes = [
1 => '新好友',
2 => '首次发消息',
3 => '时间触发',
4 => '关键词',
5 => '生日触发',
6 => '自定义'
];
$autoIdToRule[$rule['id']] = [
'name' => $rule['name'],
'trigger' => $rule['trigger'],
'triggerText' => $triggerTypes[$rule['trigger']] ?? '未知',
];
}
}
foreach ($ruleRanking as &$row) {
$row['usageCount'] = (int)($row['usageCount'] ?? 0);
$row['name'] = $autoIdToRule[$row['id']]['name'] ?? '';
$row['trigger'] = $autoIdToRule[$row['id']]['trigger'] ?? null;
$row['triggerText'] = $autoIdToRule[$row['id']]['triggerText'] ?? '';
// 格式化使用次数显示
$row['usageCountText'] = $row['usageCount'] . ' 次';
}
unset($row);
// 更新主表中的成功率字段(异步或定期更新)
$this->updatePushSuccessRate($companyId);
return ResponseHelper::success([
'activeRules' => (int)$activeRules,
'totalTriggers' => (int)$totalTriggers,
'aiPushSuccessRate' => (int)$aiPushSuccessRate,
'aiPushCount' => (int)$aiPushCount,
'ruleRanking' => $ruleRanking,
], '统计成功');
} catch (\Exception $e) {
return ResponseHelper::error('统计失败:' . $e->getMessage());
}
}
/**
* 更新推送表的成功率字段
* @param int $companyId
* @return void
*/
private function updatePushSuccessRate($companyId)
{
try {
// 获取所有启用的推送
$pushes = AiPush::where([
['companyId', '=', $companyId],
['isDel', '=', 0]
])->field('id')->select();
foreach ($pushes as $push) {
$pushId = $push['id'];
$totalCount = Db::name('kf_ai_push_record')
->where('pushId', $pushId)
->count();
$sendCount = Db::name('kf_ai_push_record')
->where('pushId', $pushId)
->where('isSend', 1)
->count();
$successRate = $totalCount > 0 ? round(($sendCount * 100) / $totalCount, 2) : 0.00;
AiPush::where('id', $pushId)->update([
'successRate' => $successRate,
'updateTime' => time()
]);
}
} catch (\Exception $e) {
// 静默失败,不影响主流程
}
}
}

View File

@@ -0,0 +1,266 @@
<?php
namespace app\chukebao\controller;
use app\chukebao\model\FriendSettings;
use app\chukebao\model\Questions;
use library\ResponseHelper;
use think\Db;
class AiSettingsController extends BaseController
{
const SETTING_DEFAULT = [
'audioSetting' => false,
'round' => 10,
'aiStopSetting' => [
'status' => true,
'key' => ['好', '不错', '好的', '下次', '可以']
],
'fileSetting' => [
'type' => 1,
'content' => ''
],
'modelSetting' => [
'model' => 'GPT-4',
'role' => '你是一名销售的AI助理同时也是一个工智能技术专家你的名字叫小灵你是单身女性出生于2003年10月10日喜欢听音乐和看电影有着丰富的人生阅历前成熟大方分享用幽默风趣的语言和客户交流顾客问起你的感情回复内容中不要使用号特别注意不要跟客户问题不要更多选择发送的信息。',
'businessBackground' => '灵销智能公司开发了多款AI营销智能技术产品以提升销售GPT AI大模型为核心接入打造的销售/营销/客服等AI智能应用为企业AI办公AI助理AI销售AI营销AI直播等大AI应用产品。',
'dialogueStyle' => '客户你们的AI解决方案具体是怎么收费的销售朋友我们的AI解决方案是根据项目需求来定的这样吧你能跟我说说你们的具体情况吗不过这样一分钱您看怎么样我们可以给您做个详细的方案对比。',
]
];
const TYPE_DATA = ['audioSetting', 'round', 'aiStopSetting', 'fileSetting', 'modelSetting'];
/**
* 获取配置信息
* @return \think\response\Json
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function getSetting()
{
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$data = Db::name('ai_settings')->where(['userId' => $userId, 'companyId' => $companyId])->find();
if (empty($data)) {
$setting = self::SETTING_DEFAULT;
$data = [
'companyId' => $companyId,
'userId' => $userId,
'config' => json_encode($setting, 256),
'createTime' => time(),
'updateTime' => time()
];
Db::name('ai_settings')->insert($data);
} else {
$setting = json_decode($data['config'], true);
}
return ResponseHelper::success($setting, '获取成功');
}
/**
* 配置
* @return \think\response\Json
* @throws \Exception
*/
public function setSetting()
{
$key = $this->request->param('key', '');
$value = $this->request->param('value', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($key) || empty($value)) {
return ResponseHelper::error('参数缺失');
}
if (!in_array($key, self::TYPE_DATA)) {
return ResponseHelper::error('该类型不在配置项');
}
Db::startTrans();
try {
$data = Db::name('ai_settings')->where(['userId' => $userId, 'companyId' => $companyId])->find();
if (empty($data)) {
$setting = self::SETTING_DEFAULT;
} else {
$setting = json_decode($data['config'], true);
}
$setting[$key] = $value;
$setting = json_encode($setting, 256);
Db::name('ai_settings')->where(['id' => $data['id']])->update(['config' => $setting, 'updateTime' => time()]);
Db::commit();
return ResponseHelper::success(' ', '配置成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('配置失败:' . $e->getMessage());
}
}
public function getUserTokens()
{
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$tokens = Db::name('users')
->where('id', $userId)
->where('companyId', $companyId)
->value('tokens');
return ResponseHelper::success($tokens, '获取成功');
}
public function getFriend()
{
$friendId = $this->request->param('friendId', '');
$wechatAccountId = $this->request->param('wechatAccountId', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$aiType = FriendSettings::where(['userId' => $userId, 'companyId' => $companyId,'friendId' => $friendId,'wechatAccountId' => $wechatAccountId])->value('type');
if (empty($aiType)) {
$aiType = 0;
}
return ResponseHelper::success($aiType, '获取成功');
}
public function setFriend()
{
$friendId = $this->request->param('friendId', '');
$wechatAccountId = $this->request->param('wechatAccountId', '');
$type = $this->request->param('type', 0);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($friendId) || empty($wechatAccountId)) {
return ResponseHelper::error('参数缺失');
}
$friend = Db::table('s2_wechat_friend')->where(['id' => $friendId,'wechatAccountId' => $wechatAccountId])->find();
if (empty($friend)) {
return ResponseHelper::error('该好友不存在');
}
$friendSettings = FriendSettings::where(['userId' => $userId, 'companyId' => $companyId,'friendId' => $friendId,'wechatAccountId' => $wechatAccountId])->find();
Db::startTrans();
try {
if (empty($friendSettings)) {
$friendSettings = new FriendSettings();
$friendSettings->companyId = $companyId;
$friendSettings->userId = $userId;
$friendSettings->type = $type;
$friendSettings->wechatAccountId = $wechatAccountId;
$friendSettings->friendId = $friendId;
$friendSettings->createTime = time();
$friendSettings->updateTime = time();
}else{
$friendSettings->type = $type;
$friendSettings->updateTime = time();
}
$friendSettings->save();
Db::commit();
return ResponseHelper::success(' ', '配置成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('配置失败:' . $e->getMessage());
}
}
public function setAllFriend()
{
$packageId = $this->request->param('packageId', []);
$type = $this->request->param('type', 0);
$isUpdata = $this->request->param('isUpdata', 0);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($packageId)) {
return ResponseHelper::error('参数缺失');
}
//列出所有好友
$row = Db::name('traffic_source_package_item')->alias('a')
->join('wechat_friendship f','a.identifier = f.wechatId and f.companyId = '.$companyId)
->join(['s2_wechat_account' => 'wa'],'f.ownerWechatId = wa.wechatId')
->whereIn('a.packageId' , $packageId)
->field('f.id as friendId,wa.id as wechatAccountId')
->group('f.id')
->select();
if (empty($row)) {
return ResponseHelper::error('`好友不存在');
}
// 1000条为一组进行批量处理
$batchSize = 1000;
$totalRows = count($row);
for ($i = 0; $i < $totalRows; $i += $batchSize) {
$batchRows = array_slice($row, $i, $batchSize);
if (!empty($batchRows)) {
// 1. 提取当前批次的phone
$friendIds = array_column($batchRows, 'friendId');
// 2. 批量查询已存在的phone
$existingPhones = [];
if (!empty($friendIds)) {
//强制更新
if(!empty($isUpdata)){
FriendSettings::whereIn('friendId',$friendIds)->update(['type' => $type,'updateTime' => time()]);
}
$existing = FriendSettings::where('companyId', $companyId)->where('friendId', 'in', $friendIds)->field('friendId')->select()->toArray();
$existingPhones = array_column($existing, 'friendId');
}
// 3. 过滤出新数据,批量插入
$newData = [];
foreach ($batchRows as $row) {
if (!empty($friendIds) && !in_array($row['friendId'], $existingPhones)) {
$newData[] = [
'companyId' => $companyId,
'userId' => $userId,
'type' => $type,
'wechatAccountId' => $row['wechatAccountId'],
'friendId' => $row['friendId'],
'createTime' => time(),
'updateTime' => time(),
];
}
}
// 4. 批量插入新数据
if (!empty($newData)) {
FriendSettings::insertAll($newData);
}
}
}
try {
return ResponseHelper::success(' ', '配置成功');
} catch (\Exception $e) {
return ResponseHelper::error('配置失败:' . $e->getMessage());
}
}
}

View File

@@ -0,0 +1,754 @@
<?php
namespace app\chukebao\controller;
use app\chukebao\model\AutoGreetings;
use library\ResponseHelper;
use think\Db;
class AutoGreetingsController extends BaseController
{
/**
* 获取问候规则列表
* @return \think\response\Json
*/
public function getList(){
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$is_template = $this->request->param('is_template', 0);
$triggerType = $this->request->param('triggerType', ''); // 触发类型筛选
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if($is_template == 1){
$where = [
['is_template','=',1],
['isDel' ,'=', 0],
];
}else{
$where = [
['companyId','=',$companyId],
['userId' ,'=', $userId],
['isDel' ,'=', 0],
];
}
if(!empty($keyword)){
$where[] = ['name','like','%'.$keyword.'%'];
}
if(!empty($triggerType)){
$where[] = ['trigger','=',$triggerType];
}
$query = AutoGreetings::where($where);
$total = $query->count();
$list = $query->where($where)->page($page,$limit)->order('level asc,id desc')->select();
// 获取使用次数
$list = is_array($list) ? $list : $list->toArray();
$ids = array_column($list, 'id');
$usageCounts = [];
if (!empty($ids)) {
$counts = Db::name('kf_auto_greetings_record')
->where('autoId', 'in', $ids)
->field('autoId, COUNT(*) as count')
->group('autoId')
->select();
foreach ($counts as $count) {
$usageCounts[$count['autoId']] = (int)$count['count'];
}
}
foreach ($list as &$item) {
$item['condition'] = json_decode($item['condition'], true);
$item['usageCount'] = $usageCounts[$item['id']] ?? 0;
// 格式化触发类型显示文本
$triggerTypes = [
1 => '新好友',
2 => '首次发消息',
3 => '时间触发',
4 => '关键词触发',
5 => '生日触发',
6 => '自定义'
];
$item['triggerText'] = $triggerTypes[$item['trigger']] ?? '未知';
}
unset($item);
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
}
/**
* 校验trigger类型对应的condition
* @param int $trigger 触发类型
* @param mixed $condition 条件参数
* @return array|string 返回处理后的condition数组或错误信息字符串
*/
private function validateTriggerCondition($trigger, $condition)
{
// trigger类型1=新好友2=首次发消息3=时间触发4=关键词触发5=生日触发6=自定义
switch ($trigger) {
case 1: // 新好友
// 不需要condition
return [];
case 2: // 首次发消息
// 不需要condition
return [];
case 3: // 时间触发
// 需要condition格式为{"type": "daily_time|yearly_datetime|fixed_range|workday", "value": "..."}
if (empty($condition)) {
return '时间触发类型需要配置具体的触发条件';
}
$condition = is_array($condition) ? $condition : json_decode($condition, true);
if (empty($condition) || !is_array($condition)) {
return '时间触发类型的条件格式不正确,应为数组格式';
}
// 验证必须包含type字段
if (!isset($condition['type']) || empty($condition['type'])) {
return '时间触发类型必须指定触发方式daily_time每天固定时间、yearly_datetime每年固定日期时间、fixed_range固定时间段、workday工作日';
}
$timeType = $condition['type'];
$allowedTypes = ['daily_time', 'yearly_datetime', 'fixed_range', 'workday'];
// 兼容旧版本的 fixed_time自动转换为 daily_time
if ($timeType === 'fixed_time') {
$timeType = 'daily_time';
}
if (!in_array($timeType, $allowedTypes)) {
return '时间触发类型无效必须为daily_time每天固定时间、yearly_datetime每年固定日期时间、fixed_range固定时间段、workday工作日';
}
// 根据不同的type验证value
switch ($timeType) {
case 'daily_time': // 每天固定时间(每天的几点几分)
// value应该是时间字符串格式HH:mm如 "14:30"
if (!isset($condition['value']) || empty($condition['value'])) {
return '每天固定时间类型需要配置具体时间格式HH:mm如 14:30';
}
$timeValue = $condition['value'];
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $timeValue)) {
return '每天固定时间格式不正确,应为 HH:mm 格式(如 14:30';
}
return [
'type' => 'daily_time',
'value' => $timeValue
];
case 'yearly_datetime': // 每年固定日期时间(每年的几月几号几点几分)
// value应该是日期时间字符串格式MM-dd HH:mm如 "12-25 14:30"
if (!isset($condition['value']) || empty($condition['value'])) {
return '每年固定日期时间类型需要配置具体日期和时间格式MM-dd HH:mm如 12-25 14:30';
}
$datetimeValue = $condition['value'];
// 验证格式MM-dd HH:mm
if (!preg_match('/^(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) ([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $datetimeValue)) {
return '每年固定日期时间格式不正确,应为 MM-dd HH:mm 格式(如 12-25 14:30';
}
// 进一步验证日期是否有效例如2月30日不存在
list($datePart, $timePart) = explode(' ', $datetimeValue);
list($month, $day) = explode('-', $datePart);
if (!checkdate((int)$month, (int)$day, 2000)) { // 使用2000年作为参考年份验证日期有效性
return '日期无效请检查月份和日期是否正确如2月不能有30日';
}
return [
'type' => 'yearly_datetime',
'value' => $datetimeValue
];
case 'fixed_range': // 固定时间段
// value应该是时间段数组格式["09:00", "18:00"]
if (!isset($condition['value']) || !is_array($condition['value'])) {
return '固定时间段类型需要配置时间段,格式:["开始时间", "结束时间"](如 ["09:00", "18:00"]';
}
$rangeValue = $condition['value'];
if (count($rangeValue) !== 2) {
return '固定时间段应为包含两个时间点的数组,格式:["09:00", "18:00"]';
}
// 验证时间格式
foreach ($rangeValue as $time) {
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $time)) {
return '时间段格式不正确,应为 HH:mm 格式(如 09:00';
}
}
// 验证开始时间小于结束时间
$startTime = strtotime('2000-01-01 ' . $rangeValue[0]);
$endTime = strtotime('2000-01-01 ' . $rangeValue[1]);
if ($startTime >= $endTime) {
return '开始时间必须小于结束时间';
}
return [
'type' => 'fixed_range',
'value' => $rangeValue
];
case 'workday': // 工作日
// 工作日需要配置时间格式HH:mm如 09:00
if (!isset($condition['value']) || empty($condition['value'])) {
return '工作日触发类型需要配置时间格式HH:mm如 09:00';
}
$timeValue = trim($condition['value']);
// 验证格式HH:mm
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $timeValue)) {
return '工作日时间格式不正确,应为 HH:mm 格式(如 09:00';
}
return [
'type' => 'workday',
'value' => $timeValue
];
default:
return '时间触发类型无效';
}
case 4: // 关键词触发
// 需要condition格式{"keywords": ["关键词1", "关键词2"], "match_type": "exact|fuzzy"}
if (empty($condition)) {
return '关键词触发类型需要配置至少一个关键词';
}
// 如果是字符串尝试解析JSON
if (is_string($condition)) {
$decoded = json_decode($condition, true);
if (json_last_error() === JSON_ERROR_NONE) {
$condition = $decoded;
} else {
return '关键词触发类型格式错误,应为对象格式:{"keywords": ["关键词1", "关键词2"], "match_type": "exact|fuzzy"}';
}
}
// 必须是对象格式
if (!is_array($condition) || !isset($condition['keywords'])) {
return '关键词触发类型格式错误,应为对象格式:{"keywords": ["关键词1", "关键词2"], "match_type": "exact|fuzzy"}';
}
$keywords = $condition['keywords'];
$matchType = isset($condition['match_type']) ? $condition['match_type'] : 'fuzzy';
// 验证match_type
if (!in_array($matchType, ['exact', 'fuzzy'])) {
return '匹配类型无效必须为exact精准匹配或 fuzzy模糊匹配';
}
// 处理keywords
if (is_string($keywords)) {
$keywords = explode(',', $keywords);
}
if (!is_array($keywords)) {
return '关键词格式不正确,应为数组格式';
}
// 过滤空值并去重
$keywords = array_filter(array_map('trim', $keywords));
if (empty($keywords)) {
return '关键词触发类型需要配置至少一个关键词';
}
// 验证每个关键词不为空
foreach ($keywords as $keyword) {
if (empty($keyword)) {
return '关键词不能为空';
}
}
return [
'keywords' => array_values($keywords),
'match_type' => $matchType
];
case 5: // 生日触发
// 需要condition格式支持
// 1. 月日字符串:'10-10' 或 '10-10 09:00'MM-DD格式不包含年份
// 2. 对象格式:{'month': 10, 'day': 10, 'time': '09:00'} 或 {'month': '10', 'day': '10', 'time_range': ['09:00', '10:00']}
if (empty($condition)) {
return '生日触发类型需要配置日期条件';
}
// 如果是字符串,只接受 MM-DD 格式(不包含年份)
if (is_string($condition)) {
// 检查是否包含时间部分
if (preg_match('/^(\d{1,2})-(\d{1,2})\s+(\d{2}:\d{2})$/', $condition, $matches)) {
// 格式:'10-10 09:00'
$month = (int)$matches[1];
$day = (int)$matches[2];
if ($month < 1 || $month > 12 || $day < 1 || $day > 31) {
return '生日日期格式不正确月份应为1-12日期应为1-31';
}
return [
'month' => $month,
'day' => $day,
'time' => $matches[3]
];
} elseif (preg_match('/^(\d{1,2})-(\d{1,2})$/', $condition, $matches)) {
// 格式:'10-10'(不指定时间,当天任何时间都可以触发)
$month = (int)$matches[1];
$day = (int)$matches[2];
if ($month < 1 || $month > 12 || $day < 1 || $day > 31) {
return '生日日期格式不正确月份应为1-12日期应为1-31';
}
return [
'month' => $month,
'day' => $day
];
} else {
return '生日日期格式不正确,应为 MM-DD 或 MM-DD HH:mm 格式(如 10-10 或 10-10 09:00不包含年份';
}
}
// 如果是数组,可能是对象格式或旧格式
if (is_array($condition)) {
// 检查是否是旧格式(仅兼容 MM-DD 格式的数组)
if (isset($condition[0]) && is_string($condition[0])) {
$dateStr = $condition[0];
// 只接受 MM-DD 格式:'10-10' 或 '10-10 09:00'
if (preg_match('/^(\d{1,2})-(\d{1,2})(?:\s+(\d{2}:\d{2}))?$/', $dateStr, $matches)) {
$month = (int)$matches[1];
$day = (int)$matches[2];
if ($month < 1 || $month > 12 || $day < 1 || $day > 31) {
return '生日日期格式不正确月份应为1-12日期应为1-31';
}
if (isset($matches[3])) {
return [
'month' => $month,
'day' => $day,
'time' => $matches[3]
];
} else {
return [
'month' => $month,
'day' => $day
];
}
} else {
return '生日日期格式不正确,应为 MM-DD 格式(如 10-10不包含年份';
}
}
// 新格式:{'month': 10, 'day': 10, 'time': '09:00'}
if (isset($condition['month']) && isset($condition['day'])) {
$month = (int)$condition['month'];
$day = (int)$condition['day'];
if ($month < 1 || $month > 12) {
return '生日月份格式不正确应为1-12';
}
if ($day < 1 || $day > 31) {
return '生日日期格式不正确应为1-31';
}
$result = [
'month' => $month,
'day' => $day
];
// 检查是否配置了时间
if (isset($condition['time']) && !empty($condition['time'])) {
$time = trim($condition['time']);
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $time)) {
return '生日时间格式不正确,应为 HH:mm 格式(如 09:00';
}
$result['time'] = $time;
}
// 检查是否配置了时间范围
if (isset($condition['time_range']) && is_array($condition['time_range']) && count($condition['time_range']) === 2) {
$startTime = trim($condition['time_range'][0]);
$endTime = trim($condition['time_range'][1]);
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $startTime) ||
!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $endTime)) {
return '生日时间范围格式不正确,应为 ["HH:mm", "HH:mm"] 格式';
}
$result['time_range'] = [$startTime, $endTime];
}
return $result;
}
return '生日触发条件格式不正确需要提供month和day字段';
}
return '生日触发条件格式不正确';
case 6: // 自定义
// 自定义类型condition可选如果有则必须是数组格式
if (!empty($condition)) {
$condition = is_array($condition) ? $condition : json_decode($condition, true);
if (!is_array($condition)) {
return '自定义类型的条件格式不正确,应为数组格式';
}
return $condition;
}
return [];
default:
return '无效的触发类型';
}
}
/**
* 添加
* @return \think\response\Json
* @throws \Exception
*/
public function create(){
$name = $this->request->param('name', '');
$trigger = $this->request->param('trigger', 0);
$condition = $this->request->param('condition', '');
$content = $this->request->param('content', '');
$level = $this->request->param('level', 0);
$status = $this->request->param('status', 1);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($name) || empty($trigger) || empty($content)){
return ResponseHelper::error('参数缺失');
}
// 校验trigger类型
if (!in_array($trigger, [1, 2, 3, 4, 5, 6])) {
return ResponseHelper::error('无效的触发类型');
}
// 校验并处理condition
$conditionResult = $this->validateTriggerCondition($trigger, $condition);
if (is_string($conditionResult)) {
// 返回的是错误信息
return ResponseHelper::error($conditionResult);
}
$condition = $conditionResult;
Db::startTrans();
try {
$AutoGreetings = new AutoGreetings();
$AutoGreetings->name = $name;
$AutoGreetings->trigger = $trigger;
$AutoGreetings->condition = json_encode($condition,256);
$AutoGreetings->content = $content;
$AutoGreetings->level = $level;
$AutoGreetings->status = $status;
$AutoGreetings->userId = $userId;
$AutoGreetings->companyId = $companyId;
$AutoGreetings->updateTime = time();
$AutoGreetings->createTime = time();
$AutoGreetings->usageCount = 0; // 初始化使用次数为0
$AutoGreetings->save();
Db::commit();
return ResponseHelper::success(['id' => $AutoGreetings->id],'创建成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('创建失败:'.$e->getMessage());
}
}
/**
* 详情
* @return \think\response\Json
*/
public function details()
{
$id = $this->request->param('id', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)){
return ResponseHelper::error('参数缺失');
}
$data = AutoGreetings::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
if (empty($data)){
return ResponseHelper::error('该内容已被删除或者不存在');
}
$data['condition'] = json_decode($data['condition'],true);
// 获取使用次数
$usageCount = Db::name('kf_auto_greetings_record')
->where('autoId', $id)
->count();
$data['usageCount'] = (int)$usageCount;
return ResponseHelper::success($data,'获取成功');
}
/**
* 删除
* @return \think\response\Json
*/
public function del()
{
$id = $this->request->param('id', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)){
return ResponseHelper::error('参数缺失');
}
$data = AutoGreetings::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
if (empty($data)){
return ResponseHelper::error('该已被删除或者不存在');
}
Db::startTrans();
try {
$data->isDel = 1;
$data->delTime = time();
$data->save();
Db::commit();
return ResponseHelper::success('','删除成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('删除失败:'.$e->getMessage());
}
}
/**
* 更新
* @return \think\response\Json
* @throws \Exception
*/
public function update(){
$id = $this->request->param('id', '');
$name = $this->request->param('name', '');
$trigger = $this->request->param('trigger', 0);
$condition = $this->request->param('condition', '');
$content = $this->request->param('content', '');
$level = $this->request->param('level', 0);
$status = $this->request->param('status', 1);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id) || empty($name) || empty($trigger) || empty($content)){
return ResponseHelper::error('参数缺失');
}
// 校验trigger类型
if (!in_array($trigger, [1, 2, 3, 4, 5, 6])) {
return ResponseHelper::error('无效的触发类型');
}
// 校验并处理condition
$conditionResult = $this->validateTriggerCondition($trigger, $condition);
if (is_string($conditionResult)) {
// 返回的是错误信息
return ResponseHelper::error($conditionResult);
}
$condition = $conditionResult;
$query = AutoGreetings::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
if (empty($query)){
return ResponseHelper::error('该内容已被删除或者不存在');
}
Db::startTrans();
try {
$query->name = $name;
$query->trigger = $trigger;
$query->condition = !empty($condition) ? json_encode($condition,256) : json_encode([]);
$query->content = $content;
$query->level = $level;
$query->status = $status;
$query->userId = $userId;
$query->companyId = $companyId;
$query->updateTime = time();
$query->createTime = time();
$query->save();
Db::commit();
return ResponseHelper::success(' ','修改成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('修改失败:'.$e->getMessage());
}
}
/**
* 修改状态
* @return \think\response\Json
* @throws \Exception
*/
public function setStatus(){
$id = $this->request->param('id', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)){
return ResponseHelper::error('参数缺失');
}
$query = AutoGreetings::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
if (empty($query)){
return ResponseHelper::error('该内容已被删除或者不存在');
}
Db::startTrans();
try {
$status = $this->request->param('status', '');
if ($status !== '') {
$query->status = (int)$status;
} else {
$query->status = $query->status == 1 ? 0 : 1;
}
$query->updateTime = time();
$query->save();
Db::commit();
return ResponseHelper::success(['status' => $query->status],'修改成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('修改失败:'.$e->getMessage());
}
}
/**
* 拷贝
* @return \think\response\Json
* @throws \Exception
*/
public function copy(){
$id = $this->request->param('id', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id) ){
return ResponseHelper::error('参数缺失');
}
$data = AutoGreetings::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
if (empty($data)){
return ResponseHelper::error('该内容已被删除或者不存在');
}
Db::startTrans();
try {
$query = new AutoGreetings();
$query->name = $data['name'] . '_copy';
$query->trigger = $data['trigger'];
$query->condition = $data['condition'];
$query->content = $data['content'];
$query->level = $data['level'];
$query->status = $data['status'];
$query->userId = $userId;
$query->companyId = $companyId;
$query->updateTime = time();
$query->createTime = time();
$query->save();
Db::commit();
return ResponseHelper::success(' ','拷贝成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('拷贝失败:'.$e->getMessage());
}
}
/**
* 统计概览
* - 总触发次数
* - 活跃规则(近一个月)
* - 发送成功率
* - 平均响应时间(秒)
* - 规则效果排行(按发送次数降序、平均响应时间升序)
* @return \think\response\Json
*/
public function stats()
{
$companyId = $this->getUserInfo('companyId');
$userId = $this->getUserInfo('id');
$start30d = time() - 30 * 24 * 3600;
try {
// 公司维度(用于除排行外的统计)
$companyWhere = [
['companyId', '=', $companyId],
];
// 排行维度(限定个人)
$rankingWhere = [
['companyId', '=', $companyId],
['userId', '=', $userId],
];
// 1) 总触发次数
$totalTriggers = Db::name('kf_auto_greetings_record')
->where($companyWhere)
->count();
// 2) 近30天活跃规则仅返回数量按公司维度distinct autoId
$activeRulesCount = Db::name('kf_auto_greetings_record')
->where($companyWhere)
->where('createTime', '>=', $start30d)
->distinct(true)
->count('autoId');
// 3) 发送成功率
$sendCount = Db::name('kf_auto_greetings_record')
->where($companyWhere)
->where('isSend', '=', 1)
->count();
// 成功率:百分比,保留两位小数
$sendRate = $totalTriggers > 0 ? round(($sendCount * 100) / $totalTriggers, 2) : 0.00;
// 4) 平均响应时间receiveTime - sendTime单位秒
$avgResponse = Db::name('kf_auto_greetings_record')
->where($companyWhere)
->whereRaw('sendTime IS NOT NULL AND receiveTime IS NOT NULL AND receiveTime >= sendTime')
->avg(Db::raw('(receiveTime - sendTime)'));
$avgResponse = $avgResponse ? (int)round($avgResponse) : 0;
// 5) 规则效果排行(按发送次数降序、平均响应时间升序)
$ranking = Db::name('kf_auto_greetings_record')
->where($rankingWhere)
->field([
'autoId AS id',
'COUNT(*) AS totalCount',
'SUM(CASE WHEN isSend = 1 THEN 1 ELSE 0 END) AS sendCount',
'AVG(CASE WHEN sendTime IS NOT NULL AND receiveTime IS NOT NULL AND receiveTime >= sendTime THEN (receiveTime - sendTime) END) AS avgResp'
])
->group('autoId')
->orderRaw('sendCount DESC, avgResp ASC')
->limit(20)
->select();
// 附加规则名称(如存在)
$autoIds = array_values(array_unique(array_column($ranking, 'id')));
$autoIdToRule = [];
if (!empty($autoIds)) {
$rules = AutoGreetings::where([['id', 'in', $autoIds]])
->field('id,name,trigger')
->select();
foreach ($rules as $rule) {
$autoIdToRule[$rule['id']] = [
'name' => $rule['name'],
'trigger' => $rule['trigger'],
];
}
}
foreach ($ranking as &$row) {
$row['avgResp'] = isset($row['avgResp']) && $row['avgResp'] !== null ? (int)round($row['avgResp']) : 0;
// 百分比,两位小数
$row['sendRate'] = ($row['totalCount'] ?? 0) > 0 ? round((($row['sendCount'] ?? 0) * 100) / $row['totalCount'], 2) : 0.00;
$row['name'] = $autoIdToRule[$row['id']]['name'] ?? '';
$row['trigger'] = $autoIdToRule[$row['id']]['trigger'] ?? null;
}
unset($row);
return ResponseHelper::success([
'totalTriggers' => (int)$totalTriggers,
'activeRules' => (int)$activeRulesCount,
'sendSuccessRate' => $sendRate,
'avgResponseSeconds' => $avgResponse,
'ruleRanking' => $ranking,
], '统计成功');
} catch (\Exception $e) {
return ResponseHelper::error('统计失败:' . $e->getMessage());
}
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace app\chukebao\controller;
use think\Controller;
/**
* 基础控制器
*/
class BaseController extends Controller
{
/**
* 获取用户信息
*
* @param string $column
* @return mixed
* @throws \Exception
*/
protected function getUserInfo(?string $column = null)
{
$user = $this->request->userInfo;
if (!$user) {
throw new \Exception('未授权访问,缺少有效的身份凭证', 401);
}
return $column ? $user[$column] : $user;
}
}

View File

@@ -0,0 +1,665 @@
<?php
namespace app\chukebao\controller;
use app\chukebao\model\Keywords;
use app\chukebao\model\Material;
use app\chukebao\model\SensitiveWord;
use think\Db;
use library\ResponseHelper;
class ContentController extends BaseController
{
//===================================================== 素材管理 =====================================================
public function getAllMaterial(){
$keyword = $this->request->param('keyword', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$query = Material::where(['userId' => $userId,'companyId' => $companyId,'isDel' => 0,'status' => 1])
->field('id,title,cover')
->order('id desc');
$list = $query->select()->toArray();
return ResponseHelper::success($list);
}
/**
* 素材列表
* @return \think\response\Json
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function getMaterial(){
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$query = Material::where(['userId' => $userId,'companyId' => $companyId,'isDel' => 0])
->order('id desc');
if (!empty($keyword)){
$query->where('title', 'like', '%'.$keyword.'%');
}
$list = $query->page($page, $limit)->select()->toArray();
$total = $query->count();
foreach ($list as $k => &$v){
$user = Db::name('users')->where(['id' => $v['userId']])->field('username,account')->find();
if (!empty($user)){
$v['userName'] = !empty($user['username']) ? $user['username'] : $user['account'];
}else{
$v['userName'] = '';
}
}
unset($v);
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
}
/**
* 素材添加
* @return \think\response\Json
* @throws \Exception
*/
public function createMaterial(){
$title = $this->request->param('title', '');
$content = $this->request->param('content', []);
$cover = $this->request->param('cover', '');
$status = $this->request->param('status', 0);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($title) || empty($content) || empty($cover)){
return ResponseHelper::error('参数缺失');
}
$newContent = [];
foreach ($content as $k => $v){
if (in_array($v['type'],['text','image','video','audio','file','link'])){
$newContent[] = $v;
}
}
Db::startTrans();
try {
$query = new Material();
$query->title = $title;
$query->content = !empty($newContent) ? json_encode($newContent,256) : json_encode([],256);
$query->cover = $cover;
$query->status = $status;
$query->userId = $userId;
$query->companyId = $companyId;
$query->createTime = time();
$query->updateTime = time();
$query->save();
Db::commit();
return ResponseHelper::success(' ','创建成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('创建失败:'.$e->getMessage());
}
}
/**
* 素材详情
* @return \think\response\Json
*/
public function detailsMaterial()
{
$id = $this->request->param('id', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)){
return ResponseHelper::error('参数缺失');
}
$data = Material::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
if (empty($data)){
return ResponseHelper::error('该素材已被删除或者不存在');
}
$data['content'] = json_decode($data['content'],true);
unset($data['createTime'],$data['updateTime'],$data['isDel'],$data['delTime']);
return ResponseHelper::success($data,'获取成功');
}
/**
* 删除素材
* @return \think\response\Json
*/
public function delMaterial()
{
$id = $this->request->param('id', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)){
return ResponseHelper::error('参数缺失');
}
$data = Material::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
if (empty($data)){
return ResponseHelper::error('该素材已被删除或者不存在');
}
Db::startTrans();
try {
$data->isDel = 1;
$data->delTime = time();
$data->save();
Db::commit();
return ResponseHelper::success('','删除成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('删除失败:'.$e->getMessage());
}
}
/**
* 修改素材
* @return \think\response\Json
* @throws \Exception
*/
public function updateMaterial(){
$id = $this->request->param('id', '');
$title = $this->request->param('title', '');
$content = $this->request->param('content', []);
$cover = $this->request->param('cover', '');
$status = $this->request->param('status', 0);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id) || empty($title) || empty($content) || empty($cover)){
return ResponseHelper::error('参数缺失');
}
$newContent = [];
foreach ($content as $k => $v){
if (in_array($v['type'],['text','image','video','audio','file','link'])){
$newContent[] = $v;
}
}
$query = Material::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
if (empty($query)){
return ResponseHelper::error('该素材已被删除或者不存在');
}
Db::startTrans();
try {
$query->title = $title;
$query->content = !empty($newContent) ? json_encode($newContent,256) : json_encode([],256);
$query->cover = $cover;
$query->status = $status;
$query->updateTime = time();
$query->save();
Db::commit();
return ResponseHelper::success(' ','修改成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('修改失败:'.$e->getMessage());
}
}
//===================================================== 素材管理 =====================================================
//==================================================== 违禁词管理 ====================================================
/**
* 违禁词列表
* @return \think\response\Json
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function getSensitiveWord(){
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$query = SensitiveWord::where(['userId' => $userId,'companyId' => $companyId,'isDel' => 0])
->order('id desc');
if (!empty($keyword)){
$query->where('title', 'like', '%'.$keyword.'%');
}
$total = $query->count();
$list = $query->page($page, $limit)->select()->toArray();
foreach ($list as $k => &$v){
$user = Db::name('users')->where(['id' => $v['userId']])->field('username,account')->find();
if (!empty($user)){
$v['userName'] = !empty($user['username']) ? $user['username'] : $user['account'];
}else{
$v['userName'] = '';
}
}
unset($v);
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
}
/**
* 违禁词添加
* @return \think\response\Json
* @throws \Exception
*/
public function createSensitiveWord(){
$title = $this->request->param('title', '');
$keywords = $this->request->param('keywords', '');
$content = $this->request->param('content', '');
$status = $this->request->param('status', 0);
$operation = $this->request->param('operation', 0);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($title) || empty($keywords)){
return ResponseHelper::error('参数缺失');
}
$keywords = explode(',',$keywords);
Db::startTrans();
try {
$query = new SensitiveWord();
$query->title = $title;
$query->keywords = $keywords;
$query->content = $content;
$query->status = $status;
$query->operation = $operation;
$query->userId = $userId;
$query->companyId = $companyId;
$query->createTime = time();
$query->updateTime = time();
$query->save();
Db::commit();
return ResponseHelper::success(' ','创建成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('创建失败:'.$e->getMessage());
}
}
/**
* 违禁词详情
* @return \think\response\Json
*/
public function detailsSensitiveWord()
{
$id = $this->request->param('id', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)){
return ResponseHelper::error('参数缺失');
}
$data = SensitiveWord::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
if (empty($data)){
return ResponseHelper::error('该素材已被删除或者不存在');
}
$data['keywords'] = json_decode($data['keywords'],true);
$data['keywords'] = implode(',',$data['keywords']);
unset($data['createTime'],$data['updateTime'],$data['isDel'],$data['delTime']);
return ResponseHelper::success($data,'获取成功');
}
/**
* 违禁词删除
* @return \think\response\Json
*/
public function delSensitiveWord()
{
$id = $this->request->param('id', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)){
return ResponseHelper::error('参数缺失');
}
$data = SensitiveWord::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
if (empty($data)){
return ResponseHelper::error('该素材已被删除或者不存在');
}
Db::startTrans();
try {
$data->isDel = 1;
$data->delTime = time();
$data->save();
Db::commit();
return ResponseHelper::success('','删除成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('删除失败:'.$e->getMessage());
}
}
/**
* 更新违禁词
* @return \think\response\Json
* @throws \Exception
*/
public function updateSensitiveWord(){
$id = $this->request->param('id', '');
$title = $this->request->param('title', '');
$keywords = $this->request->param('keywords', '');
$content = $this->request->param('content', '');
$status = $this->request->param('status', 0);
$operation = $this->request->param('operation', 0);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id) || empty($title) || empty($keywords)){
return ResponseHelper::error('参数缺失');
}
$keywords = explode(',',$keywords);
$query = SensitiveWord::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
if (empty($query)){
return ResponseHelper::error('该素材已被删除或者不存在');
}
Db::startTrans();
try {
$query->title = $title;
$query->keywords = $keywords;
$query->content = $content;
$query->status = $status;
$query->operation = $operation;
$query->updateTime = time();
$query->save();
Db::commit();
return ResponseHelper::success(' ','修改成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('修改失败:'.$e->getMessage());
}
}
/**
* 修改违禁词状态
* @return \think\response\Json
* @throws \Exception
*/
public function setSensitiveWordStatus(){
$id = $this->request->param('id', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)){
return ResponseHelper::error('参数缺失');
}
$query = SensitiveWord::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
if (empty($query)){
return ResponseHelper::error('该素材已被删除或者不存在');
}
Db::startTrans();
try {
$query->status = !empty($query['status']) ? 0 : 1;;
$query->updateTime = time();
$query->save();
Db::commit();
return ResponseHelper::success(' ','修改成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('修改失败:'.$e->getMessage());
}
}
//==================================================== 违禁词管理 ====================================================
//=================================================== 关键词词管理 ====================================================
/**
* 关键词列表
* @return \think\response\Json
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function getKeywords(){
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$query = Keywords::where(['userId' => $userId,'companyId' => $companyId,'isDel' => 0])
->order('id desc');
if (!empty($keyword)){
$query->where('title', 'like', '%'.$keyword.'%');
}
$total = $query->count();
$list = $query->page($page, $limit)->select()->toArray();
foreach ($list as $k => &$v){
$v['metailGroups'] = json_decode($v['metailGroups'],true);
$v['content'] = json_decode($v['content'],true);
$v['keywords'] = json_decode($v['keywords'],true);
$metailData = Material::where(['isDel' => 0,'userId' => $userId,'companyId' => $companyId])
->whereIn('id',$v['metailGroups'])
->select()->toArray();
$v['metailGroupsOptions'] = $metailData;
$user = Db::name('users')->where(['id' => $v['userId']])->field('username,account')->find();
if (!empty($user)){
$v['userName'] = !empty($user['username']) ? $user['username'] : $user['account'];
}else{
$v['userName'] = '';
}
}
unset($v);
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
}
/**
* 关键词添加
* @return \think\response\Json
* @throws \Exception
*/
public function createKeywords(){
$title = $this->request->param('title', '');
$type = $this->request->param('type', 0);
$keywords = $this->request->param('keywords', '');
$replyType = $this->request->param('replyType', 0);
$content = $this->request->param('content','');
$metailGroups = $this->request->param('metailGroups',[]);
$status = $this->request->param('status', 0);
$level = $this->request->param('level', 50);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($title) || empty($keywords) || (empty(metailGroups) && empty($content))){
return ResponseHelper::error('参数缺失');
}
$keywords = explode(',',$keywords);
Db::startTrans();
try {
$query = new Keywords();
$query->title = $title;
$query->type = $type;
$query->keywords = !empty($keywords) ? json_encode($keywords,256) : json_encode([]);
$query->replyType = $replyType;
$query->content = !empty($content) ? json_encode($content,256) : json_encode([]);;
$query->metailGroups = !empty($metailGroups) ? json_encode($metailGroups,256) : json_encode([]);;
$query->status = $status;
$query->level = $level;
$query->userId = $userId;
$query->companyId = $companyId;
$query->createTime = time();
$query->updateTime = time();
$query->save();
Db::commit();
return ResponseHelper::success(' ','创建成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('创建失败:'.$e->getMessage());
}
}
/**
* 关键词详情
* @return \think\response\Json
*/
public function detailsKeywords()
{
$id = $this->request->param('id', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)){
return ResponseHelper::error('参数缺失');
}
$data = Keywords::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
if (empty($data)){
return ResponseHelper::error('该素材已被删除或者不存在');
}
$data['metailGroups'] = json_decode($data['metailGroups'],true);
$metailData = Material::where(['isDel' => 0,'userId' => $userId,'companyId' => $companyId])
->whereIn('id',$data['metailGroups'])
->select()->toArray();
$data['metailGroupsOptions'] = $metailData;
$data['content'] = json_decode($data['content'],true);
$data['keywords'] = json_decode($data['keywords'],true);
$data['keywords'] = implode(',',$data['keywords']);
unset($data['createTime'],$data['updateTime'],$data['isDel'],$data['delTime']);
return ResponseHelper::success($data,'获取成功');
}
/**
* 关键词删除
* @return \think\response\Json
*/
public function delKeywords()
{
$id = $this->request->param('id', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)){
return ResponseHelper::error('参数缺失');
}
$data = Keywords::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
if (empty($data)){
return ResponseHelper::error('该关键词已被删除或者不存在');
}
Db::startTrans();
try {
$data->isDel = 1;
$data->delTime = time();
$data->save();
Db::commit();
return ResponseHelper::success('','删除成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('删除失败:'.$e->getMessage());
}
}
/**
* 更新关键词
* @return \think\response\Json
* @throws \Exception
*/
public function updateKeywords(){
$id = $this->request->param('id', '');
$title = $this->request->param('title', '');
$type = $this->request->param('type', 0);
$keywords = $this->request->param('keywords', '');
$replyType = $this->request->param('replyType', 0);
$content = $this->request->param('content','');
$metailGroups = $this->request->param('metailGroups','');
$status = $this->request->param('status', 0);
$level = $this->request->param('level', 50);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($title) || empty($keywords) || (empty($metailGroups) && empty($content))){
return ResponseHelper::error('参数缺失');
}
$keywords = explode(',',$keywords);
$query = Keywords::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
if (empty($query)){
return ResponseHelper::error('该素材已被删除或者不存在');
}
Db::startTrans();
try {
$query->title = $title;
$query->type = $type;
$query->keywords = !empty($keywords) ? json_encode($keywords,256) : json_encode([]);
$query->replyType = $replyType;
$query->content = !empty($content) ? json_encode($content,256) : json_encode([]);;
$query->metailGroups = !empty($metailGroups) ? json_encode($metailGroups,256) : json_encode([]);;;
$query->status = $status;
$query->level = $level;
$query->save();
Db::commit();
return ResponseHelper::success(' ','修改成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('修改失败:'.$e->getMessage());
}
}
/**
* 修改关键词状态
* @return \think\response\Json
* @throws \Exception
*/
public function setKeywordStatus(){
$id = $this->request->param('id', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)){
return ResponseHelper::error('参数缺失');
}
$query = Keywords::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
if (empty($query)){
return ResponseHelper::error('该素材已被删除或者不存在');
}
Db::startTrans();
try {
$query->status = !empty($query['status']) ? 0 : 1;
$query->updateTime = time();
$query->save();
Db::commit();
return ResponseHelper::success(' ','修改成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('修改失败:'.$e->getMessage());
}
}
//=================================================== 关键词词管理 ====================================================
}

View File

@@ -0,0 +1,51 @@
<?php
namespace app\chukebao\controller;
use library\ResponseHelper;
use think\Db;
class CustomerServiceController extends BaseController
{
public function getList(){
$accountId = $this->getUserInfo('s2_accountId');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($accountId)){
return ResponseHelper::error('请先登录');
}
$accountIds1= Db::table('s2_wechat_friend')->where(['accountId' => $accountId,'isDeleted' => 0])->group('wechatAccountId')->column('wechatAccountId');
$accountIds2 = Db::table('s2_wechat_chatroom')->where(['accountId' => $accountId,'isDeleted' => 0])->group('wechatAccountId')->column('wechatAccountId');
// 确保即使有空数组也不会报错,并且去除重复值
$accountIds = array_unique(array_merge($accountIds1 ?: [], $accountIds2 ?: []));
$wechatAliveTime = time() - 86400 * 30;
$list = Db::table('s2_wechat_account')
->whereIn('id',$accountIds)
->where('wechatAliveTime','>',$wechatAliveTime)
->order('id desc')
->group('id')
->select();
foreach ($list as $k=>&$v){
$v['createTime'] = !empty($v['createTime']) ? date('Y-m-d H:i:s',$v['createTime']) : '';
$v['updateTime'] = !empty($v['updateTime']) ? date('Y-m-d H:i:s',$v['updateTime']) : '';
$v['labels'] = json_decode($v['labels'],true);
$momentsSetting = Db::name('kf_moments_settings')->where(['userId' => $userId,'companyId' => $companyId,'wechatId' =>$v['id']])->find();
$v['momentsMax'] = !empty($momentsSetting['max']) ? $momentsSetting['max'] : 5;
$v['momentsNum'] = !empty($momentsSetting['sendNum']) ? $momentsSetting['sendNum'] : 0;
unset(
$v['accountUserName'],
$v['accountRealName'],
$v['accountNickname'],
);
}
unset($v);
return ResponseHelper::success($list);
}
}

View File

@@ -0,0 +1,206 @@
<?php
namespace app\chukebao\controller;
use app\api\model\WechatChatroomModel;
use library\ResponseHelper;
use app\api\model\WechatFriendModel;
use app\api\model\WechatMessageModel;
use app\api\controller\MessageController;
class DataProcessing extends BaseController
{
public function index()
{
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$type = $this->request->param('type', '');
$wechatAccountId = $this->request->param('wechatAccountId', '');
//微信好友
$toAccountId = $this->request->param('toAccountId', '');
$wechatFriendId = $this->request->param('wechatFriendId', '');
$newRemark = $this->request->param('newRemark', '');
$labels = $this->request->param('labels', []);
//微信群
$wechatChatroomId = $this->request->param('wechatChatroomId', '');
//新消息
$friendMessage = $this->request->param('friendMessage', '');
$chatroomMessage = $this->request->param('chatroomMessage', '');
$typeData = [
'CmdModifyFriendRemark', //好友修改备注 {newRemark、wechatAccountId、wechatFriendId}
'CmdModifyFriendLabel', //好友修改标签 {labels、wechatAccountId、wechatFriendId}
'CmdAllotFriend', //转让好友 {labels、wechatAccountId、wechatFriendId}
'CmdChatroomOperate', //修改群信息 {chatroomName群名、announce公告、extra公告、wechatAccountId、wechatChatroomId}
'CmdNewMessage', //接收消息
'CmdSendMessageResult', //更新消息状态
'CmdPinToTop', //置顶
];
if (empty($type) || empty($wechatAccountId)) {
return ResponseHelper::error('参数缺失');
}
if (!in_array($type, $typeData)) {
return ResponseHelper::error('类型错误');
}
$msg = '';
$codee = 200;
switch ($type) {
case 'CmdModifyFriendRemark': //修改好友备注
if(empty($wechatFriendId) || empty($newRemark)){
return ResponseHelper::error('参数缺失');
}
$friend = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find();
if(empty($friend)){
return ResponseHelper::error('好友不存在');
}
$friend->conRemark = $newRemark;
$friend->updateTime = time();
$friend->save();
$msg = '修改备成功';
break;
case 'CmdModifyFriendLabel': //修改好友标签
if(empty($wechatFriendId)){
return ResponseHelper::error('参数缺失');
}
$friend = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find();
if(empty($friend)){
return ResponseHelper::error('好友不存在');
}
$friend->labels = json_encode($labels,256);
$friend->updateTime = time();
$friend->save();
$msg = '修标签成功';
break;
case 'CmdAllotFriend': //迁移好友
if(empty($toAccountId)){
return ResponseHelper::error('参数缺失');
}
if(empty($wechatFriendId) && empty($wechatChatroomId)){
return ResponseHelper::error('参数缺失');
}
if (!empty($wechatFriendId)){
$data = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find();
$msg = '好友转移成功';
if(empty($data)){
return ResponseHelper::error('好友不存在');
}
}
if (!empty($wechatChatroomId)){
$data = WechatChatroomModel::where(['id' => $wechatChatroomId,'wechatAccountId' => $wechatAccountId])->find();
$msg = '群聊转移成功';
if(empty($data)){
return ResponseHelper::error('群聊不存在');
}
}
$data->accountId = $toAccountId;
$data->updateTime = time();
$data->save();
break;
case 'CmdNewMessage':
if(empty($friendMessage) && empty($chatroomMessage)){
return ResponseHelper::error('参数缺失');
}
if(is_array($friendMessage) && is_array($chatroomMessage)){
return ResponseHelper::error('数据类型错误');
}
$messageController = new MessageController();
if (!empty($friendMessage)){
$res = $messageController->saveMessage($friendMessage[0]);
}else{
$res = $messageController->saveChatroomMessage($chatroomMessage[0]);
}
if (!empty($res)){
$msg = '消息记录成功';
}else{
$msg = '消息记录失败';
$codee = 200;
}
break;
case 'CmdSendMessageResult':
$friendMessageId = $this->request->param('friendMessageId', 0);
$chatroomMessageId = $this->request->param('chatroomMessageId', 0);
$sendStatus = $this->request->param('sendStatus', null);
$wechatTime = $this->request->param('wechatTime', 0);
if ($sendStatus === null) {
return ResponseHelper::error('sendStatus不能为空');
}
if (empty($friendMessageId) && empty($chatroomMessageId)) {
return ResponseHelper::error('friendMessageId或chatroomMessageId至少提供一个');
}
$messageId = $friendMessageId ?: $chatroomMessageId;
$update = [
'sendStatus' => (int)$sendStatus,
];
if (!empty($wechatTime)) {
$update['wechatTime'] = strlen((string)$wechatTime) > 10
? intval($wechatTime / 1000)
: (int)$wechatTime;
}
$affected = WechatMessageModel::where('id', $messageId)->update($update);
if ($affected === false) {
return ResponseHelper::success('','更新消息状态失败');
}
if ($affected === 0) {
return ResponseHelper::success('','消息不存在');
}
$msg = '更新消息状态成功';
break;
case 'CmdPinToTop': //置顶
$wechatFriendId = $this->request->param('wechatFriendId', 0);
$wechatChatroomId = $this->request->param('wechatChatroomId', 0);
$isTop = $this->request->param('isTop', null);
if ($isTop === null) {
return ResponseHelper::error('isTop不能为空');
}
if (empty($wechatFriendId) && empty($wechatChatroomId)) {
return ResponseHelper::error('wechatFriendId或chatroomId至少提供一个');
}
if (!empty($wechatFriendId)){
$data = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find();
$msg = $isTop == 1 ? '已置顶' : '取消置顶';
if(empty($data)){
return ResponseHelper::error('好友不存在');
}
}
if (!empty($wechatChatroomId)){
$data = WechatChatroomModel::where(['id' => $wechatChatroomId,'wechatAccountId' => $wechatAccountId])->find();
$msg = $isTop == 1 ? '已置顶' : '取消置顶';
if(empty($data)){
return ResponseHelper::error('群聊不存在');
}
}
$data->updateTime = time();
$data->isTop = $isTop;
$data->save();
break;
}
return ResponseHelper::success('',$msg,$codee);
}
}

View File

@@ -0,0 +1,144 @@
<?php
namespace app\chukebao\controller;
use app\chukebao\model\FollowUp;
use library\ResponseHelper;
use think\Db;
class FollowUpController extends BaseController
{
public function getList(){
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$isRemind = $this->request->param('isRemind', '');
$isProcess = $this->request->param('isProcess', '');
$type = $this->request->param('type', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$where = [
['companyId','=',$companyId],
['userId' ,'=', $userId]
];
if ($isRemind != '') {
$where[] = ['isRemind','=',$isRemind];
}
if ($type != '') {
$where[] = ['type','=',$type];
}
if ($isProcess != '') {
$where[] = ['isProcess','=',$isProcess];
}
if(!empty($keyword)){
$where[] = ['title|description','like','%'.$keyword.'%'];
}
$query = FollowUp::where($where);
$total = $query->count();
$list = $query->where($where)->page($page,$limit)->order('id desc')->select();
foreach ($list as &$item) {
$nickname = Db::table('s2_wechat_friend')->where(['id' => $item['friendId']])->value('nickname');
$item['nickname'] = !empty($nickname) ? $nickname : '-';
$item['reminderTime'] = date('Y-m-d H:i:s',$item['reminderTime']);
}
unset($item);
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
}
/**
* 添加
* @return \think\response\Json
* @throws \Exception
*/
public function create(){
$type = $this->request->param('type', 0);
$title = $this->request->param('title', '');
$reminderTime = $this->request->param('reminderTime', '');
$description = $this->request->param('description', '');
$friendId = $this->request->param('friendId', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($title) || empty($reminderTime) || empty($description) || empty($friendId)){
return ResponseHelper::error('参数缺失');
}
$friend = Db::table('s2_wechat_friend')->where(['id' => $friendId])->find();
if (empty($friend)) {
return ResponseHelper::error('好友不存在');
}
Db::startTrans();
try {
$FollowUp = new FollowUp();
$FollowUp->type = $type;
$FollowUp->title = $title;
$FollowUp->friendId = $friendId;
$FollowUp->reminderTime = !empty($reminderTime) ? strtotime($reminderTime) : time();
$FollowUp->description = $description;
$FollowUp->userId = $userId;
$FollowUp->companyId = $companyId;
$FollowUp->updateTime = time();
$FollowUp->createTime = time();
$FollowUp->save();
Db::commit();
return ResponseHelper::success(' ','创建成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('创建失败:'.$e->getMessage());
}
}
/**
* 处理代办事项
* @return \think\response\Json
* @throws \Exception
*/
public function process(){
$ids = $this->request->param('ids','');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($ids)){
return ResponseHelper::error('参数缺失');
}
$ids = explode(',',$ids);
if (!is_array($ids)){
return ResponseHelper::error('格式错误');
}
$FollowUpIds = FollowUp::where(['userId' => $userId,'companyId' => $companyId,'isProcess' => 0])->whereIn('id',$ids)->column('id');
if (empty($FollowUpIds)){
return ResponseHelper::error('代办事项不存在');
}
Db::startTrans();
try {
FollowUp::whereIn('id',$FollowUpIds)->update(['isProcess' => 1,'isRemind' => 1,'updateTime' => time()]);
Db::commit();
return ResponseHelper::success(' ','已处理');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('处理失败:'.$e->getMessage());
}
}
}

View File

@@ -0,0 +1,141 @@
<?php
namespace app\chukebao\controller;
use app\common\util\JwtUtil;
use Exception;
use library\ResponseHelper;
use think\Db;
use think\Controller;
use think\facade\Cache;
/**
* 认证控制器
* 处理用户登录和身份验证
*/
class LoginController extends Controller
{
/**
* 用户登录
*
* @return \think\response\Json
*/
public function index($username = '', $password = '',$verifySessionId = '',$verifyCode = '')
{
$username = !empty($username) ? $username : $this->request->param('account', '');
$password = !empty($password) ? $password : $this->request->param('password', '');
$verifySessionId =!empty($verifySessionId) ? $verifySessionId : $this->request->param('verifySessionId', '');
$verifyCode = !empty($verifyCode) ? $verifyCode : $this->request->param('verifyCode', '');
$token = JwtUtil::getRequestToken();
$payload = '';
if (!empty($token)){
$payload = JwtUtil::verifyToken($token);
}
if ((empty($username) || empty($password)) && empty($payload)){
return ResponseHelper::error('请输入账号密码');
}
// 验证账号是否存在(支持账号或手机号登录)
if (empty($payload11)){
$user = Db::name('users')
->where(function ($query) use ($username) {
$query->where('account', $username)->whereOr('phone', $username);
})
->where(function ($query2) use ($password) {
$query2->where('passwordMd5', md5($password))->whereOr('passwordLocal', localEncrypt($password));
})
->find();
}else{
$user = $payload;
}
if (empty($user)) {
return ResponseHelper::error('账号不存在或密码错误');
}
if($user['status'] != 1){
return ResponseHelper::error('账号已禁用');
}
//登录参数
$params = [
'grant_type' => 'password',
'username' => $user['account'],
'password' => !empty($user['passwordLocal']) ? localDecrypt($user['passwordLocal']) : $password
];
try {
// 调用登录接口获取token
$headerData = ['client:kefu-client'];
if (!empty($verifySessionId) && !empty($verifyCode)){
$headerData[] = 'verifysessionid:'.$verifySessionId;
$headerData[] = 'verifycode:'.$verifyCode;
}
$header = setHeader($headerData, '', 'plain');
$result = requestCurl('https://s2.siyuguanli.com:9991/token', $params, 'POST', $header);
$result = handleApiResponse($result);
if (isset($result['access_token']) && !empty($result['access_token'])) {
$kefuData['token'] = $result;
$headerData = ['client:kefu-client'];
$header = setHeader($headerData, $result['access_token']);
$result2 = requestCurl('https://s2.siyuguanli.com:9991/api/account/self', [], 'GET', $header, 'json');
$self = handleApiResponse($result2);
$kefuData['self'] = $self;
Db::name('users')->where('id', $user['id'])->update(['passwordLocal' => localEncrypt($params['password']),'updateTime' => time()]);
}else{
$kefuData = [
'token' => [
"access_token"=> "27gINKZqGux6V4j9QLawOcTKlWXg-j4zxQjKvScvDTq-YlLcwIrDP2AFaNZKnOo9zLzepOBC8qrdXh4z9GxxkwE9TKGRQI1FjITRlMZzrim13IbSEbJUoywGs_BhDmIZnnPhfjqxDB1vjZgVtT2Kp4bxbUCV3i2uO_FTv_DT2G7NUFFLjq8oIuUrd_c1YXeYkH8m8Fw1AM4yPZJZyfdaHSSMOpJ2Bk2LAghnB6OaZCYWNFQcwWARsmh1BSAANUOAoadjkztZC7Fme-GGOm2sLo0WL6Mf26NfeLmnkluewTiPMyacD7RYclAR2LZ_8Mhwr3pwRg",
"token_type"=> "bearer",
"expires_in"=> 195519999,
"refresh_token"=> "a9545daa-d1c4-4c87-8c4c-b713631d4f0d"
],
'self' => [
'account' => [
"id"=> 5538,
"realName"=> "测试",
"nickname"=> "",
"memo"=> "",
"avatar"=> "",
"userName"=> "wz_02",
"secret"=> "8f6f743395ad4198b6a4c0e6ca0e452f",
"accountType"=> 10,
"departmentId"=> 2130,
"useGoogleSecretKey"=> false,
"hasVerifyGoogleSecret"=> true
],
'tenant' => [
"id" => 242,
"name"=> "泉州市卡若网络技术有限公司",
"guid"=> "5E2C38F5A275450D935F3ECEC076124E",
"thirdParty"=> null,
"tenantType"=> 0,
"deployName"=> "deploy-s2"
]
]
];
//return ResponseHelper::error($result['error_description']);
}
unset($user['passwordMd5'],$user['deleteTime']);
$userData['member'] = $user;
// 生成JWT令牌
$expired = 86400 * 30;
$token = JwtUtil::createToken($user, $expired);
$token_expired = time() + $expired;
$userData['token'] = $token;
$userData['token_expired'] = $token_expired;
$userData['kefuData'] = $kefuData;
return ResponseHelper::success($userData, '登录成功');
} catch (Exception $e) {
return ResponseHelper::error($e->getMessage(), $e->getCode());
}
}
}

View File

@@ -0,0 +1,476 @@
<?php
namespace app\chukebao\controller;
use app\api\model\WechatMessageModel;
use app\chukebao\model\FriendSettings;
use library\ResponseHelper;
use think\Db;
use think\facade\Env;
use app\common\service\AuthService;
class MessageController extends BaseController
{
protected $baseUrl;
protected $authorization;
public function __construct()
{
parent::__construct();
$this->baseUrl = Env::get('api.wechat_url');
$this->authorization = AuthService::getSystemAuthorization();
}
public function getList()
{
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$accountId = $this->getUserInfo('s2_accountId');
if (empty($accountId)) {
return ResponseHelper::error('请先登录');
}
$friends = Db::table('s2_wechat_friend')
->where(['accountId' => $accountId, 'isDeleted' => 0])
->column('id,nickname,avatar,conRemark,labels,groupId,wechatAccountId,wechatId,extendFields,phone,region,isTop');
// 构建好友子查询
$friendSubQuery = Db::table('s2_wechat_friend')
->where(['accountId' => $accountId, 'isDeleted' => 0])
->field('id')
->buildSql();
// 优化后的查询使用MySQL兼容的查询方式
$unionQuery = "
(SELECT m.id, m.content, m.wechatFriendId, m.wechatChatroomId, m.createTime, m.wechatTime,m.wechatAccountId, 2 as msgType, wc.nickname, wc.chatroomAvatar as avatar, wc.chatroomId, wc.isTop
FROM s2_wechat_chatroom wc
INNER JOIN s2_wechat_message m ON wc.id = m.wechatChatroomId AND m.type = 2
INNER JOIN (
SELECT wechatChatroomId, MAX(wechatTime) as maxTime, MAX(id) as maxId
FROM s2_wechat_message
WHERE type = 2
GROUP BY wechatChatroomId
) latest ON m.wechatChatroomId = latest.wechatChatroomId AND m.wechatTime = latest.maxTime AND m.id = latest.maxId
WHERE wc.accountId = {$accountId} AND wc.isDeleted = 0
)
UNION ALL
(SELECT m.id, m.content, m.wechatFriendId, m.wechatChatroomId, m.createTime, m.wechatTime, 1 as msgType, 1 as nickname, 1 as avatar, 1 as chatroomId, 1 as wechatAccountId, 0 as isTop
FROM s2_wechat_message m
INNER JOIN (
SELECT wechatFriendId, MAX(wechatTime) as maxTime, MAX(id) as maxId
FROM s2_wechat_message
WHERE type = 1 AND wechatFriendId IN {$friendSubQuery}
GROUP BY wechatFriendId
) latest ON m.wechatFriendId = latest.wechatFriendId AND m.wechatTime = latest.maxTime AND m.id = latest.maxId
WHERE m.type = 1 AND m.wechatFriendId IN {$friendSubQuery}
)
ORDER BY wechatTime DESC
LIMIT " . (($page - 1) * $limit) . ", {$limit}
";
$list = Db::query($unionQuery);
// 对分页后的结果进行排序按wechatTime降序
usort($list, function ($a, $b) {
return $b['wechatTime'] <=> $a['wechatTime'];
});
// 批量统计未读数量isRead=0按好友/群聊分别聚合
$friendIds = [];
$chatroomIds = [];
foreach ($list as $row) {
if (!empty($row['wechatFriendId'])) {
$friendIds[] = $row['wechatFriendId'];
}
if (!empty($row['wechatChatroomId'])) {
$chatroomIds[] = $row['wechatChatroomId'];
}
}
$friendIds = array_values(array_unique(array_filter($friendIds)));
$chatroomIds = array_values(array_unique(array_filter($chatroomIds)));
$friendUnreadMap = [];
if (!empty($friendIds)) {
// 获取未读消息数量
$friendUnreadMap = Db::table('s2_wechat_message')
->where(['isRead' => 0])
->whereIn('wechatFriendId', $friendIds)
->group('wechatFriendId')
->column('COUNT(*) AS cnt', 'wechatFriendId');
}
$chatroomUnreadMap = [];
if (!empty($chatroomIds)) {
// 获取未读消息数量
$chatroomUnreadMap = Db::table('s2_wechat_message')
->where(['isRead' => 0])
->whereIn('wechatChatroomId', $chatroomIds)
->group('wechatChatroomId')
->column('COUNT(*) AS cnt', 'wechatChatroomId');
}
$aiTypeData = [];
if (!empty($friendIds)) {
$aiTypeData = FriendSettings::where('friendId', 'in', $friendIds)->column('friendId,type');
}
foreach ($list as $k => &$v) {
$createTime = !empty($v['createTime']) ? date('Y-m-d H:i:s', $v['createTime']) : '';
$wechatTime = !empty($v['wechatTime']) ? date('Y-m-d H:i:s', $v['wechatTime']) : '';
$unreadCount = 0;
$v['aiType'] = 0;
if (!empty($v['wechatFriendId'])) {
$v['nickname'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['nickname'] : '';
$v['avatar'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['avatar'] : '';
$v['conRemark'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['conRemark'] : '';
$v['groupId'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['groupId'] : '';
$v['wechatAccountId'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['wechatAccountId'] : '';
$v['wechatId'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['wechatId'] : '';
$v['extendFields'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['extendFields'] : [];
$v['region'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['region'] : '';
$v['phone'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['phone'] : '';
$v['isTop'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['isTop'] : 0;
$v['labels'] = !empty($friends[$v['wechatFriendId']]) ? json_decode($friends[$v['wechatFriendId']]['labels'], true) : [];
$unreadCount = isset($friendUnreadMap[$v['wechatFriendId']]) ? (int)$friendUnreadMap[$v['wechatFriendId']] : 0;
$v['aiType'] = isset($aiTypeData[$v['wechatFriendId']]) ? $aiTypeData[$v['wechatFriendId']] : 0;
unset($v['chatroomId']);
}
if (!empty($v['wechatChatroomId'])) {
$v['conRemark'] = '';
$unreadCount = isset($chatroomUnreadMap[$v['wechatChatroomId']]) ? (int)$chatroomUnreadMap[$v['wechatChatroomId']] : 0;
}
$v['id'] = !empty($v['wechatFriendId']) ? $v['wechatFriendId'] : $v['wechatChatroomId'];
$v['config'] = [
'top' => !empty($v['isTop']) ? true : false,
'unreadCount' => $unreadCount,
'chat' => true,
'msgTime' => $v['wechatTime'],
];
$v['createTime'] = $createTime;
$v['lastUpdateTime'] = $wechatTime;
// 最新消息内容已经在UNION查询中获取直接使用
$v['latestMessage'] = [
'content' => $v['content'],
'wechatTime' => $wechatTime
];
unset($v['wechatFriendId'], $v['wechatChatroomId'],$v['isTop']);
}
unset($v);
return ResponseHelper::success($list);
}
public function readMessage()
{
$wechatFriendId = $this->request->param('wechatFriendId', '');
$wechatChatroomId = $this->request->param('wechatChatroomId', '');
$accountId = $this->getUserInfo('s2_accountId');
if (empty($accountId)) {
return ResponseHelper::error('请先登录');
}
if (empty($wechatChatroomId) && empty($wechatFriendId)) {
return ResponseHelper::error('参数缺失');
}
$where = [];
if (!empty($wechatChatroomId)) {
$where[] = ['wechatChatroomId', '=', $wechatChatroomId];
}
if (!empty($wechatFriendId)) {
$where[] = ['wechatFriendId', '=', $wechatFriendId];
}
Db::table('s2_wechat_message')->where($where)->update(['isRead' => 1]);
return ResponseHelper::success([]);
}
/**
* 获取单条消息发送状态(带轮询功能)
* @return \think\response\Json
*/
public function getMessageStatus()
{
$messageId = $this->request->param('messageId', 0);
$wechatAccountId = $this->request->param('wechatAccountId', '');
$accountId = $this->getUserInfo('s2_accountId');
$wechatFriendId = $this->request->param('wechatFriendId', '');
$wechatChatroomId = $this->request->param('wechatChatroomId', '');
if (empty($accountId)) {
return ResponseHelper::error('请先登录');
}
if (empty($messageId)) {
return ResponseHelper::error('消息ID不能为空');
}
if(empty($wechatFriendId) && empty($wechatChatroomId)) {
return ResponseHelper::error('消息类型不能为空');
}
// 查询单条消息的基本信息(只需要发送状态相关字段)
$message = Db::table('s2_wechat_message')
->where('id', $messageId)
->field('id,wechatAccountId,wechatFriendId,wechatChatroomId,sendStatus')
->find();
if (empty($message)) {
$message = [
'id' => $messageId,
'wechatAccountId' => $wechatAccountId,
'wechatFriendId' => $wechatFriendId,
'wechatChatroomId' => $wechatChatroomId,
'sendStatus' => 0,
];
}
$sendStatus = isset($message['sendStatus']) ? (int)$message['sendStatus'] : 0;
$isUpdated = false;
$pollCount = 0;
$maxPollCount = 10; // 最多轮询10次
// 如果sendStatus不为0开始轮询
if ($sendStatus != 0) {
$messageRequest = [
'id' => $message['id'],
'wechatAccountId' => !empty($wechatAccountId) ? $wechatAccountId : $message['wechatAccountId'],
'wechatFriendId' => !empty($message['wechatFriendId']) ? $message['wechatFriendId'] : '',
'wechatChatroomId' => !empty($message['wechatChatroomId']) ? $message['wechatChatroomId'] : '',
'from' => '',
'to' => '',
];
// 轮询逻辑最多10次
while ($pollCount < $maxPollCount && $sendStatus != 0) {
$pollCount++;
// 请求线上接口获取最新状态
$newData = $this->fetchLatestMessageFromApi($messageRequest);
if (!empty($newData)) {
// 重新查询消息状态(可能已更新)
$updatedMessage = Db::table('s2_wechat_message')
->where('id', $messageId)
->field('sendStatus')
->find();
if (!empty($updatedMessage)) {
$newSendStatus = isset($updatedMessage['sendStatus']) ? (int)$updatedMessage['sendStatus'] : 0;
// 如果状态已更新为0已发送停止轮询
if ($newSendStatus == 0) {
$sendStatus = 0;
$isUpdated = true;
break;
}
// 如果状态仍然是1继续轮询但需要等待一下避免请求过快
if ($newSendStatus != 0 && $pollCount < $maxPollCount) {
// 每次轮询间隔500毫秒0.5秒)
usleep(500000);
}
}
} else {
// 如果请求失败,等待后继续尝试
if ($pollCount < $maxPollCount) {
usleep(500000);
}
}
}
}
// 返回发送状态信息
return ResponseHelper::success([
'messageId' => $messageId,
'sendStatus' => $sendStatus,
'statusText' => $sendStatus == 0 ? '已发送' : '发送中'
]);
}
public function details()
{
$wechatFriendId = $this->request->param('wechatFriendId', '');
$wechatChatroomId = $this->request->param('wechatChatroomId', '');
$wechatAccountId = $this->request->param('wechatAccountId', '');
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$from = $this->request->param('From', '');
$to = $this->request->param('To', '');
$olderData = $this->request->param('olderData', false);
$accountId = $this->getUserInfo('s2_accountId');
if (empty($accountId)) {
return ResponseHelper::error('请先登录');
}
if (empty($wechatChatroomId) && empty($wechatFriendId)) {
return ResponseHelper::error('参数缺失');
}
$where = [];
if (!empty($wechatChatroomId)) {
$where[] = ['wechatChatroomId', '=', $wechatChatroomId];
}
if (!empty($wechatFriendId)) {
$where[] = ['wechatFriendId', '=', $wechatFriendId];
}
if (!empty($From) && !empty($To)) {
$where[] = ['wechatTime', 'between', [$from, $to]];
}
$total = Db::table('s2_wechat_message')->where($where)->count();
$list = Db::table('s2_wechat_message')->where($where)->page($page, $limit)->order('id DESC')->select();
// 检查消息是否有sendStatus字段如果有且不为0则请求线上最新接口
foreach ($list as $k => &$item) {
// 检查是否存在sendStatus字段且不为00表示已发送成功
if (isset($item['sendStatus']) && $item['sendStatus'] != 0) {
// 需要请求新的数据
$messageRequest = [
'id' => $item['id'],
'wechatAccountId' => $wechatAccountId,
'wechatFriendId' => $wechatFriendId,
'wechatChatroomId' => $wechatChatroomId,
'from' => '',
'to' => '',
];
$newData = $this->fetchLatestMessageFromApi($messageRequest);
if (!empty($newData)){
$item['sendStatus'] = 0;
}
}
// 格式化时间
$item['wechatTime'] = !empty($item['wechatTime']) ? date('Y-m-d H:i:s', $item['wechatTime']) : '';
}
unset($item);
return ResponseHelper::success(['total' => $total, 'list' => $list]);
}
/**
* 从线上接口获取最新消息
* @param array $messageRequest 消息项包含wechatAccountId、wechatFriendId或wechatChatroomId、id等
* @return array|null 最新消息数据失败返回null
*/
private function fetchLatestMessageFromApi($messageRequest)
{
if (empty($this->baseUrl) || empty($this->authorization)) {
return null;
}
try {
// 设置请求头
$headerData = ['client:system'];
$header = setHeader($headerData, $this->authorization, 'json');
// 判断是好友消息还是群聊消息
if (!empty($messageRequest['wechatFriendId'])) {
// 好友消息接口
$params = [
'keyword' => '',
'msgType' => '',
'accountId' => '',
'count' => 20, // 获取多条消息以便找到对应的消息
'messageId' => isset($messageRequest['id']) ? $messageRequest['id'] : '',
'olderData' => true,
'wechatAccountId' => $messageRequest['wechatAccountId'],
'wechatFriendId' => $messageRequest['wechatFriendId'],
'from' => $messageRequest['from'],
'to' => $messageRequest['to'],
'searchFrom' => 'admin'
];
$result = requestCurl($this->baseUrl . 'api/FriendMessage/searchMessage', $params, 'GET', $header, 'json');
$response = handleApiResponse($result);
// 查找对应的消息
if (!empty($response) && is_array($response)) {
$data = $response[0];
if ($data['sendStatus'] == 0){
WechatMessageModel::where(['id' => $data['id']])->update(['sendStatus' => 0]);
return true;
}
}
return false;
} elseif (!empty($messageRequest['wechatChatroomId'])) {
// 群聊消息接口
$params = [
'keyword' => '',
'msgType' => '',
'accountId' => '',
'count' => 20, // 获取多条消息以便找到对应的消息
'messageId' => isset($messageRequest['id']) ? $messageRequest['id'] : '',
'olderData' => true,
'wechatId' => '',
'wechatAccountId' => $messageRequest['wechatAccountId'],
'wechatChatroomId' => $messageRequest['wechatChatroomId'],
'from' => $messageRequest['from'],
'to' => $messageRequest['to'],
'searchFrom' => 'admin'
];
$result = requestCurl($this->baseUrl . 'api/ChatroomMessage/searchMessage', $params, 'GET', $header, 'json');
$response = handleApiResponse($result);
// 查找对应的消息
if (!empty($response) && is_array($response)) {
$data = $response[0];
if ($data['sendStatus'] == 0){
WechatMessageModel::where(['id' => $data['id']])->update(['sendStatus' => 0]);
return true;
}
}
return false;
}
} catch (\Exception $e) {
// 记录错误日志,但不影响主流程
\think\facade\Log::error('获取线上最新消息失败:' . $e->getMessage());
}
return null;
}
/**
* 更新数据库中的消息
* @param array $latestMessage 线上获取的最新消息
* @param array $oldMessage 旧消息数据
*/
private function updateMessageInDatabase($latestMessage, $oldMessage)
{
try {
// 使用API模块的MessageController来保存消息
$apiMessageController = new \app\api\controller\MessageController();
// 判断是好友消息还是群聊消息
if (!empty($oldMessage['wechatFriendId'])) {
// 保存好友消息
$apiMessageController->saveMessage($latestMessage);
} elseif (!empty($oldMessage['wechatChatroomId'])) {
// 保存群聊消息
$apiMessageController->saveChatroomMessage($latestMessage);
}
} catch (\Exception $e) {
// 记录错误日志,但不影响主流程
\think\facade\Log::error('更新数据库消息失败:' . $e->getMessage());
}
}
}

View File

@@ -0,0 +1,504 @@
<?php
namespace app\chukebao\controller;
use app\chukebao\model\KfMoments;
use library\ResponseHelper;
use think\Db;
class MomentsController extends BaseController
{
/**
* 创建朋友圈
* @return \think\response\Json
*/
public function create()
{
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
// 获取请求参数
$text = $this->request->param('content', ''); // 朋友圈内容
$picUrlList = $this->request->param('picUrlList', []); // 图片列表
$videoUrl = $this->request->param('videoUrl', ''); // 视频链接
$link = $this->request->param('link', []); // 链接信息
$momentContentType = (int)$this->request->param('type', 1); // 内容类型 1文本 2图文 3视频 4链接
$publicMode = (int)$this->request->param('publicMode', 0); // 公开模式
$wechatIds = $this->request->param('wechatIds', []); // 微信账号ID列表
$labels = $this->request->param('labels', []); // 标签列表
$timingTime = $this->request->param('timingTime', date('Y-m-d H:i:s')); // 定时发布时间
$immediately = $this->request->param('immediately', false); // 是否立即发布
// 格式化时间字符串为统一格式
$timingTime = $this->normalizeTimingTime($timingTime);
if ($timingTime === false) {
return ResponseHelper::error('定时发布时间格式不正确');
}
// 参数验证
if (empty($text) && empty($picUrlList) && empty($videoUrl)) {
return ResponseHelper::error('朋友圈内容不能为空');
}
if (empty($wechatIds)) {
return ResponseHelper::error('请选择发布账号');
}
// 校验内容类型
if (!in_array($momentContentType, [1, 2, 3, 4])) {
return ResponseHelper::error('内容类型不合法支持1文本 2图文 3视频 4链接');
}
if(!empty($labels)){
$publicMode = 2;
}
// 根据内容类型校验必要参数
switch ($momentContentType) {
case 1: // 文本
if (empty($text)) {
return ResponseHelper::error('文本类型必须填写内容');
}
break;
case 2: // 图文
if (empty($text) || empty($picUrlList)) {
return ResponseHelper::error('图文类型必须填写内容和上传图片');
}
break;
case 3: // 视频
if (empty($videoUrl)) {
return ResponseHelper::error('视频类型必须上传视频');
}
break;
case 4: // 链接
if (empty($link)) {
return ResponseHelper::error('链接类型必须填写链接信息');
}
if (empty($link['url'])) {
return ResponseHelper::error('链接类型必须填写链接地址');
}
if (empty($link['desc'])) {
return ResponseHelper::error('链接类型必须填写链接描述');
}
if (empty($link['image'])) {
return ResponseHelper::error('链接类型必须填写链接图片');
}
break;
}
// 处理链接信息 - 所有链接都必须验证
if (!empty($link)) {
$link = [
'desc' => $link['desc'] ?? '',
'image' => $link['image'] ?? '',
'url' => $link['url'] ?? ''
];
// 验证链接URL格式
if (!empty($link['url']) && !filter_var($link['url'], FILTER_VALIDATE_URL)) {
return ResponseHelper::error('链接地址格式不正确');
}
} else {
$link = ['desc' => '', 'image' => '', 'url' => ''];
}
// 构建发布账号列表
$jobPublishWechatMomentsItems = $this->buildJobPublishWechatMomentsItems($wechatIds, $labels);
if (empty($jobPublishWechatMomentsItems)) {
return ResponseHelper::error('无法获取有效的发布账号信息');
}
try {
// 构建发送数据
$sendData = [
'altList' => '',
'beginTime' => $timingTime,
'endTime' => date('Y-m-d H:i:s', strtotime($timingTime) + 3600),
'immediately' => $immediately,
'isUseLocation' => false,
'jobPublishWechatMomentsItems' => $jobPublishWechatMomentsItems,
'lat' => 0,
'lng' => 0,
'link' => $link,
'momentContentType' => $momentContentType,
'picUrlList' => $picUrlList,
'poiAddress' => '',
'poiName' => '',
'publicMode' => $publicMode,
'text' => $text,
'timingTime' => $timingTime ?: date('Y-m-d H:i:s'),
'videoUrl' => $videoUrl
];
// 保存到数据库
$moments = new KfMoments();
$moments->companyId = $companyId;
$moments->userId = $userId;
$moments->sendData = json_encode($sendData, 256);
$nowTs = time();
$moments->createTime = $nowTs;
$moments->updateTime = $nowTs;
$moments->isDel = 0;
$moments->delTime = null;
$moments->isSend = $immediately ? 1 : 0;
$moments->sendTime = $immediately ? $nowTs : strtotime($timingTime);
$moments->save();
// 如果立即发布,调用发布接口
if ($immediately) {
$this->publishMoments($sendData);
}
return ResponseHelper::success('', '朋友圈创建成功');
} catch (\Exception $e) {
return ResponseHelper::error('创建失败:' . $e->getMessage());
}
}
/**
* 编辑朋友圈
* @return \think\response\Json
*/
public function update()
{
$id = (int)$this->request->param('id', 0);
if ($id <= 0) {
return ResponseHelper::error('ID不合法');
}
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
// 获取请求参数(与创建一致的字段名)
$text = $this->request->param('content', '');
$picUrlList = $this->request->param('picUrlList', []);
$videoUrl = $this->request->param('videoUrl', '');
$link = $this->request->param('link', []);
$momentContentType = (int)$this->request->param('type', 1);
$publicMode = (int)$this->request->param('publicMode', 0);
$wechatIds = $this->request->param('wechatIds', []);
$labels = $this->request->param('labels', []);
$timingTime = $this->request->param('timingTime', date('Y-m-d H:i:s'));
$immediately = $this->request->param('immediately', false);
// 格式化时间字符串为统一格式
$timingTime = $this->normalizeTimingTime($timingTime);
if ($timingTime === false) {
return ResponseHelper::error('定时发布时间格式不正确');
}
// 读取待编辑记录
/** @var KfMoments|null $moments */
$moments = KfMoments::where(['id' => $id, 'companyId' => $companyId, 'userId' => $userId, 'isDel' => 0])->find();
if (empty($moments)) {
return ResponseHelper::error('朋友圈不存在');
}
// 参数校验
if (empty($text) && empty($picUrlList) && empty($videoUrl)) {
return ResponseHelper::error('朋友圈内容不能为空');
}
if (empty($wechatIds)) {
return ResponseHelper::error('请选择发布账号');
}
if (!in_array($momentContentType, [1, 2, 3, 4])) {
return ResponseHelper::error('内容类型不合法支持1文本 2图文 3视频 4链接');
}
if (!empty($labels)) {
$publicMode = 2;
}
switch ($momentContentType) {
case 1:
if (empty($text)) {
return ResponseHelper::error('文本类型必须填写内容');
}
break;
case 2:
if (empty($text) || empty($picUrlList)) {
return ResponseHelper::error('图文类型必须填写内容和上传图片');
}
break;
case 3:
if (empty($videoUrl)) {
return ResponseHelper::error('视频类型必须上传视频');
}
break;
case 4:
if (empty($link)) {
return ResponseHelper::error('链接类型必须填写链接信息');
}
if (empty($link['url'])) {
return ResponseHelper::error('链接类型必须填写链接地址');
}
if (empty($link['desc'])) {
return ResponseHelper::error('链接类型必须填写链接描述');
}
if (empty($link['image'])) {
return ResponseHelper::error('链接类型必须填写链接图片');
}
break;
}
if (!empty($link)) {
$link = [
'desc' => $link['desc'] ?? '',
'image' => $link['image'] ?? '',
'url' => $link['url'] ?? ''
];
if (!empty($link['url']) && !filter_var($link['url'], FILTER_VALIDATE_URL)) {
return ResponseHelper::error('链接地址格式不正确');
}
} else {
$link = ['desc' => '', 'image' => '', 'url' => ''];
}
// 构建账号列表
$jobPublishWechatMomentsItems = $this->buildJobPublishWechatMomentsItems($wechatIds, $labels);
if (empty($jobPublishWechatMomentsItems)) {
return ResponseHelper::error('无法获取有效的发布账号信息');
}
try {
$sendData = [
'altList' => '',
'beginTime' => $timingTime,
'endTime' => date('Y-m-d H:i:s', strtotime($timingTime) + 1800),
'immediately' => $immediately,
'isUseLocation' => false,
'jobPublishWechatMomentsItems' => $jobPublishWechatMomentsItems,
'lat' => 0,
'lng' => 0,
'link' => $link,
'momentContentType' => $momentContentType,
'picUrlList' => $picUrlList,
'poiAddress' => '',
'poiName' => '',
'publicMode' => $publicMode,
'text' => $text,
'timingTime' => $timingTime ?: date('Y-m-d H:i:s'),
'videoUrl' => $videoUrl
];
$moments->sendData = json_encode($sendData, 256);
$moments->isSend = $immediately ? 1 : 0;
$moments->sendTime = $immediately ? time() : strtotime($timingTime);
$moments->updateTime = time();
$moments->save();
if ($immediately) {
$this->publishMoments($sendData);
}
return ResponseHelper::success('', '朋友圈更新成功');
} catch (\Exception $e) {
return ResponseHelper::error('更新失败:' . $e->getMessage());
}
}
/**
* 构建发布账号列表
* @param array $wechatIds 微信账号ID列表
* @param array $labels 标签列表
* @return array
*/
private function buildJobPublishWechatMomentsItems($wechatIds, $labels)
{
try {
// 查询微信账号信息
$wechatAccounts = Db::table('s2_wechat_account')
->whereIn('id', $wechatIds)
->field('id,labels')
->select();
if (empty($wechatAccounts)) {
return [];
}
$result = [];
foreach ($wechatAccounts as $account) {
$accountLabels = [];
// 如果账号有标签,解析标签
if (!empty($account['labels'])) {
$accountLabels = is_string($account['labels'])
? json_decode($account['labels'], true)
: $account['labels'];
}
// 取传入标签与账号标签的交集
$finalLabels = array_intersect($labels, $accountLabels);
$result[] = [
'wechatAccountId' => $account['id'],
'labels' => array_values($finalLabels), // 重新索引数组
'comments' => []
];
}
return $result;
} catch (\Exception $e) {
\think\facade\Log::error('构建发布账号列表失败:' . $e->getMessage());
return [];
}
}
/**
* 发布朋友圈到微信
* @param array $sendData
* @return bool
*/
private function publishMoments($sendData)
{
try {
// 这里调用实际的朋友圈发布接口
// 根据您的系统架构,可能需要调用 WebSocket 或其他服务
// 示例:调用 MomentsController 的 addJob 方法
$moments = new \app\api\controller\MomentsController();
return $moments->addJob($sendData);
} catch (\Exception $e) {
// 记录错误日志
\think\facade\Log::error('朋友圈发布失败:' . $e->getMessage());
return false;
}
}
/**
* 获取朋友圈列表
* @return \think\response\Json
*/
public function getList()
{
$page = (int)$this->request->param('page', 1);
$limit = (int)$this->request->param('limit', 10);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
try {
$list = KfMoments::where(['companyId' => $companyId, 'userId' => $userId, 'isDel' => 0])
->order('createTime desc')
->page($page, $limit)
->select();
$total = KfMoments::where(['companyId' => $companyId, 'userId' => $userId, 'isDel' => 0])->count();
// 处理数据
$data = [];
foreach ($list as $item) {
$sendData = json_decode($item->sendData,true);
$data[] = [
'id' => $item->id,
'content' => $sendData['text'] ?? '',
'momentContentType' => $sendData['momentContentType'] ?? 1,
'picUrlList' => $sendData['picUrlList'] ?? [],
'videoUrl' => $sendData['videoUrl'] ?? '',
'link' => $sendData['link'] ?? [],
'publicMode' => $sendData['publicMode'] ?? 2,
'isSend' => $item->isSend,
'sendTime' => date('Y-m-d H:i:s',$item->sendTime),
'accountCount' => count($sendData['jobPublishWechatMomentsItems'] ?? [])
];
}
return ResponseHelper::success([
'list' => $data,
'total' => $total,
'page' => $page,
'limit' => $limit
], '获取成功');
} catch (\Exception $e) {
return ResponseHelper::error('获取失败:' . $e->getMessage());
}
}
/**
* 删除朋友圈
* @return \think\response\Json
*/
public function delete()
{
$id = (int)$this->request->param('id', 0);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if ($id <= 0) {
return ResponseHelper::error('ID不合法');
}
try {
$moments = KfMoments::where(['id' => $id, 'companyId' => $companyId, 'userId' => $userId, 'isDel' => 0])->find();
if (empty($moments)) {
return ResponseHelper::error('朋友圈不存在');
}
$moments->isDel = 1;
$moments->delTime = time();
$moments->updateTime = time();
$moments->save();
return ResponseHelper::success([], '删除成功');
} catch (\Exception $e) {
return ResponseHelper::error('删除失败:' . $e->getMessage());
}
}
/**
* 规范化时间字符串为 Y-m-d H:i:s 格式
* 支持多种时间格式:
* - "2026年1月5日15:43:00"
* - "2026-01-05 15:43:00"
* - "2026/01/05 15:43:00"
* - 时间戳
* @param string|int $timingTime 时间字符串或时间戳
* @return string|false 格式化后的时间字符串失败返回false
*/
private function normalizeTimingTime($timingTime)
{
if (empty($timingTime)) {
return date('Y-m-d H:i:s');
}
// 如果是时间戳
if (is_numeric($timingTime) && strlen($timingTime) == 10) {
return date('Y-m-d H:i:s', $timingTime);
}
// 如果是毫秒时间戳
if (is_numeric($timingTime) && strlen($timingTime) == 13) {
return date('Y-m-d H:i:s', intval($timingTime / 1000));
}
// 如果已经是标准格式,直接返回
if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $timingTime)) {
return $timingTime;
}
// 处理中文日期格式2026年1月5日15:43:00 或 2026年01月05日15:43:00
if (preg_match('/^(\d{4})年(\d{1,2})月(\d{1,2})日(\d{1,2}):(\d{1,2}):(\d{1,2})$/', $timingTime, $matches)) {
$year = $matches[1];
$month = str_pad($matches[2], 2, '0', STR_PAD_LEFT);
$day = str_pad($matches[3], 2, '0', STR_PAD_LEFT);
$hour = str_pad($matches[4], 2, '0', STR_PAD_LEFT);
$minute = str_pad($matches[5], 2, '0', STR_PAD_LEFT);
$second = str_pad($matches[6], 2, '0', STR_PAD_LEFT);
return "{$year}-{$month}-{$day} {$hour}:{$minute}:{$second}";
}
// 处理中文日期格式无秒2026年1月5日15:43
if (preg_match('/^(\d{4})年(\d{1,2})月(\d{1,2})日(\d{1,2}):(\d{1,2})$/', $timingTime, $matches)) {
$year = $matches[1];
$month = str_pad($matches[2], 2, '0', STR_PAD_LEFT);
$day = str_pad($matches[3], 2, '0', STR_PAD_LEFT);
$hour = str_pad($matches[4], 2, '0', STR_PAD_LEFT);
$minute = str_pad($matches[5], 2, '0', STR_PAD_LEFT);
return "{$year}-{$month}-{$day} {$hour}:{$minute}:00";
}
// 尝试使用 strtotime 解析其他格式
$timestamp = strtotime($timingTime);
if ($timestamp !== false) {
return date('Y-m-d H:i:s', $timestamp);
}
// 如果所有方法都失败,返回 false
return false;
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace app\chukebao\controller;
use app\chukebao\model\FollowUp;
use app\chukebao\model\NoticeModel;
use app\chukebao\model\ToDo;
use library\ResponseHelper;
use think\Db;
class NoticeController extends BaseController
{
/**
* 列表
* @return \think\response\Json
* @throws \Exception
*/
public function getList()
{
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$accountId = $this->getUserInfo('s2_accountId');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($accountId)) {
return ResponseHelper::error('请先登录');
}
$noRead = NoticeModel::where(['userId' => $userId, 'companyId' => $companyId,'isRead' => 0])->count();
$query = NoticeModel::where(['userId' => $userId, 'companyId' => $companyId])
->order('id desc');
if (!empty($keyword)) {
$query->where('title|message', 'like', '%' . $keyword . '%');
}
$total = $query->count();
$list = $query->page($page, $limit)->order('isRead ASC,id DESC')->select()->toArray();
foreach ($list as $k => &$v) {
if ($v['type'] == 1) {
$friendId = ToDo::where(['id' => $v['bindId']])->value('friendId');
} elseif ($v['type'] == 2) {
$friendId = FollowUp::where(['id' => $v['bindId']])->value('friendId');
}
if (!empty($friendId)) {
$friend = Db::table('s2_wechat_friend')->where(['id' => $friendId])->field('nickname,avatar')->find();
} else {
$friend = ['nickname' => '', 'avatar' => ''];
}
$v['friendData'] = $friend;
$v['readTime'] = !empty($v['readTime']) ? date('Y-m-d H:i:s', $v['readTime']) : '';
}
unset($v);
return ResponseHelper::success(['list' => $list, 'total' => $total,'noRead' => $noRead]);
}
public function readMessage()
{
$id = $this->request->param('id', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)) {
return ResponseHelper::error('参数缺失');
}
Db::startTrans();
try {
$notice = NoticeModel::where(['userId' => $userId, 'companyId' => $companyId, 'id' => $id, 'isRead' => 0])->find();
if (empty($notice)) {
return ResponseHelper::error('该消息不存在或标记已读');
}
$notice->isRead = 1;
$notice->readTime = time();
$notice->save();
if ($notice->type == 1) {
ToDo::where(['userId' => $userId, 'companyId' => $companyId, 'id' => $notice->bindId])->update(['isProcess' => 1, 'updateTime' => time()]);
} elseif ($notice->type == 2) {
FollowUp::where(['userId' => $userId, 'companyId' => $companyId, 'id' => $notice->bindId])->update(['isProcess' => 1, 'updateTime' => time()]);
}
Db::commit();
return ResponseHelper::success(' ', '处理成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('处理失败:' . $e->getMessage());
}
}
public function readAll()
{
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
Db::startTrans();
try {
$noticeData = NoticeModel::where(['userId' => $userId, 'companyId' => $companyId, 'isRead' => 0])->select()->toArray();
if (empty($noticeData)) {
return ResponseHelper::error('暂未有新消息');
}
NoticeModel::where(['userId' => $userId, 'companyId' => $companyId, 'isRead' => 0])->update(['isRead' => 1, 'readTime' => time()]);
FollowUp::where(['userId' => $userId, 'companyId' => $companyId, 'isProcess' => 0])->update(['isProcess' => 1, 'updateTime' => time()]);
ToDo::where(['userId' => $userId, 'companyId' => $companyId, 'isProcess' => 0])->update(['isProcess' => 1, 'updateTime' => time()]);
Db::commit();
return ResponseHelper::success(' ', '全部已读');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('处理失败:' . $e->getMessage());
}
}
}

View File

@@ -0,0 +1,233 @@
<?php
namespace app\chukebao\controller;
use app\chukebao\model\Questions;
use library\ResponseHelper;
use think\Db;
class QuestionsController extends BaseController
{
/**
* 列表
* @return \think\response\Json
* @throws \Exception
*/
public function getList(){
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$accountId = $this->getUserInfo('s2_accountId');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($accountId)){
return ResponseHelper::error('请先登录');
}
$query = Questions::where(['userId' => $userId,'companyId' => $companyId,'isDel' => 0])
->order('id desc');
if (!empty($keyword)){
$query->where('questions|answers', 'like', '%'.$keyword.'%');
}
$total = $query->count();
$list = $query->page($page, $limit)->select()->toArray();
foreach ($list as $k => &$v){
$user = Db::name('users')->where(['id' => $v['userId']])->field('username,account')->find();
if (!empty($user)){
$v['userName'] = !empty($user['username']) ? $user['username'] : $user['account'];
}else{
$v['userName'] = '';
}
$v['answers'] = json_decode($v['answers'],true);
}
unset($v);
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
}
/**
* 新增
* @return \think\response\Json
* @throws \Exception
*/
public function create(){
$type = $this->request->param('type', 0);
$questions = $this->request->param('questions', '');
$answers = $this->request->param('answers', []);
$status = $this->request->param('status', 0);
$accountId = $this->getUserInfo('s2_accountId');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($accountId)){
return ResponseHelper::error('请先登录');
}
if (empty($questions) || empty($answers)){
return ResponseHelper::error('问题和答案不能为空');
}
Db::startTrans();
try {
$questionsModel = new Questions();
$questionsModel->type = $type;
$questionsModel->questions = $questions;
$questionsModel->answers = !empty($answers) ? json_encode($answers,256) : json_encode([],256);
$questionsModel->status = $status;
$questionsModel->accountId = $accountId;
$questionsModel->userId = $userId;
$questionsModel->companyId = $companyId;
$questionsModel->createTime = time();
$questionsModel->updateTime = time();
$questionsModel->save();
Db::commit();
return ResponseHelper::success(' ','创建成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('创建失败:'.$e->getMessage());
}
}
/**
* 更新
* @return \think\response\Json
* @throws \Exception
*/
public function update(){
$id = $this->request->param('id', 0);
$accountId = $this->getUserInfo('s2_accountId');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$type = $this->request->param('type', 0);
$questions = $this->request->param('questions', '');
$answers = $this->request->param('answers', []);
$status = $this->request->param('status', 0);
$accountId = $this->getUserInfo('s2_accountId');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($accountId)){
return ResponseHelper::error('请先登录');
}
if (empty($id)){
return ResponseHelper::error('参数缺失');
}
if (empty($questions) || empty($answers)){
return ResponseHelper::error('问题和答案不能为空');
}
Db::startTrans();
try {
$questionsData = Questions::where(['id' => $id,'userId' => $userId,'companyId' => $companyId,'isDel' => 0])->find();
$questionsData->type = $type;
$questionsData->questions = $questions;
$questionsData->answers = !empty($answers) ? json_encode($answers,256) : json_encode([],256);
$questionsData->status = $status;
$questionsData->accountId = $accountId;
$questionsData->userId = $userId;
$questionsData->companyId = $companyId;
$questionsData->updateTime = time();
$questionsData->save();
Db::commit();
return ResponseHelper::success(' ','更新成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('更新失败:'.$e->getMessage());
}
}
/**
* 删除
* @return \think\response\Json
* @throws \Exception
*/
public function delete(){
$id = $this->request->param('id', 0);
$accountId = $this->getUserInfo('s2_accountId');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($accountId)){
return ResponseHelper::error('请先登录');
}
if (empty($id)){
return ResponseHelper::error('参数缺失');
}
$questions = Questions::where(['id' => $id,'userId' => $userId,'companyId' => $companyId,'isDel' => 0])->find();
if (empty($questions)){
return ResponseHelper::error('该问题不存在或者已删除');
}
$res = Questions::where(['id' => $id])->update(['isDel' => 1,'deleteTime' => time()]);
if (!empty($res)){
return ResponseHelper::success('','已删除');
}else{
return ResponseHelper::error('删除失败');
}
}
/**
* 详情
* @return \think\response\Json
* @throws \Exception
*/
public function detail(){
$id = $this->request->param('id', 0);
$accountId = $this->getUserInfo('s2_accountId');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($accountId)){
return ResponseHelper::error('请先登录');
}
if (empty($id)){
return ResponseHelper::error('参数缺失');
}
$questions = Questions::where(['id' => $id,'userId' => $userId,'companyId' => $companyId,'isDel' => 0])->find();
if (empty($questions)){
return ResponseHelper::error('该问题不存在或者已删除');
}
$questions['answers'] = json_decode($questions['answers'],true);
$user = Db::name('users')->where(['id' => $questions['userId']])->field('username,account')->find();
if (!empty($user)){
$questions['userName'] = !empty($user['username']) ? $user['username'] : $user['account'];
}else{
$questions['userName'] = '';
}
unset(
$questions['isDel'],
$questions['deleteTime'],
$questions['createTime'],
$questions['updateTime']
);
return ResponseHelper::success($questions,'获取成功');
}
}

View File

@@ -0,0 +1,371 @@
<?php
namespace app\chukebao\controller;
use app\chukebao\model\Reply;
use app\chukebao\model\ReplyGroup;
use library\ResponseHelper;
class ReplyController extends BaseController
{
/**
* 列表
* @return \think\response\Json
* @throws \Exception
*/
public function getList()
{
$replyType = $this->request->param('replyType', 0);
$keyword = $this->request->param('keyword', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
try {
// 构建分组查询条件
$groupWhere = [
['isDel','=',0]
];
switch ($replyType) {
case 0:
//公共快捷语
$groupWhere[] = ['replyType', '=', 0];
break;
case 1:
//私有快捷语
$groupWhere[] = ['companyId', '=', $companyId];
$groupWhere[] = ['userId', '=', $userId];
$groupWhere[] = ['replyType', '=', 1];
break;
case 2:
//公司快捷语
$groupWhere[] = ['companyId', '=', $companyId];
$groupWhere[] = ['replyType', '=', 2];
break;
default:
$groupWhere[] = ['replyType', '=', 0];
break;
}
if (!empty($keyword)) {
$groupWhere[] = ['groupName','like', '%' . $keyword . '%'];
}
// 获取所有分组
$allGroups = ReplyGroup::where($groupWhere)
->order('sortIndex asc,id DESC')
->select();
// 构建树形结构
$result = $this->buildGroupTree($allGroups, $keyword);
return ResponseHelper::success($result, '获取成功');
} catch (\Exception $e) {
return ResponseHelper::error('获取失败:' . $e->getMessage());
}
}
/**
* 新增快捷语分组
* @return \think\response\Json
*/
public function addGroup()
{
$groupName = $this->request->param('groupName', '');
$parentId = (int)$this->request->param('parentId', 0);
$replyType = (int)$this->request->param('replyType', 0); // 0公共 1私有 2公司
$sortIndex = (string)$this->request->param('sortIndex', 50);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$accountId = $this->getUserInfo('s2_accountId');
if ($groupName === '') {
return ResponseHelper::error('分组名称不能为空');
}
try {
$data = [
'groupName' => $groupName,
'parentId' => $parentId,
'replyType' => $replyType,
'sortIndex' => $sortIndex,
// 兼容现有程序中使用到的字段
'companyId' => $companyId,
'userId' => $userId,
];
/** @var ReplyGroup $group */
$group = new ReplyGroup();
$group->save($data);
return ResponseHelper::success($group->toArray(), '创建成功');
} catch (\Exception $e) {
return ResponseHelper::error('创建失败:' . $e->getMessage());
}
}
/**
* 新增快捷语
* @return \think\response\Json
*/
public function addReply()
{
$groupId = (int)$this->request->param('groupId', 0);
$title = $this->request->param('title', '');
$msgType = (int)$this->request->param('msgType', 1); // 1文本 3图片 43视频 49链接 等
$content = $this->request->param('content', '');
$sortIndex = (string)$this->request->param('sortIndex', 50);
$accountId = $this->getUserInfo('s2_accountId');
$companyId = $this->getUserInfo('companyId');
$userId = $this->getUserInfo('id');
if ($groupId <= 0) {
return ResponseHelper::error('分组ID不合法');
}
if ($title === '') {
return ResponseHelper::error('标题不能为空');
}
try {
$now = time();
$data = [
'tenantId' => $companyId,
'groupId' => $groupId,
'accountId' => $accountId,
'title' => $title,
'msgType' => $msgType,
'content' => $content,
'sortIndex' => $sortIndex,
'createTime' => $now,
'lastUpdateTime' => $now,
'userId' => $userId,
];
/** @var Reply $reply */
$reply = new Reply();
$reply->save($data);
return ResponseHelper::success($reply->toArray(), '创建成功');
} catch (\Exception $e) {
return ResponseHelper::error('创建失败:' . $e->getMessage());
}
}
/**
* 编辑快捷语分组
* @return \think\response\Json
*/
public function updateGroup()
{
$id = (int)$this->request->param('id', 0);
if ($id <= 0) {
return ResponseHelper::error('分组ID不合法');
}
$data = [];
$groupName = $this->request->param('groupName', null);
$parentId = $this->request->param('parentId', null);
$replyType = $this->request->param('replyType', null);
$sortIndex = $this->request->param('sortIndex', null);
if ($groupName !== null) $data['groupName'] = $groupName;
if ($parentId !== null) $data['parentId'] = (int)$parentId;
if ($replyType !== null) $data['replyType'] = (int)$replyType;
if ($sortIndex !== null) $data['sortIndex'] = (string)$sortIndex;
if (empty($data)) {
return ResponseHelper::error('无可更新字段');
}
try {
$group = ReplyGroup::where(['id' => $id,'isDel' => 0])->find();
if (empty($group)) {
return ResponseHelper::error('分组不存在');
}
$group->save($data);
return ResponseHelper::success($group->toArray(), '更新成功');
} catch (\Exception $e) {
return ResponseHelper::error('更新失败:' . $e->getMessage());
}
}
/**
* 假删除快捷语分组
* @return \think\response\Json
*/
public function deleteGroup()
{
$id = (int)$this->request->param('id', 0);
if ($id <= 0) {
return ResponseHelper::error('分组ID不合法');
}
try {
$group = ReplyGroup::where(['id' => $id,'isDel' => 0])->find();
if (empty($group)) {
return ResponseHelper::error('分组不存在');
}
$group->save(['isDel' => 1,'delTime' => time()]);
return ResponseHelper::success([], '删除成功');
} catch (\Exception $e) {
return ResponseHelper::error('删除失败:' . $e->getMessage());
}
}
/**
* 编辑快捷语
* @return \think\response\Json
*/
public function updateReply()
{
$id = (int)$this->request->param('id', 0);
if ($id <= 0) {
return ResponseHelper::error('快捷语ID不合法');
}
$data = [];
$groupId = $this->request->param('groupId', null);
$title = $this->request->param('title', null);
$msgType = $this->request->param('msgType', null);
$content = $this->request->param('content', null);
$sortIndex = $this->request->param('sortIndex', null);
if ($groupId !== null) $data['groupId'] = (int)$groupId;
if ($title !== null) $data['title'] = $title;
if ($msgType !== null) $data['msgType'] = (int)$msgType;
if ($content !== null) $data['content'] = $content;
if ($sortIndex !== null) $data['sortIndex'] = (string)$sortIndex;
if (!empty($data)) {
$data['lastUpdateTime'] = time();
}
if (empty($data)) {
return ResponseHelper::error('无可更新字段');
}
try {
$reply = Reply::where(['id' => $id,'isDel' => 0])->find();
if (empty($reply)) {
return ResponseHelper::error('快捷语不存在');
}
$reply->save($data);
return ResponseHelper::success($reply->toArray(), '更新成功');
} catch (\Exception $e) {
return ResponseHelper::error('更新失败:' . $e->getMessage());
}
}
/**
* 假删除快捷语
* @return \think\response\Json
*/
public function deleteReply()
{
$id = (int)$this->request->param('id', 0);
if ($id <= 0) {
return ResponseHelper::error('快捷语ID不合法');
}
try {
$reply = Reply::where(['id' => $id,'isDel' => 0])->find();
if (empty($reply)) {
return ResponseHelper::error('快捷语不存在');
}
$reply->save(['isDel' => 1, 'delTime' => time()]);
return ResponseHelper::success([], '删除成功');
} catch (\Exception $e) {
return ResponseHelper::error('删除失败:' . $e->getMessage());
}
}
/**
* 构建分组树形结构
* @param array $groups 所有分组数据
* @param string $keyword 搜索关键词
* @return array
*/
private function buildGroupTree($groups, $keyword = '')
{
$tree = [];
$groupMap = [];
// 先构建分组映射
foreach ($groups as $group) {
$groupMap[$group->id] = $group->toArray();
}
// 构建树形结构
foreach ($groups as $group) {
$groupData = $this->buildGroupData($group, $keyword);
if ($group->parentId == null || $group->parentId == 0) {
// 顶级分组
$tree[] = $groupData;
} else {
// 子分组需要找到父分组并添加到其children中
$this->addToParentGroup($tree, $group->parentId, $groupData);
}
}
return $tree;
}
/**
* 构建单个分组数据
* @param object $group 分组对象
* @param string $keyword 搜索关键词
* @return array
*/
private function buildGroupData($group, $keyword = '')
{
// 构建快捷回复查询条件
$replyWhere[] =[
['groupId' ,'=', $group->id],
['isDel','=',0]
];
if (!empty($keyword)) {
$replyWhere[] = ['title','like', '%' . $keyword . '%'];
}
// 获取该分组下的快捷回复
$replies = Reply::where($replyWhere)
->order('sortIndex asc, id desc
')
->select();
return [
'id' => $group->id,
'groupName' => $group->groupName,
'sortIndex' => $group->sortIndex,
'parentId' => $group->parentId,
'replyType' => $group->replyType,
'replys' => $group->replys,
'companyId' => $group->companyId,
'userId' => $group->userId,
'replies' => $replies->toArray(),
'children' => [] // 子分组
];
}
/**
* 将子分组添加到父分组中
* @param array $tree 树形结构
* @param int $parentId 父分组ID
* @param array $childGroup 子分组数据
*/
private function addToParentGroup(&$tree, $parentId, $childGroup)
{
foreach ($tree as &$group) {
if ($group['id'] == $parentId) {
$group['children'][] = $childGroup;
return;
}
// 递归查找子分组
if (!empty($group['children'])) {
$this->addToParentGroup($group['children'], $parentId, $childGroup);
}
}
}
}

View File

@@ -0,0 +1,143 @@
<?php
namespace app\chukebao\controller;
use app\chukebao\model\ToDo;
use library\ResponseHelper;
use think\Db;
class ToDoController extends BaseController
{
public function getList(){
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$isRemind = $this->request->param('isRemind', '');
$isProcess = $this->request->param('isProcess', '');
$level = $this->request->param('level', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$where = [
['companyId','=',$companyId],
['userId' ,'=', $userId]
];
if ($isRemind != '') {
$where[] = ['isRemind','=',$isRemind];
}
if ($level != '') {
$where[] = ['level','=',$level];
}
if ($isProcess != '') {
$where[] = ['isProcess','=',$isProcess];
}
if(!empty($keyword)){
$where[] = ['title|description','like','%'.$keyword.'%'];
}
$query = ToDo::where($where);
$total = $query->count();
$list = $query->where($where)->page($page,$limit)->order('id desc')->select();
foreach ($list as &$item) {
$nickname = Db::table('s2_wechat_friend')->where(['id' => $item['friendId']])->value('nickname');
$item['nickname'] = !empty($nickname) ? $nickname : '-';
$item['reminderTime'] = date('Y-m-d H:i:s',$item['reminderTime']);
}
unset($item);
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
}
/**
* 添加
* @return \think\response\Json
* @throws \Exception
*/
public function create(){
$level = $this->request->param('level', 0);
$title = $this->request->param('title', '');
$reminderTime = $this->request->param('reminderTime', '');
$description = $this->request->param('description', '');
$friendId = $this->request->param('friendId', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($title) || empty($reminderTime) || empty($description) || empty($friendId)){
return ResponseHelper::error('参数缺失');
}
$friend = Db::table('s2_wechat_friend')->where(['id' => $friendId])->find();
if (empty($friend)) {
return ResponseHelper::error('好友不存在');
}
Db::startTrans();
try {
$todo = new ToDo();
$todo->level = $level;
$todo->title = $title;
$todo->friendId = $friendId;
$todo->reminderTime = !empty($reminderTime) ? strtotime($reminderTime) : time();
$todo->description = $description;
$todo->userId = $userId;
$todo->companyId = $companyId;
$todo->updateTime = time();
$todo->createTime = time();
$todo->save();
Db::commit();
return ResponseHelper::success(' ','创建成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('创建失败:'.$e->getMessage());
}
}
/**
* 处理代办事项
* @return \think\response\Json
* @throws \Exception
*/
public function process(){
$ids = $this->request->param('ids','');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($ids)){
return ResponseHelper::error('参数缺失');
}
$ids = explode(',',$ids);
if (!is_array($ids)){
return ResponseHelper::error('格式错误');
}
$todoIds = ToDo::where(['userId' => $userId,'companyId' => $companyId,'isProcess' => 0])->whereIn('id',$ids)->column('id');
if (empty($todoIds)){
return ResponseHelper::error('代办事项不存在');
}
Db::startTrans();
try {
ToDo::whereIn('id',$todoIds)->update(['isProcess' => 1,'isRemind' => 1,'updateTime' => time()]);
Db::commit();
return ResponseHelper::success(' ','已处理');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('处理失败:'.$e->getMessage());
}
}
}

View File

@@ -0,0 +1,192 @@
<?php
namespace app\chukebao\controller;
use app\chukebao\model\TokensCompany;
use app\chukebao\model\TokensRecord;
use library\ResponseHelper;
use think\Db;
class TokensRecordController extends BaseController
{
public function getList(){
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$type = $this->request->param('type', '');
$form = $this->request->param('form', '');
$startTime = $this->request->param('startTime', '');
$endTime = $this->request->param('endTime', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$where = [
['companyId','=',$companyId],
['userId' ,'=', $userId]
];
if ($type != '') {
$where[] = ['type','=',$type];
}
if ($form != '') {
$where[] = ['form','=',$form];
}
// 时间筛选
if (!empty($startTime)) {
// 支持时间戳或日期字符串格式
$startTimestamp = is_numeric($startTime) ? intval($startTime) : strtotime($startTime);
if ($startTimestamp !== false) {
$where[] = ['createTime', '>=', $startTimestamp];
}
}
if (!empty($endTime)) {
// 支持时间戳或日期字符串格式
$endTimestamp = is_numeric($endTime) ? intval($endTime) : strtotime($endTime);
if ($endTimestamp !== false) {
// 如果是日期字符串自动设置为当天的23:59:59
if (!is_numeric($endTime)) {
$endTimestamp = strtotime(date('Y-m-d 23:59:59', $endTimestamp));
}
$where[] = ['createTime', '<=', $endTimestamp];
}
}
$query = TokensRecord::where($where);
$total = $query->count();
$list = $query->where($where)->page($page,$limit)->order('id desc')->select();
foreach ($list as &$item) {
if (in_array($item['type'],[1])){
$nickname = Db::table('s2_wechat_friend')->where(['id' => $item['friendIdOrGroupId']])->value('nickname');
$item['nickname'] = !empty($nickname) ? $nickname : '-';
}
if (in_array($item['type'],[2,3])){
$nickname = Db::table('s2_wechat_chatroom')->where(['id' => $item['friendIdOrGroupId']])->value('nickname');
$item['nickname'] = !empty($nickname) ? $nickname : '-';
}
}
unset($item);
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
}
public function consumeTokens($data = [])
{
if (empty($data)){
return ResponseHelper::error('数据缺失');
}
$tokens = isset($data['tokens']) ? intval($data['tokens']) : 0;
$type = isset($data['type']) ? intval($data['type']) : 0;
$form = isset($data['form']) ? intval($data['form']) : 0;
$wechatAccountId = isset($data['wechatAccountId']) ? intval($data['wechatAccountId']) : 0;
$friendIdOrGroupId = isset($data['friendIdOrGroupId']) ? intval($data['friendIdOrGroupId']) : 0;
$remarks = isset($data['remarks']) ? $data['remarks'] : '';
$companyId = isset($data['companyId']) ? intval($data['companyId']) : $this->getUserInfo('companyId');
$userId = isset($data['userId']) ? intval($data['userId']) : $this->getUserInfo('id');
// 验证必要参数
if ($tokens <= 0) {
return ResponseHelper::error('tokens数量必须大于0');
}
if (!in_array($type, [0, 1])) {
return ResponseHelper::error('类型参数错误0为减少1为增加');
}
// 重试机制最多重试3次
$maxRetries = 3;
$retryCount = 0;
while ($retryCount < $maxRetries) {
try {
return $this->doConsumeTokens($userId, $companyId, $tokens, $type, $form, $wechatAccountId, $friendIdOrGroupId, $remarks);
} catch (\Exception $e) {
$retryCount++;
if ($retryCount >= $maxRetries) {
return ResponseHelper::error('操作失败,请稍后重试:' . $e->getMessage());
}
// 短暂延迟后重试
usleep(100000); // 100ms
}
}
}
/**
* 执行tokens消费的核心方法
*/
private function doConsumeTokens($userId, $companyId, $tokens, $type, $form, $wechatAccountId, $friendIdOrGroupId, $remarks)
{
// 开启数据库事务
Db::startTrans();
try {
// 使用悲观锁获取用户当前tokens余额确保并发安全
$userInfo = TokensCompany::where(['companyId'=> $companyId,'userId' => $userId])->lock(true)->find();
if (!$userInfo) {
throw new \Exception('用户不存在');
}
$currentTokens = intval($userInfo['tokens']);
// 计算新的余额
$newBalance = $type == 1 ? ($currentTokens + $tokens) : ($currentTokens - $tokens);
// 使用原子更新操作,基于当前值进行更新,防止并发覆盖
$updateResult = TokensCompany::where('companyId', $companyId)
->where('companyId', $companyId)
->update([
'tokens' => $newBalance,
'updateTime' => time()
]);
if (!$updateResult) {
// 如果更新失败说明tokens值已被其他事务修改需要重新获取
throw new \Exception('tokens余额已被其他操作修改请重试');
}
// 记录tokens变动
$recordData = [
'companyId' => $companyId,
'userId' => $userId,
'wechatAccountId' => $wechatAccountId,
'friendIdOrGroupId' => $friendIdOrGroupId,
'form' => $form,
'type' => $type,
'tokens' => $tokens,
'balanceTokens' => $newBalance,
'remarks' => $remarks,
'createTime' => time()
];
$recordId = Db::name('tokens_record')->insertGetId($recordData);
if (!$recordId) {
throw new \Exception('记录tokens变动失败');
}
// 提交事务
Db::commit();
return ResponseHelper::success([
'recordId' => $recordId,
'oldBalance' => $currentTokens,
'newBalance' => $newBalance,
'changeAmount' => $type == 1 ? $tokens : -$tokens
], 'tokens变动记录成功');
} catch (\Exception $e) {
// 回滚事务
Db::rollback();
throw $e; // 重新抛出异常,让重试机制处理
}
}
}

View File

@@ -0,0 +1,269 @@
<?php
namespace app\chukebao\controller;
use app\ai\controller\DouBaoAI;
use app\chukebao\controller\TokensRecordController as tokensRecord;
use app\chukebao\model\TokensCompany;
use library\ResponseHelper;
use think\Db;
class WechatChatroomController extends BaseController
{
public function getList(){
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$groupIds = $this->request->param('groupId', '');
$ownerWechatId = $this->request->param('ownerWechatId', '');
$accountId = $this->getUserInfo('s2_accountId');
if (empty($accountId)){
return ResponseHelper::error('请先登录');
}
$query = Db::table('s2_wechat_chatroom')
->where(['accountId' => $accountId,'isDeleted' => 0]);
// 关键字搜索群昵称、微信号这里使用chatroomId作为群标识
if ($keyword !== '' && $keyword !== null) {
$query->where(function ($q) use ($keyword) {
$like = '%' . $keyword . '%';
$q->whereLike('nickname', $like)
->whereOr('conRemark', 'like', $like);
});
}
// 分组筛选groupIds单个分组ID
if ($groupIds !== '' && $groupIds !== null) {
$query->where('groupIds', $groupIds);
}
if (!empty($ownerWechatId)) {
$query->where('ownerWechatId', $ownerWechatId);
}
$query->order('id desc');
$total = $query->count();
$list = $query->page($page, $limit)->select();
// 提取所有聊天室ID用于批量查询
$chatroomIds = array_column($list, 'id');
// 一次性查询所有聊天室的未读消息数量
$unreadCounts = [];
if (!empty($chatroomIds)) {
$unreadResults = Db::table('s2_wechat_message')
->field('wechatChatroomId, COUNT(*) as count')
->where('wechatChatroomId', 'in', $chatroomIds)
->where('isRead', 0)
->group('wechatChatroomId')
->select();
foreach ($unreadResults as $result) {
$unreadCounts[$result['wechatChatroomId']] = $result['count'];
}
}
// 一次性查询所有聊天室的最新消息
$latestMessages = [];
if (!empty($chatroomIds)) {
// 使用子查询获取每个聊天室的最新消息ID
$subQuery = Db::table('s2_wechat_message')
->field('MAX(id) as max_id, wechatChatroomId')
->where('wechatChatroomId', 'in', $chatroomIds)
->group('wechatChatroomId')
->buildSql();
// 查询最新消息的详细信息
$messageResults = Db::table('s2_wechat_message')
->alias('m')
->join([$subQuery => 'sub'], 'm.id = sub.max_id')
->field('m.*, sub.wechatChatroomId')
->select();
foreach ($messageResults as $message) {
$latestMessages[$message['wechatChatroomId']] = $message;
}
}
// 处理每个聊天室的数据
foreach ($list as $k => &$v) {
$v['createTime'] = !empty($v['createTime']) ? date('Y-m-d H:i:s', $v['createTime']) : '';
$v['updateTime'] = !empty($v['updateTime']) ? date('Y-m-d H:i:s', $v['updateTime']) : '';
$config = [
'unreadCount' => isset($unreadCounts[$v['id']]) ? $unreadCounts[$v['id']] : 0,
'chat' => isset($latestMessages[$v['id']]),
'msgTime' => isset($latestMessages[$v['id']]) ? $latestMessages[$v['id']]['wechatTime'] : 0
];
$v['config'] = $config;
}
unset($v);
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
}
public function getDetail(){
$id = input('id', 0);
if (!$id) {
return ResponseHelper::error('聊天室ID不能为空');
}
$accountId = $this->getUserInfo('s2_accountId');
if (empty($accountId)){
return ResponseHelper::error('请先登录');
}
$detail = Db::table('s2_wechat_chatroom')
->where(['accountId' => $accountId, 'id' => $id, 'isDeleted' => 0])
->find();
if (!$detail) {
return ResponseHelper::error('聊天室不存在或无权限访问');
}
// 处理时间格式
$detail['createTime'] = !empty($detail['createTime']) ? date('Y-m-d H:i:s', $detail['createTime']) : '';
$detail['updateTime'] = !empty($detail['updateTime']) ? date('Y-m-d H:i:s', $detail['updateTime']) : '';
// 查询未读消息数量
$unreadCount = Db::table('s2_wechat_message')
->where('wechatChatroomId', $id)
->where('isRead', 0)
->count();
// 查询最新消息
$latestMessage = Db::table('s2_wechat_message')
->where('wechatChatroomId', $id)
->order('id desc')
->find();
$config = [
'unreadCount' => $unreadCount,
'chat' => !empty($latestMessage),
'msgTime' => isset($latestMessage['wechatTime']) ? $latestMessage['wechatTime'] : 0
];
$detail['config'] = $config;
return ResponseHelper::success($detail);
}
public function getMembers()
{
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$groupId = $this->request->param('groupId', '');
$keyword = $this->request->param('keyword', '');
$accountId = $this->getUserInfo('s2_accountId');
if (empty($accountId)) {
return ResponseHelper::error('请先登录');
}
// 验证群组ID必填
if (empty($groupId)) {
return ResponseHelper::error('群组ID不能为空');
}
// 验证群组是否属于当前账号
$chatroom = Db::table('s2_wechat_chatroom')
->where(['id' => $groupId, 'isDeleted' => 0])
->find();
if (!$chatroom) {
return ResponseHelper::error('群组不存在或无权限访问');
}
// 获取群组的chatroomId微信群聊ID
$chatroomId = $chatroom['chatroomId'] ?? $chatroom['id'];
// 如果chatroomId为空使用id作为chatroomId
if (empty($chatroomId)) {
$chatroomId = $chatroom['id'];
}
// 构建查询
$query = Db::table('s2_wechat_chatroom_member')
->where('chatroomId', $chatroomId);
// 关键字搜索:昵称、备注、别名
if ($keyword !== '' && $keyword !== null) {
$query->where(function ($q) use ($keyword) {
$like = '%' . $keyword . '%';
$q->whereLike('nickname', $like)
->whereOr('conRemark', 'like', $like)
->whereOr('alias', 'like', $like);
});
}
$query->order('id desc');
$total = $query->count();
$list = $query->page($page, $limit)->select();
// 处理时间格式
foreach ($list as $k => &$v) {
$v['createTime'] = !empty($v['createTime']) ? date('Y-m-d H:i:s', $v['createTime']) : '';
$v['updateTime'] = !empty($v['updateTime']) ? date('Y-m-d H:i:s', $v['updateTime']) : '';
}
unset($v);
return ResponseHelper::success(['list' => $list, 'total' => $total]);
}
public function aiAnnouncement()
{
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$wechatAccountId = $this->request->param('wechatAccountId', '');
$groupId = $this->request->param('groupId', '');
$content = $this->request->param('content', '');
if (empty($groupId) || empty($content)|| empty($wechatAccountId)){
return ResponseHelper::error('参数缺失');
}
$tokens = TokensCompany::where(['companyId' => $companyId])->value('tokens');
if (empty($tokens) || $tokens <= 0){
return ResponseHelper::error('用户Tokens余额不足');
}
$params = [
'model' => 'doubao-1-5-pro-32k-250115',
'messages' => [
['role' => 'system', 'content' => '你现在是存客宝的AI助理你精通中国大陆的法律'],
['role' => 'user', 'content' => $content],
],
];
//AI处理
$ai = new DouBaoAI();
$res = $ai->text($params);
$res = json_decode($res,true);
if ($res['code'] == 200) {
//扣除Tokens
$tokensRecord = new tokensRecord();
$nickname = Db::table('s2_wechat_chatroom')->where(['id' => $groupId])->value('nickname');
$remarks = !empty($nickname) ? '生成【'.$nickname.'】群公告' : '生成群公告';
$data = [
'tokens' => $res['data']['token'],
'type' => 0,
'form' => 14,
'wechatAccountId' => $wechatAccountId,
'friendIdOrGroupId' => $groupId,
'remarks' => $remarks,
];
$tokensRecord->consumeTokens($data);
return ResponseHelper::success($res['data']['content']);
}else{
return ResponseHelper::error($res['msg']);
}
}
}

View File

@@ -0,0 +1,302 @@
<?php
namespace app\chukebao\controller;
use app\chukebao\model\FriendSettings;
use library\ResponseHelper;
use think\Db;
class WechatFriendController extends BaseController
{
public function getList()
{
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$groupIds = $this->request->param('groupId', '');
$ownerWechatId = $this->request->param('ownerWechatId', '');
$accountId = $this->getUserInfo('s2_accountId');
if (empty($accountId)) {
return ResponseHelper::error('请先登录');
}
$query = Db::table('s2_wechat_friend')
->where(['accountId' => $accountId, 'isDeleted' => 0]);
// 关键字搜索:昵称、备注、微信号
if ($keyword !== '' && $keyword !== null) {
$query->where(function ($q) use ($keyword) {
$like = '%' . $keyword . '%';
$q->whereLike('nickname', $like)
->whereOr('conRemark', 'like', $like)
->whereOr('alias', 'like', $like)
->whereOr('wechatId', 'like', $like);
});
}
// 分组筛选groupIds单个分组ID
if ($groupIds !== '' && $groupIds !== null) {
$query->where('groupIds', $groupIds);
}
if (!empty($ownerWechatId)) {
$query->where('ownerWechatId', $ownerWechatId);
}
$query->order('id desc');
$total = $query->count();
$list = $query->page($page, $limit)->select();
// 提取所有好友ID
$friendIds = array_column($list, 'id');
$aiTypeData = [];
if (!empty($friendIds)) {
$aiTypeData = FriendSettings::where('friendId', 'in', $friendIds)->column('friendId,type');
}
// 处理每个好友的数据
foreach ($list as $k => &$v) {
$v['labels'] = json_decode($v['labels'], true);
$v['siteLabels'] = json_decode($v['siteLabels'], true);
$v['createTime'] = !empty($v['createTime']) ? date('Y-m-d H:i:s', $v['createTime']) : '';
$v['updateTime'] = !empty($v['updateTime']) ? date('Y-m-d H:i:s', $v['updateTime']) : '';
$v['passTime'] = !empty($v['passTime']) ? date('Y-m-d H:i:s', $v['passTime']) : '';
$v['aiType'] = isset($aiTypeData[$v['id']]) ? $aiTypeData[$v['id']] : 0;
}
unset($v);
return ResponseHelper::success(['list' => $list, 'total' => $total]);
}
/**
* 获取单个好友详情
* @return \think\response\Json
*/
public function getDetail()
{
$friendId = $this->request->param('id');
$accountId = $this->getUserInfo('s2_accountId');
if (empty($accountId)) {
return ResponseHelper::error('请先登录');
}
if (empty($friendId)) {
return ResponseHelper::error('好友ID不能为空');
}
// 查询好友详情
$friend = Db::table('s2_wechat_friend')
->where(['id' => $friendId, 'isDeleted' => 0])
->find();
if (empty($friend)) {
return ResponseHelper::error('好友不存在');
}
// 处理好友数据
$friend['labels'] = json_decode($friend['labels'], true);
$friend['siteLabels'] = json_decode($friend['siteLabels'], true);
$friend['createTime'] = !empty($friend['createTime']) ? date('Y-m-d H:i:s', $friend['createTime']) : '';
$friend['updateTime'] = !empty($friend['updateTime']) ? date('Y-m-d H:i:s', $friend['updateTime']) : '';
$friend['passTime'] = !empty($friend['passTime']) ? date('Y-m-d H:i:s', $friend['passTime']) : '';
// 获取AI类型设置
$aiTypeSetting = FriendSettings::where('friendId', $friendId)->find();
$friend['aiType'] = $aiTypeSetting ? $aiTypeSetting['type'] : 0;
return ResponseHelper::success(['detail' => $friend]);
}
/**
* 更新好友资料(公司、姓名、手机号等字段可单独更新)
* @return \think\response\Json
*/
public function updateFriendInfo()
{
$friendId = $this->request->param('id');
$accountId = $this->getUserInfo('s2_accountId');
if (empty($accountId)) {
return ResponseHelper::error('请先登录');
}
if (empty($friendId)) {
return ResponseHelper::error('好友ID不能为空');
}
$friend = Db::table('s2_wechat_friend')
->where(['id' => $friendId, 'accountId' => $accountId, 'isDeleted' => 0])
->find();
if (empty($friend)) {
return ResponseHelper::error('好友不存在或无权限操作');
}
$requestData = $this->request->param();
$updatableColumns = [
'phone',
'conRemark',
];
$columnUpdates = [];
foreach ($updatableColumns as $field) {
if (array_key_exists($field, $requestData)) {
$columnUpdates[$field] = $requestData[$field];
}
}
$extendFieldsData = [];
if (!empty($friend['extendFields'])) {
$decodedExtend = json_decode($friend['extendFields'], true);
$extendFieldsData = is_array($decodedExtend) ? $decodedExtend : [];
}
$extendFieldKeys = [
'company',
'name',
'position',
'email',
'address',
'wechat',
'qq',
'remark'
];
$extendFieldsUpdated = false;
foreach ($extendFieldKeys as $key) {
if (array_key_exists($key, $requestData)) {
$extendFieldsData[$key] = $requestData[$key];
$extendFieldsUpdated = true;
}
}
if ($extendFieldsUpdated) {
$columnUpdates['extendFields'] = json_encode($extendFieldsData, JSON_UNESCAPED_UNICODE);
}
if (empty($columnUpdates)) {
return ResponseHelper::error('没有可更新的字段');
}
$columnUpdates['updateTime'] = time();
try {
Db::table('s2_wechat_friend')->where('id', $friendId)->update($columnUpdates);
} catch (\Exception $e) {
return ResponseHelper::error('更新失败:' . $e->getMessage());
}
return ResponseHelper::success(['id' => $friendId]);
}
/**
* 获取添加好友任务记录列表(全新功能)
* 返回当前账号的所有添加好友任务记录,无论是否通过都展示
* 包含:添加者头像、昵称、微信号、添加状态、添加时间、通过时间等信息
* @return \think\response\Json
*/
public function getAddTaskList()
{
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$status = $this->request->param('status', ''); // 可选:筛选状态 0执行中1执行成功2执行失败
$accountId = $this->getUserInfo('s2_accountId');
if (empty($accountId)) {
return ResponseHelper::error('请先登录');
}
// 直接使用operatorAccountId查询添加好友任务记录
$query = Db::table('s2_friend_task')
->where('operatorAccountId', $accountId)
->order('createTime desc');
// 如果指定了状态筛选
if ($status !== '' && $status !== null) {
$query->where('status', $status);
}
$total = $query->count();
$tasks = $query->page($page, $limit)->select();
// 处理任务数据
$list = [];
foreach ($tasks as $task) {
// 提取所有任务的phone、wechatId用于查询好友信息获取通过时间
$friendInfo = Db::table('s2_wechat_friend')
->where(['isDeleted' => 0, 'ownerWechatId' => $task['wechatId']])
->where(function ($query) use ($task) {
$query->whereLike('phone', '%'.$task['phone'].'%')->whereOr('alias', $task['phone'])->whereOr('wechatId', $task['phone']);
})->field('phone,wechatId,alias,passTime,nickname')->find();
$item = [
'taskId' => $task['id'] ?? 0,
'phone' => $task['phone'] ?? '',
'wechatId' => $task['wechatId'] ?? '',
'alias' => $task['alias'] ?? '',
// 添加者信息
'adder' => [
'avatar' => $task['wechatAvatar'] ?? '', // 添加者头像
'nickname' => $task['wechatNickname'] ?? '', // 添加者昵称
'username' => $task['accountUsername'] ?? '', // 添加者微信号
'accountNickname' => $task['accountNickname'] ?? '', // 账号昵称
'accountRealName' => $task['accountRealName'] ?? '', // 账号真实姓名
],
// 添加状态
'status' => [
'code' => $task['status'] ?? 0, // 状态码0执行中1执行成功2执行失败
'text' => $this->getTaskStatusText($task['status'] ?? 0), // 状态文本
'extra' => ''
],
// 时间信息
'time' => [
'addTime' => !empty($task['createTime']) ? date('Y-m-d H:i:s', $task['createTime']) : '', // 添加时间
'addTimeStamp' => $task['createTime'] ?? 0, // 添加时间戳
'updateTime' => !empty($task['updateTime']) ? date('Y-m-d H:i:s', $task['updateTime']) : '', // 更新时间
'updateTimeStamp' => $task['updateTime'] ?? 0, // 更新时间戳
'passTime' => !empty($friendInfo['passTime']) ? date('Y-m-d H:i:s', $friendInfo['passTime']) : '', // 通过时间
'passTimeStamp' => $friendInfo['passTime'] ?? 0, // 通过时间戳
],
// 好友信息(如果已通过)
'friend' => [
'nickname' => $friendInfo['nickname'] ?? '', // 好友昵称
'isPassed' => !empty($friendInfo['passTime']), // 是否已通过
],
// 其他信息
'other' => [
'msgContent' => $task['msgContent'] ?? '', // 验证消息
'remark' => $task['remark'] ?? '', // 备注
'from' => $task['from'] ?? '', // 来源
'labels' => !empty($task['labels']) ? explode(',', $task['labels']) : [], // 标签
]
];
$list[] = $item;
}
return ResponseHelper::success(['list' => $list, 'total' => $total]);
}
/**
* 获取任务状态文本
* @param int $status 状态码
* @return string 状态文本
*/
private function getTaskStatusText($status)
{
$statusMap = [
0 => '执行中',
1 => '执行成功',
2 => '执行失败',
];
return isset($statusMap[$status]) ? $statusMap[$status] : '未知状态';
}
}

View File

@@ -0,0 +1,273 @@
<?php
namespace app\chukebao\controller;
use library\ResponseHelper;
use think\Db;
use app\chukebao\model\ChatGroups;
class WechatGroupController extends BaseController
{
/**
* 获取分组列表
* @return \think\response\Json
* @throws \Exception
*/
public function getList()
{
// 公司维度分组,不强制校验 userId
$companyId = $this->getUserInfo('companyId');
$query = ChatGroups::where([
'companyId' => $companyId,
'isDel' => 0,
])
->order('groupType desc,sort desc,id desc');
$total = $query->count();
$list = $query->select();
// 处理每个分组的数据
$list = is_array($list) ? $list : $list->toArray();
foreach ($list as $k => &$v) {
$v['createTime'] = !empty($v['createTime']) ? date('Y-m-d H:i:s', $v['createTime']) : '';
}
unset($v);
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
}
/**
* 新增分组
* @return \think\response\Json
* @throws \Exception
*/
public function create()
{
$groupName = $this->request->param('groupName', '');
$groupMemo = $this->request->param('groupMemo', '');
$groupType = $this->request->param('groupType', 1);
$sort = $this->request->param('sort', 0);
$companyId = $this->getUserInfo('companyId');
// 只校验公司维度
if (empty($companyId)) {
return ResponseHelper::error('请先登录');
}
if (empty($groupName)) {
return ResponseHelper::error('分组名称不能为空');
}
// 验证分组类型
if (!in_array($groupType, [1, 2])) {
return ResponseHelper::error('无效的分组类型');
}
Db::startTrans();
try {
$chatGroup = new ChatGroups();
$chatGroup->groupName = $groupName;
$chatGroup->groupMemo = $groupMemo;
$chatGroup->groupType = $groupType;
$chatGroup->sort = $sort;
$chatGroup->userId = $this->getUserInfo('id');
$chatGroup->companyId = $companyId;
$chatGroup->createTime = time();
$chatGroup->isDel = 0;
$chatGroup->save();
Db::commit();
return ResponseHelper::success(['id' => $chatGroup->id], '创建成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('创建失败:' . $e->getMessage());
}
}
/**
* 更新分组
* @return \think\response\Json
* @throws \Exception
*/
public function update()
{
$id = $this->request->param('id', 0);
$groupName = $this->request->param('groupName', '');
$groupMemo = $this->request->param('groupMemo', '');
$groupType = $this->request->param('groupType', 1);
$sort = $this->request->param('sort', 0);
$companyId = $this->getUserInfo('companyId');
if (empty($companyId)) {
return ResponseHelper::error('请先登录');
}
if (empty($id)) {
return ResponseHelper::error('参数缺失');
}
if (empty($groupName)) {
return ResponseHelper::error('分组名称不能为空');
}
// 验证分组类型
if (!in_array($groupType, [1, 2])) {
return ResponseHelper::error('无效的分组类型');
}
// 检查分组是否存在
$chatGroup = ChatGroups::where([
'id' => $id,
'companyId' => $companyId,
'isDel' => 0,
])->find();
if (empty($chatGroup)) {
return ResponseHelper::error('该分组不存在或已删除');
}
Db::startTrans();
try {
$chatGroup->groupName = $groupName;
$chatGroup->groupMemo = $groupMemo;
$chatGroup->groupType = $groupType;
$chatGroup->sort = $sort;
$chatGroup->save();
Db::commit();
return ResponseHelper::success('', '更新成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('更新失败:' . $e->getMessage());
}
}
/**
* 删除分组(假删除)
* @return \think\response\Json
* @throws \Exception
*/
public function delete()
{
$id = $this->request->param('id', 0);
$companyId = $this->getUserInfo('companyId');
if (empty($companyId)) {
return ResponseHelper::error('请先登录');
}
if (empty($id)) {
return ResponseHelper::error('参数缺失');
}
// 检查分组是否存在
$chatGroup = ChatGroups::where([
'id' => $id,
'companyId' => $companyId,
'isDel' => 0,
])->find();
if (empty($chatGroup)) {
return ResponseHelper::error('该分组不存在或已删除');
}
Db::startTrans();
try {
// 1. 假删除当前分组
$chatGroup->isDel = 1;
$chatGroup->deleteTime = time();
$chatGroup->save();
// 2. 重置该分组下所有好友的分组IDs2_wechat_friend.groupIds -> 0
Db::table('s2_wechat_friend')
->where('groupIds', $id)
->update(['groupIds' => 0]);
// 3. 重置该分组下所有微信群的分组IDs2_wechat_chatroom.groupIds -> 0
Db::table('s2_wechat_chatroom')
->where('groupIds', $id)
->update(['groupIds' => 0]);
Db::commit();
return ResponseHelper::success('', '删除成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('删除失败:' . $e->getMessage());
}
}
/**
* 移动分组(将好友或群移动到指定分组)
* @return \think\response\Json
* @throws \Exception
*/
public function move()
{
// type: friend 好友, chatroom 群
$type = $this->request->param('type', 'friend');
$targetId = (int)$this->request->param('groupId', 0);
// 仅支持单个ID移动
$idParam = $this->request->param('id', 0);
$companyId = $this->getUserInfo('companyId');
if (empty($companyId)) {
return ResponseHelper::error('请先登录');
}
if (empty($targetId)) {
return ResponseHelper::error('目标分组ID不能为空');
}
// 仅允许单个 ID禁止批量
$moveId = (int)$idParam;
if (empty($moveId)) {
return ResponseHelper::error('需要移动的ID不能为空');
}
// 校验目标分组是否存在且属于当前公司
$targetGroup = ChatGroups::where([
'id' => $targetId,
'companyId' => $companyId,
'isDel' => 0,
])->find();
if (empty($targetGroup)) {
return ResponseHelper::error('目标分组不存在或已删除');
}
// 校验分组类型与移动对象类型是否匹配
// groupType: 1=好友分组, 2=群分组
if ($type === 'friend' && (int)$targetGroup->groupType !== 1) {
return ResponseHelper::error('目标分组类型错误(需要好友分组)');
}
if ($type === 'chatroom' && (int)$targetGroup->groupType !== 2) {
return ResponseHelper::error('目标分组类型错误(需要群分组)');
}
Db::startTrans();
try {
if ($type === 'friend') {
// 移动单个好友到指定分组:更新 s2_wechat_friend.groupIds
Db::table('s2_wechat_friend')
->where('id', $moveId)
->update(['groupIds' => $targetId]);
} elseif ($type === 'chatroom') {
// 移动单个群到指定分组:更新 s2_wechat_chatroom.groupIds
Db::table('s2_wechat_chatroom')
->where('id', $moveId)
->update(['groupIds' => $targetId]);
} else {
Db::rollback();
return ResponseHelper::error('无效的类型参数');
}
Db::commit();
return ResponseHelper::success('', '移动成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('移动失败:' . $e->getMessage());
}
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace app\chukebao\model;
use think\Model;
/**
* AI知识库模型
*/
class AiKnowledgeBase extends Model
{
// 设置表名
protected $name = 'ai_knowledge_base';
// 设置主键
protected $pk = 'id';
// 设置JSON字段
protected $json = ['label'];
// 设置JSON字段自动转换为数组
protected $jsonAssoc = true;
/**
* 关联知识库类型
*/
public function type()
{
return $this->belongsTo(AiKnowledgeBaseType::class, 'typeId', 'id');
}
/**
* 获取有效的知识库列表(未删除)
*/
public static function getValidList($companyId, $typeId = null)
{
$where = [
['isDel', '=', 0],
['companyId', '=', $companyId]
];
if ($typeId !== null) {
$where[] = ['typeId', '=', $typeId];
}
return self::where($where)
->with(['type'])
->order('createTime', 'desc')
->select();
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace app\chukebao\model;
use think\Model;
/**
* AI知识库类型模型
*/
class AiKnowledgeBaseType extends Model
{
// 设置表名
protected $name = 'ai_knowledge_base_type';
// 设置主键
protected $pk = 'id';
// 设置JSON字段
protected $json = ['label'];
// 设置JSON字段自动转换为数组
protected $jsonAssoc = true;
// 类型常量
const TYPE_SYSTEM = 0; // 系统类型
const TYPE_USER = 1; // 用户创建类型
/**
* 获取有效的类型列表(未删除)
*/
public static function getValidList($companyId, $includeSystem = true)
{
$where = [
['isDel', '=', 0]
];
if ($includeSystem) {
$where[] = ['type|companyId', 'in', [0, $companyId]];
} else {
$where[] = ['companyId', '=', $companyId];
$where[] = ['type', '=', self::TYPE_USER];
}
return self::where($where)
->order('createTime', 'desc')
->select();
}
/**
* 检查是否为系统类型
*/
public function isSystemType()
{
return $this->type == self::TYPE_SYSTEM;
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace app\chukebao\model;
use think\Model;
class AiPush extends Model
{
protected $pk = 'id';
protected $name = 'kf_ai_push';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
protected $createTime = 'createTime';
protected $updateTime = 'updateTime';
}

View File

@@ -0,0 +1,16 @@
<?php
namespace app\chukebao\model;
use think\Model;
class AiPushRecord extends Model
{
protected $pk = 'id';
protected $name = 'kf_ai_push_record';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
protected $createTime = 'createTime';
}

View File

@@ -0,0 +1,17 @@
<?php
namespace app\chukebao\model;
use think\Model;
class AiSettings extends Model
{
protected $pk = 'id';
protected $name = 'ai_settings';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
protected $createTime = 'createTime';
protected $updateTime = 'updateTime';
}

View File

@@ -0,0 +1,17 @@
<?php
namespace app\chukebao\model;
use think\Model;
class AutoGreetings extends Model
{
protected $pk = 'id';
protected $name = 'kf_auto_greetings';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
protected $createTime = 'createTime';
protected $updateTime = 'updateTime';
}

View File

@@ -0,0 +1,16 @@
<?php
namespace app\chukebao\model;
use think\Model;
class ChatGroups extends Model
{
protected $pk = 'id';
protected $name = 'chat_groups';
// 不开启自动时间戳,手动维护 createTime / deleteTime
protected $autoWriteTimestamp = false;
}

View File

@@ -0,0 +1,17 @@
<?php
namespace app\chukebao\model;
use think\Model;
class FollowUp extends Model
{
protected $pk = 'id';
protected $name = 'kf_follow_up';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
protected $createTime = 'createTime';
protected $updateTime = 'updateTime';
}

View File

@@ -0,0 +1,17 @@
<?php
namespace app\chukebao\model;
use think\Model;
class FriendSettings extends Model
{
protected $pk = 'id';
protected $name = 'kf_friend_settings';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
protected $createTime = 'createTime';
protected $updateTime = 'updateTime';
}

View File

@@ -0,0 +1,17 @@
<?php
namespace app\chukebao\model;
use think\Model;
class Keywords extends Model
{
protected $pk = 'id';
protected $name = 'kf_keywords';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
protected $createTime = 'createTime';
protected $updateTime = 'updateTime';
}

View File

@@ -0,0 +1,11 @@
<?php
namespace app\chukebao\model;
use think\Model;
class KfMoments extends Model
{
protected $table = 'ck_kf_moments';
}

View File

@@ -0,0 +1,17 @@
<?php
namespace app\chukebao\model;
use think\Model;
class Material extends Model
{
protected $pk = 'id';
protected $name = 'kf_material';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
protected $createTime = 'createTime';
protected $updateTime = 'updateTime';
}

View File

@@ -0,0 +1,16 @@
<?php
namespace app\chukebao\model;
use think\Model;
class NoticeModel extends Model
{
protected $pk = 'id';
protected $name = 'kf_notice';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
protected $createTime = 'createTime';
protected $updateTime = 'updateTime';
}

View File

@@ -0,0 +1,17 @@
<?php
namespace app\chukebao\model;
use think\Model;
class Questions extends Model
{
protected $pk = 'id';
protected $name = 'kf_questions';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
protected $createTime = 'createTime';
protected $updateTime = 'updateTime';
}

View File

@@ -0,0 +1,17 @@
<?php
namespace app\chukebao\model;
use think\Model;
class Reply extends Model
{
protected $pk = 'id';
protected $name = 'kf_reply';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
protected $createTime = 'createTime';
protected $updateTime = 'lastUpdateTime';
}

View File

@@ -0,0 +1,10 @@
<?php
namespace app\chukebao\model;
use think\Model;
class ReplyGroup extends Model
{
protected $pk = 'id';
protected $name = 'kf_reply_group';
}

View File

@@ -0,0 +1,17 @@
<?php
namespace app\chukebao\model;
use think\Model;
class SensitiveWord extends Model
{
protected $pk = 'id';
protected $name = 'kf_sensitive_word';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
protected $createTime = 'createTime';
protected $updateTime = 'updateTime';
}

View File

@@ -0,0 +1,17 @@
<?php
namespace app\chukebao\model;
use think\Model;
class ToDo extends Model
{
protected $pk = 'id';
protected $name = 'kf_to_do';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
protected $createTime = 'createTime';
protected $updateTime = 'updateTime';
}

View File

@@ -0,0 +1,16 @@
<?php
namespace app\chukebao\model;
use think\Model;
class TokensCompany extends Model
{
protected $pk = 'id';
protected $name = 'tokens_company';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
protected $createTime = 'createTime';
}

View File

@@ -0,0 +1,16 @@
<?php
namespace app\chukebao\model;
use think\Model;
class TokensRecord extends Model
{
protected $pk = 'id';
protected $name = 'tokens_record';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
protected $createTime = 'createTime';
}