chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
namespace addon\mqttbroker\service;
|
||||
|
||||
use Workerman\Mqtt\Client;
|
||||
|
||||
/**
|
||||
* 外部 Broker 转发器(规则引擎的投递通道)
|
||||
*
|
||||
* 将本地匹配到的消息转发到外部 MQTT Broker(如 EMQX),实现"桥接到 EMQX"。
|
||||
* 每个目标 Broker 维护一个异步常驻连接(基于 Workerman\Mqtt\Client,复用 Broker 的事件循环),
|
||||
* 连接未就绪时消息进入待发缓冲(上限 200,避免无限堆积),连接成功后冲刷。
|
||||
*
|
||||
* 依赖 workerman/mqtt(composer require workerman/mqtt)。未安装时 isAvailable()=false,
|
||||
* 规则引擎自动跳过转发,不影响 Broker 其它功能。
|
||||
*/
|
||||
class Forwarder
|
||||
{
|
||||
/** @var array key => ['client'=>Client|null,'connected'=>bool,'pending'=>callable[]] */
|
||||
protected $clients = [];
|
||||
|
||||
|
||||
public function isAvailable(): bool
|
||||
{
|
||||
return class_exists(Client::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按规则转发一条消息到外部 Broker
|
||||
*/
|
||||
public function forward(array $rule, string $topic, string $msg, int $qos): void
|
||||
{
|
||||
if (!$this->isAvailable()) {
|
||||
return;
|
||||
}
|
||||
$key = md5(($rule['target_broker'] ?? '') . '|' . ($rule['target_clientid'] ?? '') . '|' . ($rule['target_username'] ?? ''));
|
||||
if (!isset($this->clients[$key]) || $this->clients[$key]['client'] === null) {
|
||||
$this->connect($key, $rule);
|
||||
}
|
||||
$entry = &$this->clients[$key];
|
||||
$target = $this->buildTargetTopic((string) ($rule['target_topic'] ?? ''), $topic);
|
||||
$pubQos = min($qos, (int) ($rule['target_qos'] ?? 0));
|
||||
|
||||
$send = function () use ($entry, $target, $msg, $pubQos) {
|
||||
try {
|
||||
$entry['client']->publish($target, $msg, ['qos' => $pubQos]);
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
};
|
||||
|
||||
if ($entry['connected']) {
|
||||
$send();
|
||||
} elseif (count($entry['pending']) < 200) {
|
||||
$entry['pending'][] = $send;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 建立到目标 Broker 的异步连接(懒连接)
|
||||
*/
|
||||
protected function connect(string $key, array $rule): void
|
||||
{
|
||||
$this->clients[$key] = ['client' => null, 'connected' => false, 'pending' => []];
|
||||
try {
|
||||
$uri = $this->buildUri((string) ($rule['target_broker'] ?? ''));
|
||||
$opts = [];
|
||||
if (!empty($rule['target_clientid'])) {
|
||||
$opts['clientId'] = $rule['target_clientid'];
|
||||
}
|
||||
if (!empty($rule['target_username'])) {
|
||||
$opts['username'] = $rule['target_username'];
|
||||
$opts['password'] = $rule['target_password'] ?? '';
|
||||
}
|
||||
$client = new Client($uri, $opts);
|
||||
$self = $this;
|
||||
$client->onConnect = function () use ($key, $self) {
|
||||
$self->clients[$key]['connected'] = true;
|
||||
foreach ($self->clients[$key]['pending'] as $cb) {
|
||||
try { $cb(); } catch (\Throwable $e) {
|
||||
}
|
||||
}
|
||||
$self->clients[$key]['pending'] = [];
|
||||
};
|
||||
$client->onError = function () use ($key, $self) {
|
||||
$self->clients[$key]['connected'] = false;
|
||||
};
|
||||
$client->onClose = function () use ($key, $self) {
|
||||
$self->clients[$key]['connected'] = false;
|
||||
};
|
||||
$client->connect();
|
||||
$this->clients[$key]['client'] = $client;
|
||||
} catch (\Throwable $e) {
|
||||
$this->clients[$key] = ['client' => null, 'connected' => false, 'pending' => []];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function buildUri(string $broker): string
|
||||
{
|
||||
$broker = trim($broker);
|
||||
if ($broker === '' || strpos($broker, '://') !== false) {
|
||||
return $broker === '' ? 'mqtt://127.0.0.1:1883' : $broker;
|
||||
}
|
||||
return 'mqtt://' . $broker;
|
||||
}
|
||||
|
||||
/**
|
||||
* 目标主题模板:空或 ${topic} 表示沿用原主题,否则替换变量
|
||||
*/
|
||||
protected function buildTargetTopic(string $tpl, string $srcTopic): string
|
||||
{
|
||||
if ($tpl === '' || $tpl === '${topic}') {
|
||||
return $srcTopic;
|
||||
}
|
||||
return str_replace('${topic}', $srcTopic, $tpl);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user