308 lines
10 KiB
PHP
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('已删除');
|
|
}
|
|
}
|