diff --git a/Server/application/ai/config/route.php b/Server/application/ai/config/route.php
index 286b7c24..ada60ae6 100644
--- a/Server/application/ai/config/route.php
+++ b/Server/application/ai/config/route.php
@@ -14,6 +14,7 @@ Route::group('v1/ai', function () {
//豆包ai
Route::group('doubao', function () {
Route::post('text', 'app\ai\controller\DouBaoAI@text');
+ Route::post('aiChat', 'app\ai\controller\DouBaoAI@aiChat');
});
diff --git a/Server/application/ai/controller/DouBaoAI.php b/Server/application/ai/controller/DouBaoAI.php
index 18012dbb..d61a6c89 100644
--- a/Server/application/ai/controller/DouBaoAI.php
+++ b/Server/application/ai/controller/DouBaoAI.php
@@ -56,4 +56,40 @@ class DouBaoAI
$result = json_decode($result, true);
return successJson($result);
}
+
+
+ public function aiChat()
+ {
+ $this->__init();
+
+ $content = input('content','');
+ if (empty($content)){
+ return json_encode(['code' => 500, 'msg' => '提示词缺失']);
+ }
+
+ $content = $content. '
+
+ 请结合上面的聊天记录给我最佳的客服回复';
+
+ // 发送请求
+ $params = [
+ 'model' => 'doubao-1-5-pro-32k-250115',
+ 'messages' => [
+ ['role' => 'system', 'content' => '以下是客服跟用户的对话.'],
+ ['role' => 'user', 'content' => $content],
+ ],
+ /*'extra_headers' => [
+ 'x-is-encrypted' => true
+ ],
+ 'temperature' => 1,
+ 'top_p' => 0.7,
+ 'max_tokens' => 4096,
+ 'frequency_penalty' => 0,*/
+ ];
+ $result = requestCurl($this->apiUrl, $params, 'POST', $this->headers, 'json');
+ $result = json_decode($result, true);
+ return successJson($result);
+ }
+
+
}
\ No newline at end of file
diff --git a/Server/application/chukebao/config/route.php b/Server/application/chukebao/config/route.php
index 1384ac63..9d2a93a6 100644
--- a/Server/application/chukebao/config/route.php
+++ b/Server/application/chukebao/config/route.php
@@ -26,8 +26,35 @@ Route::group('v1/', function () {
//客服相关
Route::group('message/', function () {
Route::get('list', 'app\chukebao\controller\MessageController@getList'); // 获取好友列表
+ Route::get('readMessage', 'app\chukebao\controller\MessageController@readMessage'); // 读取消息
});
+ //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::get('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');
+ });
+
+
+ });
+
+
+
});
diff --git a/Server/application/chukebao/controller/AiSettingsController.php b/Server/application/chukebao/controller/AiSettingsController.php
new file mode 100644
index 00000000..64161ddc
--- /dev/null
+++ b/Server/application/chukebao/controller/AiSettingsController.php
@@ -0,0 +1,153 @@
+ 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 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('该好友不存在');
+ }
+
+ $aiFriendSettings = AiFriendSettings::where(['userId' => $userId, 'companyId' => $companyId,'friendId' => $friendId,'wechatAccountId' => $wechatAccountId])->find();
+ Db::startTrans();
+ try {
+ if (empty($aiFriendSettings)) {
+ $aiFriendSettings = new AiFriendSettings();
+ $aiFriendSettings->companyId = $companyId;
+ $aiFriendSettings->userId = $userId;
+ $aiFriendSettings->type = $type;
+ $aiFriendSettings->wechatAccountId = $wechatAccountId;
+ $aiFriendSettings->friendId = $friendId;
+ $aiFriendSettings->createTime = time();
+ $aiFriendSettings->updateTime = time();
+ }else{
+ $aiFriendSettings->type = $type;
+ $aiFriendSettings->updateTime = time();
+ }
+ $aiFriendSettings->save();
+ Db::commit();
+ return ResponseHelper::success(' ', '配置成功');
+ } catch (\Exception $e) {
+ Db::rollback();
+ return ResponseHelper::error('配置失败:' . $e->getMessage());
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/Server/application/chukebao/controller/CustomerServiceController.php b/Server/application/chukebao/controller/CustomerServiceController.php
index 93892fe5..3197e91a 100644
--- a/Server/application/chukebao/controller/CustomerServiceController.php
+++ b/Server/application/chukebao/controller/CustomerServiceController.php
@@ -25,8 +25,8 @@ class CustomerServiceController extends BaseController
->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['createTime'] = !empty($v['createTime']) && is_numeric($v['createTime']) ? date('Y-m-d H:i:s',$v['createTime']) : '';
+ $v['updateTime'] = !empty($v['updateTime']) && is_numeric($v['updateTime']) ? date('Y-m-d H:i:s',$v['updateTime']) : '';
$v['labels'] = json_decode($v['labels'],true);
unset(
$v['accountUserName'],
diff --git a/Server/application/chukebao/controller/MessageController.php b/Server/application/chukebao/controller/MessageController.php
index c213b7e8..531714f9 100644
--- a/Server/application/chukebao/controller/MessageController.php
+++ b/Server/application/chukebao/controller/MessageController.php
@@ -53,6 +53,9 @@ class MessageController extends BaseController
->field('id,nickname,avatar')
->find();
$v['msgInfo'] = $friend;
+ $v['unreadCount'] = Db::table('s2_wechat_message')
+ ->where(['wechatFriendId' => $v['wechatFriendId'],'isRead' => 0])
+ ->count();
}
if (!empty($v['wechatChatroomId'])){
@@ -61,12 +64,45 @@ class MessageController extends BaseController
->field('id,nickname,chatroomAvatar as avatar')
->find();
$v['msgInfo'] = $chatroom;
+ $v['unreadCount'] = Db::table('s2_wechat_message')
+ ->where(['wechatChatroomId' => $v['wechatChatroomId'],'isRead' => 0])
+ ->count();
}
-
}
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([]);
+
+
+ }
+
+
}
\ No newline at end of file
diff --git a/Server/application/chukebao/controller/QuestionsController.php b/Server/application/chukebao/controller/QuestionsController.php
new file mode 100644
index 00000000..9ee8799e
--- /dev/null
+++ b/Server/application/chukebao/controller/QuestionsController.php
@@ -0,0 +1,232 @@
+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.'%');
+ }
+ $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'] = '';
+ }
+ $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,'获取成功');
+
+
+ }
+
+
+
+
+}
\ No newline at end of file
diff --git a/Server/application/chukebao/controller/WechatChatroomController.php b/Server/application/chukebao/controller/WechatChatroomController.php
index e6c64f5c..49fb802d 100644
--- a/Server/application/chukebao/controller/WechatChatroomController.php
+++ b/Server/application/chukebao/controller/WechatChatroomController.php
@@ -22,9 +22,57 @@ class WechatChatroomController extends BaseController
$total = $query->count();
- 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']) : '';
+ // 提取所有聊天室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);
diff --git a/Server/application/chukebao/controller/WechatFriendController.php b/Server/application/chukebao/controller/WechatFriendController.php
index bd8f0095..82a144ed 100644
--- a/Server/application/chukebao/controller/WechatFriendController.php
+++ b/Server/application/chukebao/controller/WechatFriendController.php
@@ -22,10 +22,60 @@ class WechatFriendController extends BaseController
$total = $query->count();
- 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['passTime'] = !empty($v['passTime']) ? date('Y-m-d H:i:s',$v['passTime']) : '';
+ // 提取所有好友ID
+ $friendIds = array_column($list, 'id');
+
+ // 一次性查询所有好友的未读消息数量
+ $unreadCounts = [];
+ if (!empty($friendIds)) {
+ $unreadResults = Db::table('s2_wechat_message')
+ ->field('wechatFriendId, COUNT(*) as count')
+ ->where('wechatFriendId', 'in', $friendIds)
+ ->where('isRead', 0)
+ ->group('wechatFriendId')
+ ->select();
+
+ foreach ($unreadResults as $result) {
+ $unreadCounts[$result['wechatFriendId']] = $result['count'];
+ }
+ }
+
+ // 一次性查询所有好友的最新消息
+ $latestMessages = [];
+ if (!empty($friendIds)) {
+ // 使用子查询获取每个好友的最新消息ID
+ $subQuery = Db::table('s2_wechat_message')
+ ->field('MAX(id) as max_id, wechatFriendId')
+ ->where('wechatFriendId', 'in', $friendIds)
+ ->group('wechatFriendId')
+ ->buildSql();
+
+ // 查询最新消息的详细信息
+ $messageResults = Db::table('s2_wechat_message')
+ ->alias('m')
+ ->join([$subQuery => 'sub'], 'm.id = sub.max_id')
+ ->field('m.*, sub.wechatFriendId')
+ ->select();
+
+ foreach ($messageResults as $message) {
+ $latestMessages[$message['wechatFriendId']] = $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']) : '';
+ $v['passTime'] = !empty($v['passTime']) ? date('Y-m-d H:i:s', $v['passTime']) : '';
+
+ $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);
diff --git a/Server/application/chukebao/model/AiFriendSettings.php b/Server/application/chukebao/model/AiFriendSettings.php
new file mode 100644
index 00000000..de99b4a9
--- /dev/null
+++ b/Server/application/chukebao/model/AiFriendSettings.php
@@ -0,0 +1,17 @@
+setName('clean:expired_group_messages')
+ ->setDescription('Clean expired group messages from the database')
+ ->addOption('days', 'd', Option::VALUE_OPTIONAL, 'Number of days to keep messages (default: 90)', 90)
+ ->addOption('dry-run', null, Option::VALUE_NONE, 'Perform a dry run without deleting any data')
+ ->addOption('batch-size', 'b', Option::VALUE_OPTIONAL, 'Batch size for deletion (default: 1000)', 1000);
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $days = (int)$input->getOption('days');
+ $dryRun = $input->getOption('dry-run');
+ $batchSize = (int)$input->getOption('batch-size');
+
+ if ($dryRun) {
+ $output->writeln("Running in dry-run mode. No data will be deleted.");
+ }
+
+ $cutoffDate = date('Y-m-d H:i:s', strtotime("-{$days} days"));
+ $output->writeln("Cleaning group messages older than {$cutoffDate} (keeping last {$days} days)");
+
+ // 清理微信群组消息
+ $this->cleanWechatGroupMessages($cutoffDate, $dryRun, $batchSize, $output);
+
+ $output->writeln("Group message cleanup completed successfully.");
+ }
+
+ protected function cleanWechatGroupMessages($cutoffDate, $dryRun, $batchSize, Output $output)
+ {
+ $output->writeln("\nCleaning s2_wechat_group_message table...");
+
+ // 获取符合条件的消息总数
+ $totalCount = Db::table('s2_wechat_group_message')
+ ->where('createTime', '<', $cutoffDate)
+ ->count();
+
+ if ($totalCount === 0) {
+ $output->writeln(" No expired group messages found.");
+ return;
+ }
+
+ $output->writeln(" Found {$totalCount} group messages to clean up.");
+
+ if ($dryRun) {
+ $output->writeln(" Dry run mode: would delete {$totalCount} group messages.");
+ return;
+ }
+
+ // 计算需要执行的批次数
+ $batches = ceil($totalCount / $batchSize);
+ $deletedCount = 0;
+
+ $output->writeln(" Deleting in {$batches} batches of {$batchSize} records...");
+
+ // 分批删除数据
+ for ($i = 0; $i < $batches; $i++) {
+ // 获取一批要删除的ID
+ $ids = Db::table('s2_wechat_group_message')
+ ->where('createTime', '<', $cutoffDate)
+ ->limit($batchSize)
+ ->column('id');
+
+ if (empty($ids)) {
+ break;
+ }
+
+ // 删除这批数据
+ $count = Db::table('s2_wechat_group_message')
+ ->whereIn('id', $ids)
+ ->delete();
+
+ $deletedCount += $count;
+ $progress = round(($deletedCount / $totalCount) * 100, 2);
+ $output->write(" Progress: {$progress}% ({$deletedCount}/{$totalCount})\r");
+
+ // 短暂暂停,减轻数据库负担
+ usleep(500000); // 暂停0.5秒
+ }
+
+ $output->writeln("");
+ $output->writeln(" Successfully deleted {$deletedCount} expired group messages.");
+
+ // 优化表
+ $output->writeln(" Optimizing table...");
+ Db::execute("OPTIMIZE TABLE s2_wechat_group_message");
+ $output->writeln(" Table optimization completed.");
+ }
+}
\ No newline at end of file
diff --git a/Server/application/command/CleanExpiredMessages.php b/Server/application/command/CleanExpiredMessages.php
new file mode 100644
index 00000000..d499f653
--- /dev/null
+++ b/Server/application/command/CleanExpiredMessages.php
@@ -0,0 +1,100 @@
+setName('clean:expired_messages')
+ ->setDescription('Clean expired messages from the database')
+ ->addOption('days', 'd', Option::VALUE_OPTIONAL, 'Number of days to keep messages (default: 90)', 90)
+ ->addOption('dry-run', null, Option::VALUE_NONE, 'Perform a dry run without deleting any data')
+ ->addOption('batch-size', 'b', Option::VALUE_OPTIONAL, 'Batch size for deletion (default: 1000)', 1000);
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $days = (int)$input->getOption('days');
+ $dryRun = $input->getOption('dry-run');
+ $batchSize = (int)$input->getOption('batch-size');
+
+ if ($dryRun) {
+ $output->writeln("Running in dry-run mode. No data will be deleted.");
+ }
+
+ $cutoffDate = date('Y-m-d H:i:s', strtotime("-{$days} days"));
+ $output->writeln("Cleaning messages older than {$cutoffDate} (keeping last {$days} days)");
+
+ // 清理微信消息
+ $this->cleanWechatMessages($cutoffDate, $dryRun, $batchSize, $output);
+
+ $output->writeln("Message cleanup completed successfully.");
+ }
+
+ protected function cleanWechatMessages($cutoffDate, $dryRun, $batchSize, Output $output)
+ {
+ $output->writeln("\nCleaning s2_wechat_message table...");
+
+ // 获取符合条件的消息总数
+ $totalCount = Db::table('s2_wechat_message')
+ ->where('createTime', '<', $cutoffDate)
+ ->count();
+
+ if ($totalCount === 0) {
+ $output->writeln(" No expired messages found.");
+ return;
+ }
+
+ $output->writeln(" Found {$totalCount} messages to clean up.");
+
+ if ($dryRun) {
+ $output->writeln(" Dry run mode: would delete {$totalCount} messages.");
+ return;
+ }
+
+ // 计算需要执行的批次数
+ $batches = ceil($totalCount / $batchSize);
+ $deletedCount = 0;
+
+ $output->writeln(" Deleting in {$batches} batches of {$batchSize} records...");
+
+ // 分批删除数据
+ for ($i = 0; $i < $batches; $i++) {
+ // 获取一批要删除的ID
+ $ids = Db::table('s2_wechat_message')
+ ->where('createTime', '<', $cutoffDate)
+ ->limit($batchSize)
+ ->column('id');
+
+ if (empty($ids)) {
+ break;
+ }
+
+ // 删除这批数据
+ $count = Db::table('s2_wechat_message')
+ ->whereIn('id', $ids)
+ ->delete();
+
+ $deletedCount += $count;
+ $progress = round(($deletedCount / $totalCount) * 100, 2);
+ $output->write(" Progress: {$progress}% ({$deletedCount}/{$totalCount})\r");
+
+ // 短暂暂停,减轻数据库负担
+ usleep(500000); // 暂停0.5秒
+ }
+
+ $output->writeln("");
+ $output->writeln(" Successfully deleted {$deletedCount} expired messages.");
+
+ // 优化表
+ $output->writeln(" Optimizing table...");
+ Db::execute("OPTIMIZE TABLE s2_wechat_message");
+ $output->writeln(" Table optimization completed.");
+ }
+}
\ No newline at end of file
diff --git a/Server/application/command/OptimizeMessageIndexes.php b/Server/application/command/OptimizeMessageIndexes.php
new file mode 100644
index 00000000..a604191e
--- /dev/null
+++ b/Server/application/command/OptimizeMessageIndexes.php
@@ -0,0 +1,112 @@
+setName('optimize:message_indexes')
+ ->setDescription('Optimize database indexes for message-related tables');
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $output->writeln("Starting index optimization for message-related tables...");
+
+ // 优化 s2_wechat_message 表索引
+ $this->optimizeWechatMessageIndexes($output);
+
+ // 优化 s2_wechat_chatroom 表索引
+ $this->optimizeWechatChatroomIndexes($output);
+
+ // 优化 s2_wechat_friend 表索引
+ $this->optimizeWechatFriendIndexes($output);
+
+ $output->writeln("Index optimization completed successfully.");
+ }
+
+ protected function optimizeWechatMessageIndexes(Output $output)
+ {
+ $output->writeln("Optimizing s2_wechat_message table indexes...");
+
+ // 检查并添加 wechatChatroomId 索引
+ $this->addIndexIfNotExists('s2_wechat_message', 'idx_chatroom_id', 'wechatChatroomId', $output);
+
+ // 检查并添加 wechatFriendId 索引
+ $this->addIndexIfNotExists('s2_wechat_message', 'idx_friend_id', 'wechatFriendId', $output);
+
+ // 检查并添加 isRead 索引
+ $this->addIndexIfNotExists('s2_wechat_message', 'idx_is_read', 'isRead', $output);
+
+ // 检查并添加 type 索引
+ $this->addIndexIfNotExists('s2_wechat_message', 'idx_type', 'type', $output);
+
+ // 检查并添加 createTime 索引
+ $this->addIndexIfNotExists('s2_wechat_message', 'idx_create_time', 'createTime', $output);
+
+ // 检查并添加组合索引 (wechatChatroomId, isRead)
+ $this->addIndexIfNotExists('s2_wechat_message', 'idx_chatroom_read', 'wechatChatroomId,isRead', $output);
+
+ // 检查并添加组合索引 (wechatFriendId, isRead)
+ $this->addIndexIfNotExists('s2_wechat_message', 'idx_friend_read', 'wechatFriendId,isRead', $output);
+ }
+
+ protected function optimizeWechatChatroomIndexes(Output $output)
+ {
+ $output->writeln("Optimizing s2_wechat_chatroom table indexes...");
+
+ // 检查并添加 accountId 索引
+ $this->addIndexIfNotExists('s2_wechat_chatroom', 'idx_account_id', 'accountId', $output);
+
+ // 检查并添加 isDeleted 索引
+ $this->addIndexIfNotExists('s2_wechat_chatroom', 'idx_is_deleted', 'isDeleted', $output);
+
+ // 检查并添加组合索引 (accountId, isDeleted)
+ $this->addIndexIfNotExists('s2_wechat_chatroom', 'idx_account_deleted', 'accountId,isDeleted', $output);
+ }
+
+ protected function optimizeWechatFriendIndexes(Output $output)
+ {
+ $output->writeln("Optimizing s2_wechat_friend table indexes...");
+
+ // 检查并添加 accountId 索引
+ $this->addIndexIfNotExists('s2_wechat_friend', 'idx_account_id', 'accountId', $output);
+
+ // 检查并添加 isDeleted 索引
+ $this->addIndexIfNotExists('s2_wechat_friend', 'idx_is_deleted', 'isDeleted', $output);
+
+ // 检查并添加组合索引 (accountId, isDeleted)
+ $this->addIndexIfNotExists('s2_wechat_friend', 'idx_account_deleted', 'accountId,isDeleted', $output);
+ }
+
+ protected function addIndexIfNotExists($table, $indexName, $columns, Output $output)
+ {
+ try {
+ // 检查索引是否已存在
+ $indexExists = false;
+ $indexes = Db::query("SHOW INDEX FROM {$table}");
+
+ foreach ($indexes as $index) {
+ if ($index['Key_name'] === $indexName) {
+ $indexExists = true;
+ break;
+ }
+ }
+
+ if (!$indexExists) {
+ // 添加索引
+ Db::execute("ALTER TABLE {$table} ADD INDEX {$indexName} ({$columns})");
+ $output->writeln(" - Added index {$indexName} on {$table}({$columns})");
+ } else {
+ $output->writeln(" - Index {$indexName} already exists on {$table}");
+ }
+ } catch (\Exception $e) {
+ $output->writeln(" - Error adding index {$indexName} to {$table}: " . $e->getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/Server/application/command/ScheduleMessageMaintenance.php b/Server/application/command/ScheduleMessageMaintenance.php
new file mode 100644
index 00000000..359faf93
--- /dev/null
+++ b/Server/application/command/ScheduleMessageMaintenance.php
@@ -0,0 +1,121 @@
+setName('schedule:message_maintenance')
+ ->setDescription('Schedule and run message maintenance tasks')
+ ->addOption('optimize-indexes', null, Option::VALUE_NONE, 'Run index optimization')
+ ->addOption('clean-messages', null, Option::VALUE_NONE, 'Clean expired messages')
+ ->addOption('days', 'd', Option::VALUE_OPTIONAL, 'Number of days to keep messages (default: 90)', 90)
+ ->addOption('batch-size', 'b', Option::VALUE_OPTIONAL, 'Batch size for deletion (default: 1000)', 1000)
+ ->addOption('dry-run', null, Option::VALUE_NONE, 'Perform a dry run without deleting any data');
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $optimizeIndexes = $input->getOption('optimize-indexes');
+ $cleanMessages = $input->getOption('clean-messages');
+ $days = (int)$input->getOption('days');
+ $batchSize = (int)$input->getOption('batch-size');
+ $dryRun = $input->getOption('dry-run');
+
+ // 如果没有指定任何选项,则运行所有维护任务
+ if (!$optimizeIndexes && !$cleanMessages) {
+ $optimizeIndexes = true;
+ $cleanMessages = true;
+ }
+
+ $output->writeln("Starting scheduled message maintenance tasks...");
+ $startTime = microtime(true);
+
+ // 运行索引优化
+ if ($optimizeIndexes) {
+ $this->runCommand($output, 'optimize:message_indexes');
+ }
+
+ // 清理过期消息
+ if ($cleanMessages) {
+ $options = [];
+
+ if ($days !== 90) {
+ $options[] = "--days={$days}";
+ }
+
+ if ($batchSize !== 1000) {
+ $options[] = "--batch-size={$batchSize}";
+ }
+
+ if ($dryRun) {
+ $options[] = "--dry-run";
+ }
+
+ $this->runCommand($output, 'clean:expired_messages', $options);
+ $this->runCommand($output, 'clean:expired_group_messages', $options);
+ }
+
+ $endTime = microtime(true);
+ $executionTime = round($endTime - $startTime, 2);
+ $output->writeln("All maintenance tasks completed in {$executionTime} seconds.");
+ }
+
+ protected function runCommand(Output $output, $command, array $options = [])
+ {
+ $output->writeln("\nRunning command: {$command}");
+
+ $optionsStr = implode(' ', $options);
+ $fullCommand = "php think {$command} {$optionsStr}";
+
+ $output->writeln("Executing: {$fullCommand}");
+ $output->writeln("\nCommand output:");
+
+ // 执行命令并实时输出结果
+ $descriptorSpec = [
+ 0 => ["pipe", "r"], // stdin
+ 1 => ["pipe", "w"], // stdout
+ 2 => ["pipe", "w"] // stderr
+ ];
+
+ $process = proc_open($fullCommand, $descriptorSpec, $pipes);
+
+ if (is_resource($process)) {
+ // 关闭标准输入
+ fclose($pipes[0]);
+
+ // 读取标准输出
+ while (!feof($pipes[1])) {
+ $line = fgets($pipes[1]);
+ if ($line !== false) {
+ $output->write($line);
+ }
+ }
+ fclose($pipes[1]);
+
+ // 读取标准错误
+ $errorOutput = stream_get_contents($pipes[2]);
+ fclose($pipes[2]);
+
+ // 获取命令执行结果
+ $exitCode = proc_close($process);
+
+ if ($exitCode !== 0) {
+ $output->writeln("\nCommand failed with exit code {$exitCode}");
+ if (!empty($errorOutput)) {
+ $output->writeln("Error output:");
+ $output->writeln($errorOutput);
+ }
+ } else {
+ $output->writeln("\nCommand completed successfully.");
+ }
+ } else {
+ $output->writeln("Failed to execute command.");
+ }
+ }
+}
\ No newline at end of file