* @Date: 2026-07-10 17:48:22 * @LastEditors: YwxApp * @LastEditTime: 2026-09-18 02:09:05 * @Description: * @FilePath: \ywxapp_dev\addon\iot\route\app.php * @CustomString: Copyright (c) 2026 YwxApp */ // +---------------------------------------------------------------------- // | YwxApp IoT MQTT 路由 // +---------------------------------------------------------------------- // | 说明: // | 1) /iot/backend/* 后台管理(由框架 boot 阶段 loadAddonRoutes 嵌套注册,并追加 iot 前缀) // | 2) /iot/api/* App 端接口(插件作为 addon 应用独立挂载时,本文件会被“顶层”再引入一次, // | 因此会出现 嵌套(iot/...) 与 顶层(api/...) 双份注册) // | // | ⚠️ 关键坑:think 字符串路由目标会自动注册同名“路由名”,且 RuleGroup::checkUrl() 在 // | url_route_must=false 时:若请求 URL 恰等于某已注册路由名、且同名第一条 rule 与 URL 不同, // | 会直接拦截(“定义过路由地址的 不支持访问”)。本插件 client/* 规则在双份注册下 // | 嵌套版(rule=iot/api/client/x)先注册 → 顶层 URL=api/client/x 与其同名 → 被拦 → 404。 // | 修复:client/* 采用 [类名, 方法] 数组目标(自动名为空,不进入路由名索引)。 // | 单段规则字符串目标不与 URL 全等,保持原样即可。 // +---------------------------------------------------------------------- use think\facade\Route; use addon\iot\controller\api\Client; use addon\iot\controller\api\Monitor; use addon\iot\controller\api\Scene; use addon\iot\controller\api\Ota; use addon\iot\controller\api\Mqtt; // ===================== 前台展示页(免登录门面) ===================== // GET /iot/index/index —— 插件功能/能力/优势介绍 + 实时指标快照 // 与 articles 插件同构:控制器在 controller/ 根、继承 FrontendBase(视图根自动切 // view/frontend/、游客放行),字符串路由目标,模板 view/frontend/index/index.html Route::get('index/index', 'addon\iot\controller\Index@index'); // ===================== 后台(Backend) ===================== Route::group('backend', function () { Route::get('index', 'backend/Overview/index'); Route::get('statsData', 'backend/Overview/statsData'); Route::get('connections', 'backend/Connections/connections'); Route::get('connectionsTable', 'backend/Connections/connectionsTable'); Route::get('messages', 'backend/Messages/messages'); Route::get('messagesTable', 'backend/Messages/messagesTable'); Route::get('publish', 'backend/Publish/publish'); Route::post('publish', 'backend/Publish/doPublish'); Route::get('setting', 'backend/Setting/setting'); Route::post('setting', 'backend/Setting/saveSetting'); Route::get('auth', 'backend/Auth/auth'); Route::get('authTable', 'backend/Auth/authTable'); Route::post('saveAuth', 'backend/Auth/saveAuth'); Route::post('deleteAuth', 'backend/Auth/deleteAuth'); Route::get('acl', 'backend/Acl/acl'); Route::get('aclTable', 'backend/Acl/aclTable'); Route::post('saveAcl', 'backend/Acl/saveAcl'); Route::post('deleteAcl', 'backend/Acl/deleteAcl'); Route::get('device', 'backend/Devices/device'); Route::get('deviceTable', 'backend/Devices/deviceTable'); Route::post('saveDevice', 'backend/Devices/saveDevice'); Route::post('resetDeviceSecret', 'backend/Devices/resetDeviceSecret'); Route::post('resetActivateToken', 'backend/Devices/resetActivateToken'); Route::post('deleteDevice', 'backend/Devices/deleteDevice'); Route::get('rule', 'backend/Rules/rule'); Route::get('ruleTable', 'backend/Rules/ruleTable'); Route::post('saveRule', 'backend/Rules/saveRule'); Route::post('deleteRule', 'backend/Rules/deleteRule'); Route::get('scene', 'backend/Scenes/scene'); Route::get('sceneDetail', 'backend/Scenes/sceneDetail'); Route::post('saveScene', 'backend/Scenes/saveScene'); Route::post('deleteScene', 'backend/Scenes/deleteScene'); Route::post('toggleScene', 'backend/Scenes/toggleScene'); Route::post('runScene', 'backend/Scenes/runScene'); Route::get('sceneLogs', 'backend/Scenes/sceneLogs'); Route::get('group', 'backend/Groups/group'); Route::post('saveGroup', 'backend/Groups/saveGroup'); Route::post('deleteGroup', 'backend/Groups/deleteGroup'); Route::get('deviceLogs', 'backend/Devices/deviceLogs'); // 传感器位置配置(配置真源:后台编辑 → retained 下发设备 → App 共用同一份) Route::get('deviceConfig', 'backend/Devices/deviceConfig'); Route::post('saveDeviceConfig', 'backend/Devices/saveDeviceConfig'); Route::get('alertsPage', 'backend/Alerts/alertsPage'); Route::get('alerts', 'backend/Alerts/alerts'); Route::post('readAlertAll', 'backend/Alerts/readAlertAll'); Route::get('ota', 'backend/Ota/ota'); Route::get('otaList', 'backend/Ota/otaList'); Route::get('otaTable', 'backend/Ota/otaTable'); Route::post('saveOta', 'backend/Ota/saveOta'); Route::post('deleteOta', 'backend/Ota/deleteOta'); Route::post('toggleOta', 'backend/Ota/toggleOta'); Route::post('publishOta', 'backend/Ota/publishOta'); }); // ===================== App API ===================== // URL 按控制器域分组,一眼可辨: // api/mqtt/* Broker 状态/直发(Mqtt 控制器) // api/client/* 会员与设备管理(Client 控制器:绑定/共享/票据/命令) // api/monitor/* 监测域(Monitor 控制器:传感器配置/曲线/日志/告警) // api/scene/* 联动域(Scene 控制器:场景 + 分组) // api/ota/* 固件升级(Ota 控制器) // deviceActivate 保持顶层(设备固件侧入网,不带域) // // 文件末尾保留一段旧 client/* 兼容别名(URL 迁移期),App 全量切到新 URL 后可整段删除。 Route::group('api', function () { // ---------- Broker(Mqtt 控制器) ---------- Route::get('mqtt/status', [Mqtt::class, 'status']); Route::post('mqtt/publish', [Mqtt::class, 'publish']); // 设备激活(公开,设备侧入云换 uuid/secret) Route::post('deviceActivate', [Client::class, 'deviceActivate']); // ---------- 会员/设备域(Client 控制器) ---------- Route::get('client/devices', [Client::class, 'devices']); Route::get('client/subscriptions', [Client::class, 'subscriptions']); Route::post('client/publish', [Client::class, 'publish']); Route::post('client/bindDevice', [Client::class, 'bindDevice']); Route::get('client/myDevices', [Client::class, 'myDevices']); Route::post('client/unbindDevice', [Client::class, 'unbindDevice']); Route::post('client/resetSecret', [Client::class, 'resetSecret']); Route::post('client/mqttTicket', [Client::class, 'mqttTicket']); Route::post('client/sendCommand', [Client::class, 'sendCommand']); Route::get('client/deviceState', [Client::class, 'deviceState']); Route::post('client/shareDevice', [Client::class, 'shareDevice']); Route::post('client/revokeShare', [Client::class, 'revokeShare']); Route::get('client/shareList', [Client::class, 'shareList']); // ---------- 监测域(Monitor 控制器:传感器配置/曲线/日志/告警) ---------- // 传感器位置配置(配置真源:后台编辑 → retained 下发设备 → App 共用同一份) Route::get('monitor/deviceConfig', [Monitor::class, 'deviceConfig']); Route::post('monitor/saveDeviceConfig', [Monitor::class, 'saveDeviceConfig']); // 传感器历史曲线(服务端结构化时序 iot_sensor_point,稀疏点 + interval 供前向填充) Route::get('monitor/sensorSeries', [Monitor::class, 'sensorSeries']); // 传感器分钟级聚合曲线(时间加权平均,step/window 可控:最近30/60分钟每分钟一点等) Route::get('monitor/sensorAvg', [Monitor::class, 'sensorAvg']); // 最近 N 分钟窗口平均(按位置汇总,时间加权;window=分钟数,返回单条 avg/min/max/samples) Route::get('monitor/recentAvg', [Monitor::class, 'recentAvg']); Route::get('monitor/deviceLogs', [Monitor::class, 'deviceLogs']); Route::get('monitor/alertList', [Monitor::class, 'alertList']); Route::post('monitor/readAlert', [Monitor::class, 'readAlert']); Route::get('monitor/alertUnread', [Monitor::class, 'alertUnread']); // ---------- 联动域(Scene 控制器:场景 + 分组) ---------- Route::get('scene/sceneList', [Scene::class, 'sceneList']); Route::get('scene/sceneDetail', [Scene::class, 'sceneDetail']); Route::post('scene/saveScene', [Scene::class, 'saveScene']); Route::post('scene/deleteScene', [Scene::class, 'deleteScene']); Route::post('scene/toggleScene', [Scene::class, 'toggleScene']); Route::post('scene/runScene', [Scene::class, 'runScene']); Route::get('scene/sceneLogs', [Scene::class, 'sceneLogs']); Route::get('scene/groupList', [Scene::class, 'groupList']); Route::post('scene/saveGroup', [Scene::class, 'saveGroup']); Route::post('scene/deleteGroup', [Scene::class, 'deleteGroup']); Route::post('scene/groupAddDevice', [Scene::class, 'groupAddDevice']); Route::post('scene/groupRemoveDevice', [Scene::class, 'groupRemoveDevice']); Route::get('scene/groupDevices', [Scene::class, 'groupDevices']); // ---------- OTA 域(Ota 控制器) ---------- Route::get('ota/otaList', [Ota::class, 'otaList']); Route::post('ota/publishOta', [Ota::class, 'publishOta']); // ================ 旧 client/* 兼容别名(迁移期保留,App 切完新 URL 后整段删除) ================ // 全部数组目标:不注册路由名,规避双份注册同名拦截(见文件头 ⚠️) Route::get('client/deviceConfig', [Monitor::class, 'deviceConfig']); Route::post('client/saveDeviceConfig', [Monitor::class, 'saveDeviceConfig']); Route::get('client/sensorSeries', [Monitor::class, 'sensorSeries']); Route::get('client/sensorAvg', [Monitor::class, 'sensorAvg']); Route::get('client/recentAvg', [Monitor::class, 'recentAvg']); Route::get('client/deviceLogs', [Monitor::class, 'deviceLogs']); Route::get('client/alertList', [Monitor::class, 'alertList']); Route::post('client/readAlert', [Monitor::class, 'readAlert']); Route::get('client/alertUnread', [Monitor::class, 'alertUnread']); Route::get('client/sceneList', [Scene::class, 'sceneList']); Route::get('client/sceneDetail', [Scene::class, 'sceneDetail']); Route::post('client/saveScene', [Scene::class, 'saveScene']); Route::post('client/deleteScene', [Scene::class, 'deleteScene']); Route::post('client/toggleScene', [Scene::class, 'toggleScene']); Route::post('client/runScene', [Scene::class, 'runScene']); Route::get('client/sceneLogs', [Scene::class, 'sceneLogs']); Route::get('client/groupList', [Scene::class, 'groupList']); Route::post('client/saveGroup', [Scene::class, 'saveGroup']); Route::post('client/deleteGroup', [Scene::class, 'deleteGroup']); Route::post('client/groupAddDevice', [Scene::class, 'groupAddDevice']); Route::post('client/groupRemoveDevice', [Scene::class, 'groupRemoveDevice']); Route::get('client/groupDevices', [Scene::class, 'groupDevices']); Route::get('client/otaList', [Ota::class, 'otaList']); Route::post('client/publishOta', [Ota::class, 'publishOta']); // Broker 旧顶层路径(/iot/api/status、/iot/api/publish) Route::get('status', [Mqtt::class, 'status']); Route::post('publish', [Mqtt::class, 'publish']); })->namespace('addon\iot\controller\api'); Route::get('/', 'Index/index');