ratacibernetica/yii2-node-socket

此包的最新版本(v1.0)没有提供许可信息。

这是 oncesk/yii-node-socket 的分支。

v1.0 2015-10-02 15:46 UTC

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

抱歉我的英语不好 :)