ratacibernetica / yii2-node-socket
这是 oncesk/yii-node-socket 的分支。
Requires
This package is not auto-updated.
Last update: 2024-09-14 18:58:22 UTC
README
####免责声明:此分支正在进行开发以使其与 Yii2 兼容。截至目前,我还没有检查订阅功能,许多代码部分已被重写并在一个项目中进行了测试,所以我认为这并不 即插即用。
请注意。
Yii2 Node Socket
连接 php、javascript 和 nodejs 以在 Yii 应用程序中。
####Yii1
嗨,如果您需要使用 yii1,您可以从 (https://github.com/oncesk/yii-node-socket/tree/2.0.0) 开始。
####您可以做什么
- 向所有客户端或具体房间或具体通道发送事件(s)
- 现在您可以按用户 ID 向单个(具体用户)发送事件!
- 在 window 上下文中调用某些函数或对象方法
- 您可以使用 jQuery 从 php 更改 DOM 模型
- 在您的 JavaScript 应用程序中设置数据并获取它
- 从 JavaScript 向所有客户端或具体房间或通道发送事件
##更改
- 为 Yii 2.0 更新
- 添加命名空间 更新为 Yii 2.0
- Composer 设置更新以与 Yii 扩展更新一起工作
##要求
- linux/unix/windows
- git
- vps 或专用服务器(用于 nodejs 进程)
- curl 必须安装或启用
#安装
安装 nodejs,如果尚未安装,请参阅 https://node.org.cn/
安装扩展
- Composer
composer require ratacibernetica/yii2-node-socket
- 使用 git clone
$> git clone https://github.com/ratacibernetica/yii2-node-socket.git
现在转到您安装扩展的文件夹 vendors/ratacibernetica/yii2-node-socket 并执行
$> git submodule init $> git submodule update
Yii2 配置
- 在 (console/config/main.php) 中配置控制台命令。您可以使用以下配置
... 'controllerMap' => [ 'node-socket' => '\YiiNodeSocket\NodeSocketCommand', ], ... 'components' => [ 'nodeSocket' => [ 'class' => '\YiiNodeSocket\NodeSocket', 'host' => 'localhost', 'allowedServerAddresses' => [ "localhost", "127.0.0.1" ], ], ],
- 注册 Yii 组件,需要添加到 frontend/config/main.php 中的前端应用程序
'nodeSocket' => [ 'class' => '\YiiNodeSocket\NodeSocket', 'dbOptions' => '', 'host' => 'localhost', 'allowedServerAddresses' => [ "localhost", "127.0.0.1" ], 'origin' => '*:*', 'sessionVarName' => 'PHPSESSID', 'port' => 3001, 'socketLogFile' => '/var/log/node-socket.log', ],
注意: host 应该是域名,如您的虚拟主机配置中的域名或服务器 IP 地址,如果您使用 IP 地址请求页面
- 在 (common/config/main.php) 中的 common 配置中配置别名。
- 第一个是为 Yii 找到 PHP 命名空间,第二个是为找到 JS 资产。
- 您可以使用以下配置
'aliases' => [ '@YiiNodeSocket' => '@vendor/ratacibernetica/yii2-node-socket/lib/php', '@nodeWeb' => '@vendor/ratacibernetica/yii2-node-socket/lib/js', '@console'=>dirname(dirname(__DIR__)) . '/console', ],
注意:如果您将使用 behaviors 或 node-socket 模型,您需要在 preload 组件列表中添加 nodeSocket 组件
'bootstrap' => ['log', 'nodeSocket'],
在 application.ext.yii-node-socket.lib.js.server 中安装 nodejs 组件
$> npm install
恭喜,安装完成!
注意:如果组件名称不是 nodeSocket,您需要在控制台命令中使用特殊密钥 --componentName=component_name
注意:JS 很可能不会自动加载,因此我们必须在视图布局中注册 YiiSocketNode Assets。
use yii\helpers\Html; use yii\bootstrap\Nav; use yii\bootstrap\NavBar; use kartik\form\ActiveForm; use frontend\assets\AppAsset; use kartik\widgets\AlertBlock; /* @var $this \yii\web\View */ /* @var $content string */ \YiiNodeSocket\Assets\NodeSocketAssets::register($this); AppAsset::register($this); ?> <?php $this->beginPage() ?> <!DOCTYPE html> <html lang="<?= Yii::$app->language ?>"> <head> ...
如下所示。
###控制台命令操作(作为管理员用户)
使用 (./yii node-socket/)
$> ./yii node-socket/start # start server $> ./yii node-socket/stop # stop server $> ./yii node-socket/restart # restart server $> ./yii node-socket/get-pid # show pid of nodejs process
##定义
- Frame - 将 Class 包装的数据包用于 nodejs 服务器。对于每个请求到 nodejs 服务器的请求,您只能发送 1 个 Frame。要同时发送多个 Frame,请使用 Multiple frame。
- room - 在具体命名空间中的一个或多个客户端:每个客户端都可以创建房间,其他客户端可以加入具体房间,房间中的任何客户端都可以在此房间中发送事件。
###关于 Yii2 的用户模型
我们需要遵循以下步骤来向特定用户发送事件
/** * This is the model class for table "usuer". */ ... const EVENT_BEFORE_LOGIN = 'beforeLogin'; const EVENT_BEFORE_LOGOUT = 'beforeLogout'; const EVENT_AFTER_LOGIN = 'afterLogin'; ... /** * Crea un único Frame para autorizar al usuario * y recibir notificaciones exclusivas para él. * @param type $fromCookie */ protected function afterLogin($fromCookie) { $frame = \Yii::$app->nodeSocket->getFrameFactory()->createAuthenticationFrame(); $frame->setUserId(\Yii::$app->user->id); $frame->send(); } public function beforeLogin() { } public function beforeLogout() { }
#### 注册这些事件
因为Yii2不再支持这些事件,我们需要添加它们。
所以我创建了一个名为 "AppBootstrap" 的文件。
<?php namespace frontend\bootstraps; use yii\base\BootstrapInterface; class AppBootstrap implements BootstrapInterface{ public function bootstrap($app){ $app->user->on(\common\models\Usuario::EVENT_BEFORE_LOGIN,['common\models\Usuario', 'beforeLogin']); $app->user->on(\common\models\Usuario::EVENT_AFTER_LOGIN,['common\models\Usuario', 'afterLogin']); $app->user->on(\common\models\Usuario::EVENT_BEFORE_LOGOUT,['common\models\Usuario', 'beforeLogout']); } }
并将它添加到了 frontend/config/main.php 中。
'bootstrap' => ['log','nodeSocket','frontend\bootstraps\AppBootstrap'],
所以,我们只是将已登录的用户订阅到节点的服务(或者类似的东西,不确定)。
### 事件
### 在JavaScript中工作
使用 YiiNodeSocket
类
#### 开始工作
// create object var socket = new YiiNodeSocket(); // enable debug mode socket.debug(true); socket.onConnect(function () { // fire when connection established }); socket.onDisconnect(function () { // fire when connection close or lost }); socket.onConnecting(function () { // fire when the socket is attempting to connect with the server }); socket.onReconnect(function () { // fire when successfully reconnected to the server });
#### 捕获事件
现在事件只能在PHP端创建。所有数据都以json格式传输。在回调函数中,数据被粘贴为JavaScript原生对象(或字符串,整数,取决于您的PHP框架配置)。
// add event listener socket.on('updateBoard', function (data) { // do any action });
#### 房间
socket.onConnect(function () { socket.room('testRoom').join(function (success, numberOfRoomSubscribers) { // success - boolean, numberOfRoomSubscribers - number of room members // if error occurred then success = false, and numberOfRoomSubscribers - contains error message if (success) { console.log(numberOfRoomSubscribers + ' clients in room: ' + roomId); // do something // bind events this.on('join', function (newMembersCount) { // fire on client join }); this.on('data', function (data) { // fire when server send frame into this room with 'data' event }); } else { // numberOfRoomSubscribers - error message alert(numberOfRoomSubscribers); } }); });
#### 通道
通道与房间非常相似,但您可以控制客户端对通道的访问。
// join to channel, join needed when you try subscribe to channel from javascript, if you subscribed to channel in php you can bind events without join socket.onConnect(function () { var testChannel = socket.channel('test').join(function (success) { // success - boolean if (success) { // fore getting channel attributes console.log(this.getAttributes()); // bind event listeners this.on('some_event', function (data) { // fire when server send frame into this room with 'data' event }); } else { console.log(this.getError()); } }); // you can bind events handlers for some events without join // in this case you should be subscribed to `test` channel socket.channel('test').on('some_event', function (data) { }); });
#### 发射事件
您可以向以下对象发射事件:
- 所有客户端
- 特定房间的客户端
全局事件
socket.emit('global.event', { message : { id : 12, title : 'This is a test message' } }); socket.on('global.event', function (data) { console.log(data.message.title); // you will see in console `This is a test message` });
房间事件
socket.onConnect(function () { var testRoom = socket.room('testRoom').join(function (success, numberOfRoomSubscribers) { // success - boolean, numberOfRoomSubscribers - number of room members // if error occurred then success = false, and numberOfRoomSubscribers - contains error message if (success) { console.log(numberOfRoomSubscribers + ' clients in room: ' + roomId); // do something // bind events this.on('message', function (message) { console.log(message); }); this.on('ping', function () { console.log('Ping!'); }); this.emit('ping'); // emit ping event } else { // numberOfRoomSubscribers - error message alert(numberOfRoomSubscribers); } }); // emit message event testRoom.emit('message', { message : { id : 12, title : 'This is a test message' } }); });
#### 共享公共数据
您只能使用PublicData框架从PHP设置共享数据(请参阅下面的PHP部分)。要访问数据,您可以使用 getPublicData(string key, callback fn)
方法。
socket.getPublicData('error.strings', function (strings) { // you need to check if strings exists, because strings can be not setted or expired, if (strings) { // do something } });
## PHP
#### 行为
- YiiNodeSocket\Behaviors\ArChannel - 可以用来创建新通道。例如:您可以将此行为附加到User,结果任何用户都将有自己的通道,其他用户可以订阅到特定用户的事件。
- YiiNodeSocket\Behaviors\ArSubscriber - 应该附加到可以订阅某些通道的对象。例如:模型User在一段时间内可以是通道和订阅者。
/** * * @method \YiiNodeSocket\Models\Channel getChannel() * @method \YiiNodeSocket\Frames\Event|null createEvent($name) */ class User extends CActiveRecord { ... public function behaviors() { return array( // attach channel behavior 'channel' => array( 'class' => '\YiiNodeSocket\Behaviors\ArChannel', 'updateOnSave' => true ), // attach subscriber behavior 'subscriber' => array( 'class' => '\YiiNodeSocket\Behaviors\ArSubscriber' ) ); } ... } // Example of subscribe $user1 = new User(); $user1->setAttributes($attributes); if ($user1->save()) { // imagine that $user1->id == 122 // user channel was created and sent to nodejs server // subscriber was created and sent to nodejs server // create second user $user2 = User::model()->findByPk(121); // now we can subscribe one user to second user $user1->subscribe($user2); // and $user2 can catch events from $user1 channel like in twitter } // Example of emit event in concrete channel $user = User::model()->findByPk(122); if ($user) { // First method $event = $user->createEvent('test_event'); if ($event) { // set event data $event->setData(array( 'black', 'red', 'white' )); // send event to user channel $event->send(); } // Second method with getting channel $channel = $user->getChannel(); if ($channel) { $event = $channel->createEvent('test_event'); // set event data $event->setData(array( 'black', 'red', 'white' )); // send event to user channel $event->send(); } } // Example of unsubscribe $user1 = User::model()->findByPk(122); $user2 = User::model()->findByPk(121); $user1->unSubscribe($user2); // now $user2 can not catch events in channel of $user1
#### 客户端授权
为了授权客户端,您需要发送特殊的Authentication帧,您可以在您的 user 组件的 afterLogin 事件中这样做。
protected function afterLogin($fromCookie) { parent::afterLogin($fromCookie); $frame = Yii::app()->nodeSocket->getFrameFactory()->createAuthenticationFrame(); $frame->setUserId($this->getId()); $frame->send(); }
之后,此用户只能接收到为他生成的事件。请参见下面的示例,只向单个(具体)$user 发送事件
// $user - the user model, which can receive this event $event = Yii::app()->nodeSocket->getFrameFactory()->createUserEventFrame(); $event->setUserId($user->id); $event->setEventName('message'); $event['text'] = 'Hello, how are you?'; $event->send();
和您的javascript
var socket = new YiiNodeSocket(); socket.on('message', function (message) { console.log(message.text); // render message });
#### 注册客户端脚本
public function actionIndex() { ... Yii::app()->nodeSocket->registerClientScripts(); ... }
#### 事件帧
... // create event frame $frame = Yii::app()->nodeSocket->getFrameFactory()->createEventFrame(); // set event name $frame->setEventName('updateBoard'); // set data using ArrayAccess interface $frame['boardId'] = 25; $frame['boardData'] = $html; // or you can use setData(array $data) method // setData overwrite data setted before $frame->send(); ...
#### 设置共享数据
您可以使用 setLifeTime(integer $lifetime) 方法为PublicData类设置过期时间。
... // create frame $frame = Yii::app()->nodeSocket->getFrameFactory()->createPublicDataFrame(); // set key in storage $frame->setKey('error.strings'); // set data $frame->setData($errorStrings); // you can set data via ArrayAccess interface // $frame['empty_name'] = 'Please enter name'; // set data lifetime $frame->setLifeTime(3600*2); // after two hours data will be deleted from storage // send $frame->send(); ...
#### 房间事件
... // create frame $frame = Yii::app()->nodeSocket->getFrameFactory()->createEventFrame(); // set event name $frame->setEventName('updateBoard'); // set room name $frame->setRoom('testRoom'); // set data $frame['key'] = $value; // send $frame->send(); ...
只有testRoom的成员可以捕获此事件。
#### 调用客户端函数或方法
在您的PHP应用程序中,您可以在window上下文中调用javascript函数或对象的方法。
$invokeFrame = Yii::app()->nodeSocket->getFrameFactory()->createInvokeFrame(); $invokeFrame->invokeFunction('alert', array('Hello world')); $invokeFrame->send(); // alert will be showed on all clients
扩展自Event框架 => 您可以将其发送到特定房间
#### 使用jQuery进行DOM操作
任务:您需要在每个产品的价格更新后更新客户端的价格。
... $product = Product::model()->findByPk($productId); if ($product) { $product->price = $newPrice; if ($product->save()) { $jFrame = Yii::app()->nodeSocket->getFrameFactory()->createJQueryFrame(); $jFrame ->createQuery('#product' . $product->id) ->find('span.price') ->text($product->price); $jFrame->send(); // and all connected clients will can see updated price } } ...
#### 同时发送多个帧
示例 1
$multipleFrame = Yii::app()->nodeSocket->getFrameFactory()->createMultipleFrame(); $eventFrame = Yii::app()->nodeSocket->getFrameFactory()->createEventFrame(); $eventFrame->setEventName('updateBoard'); $eventFrame['boardId'] = 25; $eventFrame['boardData'] = $html; $dataEvent = Yii::app()->nodeSocket->getFrameFactory()->createPublicDataFrame(); $dataEvent->setKey('error.strings'); $dataEvent['key'] = $value; $multipleFrame->addFrame($eventFrame); $multipleFrame->addFrame($dataEvent); $multipleFrame->send();
示例 2
$multipleFrame = Yii::app()->nodeSocket->getFrameFactory()->createMultipleFrame(); $eventFrame = $multipleFrame->createEventFrame(); $eventFrame->setEventName('updateBoard'); $eventFrame['boardId'] = 25; $eventFrame['boardData'] = $html; $dataEvent = $multipleFrame->createPublicDataFrame(); $dataEvent->setKey('error.strings'); $dataEvent['key'] = $value; $multipleFrame->send();
## PS
抱歉我的英语不好 :)