From 8fa760a3bd27cce6820115a6eb2d9c5bc3b47144 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Tue, 15 Jul 2025 09:07:15 +0800 Subject: [PATCH 01/33] =?UTF-8?q?=E5=B7=A5=E5=8D=95=E5=88=86=E7=B1=BB?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/service/CategorieService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ticket/service/CategorieService.php b/app/ticket/service/CategorieService.php index 4954c85..67d1d74 100644 --- a/app/ticket/service/CategorieService.php +++ b/app/ticket/service/CategorieService.php @@ -14,7 +14,7 @@ class CategorieService public function list($params) { $result = (new CategorieLogic())->list($params); - $value=$result[0]->toArray(); +// $value=$result[0]->toArray(); foreach ($result as $key => &$value) { $value['name'] = isset($value['translation']) ? $value['translation']['name'] : ''; unset($value['translation']); From 78e28a99dc224b18f2bced6afff56e49b0e27129 Mon Sep 17 00:00:00 2001 From: mzl Date: Wed, 20 Aug 2025 13:46:59 +0800 Subject: [PATCH 02/33] =?UTF-8?q?ws=E6=96=B0=E5=A2=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/service/ResourceService.php | 1 + app/common/utils/WsEvents.php | 221 ++++++++++++++++++ app/common/utils/WsResponse.php | 47 ++++ composer.json | 3 +- composer.lock | 89 ++++++- config/plugin/webman/gateway-worker/app.php | 4 + .../plugin/webman/gateway-worker/process.php | 39 ++++ plugin/webman/gateway/Events.php | 38 +++ 8 files changed, 440 insertions(+), 2 deletions(-) create mode 100644 app/common/utils/WsEvents.php create mode 100644 app/common/utils/WsResponse.php create mode 100644 config/plugin/webman/gateway-worker/app.php create mode 100644 config/plugin/webman/gateway-worker/process.php create mode 100644 plugin/webman/gateway/Events.php diff --git a/app/common/service/ResourceService.php b/app/common/service/ResourceService.php index ad3f85b..c1654a5 100644 --- a/app/common/service/ResourceService.php +++ b/app/common/service/ResourceService.php @@ -53,6 +53,7 @@ class ResourceService $config['order']['id'] = 'asc'; break; case 'menuTreeList': + $config['where']['system_id'] = ['in', [0, $p['system_id']]]; $config['where']['status'] = 1; $config['order']['sort'] = 'asc'; $config['order']['id'] = 'asc'; diff --git a/app/common/utils/WsEvents.php b/app/common/utils/WsEvents.php new file mode 100644 index 0000000..0916078 --- /dev/null +++ b/app/common/utils/WsEvents.php @@ -0,0 +1,221 @@ +registerAddress; + } + + /** + * 监听消息接收 + */ + public function message($client_id, $message) + { + $this->clientId = $client_id; + + $payload = json_decode($message, true); + $event = $payload['event']; + $this->eventData = $payload['data'] ?? []; + $traceId = $payload['trace_id'] ?? null; + + $method = $this->eventToMethod($event); + + if (method_exists($this, $method)) { + try { + $this->$method($traceId); + } catch (\Throwable $th) { + Gateway::sendToClient($client_id, WsResponse::error( + $event, + 500, + $th->getMessage(), + $traceId + )); + } + } else { + Gateway::sendToClient($client_id, WsResponse::error( + $event, + 404, + "Event handler not found: $method", + $traceId + )); + } + } + + + // ================= 系统事件 ================= + /** + * 认证 + */ + public function auth($client_id, $payload) + { + try { + // 检查来源 + if (!isset($payload['server']['HTTP_ORIGIN'])) { + flg('[x] [TOKEN认证] 未定义HTTP_ORIGIN', false); + Gateway::sendToClient($client_id, WsResponse::error( + 'system.auth', + 1001, + '[x] [TOKEN认证] 未定义HTTP_ORIGIN' + )); + Gateway::closeClient($client_id); + return; + } + + // // 连接是否满足条件 + // if ($payload['server']['HTTP_ORIGIN'] != 'https://wstool.js.org') { + // flg('[x] [TOKEN认证] HTTP_ORIGIN不满足条件', false); + // Gateway::sendToClient($client_id, WsResponse::error( + // 'system.auth', + // 1001, + // '[x] [TOKEN认证] HTTP_ORIGIN不满足条件' + // )); + // Gateway::closeClient($client_id); + // return; + // } + + // 签名检查 + if (!isset($payload['get']['token'])) { + flg('[x] [TOKEN认证] 未携带TOKEN参数', false); + Gateway::sendToClient($client_id, WsResponse::error( + 'system.auth', + 1001, + '[x] [TOKEN认证] 未携带TOKEN参数' + )); + Gateway::closeClient($client_id); + return; + } + + // 解析TOKEN + $user = (new Jwt())->verifyToken($payload['get']['token']); + // 绑定用户ID + Gateway::bindUid($client_id, $user->uid); + + // 响应 + Gateway::sendToClient($client_id, WsResponse::success( + 'system.auth', + ['uid' => $user->uid] + )); + } catch (\Throwable $th) { + flg('[x] [TOKEN认证] TOKEN认证失败:'.$th->getMessage(), false); + Gateway::sendToClient($client_id, WsResponse::error( + 'system.auth', + 1001, + '[x] [TOKEN认证] TOKEN认证失败:'.$th->getMessage() + )); + Gateway::closeClient($client_id); + return; + } + } + + /** + * 系统心跳 + * + * @param string|null $traceId + */ + public function systemPing(?string $traceId) + { + Gateway::sendToClient($this->clientId, WsResponse::success( + 'system.pong', + ['time' => time()], + $traceId + )); + } + + + // ================= 消息通知事件 ================= + /** + * 管理端主动推送通知 + * data = [ + * 'to_uids' => [uid1, uid2], // 可选,为空表示全员 + * 'title' => '标题', + * 'content' => '内容', + * ] + */ + public function notifyPush(?string $traceId) + { + $title = $this->eventData['title'] ?? null; + $content = $this->eventData['content'] ?? null; + $toUids = $this->eventData['to_uids'] ?? []; + + if (!$title || !$content) { + Gateway::sendToClient($this->clientId, WsResponse::error( + 'notify.push', + 400, + 'title and content are required', + $traceId + )); + return; + } + + $msgId = rand(1000, 9999); // 生成消息ID,可用数据库存储 + + $payload = WsResponse::success('notify.push', [ + 'msg_id' => $msgId, + 'title' => $title, + 'content' => $content, + 'created_at' => time() + ], $traceId); + + if (!empty($toUids)) { + // 推送给指定用户 + foreach ($toUids as $uid) { + Gateway::sendToUid($uid, $payload); + } + } else { + // 推送给全体在线用户 + Gateway::sendToAll($payload); + } + + // 回复发送者确认 + Gateway::sendToClient($this->clientId, WsResponse::success( + 'notify.push.result', + ['msg_id' => $msgId], + $traceId + )); + } + + /** + * 客户端标记通知已读 + * data = ['msg_ids' => [1,2,3]] + */ + public function notifyRead(?string $traceId) + { + $msgIds = $this->eventData['msg_ids'] ?? []; + if (empty($msgIds)) { + Gateway::sendToClient($this->clientId, WsResponse::error( + 'notify.read', + 400, + 'msg_ids required', + $traceId + )); + return; + } + Gateway::sendToClient($this->clientId, WsResponse::success( + 'notify.read.result', + ['msg_ids' => $msgIds], + $traceId + )); + } + + // ================= 辅助方法 ================= + protected function eventToMethod(string $event): string + { + $parts = explode('.', $event); + $method = array_shift($parts); + foreach ($parts as $part) { + $method .= ucfirst($part); + } + return $method; + } +} \ No newline at end of file diff --git a/app/common/utils/WsResponse.php b/app/common/utils/WsResponse.php new file mode 100644 index 0000000..6cf0b28 --- /dev/null +++ b/app/common/utils/WsResponse.php @@ -0,0 +1,47 @@ + $event, + 'code' => $code, + 'message' => $message, + 'data' => $data, + 'trace_id' => $traceId, // 如果没有传就是 null + ], JSON_UNESCAPED_UNICODE); + } + + /** + * 错误返回 + * + * @param string $event 事件名称 + * @param int $code 错误码 + * @param string $message 错误信息 + * @param string|null $traceId 请求追踪ID,可选,默认 null + * @param array $data 附加数据(可选) + * @return string JSON + */ + public static function error(string $event, int $code, string $message, ?string $traceId = null, array $data = []): string + { + return json_encode([ + 'event' => $event, + 'code' => $code, + 'message' => $message, + 'data' => $data, + 'trace_id' => $traceId, // 如果没有传就是 null + ], JSON_UNESCAPED_UNICODE); + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index 98e3e54..c474010 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,8 @@ "symfony/filesystem": "^7.3", "workerman/crontab": "^1.0", "phpoffice/phpspreadsheet": "^4.5", - "phpmailer/phpmailer": "^6.10" + "phpmailer/phpmailer": "^6.10", + "webman/gateway-worker": "^1.0" }, "suggest": { "ext-event": "For better performance. " diff --git a/composer.lock b/composer.lock index edf286f..7d41f97 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "505fb3d97c338ea53b787196fcc3daaa", + "content-hash": "7c70d8c8bda8a5098b8baed463db8839", "packages": [ { "name": "aliyuncs/oss-sdk-php", @@ -6634,6 +6634,39 @@ }, "time": "2025-04-04T13:23:25+00:00" }, + { + "name": "webman/gateway-worker", + "version": "v1.0.9", + "source": { + "type": "git", + "url": "https://github.com/webman-php/gateway-worker.git", + "reference": "6780c1c788c273a24699caee84feb14224234000" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/gateway-worker/zipball/6780c1c788c273a24699caee84feb14224234000", + "reference": "6780c1c788c273a24699caee84feb14224234000", + "shasum": "" + }, + "require": { + "workerman/gateway-worker": ">=3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\GatewayWorker\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "support": { + "issues": "https://github.com/webman-php/gateway-worker/issues", + "source": "https://github.com/webman-php/gateway-worker/tree/v1.0.9" + }, + "time": "2024-04-30T01:05:42+00:00" + }, { "name": "webman/redis", "version": "v2.1.3", @@ -6793,6 +6826,60 @@ }, "time": "2025-01-15T07:20:50+00:00" }, + { + "name": "workerman/gateway-worker", + "version": "v4.0.1", + "source": { + "type": "git", + "url": "https://github.com/walkor/GatewayWorker.git", + "reference": "25e4430982caa98f82e9187d092ce34b2fddfc90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/GatewayWorker/zipball/25e4430982caa98f82e9187d092ce34b2fddfc90", + "reference": "25e4430982caa98f82e9187d092ce34b2fddfc90", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "workerman/workerman": "^4.0.30 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "GatewayWorker\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "homepage": "http://www.workerman.net", + "keywords": [ + "communication", + "distributed" + ], + "support": { + "issues": "https://github.com/walkor/GatewayWorker/issues", + "source": "https://github.com/walkor/GatewayWorker/tree/v4.0.1" + }, + "funding": [ + { + "url": "https://opencollective.com/walkor", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/walkor", + "type": "patreon" + } + ], + "time": "2025-06-27T01:30:09+00:00" + }, { "name": "workerman/redis", "version": "v2.0.4", diff --git a/config/plugin/webman/gateway-worker/app.php b/config/plugin/webman/gateway-worker/app.php new file mode 100644 index 0000000..8f9c426 --- /dev/null +++ b/config/plugin/webman/gateway-worker/app.php @@ -0,0 +1,4 @@ + true, +]; \ No newline at end of file diff --git a/config/plugin/webman/gateway-worker/process.php b/config/plugin/webman/gateway-worker/process.php new file mode 100644 index 0000000..5c7efaa --- /dev/null +++ b/config/plugin/webman/gateway-worker/process.php @@ -0,0 +1,39 @@ + [ + 'handler' => Gateway::class, + 'listen' => 'websocket://0.0.0.0:5151', + 'count' => 2, + 'reloadable' => false, + 'constructor' => ['config' => [ + 'lanIp' => '127.0.0.1', + 'startPort' => 2300, + 'pingInterval' => 55, + 'pingNotResponseLimit' => 1, + 'pingData' => '', + 'registerAddress' => '127.0.0.1:1236', + 'onConnect' => function(){}, + ]] + ], + 'worker' => [ + 'handler' => BusinessWorker::class, + 'count' => cpu_count()*2, + 'constructor' => ['config' => [ + 'eventHandler' => plugin\webman\gateway\Events::class, + 'name' => 'ChatBusinessWorker', + 'registerAddress' => '127.0.0.1:1236', + ]] + ], + 'register' => [ + 'handler' => Register::class, + 'listen' => 'text://127.0.0.1:1236', + 'count' => 1, // Must be 1 + 'reloadable' => false, + 'constructor' => [] + ], +]; diff --git a/plugin/webman/gateway/Events.php b/plugin/webman/gateway/Events.php new file mode 100644 index 0000000..d63708e --- /dev/null +++ b/plugin/webman/gateway/Events.php @@ -0,0 +1,38 @@ +auth($client_id, $data); + } + + public static function onMessage($client_id, $message) + { + self::$wsEvents->message($client_id, $message); + } + + public static function onClose($client_id) + { + var_dump('关闭客户端ID:'.$client_id); + } + +} \ No newline at end of file From 9f96b8d6253f6f380f4d93a4026b0103710ef8ed Mon Sep 17 00:00:00 2001 From: mzl Date: Wed, 20 Aug 2025 18:12:07 +0800 Subject: [PATCH 03/33] =?UTF-8?q?=E6=B6=88=E6=81=AF=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MessageNotificationsController.php | 33 +++++++ .../MessageUserStatusController.php | 33 +++++++ .../logic/MessageNotificationsLogic.php | 17 ++++ app/common/logic/MessageUserStatusLogic.php | 17 ++++ app/common/model/MessageNotifications.php | 49 ++++++++++ app/common/model/MessageUserStatus.php | 49 ++++++++++ .../service/MessageNotificationsService.php | 95 +++++++++++++++++++ .../service/MessageUserStatusService.php | 31 ++++++ app/common/utils/WsEvents.php | 56 +++++++---- .../validate/MessageNotificationsValidate.php | 29 ++++++ .../validate/MessageUserStatusValidate.php | 29 ++++++ app/controller/IndexController.php | 30 ++---- app/functions.php | 24 +++++ 13 files changed, 451 insertions(+), 41 deletions(-) create mode 100644 app/common/controller/MessageNotificationsController.php create mode 100644 app/common/controller/MessageUserStatusController.php create mode 100644 app/common/logic/MessageNotificationsLogic.php create mode 100644 app/common/logic/MessageUserStatusLogic.php create mode 100644 app/common/model/MessageNotifications.php create mode 100644 app/common/model/MessageUserStatus.php create mode 100644 app/common/service/MessageNotificationsService.php create mode 100644 app/common/service/MessageUserStatusService.php create mode 100644 app/common/validate/MessageNotificationsValidate.php create mode 100644 app/common/validate/MessageUserStatusValidate.php diff --git a/app/common/controller/MessageNotificationsController.php b/app/common/controller/MessageNotificationsController.php new file mode 100644 index 0000000..4e475eb --- /dev/null +++ b/app/common/controller/MessageNotificationsController.php @@ -0,0 +1,33 @@ +service = new MessageNotificationsService(); + } + + /** + * 列表页数据格式化 + * + * @param array $data + * @return array + */ + // protected function indexFormat($data) + // { + // if (isset($data['created_at'])) + // $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + // if (isset($data['updated_at'])) + // $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + // return $data; + // } +} \ No newline at end of file diff --git a/app/common/controller/MessageUserStatusController.php b/app/common/controller/MessageUserStatusController.php new file mode 100644 index 0000000..302aec4 --- /dev/null +++ b/app/common/controller/MessageUserStatusController.php @@ -0,0 +1,33 @@ +service = new MessageUserStatusService(); + } + + /** + * 列表页数据格式化 + * + * @param array $data + * @return array + */ + // protected function indexFormat($data) + // { + // if (isset($data['created_at'])) + // $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + // if (isset($data['updated_at'])) + // $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + // return $data; + // } +} \ No newline at end of file diff --git a/app/common/logic/MessageNotificationsLogic.php b/app/common/logic/MessageNotificationsLogic.php new file mode 100644 index 0000000..b0a1429 --- /dev/null +++ b/app/common/logic/MessageNotificationsLogic.php @@ -0,0 +1,17 @@ +validator; + } + + + /** + * 创建一条消息 + */ + public function createMessage(array $data): int + { + $msgId = Db::table('message_notifications')->insertGetId([ + 'msg_no' => generateMsgId(), + 'sender_id' => $data['sender_id'], + 'sender_type' => $data['sender_type'], + 'title' => $data['title'], + 'content' => $data['content'], + 'scene' => $data['scene'], + 'recipient' => $data['recipient'], + 'created_at' => time(), + ]); + + return $msgId; + } + + /** + * 批量插入用户消息状态 + */ + public function addUserStatus(int $msgId, array $uids) + { + $rows = []; + foreach ($uids as $uid) { + $rows[] = [ + 'msg_id' => $msgId, + 'uid' => $uid, + 'status' => 0, + 'read_at' => null + ]; + } + if ($rows) { + Db::table('message_user_status')->insert($rows); + } + } + + /** + * 标记消息已读 + */ + public function markAsRead(array|string $msgIds, int $uid) + { + $query = Db::table('message_user_status')->where('uid', $uid); + if (is_array($msgIds)) { + $query->whereIn('msg_id', $msgIds); + } + return $query->update([ + 'status' => 1, + 'read_at' => time() + ]); + } + + /** + * 获取所有用户ID + */ + public function getAllUserIds(): array + { + return User::where('status', 1)->pluck('id')->toArray(); + } +} \ No newline at end of file diff --git a/app/common/service/MessageUserStatusService.php b/app/common/service/MessageUserStatusService.php new file mode 100644 index 0000000..f04e8b5 --- /dev/null +++ b/app/common/service/MessageUserStatusService.php @@ -0,0 +1,31 @@ +validator; + } +} \ No newline at end of file diff --git a/app/common/utils/WsEvents.php b/app/common/utils/WsEvents.php index 0916078..c5642c0 100644 --- a/app/common/utils/WsEvents.php +++ b/app/common/utils/WsEvents.php @@ -4,6 +4,7 @@ namespace app\common\utils; use GatewayWorker\Lib\Gateway; use app\common\utils\WsResponse; +use app\common\service\MessageNotificationsService; class WsEvents { @@ -72,17 +73,17 @@ class WsEvents return; } - // // 连接是否满足条件 - // if ($payload['server']['HTTP_ORIGIN'] != 'https://wstool.js.org') { - // flg('[x] [TOKEN认证] HTTP_ORIGIN不满足条件', false); - // Gateway::sendToClient($client_id, WsResponse::error( - // 'system.auth', - // 1001, - // '[x] [TOKEN认证] HTTP_ORIGIN不满足条件' - // )); - // Gateway::closeClient($client_id); - // return; - // } + // 连接是否满足条件 + if ($payload['server']['HTTP_ORIGIN'] != 'https://wstool.js.org') { + flg('[x] [TOKEN认证] HTTP_ORIGIN不满足条件', false); + Gateway::sendToClient($client_id, WsResponse::error( + 'system.auth', + 1001, + '[x] [TOKEN认证] HTTP_ORIGIN不满足条件' + )); + Gateway::closeClient($client_id); + return; + } // 签名检查 if (!isset($payload['get']['token'])) { @@ -136,11 +137,6 @@ class WsEvents // ================= 消息通知事件 ================= /** * 管理端主动推送通知 - * data = [ - * 'to_uids' => [uid1, uid2], // 可选,为空表示全员 - * 'title' => '标题', - * 'content' => '内容', - * ] */ public function notifyPush(?string $traceId) { @@ -158,7 +154,20 @@ class WsEvents return; } - $msgId = rand(1000, 9999); // 生成消息ID,可用数据库存储 + // 写消息 + $msgService = new MessageNotificationsService(); + $msgId = $msgService->createMessage([ + 'sender_id' => $this->eventData['sender_id'] ?? 0, + 'sender_type' => $this->eventData['sender_type'] ?? 'system', + 'title' => $title, + 'content' => $content, + 'scene' => $this->eventData['scene'] ?? 0, + 'recipient' => $this->eventData['to_uids'] ? implode(',', $this->eventData['to_uids']) : '', + ]); + + // 写用户状态 + $uids = !empty($toUids) ? $toUids : $msgService->getAllUserIds(); + $msgService->addUserStatus($msgId, $uids); $payload = WsResponse::success('notify.push', [ 'msg_id' => $msgId, @@ -181,18 +190,18 @@ class WsEvents Gateway::sendToClient($this->clientId, WsResponse::success( 'notify.push.result', ['msg_id' => $msgId], - $traceId + $traceId, + '消息发送成功' )); } /** * 客户端标记通知已读 - * data = ['msg_ids' => [1,2,3]] */ public function notifyRead(?string $traceId) { - $msgIds = $this->eventData['msg_ids'] ?? []; - if (empty($msgIds)) { + $msgIds = $this->eventData['msg_ids']; + if (!is_array($msgIds) && $msgIds !== 'all') { Gateway::sendToClient($this->clientId, WsResponse::error( 'notify.read', 400, @@ -201,6 +210,11 @@ class WsEvents )); return; } + + // 更新消息为已读 + $msgService = new MessageNotificationsService(); + $msgService->markAsRead($msgIds, Gateway::getUidByClientId($this->clientId)); + Gateway::sendToClient($this->clientId, WsResponse::success( 'notify.read.result', ['msg_ids' => $msgIds], diff --git a/app/common/validate/MessageNotificationsValidate.php b/app/common/validate/MessageNotificationsValidate.php new file mode 100644 index 0000000..c5dd6e1 --- /dev/null +++ b/app/common/validate/MessageNotificationsValidate.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/common/validate/MessageUserStatusValidate.php b/app/common/validate/MessageUserStatusValidate.php new file mode 100644 index 0000000..647cebd --- /dev/null +++ b/app/common/validate/MessageUserStatusValidate.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index e7ae182..3651926 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -77,25 +77,25 @@ class IndexController $options = [ 'controller' => [ 'module' => 'common', - 'name' => 'resources' + 'name' => 'message_notifications' ], 'model' => [ 'module' => 'common', - 'name' => 'resources' + 'name' => 'message_notifications' ], 'service' => [ 'module' => 'common', - 'name' => 'resources' + 'name' => 'message_notifications' ], 'logic' => [ 'module' => 'common', - 'name' => 'resources' + 'name' => 'message_notifications' ], 'validate' => [ 'module' => 'common', - 'name' => 'resources' + 'name' => 'message_notifications' ], - 'table' => 'resources' + 'table' => 'message_notifications' ]; $result = (new Generate($options))->doGenerate(); @@ -106,21 +106,11 @@ class IndexController public function test() { - // $data = [ - // 'code' => 'GD20250703-1DC80', - // 'category' => '账号类问题', - // 'content' => '这是一条测试工单内容。' - // ]; - // \app\common\utils\email\MailSend::send('mzl@hnxinyouhe.com', '【XYH】新的工单提醒', replaceTpl($data, 'work_order')); + $service = new \app\common\service\MessageNotificationsService(); + $udis = $service->getAllUserIds(); - // $options = [ - // 'corp_id' => 3, - // 'user_id' => 666 - // ]; - // \Webman\RedisQueue\Redis::send('perm-agt', $options); + var_dump($udis); - $res = Cache::delete("user_resources_74_fdesk"); - - return 22; + return json($udis); } } \ No newline at end of file diff --git a/app/functions.php b/app/functions.php index 299bad4..d9bb411 100644 --- a/app/functions.php +++ b/app/functions.php @@ -283,4 +283,28 @@ if (!function_exists('normalizeRichText')) { // 替换所有空白实体为普通空格 return preg_replace('/\s+/u', ' ', $result); } +} + + +/** + * 生成消息编号(唯一业务标识) + * 格式:年月日时分秒 + 微秒 + 随机数 + * 例如:20250820175530987654321 + * + * @return string + */ +if (!function_exists('generateMsgId')) { + function generateMsgId(): string + { + // 时间部分(精确到微秒) + $time = microtime(true); + $micro = sprintf("%06d", ($time - floor($time)) * 1e6); + $date = date('YmdHis', (int)$time); + + // 随机部分(3位) + $rand = mt_rand(100, 999); + + // 拼接:日期时间 + 微秒 + 随机数 + return $date . $micro . $rand; + } } \ No newline at end of file From 58dc6729d9b020ec06421f87790463f46cc44811 Mon Sep 17 00:00:00 2001 From: mzl Date: Wed, 20 Aug 2025 19:14:27 +0800 Subject: [PATCH 04/33] =?UTF-8?q?=E6=B6=88=E6=81=AF=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/model/MessageNotifications.php | 8 ++++ .../service/MessageNotificationsService.php | 37 +++++++++++++- app/common/utils/WsEvents.php | 23 +++++++++ app/controller/IndexController.php | 19 ++++++-- app/queue/redis/Notice.php | 48 +++++++++++++++++++ 5 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 app/queue/redis/Notice.php diff --git a/app/common/model/MessageNotifications.php b/app/common/model/MessageNotifications.php index e17e7c0..9ee9b77 100644 --- a/app/common/model/MessageNotifications.php +++ b/app/common/model/MessageNotifications.php @@ -46,4 +46,12 @@ class MessageNotifications extends Model * 不允许自动填充模型字段 */ protected $guarded = []; + + // 消息创建 + const SCENE = [ + self::SCENE0 => '系统消息', + self::SCENE1 => '系统通知' + ]; + const SCENE0 = 0; + const SCENE1 = 1; } \ No newline at end of file diff --git a/app/common/service/MessageNotificationsService.php b/app/common/service/MessageNotificationsService.php index 077dad5..720fec2 100644 --- a/app/common/service/MessageNotificationsService.php +++ b/app/common/service/MessageNotificationsService.php @@ -75,7 +75,7 @@ class MessageNotificationsService */ public function markAsRead(array|string $msgIds, int $uid) { - $query = Db::table('message_user_status')->where('uid', $uid); + $query = Db::table('message_user_status')->where('uid', $uid)->where('status', 0); if (is_array($msgIds)) { $query->whereIn('msg_id', $msgIds); } @@ -92,4 +92,39 @@ class MessageNotificationsService { return User::where('status', 1)->pluck('id')->toArray(); } + + /** + * 批量推送消息 + */ + public function pushNotice(array $data): int + { + // 数据结构: + // $data = [ + // 'sender_id' => 0, + // 'sender_type' => 'system', + // 'title' => '测试消息 - 标题', + // 'content' => '测试消息 - 内容', + // 'scene' => 1, + // 'recipient' => [], + // ]; + + // 写消息 + $msgId = $this->createMessage([ + 'sender_id' => $data['sender_id'], + 'sender_type' => $data['sender_type'], + 'title' => $data['title'], + 'content' => $data['content'], + 'scene' => $data['scene'], + 'recipient' => $data['recipient'] ? implode(',', $data['recipient']) : '', + ]); + + // 写用户状态 + $uids = count($data['recipient']) > 0 ? $data['recipient'] : $this->getAllUserIds(); + $this->addUserStatus($msgId, $uids); + + // 执行推送 + (new \app\common\utils\WsEvents())->pushNotice($msgId, $data['title'], $data['content'], $data['recipient']); + + return $msgId; + } } \ No newline at end of file diff --git a/app/common/utils/WsEvents.php b/app/common/utils/WsEvents.php index c5642c0..cded7a0 100644 --- a/app/common/utils/WsEvents.php +++ b/app/common/utils/WsEvents.php @@ -222,6 +222,29 @@ class WsEvents )); } + /** + * 推送消息通知 + */ + public function pushNotice(int $msgId, string $title, string $content, array $recipient) + { + $payload = WsResponse::success('notify.push', [ + 'msg_id' => $msgId, + 'title' => $title, + 'content' => $content, + 'created_at' => time() + ]); + + if (count($recipient) > 0) { + // 推送给指定用户 + foreach ($recipient as $uid) { + Gateway::sendToUid($uid, $payload); + } + } else { + // 推送给全体在线用户 + Gateway::sendToAll($payload); + } + } + // ================= 辅助方法 ================= protected function eventToMethod(string $event): string { diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 3651926..b12be98 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -6,6 +6,7 @@ use app\common\model\InquiryLog; use app\common\model\User; use app\common\model\UserRoles; use app\common\model\UserSystems; +use app\common\service\MessageNotificationsService; use app\common\traits\DataPermission; use app\common\utils\email\MailSend; use app\common\utils\scaffold\Generate; @@ -106,11 +107,21 @@ class IndexController public function test() { - $service = new \app\common\service\MessageNotificationsService(); - $udis = $service->getAllUserIds(); + // $data = [ + // 'sender_id' => 0, + // 'sender_type' => 'system', + // 'title' => '测试消息 - 标题', + // 'content' => '测试消息 - 内容', + // 'scene' => 1, + // 'recipient' => [1], + // ]; - var_dump($udis); + // // 同步推送 + // (new MessageNotificationsService())->pushNotice($data); - return json($udis); + // // 异步推送 + // \Webman\RedisQueue\Redis::send('notice', $data); + + return json($data); } } \ No newline at end of file diff --git a/app/queue/redis/Notice.php b/app/queue/redis/Notice.php new file mode 100644 index 0000000..3a310c8 --- /dev/null +++ b/app/queue/redis/Notice.php @@ -0,0 +1,48 @@ +pushNotice($data); + return true; + } catch (\Throwable $th) { + var_dump('4 :'.$th->getFile()."|".$th->getLine()."|".$th->getMessage()); + throw new BusinessException($th->getFile()."|".$th->getLine()."|".$th->getMessage()); + } + } + + // 消费失败回调 + /* + $package = [ + 'id' => 1357277951, // 消息ID + 'time' => 1709170510, // 消息时间 + 'delay' => 0, // 延迟时间 + 'attempts' => 2, // 消费次数 + 'queue' => 'send-mail', // 队列名 + 'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容 + 'max_attempts' => 5, // 最大重试次数 + 'error' => '错误信息' // 错误信息 + ] + */ + public function onConsumeFailure(\Throwable $e, $package) + { + echo "consume failure\n"; + echo $e->getMessage() . "\n"; + // 无需反序列化 + var_export($package); + } +} \ No newline at end of file From c1de258ce249596798445319956ba88cbddf7d56 Mon Sep 17 00:00:00 2001 From: mzl Date: Thu, 21 Aug 2025 15:47:05 +0800 Subject: [PATCH 05/33] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E9=80=9A=E7=9F=A5=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MessageNotificationsController.php | 35 +++++++-- .../logic/MessageNotificationsLogic.php | 12 +++ app/common/model/MessageNotifications.php | 42 +++++++++- app/common/model/MessageUserStatus.php | 17 +++++ .../service/MessageNotificationsService.php | 76 +++++++++++++++++++ app/common/utils/WsEvents.php | 13 ++-- app/controller/IndexController.php | 11 ++- config/route.php | 9 +++ 8 files changed, 197 insertions(+), 18 deletions(-) diff --git a/app/common/controller/MessageNotificationsController.php b/app/common/controller/MessageNotificationsController.php index 4e475eb..19341fc 100644 --- a/app/common/controller/MessageNotificationsController.php +++ b/app/common/controller/MessageNotificationsController.php @@ -4,6 +4,8 @@ namespace app\common\controller; use app\common\controller\BaseController; use app\common\traits\crud\CrudControllerTrait; use app\common\service\MessageNotificationsService; +use app\common\model\MessageNotifications; +use support\Request; class MessageNotificationsController extends BaseController { @@ -22,12 +24,29 @@ class MessageNotificationsController extends BaseController * @param array $data * @return array */ - // protected function indexFormat($data) - // { - // if (isset($data['created_at'])) - // $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); - // if (isset($data['updated_at'])) - // $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); - // return $data; - // } + protected function indexFormat($data) + { + if (isset($data['created_at'])) + $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + return $data; + } + + protected function showFormat($data) + { + if (isset($data['created_at'])) + $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + return $data; + } + + + public function push(Request $request) + { + try { + $data = $this->validate($this->service->getValidator(), $request->post(), 'store'); + $res = $this->service->servicePush($request, $data); + return $this->success($res, '推送成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } } \ No newline at end of file diff --git a/app/common/logic/MessageNotificationsLogic.php b/app/common/logic/MessageNotificationsLogic.php index b0a1429..594bb96 100644 --- a/app/common/logic/MessageNotificationsLogic.php +++ b/app/common/logic/MessageNotificationsLogic.php @@ -14,4 +14,16 @@ class MessageNotificationsLogic */ protected string $modelClass = MessageNotifications::class; + + public function logicDeleteBy($config) + { + $msg = $this->logicShow($config); + if ($msg) { + // 删除关联的用户状态记录 + $msg->userStatuses()->delete(); + // 删除消息本身 + $msg->delete(); + } + return true; + } } \ No newline at end of file diff --git a/app/common/model/MessageNotifications.php b/app/common/model/MessageNotifications.php index 9ee9b77..ef1b418 100644 --- a/app/common/model/MessageNotifications.php +++ b/app/common/model/MessageNotifications.php @@ -32,15 +32,16 @@ class MessageNotifications extends Model * * @var bool */ - public $timestamps = true; + public $timestamps = false; + // const UPDATED_AT = false; - protected $dateFormat = 'U'; + // protected $dateFormat = 'U'; /** * 允许自动填充模型字段 */ - protected $fillable = ['id', 'msg_no', 'sender_id', 'sender_type', 'recipient', 'title', 'content', 'scene', 'created_at']; + protected $fillable = ['id', 'msg_no', 'sender_id', 'sender_type', 'recipient', 'title', 'content', 'scene', 'push_method', 'created_at']; /** * 不允许自动填充模型字段 @@ -54,4 +55,39 @@ class MessageNotifications extends Model ]; const SCENE0 = 0; const SCENE1 = 1; + + // 推送方式 + const PUSH_METHOD = [ + self::PUSH_METHOD0 => '系统推送', + self::PUSH_METHOD1 => '手动推送' + ]; + const PUSH_METHOD0 = 0; + const PUSH_METHOD1 = 1; + + + /** + * 关联用户消息状态 (一对多) + */ + public function userStatuses() + { + return $this->hasMany(MessageUserStatus::class, 'msg_id', 'id'); + } + + /** + * 获取已读用户状态 + */ + public function readUsers() + { + return $this->hasMany(MessageUserStatus::class, 'msg_id', 'id') + ->where('status', 1); + } + + /** + * 获取未读用户状态 + */ + public function unreadUsers() + { + return $this->hasMany(MessageUserStatus::class, 'msg_id', 'id') + ->where('status', 0); + } } \ No newline at end of file diff --git a/app/common/model/MessageUserStatus.php b/app/common/model/MessageUserStatus.php index e1e1f74..70ff44e 100644 --- a/app/common/model/MessageUserStatus.php +++ b/app/common/model/MessageUserStatus.php @@ -46,4 +46,21 @@ class MessageUserStatus extends Model * 不允许自动填充模型字段 */ protected $guarded = []; + + + /** + * 关联消息 (多对一) + */ + public function message() + { + return $this->belongsTo(MessageNotifications::class, 'msg_id', 'id'); + } + + /** + * 关联用户 (假设你有 User 模型) + */ + public function user() + { + return $this->belongsTo(User::class, 'uid', 'id'); + } } \ No newline at end of file diff --git a/app/common/service/MessageNotificationsService.php b/app/common/service/MessageNotificationsService.php index 720fec2..4e8162c 100644 --- a/app/common/service/MessageNotificationsService.php +++ b/app/common/service/MessageNotificationsService.php @@ -2,11 +2,13 @@ namespace app\common\service; +use app\common\logic\MessageNotificationsLogic; use app\common\validate\MessageNotificationsValidate; use app\common\traits\crud\CrudServiceTrait; use app\common\model\MessageNotifications; use app\common\model\User; use support\Db; +use support\Request; class MessageNotificationsService { @@ -31,6 +33,78 @@ class MessageNotificationsService return new $this->validator; } + protected function buildQueryConfig(Request $request, string $scene = 'default'): array + { + $p = $request->all(); + + $config = []; + $config['model'] = $this->modelClass; + switch ($scene) { + case 'index': + $config['order']['id'] = 'desc'; + break; + case 'show': + $config['where']['id'] = $p['id']; + break; + case 'store': + break; + case 'update': + $config['where']['id'] = $p['id']; + break; + case 'destroy': + $config['where']['id'] = $p['id']; + break; + } + + return $config; + } + + + public function serviceDestroy(Request $request) + { + $config = $this->buildQueryConfig($request, 'destroy'); + $deleted = (new MessageNotificationsLogic())->logicDeleteBy($config); + return $this->postProcess('Destroy', $deleted); + } + + /** + * 添加前置处理 + */ + public function servicePush(Request $request, $data) + { + $pushData = [ + 'sender_id' => $request->user->uid, + 'sender_type' => 'admin', + 'title' => $data['title'], + 'content' => $data['content'], + 'scene' => 1, + 'recipient' => $data['recipient'], + 'push_method' => 1 + ]; + $msgId = (new MessageNotificationsService())->pushNotice($pushData); + return ['msg_id' => $msgId]; + } + + public function afterServiceList($data) + { + $data->each(function (&$item) { + $temp = explode(',', $item->recipient); + $item->recipient_list = User::whereIn('id', $temp)->pluck('name')->toArray(); + $item->scene_name = MessageNotifications::SCENE[$item->scene]; + $item->push_method_name = MessageNotifications::PUSH_METHOD[$item->push_method]; + }); + unset($item); + return $data; + } + + public function afterServiceShow($data) + { + $temp = explode(',', $data->recipient); + $data->recipient_list = User::whereIn('id', $temp)->pluck('name')->toArray(); + $data->scene_name = MessageNotifications::SCENE[$data->scene]; + $data->push_method_name = MessageNotifications::PUSH_METHOD[$data->push_method]; + return $data; + } /** * 创建一条消息 @@ -45,6 +119,7 @@ class MessageNotificationsService 'content' => $data['content'], 'scene' => $data['scene'], 'recipient' => $data['recipient'], + 'push_method' => $data['push_method'], 'created_at' => time(), ]); @@ -116,6 +191,7 @@ class MessageNotificationsService 'content' => $data['content'], 'scene' => $data['scene'], 'recipient' => $data['recipient'] ? implode(',', $data['recipient']) : '', + 'push_method' => $data['push_method'], ]); // 写用户状态 diff --git a/app/common/utils/WsEvents.php b/app/common/utils/WsEvents.php index cded7a0..50f82bb 100644 --- a/app/common/utils/WsEvents.php +++ b/app/common/utils/WsEvents.php @@ -146,9 +146,9 @@ class WsEvents if (!$title || !$content) { Gateway::sendToClient($this->clientId, WsResponse::error( - 'notify.push', + 'notify.push.result', 400, - 'title and content are required', + '标题和内容不能为空', $traceId )); return; @@ -163,13 +163,14 @@ class WsEvents 'content' => $content, 'scene' => $this->eventData['scene'] ?? 0, 'recipient' => $this->eventData['to_uids'] ? implode(',', $this->eventData['to_uids']) : '', + 'push_method' => isset($this->eventData['push_method']) ? $this->eventData['push_method'] : 0 ]); // 写用户状态 $uids = !empty($toUids) ? $toUids : $msgService->getAllUserIds(); $msgService->addUserStatus($msgId, $uids); - $payload = WsResponse::success('notify.push', [ + $payload = WsResponse::success('notify.receive', [ 'msg_id' => $msgId, 'title' => $title, 'content' => $content, @@ -203,9 +204,9 @@ class WsEvents $msgIds = $this->eventData['msg_ids']; if (!is_array($msgIds) && $msgIds !== 'all') { Gateway::sendToClient($this->clientId, WsResponse::error( - 'notify.read', + 'notify.read.result', 400, - 'msg_ids required', + '消息ID不能为空', $traceId )); return; @@ -227,7 +228,7 @@ class WsEvents */ public function pushNotice(int $msgId, string $title, string $content, array $recipient) { - $payload = WsResponse::success('notify.push', [ + $payload = WsResponse::success('notify.receive', [ 'msg_id' => $msgId, 'title' => $title, 'content' => $content, diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index b12be98..c8427cd 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -122,6 +122,15 @@ class IndexController // // 异步推送 // \Webman\RedisQueue\Redis::send('notice', $data); - return json($data); + // return json($data); + + $phone = ''; + $email = ''; + + $userByPhone = User::where('corp_id', 2)->where('phone', $phone)->where('phone', '!=', '')->first(); + $userByEmail = User::where('corp_id', 2)->where('email', $email)->where('email', '!=', '')->first(); + $user = $userByPhone ?: $userByEmail; + + return json($user); } } \ No newline at end of file diff --git a/config/route.php b/config/route.php index 9c33c32..99837b0 100644 --- a/config/route.php +++ b/config/route.php @@ -68,6 +68,15 @@ Route::group('/common', function () { // 后台管理 Route::group('/admin', function () { + // 消息通知 + Route::group('/notice', function () { + Route::any('/list', [app\common\controller\MessageNotificationsController::class, 'index']); + Route::any('/show', [app\common\controller\MessageNotificationsController::class, 'show']); + Route::any('/push', [app\common\controller\MessageNotificationsController::class, 'push']); + // Route::any('/update', [app\common\controller\MessageNotificationsController::class, 'update']); + Route::any('/destroy', [app\common\controller\MessageNotificationsController::class, 'destroy']); + }); + // 角色 Route::group('/role', function () { Route::any('/list', [app\admin\controller\RoleController::class, 'index']); From 61bf0fadf3ac1b56e4652e6f583d22f80b13ef70 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Thu, 21 Aug 2025 17:25:37 +0800 Subject: [PATCH 06/33] =?UTF-8?q?=E7=AE=A1=E7=90=86=E7=AB=AF=E5=9B=9E?= =?UTF-8?q?=E6=94=B6=E7=AB=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CustomerController.php | 43 ++++++++++++++----- app/crmAdmin/service/CustomerService.php | 16 +++++-- config/route.php | 1 + 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/app/crmAdmin/controller/CustomerController.php b/app/crmAdmin/controller/CustomerController.php index 900046d..535726f 100644 --- a/app/crmAdmin/controller/CustomerController.php +++ b/app/crmAdmin/controller/CustomerController.php @@ -4,6 +4,7 @@ namespace app\crmAdmin\controller; use app\common\model\Corp; use app\crm\model\Customer; +use app\crm\utils\Util; use app\crmAdmin\service\CustomerService; use support\Request; @@ -148,19 +149,18 @@ class CustomerController extends BaseController } $customer_id = explode(',', $param['customer_id']); - $customer = Customer::whereIn('id',$customer_id)->withTrashed()->first(); - if(empty($customer)){ + $query = Customer::whereIn('id',$customer_id)->withTrashed(); + if(!$query->exists()){ return $this->error( trans('fail')); } - if($param['scene'] == 'list'){ //管理端客户列表移除 - $customer->status = 4; - - }else{ //公海软删除 - $customer->status = 3; - + $scene = $param['scene'] ?? ''; + if ($scene === 'list') { //管理端客户列表移除 + $res = $query->update(['status' => 4, 'deleted_at' => time()]); + } elseif ($scene === 'force') { //强删除 + $res = $query->forceDelete(); + } else { //公海软删除 + $res = $query->update(['status' => 3, 'deleted_at' => time()]); } - $res = $customer->save(); - $customer->delete(); return $this->success($res); } catch (\Throwable $th) { @@ -168,6 +168,29 @@ class CustomerController extends BaseController } } + + /** + * 客户恢复 + * @param Request $request + * @return \support\Response + */ + public function restore(Request $request) + { + $id = $request->input('id', ''); + if (Util::isEmpty($id)) { + return $this->error(trans('id_empty')); + } + $id = explode(',', $id); + $query = Customer::whereIn('id', $id); + //$res = $query->restore(); + $res = $query->update(['status' => 1, 'user_id' => 0]); + if($res){ + return $this->success($res, trans('success')); + }else{ + return $this->error(trans('fail')); + } + } + /** * 客户详情 * @param Request $request diff --git a/app/crmAdmin/service/CustomerService.php b/app/crmAdmin/service/CustomerService.php index 038a5e0..546bc7c 100644 --- a/app/crmAdmin/service/CustomerService.php +++ b/app/crmAdmin/service/CustomerService.php @@ -51,12 +51,14 @@ class CustomerService $follow_last = $params['follow_last'] ?? ''; $tagId = $params['tag_id'] ?? []; $user_id = $params['user_id'] ?? ''; - $seas = $params['seas'] ?? ''; + $seas = $params['seas'] ?? ''; //recyclea:管理端回收站,audit:待审核列表,seas:公海 $scene = $params['scene'] ?? ''; $where = []; if ($scene == 'audit') { $where[] = ['status', '=', 2]; + } elseif ($scene == 'recyclea') { + $where[] = ['status', '>', 2]; } else { $where[] = ['status', '=', 1]; } @@ -121,8 +123,8 @@ class CustomerService if ($recycle == 'recycle' || $scene == 'recycle' || $scene == 'audit') { $query->onlyTrashed(); } - if ($seas == 'seas' || $scene == 'seas') { - $query->withTrashed(); + if ($seas == 'seas' || $scene == 'recyclea') { + $query = $query->withTrashed(); } if (!$export) { @@ -153,6 +155,14 @@ class CustomerService } $item['audit'] = $audit; } + if ($scene == 'recyclea' && $item['status'] == 3) { //回收站删除来源 + $item['del_source'] = '公海'; + }elseif ($scene == 'recyclea' && $item['status'] == 4) { + $item['del_source'] = '管理端'; + }elseif ($scene == 'recyclea' && $item['status'] == 5) { + $source = (!empty($item['user_id'])) ? (':' . User::where('id',$item['user_id'])->value('name')) : ''; + $item['del_source'] = '业务员' . $source; + } } } unset($item); diff --git a/config/route.php b/config/route.php index 2abd88a..ec46cc4 100644 --- a/config/route.php +++ b/config/route.php @@ -157,6 +157,7 @@ Route::group('/admin', function () { Route::any('/transfer', [app\crmAdmin\controller\CustomerController::class, 'transfer']); Route::any('/detail', [app\crmAdmin\controller\CustomerController::class, 'detail']); Route::any('/delete', [app\crmAdmin\controller\CustomerController::class, 'delete']); + Route::any('/restore', [app\crmAdmin\controller\CustomerController::class, 'restore']); //恢复客户 Route::any('/cede', [app\crm\controller\CustomerController::class, 'cede']); Route::any('/follow', [app\crmAdmin\controller\CustomerController::class, 'follow']); Route::any('/allocation', [app\crmAdmin\controller\CustomerController::class, 'allocation']); From b713ddb722157bcba7a8806e84aaed4a52c6e3a5 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Thu, 21 Aug 2025 17:50:42 +0800 Subject: [PATCH 07/33] =?UTF-8?q?=E5=9B=9E=E6=94=B6=E7=AB=99=E6=81=A2?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CustomerController.php | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/app/crmAdmin/controller/CustomerController.php b/app/crmAdmin/controller/CustomerController.php index 535726f..c68bd18 100644 --- a/app/crmAdmin/controller/CustomerController.php +++ b/app/crmAdmin/controller/CustomerController.php @@ -176,18 +176,23 @@ class CustomerController extends BaseController */ public function restore(Request $request) { - $id = $request->input('id', ''); - if (Util::isEmpty($id)) { - return $this->error(trans('id_empty')); - } - $id = explode(',', $id); - $query = Customer::whereIn('id', $id); - //$res = $query->restore(); - $res = $query->update(['status' => 1, 'user_id' => 0]); - if($res){ + try { + $id = $request->input('id', ''); + if (Util::isEmpty($id)) { + return $this->error(trans('id_empty')); + } + $ids = explode(',', $id); + $query = Customer::withTrashed()->whereIn('id', $ids); + if (!$query->exists()) { + return $this->error(trans('fail')); + } + // 恢复软删除(清空 deleted_at) + $query->restore(); + // 恢复为在库状态,放入公海 + $res = Customer::whereIn('id', $ids)->update(['status' => 1, 'user_id' => 0]); return $this->success($res, trans('success')); - }else{ - return $this->error(trans('fail')); + } catch (\Throwable $th) { + return $this->error($th->getMessage()); } } From 5dc515f712b9bc1ceedca3502e6cba1d34f733e1 Mon Sep 17 00:00:00 2001 From: mzl Date: Thu, 21 Aug 2025 19:26:28 +0800 Subject: [PATCH 08/33] =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MessageUserStatusController.php | 85 ++++++++++++++++-- app/common/model/MessageNotifications.php | 5 ++ app/common/model/MessageUserStatus.php | 4 +- .../service/MessageUserStatusService.php | 89 +++++++++++++++++++ config/route.php | 12 ++- 5 files changed, 184 insertions(+), 11 deletions(-) diff --git a/app/common/controller/MessageUserStatusController.php b/app/common/controller/MessageUserStatusController.php index 302aec4..a9ccb11 100644 --- a/app/common/controller/MessageUserStatusController.php +++ b/app/common/controller/MessageUserStatusController.php @@ -4,6 +4,7 @@ namespace app\common\controller; use app\common\controller\BaseController; use app\common\traits\crud\CrudControllerTrait; use app\common\service\MessageUserStatusService; +use support\Request; class MessageUserStatusController extends BaseController { @@ -22,12 +23,80 @@ class MessageUserStatusController extends BaseController * @param array $data * @return array */ - // protected function indexFormat($data) - // { - // if (isset($data['created_at'])) - // $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); - // if (isset($data['updated_at'])) - // $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); - // return $data; - // } + protected function indexFormat($data) + { + if (isset($data['read_at']) && $data['read_at'] !== null) { + $data['read_at'] = date('Y-m-d H:i:s', $data['read_at']); + } else { + $data['read_at'] = ' - '; + } + + if (isset($data['message'])) + $data['message']['created_at'] = date('Y-m-d H:i:s', $data['message']['created_at']); + return $data; + } + + protected function showFormat($data) + { + if (isset($data['read_at']) && $data['read_at'] !== null) { + $data['read_at'] = date('Y-m-d H:i:s', $data['read_at']); + } else { + $data['read_at'] = ' - '; + } + + if (isset($data['message'])) + $data['message']['created_at'] = date('Y-m-d H:i:s', $data['message']['created_at']); + return $data; + } + + protected function unReadFormat($data) + { + // if (isset($data['read_at']) && $data['read_at'] !== null) { + // $data['read_at'] = date('Y-m-d H:i:s', $data['read_at']); + // } else { + // $data['read_at'] = ' - '; + // } + + // if (isset($data['message'])) { + // $data['message']['created_at'] = date('Y-m-d H:i:s', $data['message']['created_at']); + // } + + foreach ($data as &$item) { + if (isset($item['read_at']) && $item['read_at'] !== null) { + $item['read_at'] = date('Y-m-d H:i:s', $item['read_at']); + } else { + $item['read_at'] = ' - '; + } + + if (isset($item['message'])) { + $item['message']['created_at'] = date('Y-m-d H:i:s', $item['message']['created_at']); + } + } + var_dump($data); + return $data; + } + + public function read(Request $request) + { + try { + $data = $this->validate($this->service->getValidator(), $request->post(), 'read'); + $this->service->serviceRead($request, $data); + return $this->success([], '操作成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + public function listUnRead(Request $request) + { + try { + $raw = $this->service->serviceListUnRead($request); + $data = $this->transformData($raw, method_exists($this, 'unReadFormat') + ? [$this, 'unReadFormat'] + : null); + return $this->success($data); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } } \ No newline at end of file diff --git a/app/common/model/MessageNotifications.php b/app/common/model/MessageNotifications.php index ef1b418..196a329 100644 --- a/app/common/model/MessageNotifications.php +++ b/app/common/model/MessageNotifications.php @@ -90,4 +90,9 @@ class MessageNotifications extends Model return $this->hasMany(MessageUserStatus::class, 'msg_id', 'id') ->where('status', 0); } + + public function sender() + { + return $this->belongsTo(User::class, 'sender_id', 'id'); + } } \ No newline at end of file diff --git a/app/common/model/MessageUserStatus.php b/app/common/model/MessageUserStatus.php index 70ff44e..0e73ef5 100644 --- a/app/common/model/MessageUserStatus.php +++ b/app/common/model/MessageUserStatus.php @@ -32,10 +32,10 @@ class MessageUserStatus extends Model * * @var bool */ - public $timestamps = true; + public $timestamps = false; - protected $dateFormat = 'U'; + // protected $dateFormat = 'U'; /** * 允许自动填充模型字段 diff --git a/app/common/service/MessageUserStatusService.php b/app/common/service/MessageUserStatusService.php index f04e8b5..27ba060 100644 --- a/app/common/service/MessageUserStatusService.php +++ b/app/common/service/MessageUserStatusService.php @@ -5,6 +5,8 @@ namespace app\common\service; use app\common\validate\MessageUserStatusValidate; use app\common\traits\crud\CrudServiceTrait; use app\common\model\MessageUserStatus; +use support\Request; +use app\common\model\User; class MessageUserStatusService { @@ -28,4 +30,91 @@ class MessageUserStatusService { return new $this->validator; } + + + protected function buildQueryConfig(Request $request, string $scene = 'default'): array + { + $p = $request->all(); + + $config = []; + $config['model'] = $this->modelClass; + switch ($scene) { + case 'unread': + $config['order']['id'] = 'desc'; + // $config['with']['user'] = ['sn', 'name']; + $config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'created_at']; + $config['where']['uid'] = $request->user->uid; + $config['where']['status'] = 0; + break; + case 'index': + $config['order']['id'] = 'desc'; + // $config['with']['user'] = ['sn', 'name']; + $config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'created_at']; + $config['where']['uid'] = $request->user->uid; + break; + case 'show': + $config['where']['id'] = $p['id']; + // $config['with']['user'] = ['sn', 'name']; + $config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'created_at']; + $config['where']['uid'] = $request->user->uid; + break; + case 'store': + break; + case 'update': + case 'read': + $config['where']['id'] = $p['id']; + $config['where']['msg_id'] = $p['msg_id']; + $config['where']['uid'] = $request->user->uid; + break; + case 'destroy': + $config['where']['id'] = $p['id']; + $config['where']['uid'] = $request->user->uid; + break; + } + + return $config; + } + + public function serviceListUnRead(Request $request) + { + $config = $this->buildQueryConfig($request, 'unread'); + $result = $this->logicListAll($config); + return $this->postProcess('ListUnRead', $result); + } + + public function serviceRead(Request $request, array $data = []) + { + return $this->noticeRead($request->post('msg_ids'), $request->user->uid); + } + + public function noticeRead(array|string $msgIds, int $uid): bool + { + $model = MessageUserStatus::where('uid', $uid)->where('status', 0); + if (is_array($msgIds)) { + $model->whereIn('msg_id', $msgIds); + } + $udata['read_at'] = time(); + $udata['status'] = 1; + return $model->update($udata); + } + + + protected function afterServiceList($res) + { + $res->each(function (&$item) { + if (isset($item->message)) { + $item->message->sender_name = User::where('id', $item->message->sender_id)->value('name') ?: '系统'; + } + }); + unset($item); + return $res; + } + + protected function afterServiceShow($res) + { + if (isset($res->message)) { + $res->message->sender_name = User::where('id', $res->message->sender_id)->value('name') ?: '系统'; + } + return $res; + } } \ No newline at end of file diff --git a/config/route.php b/config/route.php index 99837b0..b578322 100644 --- a/config/route.php +++ b/config/route.php @@ -64,6 +64,17 @@ Route::group('/common', function () { Route::any('/cache/clear', [app\common\controller\PresetController::class, 'cacheClear']); Route::any('/menus', [app\common\controller\PresetController::class, 'menus']); }); + + + // 消息通知 + Route::group('/notice', function () { + Route::any('/list', [app\common\controller\MessageUserStatusController::class, 'index']); + Route::any('/show', [app\common\controller\MessageUserStatusController::class, 'show']); + Route::any('/read', [app\common\controller\MessageUserStatusController::class, 'read']); + Route::any('/destroy', [app\common\controller\MessageUserStatusController::class, 'destroy']); + + Route::any('/unread', [app\common\controller\MessageUserStatusController::class, 'listUnRead']); + }); }); // 后台管理 @@ -73,7 +84,6 @@ Route::group('/admin', function () { Route::any('/list', [app\common\controller\MessageNotificationsController::class, 'index']); Route::any('/show', [app\common\controller\MessageNotificationsController::class, 'show']); Route::any('/push', [app\common\controller\MessageNotificationsController::class, 'push']); - // Route::any('/update', [app\common\controller\MessageNotificationsController::class, 'update']); Route::any('/destroy', [app\common\controller\MessageNotificationsController::class, 'destroy']); }); From c6908e57ccf2b561195b8e48a36d7cd6e1f775eb Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Fri, 22 Aug 2025 13:59:39 +0800 Subject: [PATCH 09/33] =?UTF-8?q?=E8=B7=9F=E8=BF=9B=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/model/Follow.php | 10 +++------- app/crm/service/FollowService.php | 2 ++ app/crmAdmin/service/CustomerService.php | 1 + 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/crm/model/Follow.php b/app/crm/model/Follow.php index 778b669..bc284c6 100644 --- a/app/crm/model/Follow.php +++ b/app/crm/model/Follow.php @@ -38,13 +38,9 @@ class Follow extends Model */ public $timestamps = true; - protected $dates = ['created_at', 'updated_at', 'follow_time']; - protected $casts = [ - 'updated_at' => 'datetime:Y-m-d H:i:s', // 指定序列化格式 - 'created_at' => 'datetime:Y-m-d H:i:s', // 指定序列化格式 - 'follow_time' => 'datetime:Y-m-d H:i:s', // 指定序列化格式 -// 'updated_at' => 'timestamp', // 指定序列化格式 -// 'created_at' => 'timestamp', // 指定序列化格式 + 'follow_time' => 'timestamp', // 指定序列化格式 + 'updated_at' => 'timestamp', // 指定序列化格式 + 'created_at' => 'timestamp', // 指定序列化格式 ]; } \ No newline at end of file diff --git a/app/crm/service/FollowService.php b/app/crm/service/FollowService.php index e8ce0f7..e300f11 100644 --- a/app/crm/service/FollowService.php +++ b/app/crm/service/FollowService.php @@ -59,6 +59,8 @@ class FollowService $oss_picture = $ossservice->generatePrivateUrl($follow['picture'], 86400); } $follow['picture_full'] = $oss_picture; + + $follow = Util::formatDate($follow,['created_at','updated_at','follow_time']); // $follow = Util::formatDate($follow); return $follow; } diff --git a/app/crmAdmin/service/CustomerService.php b/app/crmAdmin/service/CustomerService.php index 038a5e0..f8589de 100644 --- a/app/crmAdmin/service/CustomerService.php +++ b/app/crmAdmin/service/CustomerService.php @@ -469,6 +469,7 @@ class CustomerService $ossservice = new OssService(); foreach ($follow as $item) { + $item = Util::formatDate($item,['created_at','updated_at','follow_time']); $item['picture_full'] = empty($item->picture)?'':$ossservice->generatePrivateUrl($item->picture); } From cb7d5078fe7c7d8c001ca991ef84e2e10266eb36 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Fri, 22 Aug 2025 14:31:33 +0800 Subject: [PATCH 10/33] =?UTF-8?q?=E8=B7=9F=E8=BF=9B=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/model/Follow.php | 23 +++++++++++++++++------ app/crm/service/FollowService.php | 5 ++++- app/crmAdmin/service/CustomerService.php | 3 +++ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/app/crm/model/Follow.php b/app/crm/model/Follow.php index bc284c6..f57ef18 100644 --- a/app/crm/model/Follow.php +++ b/app/crm/model/Follow.php @@ -36,11 +36,22 @@ class Follow extends Model * * @var bool */ - public $timestamps = true; + public $timestamps = false; - protected $casts = [ - 'follow_time' => 'timestamp', // 指定序列化格式 - 'updated_at' => 'timestamp', // 指定序列化格式 - 'created_at' => 'timestamp', // 指定序列化格式 - ]; + /** + * 在保存时手动设置时间戳 + */ + protected static function boot() + { + parent::boot(); + + static::creating(function ($model) { + $model->created_at = time(); + $model->updated_at = time(); + }); + + static::updating(function ($model) { + $model->updated_at = time(); + }); + } } \ No newline at end of file diff --git a/app/crm/service/FollowService.php b/app/crm/service/FollowService.php index e300f11..e8ec322 100644 --- a/app/crm/service/FollowService.php +++ b/app/crm/service/FollowService.php @@ -60,7 +60,10 @@ class FollowService } $follow['picture_full'] = $oss_picture; - $follow = Util::formatDate($follow,['created_at','updated_at','follow_time']); +// $follow = Util::formatDate($follow,['created_at','updated_at','follow_time']); + $follow['follow_time'] = empty($follow['follow_time'])?'':date('Y-m-d H:i:s', $follow['follow_time']); + $follow['created_at'] = empty($follow['created_at'])?'':date('Y-m-d H:i:s', $follow['created_at']); + $follow['updated_at'] = empty($follow['updated_at'])?'':date('Y-m-d H:i:s', $follow['updated_at']);; // $follow = Util::formatDate($follow); return $follow; } diff --git a/app/crmAdmin/service/CustomerService.php b/app/crmAdmin/service/CustomerService.php index f8589de..20f4137 100644 --- a/app/crmAdmin/service/CustomerService.php +++ b/app/crmAdmin/service/CustomerService.php @@ -470,6 +470,9 @@ class CustomerService $ossservice = new OssService(); foreach ($follow as $item) { $item = Util::formatDate($item,['created_at','updated_at','follow_time']); + $item['follow_time'] = empty($item['follow_time'])?'':date('Y-m-d H:i:s', $item['follow_time']); + $item['created_at'] = empty($item['created_at'])?'':date('Y-m-d H:i:s', $item['created_at']); + $item['updated_at'] = empty($item['updated_at'])?'':date('Y-m-d H:i:s', $item['updated_at']);; $item['picture_full'] = empty($item->picture)?'':$ossservice->generatePrivateUrl($item->picture); } From 96475892e9dd469603fc2ad4c79add18b8c2c5ff Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Fri, 22 Aug 2025 14:49:10 +0800 Subject: [PATCH 11/33] =?UTF-8?q?=E8=B7=9F=E8=BF=9B=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/service/FollowService.php | 6 ++---- app/crmAdmin/service/CustomerService.php | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/crm/service/FollowService.php b/app/crm/service/FollowService.php index e8ec322..a433692 100644 --- a/app/crm/service/FollowService.php +++ b/app/crm/service/FollowService.php @@ -60,10 +60,8 @@ class FollowService } $follow['picture_full'] = $oss_picture; -// $follow = Util::formatDate($follow,['created_at','updated_at','follow_time']); - $follow['follow_time'] = empty($follow['follow_time'])?'':date('Y-m-d H:i:s', $follow['follow_time']); - $follow['created_at'] = empty($follow['created_at'])?'':date('Y-m-d H:i:s', $follow['created_at']); - $follow['updated_at'] = empty($follow['updated_at'])?'':date('Y-m-d H:i:s', $follow['updated_at']);; + $follow = Util::formatDate($follow,['created_at','updated_at','follow_time']); + $follow['follow_time'] = empty($follow['follow_time'])?'':$follow['follow_time']; // $follow = Util::formatDate($follow); return $follow; } diff --git a/app/crmAdmin/service/CustomerService.php b/app/crmAdmin/service/CustomerService.php index 20f4137..ad51d80 100644 --- a/app/crmAdmin/service/CustomerService.php +++ b/app/crmAdmin/service/CustomerService.php @@ -470,9 +470,7 @@ class CustomerService $ossservice = new OssService(); foreach ($follow as $item) { $item = Util::formatDate($item,['created_at','updated_at','follow_time']); - $item['follow_time'] = empty($item['follow_time'])?'':date('Y-m-d H:i:s', $item['follow_time']); - $item['created_at'] = empty($item['created_at'])?'':date('Y-m-d H:i:s', $item['created_at']); - $item['updated_at'] = empty($item['updated_at'])?'':date('Y-m-d H:i:s', $item['updated_at']);; + $item['follow_time'] = empty($item['follow_time'])?'':$item['follow_time']; $item['picture_full'] = empty($item->picture)?'':$ossservice->generatePrivateUrl($item->picture); } From 8b91924d1a5bc7d8f4b8b7d202eb5e914b996aac Mon Sep 17 00:00:00 2001 From: mzl Date: Fri, 22 Aug 2025 16:27:50 +0800 Subject: [PATCH 12/33] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MessageUserStatusController.php | 15 +- .../service/MessageNotificationsService.php | 142 +++++++++++++----- .../service/MessageUserStatusService.php | 28 +++- app/common/utils/WsEvents.php | 39 ++--- app/controller/IndexController.php | 22 ++- 5 files changed, 158 insertions(+), 88 deletions(-) diff --git a/app/common/controller/MessageUserStatusController.php b/app/common/controller/MessageUserStatusController.php index a9ccb11..9a896a1 100644 --- a/app/common/controller/MessageUserStatusController.php +++ b/app/common/controller/MessageUserStatusController.php @@ -51,16 +51,6 @@ class MessageUserStatusController extends BaseController protected function unReadFormat($data) { - // if (isset($data['read_at']) && $data['read_at'] !== null) { - // $data['read_at'] = date('Y-m-d H:i:s', $data['read_at']); - // } else { - // $data['read_at'] = ' - '; - // } - - // if (isset($data['message'])) { - // $data['message']['created_at'] = date('Y-m-d H:i:s', $data['message']['created_at']); - // } - foreach ($data as &$item) { if (isset($item['read_at']) && $item['read_at'] !== null) { $item['read_at'] = date('Y-m-d H:i:s', $item['read_at']); @@ -72,7 +62,6 @@ class MessageUserStatusController extends BaseController $item['message']['created_at'] = date('Y-m-d H:i:s', $item['message']['created_at']); } } - var_dump($data); return $data; } @@ -94,6 +83,10 @@ class MessageUserStatusController extends BaseController $data = $this->transformData($raw, method_exists($this, 'unReadFormat') ? [$this, 'unReadFormat'] : null); + $data = [ + 'list' => $data, + 'counts' => count($data) + ]; return $this->success($data); } catch (\Throwable $e) { return $this->error($e->getMessage()); diff --git a/app/common/service/MessageNotificationsService.php b/app/common/service/MessageNotificationsService.php index 4e8162c..806bc1d 100644 --- a/app/common/service/MessageNotificationsService.php +++ b/app/common/service/MessageNotificationsService.php @@ -6,6 +6,7 @@ use app\common\logic\MessageNotificationsLogic; use app\common\validate\MessageNotificationsValidate; use app\common\traits\crud\CrudServiceTrait; use app\common\model\MessageNotifications; +use app\common\model\MessageUserStatus; use app\common\model\User; use support\Db; use support\Request; @@ -106,6 +107,111 @@ class MessageNotificationsService return $data; } + + + // ====== ====== ====== 通知操作的处理 ====== ====== ====== + /** + * 推送消息 + */ + public function pushNotice(array $data = [], string $scene = "Default"): int + { + // 拼装数据(可能需要处理 消息标题、内容等等数据,可自行处理) + $pushData = $this->assemblyProcess($scene, $data); + // 写入消息 + $msgId = $this->insertMessage($pushData); + // 推送 + $this->pushWs($msgId, $pushData); + return $msgId; + } + + /** + * 执行推送 + */ + public function pushWs(int $msgId, array $pushData) + { + foreach ($pushData['recipient'] as $userId) { + // 查询消息数据 + $config['model'] = MessageUserStatus::class; + $config['order']['id'] = 'desc'; + $config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'scene', 'created_at']; + $config['where']['uid'] = $userId; + $config['where']['msg_id'] = $msgId; + $config['where']['status'] = 0; + $wsData = (new MessageUserStatusService())->logicShow($config)->toArray(); + + // 组装数据 + if (isset($wsData['message']) && $wsData['message']) { + $wsData['message']['sender_name'] = User::where('id', $wsData['message']['sender_id'])->value('name') ?: '系统'; + $wsData['message']['scene_name'] = MessageNotifications::SCENE[$wsData['message']['scene']]; + $wsData['message']['created_at'] = date('Y-m-d H:i:s', $wsData['message']['created_at']); + } + + // 获取未读消息数 + $counts = MessageUserStatus::where('uid', $userId)->where('status', 0)->count('id'); + $data = [ + 'notice_data' => $wsData, + 'unread_counts' => $counts + ]; + + // 执行推送 + (new \app\common\utils\WsEvents())->pushWsNotice($data, $userId); + } + return true; + } + + /** + * 插入消息 + * @param array $data + * @return int + */ + public function insertMessage(array $data): int + { + // 写消息 + $msgId = $this->createMessage([ + 'sender_id' => $data['sender_id'], + 'sender_type' => $data['sender_type'], + 'title' => $data['title'], + 'content' => $data['content'], + 'scene' => $data['scene'], + 'recipient' => implode(',', $data['recipient']), + 'push_method' => $data['push_method'], + ]); + // 写用户状态 + $this->addUserStatus($msgId, $data['recipient']); + return $msgId; + } + + /** + * 数据拼装方法调用 + */ + protected function assemblyProcess(string $op, mixed $data): mixed + { + $hook = "assembly{$op}"; + return method_exists($this, $hook) + ? $this->{$hook}($data) + : $data; + } + + /** + * 默认数据拼装 + */ + public function assemblyDefault(array $data) + { + return [ + 'sender_id' => isset($data['sender_id']) && $data['sender_id'] ? $data['sender_id'] : 0, + 'sender_type' => isset($data['sender_type']) && $data['sender_type'] ? $data['sender_type'] : 'system', + 'title' => isset($data['title']) && $data['title'] ? $data['title'] : '系统通知', + 'content' => isset($data['content']) && $data['content'] ? $data['content'] : '系统通知-默认', + 'scene' => isset($data['scene']) && $data['scene'] ? $data['scene'] : 1, + 'recipient' => isset($data['recipient']) && count($data['recipient']) > 0 ? $data['recipient'] : $this->getAllUserIds(), + 'push_method' => isset($data['push_method']) && $data['push_method'] ? $data['push_method'] : 0, + ]; + } + + // *** *** 可以扩展数据组拼装方法 *** *** + + // ====== ====== ====== 通知操作的基础方法 ====== ====== ====== + /** * 创建一条消息 */ @@ -167,40 +273,4 @@ class MessageNotificationsService { return User::where('status', 1)->pluck('id')->toArray(); } - - /** - * 批量推送消息 - */ - public function pushNotice(array $data): int - { - // 数据结构: - // $data = [ - // 'sender_id' => 0, - // 'sender_type' => 'system', - // 'title' => '测试消息 - 标题', - // 'content' => '测试消息 - 内容', - // 'scene' => 1, - // 'recipient' => [], - // ]; - - // 写消息 - $msgId = $this->createMessage([ - 'sender_id' => $data['sender_id'], - 'sender_type' => $data['sender_type'], - 'title' => $data['title'], - 'content' => $data['content'], - 'scene' => $data['scene'], - 'recipient' => $data['recipient'] ? implode(',', $data['recipient']) : '', - 'push_method' => $data['push_method'], - ]); - - // 写用户状态 - $uids = count($data['recipient']) > 0 ? $data['recipient'] : $this->getAllUserIds(); - $this->addUserStatus($msgId, $uids); - - // 执行推送 - (new \app\common\utils\WsEvents())->pushNotice($msgId, $data['title'], $data['content'], $data['recipient']); - - return $msgId; - } } \ No newline at end of file diff --git a/app/common/service/MessageUserStatusService.php b/app/common/service/MessageUserStatusService.php index 27ba060..2162f86 100644 --- a/app/common/service/MessageUserStatusService.php +++ b/app/common/service/MessageUserStatusService.php @@ -2,6 +2,7 @@ namespace app\common\service; +use app\common\model\MessageNotifications; use app\common\validate\MessageUserStatusValidate; use app\common\traits\crud\CrudServiceTrait; use app\common\model\MessageUserStatus; @@ -41,21 +42,18 @@ class MessageUserStatusService switch ($scene) { case 'unread': $config['order']['id'] = 'desc'; - // $config['with']['user'] = ['sn', 'name']; - $config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'created_at']; + $config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'scene', 'created_at']; $config['where']['uid'] = $request->user->uid; $config['where']['status'] = 0; break; case 'index': $config['order']['id'] = 'desc'; - // $config['with']['user'] = ['sn', 'name']; - $config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'created_at']; + $config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'scene', 'created_at']; $config['where']['uid'] = $request->user->uid; break; case 'show': $config['where']['id'] = $p['id']; - // $config['with']['user'] = ['sn', 'name']; - $config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'created_at']; + $config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'scene', 'created_at']; $config['where']['uid'] = $request->user->uid; break; case 'store': @@ -104,6 +102,7 @@ class MessageUserStatusService $res->each(function (&$item) { if (isset($item->message)) { $item->message->sender_name = User::where('id', $item->message->sender_id)->value('name') ?: '系统'; + $item->message->scene_name = MessageNotifications::SCENE[$item->message->scene]; } }); unset($item); @@ -114,7 +113,24 @@ class MessageUserStatusService { if (isset($res->message)) { $res->message->sender_name = User::where('id', $res->message->sender_id)->value('name') ?: '系统'; + $res->message->scene_name = MessageNotifications::SCENE[$res->message->scene]; + } + // 标记已读 + if ($res->status == 0) { + $this->noticeRead([$res->msg_id], $res->uid); } return $res; } + + protected function afterServiceListUnRead($res) + { + $res->each(function (&$item) { + if (isset($item->message)) { + $item->message->sender_name = User::where('id', $item->message->sender_id)->value('name') ?: '系统'; + $item->message->scene_name = MessageNotifications::SCENE[$item->message->scene]; + } + }); + unset($item); + return $res; + } } \ No newline at end of file diff --git a/app/common/utils/WsEvents.php b/app/common/utils/WsEvents.php index 50f82bb..2a65f3e 100644 --- a/app/common/utils/WsEvents.php +++ b/app/common/utils/WsEvents.php @@ -74,16 +74,16 @@ class WsEvents } // 连接是否满足条件 - if ($payload['server']['HTTP_ORIGIN'] != 'https://wstool.js.org') { - flg('[x] [TOKEN认证] HTTP_ORIGIN不满足条件', false); - Gateway::sendToClient($client_id, WsResponse::error( - 'system.auth', - 1001, - '[x] [TOKEN认证] HTTP_ORIGIN不满足条件' - )); - Gateway::closeClient($client_id); - return; - } + // if ($payload['server']['HTTP_ORIGIN'] != 'https://wstool.js.org') { + // flg('[x] [TOKEN认证] HTTP_ORIGIN不满足条件', false); + // Gateway::sendToClient($client_id, WsResponse::error( + // 'system.auth', + // 1001, + // '[x] [TOKEN认证] HTTP_ORIGIN不满足条件' + // )); + // Gateway::closeClient($client_id); + // return; + // } // 签名检查 if (!isset($payload['get']['token'])) { @@ -226,24 +226,9 @@ class WsEvents /** * 推送消息通知 */ - public function pushNotice(int $msgId, string $title, string $content, array $recipient) + public function pushWsNotice(array $wsData, int $userId) { - $payload = WsResponse::success('notify.receive', [ - 'msg_id' => $msgId, - 'title' => $title, - 'content' => $content, - 'created_at' => time() - ]); - - if (count($recipient) > 0) { - // 推送给指定用户 - foreach ($recipient as $uid) { - Gateway::sendToUid($uid, $payload); - } - } else { - // 推送给全体在线用户 - Gateway::sendToAll($payload); - } + Gateway::sendToUid($userId, WsResponse::success('notify.receive', $wsData)); } // ================= 辅助方法 ================= diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index c8427cd..8e15d2a 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -116,21 +116,27 @@ class IndexController // 'recipient' => [1], // ]; + $data['recipient'] = [1, 74]; // // 同步推送 // (new MessageNotificationsService())->pushNotice($data); - // // 异步推送 - // \Webman\RedisQueue\Redis::send('notice', $data); + + + // 异步推送 + \Webman\RedisQueue\Redis::send('notice', $data); + + + return 'success'; // return json($data); - $phone = ''; - $email = ''; + // $phone = ''; + // $email = ''; - $userByPhone = User::where('corp_id', 2)->where('phone', $phone)->where('phone', '!=', '')->first(); - $userByEmail = User::where('corp_id', 2)->where('email', $email)->where('email', '!=', '')->first(); - $user = $userByPhone ?: $userByEmail; + // $userByPhone = User::where('corp_id', 2)->where('phone', $phone)->where('phone', '!=', '')->first(); + // $userByEmail = User::where('corp_id', 2)->where('email', $email)->where('email', '!=', '')->first(); + // $user = $userByPhone ?: $userByEmail; - return json($user); + // return json($user); } } \ No newline at end of file From d7157087286c3dc04f657e3dd912006c8903e39c Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Fri, 22 Aug 2025 18:00:21 +0800 Subject: [PATCH 13/33] =?UTF-8?q?=E5=88=86=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crmAdmin/service/CustomerService.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/crmAdmin/service/CustomerService.php b/app/crmAdmin/service/CustomerService.php index ad51d80..c38162b 100644 --- a/app/crmAdmin/service/CustomerService.php +++ b/app/crmAdmin/service/CustomerService.php @@ -519,14 +519,16 @@ class CustomerService $uidRotator = new \app\common\utils\RedisUidRotator('allocation_customer_user'); $uidRotator->setUids($userId, false); - Customer::whereIn('user_id',$resignUerIds)->chunk(500, function ($customers) use ($uidRotator){ - foreach ($customers as $customer) { - // 获取下一个分配的UID - $newUserId = $uidRotator->next(); - $customer->user_id = $newUserId; - $customer->save(); - } - }); + Customer::whereIn('user_id', $resignUerIds) + ->orderBy('id', 'asc') + ->chunkById(500, function ($customers) use ($uidRotator) { + foreach ($customers as $customer) { + // 获取下一个分配的UID + $newUserId = $uidRotator->next(); + $customer->user_id = $newUserId; + $customer->save(); + } + }, 'id'); return [ From 11165fcfb9cac799d2bc0c5c7f82bf5c1f0b6f8b Mon Sep 17 00:00:00 2001 From: mzl Date: Fri, 22 Aug 2025 18:16:13 +0800 Subject: [PATCH 14/33] Merge commit 'ed96e0f8589042fb8e9d6cfee713c5150612bb34' --- .../MessageNotificationsController.php | 52 ++++ .../MessageUserStatusController.php | 95 ++++++ .../logic/MessageNotificationsLogic.php | 29 ++ app/common/logic/MessageUserStatusLogic.php | 17 ++ app/common/model/MessageNotifications.php | 98 +++++++ app/common/model/MessageUserStatus.php | 66 +++++ .../service/MessageNotificationsService.php | 276 ++++++++++++++++++ .../service/MessageUserStatusService.php | 136 +++++++++ app/common/utils/WsEvents.php | 244 ++++++++++++++++ app/common/utils/WsResponse.php | 47 +++ .../validate/MessageNotificationsValidate.php | 29 ++ .../validate/MessageUserStatusValidate.php | 29 ++ app/controller/IndexController.php | 50 ++-- .../controller/CustomerController.php | 48 ++- app/crmAdmin/service/CustomerService.php | 16 +- app/functions.php | 24 ++ app/queue/redis/Notice.php | 48 +++ composer.json | 3 +- composer.lock | 89 +++++- config/plugin/webman/gateway-worker/app.php | 4 + .../plugin/webman/gateway-worker/process.php | 39 +++ config/route.php | 20 ++ plugin/webman/gateway/Events.php | 38 +++ 23 files changed, 1465 insertions(+), 32 deletions(-) create mode 100644 app/common/controller/MessageNotificationsController.php create mode 100644 app/common/controller/MessageUserStatusController.php create mode 100644 app/common/logic/MessageNotificationsLogic.php create mode 100644 app/common/logic/MessageUserStatusLogic.php create mode 100644 app/common/model/MessageNotifications.php create mode 100644 app/common/model/MessageUserStatus.php create mode 100644 app/common/service/MessageNotificationsService.php create mode 100644 app/common/service/MessageUserStatusService.php create mode 100644 app/common/utils/WsEvents.php create mode 100644 app/common/utils/WsResponse.php create mode 100644 app/common/validate/MessageNotificationsValidate.php create mode 100644 app/common/validate/MessageUserStatusValidate.php create mode 100644 app/queue/redis/Notice.php create mode 100644 config/plugin/webman/gateway-worker/app.php create mode 100644 config/plugin/webman/gateway-worker/process.php create mode 100644 plugin/webman/gateway/Events.php diff --git a/app/common/controller/MessageNotificationsController.php b/app/common/controller/MessageNotificationsController.php new file mode 100644 index 0000000..19341fc --- /dev/null +++ b/app/common/controller/MessageNotificationsController.php @@ -0,0 +1,52 @@ +service = new MessageNotificationsService(); + } + + /** + * 列表页数据格式化 + * + * @param array $data + * @return array + */ + protected function indexFormat($data) + { + if (isset($data['created_at'])) + $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + return $data; + } + + protected function showFormat($data) + { + if (isset($data['created_at'])) + $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + return $data; + } + + + public function push(Request $request) + { + try { + $data = $this->validate($this->service->getValidator(), $request->post(), 'store'); + $res = $this->service->servicePush($request, $data); + return $this->success($res, '推送成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/common/controller/MessageUserStatusController.php b/app/common/controller/MessageUserStatusController.php new file mode 100644 index 0000000..9a896a1 --- /dev/null +++ b/app/common/controller/MessageUserStatusController.php @@ -0,0 +1,95 @@ +service = new MessageUserStatusService(); + } + + /** + * 列表页数据格式化 + * + * @param array $data + * @return array + */ + protected function indexFormat($data) + { + if (isset($data['read_at']) && $data['read_at'] !== null) { + $data['read_at'] = date('Y-m-d H:i:s', $data['read_at']); + } else { + $data['read_at'] = ' - '; + } + + if (isset($data['message'])) + $data['message']['created_at'] = date('Y-m-d H:i:s', $data['message']['created_at']); + return $data; + } + + protected function showFormat($data) + { + if (isset($data['read_at']) && $data['read_at'] !== null) { + $data['read_at'] = date('Y-m-d H:i:s', $data['read_at']); + } else { + $data['read_at'] = ' - '; + } + + if (isset($data['message'])) + $data['message']['created_at'] = date('Y-m-d H:i:s', $data['message']['created_at']); + return $data; + } + + protected function unReadFormat($data) + { + foreach ($data as &$item) { + if (isset($item['read_at']) && $item['read_at'] !== null) { + $item['read_at'] = date('Y-m-d H:i:s', $item['read_at']); + } else { + $item['read_at'] = ' - '; + } + + if (isset($item['message'])) { + $item['message']['created_at'] = date('Y-m-d H:i:s', $item['message']['created_at']); + } + } + return $data; + } + + public function read(Request $request) + { + try { + $data = $this->validate($this->service->getValidator(), $request->post(), 'read'); + $this->service->serviceRead($request, $data); + return $this->success([], '操作成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + public function listUnRead(Request $request) + { + try { + $raw = $this->service->serviceListUnRead($request); + $data = $this->transformData($raw, method_exists($this, 'unReadFormat') + ? [$this, 'unReadFormat'] + : null); + $data = [ + 'list' => $data, + 'counts' => count($data) + ]; + return $this->success($data); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/common/logic/MessageNotificationsLogic.php b/app/common/logic/MessageNotificationsLogic.php new file mode 100644 index 0000000..594bb96 --- /dev/null +++ b/app/common/logic/MessageNotificationsLogic.php @@ -0,0 +1,29 @@ +logicShow($config); + if ($msg) { + // 删除关联的用户状态记录 + $msg->userStatuses()->delete(); + // 删除消息本身 + $msg->delete(); + } + return true; + } +} \ No newline at end of file diff --git a/app/common/logic/MessageUserStatusLogic.php b/app/common/logic/MessageUserStatusLogic.php new file mode 100644 index 0000000..ea4b63e --- /dev/null +++ b/app/common/logic/MessageUserStatusLogic.php @@ -0,0 +1,17 @@ + '系统消息', + self::SCENE1 => '系统通知' + ]; + const SCENE0 = 0; + const SCENE1 = 1; + + // 推送方式 + const PUSH_METHOD = [ + self::PUSH_METHOD0 => '系统推送', + self::PUSH_METHOD1 => '手动推送' + ]; + const PUSH_METHOD0 = 0; + const PUSH_METHOD1 = 1; + + + /** + * 关联用户消息状态 (一对多) + */ + public function userStatuses() + { + return $this->hasMany(MessageUserStatus::class, 'msg_id', 'id'); + } + + /** + * 获取已读用户状态 + */ + public function readUsers() + { + return $this->hasMany(MessageUserStatus::class, 'msg_id', 'id') + ->where('status', 1); + } + + /** + * 获取未读用户状态 + */ + public function unreadUsers() + { + return $this->hasMany(MessageUserStatus::class, 'msg_id', 'id') + ->where('status', 0); + } + + public function sender() + { + return $this->belongsTo(User::class, 'sender_id', 'id'); + } +} \ No newline at end of file diff --git a/app/common/model/MessageUserStatus.php b/app/common/model/MessageUserStatus.php new file mode 100644 index 0000000..0e73ef5 --- /dev/null +++ b/app/common/model/MessageUserStatus.php @@ -0,0 +1,66 @@ +belongsTo(MessageNotifications::class, 'msg_id', 'id'); + } + + /** + * 关联用户 (假设你有 User 模型) + */ + public function user() + { + return $this->belongsTo(User::class, 'uid', 'id'); + } +} \ No newline at end of file diff --git a/app/common/service/MessageNotificationsService.php b/app/common/service/MessageNotificationsService.php new file mode 100644 index 0000000..806bc1d --- /dev/null +++ b/app/common/service/MessageNotificationsService.php @@ -0,0 +1,276 @@ +validator; + } + + protected function buildQueryConfig(Request $request, string $scene = 'default'): array + { + $p = $request->all(); + + $config = []; + $config['model'] = $this->modelClass; + switch ($scene) { + case 'index': + $config['order']['id'] = 'desc'; + break; + case 'show': + $config['where']['id'] = $p['id']; + break; + case 'store': + break; + case 'update': + $config['where']['id'] = $p['id']; + break; + case 'destroy': + $config['where']['id'] = $p['id']; + break; + } + + return $config; + } + + + public function serviceDestroy(Request $request) + { + $config = $this->buildQueryConfig($request, 'destroy'); + $deleted = (new MessageNotificationsLogic())->logicDeleteBy($config); + return $this->postProcess('Destroy', $deleted); + } + + /** + * 添加前置处理 + */ + public function servicePush(Request $request, $data) + { + $pushData = [ + 'sender_id' => $request->user->uid, + 'sender_type' => 'admin', + 'title' => $data['title'], + 'content' => $data['content'], + 'scene' => 1, + 'recipient' => $data['recipient'], + 'push_method' => 1 + ]; + $msgId = (new MessageNotificationsService())->pushNotice($pushData); + return ['msg_id' => $msgId]; + } + + public function afterServiceList($data) + { + $data->each(function (&$item) { + $temp = explode(',', $item->recipient); + $item->recipient_list = User::whereIn('id', $temp)->pluck('name')->toArray(); + $item->scene_name = MessageNotifications::SCENE[$item->scene]; + $item->push_method_name = MessageNotifications::PUSH_METHOD[$item->push_method]; + }); + unset($item); + return $data; + } + + public function afterServiceShow($data) + { + $temp = explode(',', $data->recipient); + $data->recipient_list = User::whereIn('id', $temp)->pluck('name')->toArray(); + $data->scene_name = MessageNotifications::SCENE[$data->scene]; + $data->push_method_name = MessageNotifications::PUSH_METHOD[$data->push_method]; + return $data; + } + + + + // ====== ====== ====== 通知操作的处理 ====== ====== ====== + /** + * 推送消息 + */ + public function pushNotice(array $data = [], string $scene = "Default"): int + { + // 拼装数据(可能需要处理 消息标题、内容等等数据,可自行处理) + $pushData = $this->assemblyProcess($scene, $data); + // 写入消息 + $msgId = $this->insertMessage($pushData); + // 推送 + $this->pushWs($msgId, $pushData); + return $msgId; + } + + /** + * 执行推送 + */ + public function pushWs(int $msgId, array $pushData) + { + foreach ($pushData['recipient'] as $userId) { + // 查询消息数据 + $config['model'] = MessageUserStatus::class; + $config['order']['id'] = 'desc'; + $config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'scene', 'created_at']; + $config['where']['uid'] = $userId; + $config['where']['msg_id'] = $msgId; + $config['where']['status'] = 0; + $wsData = (new MessageUserStatusService())->logicShow($config)->toArray(); + + // 组装数据 + if (isset($wsData['message']) && $wsData['message']) { + $wsData['message']['sender_name'] = User::where('id', $wsData['message']['sender_id'])->value('name') ?: '系统'; + $wsData['message']['scene_name'] = MessageNotifications::SCENE[$wsData['message']['scene']]; + $wsData['message']['created_at'] = date('Y-m-d H:i:s', $wsData['message']['created_at']); + } + + // 获取未读消息数 + $counts = MessageUserStatus::where('uid', $userId)->where('status', 0)->count('id'); + $data = [ + 'notice_data' => $wsData, + 'unread_counts' => $counts + ]; + + // 执行推送 + (new \app\common\utils\WsEvents())->pushWsNotice($data, $userId); + } + return true; + } + + /** + * 插入消息 + * @param array $data + * @return int + */ + public function insertMessage(array $data): int + { + // 写消息 + $msgId = $this->createMessage([ + 'sender_id' => $data['sender_id'], + 'sender_type' => $data['sender_type'], + 'title' => $data['title'], + 'content' => $data['content'], + 'scene' => $data['scene'], + 'recipient' => implode(',', $data['recipient']), + 'push_method' => $data['push_method'], + ]); + // 写用户状态 + $this->addUserStatus($msgId, $data['recipient']); + return $msgId; + } + + /** + * 数据拼装方法调用 + */ + protected function assemblyProcess(string $op, mixed $data): mixed + { + $hook = "assembly{$op}"; + return method_exists($this, $hook) + ? $this->{$hook}($data) + : $data; + } + + /** + * 默认数据拼装 + */ + public function assemblyDefault(array $data) + { + return [ + 'sender_id' => isset($data['sender_id']) && $data['sender_id'] ? $data['sender_id'] : 0, + 'sender_type' => isset($data['sender_type']) && $data['sender_type'] ? $data['sender_type'] : 'system', + 'title' => isset($data['title']) && $data['title'] ? $data['title'] : '系统通知', + 'content' => isset($data['content']) && $data['content'] ? $data['content'] : '系统通知-默认', + 'scene' => isset($data['scene']) && $data['scene'] ? $data['scene'] : 1, + 'recipient' => isset($data['recipient']) && count($data['recipient']) > 0 ? $data['recipient'] : $this->getAllUserIds(), + 'push_method' => isset($data['push_method']) && $data['push_method'] ? $data['push_method'] : 0, + ]; + } + + // *** *** 可以扩展数据组拼装方法 *** *** + + // ====== ====== ====== 通知操作的基础方法 ====== ====== ====== + + /** + * 创建一条消息 + */ + public function createMessage(array $data): int + { + $msgId = Db::table('message_notifications')->insertGetId([ + 'msg_no' => generateMsgId(), + 'sender_id' => $data['sender_id'], + 'sender_type' => $data['sender_type'], + 'title' => $data['title'], + 'content' => $data['content'], + 'scene' => $data['scene'], + 'recipient' => $data['recipient'], + 'push_method' => $data['push_method'], + 'created_at' => time(), + ]); + + return $msgId; + } + + /** + * 批量插入用户消息状态 + */ + public function addUserStatus(int $msgId, array $uids) + { + $rows = []; + foreach ($uids as $uid) { + $rows[] = [ + 'msg_id' => $msgId, + 'uid' => $uid, + 'status' => 0, + 'read_at' => null + ]; + } + if ($rows) { + Db::table('message_user_status')->insert($rows); + } + } + + /** + * 标记消息已读 + */ + public function markAsRead(array|string $msgIds, int $uid) + { + $query = Db::table('message_user_status')->where('uid', $uid)->where('status', 0); + if (is_array($msgIds)) { + $query->whereIn('msg_id', $msgIds); + } + return $query->update([ + 'status' => 1, + 'read_at' => time() + ]); + } + + /** + * 获取所有用户ID + */ + public function getAllUserIds(): array + { + return User::where('status', 1)->pluck('id')->toArray(); + } +} \ No newline at end of file diff --git a/app/common/service/MessageUserStatusService.php b/app/common/service/MessageUserStatusService.php new file mode 100644 index 0000000..2162f86 --- /dev/null +++ b/app/common/service/MessageUserStatusService.php @@ -0,0 +1,136 @@ +validator; + } + + + protected function buildQueryConfig(Request $request, string $scene = 'default'): array + { + $p = $request->all(); + + $config = []; + $config['model'] = $this->modelClass; + switch ($scene) { + case 'unread': + $config['order']['id'] = 'desc'; + $config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'scene', 'created_at']; + $config['where']['uid'] = $request->user->uid; + $config['where']['status'] = 0; + break; + case 'index': + $config['order']['id'] = 'desc'; + $config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'scene', 'created_at']; + $config['where']['uid'] = $request->user->uid; + break; + case 'show': + $config['where']['id'] = $p['id']; + $config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'scene', 'created_at']; + $config['where']['uid'] = $request->user->uid; + break; + case 'store': + break; + case 'update': + case 'read': + $config['where']['id'] = $p['id']; + $config['where']['msg_id'] = $p['msg_id']; + $config['where']['uid'] = $request->user->uid; + break; + case 'destroy': + $config['where']['id'] = $p['id']; + $config['where']['uid'] = $request->user->uid; + break; + } + + return $config; + } + + public function serviceListUnRead(Request $request) + { + $config = $this->buildQueryConfig($request, 'unread'); + $result = $this->logicListAll($config); + return $this->postProcess('ListUnRead', $result); + } + + public function serviceRead(Request $request, array $data = []) + { + return $this->noticeRead($request->post('msg_ids'), $request->user->uid); + } + + public function noticeRead(array|string $msgIds, int $uid): bool + { + $model = MessageUserStatus::where('uid', $uid)->where('status', 0); + if (is_array($msgIds)) { + $model->whereIn('msg_id', $msgIds); + } + $udata['read_at'] = time(); + $udata['status'] = 1; + return $model->update($udata); + } + + + protected function afterServiceList($res) + { + $res->each(function (&$item) { + if (isset($item->message)) { + $item->message->sender_name = User::where('id', $item->message->sender_id)->value('name') ?: '系统'; + $item->message->scene_name = MessageNotifications::SCENE[$item->message->scene]; + } + }); + unset($item); + return $res; + } + + protected function afterServiceShow($res) + { + if (isset($res->message)) { + $res->message->sender_name = User::where('id', $res->message->sender_id)->value('name') ?: '系统'; + $res->message->scene_name = MessageNotifications::SCENE[$res->message->scene]; + } + // 标记已读 + if ($res->status == 0) { + $this->noticeRead([$res->msg_id], $res->uid); + } + return $res; + } + + protected function afterServiceListUnRead($res) + { + $res->each(function (&$item) { + if (isset($item->message)) { + $item->message->sender_name = User::where('id', $item->message->sender_id)->value('name') ?: '系统'; + $item->message->scene_name = MessageNotifications::SCENE[$item->message->scene]; + } + }); + unset($item); + return $res; + } +} \ No newline at end of file diff --git a/app/common/utils/WsEvents.php b/app/common/utils/WsEvents.php new file mode 100644 index 0000000..2a65f3e --- /dev/null +++ b/app/common/utils/WsEvents.php @@ -0,0 +1,244 @@ +registerAddress; + } + + /** + * 监听消息接收 + */ + public function message($client_id, $message) + { + $this->clientId = $client_id; + + $payload = json_decode($message, true); + $event = $payload['event']; + $this->eventData = $payload['data'] ?? []; + $traceId = $payload['trace_id'] ?? null; + + $method = $this->eventToMethod($event); + + if (method_exists($this, $method)) { + try { + $this->$method($traceId); + } catch (\Throwable $th) { + Gateway::sendToClient($client_id, WsResponse::error( + $event, + 500, + $th->getMessage(), + $traceId + )); + } + } else { + Gateway::sendToClient($client_id, WsResponse::error( + $event, + 404, + "Event handler not found: $method", + $traceId + )); + } + } + + + // ================= 系统事件 ================= + /** + * 认证 + */ + public function auth($client_id, $payload) + { + try { + // 检查来源 + if (!isset($payload['server']['HTTP_ORIGIN'])) { + flg('[x] [TOKEN认证] 未定义HTTP_ORIGIN', false); + Gateway::sendToClient($client_id, WsResponse::error( + 'system.auth', + 1001, + '[x] [TOKEN认证] 未定义HTTP_ORIGIN' + )); + Gateway::closeClient($client_id); + return; + } + + // 连接是否满足条件 + // if ($payload['server']['HTTP_ORIGIN'] != 'https://wstool.js.org') { + // flg('[x] [TOKEN认证] HTTP_ORIGIN不满足条件', false); + // Gateway::sendToClient($client_id, WsResponse::error( + // 'system.auth', + // 1001, + // '[x] [TOKEN认证] HTTP_ORIGIN不满足条件' + // )); + // Gateway::closeClient($client_id); + // return; + // } + + // 签名检查 + if (!isset($payload['get']['token'])) { + flg('[x] [TOKEN认证] 未携带TOKEN参数', false); + Gateway::sendToClient($client_id, WsResponse::error( + 'system.auth', + 1001, + '[x] [TOKEN认证] 未携带TOKEN参数' + )); + Gateway::closeClient($client_id); + return; + } + + // 解析TOKEN + $user = (new Jwt())->verifyToken($payload['get']['token']); + // 绑定用户ID + Gateway::bindUid($client_id, $user->uid); + + // 响应 + Gateway::sendToClient($client_id, WsResponse::success( + 'system.auth', + ['uid' => $user->uid] + )); + } catch (\Throwable $th) { + flg('[x] [TOKEN认证] TOKEN认证失败:'.$th->getMessage(), false); + Gateway::sendToClient($client_id, WsResponse::error( + 'system.auth', + 1001, + '[x] [TOKEN认证] TOKEN认证失败:'.$th->getMessage() + )); + Gateway::closeClient($client_id); + return; + } + } + + /** + * 系统心跳 + * + * @param string|null $traceId + */ + public function systemPing(?string $traceId) + { + Gateway::sendToClient($this->clientId, WsResponse::success( + 'system.pong', + ['time' => time()], + $traceId + )); + } + + + // ================= 消息通知事件 ================= + /** + * 管理端主动推送通知 + */ + public function notifyPush(?string $traceId) + { + $title = $this->eventData['title'] ?? null; + $content = $this->eventData['content'] ?? null; + $toUids = $this->eventData['to_uids'] ?? []; + + if (!$title || !$content) { + Gateway::sendToClient($this->clientId, WsResponse::error( + 'notify.push.result', + 400, + '标题和内容不能为空', + $traceId + )); + return; + } + + // 写消息 + $msgService = new MessageNotificationsService(); + $msgId = $msgService->createMessage([ + 'sender_id' => $this->eventData['sender_id'] ?? 0, + 'sender_type' => $this->eventData['sender_type'] ?? 'system', + 'title' => $title, + 'content' => $content, + 'scene' => $this->eventData['scene'] ?? 0, + 'recipient' => $this->eventData['to_uids'] ? implode(',', $this->eventData['to_uids']) : '', + 'push_method' => isset($this->eventData['push_method']) ? $this->eventData['push_method'] : 0 + ]); + + // 写用户状态 + $uids = !empty($toUids) ? $toUids : $msgService->getAllUserIds(); + $msgService->addUserStatus($msgId, $uids); + + $payload = WsResponse::success('notify.receive', [ + 'msg_id' => $msgId, + 'title' => $title, + 'content' => $content, + 'created_at' => time() + ], $traceId); + + if (!empty($toUids)) { + // 推送给指定用户 + foreach ($toUids as $uid) { + Gateway::sendToUid($uid, $payload); + } + } else { + // 推送给全体在线用户 + Gateway::sendToAll($payload); + } + + // 回复发送者确认 + Gateway::sendToClient($this->clientId, WsResponse::success( + 'notify.push.result', + ['msg_id' => $msgId], + $traceId, + '消息发送成功' + )); + } + + /** + * 客户端标记通知已读 + */ + public function notifyRead(?string $traceId) + { + $msgIds = $this->eventData['msg_ids']; + if (!is_array($msgIds) && $msgIds !== 'all') { + Gateway::sendToClient($this->clientId, WsResponse::error( + 'notify.read.result', + 400, + '消息ID不能为空', + $traceId + )); + return; + } + + // 更新消息为已读 + $msgService = new MessageNotificationsService(); + $msgService->markAsRead($msgIds, Gateway::getUidByClientId($this->clientId)); + + Gateway::sendToClient($this->clientId, WsResponse::success( + 'notify.read.result', + ['msg_ids' => $msgIds], + $traceId + )); + } + + /** + * 推送消息通知 + */ + public function pushWsNotice(array $wsData, int $userId) + { + Gateway::sendToUid($userId, WsResponse::success('notify.receive', $wsData)); + } + + // ================= 辅助方法 ================= + protected function eventToMethod(string $event): string + { + $parts = explode('.', $event); + $method = array_shift($parts); + foreach ($parts as $part) { + $method .= ucfirst($part); + } + return $method; + } +} \ No newline at end of file diff --git a/app/common/utils/WsResponse.php b/app/common/utils/WsResponse.php new file mode 100644 index 0000000..6cf0b28 --- /dev/null +++ b/app/common/utils/WsResponse.php @@ -0,0 +1,47 @@ + $event, + 'code' => $code, + 'message' => $message, + 'data' => $data, + 'trace_id' => $traceId, // 如果没有传就是 null + ], JSON_UNESCAPED_UNICODE); + } + + /** + * 错误返回 + * + * @param string $event 事件名称 + * @param int $code 错误码 + * @param string $message 错误信息 + * @param string|null $traceId 请求追踪ID,可选,默认 null + * @param array $data 附加数据(可选) + * @return string JSON + */ + public static function error(string $event, int $code, string $message, ?string $traceId = null, array $data = []): string + { + return json_encode([ + 'event' => $event, + 'code' => $code, + 'message' => $message, + 'data' => $data, + 'trace_id' => $traceId, // 如果没有传就是 null + ], JSON_UNESCAPED_UNICODE); + } +} \ No newline at end of file diff --git a/app/common/validate/MessageNotificationsValidate.php b/app/common/validate/MessageNotificationsValidate.php new file mode 100644 index 0000000..c5dd6e1 --- /dev/null +++ b/app/common/validate/MessageNotificationsValidate.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/common/validate/MessageUserStatusValidate.php b/app/common/validate/MessageUserStatusValidate.php new file mode 100644 index 0000000..647cebd --- /dev/null +++ b/app/common/validate/MessageUserStatusValidate.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index e7ae182..8e15d2a 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -6,6 +6,7 @@ use app\common\model\InquiryLog; use app\common\model\User; use app\common\model\UserRoles; use app\common\model\UserSystems; +use app\common\service\MessageNotificationsService; use app\common\traits\DataPermission; use app\common\utils\email\MailSend; use app\common\utils\scaffold\Generate; @@ -77,25 +78,25 @@ class IndexController $options = [ 'controller' => [ 'module' => 'common', - 'name' => 'resources' + 'name' => 'message_notifications' ], 'model' => [ 'module' => 'common', - 'name' => 'resources' + 'name' => 'message_notifications' ], 'service' => [ 'module' => 'common', - 'name' => 'resources' + 'name' => 'message_notifications' ], 'logic' => [ 'module' => 'common', - 'name' => 'resources' + 'name' => 'message_notifications' ], 'validate' => [ 'module' => 'common', - 'name' => 'resources' + 'name' => 'message_notifications' ], - 'table' => 'resources' + 'table' => 'message_notifications' ]; $result = (new Generate($options))->doGenerate(); @@ -107,20 +108,35 @@ class IndexController { // $data = [ - // 'code' => 'GD20250703-1DC80', - // 'category' => '账号类问题', - // 'content' => '这是一条测试工单内容。' + // 'sender_id' => 0, + // 'sender_type' => 'system', + // 'title' => '测试消息 - 标题', + // 'content' => '测试消息 - 内容', + // 'scene' => 1, + // 'recipient' => [1], // ]; - // \app\common\utils\email\MailSend::send('mzl@hnxinyouhe.com', '【XYH】新的工单提醒', replaceTpl($data, 'work_order')); - // $options = [ - // 'corp_id' => 3, - // 'user_id' => 666 - // ]; - // \Webman\RedisQueue\Redis::send('perm-agt', $options); + $data['recipient'] = [1, 74]; + // // 同步推送 + // (new MessageNotificationsService())->pushNotice($data); - $res = Cache::delete("user_resources_74_fdesk"); - return 22; + + // 异步推送 + \Webman\RedisQueue\Redis::send('notice', $data); + + + return 'success'; + + // return json($data); + + // $phone = ''; + // $email = ''; + + // $userByPhone = User::where('corp_id', 2)->where('phone', $phone)->where('phone', '!=', '')->first(); + // $userByEmail = User::where('corp_id', 2)->where('email', $email)->where('email', '!=', '')->first(); + // $user = $userByPhone ?: $userByEmail; + + // return json($user); } } \ No newline at end of file diff --git a/app/crmAdmin/controller/CustomerController.php b/app/crmAdmin/controller/CustomerController.php index 900046d..c68bd18 100644 --- a/app/crmAdmin/controller/CustomerController.php +++ b/app/crmAdmin/controller/CustomerController.php @@ -4,6 +4,7 @@ namespace app\crmAdmin\controller; use app\common\model\Corp; use app\crm\model\Customer; +use app\crm\utils\Util; use app\crmAdmin\service\CustomerService; use support\Request; @@ -148,19 +149,18 @@ class CustomerController extends BaseController } $customer_id = explode(',', $param['customer_id']); - $customer = Customer::whereIn('id',$customer_id)->withTrashed()->first(); - if(empty($customer)){ + $query = Customer::whereIn('id',$customer_id)->withTrashed(); + if(!$query->exists()){ return $this->error( trans('fail')); } - if($param['scene'] == 'list'){ //管理端客户列表移除 - $customer->status = 4; - - }else{ //公海软删除 - $customer->status = 3; - + $scene = $param['scene'] ?? ''; + if ($scene === 'list') { //管理端客户列表移除 + $res = $query->update(['status' => 4, 'deleted_at' => time()]); + } elseif ($scene === 'force') { //强删除 + $res = $query->forceDelete(); + } else { //公海软删除 + $res = $query->update(['status' => 3, 'deleted_at' => time()]); } - $res = $customer->save(); - $customer->delete(); return $this->success($res); } catch (\Throwable $th) { @@ -168,6 +168,34 @@ class CustomerController extends BaseController } } + + /** + * 客户恢复 + * @param Request $request + * @return \support\Response + */ + public function restore(Request $request) + { + try { + $id = $request->input('id', ''); + if (Util::isEmpty($id)) { + return $this->error(trans('id_empty')); + } + $ids = explode(',', $id); + $query = Customer::withTrashed()->whereIn('id', $ids); + if (!$query->exists()) { + return $this->error(trans('fail')); + } + // 恢复软删除(清空 deleted_at) + $query->restore(); + // 恢复为在库状态,放入公海 + $res = Customer::whereIn('id', $ids)->update(['status' => 1, 'user_id' => 0]); + return $this->success($res, trans('success')); + } catch (\Throwable $th) { + return $this->error($th->getMessage()); + } + } + /** * 客户详情 * @param Request $request diff --git a/app/crmAdmin/service/CustomerService.php b/app/crmAdmin/service/CustomerService.php index c38162b..635b6cf 100644 --- a/app/crmAdmin/service/CustomerService.php +++ b/app/crmAdmin/service/CustomerService.php @@ -51,12 +51,14 @@ class CustomerService $follow_last = $params['follow_last'] ?? ''; $tagId = $params['tag_id'] ?? []; $user_id = $params['user_id'] ?? ''; - $seas = $params['seas'] ?? ''; + $seas = $params['seas'] ?? ''; //recyclea:管理端回收站,audit:待审核列表,seas:公海 $scene = $params['scene'] ?? ''; $where = []; if ($scene == 'audit') { $where[] = ['status', '=', 2]; + } elseif ($scene == 'recyclea') { + $where[] = ['status', '>', 2]; } else { $where[] = ['status', '=', 1]; } @@ -121,8 +123,8 @@ class CustomerService if ($recycle == 'recycle' || $scene == 'recycle' || $scene == 'audit') { $query->onlyTrashed(); } - if ($seas == 'seas' || $scene == 'seas') { - $query->withTrashed(); + if ($seas == 'seas' || $scene == 'recyclea') { + $query = $query->withTrashed(); } if (!$export) { @@ -153,6 +155,14 @@ class CustomerService } $item['audit'] = $audit; } + if ($scene == 'recyclea' && $item['status'] == 3) { //回收站删除来源 + $item['del_source'] = '公海'; + }elseif ($scene == 'recyclea' && $item['status'] == 4) { + $item['del_source'] = '管理端'; + }elseif ($scene == 'recyclea' && $item['status'] == 5) { + $source = (!empty($item['user_id'])) ? (':' . User::where('id',$item['user_id'])->value('name')) : ''; + $item['del_source'] = '业务员' . $source; + } } } unset($item); diff --git a/app/functions.php b/app/functions.php index 299bad4..d9bb411 100644 --- a/app/functions.php +++ b/app/functions.php @@ -283,4 +283,28 @@ if (!function_exists('normalizeRichText')) { // 替换所有空白实体为普通空格 return preg_replace('/\s+/u', ' ', $result); } +} + + +/** + * 生成消息编号(唯一业务标识) + * 格式:年月日时分秒 + 微秒 + 随机数 + * 例如:20250820175530987654321 + * + * @return string + */ +if (!function_exists('generateMsgId')) { + function generateMsgId(): string + { + // 时间部分(精确到微秒) + $time = microtime(true); + $micro = sprintf("%06d", ($time - floor($time)) * 1e6); + $date = date('YmdHis', (int)$time); + + // 随机部分(3位) + $rand = mt_rand(100, 999); + + // 拼接:日期时间 + 微秒 + 随机数 + return $date . $micro . $rand; + } } \ No newline at end of file diff --git a/app/queue/redis/Notice.php b/app/queue/redis/Notice.php new file mode 100644 index 0000000..3a310c8 --- /dev/null +++ b/app/queue/redis/Notice.php @@ -0,0 +1,48 @@ +pushNotice($data); + return true; + } catch (\Throwable $th) { + var_dump('4 :'.$th->getFile()."|".$th->getLine()."|".$th->getMessage()); + throw new BusinessException($th->getFile()."|".$th->getLine()."|".$th->getMessage()); + } + } + + // 消费失败回调 + /* + $package = [ + 'id' => 1357277951, // 消息ID + 'time' => 1709170510, // 消息时间 + 'delay' => 0, // 延迟时间 + 'attempts' => 2, // 消费次数 + 'queue' => 'send-mail', // 队列名 + 'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容 + 'max_attempts' => 5, // 最大重试次数 + 'error' => '错误信息' // 错误信息 + ] + */ + public function onConsumeFailure(\Throwable $e, $package) + { + echo "consume failure\n"; + echo $e->getMessage() . "\n"; + // 无需反序列化 + var_export($package); + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index 98e3e54..c474010 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,8 @@ "symfony/filesystem": "^7.3", "workerman/crontab": "^1.0", "phpoffice/phpspreadsheet": "^4.5", - "phpmailer/phpmailer": "^6.10" + "phpmailer/phpmailer": "^6.10", + "webman/gateway-worker": "^1.0" }, "suggest": { "ext-event": "For better performance. " diff --git a/composer.lock b/composer.lock index edf286f..7d41f97 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "505fb3d97c338ea53b787196fcc3daaa", + "content-hash": "7c70d8c8bda8a5098b8baed463db8839", "packages": [ { "name": "aliyuncs/oss-sdk-php", @@ -6634,6 +6634,39 @@ }, "time": "2025-04-04T13:23:25+00:00" }, + { + "name": "webman/gateway-worker", + "version": "v1.0.9", + "source": { + "type": "git", + "url": "https://github.com/webman-php/gateway-worker.git", + "reference": "6780c1c788c273a24699caee84feb14224234000" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/gateway-worker/zipball/6780c1c788c273a24699caee84feb14224234000", + "reference": "6780c1c788c273a24699caee84feb14224234000", + "shasum": "" + }, + "require": { + "workerman/gateway-worker": ">=3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\GatewayWorker\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "support": { + "issues": "https://github.com/webman-php/gateway-worker/issues", + "source": "https://github.com/webman-php/gateway-worker/tree/v1.0.9" + }, + "time": "2024-04-30T01:05:42+00:00" + }, { "name": "webman/redis", "version": "v2.1.3", @@ -6793,6 +6826,60 @@ }, "time": "2025-01-15T07:20:50+00:00" }, + { + "name": "workerman/gateway-worker", + "version": "v4.0.1", + "source": { + "type": "git", + "url": "https://github.com/walkor/GatewayWorker.git", + "reference": "25e4430982caa98f82e9187d092ce34b2fddfc90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/GatewayWorker/zipball/25e4430982caa98f82e9187d092ce34b2fddfc90", + "reference": "25e4430982caa98f82e9187d092ce34b2fddfc90", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "workerman/workerman": "^4.0.30 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "GatewayWorker\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "homepage": "http://www.workerman.net", + "keywords": [ + "communication", + "distributed" + ], + "support": { + "issues": "https://github.com/walkor/GatewayWorker/issues", + "source": "https://github.com/walkor/GatewayWorker/tree/v4.0.1" + }, + "funding": [ + { + "url": "https://opencollective.com/walkor", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/walkor", + "type": "patreon" + } + ], + "time": "2025-06-27T01:30:09+00:00" + }, { "name": "workerman/redis", "version": "v2.0.4", diff --git a/config/plugin/webman/gateway-worker/app.php b/config/plugin/webman/gateway-worker/app.php new file mode 100644 index 0000000..8f9c426 --- /dev/null +++ b/config/plugin/webman/gateway-worker/app.php @@ -0,0 +1,4 @@ + true, +]; \ No newline at end of file diff --git a/config/plugin/webman/gateway-worker/process.php b/config/plugin/webman/gateway-worker/process.php new file mode 100644 index 0000000..5c7efaa --- /dev/null +++ b/config/plugin/webman/gateway-worker/process.php @@ -0,0 +1,39 @@ + [ + 'handler' => Gateway::class, + 'listen' => 'websocket://0.0.0.0:5151', + 'count' => 2, + 'reloadable' => false, + 'constructor' => ['config' => [ + 'lanIp' => '127.0.0.1', + 'startPort' => 2300, + 'pingInterval' => 55, + 'pingNotResponseLimit' => 1, + 'pingData' => '', + 'registerAddress' => '127.0.0.1:1236', + 'onConnect' => function(){}, + ]] + ], + 'worker' => [ + 'handler' => BusinessWorker::class, + 'count' => cpu_count()*2, + 'constructor' => ['config' => [ + 'eventHandler' => plugin\webman\gateway\Events::class, + 'name' => 'ChatBusinessWorker', + 'registerAddress' => '127.0.0.1:1236', + ]] + ], + 'register' => [ + 'handler' => Register::class, + 'listen' => 'text://127.0.0.1:1236', + 'count' => 1, // Must be 1 + 'reloadable' => false, + 'constructor' => [] + ], +]; diff --git a/config/route.php b/config/route.php index 2abd88a..6393f2d 100644 --- a/config/route.php +++ b/config/route.php @@ -64,10 +64,29 @@ Route::group('/common', function () { Route::any('/cache/clear', [app\common\controller\PresetController::class, 'cacheClear']); Route::any('/menus', [app\common\controller\PresetController::class, 'menus']); }); + + + // 消息通知 + Route::group('/notice', function () { + Route::any('/list', [app\common\controller\MessageUserStatusController::class, 'index']); + Route::any('/show', [app\common\controller\MessageUserStatusController::class, 'show']); + Route::any('/read', [app\common\controller\MessageUserStatusController::class, 'read']); + Route::any('/destroy', [app\common\controller\MessageUserStatusController::class, 'destroy']); + + Route::any('/unread', [app\common\controller\MessageUserStatusController::class, 'listUnRead']); + }); }); // 后台管理 Route::group('/admin', function () { + // 消息通知 + Route::group('/notice', function () { + Route::any('/list', [app\common\controller\MessageNotificationsController::class, 'index']); + Route::any('/show', [app\common\controller\MessageNotificationsController::class, 'show']); + Route::any('/push', [app\common\controller\MessageNotificationsController::class, 'push']); + Route::any('/destroy', [app\common\controller\MessageNotificationsController::class, 'destroy']); + }); + // 角色 Route::group('/role', function () { Route::any('/list', [app\admin\controller\RoleController::class, 'index']); @@ -157,6 +176,7 @@ Route::group('/admin', function () { Route::any('/transfer', [app\crmAdmin\controller\CustomerController::class, 'transfer']); Route::any('/detail', [app\crmAdmin\controller\CustomerController::class, 'detail']); Route::any('/delete', [app\crmAdmin\controller\CustomerController::class, 'delete']); + Route::any('/restore', [app\crmAdmin\controller\CustomerController::class, 'restore']); //恢复客户 Route::any('/cede', [app\crm\controller\CustomerController::class, 'cede']); Route::any('/follow', [app\crmAdmin\controller\CustomerController::class, 'follow']); Route::any('/allocation', [app\crmAdmin\controller\CustomerController::class, 'allocation']); diff --git a/plugin/webman/gateway/Events.php b/plugin/webman/gateway/Events.php new file mode 100644 index 0000000..d63708e --- /dev/null +++ b/plugin/webman/gateway/Events.php @@ -0,0 +1,38 @@ +auth($client_id, $data); + } + + public static function onMessage($client_id, $message) + { + self::$wsEvents->message($client_id, $message); + } + + public static function onClose($client_id) + { + var_dump('关闭客户端ID:'.$client_id); + } + +} \ No newline at end of file From 8e647d80afe6f16ae946baa4be9733cbecfe2de3 Mon Sep 17 00:00:00 2001 From: mzl Date: Fri, 22 Aug 2025 18:27:47 +0800 Subject: [PATCH 15/33] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E7=A6=BB=E8=81=8C?= =?UTF-8?q?=E4=BA=BA=E5=91=98=E5=85=B3=E8=81=94=E7=9A=84=E8=AF=A2=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/model/InquiryLog.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/common/model/InquiryLog.php b/app/common/model/InquiryLog.php index 798b267..4d8b483 100644 --- a/app/common/model/InquiryLog.php +++ b/app/common/model/InquiryLog.php @@ -60,9 +60,13 @@ class InquiryLog extends Model return $this->hasOne(User::class, 'id', 'enter_user_id'); } + // public function recipientInfo() + // { + // return $this->hasOne(User::class, 'id', 'recipient')->where('status', 1); + // } public function recipientInfo() { - return $this->hasOne(User::class, 'id', 'recipient')->where('status', 1); + return $this->hasOne(User::class, 'id', 'recipient'); } public function channelInfo() From df10c7c7d726bfbe64bc5d974585481eb39fd218 Mon Sep 17 00:00:00 2001 From: mzl Date: Fri, 22 Aug 2025 19:04:15 +0800 Subject: [PATCH 16/33] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E8=AF=A2=E5=8D=95?= =?UTF-8?q?=E5=88=86=E9=85=8D=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/queue/redis/InquiryToCrm.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/queue/redis/InquiryToCrm.php b/app/queue/redis/InquiryToCrm.php index 7bec9ca..b3c3240 100644 --- a/app/queue/redis/InquiryToCrm.php +++ b/app/queue/redis/InquiryToCrm.php @@ -77,6 +77,17 @@ class InquiryToCrm implements Consumer // Log::info('发送邮件消息:' . json_encode($options)); \Webman\RedisQueue\Redis::send('email-message', $options); } + + $wsdata = [ + 'sender_id' => 0, + 'sender_type' => 'system', + 'title' => '有新的客户线索,SN:' . $info->order_no . ',请及时处理', + 'content' => '有新的客户线索,SN:' . $info->order_no . ',请及时处理', + 'scene' => 1, + 'recipient' => [$info->recipient] + ]; + // 异步推送 + \Webman\RedisQueue\Redis::send('notice', $wsdata); } } catch (\Throwable $th) { throw $th; From 6a66b56171ba4ad344101dbfdddcd62cc6689d36 Mon Sep 17 00:00:00 2001 From: mzl Date: Fri, 22 Aug 2025 19:12:55 +0800 Subject: [PATCH 17/33] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=BD=95=E5=8D=95?= =?UTF-8?q?=E6=97=B6=E6=9C=AA=E9=80=89=E6=8B=A9=E8=B7=9F=E5=8D=95=E4=BA=BA?= =?UTF-8?q?=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/inquiry/service/InquiryLogService.php | 2 ++ app/queue/redis/InquiryToCrm.php | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/inquiry/service/InquiryLogService.php b/app/inquiry/service/InquiryLogService.php index 10367db..31c161a 100644 --- a/app/inquiry/service/InquiryLogService.php +++ b/app/inquiry/service/InquiryLogService.php @@ -162,6 +162,8 @@ class InquiryLogService $data['r_time'] = time(); $data['t_time'] = time(); $data['status'] = 2; + } else { + $data['recipient'] = 0; } return $data; } diff --git a/app/queue/redis/InquiryToCrm.php b/app/queue/redis/InquiryToCrm.php index b3c3240..4d48d1c 100644 --- a/app/queue/redis/InquiryToCrm.php +++ b/app/queue/redis/InquiryToCrm.php @@ -68,21 +68,21 @@ class InquiryToCrm implements Consumer throw new BusinessException('同步失败~'); } - // 发送邮件消息 - if ($uinfo && $uinfo->email) { - $options = [ - 'id' => $info->id, - 'email' => $uinfo->email - ]; - // Log::info('发送邮件消息:' . json_encode($options)); - \Webman\RedisQueue\Redis::send('email-message', $options); - } + // // 发送邮件消息 + // if ($uinfo && $uinfo->email) { + // $options = [ + // 'id' => $info->id, + // 'email' => $uinfo->email + // ]; + // // Log::info('发送邮件消息:' . json_encode($options)); + // \Webman\RedisQueue\Redis::send('email-message', $options); + // } $wsdata = [ 'sender_id' => 0, 'sender_type' => 'system', - 'title' => '有新的客户线索,SN:' . $info->order_no . ',请及时处理', - 'content' => '有新的客户线索,SN:' . $info->order_no . ',请及时处理', + 'title' => '有新的客户线索,' . $info->order_no . ',请及时处理', + 'content' => '有新的客户线索,' . $info->order_no . ',请及时处理', 'scene' => 1, 'recipient' => [$info->recipient] ]; From 4b4169b5ee6220dd2b40600e2c97daec965fb678 Mon Sep 17 00:00:00 2001 From: chenlan <18339827479@163.com> Date: Sat, 23 Aug 2025 13:37:47 +0800 Subject: [PATCH 18/33] =?UTF-8?q?=E4=BF=AE=E6=94=B9gateway=E7=9B=91?= =?UTF-8?q?=E5=90=AC=E7=AB=AF=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/plugin/webman/gateway-worker/process.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/plugin/webman/gateway-worker/process.php b/config/plugin/webman/gateway-worker/process.php index 5c7efaa..64796e9 100644 --- a/config/plugin/webman/gateway-worker/process.php +++ b/config/plugin/webman/gateway-worker/process.php @@ -7,7 +7,7 @@ use Webman\GatewayWorker\Register; return [ 'gateway' => [ 'handler' => Gateway::class, - 'listen' => 'websocket://0.0.0.0:5151', + 'listen' => 'websocket://0.0.0.0:' . getenv('GATEWAY_LISTEN'), 'count' => 2, 'reloadable' => false, 'constructor' => ['config' => [ From 722a79b58bd2a55370e55b1216597e1dea4a0f8e Mon Sep 17 00:00:00 2001 From: mzl Date: Sat, 23 Aug 2025 13:55:55 +0800 Subject: [PATCH 19/33] Merge branch 'develop' --- config/plugin/webman/gateway-worker/process.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/plugin/webman/gateway-worker/process.php b/config/plugin/webman/gateway-worker/process.php index 5c7efaa..64796e9 100644 --- a/config/plugin/webman/gateway-worker/process.php +++ b/config/plugin/webman/gateway-worker/process.php @@ -7,7 +7,7 @@ use Webman\GatewayWorker\Register; return [ 'gateway' => [ 'handler' => Gateway::class, - 'listen' => 'websocket://0.0.0.0:5151', + 'listen' => 'websocket://0.0.0.0:' . getenv('GATEWAY_LISTEN'), 'count' => 2, 'reloadable' => false, 'constructor' => ['config' => [ From 6730f11ccac542192607c0c36545ea77aef4da20 Mon Sep 17 00:00:00 2001 From: mzl Date: Sat, 23 Aug 2025 16:52:08 +0800 Subject: [PATCH 20/33] tz --- app/common/utils/email/Emails.php | 2 +- app/controller/IndexController.php | 23 ++---- app/queue/redis/InquiryToCrm.php | 7 -- app/queue/redis/InquiryToCrmNew.php | 112 ++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 23 deletions(-) create mode 100644 app/queue/redis/InquiryToCrmNew.php diff --git a/app/common/utils/email/Emails.php b/app/common/utils/email/Emails.php index 6d67f1a..18b06c2 100644 --- a/app/common/utils/email/Emails.php +++ b/app/common/utils/email/Emails.php @@ -436,7 +436,7 @@ class Emails // 初始化 UID 轮询器 $uidRotator = new \app\common\utils\RedisUidRotator('email_parsed_result_20250804'); - $uidRotator->setUids([64, 63, 83, 93, 108, 118, 131, 132, 145, 150, 143, 156, 157, 158], false); + $uidRotator->setUids([64, 63, 83, 93, 108, 118, 132, 145, 150, 143, 156, 157, 158], false); EmailParsedResult::where('status', 0) ->chunkById($batchSize, function ($records) use ($delaySeconds, $throttleAfter, $throttleSeconds, $enterUser, $channelInfo, $uidRotator) { diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 8e15d2a..766344a 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -116,27 +116,20 @@ class IndexController // 'recipient' => [1], // ]; - $data['recipient'] = [1, 74]; - // // 同步推送 - // (new MessageNotificationsService())->pushNotice($data); + // $data['recipient'] = [1, 74]; + // // // 同步推送 + // // (new MessageNotificationsService())->pushNotice($data); - // 异步推送 - \Webman\RedisQueue\Redis::send('notice', $data); + // // 异步推送 + // \Webman\RedisQueue\Redis::send('notice', $data); - return 'success'; + $info = InquiryLog::with(['channelInfo', 'countryInfo', 'enter'])->where('id', 25998)->where('sync_crm', 0)->first(); - // return json($data); + var_dump($info); - // $phone = ''; - // $email = ''; - - // $userByPhone = User::where('corp_id', 2)->where('phone', $phone)->where('phone', '!=', '')->first(); - // $userByEmail = User::where('corp_id', 2)->where('email', $email)->where('email', '!=', '')->first(); - // $user = $userByPhone ?: $userByEmail; - - // return json($user); + return json(['code' => 0, 'msg' => 'success']); } } \ No newline at end of file diff --git a/app/queue/redis/InquiryToCrm.php b/app/queue/redis/InquiryToCrm.php index 4d48d1c..4d7ca3e 100644 --- a/app/queue/redis/InquiryToCrm.php +++ b/app/queue/redis/InquiryToCrm.php @@ -37,13 +37,6 @@ class InquiryToCrm implements Consumer if ($uinfo) { $corpId = $uinfo->corp_id; } - // // 消息推送参数 分配 - // $scene = 'inquiry-person-allocation'; - // $receiveType = 'person'; - } else { - // // 消息推送参数 发布 - // $scene = 'inquiry-group-send'; - // $receiveType = 'group'; } // 同步客户数据 $crmCustomer = new CrmCustomer(); diff --git a/app/queue/redis/InquiryToCrmNew.php b/app/queue/redis/InquiryToCrmNew.php new file mode 100644 index 0000000..a076f30 --- /dev/null +++ b/app/queue/redis/InquiryToCrmNew.php @@ -0,0 +1,112 @@ +where('id', $data['id'])->where('sync_crm', 0)->first(); + + + + if ($info) { + // 获取用户信息 + $recipient = 0; + $corpId = 0; + $uinfo = null; + if ($info->recipient) { + $recipient = $info->recipient; + $uinfo = User::where('id', $info->recipient)->first(); + if ($uinfo) { + $corpId = $uinfo->corp_id; + } + } + // 同步客户数据 + $crmCustomer = new CrmCustomer(); + $crmCustomer->sn = $info->order_no; + $crmCustomer->corp_abbr = $corpId; + $crmCustomer->user_id = $recipient; + $crmCustomer->type = 1; + $crmCustomer->channel_id = $info->channel; + $crmCustomer->country_id = $info->country; + $crmCustomer->magnitude = $info->quality; + $crmCustomer->email = $info->email; + $crmCustomer->phone = $info->tel; + $crmCustomer->WhatsApp = $info->whatsapp; + $crmCustomer->contacts = $info->contacts; + $crmCustomer->desc_collect = $info->content; + $crmCustomer->enter_user_id = $info->enter_user_id; + $crmCustomer->obtain = time(); + // 修改同步CRM状态 + $info->sync_crm = 1; + // 保存 + if (!$crmCustomer->save() || !$info->save()) { + throw new BusinessException('同步失败~'); + } + + // // 发送邮件消息 + // if ($uinfo && $uinfo->email) { + // $options = [ + // 'id' => $info->id, + // 'email' => $uinfo->email + // ]; + // // Log::info('发送邮件消息:' . json_encode($options)); + // \Webman\RedisQueue\Redis::send('email-message', $options); + // } + + $wsdata = [ + 'sender_id' => 0, + 'sender_type' => 'system', + 'title' => '有新的客户线索,' . $info->order_no . ',请及时处理', + 'content' => '有新的客户线索,' . $info->order_no . ',请及时处理', + 'scene' => 1, + 'recipient' => [$info->recipient] + ]; + // 异步推送 + \Webman\RedisQueue\Redis::send('notice', $wsdata); + } + } catch (\Throwable $th) { + throw $th; + } + } + + // 消费失败回调 + /* + $package = [ + 'id' => 1357277951, // 消息ID + 'time' => 1709170510, // 消息时间 + 'delay' => 0, // 延迟时间 + 'attempts' => 2, // 消费次数 + 'queue' => 'send-mail', // 队列名 + 'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容 + 'max_attempts' => 5, // 最大重试次数 + 'error' => '错误信息' // 错误信息 + ] + */ + public function onConsumeFailure(\Throwable $e, $package) + { + echo "consume failure\n"; + echo $e->getMessage() . "\n"; + // 无需反序列化 + var_export($package); + } +} \ No newline at end of file From 118c8d99f2e6f97f35fe6979c8ca3b6f147bc6a8 Mon Sep 17 00:00:00 2001 From: beiman <1696136552@qq.com> Date: Mon, 25 Aug 2025 09:21:28 +0800 Subject: [PATCH 21/33] 11 --- app/controller/IndexController.php | 17 ++- app/queue/redis/InquiryToCrmNew.php | 167 ++++++++++++++++++---------- 2 files changed, 123 insertions(+), 61 deletions(-) diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 766344a..ba87d98 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -126,9 +126,22 @@ class IndexController // \Webman\RedisQueue\Redis::send('notice', $data); - $info = InquiryLog::with(['channelInfo', 'countryInfo', 'enter'])->where('id', 25998)->where('sync_crm', 0)->first(); + $info = InquiryLog::with(['channelInfo', 'countryInfo', 'enter'])->where('id', 1)->where('sync_crm', 0)->first(); - var_dump($info); + var_dump(empty($info->recipient)); + + if ($info) { + var_dump(11); + } else { + var_dump(22); + } + + + if ($info !== NULL) { + var_dump(33); + } else { + var_dump(44); + } return json(['code' => 0, 'msg' => 'success']); } diff --git a/app/queue/redis/InquiryToCrmNew.php b/app/queue/redis/InquiryToCrmNew.php index a076f30..789d10d 100644 --- a/app/queue/redis/InquiryToCrmNew.php +++ b/app/queue/redis/InquiryToCrmNew.php @@ -10,8 +10,9 @@ use app\common\model\User; use app\common\utils\FeishuMessage; use support\exception\BusinessException; use support\Log; +use support\Model; -class InquiryToCrm implements Consumer +class InquiryToCrmNew implements Consumer { // 要消费的队列名 public $queue = 'inquiry-to-crm-new'; @@ -22,73 +23,121 @@ class InquiryToCrm implements Consumer // 消费 public function consume($data) { + + /* + 询单 与 CRM 之间的同步场景描述 + + 添加(询单) + 添加 不发 不分 (不同步)1 + 添加 发了 不分 (要同步)2 + 添加 不发 分了 (要同步)3 + 添加 不发 直分 (要同步)4 + + 更新(询单) + 没发 没分 更新 (不同步)5 + 没发 分过 更新 (要同步)6 + 发过 没分 更新 (要同步)7 + 发过 分过 更新 (要同步)8 + */ + try { // 获取询盘信息 - $info = InquiryLog::with(['channelInfo', 'countryInfo', 'enter'])->where('id', $data['id'])->where('sync_crm', 0)->first(); + $info = InquiryLog::with(['channelInfo', 'countryInfo', 'enter'])->where('id', $data['id'])->first(); - - - if ($info) { - // 获取用户信息 - $recipient = 0; - $corpId = 0; - $uinfo = null; - if ($info->recipient) { - $recipient = $info->recipient; - $uinfo = User::where('id', $info->recipient)->first(); - if ($uinfo) { - $corpId = $uinfo->corp_id; - } - } - // 同步客户数据 - $crmCustomer = new CrmCustomer(); - $crmCustomer->sn = $info->order_no; - $crmCustomer->corp_abbr = $corpId; - $crmCustomer->user_id = $recipient; - $crmCustomer->type = 1; - $crmCustomer->channel_id = $info->channel; - $crmCustomer->country_id = $info->country; - $crmCustomer->magnitude = $info->quality; - $crmCustomer->email = $info->email; - $crmCustomer->phone = $info->tel; - $crmCustomer->WhatsApp = $info->whatsapp; - $crmCustomer->contacts = $info->contacts; - $crmCustomer->desc_collect = $info->content; - $crmCustomer->enter_user_id = $info->enter_user_id; - $crmCustomer->obtain = time(); - // 修改同步CRM状态 - $info->sync_crm = 1; - // 保存 - if (!$crmCustomer->save() || !$info->save()) { - throw new BusinessException('同步失败~'); - } - - // // 发送邮件消息 - // if ($uinfo && $uinfo->email) { - // $options = [ - // 'id' => $info->id, - // 'email' => $uinfo->email - // ]; - // // Log::info('发送邮件消息:' . json_encode($options)); - // \Webman\RedisQueue\Redis::send('email-message', $options); - // } - - $wsdata = [ - 'sender_id' => 0, - 'sender_type' => 'system', - 'title' => '有新的客户线索,' . $info->order_no . ',请及时处理', - 'content' => '有新的客户线索,' . $info->order_no . ',请及时处理', - 'scene' => 1, - 'recipient' => [$info->recipient] - ]; - // 异步推送 - \Webman\RedisQueue\Redis::send('notice', $wsdata); + // 无论添加 或 更新,询单必然要存在 + if (!$info) { + throw new BusinessException('询盘不存在:' . $data['id']); } + + if ($info->status == 0 && $info->recipient == 0) { + // 如果询单未发、未分,则不与crm同步,不做任何处理 + return true; + } + + // 更改库信息 + $this->syncCustomer($info); + + $wsdata = [ + 'sender_id' => 0, + 'sender_type' => 'system', + 'title' => '有新的客户线索,' . $info->order_no . ',请及时处理', + 'content' => '有新的客户线索,' . $info->order_no . ',请及时处理', + 'scene' => 1, + 'recipient' => [$info->recipient] + ]; + // 异步推送 + \Webman\RedisQueue\Redis::send('notice', $wsdata); } catch (\Throwable $th) { throw $th; } } + + public function syncCustomer(Model $info) + { + $uInfo = $this->recipientInfo($info); + $crmCustomerInfo = CrmCustomer::where('sn', $info->order_no)->first(); + if ($crmCustomerInfo) { + $udata = [ + 'corp_abbr' => $uInfo['corpId'], + 'user_id' => $info->recipient, + 'channel_id' => $info->channel, + 'country_id' => $info->country, + 'magnitude' => $info->quality, + 'email' => $info->email, + 'phone' => $info->tel, + 'WhatsApp' => $info->whatsapp, + 'contacts' => $info->contacts, + 'desc_collect' => $info->content, + ]; + if (!$crmCustomerInfo->update($udata)) { + throw new BusinessException('同步失败,更新客户线索失败'); + } + } else { + $crmCustomer = new CrmCustomer(); + $crmCustomer->sn = $info->order_no; + $crmCustomer->corp_abbr = $uInfo['corpId']; + $crmCustomer->user_id = $uInfo['recipient']; + $crmCustomer->type = 1; + $crmCustomer->channel_id = $info->channel; + $crmCustomer->country_id = $info->country; + $crmCustomer->magnitude = $info->quality; + $crmCustomer->email = $info->email; + $crmCustomer->phone = $info->tel; + $crmCustomer->WhatsApp = $info->whatsapp; + $crmCustomer->contacts = $info->contacts; + $crmCustomer->desc_collect = $info->content; + $crmCustomer->enter_user_id = $info->enter_user_id; + $crmCustomer->obtain = time(); + // 修改同步CRM状态 + $info->sync_crm = 1; + // 保存 + if (!$crmCustomer->save() || !$info->save()) { + throw new BusinessException('同步失败,创建客户线索失败'); + } + } + return true; + } + + public function recipientInfo(Model $info) + { + $recipient = 0; + $corpId = 0; + $uinfo = null; + if (!empty($info->recipient)) { + $recipient = $info->recipient; + $uinfo = User::where('id', $info->recipient)->first(); + if ($uinfo) { + $corpId = $uinfo->corp_id; + } + } + return [ + 'recipient' => $recipient, + 'corpId' => $corpId, + 'uinfo' => $uinfo + ]; + } + // 消费失败回调 /* $package = [ From d5c16d44acd580ebc515b02c9766057b81ba73df Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Mon, 25 Aug 2025 09:55:12 +0800 Subject: [PATCH 22/33] =?UTF-8?q?=E8=84=B1=E6=95=8F=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/utils/Util.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/crm/utils/Util.php b/app/crm/utils/Util.php index 586ee45..0701f5e 100644 --- a/app/crm/utils/Util.php +++ b/app/crm/utils/Util.php @@ -126,9 +126,14 @@ class Util public static function mask($str) { + $length = strlen($str); + if(empty($str)){ return ''; } + if($length <= 6){ + return $str; + } return substr($str, 0, 3) . '****' . substr($str, -4); } From 4d8b54722f4ec49f16e39549bf88cbaad4d77ce3 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Mon, 25 Aug 2025 14:30:38 +0800 Subject: [PATCH 23/33] =?UTF-8?q?=E5=B7=A5=E5=8D=95=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/service/ConversationService.php | 48 +++++++++++++++------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/app/ticket/service/ConversationService.php b/app/ticket/service/ConversationService.php index ea97cef..5b0bef9 100644 --- a/app/ticket/service/ConversationService.php +++ b/app/ticket/service/ConversationService.php @@ -3,6 +3,7 @@ namespace app\ticket\service; use app\common\model\User; +use app\common\service\MessageNotificationsService; use app\common\service\OssService; use app\ticket\logic\CategorieLogic; use app\ticket\logic\ConversationLogic; @@ -55,41 +56,60 @@ class ConversationService $categorie= (new CategorieLogic())->find($ticket['categorie_id']); $categorieName = isset($categorie['translation']) ? $categorie['translation']['name'] : ''; - $notice = [ - 'code' => $ticket['code'], - 'category' => $categorieName, - 'content' => $data['content'], - 'reply' => '', - ]; +// $notice = [ +// 'code' => $ticket['code'], +// 'category' => $categorieName, +// 'content' => $data['content'], +// 'reply' => '', +// ]; //$emailDefault = 'app@hnxinyouhe.com'; //公共邮箱 //$emailDefault = 'support@hnxinyouhe.com'; //公共邮箱 $emailDefault = env('TICKET_EMAIL_DEFAULT'); - if (empty($conversation)) { + $userDefault = [1, 75, 78]; + if (empty($conversation)) { //没有沟通记录 + $title = '新工单提醒-'.$categorieName; $email = $emailDefault; + $notice_user_id = $userDefault; }else{ + $title = '工单回复'; $tiket = (new TicketLogic())->find($conversation['ticket_id']); if($data['user_id'] != $tiket['user_id']){ - $email = User::where('id',$tiket['user_id'])->value('email'); + //$email = User::where('id',$tiket['user_id'])->value('email'); + $notice_user_id = [$tiket['user_id']]; }else{ $conversationLast = Conversation::where('ticket_id', '=', $conversation['ticket_id']) ->where('user_id', '!=', $data['user_id']) ->orderBy('id', 'desc') ->first(); - $email = (!empty($conversationLast['user_id']))?User::where('id',$conversationLast['user_id'])->value('email',$emailDefault):$emailDefault; + //$email = (!empty($conversationLast['user_id']))?User::where('id',$conversationLast['user_id'])->value('email',$emailDefault):$emailDefault; + $notice_user_id = (!empty($conversationLast['user_id']))? [$conversationLast['user_id']]:$userDefault; } - $notice['content'] = $tiket['content']; + //$notice['content'] = $tiket['content']; + $tiket_content = $tiket['content']; $status_fanyi = array_map(fn($v) => "{{{$v}}}", array_flip(Constants::TICKET_STATUS)); $locale = locale(); $ticket_status = $locale == 'zh'? Constants::TICKET_STATUS_ZH:Constants::TICKET_STATUS_EN; $content = str_replace($status_fanyi,$ticket_status,$data['content']); - $notice['reply'] = '
  • 最新回复:' . $content . '
  • '; - } - if(!empty($emailDefault)){ - \app\common\utils\email\MailSend::send($email, '【XYH】新的工单提醒', replaceTpl($notice, 'work_order')); + //$notice['reply'] = '
  • 最新回复:' . $content . '
  • '; + $reply = $content?($content.'\n'.'工单内容:'):'' ; } +// if(!empty($emailDefault)){ +// \app\common\utils\email\MailSend::send($email, '【XYH】新的工单提醒', replaceTpl($notice, 'work_order')); +// } + $notice = ['sender_id' => 0, //发送人ID 0默认系统 + 'sender_type' => 'system', //发送者类型 user admin system + 'title' => $title, //标题 + 'content' => $reply . $tiket_content, //内容 + 'scene' => 1, //场景 0系统公共 1系统通知 + 'recipient' => $notice_user_id, //接收人 空为全部 + 'push_method' => 0 //推送方式0系统推送1手动推送 + ]; + + // 异步推送 + \Webman\RedisQueue\Redis::send('notice', $notice); unset($data['categorie_id']); unset($data['code']); From 7a9999508c30505c3e033dd6f61ae6380920a7de Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Mon, 25 Aug 2025 14:32:14 +0800 Subject: [PATCH 24/33] =?UTF-8?q?=E5=B7=A5=E5=8D=95=E6=8F=90=E9=86=92?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/service/ConversationService.php | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/app/ticket/service/ConversationService.php b/app/ticket/service/ConversationService.php index 5b0bef9..b07ef3c 100644 --- a/app/ticket/service/ConversationService.php +++ b/app/ticket/service/ConversationService.php @@ -55,26 +55,15 @@ class ConversationService $ticket = (new TicketLogic())->find($data['ticket_id']); $categorie= (new CategorieLogic())->find($ticket['categorie_id']); $categorieName = isset($categorie['translation']) ? $categorie['translation']['name'] : ''; - -// $notice = [ -// 'code' => $ticket['code'], -// 'category' => $categorieName, -// 'content' => $data['content'], -// 'reply' => '', -// ]; - //$emailDefault = 'app@hnxinyouhe.com'; //公共邮箱 - //$emailDefault = 'support@hnxinyouhe.com'; //公共邮箱 - $emailDefault = env('TICKET_EMAIL_DEFAULT'); + $userDefault = [1, 75, 78]; if (empty($conversation)) { //没有沟通记录 $title = '新工单提醒-'.$categorieName; - $email = $emailDefault; $notice_user_id = $userDefault; }else{ $title = '工单回复'; $tiket = (new TicketLogic())->find($conversation['ticket_id']); if($data['user_id'] != $tiket['user_id']){ - //$email = User::where('id',$tiket['user_id'])->value('email'); $notice_user_id = [$tiket['user_id']]; }else{ $conversationLast = Conversation::where('ticket_id', '=', $conversation['ticket_id']) @@ -82,10 +71,8 @@ class ConversationService ->orderBy('id', 'desc') ->first(); - //$email = (!empty($conversationLast['user_id']))?User::where('id',$conversationLast['user_id'])->value('email',$emailDefault):$emailDefault; $notice_user_id = (!empty($conversationLast['user_id']))? [$conversationLast['user_id']]:$userDefault; } - //$notice['content'] = $tiket['content']; $tiket_content = $tiket['content']; $status_fanyi = array_map(fn($v) => "{{{$v}}}", array_flip(Constants::TICKET_STATUS)); @@ -93,12 +80,9 @@ class ConversationService $ticket_status = $locale == 'zh'? Constants::TICKET_STATUS_ZH:Constants::TICKET_STATUS_EN; $content = str_replace($status_fanyi,$ticket_status,$data['content']); - //$notice['reply'] = '
  • 最新回复:' . $content . '
  • '; $reply = $content?($content.'\n'.'工单内容:'):'' ; } -// if(!empty($emailDefault)){ -// \app\common\utils\email\MailSend::send($email, '【XYH】新的工单提醒', replaceTpl($notice, 'work_order')); -// } + $notice = ['sender_id' => 0, //发送人ID 0默认系统 'sender_type' => 'system', //发送者类型 user admin system 'title' => $title, //标题 From e3a20d8f85d5370cbf98f5b1d666803c40948a26 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Mon, 25 Aug 2025 14:36:01 +0800 Subject: [PATCH 25/33] =?UTF-8?q?=E5=B7=A5=E5=8D=95=E6=8F=90=E9=86=92?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/service/ConversationService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ticket/service/ConversationService.php b/app/ticket/service/ConversationService.php index b07ef3c..d326d6b 100644 --- a/app/ticket/service/ConversationService.php +++ b/app/ticket/service/ConversationService.php @@ -86,7 +86,7 @@ class ConversationService $notice = ['sender_id' => 0, //发送人ID 0默认系统 'sender_type' => 'system', //发送者类型 user admin system 'title' => $title, //标题 - 'content' => $reply . $tiket_content, //内容 + 'content' => ($reply??'') . $tiket_content, //内容 'scene' => 1, //场景 0系统公共 1系统通知 'recipient' => $notice_user_id, //接收人 空为全部 'push_method' => 0 //推送方式0系统推送1手动推送 From 341dae491d782587da1f32ef6c17bd024c633fe0 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Mon, 25 Aug 2025 14:40:34 +0800 Subject: [PATCH 26/33] =?UTF-8?q?=E5=B7=A5=E5=8D=95=E6=8F=90=E9=86=92?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/service/ConversationService.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/ticket/service/ConversationService.php b/app/ticket/service/ConversationService.php index d326d6b..852fc0c 100644 --- a/app/ticket/service/ConversationService.php +++ b/app/ticket/service/ConversationService.php @@ -57,6 +57,7 @@ class ConversationService $categorieName = isset($categorie['translation']) ? $categorie['translation']['name'] : ''; $userDefault = [1, 75, 78]; + $tiket_content = $ticket['content']; if (empty($conversation)) { //没有沟通记录 $title = '新工单提醒-'.$categorieName; $notice_user_id = $userDefault; From 635027b97c6cba969663f0b2f4fb2cf7d762cb75 Mon Sep 17 00:00:00 2001 From: mzl Date: Mon, 25 Aug 2025 15:41:07 +0800 Subject: [PATCH 27/33] =?UTF-8?q?=E8=AF=A2=E5=8D=95=E4=B8=8Ecrm=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/queue/redis/InquiryToCrmNew.php | 65 ++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/app/queue/redis/InquiryToCrmNew.php b/app/queue/redis/InquiryToCrmNew.php index 789d10d..420fe95 100644 --- a/app/queue/redis/InquiryToCrmNew.php +++ b/app/queue/redis/InquiryToCrmNew.php @@ -56,28 +56,54 @@ class InquiryToCrmNew implements Consumer // 更改库信息 $this->syncCustomer($info); - - $wsdata = [ - 'sender_id' => 0, - 'sender_type' => 'system', - 'title' => '有新的客户线索,' . $info->order_no . ',请及时处理', - 'content' => '有新的客户线索,' . $info->order_no . ',请及时处理', - 'scene' => 1, - 'recipient' => [$info->recipient] - ]; - // 异步推送 - \Webman\RedisQueue\Redis::send('notice', $wsdata); } catch (\Throwable $th) { throw $th; } } + public function pushWs($title, $content, $recipient) + { + $wsdata = [ + 'sender_id' => 0, + 'sender_type' => 'system', + 'title' => $title, + 'content' => $content, + 'scene' => 1, + 'recipient' => $recipient + ]; + // 异步推送 + \Webman\RedisQueue\Redis::send('notice', $wsdata); + } public function syncCustomer(Model $info) { $uInfo = $this->recipientInfo($info); $crmCustomerInfo = CrmCustomer::where('sn', $info->order_no)->first(); if ($crmCustomerInfo) { + + // 消息推送 + if ($info->status == 2 && empty($crmCustomerInfo->user_id)) { + // $notice = '分配'; + $title = '[询单·分配]新的客户线索【' . $info->order_no . '】,请及时处理'; + $content = '[询单·分配]新的客户线索【' . $info->order_no . '】,请及时处理'; + $recipient = [$info->recipient]; + $this->pushWs($title, $content, $recipient); + } else { + if ($info->status == 2 && $crmCustomerInfo->user_id == $info->recipient) { + // $notice = '更新'; + $title = '[询单·更新]客户线索' . $info->order_no . '有新的调整,请及时处理'; + $content = '[询单·更新]客户线索' . $info->order_no . '有新的调整,请及时处理'; + $recipient = [$info->recipient]; + $this->pushWs($title, $content, $recipient); + } else { + // $notice = '重新分配'; + $title = '[询单·分配]新的客户线索【' . $info->order_no . '】,请及时处理'; + $content = '[询单·分配]新的客户线索【' . $info->order_no . '】,请及时处理'; + $recipient = [$info->recipient]; + $this->pushWs($title, $content, $recipient); + } + } + $udata = [ 'corp_abbr' => $uInfo['corpId'], 'user_id' => $info->recipient, @@ -115,6 +141,23 @@ class InquiryToCrmNew implements Consumer if (!$crmCustomer->save() || !$info->save()) { throw new BusinessException('同步失败,创建客户线索失败'); } + + // 消息推送 + if ($info->status == 1) { + // $notice = '发布'; + $title = '[询单·公海]新的客户线索【' . $info->order_no . '】发布'; + $content = '[询单·公海]新的客户线索【' . $info->order_no . '】发布'; + $recipient = [$info->recipient]; + $this->pushWs($title, $content, $recipient); + } + + if ($info->status == 2) { + // $notice = '分配'; + $title = '[询单·分配]新的客户线索【' . $info->order_no . '】,请及时处理'; + $content = '[询单·分配]新的客户线索【' . $info->order_no . '】,请及时处理'; + $recipient = [$info->recipient]; + $this->pushWs($title, $content, $recipient); + } } return true; } From adc1d9658d0eac7554e352a66ef79a68ca0d9af0 Mon Sep 17 00:00:00 2001 From: mzl Date: Mon, 25 Aug 2025 15:43:32 +0800 Subject: [PATCH 28/33] ts --- app/inquiry/service/InquiryLogService.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/inquiry/service/InquiryLogService.php b/app/inquiry/service/InquiryLogService.php index 31c161a..8f189ea 100644 --- a/app/inquiry/service/InquiryLogService.php +++ b/app/inquiry/service/InquiryLogService.php @@ -262,7 +262,7 @@ class InquiryLogService */ protected function afterServiceRelease($res) { - RedisQueueRedis::send('inquiry-to-crm', $res); + RedisQueueRedis::send('inquiry-to-crm-new', $res); return $res; } @@ -271,7 +271,7 @@ class InquiryLogService */ protected function afterServiceAllocation($res) { - RedisQueueRedis::send('inquiry-to-crm', $res); + RedisQueueRedis::send('inquiry-to-crm-new', $res); return $res; } @@ -280,7 +280,7 @@ class InquiryLogService */ protected function afterServiceStore($res) { - RedisQueueRedis::send('inquiry-to-crm', $res); + RedisQueueRedis::send('inquiry-to-crm-new', $res); return $res; } @@ -290,7 +290,7 @@ class InquiryLogService protected function afterServiceUpdate($res) { if ($res['recipient']) { - RedisQueueRedis::send('inquiry-to-crm-update', $res); + RedisQueueRedis::send('inquiry-to-crm-new', $res); } return $res; } @@ -300,7 +300,7 @@ class InquiryLogService */ protected function afterServiceReAllocation($res) { - RedisQueueRedis::send('inquiry-to-crm-update', $res); + RedisQueueRedis::send('inquiry-to-crm-new', $res); return $res; } From d6dae82157632bb4413742294ff91db8d417abd8 Mon Sep 17 00:00:00 2001 From: beiman <1696136552@qq.com> Date: Mon, 25 Aug 2025 16:03:34 +0800 Subject: [PATCH 29/33] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=AF=A2=E5=8D=95?= =?UTF-8?q?=E4=B8=8Ecrm=E6=B6=88=E6=81=AF=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/queue/redis/InquiryToCrmNew.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/queue/redis/InquiryToCrmNew.php b/app/queue/redis/InquiryToCrmNew.php index 420fe95..a350c7e 100644 --- a/app/queue/redis/InquiryToCrmNew.php +++ b/app/queue/redis/InquiryToCrmNew.php @@ -61,7 +61,7 @@ class InquiryToCrmNew implements Consumer } } - public function pushWs($title, $content, $recipient) + public function pushWs($title, $content, $recipient = []) { $wsdata = [ 'sender_id' => 0, @@ -84,21 +84,21 @@ class InquiryToCrmNew implements Consumer // 消息推送 if ($info->status == 2 && empty($crmCustomerInfo->user_id)) { // $notice = '分配'; - $title = '[询单·分配]新的客户线索【' . $info->order_no . '】,请及时处理'; - $content = '[询单·分配]新的客户线索【' . $info->order_no . '】,请及时处理'; + $title = '[询单·分配] 新的客户线索【' . $info->order_no . '】,请及时处理'; + $content = '[询单·分配] 新的客户线索【' . $info->order_no . '】,请及时处理'; $recipient = [$info->recipient]; $this->pushWs($title, $content, $recipient); } else { if ($info->status == 2 && $crmCustomerInfo->user_id == $info->recipient) { // $notice = '更新'; - $title = '[询单·更新]客户线索' . $info->order_no . '有新的调整,请及时处理'; - $content = '[询单·更新]客户线索' . $info->order_no . '有新的调整,请及时处理'; + $title = '[询单·更新] 客户线索' . $info->order_no . '有新的调整,请及时处理'; + $content = '[询单·更新] 客户线索' . $info->order_no . '有新的调整,请及时处理'; $recipient = [$info->recipient]; $this->pushWs($title, $content, $recipient); } else { // $notice = '重新分配'; - $title = '[询单·分配]新的客户线索【' . $info->order_no . '】,请及时处理'; - $content = '[询单·分配]新的客户线索【' . $info->order_no . '】,请及时处理'; + $title = '[询单·分配] 新的客户线索【' . $info->order_no . '】,请及时处理'; + $content = '[询单·分配] 新的客户线索【' . $info->order_no . '】,请及时处理'; $recipient = [$info->recipient]; $this->pushWs($title, $content, $recipient); } @@ -145,16 +145,16 @@ class InquiryToCrmNew implements Consumer // 消息推送 if ($info->status == 1) { // $notice = '发布'; - $title = '[询单·公海]新的客户线索【' . $info->order_no . '】发布'; - $content = '[询单·公海]新的客户线索【' . $info->order_no . '】发布'; + $title = '[询单·公海] 新的客户线索【' . $info->order_no . '】发布'; + $content = '[询单·公海] 新的客户线索【' . $info->order_no . '】发布'; $recipient = [$info->recipient]; - $this->pushWs($title, $content, $recipient); + $this->pushWs($title, $content); } if ($info->status == 2) { // $notice = '分配'; - $title = '[询单·分配]新的客户线索【' . $info->order_no . '】,请及时处理'; - $content = '[询单·分配]新的客户线索【' . $info->order_no . '】,请及时处理'; + $title = '[询单·分配] 新的客户线索【' . $info->order_no . '】,请及时处理'; + $content = '[询单·分配] 新的客户线索【' . $info->order_no . '】,请及时处理'; $recipient = [$info->recipient]; $this->pushWs($title, $content, $recipient); } From a9666cfd0fe767261f92a152f58715698781541d Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Mon, 25 Aug 2025 16:04:42 +0800 Subject: [PATCH 30/33] =?UTF-8?q?=E5=B7=A5=E5=8D=95=E6=8F=90=E9=86=92?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/service/ConversationService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ticket/service/ConversationService.php b/app/ticket/service/ConversationService.php index 852fc0c..972938e 100644 --- a/app/ticket/service/ConversationService.php +++ b/app/ticket/service/ConversationService.php @@ -81,7 +81,7 @@ class ConversationService $ticket_status = $locale == 'zh'? Constants::TICKET_STATUS_ZH:Constants::TICKET_STATUS_EN; $content = str_replace($status_fanyi,$ticket_status,$data['content']); - $reply = $content?($content.'\n'.'工单内容:'):'' ; + $reply = $content?($content."\n".'工单内容:'):'' ; } $notice = ['sender_id' => 0, //发送人ID 0默认系统 From 7b280afd72372b86c41bc876353407ac2c39272d Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Mon, 25 Aug 2025 18:13:16 +0800 Subject: [PATCH 31/33] =?UTF-8?q?=E5=AE=A2=E6=88=B7=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/controller/CustomerController.php | 19 +++++++++-- app/crm/controller/FollowController.php | 2 +- app/crm/service/CustomerService.php | 32 +++++++++++++++++++ .../controller/CustomerController.php | 4 +-- app/crmAdmin/service/CustomerService.php | 2 ++ 5 files changed, 53 insertions(+), 6 deletions(-) diff --git a/app/crm/controller/CustomerController.php b/app/crm/controller/CustomerController.php index e51b83f..3b4b9a4 100644 --- a/app/crm/controller/CustomerController.php +++ b/app/crm/controller/CustomerController.php @@ -163,7 +163,7 @@ class CustomerController extends BaseController $perPage = $request->input('size', 10); $where = [ ['user_id', '=', $userId], -// ['corp_abbr', '=', $request->basic_data['corp_abbr']], + //['corp_abbr', '=', $request->basic_data['corp_abbr']], ['follow_next', '<>', 0], ]; @@ -306,6 +306,7 @@ class CustomerController extends BaseController $contacts = Util::getParam($request, 'contacts'); $email = Util::getParam($request, 'email'); $phone = Util::getParam($request, 'phone'); + $whatsApp = Util::getParam($request, 'WhatsApp', ''); if (Util::isEmpty($contacts) && Util::isEmpty($email) && Util::isEmpty($phone)) { return $this->error(trans('contacts_not_null')); } @@ -314,7 +315,7 @@ class CustomerController extends BaseController $corpId = $corpInfo->id; $data = [ 'user_id' => $request->user->uid, - 'corp_abbr' => $corpId, + 'corp_id' => $corpId, 'grade' => Util::getParam($request, 'grade', 0), 'channel_id' => $channel_id, 'country_id' => $country_id, @@ -322,7 +323,7 @@ class CustomerController extends BaseController 'magnitude' => Util::getParam($request, 'magnitude', 0), 'email' => $email, 'phone' => $phone, - 'WhatsApp' => Util::getParam($request, 'WhatsApp', ''), + 'WhatsApp' => $whatsApp, 'contacts' => $contacts, 'describe' => Util::getParam($request, 'describe'), 'lat' => Util::getParam($request, 'lat', 0), @@ -335,6 +336,15 @@ class CustomerController extends BaseController if (Util::isEmpty($id)) { $data['enter_user_id'] = $request->user->uid; + $distinct = [ + 'email' => $email, + 'phone' => $phone, + 'whatsapp' => $whatsApp, + ]; + $dis = (new CustomerService())->distinct($distinct,$corpId); + if($dis){ + return $this->error('客户线索重复,已经有人在跟进'); + } $res = Customer::create($data); } else { unset($data['grade']); @@ -506,12 +516,14 @@ class CustomerController extends BaseController { $customerId = Util::getParam($request, 'customer_id'); $userId = $request->user->uid; + $user = User::where(['id' => $userId])->first(); if (Util::isEmpty($customerId)) { return $this->error(trans('customer_id').trans('not_null')); } $data = [ 'user_id' => $request->user->uid, 'obtain' => time(), + 'corp_id' => $user->corp_id, ]; try { @@ -592,6 +604,7 @@ class CustomerController extends BaseController $data = [ 'user_id' => $user->id, 'obtain' => time(), + 'corp_id' => $user->corp_id, ]; $res = $query->update($data); diff --git a/app/crm/controller/FollowController.php b/app/crm/controller/FollowController.php index 42d6a01..9dc9ae0 100644 --- a/app/crm/controller/FollowController.php +++ b/app/crm/controller/FollowController.php @@ -103,7 +103,7 @@ class FollowController extends BaseController $corpId = $corpInfo->id; $data = [ 'user_id' => $request->user->uid, - 'corp_abbr' => $corpId, + 'corp_id' => $corpId, 'customer_id' => $customer_id, 'type' => $type, 'picture' => $picture, diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index a3559d5..3275d34 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -319,4 +319,36 @@ class CustomerService return $customer; } + + public function distinct($params,$corpId){ + + $customer = Customer::Orwhere('email', '=', $params['email']) + ->Orwhere('phone', '=', $params['phone']) + ->Orwhere('whatsApp', '=', $params['whatsApp']) + ->first(); + if (!empty($customer)) { + //客户重复,消息通知 + $user = User::where('id', $customer->user_id)->first(); + if($corpId != $customer->corp_id){ + return false; + } + $contacts = $customer->contacts??$params['phone']; + $contacts = $contacts??$params['whatsApp']; + $contacts = $contacts??$params['email']; + $data = [ + 'sender_id' => 0, //发送人ID 0默认系统 + 'sender_type' => 'system', //发送者类型 user admin system + 'title' => '客户跟进提醒 - ' . $contacts, //标题 + 'content' => $contacts . '有新的询单,请及时跟进', //内容 + 'scene' => 1, //场景 0系统公共 1系统通知 + 'recipient' => [$customer->user_id], //接收人 空为全部 + 'push_method' => 0 //推送方式0系统推送1手动推送 + ]; + + // 异步推送 + \Webman\RedisQueue\Redis::send('notice', $data); + return true; + } + return false; + } } \ No newline at end of file diff --git a/app/crmAdmin/controller/CustomerController.php b/app/crmAdmin/controller/CustomerController.php index c68bd18..6104fcd 100644 --- a/app/crmAdmin/controller/CustomerController.php +++ b/app/crmAdmin/controller/CustomerController.php @@ -29,7 +29,7 @@ class CustomerController extends BaseController try { $param = $request->all(); $corp = Corp::where(['abbr' => $request->basic_data['corp_abbr']])->first(); - $param['corp_abbr'] = $corp->id; + $param['corp_id'] = $corp->id; // $param['corp_abbr'] = $request->basic_data['corp_abbr']; $result = (new CustomerService())->list($param); @@ -49,7 +49,7 @@ class CustomerController extends BaseController try { $param = $request->all(); // $corp = Corp::where(['abbr' => $request->basic_data['corp_abbr']])->first(); -// $param['corp_abbr'] = $corp->id; +// $param['corp_id'] = $corp->id; $result = (new CustomerService())->export($param); $excelContent = file_get_contents($result['filepath']); diff --git a/app/crmAdmin/service/CustomerService.php b/app/crmAdmin/service/CustomerService.php index 635b6cf..43682e5 100644 --- a/app/crmAdmin/service/CustomerService.php +++ b/app/crmAdmin/service/CustomerService.php @@ -339,6 +339,7 @@ class CustomerService $query= Customer::whereIn('id',explode(',',$customerId)); $customer = $query->get(); + $user = User::where(['id' => $userId])->first(); foreach ($customer as $item) { //记录客户关系变化 @@ -357,6 +358,7 @@ class CustomerService } $data = [ 'user_id' => $userId, + 'corp_id' => $user->corp_id, 'obtain' => time(), ]; $res = $query->update($data); From 958f4448d61dac40ead3c8fd8e0f53f6e627a18c Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Mon, 25 Aug 2025 18:17:14 +0800 Subject: [PATCH 32/33] =?UTF-8?q?=E8=AF=A2=E5=8D=95=E6=8E=A8=E9=80=81?= =?UTF-8?q?=E5=88=B0crm=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/queue/redis/InquiryToCrm.php | 5 ++++- app/queue/redis/InquiryToCrmNew.php | 7 +++++-- app/queue/redis/InquiryToCrmUpdate.php | 7 +++++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/queue/redis/InquiryToCrm.php b/app/queue/redis/InquiryToCrm.php index 4d7ca3e..ad973fd 100644 --- a/app/queue/redis/InquiryToCrm.php +++ b/app/queue/redis/InquiryToCrm.php @@ -41,7 +41,7 @@ class InquiryToCrm implements Consumer // 同步客户数据 $crmCustomer = new CrmCustomer(); $crmCustomer->sn = $info->order_no; - $crmCustomer->corp_abbr = $corpId; + $crmCustomer->corp_id = $corpId; $crmCustomer->user_id = $recipient; $crmCustomer->type = 1; $crmCustomer->channel_id = $info->channel; @@ -54,6 +54,9 @@ class InquiryToCrm implements Consumer $crmCustomer->desc_collect = $info->content; $crmCustomer->enter_user_id = $info->enter_user_id; $crmCustomer->obtain = time(); + if(empty($recipient)){ + $crmCustomer->deleted_at = time(); + } // 修改同步CRM状态 $info->sync_crm = 1; // 保存 diff --git a/app/queue/redis/InquiryToCrmNew.php b/app/queue/redis/InquiryToCrmNew.php index a350c7e..075deda 100644 --- a/app/queue/redis/InquiryToCrmNew.php +++ b/app/queue/redis/InquiryToCrmNew.php @@ -105,7 +105,7 @@ class InquiryToCrmNew implements Consumer } $udata = [ - 'corp_abbr' => $uInfo['corpId'], + 'corp_id' => $uInfo['corpId'], 'user_id' => $info->recipient, 'channel_id' => $info->channel, 'country_id' => $info->country, @@ -122,7 +122,7 @@ class InquiryToCrmNew implements Consumer } else { $crmCustomer = new CrmCustomer(); $crmCustomer->sn = $info->order_no; - $crmCustomer->corp_abbr = $uInfo['corpId']; + $crmCustomer->corp_id = $uInfo['corpId']; $crmCustomer->user_id = $uInfo['recipient']; $crmCustomer->type = 1; $crmCustomer->channel_id = $info->channel; @@ -135,6 +135,9 @@ class InquiryToCrmNew implements Consumer $crmCustomer->desc_collect = $info->content; $crmCustomer->enter_user_id = $info->enter_user_id; $crmCustomer->obtain = time(); + if(empty($uInfo['recipient'])){ + $crmCustomer->deleted_at = time(); + } // 修改同步CRM状态 $info->sync_crm = 1; // 保存 diff --git a/app/queue/redis/InquiryToCrmUpdate.php b/app/queue/redis/InquiryToCrmUpdate.php index c4e6d02..5a5a330 100644 --- a/app/queue/redis/InquiryToCrmUpdate.php +++ b/app/queue/redis/InquiryToCrmUpdate.php @@ -38,7 +38,7 @@ class InquiryToCrmUpdate implements Consumer $crmCustomerInfo = CrmCustomer::where('sn', $info->order_no)->first(); if ($crmCustomerInfo) { $udata = [ - 'corp_abbr' => $corpId, + 'corp_id' => $corpId, 'user_id' => $info->recipient, 'channel_id' => $info->channel, 'country_id' => $info->country, @@ -54,7 +54,7 @@ class InquiryToCrmUpdate implements Consumer // 同步客户数据 $crmCustomer = new CrmCustomer(); $crmCustomer->sn = $info->order_no; - $crmCustomer->corp_abbr = $corpId; + $crmCustomer->corp_id = $corpId; $crmCustomer->user_id = $info->recipient; $crmCustomer->type = 1; $crmCustomer->channel_id = $info->channel; @@ -66,6 +66,9 @@ class InquiryToCrmUpdate implements Consumer $crmCustomer->contacts = $info->contacts; $crmCustomer->desc_collect = $info->content; $crmCustomer->enter_user_id = $info->enter_user_id; + if(empty($info->recipient)){ + $crmCustomer->deleted_at = time(); + } // 修改同步CRM状态 $info->sync_crm = 1; // 保存 From e031c6dbd5899d637b82db5f76e9a9a78f9f7dbe Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Tue, 26 Aug 2025 09:29:46 +0800 Subject: [PATCH 33/33] =?UTF-8?q?=E5=AE=A2=E6=88=B7=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/traits/ResponseTrait.php | 7 +++-- app/crm/controller/CustomerController.php | 23 +++++++++++++-- app/crm/service/CustomerService.php | 36 ++++++++++++++++------- config/route.php | 1 + 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/app/common/traits/ResponseTrait.php b/app/common/traits/ResponseTrait.php index a8adc90..cc20773 100644 --- a/app/common/traits/ResponseTrait.php +++ b/app/common/traits/ResponseTrait.php @@ -18,12 +18,13 @@ trait ResponseTrait */ public function success( $data = [], - string $message = '请求成功', - int $code = 200, + string $message = '请求成功', + string $status = 'success', + int $code = 200, int $httpStatus = 200, array $header = ['Content-Type' => 'application/json'] ): Response { - return $this->result('success', $code, $httpStatus, $message, $data, $header); + return $this->result($status, $code, $httpStatus, $message, $data, $header); } /** diff --git a/app/crm/controller/CustomerController.php b/app/crm/controller/CustomerController.php index 3b4b9a4..104eae5 100644 --- a/app/crm/controller/CustomerController.php +++ b/app/crm/controller/CustomerController.php @@ -339,11 +339,11 @@ class CustomerController extends BaseController $distinct = [ 'email' => $email, 'phone' => $phone, - 'whatsapp' => $whatsApp, + 'whatsApp' => $whatsApp, ]; $dis = (new CustomerService())->distinct($distinct,$corpId); if($dis){ - return $this->error('客户线索重复,已经有人在跟进'); + return $this->success($dis,'客户线索重复,已经业务跟进','distinct'); } $res = Customer::create($data); } else { @@ -361,6 +361,25 @@ class CustomerController extends BaseController } } + /** + * 重复通知 + * @param Request $request + * @return \support\Response + */ + public function distinctNotice(Request $request){ + $customer_id = Util::getParam($request, 'customer_id'); + if (Util::isEmpty($customer_id)) { + return $this->error('客户id不能为空'); + } + $res = (new CustomerService())->distinctNotice($customer_id); + + if ($res) { + return $this->success($res, '成功'); + } else { + return $this->error(trans('fail')); + } + } + /** diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index 3275d34..fc1215c 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -322,24 +322,40 @@ class CustomerService public function distinct($params,$corpId){ - $customer = Customer::Orwhere('email', '=', $params['email']) + $customer = Customer::where('corp_id',$corpId) + ->Orwhere('email', '=', $params['email']) ->Orwhere('phone', '=', $params['phone']) ->Orwhere('whatsApp', '=', $params['whatsApp']) ->first(); if (!empty($customer)) { //客户重复,消息通知 - $user = User::where('id', $customer->user_id)->first(); - if($corpId != $customer->corp_id){ - return false; - } - $contacts = $customer->contacts??$params['phone']; - $contacts = $contacts??$params['whatsApp']; - $contacts = $contacts??$params['email']; +// if($corpId != $customer->corp_id){ +// return false; +// } + return $customer; + } + return false; + } + + /** + * 客户重复通知 + * @param $params + * @param $corpId + * @return Customer|false|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object + */ + public function distinctNotice($customer_id){ + + $customer = Customer::where('id', $customer_id) + ->first(); + if (!empty($customer)) { + $contacts = $customer->contacts??$customer['phone']; + $contacts = $contacts??$customer['whatsApp']; + $contacts = $contacts??$customer['email']; $data = [ 'sender_id' => 0, //发送人ID 0默认系统 'sender_type' => 'system', //发送者类型 user admin system 'title' => '客户跟进提醒 - ' . $contacts, //标题 - 'content' => $contacts . '有新的询单,请及时跟进', //内容 + 'content' => $contacts . ' 产生了新的询单,请及时跟进', //内容 'scene' => 1, //场景 0系统公共 1系统通知 'recipient' => [$customer->user_id], //接收人 空为全部 'push_method' => 0 //推送方式0系统推送1手动推送 @@ -347,7 +363,7 @@ class CustomerService // 异步推送 \Webman\RedisQueue\Redis::send('notice', $data); - return true; + return $customer; } return false; } diff --git a/config/route.php b/config/route.php index 6393f2d..86aa1a0 100644 --- a/config/route.php +++ b/config/route.php @@ -234,6 +234,7 @@ Route::group('/crm', function () { Route::any('/customer/getCountry', [app\crm\controller\CustomerController::class, 'getCountry']); Route::any('/customer/followNext', [app\crm\controller\CustomerController::class, 'followNext']); Route::any('/customer/report', [app\crm\controller\CustomerController::class, 'report']); + Route::any('/customer/distinctNotice', [app\crm\controller\CustomerController::class, 'distinctNotice']); Route::any('/tagGroup/list', [app\crm\controller\TagGroupController::class, 'list']); Route::any('/tagGroup/updatePivot', [app\crm\controller\TagGroupController::class, 'updatePivot']);