摘要:目前網(wǎng)站有兩個用到實時消息推送的功能,源碼最新動態(tài),實時顯示用戶的操作行為消息推送,如重要消息通知,任務指派等等考慮的問題既然要實現(xiàn)即時,那就少不了。
目前網(wǎng)站有兩個用到實時消息推送的功能,源碼:https://github.com/wuzhc/team
最新動態(tài),實時顯示用戶的操作行為
消息推送,如重要消息通知,任務指派等等
考慮的問題既然要實現(xiàn)即時,那就少不了socketio。因為項目是PHP寫的,所以服務端直接用phpsocket.io
我們應該保存離線消息,否則如果用戶不在線,那就接受不到消息。這里我用mongodb來存儲消息。
一是消息不需要關(guān)聯(lián)表,一條消息一個文檔
二是mongodb適合做海量數(shù)據(jù)存儲,并且分片也很簡單
三是消息不需要永久存儲,隨著時間推移,消息的價值性越低,可以用固定集合來存儲消息,當數(shù)據(jù)量達到一定值時覆蓋最久的消息
一個頁面連接一個socket,若用戶同時打開了多個頁面(即一個用戶會對應多個socketID),那么消息應該推送到用戶每一個打開的頁面。一個簡單的做法就是把用戶多個sockID加到group分組(socket.emit("group")),group分組名稱可以是"uid:1",表示userID為1的用戶,然后socket.broadcast.to("uid:1").emit("event_name", data);
就可以實現(xiàn)對多個頁面推送消息
實時動態(tài) 實時消息推送 客戶端 // 初始化io對象
var socket = io("http://" + document.domain + ":2120");
// 當socket連接后發(fā)送登錄請求
socket.on("connect", function () {
socket.emit("login", {
uid: "=Yii::$app->user->id?>",
companyID: "=Yii::$app->user->identity->fdCompanyID?>"
});
});
// 實時消息,當服務端推送來消息時觸發(fā)
socket.on("new_msg", function (msg) {
if (msg.typeID == "= commonconfigConf::MSG_HANDLE?>") {
var html = " /**
* 消息推送
*/
public function actionStart()
{
// PHPSocketIO服務
$io = new SocketIO(2120);
// 客戶端發(fā)起連接事件時,設置連接socket的各種事件回調(diào)
$io->on("connection", function ($socket) use ($io) {
/** @var Connection $redis */
$redis = Yii::$app->redis;
// 當客戶端發(fā)來登錄事件時觸發(fā)
$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;
// 已經(jīng)登錄過了
if (isset($socket->uid)) {
return;
}
$key = "socketio:company:" . $companyID;
$field = "uid:" . $uid;
// 用hash方便統(tǒng)計用戶打開頁面數(shù)量
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;
// 整個公司在線人數(shù)更新
$io->to("company:".$companyID)->emit("update_online_count", $redis->hlen($key));
});
// 當客戶端斷開連接是觸發(fā)(一般是關(guān)閉網(wǎng)頁或者跳轉(zhuǎn)刷新導致)
$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);
// 某某下線了,刷新整個公司的在線人數(shù)
$io->to("company:".$socket->companyID)->emit("update_online_count", $redis->hlen($key));
}
});
});
// 開始進程時,監(jiān)聽2121端口,用戶數(shù)據(jù)推送
$io->on("workerStart", function () use ($io) {
/** @var Connection $redis */
$redis = Yii::$app->redis;
$httpWorker = new Worker("http://0.0.0.0:2121");
// 當http客戶端發(fā)來數(shù)據(jù)時觸發(fā)
$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組發(fā)送數(shù)據(jù)
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組發(fā)送數(shù)據(jù)
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");
};
// 執(zhí)行監(jiān)聽
$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 = "創(chuàng)建了新任務";
$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
]);
// 動態(tài)推送
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, "創(chuàng)建成功");
} else {
ResponseUtil::jsonCORS(null, Conf::FAILED, "創(chuàng)建失敗");
}
} else {
return $this->render("create", [
"projectID" => $projectID,
"category" => TaskCategory::findOne(["id" => $categoryID]),
]);
}
}
調(diào)用保存操作日志底層接口
LogService::factory()->saveHandleLog($args)參數(shù)說明
| 參數(shù) | 說明 |
|---|---|
| operatorID | 操作者ID,用于用戶跳轉(zhuǎn)鏈接 |
| companyID | 公司ID,用于該公司下的動態(tài) |
| operator | 操作者姓名 |
| portrait | 操作者頭像 |
| receiver | 接受者,可選 |
| title | 動態(tài)標題,例如創(chuàng)建任務 |
| content | 動態(tài)內(nèi)容,例如創(chuàng)建任務的名稱 |
| date | 動態(tài)時間 |
| url | 動態(tài)跳轉(zhuǎn)鏈接 |
| token | 驗證 |
| objectType | 對象類型,例如任務,文檔等等 |
| objectID | 對象ID,例如任務ID,文檔ID等等 |
companyID 用于查詢該公司下的動態(tài)
objectType和objectID組合可以查詢某個對象的操作日志,例如一個任務被操作的日志
保存消息| 參數(shù) | 說明 |
|---|---|
| senderID | 發(fā)送者ID,用于用戶跳轉(zhuǎn)鏈接 |
| companyID | 公司ID,用于該公司下的動態(tài) |
| sender | 發(fā)送者姓名 |
| portrait | 發(fā)送者頭像 |
| receiverID | 接受者ID |
| title | 動態(tài)標題,例如創(chuàng)建任務 |
| content | 動態(tài)內(nèi)容,例如創(chuàng)建任務的名稱 |
| date | 動態(tài)時間 |
| url | 動態(tài)跳轉(zhuǎn)鏈接 |
| typeID | 消息類型,1系統(tǒng)通知,2管理員通知,3操作通知 |
receiverID和typeID和isRead組合可以查詢用戶未讀消息
請求接口MsgService::factory()->push($action, $args)即時動態(tài)
| 參數(shù) | 說明 |
|---|---|
| operatorID | 操作者ID,用于用戶跳轉(zhuǎn)鏈接 |
| companyID | 公司ID,用于給該公司的所有socket推送 |
| operator | 操作者姓名 |
| portrait | 操作者頭像 |
| receiver | 接受者,可選 |
| title | 動態(tài)標題,例如創(chuàng)建任務 |
| content | 動態(tài)內(nèi)容,例如創(chuàng)建任務的名稱 |
| date | 動態(tài)時間 |
| url | 動態(tài)跳轉(zhuǎn)鏈接 |
| 參數(shù) | 說明 |
|---|---|
| senderID | 發(fā)送者ID,用于用戶跳轉(zhuǎn)鏈接 |
| sender | 發(fā)送者姓名 |
| receiverID | 接受者ID,用于給接受者的所有socket推送消息 |
| typeID | 消息類型,1系統(tǒng)通知,2管理員通知,3操作通知 |
| portrait | 發(fā)送者頭像 |
| title | 消息標題 |
| content | 消息內(nèi)容 |
| url | 消息跳轉(zhuǎn)鏈接 |
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/28194.html
摘要:打造你的即時應用二消息推送與監(jiān)聽年月日接于上篇博客打造你的即時應用一項目初始化構(gòu)建在上一篇博客中介紹了項目的基本構(gòu)建現(xiàn)在進入實戰(zhàn)操作一消息推送創(chuàng)建事件類的廣播推送通過來實現(xiàn)下面通過命令來創(chuàng)建一個事件類為了配合我們的廣播系統(tǒng)使用需要實現(xiàn)接 打造你的Laravel即時應用(二)-消息推送與監(jiān)聽 2019年08月04日20:16:21 XXM 接于上篇博客: 打造你的Laravel即時應用(...
摘要:現(xiàn)在很多網(wǎng)站都通過服務來實現(xiàn)消息推送及數(shù)據(jù)即時同步功能,即時通訊組件逐漸成為產(chǎn)品的標配。目前國內(nèi)有很多成熟穩(wěn)定的第三方即時通訊服務廠家,比如融云。 現(xiàn)在很多網(wǎng)站、APP都通過IM服務來實現(xiàn)消息推送及數(shù)據(jù)即時同步功能,即時通訊組件逐漸成為產(chǎn)品的標配。目前國內(nèi)有很多成熟穩(wěn)定的第三方即時通訊服務廠家,比如:融云。使用這些專業(yè)的服務可以提高開發(fā)效率而且服務穩(wěn)定有保障。 如果自己DIY或者需要在...
閱讀 1274·2021-09-30 09:47
閱讀 3839·2021-09-06 15:02
閱讀 1852·2021-09-01 10:46
閱讀 2431·2019-08-30 15:52
閱讀 699·2019-08-29 15:28
閱讀 1930·2019-08-29 15:08
閱讀 1224·2019-08-29 13:28
閱讀 2627·2019-08-29 12:19