Files
YwxAppThink/addon/mqttbroker/controller/backend/MqttBroker.php
T

308 lines
10 KiB
PHP

<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace addon\mqttbroker\controller\backend;
use think\facade\Db;
use think\facade\View;
use ywxapp\controller\BackendBase;
use ywxapp\service\AddonService;
use addon\mqttbroker\model\Connection;
use addon\mqttbroker\model\Message;
use addon\mqttbroker\service\Auth;
use addon\mqttbroker\service\Store;
/**
* MqttBroker 类
*
* @author ywxapp <admin@ywxapp.cn>
*/
class MqttBroker extends BackendBase
{
// 与 blog 后台一致:开发期放宽登录校验(生产请改回需登录)
protected $noNeedVerify = ['*'];
/**
* 概览
*/
public function index()
{
$stats = [
'online' => Connection::where('status', 1)->count(),
'total' => Connection::count(),
'messages' => Message::count(),
'topics' => Db::name('mqttbroker_topic')->count(),
];
$recent = Message::order('id', 'desc')->limit(10)->select();
View::assign('stats', $stats);
View::assign('recent', $recent);
return View::fetch('admin/index');
}
/**
* 客户端连接列表
*/
public function connections()
{
$list = Connection::order('id', 'desc')->paginate(20);
View::assign('list', $list);
return View::fetch('admin/connections');
}
/**
* 消息日志
*/
public function messages()
{
$topic = input('topic', '');
$query = Message::order('id', 'desc');
if ($topic) {
$query->where('topic', 'like', "%{$topic}%");
}
$list = $query->paginate(20);
View::assign('list', $list);
View::assign('topic', $topic);
return View::fetch('admin/messages');
}
/**
* 发布消息表单
*/
public function publish()
{
return View::fetch('admin/publish');
}
/**
* 执行发布(零外部依赖:写入出站队列,由常驻 Broker 消费后经内部路由投递)
*/
public function doPublish()
{
$topic = input('post.topic', '');
$payload = input('post.payload', '');
$qos = (int) input('post.qos', 0);
if (!$topic) {
$this->result->error('主题不能为空');
}
$ok = Store::enqueueManual($topic, $payload, $qos, 0, 'backend');
if ($ok) {
$this->result->success('已加入发布队列,Broker 将投递给在线订阅者');
}
$this->result->error('发布失败:无法写入出站队列');
}
/**
* 服务设置
*/
public function setting()
{
$def = include ADDON_PATH . 'mqttbroker' . DIRECTORY_SEPARATOR . 'config.php';
$saved = AddonService::config('mqttbroker');
View::assign('def', $def);
View::assign('saved', $saved ?: []);
return View::fetch('admin/setting');
}
/**
* 保存设置
*/
public function saveSetting()
{
$data = input('post.');
AddonService::config('mqttbroker', $data);
$this->result->success('保存成功');
}
/* ============================================================
* 认证账号管理
* ========================================================== */
public function auth()
{
$list = Db::name('mqttbroker_auth')->order('id', 'desc')->paginate(20);
View::assign('list', $list);
return View::fetch('admin/auth');
}
public function saveAuth()
{
$id = (int) input('post.id', 0);
$username = trim((string) input('post.username', ''));
$password = (string) input('post.password', '');
$isSuper = (int) input('post.is_superuser', 0);
$status = (int) input('post.status', 1);
$remark = (string) input('post.remark', '');
if ($username === '') {
$this->result->error('用户名不能为空');
}
$data = [
'username' => $username,
'is_superuser' => $isSuper ? 1 : 0,
'status' => $status ? 1 : 0,
'remark' => $remark,
'update_at' => time(),
];
// 仅当填写了密码才更新(编辑时留空表示不改)
if ($password !== '') {
$data['password'] = Auth::hashPassword($password);
}
try {
if ($id > 0) {
Db::name('mqttbroker_auth')->where('id', $id)->update($data);
} else {
if ($password === '') {
$this->result->error('新增账号必须设置密码');
}
$data['create_at'] = time();
Db::name('mqttbroker_auth')->insert($data);
}
} catch (\Throwable $e) {
$this->result->error('保存失败:' . $e->getMessage());
}
$this->result->success('保存成功');
}
public function deleteAuth()
{
$id = (int) input('post.id', 0);
Db::name('mqttbroker_auth')->where('id', $id)->delete();
$this->result->success('已删除');
}
/* ============================================================
* ACL 规则管理
* ========================================================== */
public function acl()
{
$list = Db::name('mqttbroker_acl')->order('sort', 'asc')->order('id', 'asc')->paginate(50);
View::assign('list', $list);
return View::fetch('admin/acl');
}
public function saveAcl()
{
$id = (int) input('post.id', 0);
$data = [
'target_type' => (string) input('post.target_type', 'all'),
'target' => (string) input('post.target', ''),
'topic' => (string) input('post.topic', ''),
'access' => (int) input('post.access', 3),
'allow' => (int) input('post.allow', 1),
'sort' => (int) input('post.sort', 0),
'remark' => (string) input('post.remark', ''),
];
if ($data['topic'] === '') {
$this->result->error('主题过滤器不能为空');
}
try {
if ($id > 0) {
Db::name('mqttbroker_acl')->where('id', $id)->update($data);
} else {
$data['create_at'] = time();
Db::name('mqttbroker_acl')->insert($data);
}
} catch (\Throwable $e) {
$this->result->error('保存失败:' . $e->getMessage());
}
$this->result->success('保存成功(最长 30s 后在 Broker 生效)');
}
public function deleteAcl()
{
$id = (int) input('post.id', 0);
Db::name('mqttbroker_acl')->where('id', $id)->delete();
$this->result->success('已删除');
}
/* ============================================================
* 实时监控仪表盘(轮询 stats 接口)
* ========================================================== */
public function stats()
{
$row = Db::name('mqttbroker_stats')->where('id', 1)->find();
if (!$row) {
$row = [
'uptime' => 0, 'clients_online' => 0, 'clients_total' => 0,
'subscriptions' => 0, 'messages_received' => 0, 'messages_sent' => 0,
'retained' => 0, 'timestamp' => time(),
];
}
// 以连接表的实时在线数补全(更贴近 DB 视角的在线状态)
try {
$row['db_online'] = Connection::where('status', 1)->count();
} catch (\Throwable $e) {
$row['db_online'] = $row['clients_online'] ?? 0;
}
$this->result->success($row);
}
/* ============================================================
* 转发规则(规则引擎 / 桥接到 EMQX)
* ========================================================== */
public function rule()
{
$list = Db::name('mqttbroker_rule')->order('id', 'desc')->paginate(20);
View::assign('list', $list);
return View::fetch('admin/rule');
}
public function saveRule()
{
$id = (int) input('post.id', 0);
$data = [
'name' => (string) input('post.name', ''),
'source_filter' => (string) input('post.source_filter', ''),
'target_type' => (string) input('post.target_type', 'mqtt'),
'target_broker' => (string) input('post.target_broker', ''),
'target_clientid' => (string) input('post.target_clientid', ''),
'target_username' => (string) input('post.target_username', ''),
'target_password' => (string) input('post.target_password', ''),
'target_topic' => (string) input('post.target_topic', ''),
'target_qos' => (int) input('post.target_qos', 0),
'enabled' => (int) input('post.enabled', 1),
'remark' => (string) input('post.remark', ''),
];
if ($data['name'] === '' || $data['source_filter'] === '') {
$this->result->error('规则名称与源主题过滤器必填');
}
if ($data['target_broker'] === '') {
$this->result->error('目标 Broker 地址必填');
}
try {
if ($id > 0) {
Db::name('mqttbroker_rule')->where('id', $id)->update($data);
} else {
$data['create_at'] = time();
Db::name('mqttbroker_rule')->insert($data);
}
} catch (\Throwable $e) {
$this->result->error('保存失败:' . $e->getMessage());
}
$this->result->success('保存成功(最多 30s 后在 Broker 生效)');
}
public function deleteRule()
{
$id = (int) input('post.id', 0);
Db::name('mqttbroker_rule')->where('id', $id)->delete();
$this->result->success('已删除');
}
}