chore: 重写初始提交(清空历史,整理后全量提交)

This commit is contained in:
ywxapp
2026-08-16 16:54:14 +08:00
commit 6c1a106bc1
1808 changed files with 238144 additions and 0 deletions
+123
View File
@@ -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/mqttcomposer 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);
}
}