目前網站有兩個用到實時消息推送的功能,源碼:https://github.com/wuzhc/teamjavascript
咱們應該保存離線消息,不然若是用戶不在線,那就接受不到消息。這裏我用mongodb來存儲消息。php
就能夠實現對多個頁面推送消息html
// 初始化io對象 var socket = io('http://' + document.domain + ':2120'); // 當socket鏈接後發送登陸請求 socket.on('connect', function () { socket.emit('login', { uid: "<?=Yii::$app->user->id?>", companyID: "<?=Yii::$app->user->identity->fdCompanyID?>" }); }); // 實時消息,當服務端推送來消息時觸發 socket.on('new_msg', function (msg) { if (msg.typeID == '<?= \common\config\Conf::MSG_HANDLE?>') { var html = '<li>' + '<a href="' + msg.url + '">' + '<div class="pull-left">' + '<img src="<?= $directoryAsset ?>/img/user2-160x160.jpg" class="img-circle" alt="User Image"/>' + '</div>' + '<h4 style="overflow:hidden;text-overflow:ellipsis;">' + msg.title + '</h4>' + '<p style="overflow:hidden;text-overflow:ellipsis;">' + msg.content + '</p>' + '</a>' + '</li>'; $('#msg-handle').prepend(html); var num = $('.msg-handle-num').eq(1).text() * 1; $('.msg-handle-num').text(num + 1); } }); // 實時動態,當服務端推送來消息時觸發 socket.on('update_dynamic', function (msg) { var html = '<li>' + '<i class="portrait">' + '<img class="img-circle img-bordered-sm portrait-img" src="' + msg.portrait + '" alt="User Image"></i>' + '<div class="timeline-item">' + '<span class="time">' + '<i class="fa fa-clock-o"></i>' + msg.date + '</span>' + '<h3 class="timeline-header">' + '<a href="' + msg.url + '">' + msg.operator + '</a>' + '<span class="desc">' + msg.title + '</span>' + '</h3>' + '<div class="timeline-body">' + msg.content + '</div>' + '</div>' + '</li>'; $('#dynamic-list').prepend(html); }); // 在線人數 socket.on('update_online_count', function (msg) { $('#online-people').html(msg); });
/** * 消息推送 */ public function actionStart() { // PHPSocketIO服務 $io = new SocketIO(2120); // 客戶端發起鏈接事件時,設置鏈接socket的各類事件回調 $io->on('connection', function ($socket) use ($io) { /** @var Connection $redis */ $redis = Yii::$app->redis; // 當客戶端發來登陸事件時觸發 $socket->on('login', function ($data) use ($socket, $redis, $io) { $uid = isset($data['uid']) ? $data['uid'] : null; $companyID = isset($data['companyID']) ? $data['companyID'] : null; // uid和companyID應該作下加密 by wuzhc 2018-01-28 if (empty($uid) || empty($companyID)) { return ; } $this->companyID = $companyID; // 已經登陸過了 if (isset($socket->uid)) { return; } $key = 'socketio:company:' . $companyID; $field = 'uid:' . $uid; // 用hash方便統計用戶打開頁面數量 if (!$redis->hget($key, $field)) { $redis->hset($key, $field, 0); } // 同個用戶打開新頁面時加1 $redis->hincrby($key, $field, 1); // 加入uid分組,方便對同個用戶的全部打開頁面推送消息 $socket->join('uid:'. $uid); // 加入companyID,方便對整個公司的全部用戶推送消息 $socket->join('company:'.$companyID); $socket->uid = $uid; $socket->companyID = $companyID; // 整個公司在線人數更新 $io->to('company:'.$companyID)->emit('update_online_count', $redis->hlen($key)); }); // 當客戶端斷開鏈接是觸發(通常是關閉網頁或者跳轉刷新致使) $socket->on('disconnect', function () use ($socket, $redis, $io) { if (!isset($socket->uid)) { return; } $key = 'socketio:company:' . $socket->companyID; $field = 'uid:' . $socket->uid; $redis->hincrby($key, $field, -1); if ($redis->hget($key, $field) <= 0) { $redis->hdel($key, $field); // 某某下線了,刷新整個公司的在線人數 $io->to('company:'.$socket->companyID)->emit('update_online_count', $redis->hlen($key)); } }); }); // 開始進程時,監聽2121端口,用戶數據推送 $io->on('workerStart', function () use ($io) { /** @var Connection $redis */ $redis = Yii::$app->redis; $httpWorker = new Worker('http://0.0.0.0:2121'); // 當http客戶端發來數據時觸發 $httpWorker->onMessage = function ($conn, $data) use ($io, $redis) { $_POST = $_POST ? $_POST : $_GET; switch (@$_POST['action']) { case 'message': $to = 'uid:' . @$_POST['receiverID']; $_POST['content'] = htmlspecialchars(@$_POST['content']); $_POST['title'] = htmlspecialchars(@$_POST['title']); // 有指定uid則向uid所在socket組發送數據 if ($to) { $io->to($to)->emit('new_msg', $_POST); } $companyID = @$_POST['companyID']; $key = 'socketio:company:' . $companyID; $field = 'uid:' . $to; // http接口返回,若是用戶離線socket返回fail if ($to && $redis->hget($key, $field)) { return $conn->send('offline'); } else { return $conn->send('ok'); } break; case 'dynamic': $to = 'company:' . @$_POST['companyID']; $_POST['content'] = htmlspecialchars(@$_POST['content']); $_POST['title'] = htmlspecialchars(@$_POST['title']); // 有指定uid則向uid所在socket組發送數據 if ($to) { $io->to($to)->emit('update_dynamic', $_POST); } $companyID = @$_POST['companyID']; $key = 'socketio:company:' . $companyID; // http接口返回,若是用戶離線socket返回fail if ($to && $redis->hlen($key)) { return $conn->send('offline'); } else { return $conn->send('ok'); } } return $conn->send('fail'); }; // 執行監聽 $httpWorker->listen(); }); // 運行全部的實例 global $argv; array_shift($argv); if (isset($argv[2]) && $argv[2] == 'daemon') { $argv[2] = '-d'; } Worker::runAll(); }
/** * 新建任務 * @param $projectID * @param $categoryID * @return string * @throws ForbiddenHttpException * @since 2018-01-26 */ public function actionCreate($projectID, $categoryID) { $userID = Yii::$app->user->id; if (!Yii::$app->user->can('createTask')) { throw new ForbiddenHttpException(ResponseUtil::$msg[1]); } if ($data = Yii::$app->request->post()) { if (empty($data['name'])) { ResponseUtil::jsonCORS(['status' => Conf::FAILED, 'msg' => '任務標題不能爲空']); } // 保存任務 $taskID = TaskService::factory()->save([ 'name' => $data['name'], 'creatorID' => $userID, 'companyID' => $this->companyID, 'level' => $data['level'], 'categoryID' => $categoryID, 'projectID' => $projectID, 'content' => $data['content'] ]); if ($taskID) { $url = Url::to(['task/view', 'taskID' => $taskID]); $portrait = UserService::factory()->getUserPortrait($userID); $username = UserService::factory()->getUserName($userID); $title = '建立了新任務'; $content = $data['name']; // 保存操做日誌 LogService::factory()->saveHandleLog([ 'objectID' => $taskID, 'companyID' => $this->companyID, 'operatorID' => $userID, 'objectType' => Conf::OBJECT_TASK, 'content' => $content, 'url' => $url, 'title' => $title, 'portrait' => $portrait, 'operator' => $username ]); // 動態推送 MsgService::factory()->push('dynamic', [ 'companyID' => $this->companyID, 'operatorID' => $userID, 'operator' => $username, 'portrait' => $portrait, 'title' => $title, 'content' => $content, 'url' => $url, 'date' => date('Y-m-d H:i:s'), ]); ResponseUtil::jsonCORS(null, Conf::SUCCESS, '建立成功'); } else { ResponseUtil::jsonCORS(null, Conf::FAILED, '建立失敗'); } } else { return $this->render('create', [ 'projectID' => $projectID, 'category' => TaskCategory::findOne(['id' => $categoryID]), ]); } }
LogService::factory()->saveHandleLog($args)
參數 | 說明 |
---|---|
operatorID | 操做者ID,用於用戶跳轉連接 |
companyID | 公司ID,用於該公司下的動態 |
operator | 操做者姓名 |
portrait | 操做者頭像 |
receiver | 接受者,可選 |
title | 動態標題,例如建立任務 |
content | 動態內容,例如建立任務的名稱 |
date | 動態時間 |
url | 動態跳轉連接 |
token | 驗證 |
objectType | 對象類型,例如任務,文檔等等 |
objectID | 對象ID,例如任務ID,文檔ID等等 |
參數 | 說明 |
---|---|
senderID | 發送者ID,用於用戶跳轉連接 |
companyID | 公司ID,用於該公司下的動態 |
sender | 發送者姓名 |
portrait | 發送者頭像 |
receiverID | 接受者ID |
title | 動態標題,例如建立任務 |
content | 動態內容,例如建立任務的名稱 |
date | 動態時間 |
url | 動態跳轉連接 |
typeID | 消息類型,1系統通知,2管理員通知,3操做通知 |
MsgService::factory()->push($action, $args)
參數 | 說明 |
---|---|
operatorID | 操做者ID,用於用戶跳轉連接 |
companyID | 公司ID,用於給該公司的全部socket推送 |
operator | 操做者姓名 |
portrait | 操做者頭像 |
receiver | 接受者,可選 |
title | 動態標題,例如建立任務 |
content | 動態內容,例如建立任務的名稱 |
date | 動態時間 |
url | 動態跳轉連接 |
參數 | 說明 |
---|---|
senderID | 發送者ID,用於用戶跳轉連接 |
sender | 發送者姓名 |
receiverID | 接受者ID,用於給接受者的全部socket推送消息 |
typeID | 消息類型,1系統通知,2管理員通知,3操做通知 |
portrait | 發送者頭像 |
title | 消息標題 |
content | 消息內容 |
url | 消息跳轉連接 |