diff --git a/app/Http/Middleware/WebApi.php b/app/Http/Middleware/WebApi.php index 136f3c0dc..a0d09a9a6 100644 --- a/app/Http/Middleware/WebApi.php +++ b/app/Http/Middleware/WebApi.php @@ -4,8 +4,10 @@ namespace App\Http\Middleware; @error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING); +use App\Module\Base; use App\Module\Doo; use App\Services\RequestContext; +use Cache; use Closure; class WebApi @@ -29,6 +31,12 @@ class WebApi // 加载Doo类 Doo::load(); + // 记录 PC 端活跃时间 + $userid = Doo::userId(); + if ($userid > 0 && Base::isPc()) { + Cache::put("user_pc_active:{$userid}", time(), 60); + } + // 解密请求内容 $encrypt = Doo::pgpParseStr($request->header('encrypt')); if ($request->isMethod('post')) { diff --git a/app/Models/WebSocket.php b/app/Models/WebSocket.php index 8d0d68727..054a1854b 100644 --- a/app/Models/WebSocket.php +++ b/app/Models/WebSocket.php @@ -10,6 +10,7 @@ namespace App\Models; * @property string $key * @property string|null $fd * @property string|null $path + * @property string|null $platform 平台类型:android, ios, win, mac, web * @property int|null $userid * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at @@ -27,6 +28,7 @@ namespace App\Models; * @method static \Illuminate\Database\Eloquent\Builder|WebSocket whereId($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocket whereKey($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocket wherePath($value) + * @method static \Illuminate\Database\Eloquent\Builder|WebSocket wherePlatform($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocket whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocket whereUserid($value) * @mixin \Eloquent diff --git a/app/Module/Base.php b/app/Module/Base.php index 172de773a..c2cb8797a 100755 --- a/app/Module/Base.php +++ b/app/Module/Base.php @@ -1827,6 +1827,19 @@ class Base return $platform; } + /** + * 是否是PC端(包括 Electron 桌面端和 Web 浏览器) + * @param string|null $platform 平台类型,不传则自动获取 + * @return bool + */ + public static function isPc($platform = null) + { + if ($platform === null) { + $platform = self::platform(); + } + return in_array($platform, ['win', 'mac', 'web']); + } + /** * 是否是App移动端 * @return bool diff --git a/app/Services/WebSocketService.php b/app/Services/WebSocketService.php index 62e96005d..409a2941f 100644 --- a/app/Services/WebSocketService.php +++ b/app/Services/WebSocketService.php @@ -64,7 +64,7 @@ class WebSocketService implements WebSocketHandlerInterface 'ud' => $userid, ], ])); - $this->userOn($fd, $userid); + $this->userOn($fd, $userid, $get['platform']); } else { // 用户不存在 $server->push($fd, Base::array2json([ @@ -105,6 +105,11 @@ class WebSocketService implements WebSocketHandlerInterface // 握手信息 case 'handshake': + // 更新 PC 端活跃时间 + $row = WebSocket::whereFd($frame->fd)->first(); + if ($row && Base::isPc($row->platform)) { + Cache::put("user_pc_active:{$row->userid}", time(), 60); + } break; // 访问状态 @@ -166,17 +171,27 @@ class WebSocketService implements WebSocketHandlerInterface * 用户上线 * @param $fd * @param $userid + * @param $platform * @return void */ - private function userOn($fd, $userid) + private function userOn($fd, $userid, $platform = 'web') { + // 校验平台类型 + if (!in_array($platform, ['android', 'ios', 'win', 'mac', 'web'])) { + $platform = 'web'; + } WebSocket::updateInsert([ 'key' => md5($fd . '@' . $userid) ], [ 'fd' => $fd, 'userid' => $userid, + 'platform' => $platform, ]); OnlineData::online($userid); + // PC 端上线时更新活跃时间 + if (Base::isPc($platform)) { + Cache::put("user_pc_active:{$userid}", time(), 60); + } } /** diff --git a/app/Tasks/PushUmengMsg.php b/app/Tasks/PushUmengMsg.php index b69817e9a..fc6f087d4 100644 --- a/app/Tasks/PushUmengMsg.php +++ b/app/Tasks/PushUmengMsg.php @@ -2,7 +2,10 @@ namespace App\Tasks; use App\Models\UmengAlias; +use App\Models\WebSocketDialogMsgRead; use App\Module\Base; +use Cache; +use Hhxsv5\LaravelS\Swoole\Task\Task; /** * 推送友盟消息 @@ -11,6 +14,7 @@ class PushUmengMsg extends AbstractTask { protected $userid = 0; protected $array = []; + protected $endPush = []; // 需要在 end() 方法中处理的延迟推送列表 /** * @param array|int $userid @@ -32,11 +36,68 @@ class PushUmengMsg extends AbstractTask if ($setting['push'] !== 'open') { return; } - UmengAlias::pushMsgToUserid($this->userid, $this->array); + + // 消息ID + $msgId = isset($this->array['id']) ? intval($this->array['id']) : 0; + + // 处理用户列表 + $userids = is_array($this->userid) ? $this->userid : [$this->userid]; + $directPushUsers = []; // 直接推送的用户 + $delayedPushUsers = []; // 需要延迟推送的用户 + + foreach ($userids as $uid) { + if ($this->getDelay() > 0) { + // 已经延迟过,检查消息是否已读 + if ($msgId > 0) { + $isRead = WebSocketDialogMsgRead::whereMsgId($msgId) + ->whereUserid($uid) + ->whereNotNull('read_at') + ->exists(); + if ($isRead) { + // 已读,跳过推送 + continue; + } + } + // 未读或无法判断,执行推送 + $directPushUsers[] = $uid; + } else { + // 首次推送,检查 PC 端是否活跃 + $lastActive = Cache::get("user_pc_active:{$uid}"); + $isPcActive = $lastActive && (time() - $lastActive) < 60; + + if ($isPcActive) { + // PC 端活跃,需要延迟推送 + $delayedPushUsers[] = $uid; + } else { + // PC 端不活跃,直接推送 + $directPushUsers[] = $uid; + } + } + } + + // 直接推送 + if ($directPushUsers) { + UmengAlias::pushMsgToUserid($directPushUsers, $this->array); + } + + // 创建延迟推送任务 + if ($delayedPushUsers) { + $this->endPush[] = [ + 'userid' => $delayedPushUsers, + 'array' => $this->array, + ]; + } } public function end() { - + if (empty($this->endPush)) { + return; + } + foreach ($this->endPush as $item) { + $task = new PushUmengMsg($item['userid'], $item['array']); + $task->delay(10); + Task::deliver($task); + } } } diff --git a/app/Tasks/WebSocketDialogMsgTask.php b/app/Tasks/WebSocketDialogMsgTask.php index cd00242b9..c1e6e411f 100644 --- a/app/Tasks/WebSocketDialogMsgTask.php +++ b/app/Tasks/WebSocketDialogMsgTask.php @@ -211,6 +211,10 @@ class WebSocketDialogMsgTask extends AbstractTask 'description' => "MID:{$msg->id}", 'seconds' => 3600, 'badge' => 1, + 'extra' => [ + 'dialog_id' => $msg->dialog_id, + 'msg_id' => $msg->id, + ] ]; $this->endArray[] = new PushUmengMsg($uids->toArray(), $umengMsg); } diff --git a/database/migrations/2025_12_05_100000_add_web_sockets_platform.php b/database/migrations/2025_12_05_100000_add_web_sockets_platform.php new file mode 100644 index 000000000..6b0168fc1 --- /dev/null +++ b/database/migrations/2025_12_05_100000_add_web_sockets_platform.php @@ -0,0 +1,33 @@ +string('platform', 20)->nullable()->default('')->after('path')->comment('平台类型:android, ios, win, mac, web'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('web_sockets', function (Blueprint $table) { + $table->dropColumn('platform'); + }); + } +} + diff --git a/resources/assets/js/store/actions.js b/resources/assets/js/store/actions.js index 2a55881a8..b39e0de04 100644 --- a/resources/assets/js/store/actions.js +++ b/resources/assets/js/store/actions.js @@ -4484,7 +4484,7 @@ export default { let url = $A.mainUrl('ws'); url = url.replace("https://", "wss://"); url = url.replace("http://", "ws://"); - url += `?action=web&token=${state.userToken}&language=${languageName}`; + url += `?action=web&token=${state.userToken}&language=${languageName}&platform=${$A.Platform}`; // const wgLog = $A.openLog; const wsRandom = $A.randomString(16);