摘要:目前網站有兩個用到實時消息推送的功能,源碼最新動態,實時顯示用戶的操作行為消息推送,如重要消息通知,任務指派等等考慮的問題既然要實現即時,那就少不了。
目前網站有兩個用到實時消息推送的功能,源碼:https://github.com/wuzhc/team
最新動態,實時顯示用戶的操作行為
消息推送,如重要消息通知,任務指派等等
考慮的問題既然要實現即時,那就少不了socketio。因為項目是PHP寫的,所以服務端直接用phpsocket.io
我們應該保存離線消息,否則如果用戶不在線,那就接受不到消息。這里我用mongodb來存儲消息。
一是消息不需要關聯表,一條消息一個文檔
二是mongodb適合做海量數據存儲,并且分片也很簡單
三是消息不需要永久存儲,隨著時間推移,消息的價值性越低,可以用固定集合來存儲消息,當數據量達到一定值時覆蓋最久的消息
一個頁面連接一個socket,若用戶同時打開了多個頁面(即一個用戶會對應多個socketID),那么消息應該推送到用戶每一個打開的頁面。一個簡單的做法就是把用戶多個sockID加到group分組(socket.emit("group")),group分組名稱可以是"uid:1",表示userID為1的用戶,然后socket.broadcast.to("uid:1").emit("event_name", data);
就可以實現對多個頁面推送消息
實時動態 實時消息推送 客戶端// 初始化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 == "= commonconfigConf::MSG_HANDLE?>") { var html = "
/** * 消息推送 */ 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等等 |
companyID 用于查詢該公司下的動態
objectType和objectID組合可以查詢某個對象的操作日志,例如一個任務被操作的日志
保存消息參數 | 說明 |
---|---|
senderID | 發送者ID,用于用戶跳轉鏈接 |
companyID | 公司ID,用于該公司下的動態 |
sender | 發送者姓名 |
portrait | 發送者頭像 |
receiverID | 接受者ID |
title | 動態標題,例如創建任務 |
content | 動態內容,例如創建任務的名稱 |
date | 動態時間 |
url | 動態跳轉鏈接 |
typeID | 消息類型,1系統通知,2管理員通知,3操作通知 |
receiverID和typeID和isRead組合可以查詢用戶未讀消息
請求接口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 | 消息跳轉鏈接 |
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/28194.html
摘要:打造你的即時應用二消息推送與監聽年月日接于上篇博客打造你的即時應用一項目初始化構建在上一篇博客中介紹了項目的基本構建現在進入實戰操作一消息推送創建事件類的廣播推送通過來實現下面通過命令來創建一個事件類為了配合我們的廣播系統使用需要實現接 打造你的Laravel即時應用(二)-消息推送與監聽 2019年08月04日20:16:21 XXM 接于上篇博客: 打造你的Laravel即時應用(...
摘要:現在很多網站都通過服務來實現消息推送及數據即時同步功能,即時通訊組件逐漸成為產品的標配。目前國內有很多成熟穩定的第三方即時通訊服務廠家,比如融云。 現在很多網站、APP都通過IM服務來實現消息推送及數據即時同步功能,即時通訊組件逐漸成為產品的標配。目前國內有很多成熟穩定的第三方即時通訊服務廠家,比如:融云。使用這些專業的服務可以提高開發效率而且服務穩定有保障。 如果自己DIY或者需要在...
閱讀 1218·2021-09-30 09:47
閱讀 3770·2021-09-06 15:02
閱讀 1783·2021-09-01 10:46
閱讀 2367·2019-08-30 15:52
閱讀 601·2019-08-29 15:28
閱讀 1875·2019-08-29 15:08
閱讀 1158·2019-08-29 13:28
閱讀 2582·2019-08-29 12:19