Merge branch 'master' into json_social_login

This commit is contained in:
LemonTree
2022-04-24 18:26:30 +08:00
13 changed files with 2458 additions and 135 deletions

View File

@@ -6,13 +6,14 @@
#### 2. 设备接入使用EMQX消息服务器加密认证后端采用Spring boot前端采用Vue移动端采用Uniapp数据库采用Mysql和Redis设备端支持ESP32、ESP8266、树莓派等系统架构图如下 #### 2. 设备接入使用EMQX消息服务器加密认证后端采用Spring boot前端采用Vue移动端采用Uniapp数据库采用Mysql和Redis设备端支持ESP32、ESP8266、树莓派等系统架构图如下
<img src="https://oscimg.oschina.net/oscnet/up-98eefff896394066a60d664b875a3d05d1d.png" max-width="800" /> <img src="https://oscimg.oschina.net/oscnet/up-98eefff896394066a60d664b875a3d05d1d.png" max-width="800" />
#### 3. 案例展示 #### 3. 项目可用于个人学习和使用,商业用途需要赞助项目,获得授权。[查看详情 >>](https://gitee.com/kerwincui/wumei-smart/blob/master/app/README.md)
| Wifi通断器 | 信息牌 | 桌面小电视 | 雾霾/粉尘/空气检测仪
| :---- | :---------- |:---------- |:---------- | #### 4. 案例展示
| ![](https://oscimg.oschina.net/oscnet/up-91921ed009aed9b70e99e6216a3ffb5c707.png) | ![](https://oscimg.oschina.net/oscnet/up-b25dde78b0127e0ec08db4496fcf777069d.png) | ![](https://oscimg.oschina.net/oscnet/up-9c53911f42386189b943ce753abed5346f7.png) | ![](https://oscimg.oschina.net/oscnet/up-072a5880eaf5ea502dc265bc208902b8529.png) | Wifi通断器 | 信息牌 | 桌面小电视 | 雾霾/粉尘/空气检测仪
| 【小驿物联】<br /> 1. 手机、电脑远程控制<br /> 2. 遥控配对、清码和控制<br /> 3. 空气温湿度实时监测<br />4. 雷达感应和报警<br /> 5. 供电电压220V和5V<br /> 6. 控制阻性负载2500W感性负载250W家用设备开关。 | 【更多设备等您加入】<br />待定 | 【更多设备等您加入】<br />待定 | 【小驿物联】<br />1. PM2.5雾霾监测<br />2. PM10粉尘监测<br /> 3. 空气质量监测 <br />4. TFT彩色屏幕 | :----: | :----------: |:----------: |:----------: |
| ![](https://oscimg.oschina.net/oscnet/up-91921ed009aed9b70e99e6216a3ffb5c707.png) | ![](https://oscimg.oschina.net/oscnet/up-b25dde78b0127e0ec08db4496fcf777069d.png) | ![](https://oscimg.oschina.net/oscnet/up-9c53911f42386189b943ce753abed5346f7.png) | ![](https://oscimg.oschina.net/oscnet/up-072a5880eaf5ea502dc265bc208902b8529.png)
| 【小驿物联】 | 待定 | 待定 | 【小驿物联】
#### 4. 项目可用于个人学习和使用,商业用途需要赞助项目,获得授权。[查看详情 >>](https://gitee.com/kerwincui/wumei-smart/blob/master/app/README.md)
### 二、功能 ### 二、功能
@@ -20,16 +21,11 @@
- 系统监控: 操作日志、登录日志、系统日志、在线用户、服务监控、连接池监控、缓存监控等 - 系统监控: 操作日志、登录日志、系统日志、在线用户、服务监控、连接池监控、缓存监控等
- 产品管理: 产品、产品物模型、产品分类、产品固件、授权码等 - 产品管理: 产品、产品物模型、产品分类、产品固件、授权码等
- 设备管理: 控制、分组、定时、日志、统计、定位、OTA升级、影子模式、实时监测、加密认证等 - 设备管理: 控制、分组、定时、日志、统计、定位、OTA升级、影子模式、实时监测、加密认证等
- EMQ管理 Mqtt客户端、监听器、消息主题、消息订阅、插件管理 - EMQ管理 Mqtt客户端、监听器、消息主题、消息订阅、插件管理、规则引擎、资源
- 硬件 SDK 支持WIFI和MQTT连接、物模型响应、实时监测、定时上报监测数据、AES加密、NTP时间等 - 硬件 SDK 支持WIFI和MQTT连接、物模型响应、实时监测、定时上报监测数据、AES加密、NTP时间等
- 物模型管理: 属性(设备状态和监测数据),功能(执行特定任务),事件(设备主动上报给云端) - 物模型管理: 属性(设备状态和监测数据),功能(执行特定任务),事件(设备主动上报给云端)
- 其他开发中第三方登录设备分享、设备告警、场景联动进度50%智能音箱、多租户、APP界面自定义进度40%时序数据库、分布式集群部署、Granfa监控进度30%),视频流处理、桌面端模拟器/监控、安卓端模拟器/监控进度20%App和小程序正在开发中......
### 三、计划开发功能
- 设备分享、设备告警、场景联动进度50%
- 智能音箱、多租户、APP界面自定义进度40%
- 时序数据库、分布式集群部署、Granfa监控进度30%
- 视频流处理、桌面端模拟器/监控、安卓端模拟器/监控进度0%
- App和小程序正在开发中......
### 四、技术栈 ### 四、技术栈
* 服务端 * 服务端
@@ -44,12 +40,6 @@
* 硬件端 * 硬件端
- 相关技术: ESP-IDF、Arduino、FreeRTOS等 - 相关技术: ESP-IDF、Arduino、FreeRTOS等
- 开发工具Visual Studio Code 和 Arduino - 开发工具Visual Studio Code 和 Arduino
* 安卓端模拟器/监控
- 相关技术android
- 开发工具Android Studio
* 电脑端模拟器/监控
- 相关技术WPF
- 开发工具Visual Studio
### 五、硬件接入 ### 五、硬件接入
1. 设备认证 1. 设备认证
@@ -72,9 +62,9 @@
&nbsp;&nbsp;&nbsp;&nbsp; app ----------------------- [获取App源码](https://gitee.com/kerwincui/wumei-smart/tree/master/app)<br /> &nbsp;&nbsp;&nbsp;&nbsp; app ----------------------- [获取App源码](https://gitee.com/kerwincui/wumei-smart/tree/master/app)<br />
###### 移动端适配多端 ###### 移动端适配多端
|安卓|IOS|微信小程序|支付宝小程序|百度小程序|字节小程序|QQ小程序|快应用| |安卓|IOS|微信小程序|其他平台|
| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | | :---: | :---: | :---: | :---: |
|√|√|√|未测试|未测试|未测试|未测试|未测试| |√|√|√|未测试|
### 七、相关文档 ### 七、相关文档
@@ -109,8 +99,8 @@ kerwincui/wumei-smart:1.1
* [演示地址>>](https://iot.wumei.live/) * [演示地址>>](https://iot.wumei.live/)
##### 项目贡献者 ##### 项目贡献者
|[小驿物联](https://gitee.com/iot-xiaoyi) |[guanshubiao](https://gitee.com/guanshubiao)|[crazyDull](https://gitee.com/crazyDull) |[kami0314](https://github.com/kami0314)| [sxh](https://gitee.com/sixiaohu) | [redamancy_zxp](https://gitee.com/redamancy-zxp) |[小驿物联](https://gitee.com/iot-xiaoyi) |[guanshubiao](https://gitee.com/guanshubiao)|[crazyDull](https://gitee.com/crazyDull) |[kami0314](https://github.com/kami0314)| [sxh](https://gitee.com/sixiaohu) | [redamancy_zxp](https://gitee.com/redamancy-zxp) | [LEE](https://gitee.com/yueming188)
|--|--|--|--|--|--| |--|--|--|--|--|--|--|
### 九、部分图片 ### 九、部分图片

View File

@@ -11,7 +11,7 @@
Target Server Version : 50734 Target Server Version : 50734
File Encoding : 65001 File Encoding : 65001
Date: 17/04/2022 00:41:41 Date: 22/04/2022 01:33:54
*/ */
SET NAMES utf8mb4; SET NAMES utf8mb4;
@@ -161,7 +161,7 @@ CREATE TABLE `QRTZ_SCHEDULER_STATE` (
-- ---------------------------- -- ----------------------------
-- Records of QRTZ_SCHEDULER_STATE -- Records of QRTZ_SCHEDULER_STATE
-- ---------------------------- -- ----------------------------
INSERT INTO `QRTZ_SCHEDULER_STATE` VALUES ('RuoyiScheduler', 'DESKTOP-KKH3KAT1650125174799', 1650127309379, 15000); INSERT INTO `QRTZ_SCHEDULER_STATE` VALUES ('RuoyiScheduler', 'DESKTOP-KKH3KAT1650559591596', 1650562435320, 15000);
-- ---------------------------- -- ----------------------------
-- Table structure for QRTZ_SIMPLE_TRIGGERS -- Table structure for QRTZ_SIMPLE_TRIGGERS
@@ -238,7 +238,7 @@ CREATE TABLE `QRTZ_TRIGGERS` (
-- ---------------------------- -- ----------------------------
-- Records of QRTZ_TRIGGERS -- Records of QRTZ_TRIGGERS
-- ---------------------------- -- ----------------------------
INSERT INTO `QRTZ_TRIGGERS` VALUES ('RuoyiScheduler', 'TASK_CLASS_NAME1', 'DEFAULT', 'TASK_CLASS_NAME1', 'DEFAULT', NULL, 1650162000000, -1, 5, 'WAITING', 'CRON', 1650125184000, 0, NULL, 1, ''); INSERT INTO `QRTZ_TRIGGERS` VALUES ('RuoyiScheduler', 'TASK_CLASS_NAME1', 'DEFAULT', 'TASK_CLASS_NAME1', 'DEFAULT', NULL, 1650594000000, -1, 5, 'WAITING', 'CRON', 1650559600000, 0, NULL, 1, '');
-- ---------------------------- -- ----------------------------
-- Table structure for gen_table -- Table structure for gen_table
@@ -1023,6 +1023,76 @@ CREATE TABLE `iot_scene` (
-- Records of iot_scene -- Records of iot_scene
-- ---------------------------- -- ----------------------------
-- ----------------------------
-- Table structure for iot_social_platform
-- ----------------------------
DROP TABLE IF EXISTS `iot_social_platform`;
CREATE TABLE `iot_social_platform` (
`social_platform_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '第三方登录平台主键',
`platform` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '第三方登录平台',
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT ' 0:启用 ,1:禁用',
`client_id` varchar(48) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '第三方平台申请Id',
`secret_key` varchar(48) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '第三方平台密钥',
`redirect_uri` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户认证后跳转地址',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '删除标记位(0代表存在1代表删除)',
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '创建者',
`create_time` datetime(0) NOT NULL COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新者',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
`bind_uri` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '绑定注册登录uri,http://localhost/login?bindId=',
`redirect_login_uri` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '跳转登录uri,http://localhost/login?loginId=',
`error_msg_uri` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '错误提示uri,http://localhost/login?errorId=',
PRIMARY KEY (`social_platform_id`) USING BTREE,
UNIQUE INDEX `iot_social_platform_platform_uindex`(`platform`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '第三方登录平台控制' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of iot_social_platform
-- ----------------------------
-- ----------------------------
-- Table structure for iot_social_user
-- ----------------------------
DROP TABLE IF EXISTS `iot_social_user`;
CREATE TABLE `iot_social_user` (
`social_user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '第三方系统用户表主键',
`uuid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '第三方系统的唯一ID',
`source` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '第三方用户来源',
`access_token` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户的授权令牌',
`expire_in` int(11) NULL DEFAULT NULL COMMENT '第三方用户的授权令牌的有效期(部分平台可能没有)',
`refresh_token` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '刷新令牌(部分平台可能没有)',
`open_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '第三方用户的 open id部分平台可能没有',
`uid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '第三方用户的 ID(部分平台可能没有)',
`access_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '个别平台的授权信息(部分平台可能没有)',
`union_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '第三方用户的 union id(部分平台可能没有)',
`scope` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '第三方用户授予的权限(部分平台可能没有)',
`token_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '个别平台的授权信息(部分平台可能没有)',
`id_token` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'id token部分平台可能没有',
`mac_algorithm` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '小米平台用户的附带属性(部分平台可能没有)',
`mac_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '小米平台用户的附带属性(部分平台可能没有)',
`code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户的授权code部分平台可能没有',
`oauth_token` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'Twitter平台用户的附带属性(部分平台可能没有)',
`oauth_token_secret` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'Twitter平台用户的附带属性(部分平台可能没有)',
`create_time` datetime(0) NOT NULL COMMENT '创建时间',
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '创建者',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新者',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '删除标记位(0代表存在,2代表删除)',
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '绑定状态(0:未绑定,1:绑定)',
`sys_user_id` int(11) NULL DEFAULT NULL COMMENT '用户ID',
`username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名',
`nickname` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户昵称',
`avatar` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户头像',
`gender` tinyint(4) NULL DEFAULT NULL COMMENT '用户性别',
UNIQUE INDEX `iot_social_user_pk`(`social_user_id`) USING BTREE,
UNIQUE INDEX `iot_social_user_unique_key`(`uuid`, `source`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of iot_social_user
-- ----------------------------
-- ---------------------------- -- ----------------------------
-- Table structure for iot_things_model -- Table structure for iot_things_model
-- ---------------------------- -- ----------------------------
@@ -1087,7 +1157,7 @@ CREATE TABLE `iot_things_model_template` (
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`template_id`) USING BTREE PRIMARY KEY (`template_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 18 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '物模型模板' ROW_FORMAT = Dynamic; ) ENGINE = InnoDB AUTO_INCREMENT = 21 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '物模型模板' ROW_FORMAT = Dynamic;
-- ---------------------------- -- ----------------------------
-- Records of iot_things_model_template -- Records of iot_things_model_template
@@ -1562,12 +1632,11 @@ CREATE TABLE `sys_logininfor` (
`msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '提示消息', `msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '提示消息',
`login_time` datetime(0) NULL DEFAULT NULL COMMENT '访问时间', `login_time` datetime(0) NULL DEFAULT NULL COMMENT '访问时间',
PRIMARY KEY (`info_id`) USING BTREE PRIMARY KEY (`info_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 109 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统访问记录' ROW_FORMAT = Dynamic; ) ENGINE = InnoDB AUTO_INCREMENT = 111 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统访问记录' ROW_FORMAT = Dynamic;
-- ---------------------------- -- ----------------------------
-- Records of sys_logininfor -- Records of sys_logininfor
-- ---------------------------- -- ----------------------------
INSERT INTO `sys_logininfor` VALUES (108, 'admin', '127.0.0.1', '内网IP', 'Chrome 10', 'Windows 10', '0', '退出成功', '2022-04-17 00:40:43');
-- ---------------------------- -- ----------------------------
-- Table structure for sys_menu -- Table structure for sys_menu
@@ -1810,7 +1879,7 @@ CREATE TABLE `sys_oper_log` (
`error_msg` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '错误消息', `error_msg` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '错误消息',
`oper_time` datetime(0) NULL DEFAULT NULL COMMENT '操作时间', `oper_time` datetime(0) NULL DEFAULT NULL COMMENT '操作时间',
PRIMARY KEY (`oper_id`) USING BTREE PRIMARY KEY (`oper_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 226 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '操作日志记录' ROW_FORMAT = Dynamic; ) ENGINE = InnoDB AUTO_INCREMENT = 234 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '操作日志记录' ROW_FORMAT = Dynamic;
-- ---------------------------- -- ----------------------------
-- Records of sys_oper_log -- Records of sys_oper_log
@@ -2186,12 +2255,8 @@ CREATE TABLE `sys_user` (
-- ---------------------------- -- ----------------------------
-- Records of sys_user -- Records of sys_user
-- ---------------------------- -- ----------------------------
INSERT INTO `sys_user` VALUES (1, 103, 'admin', '物美智能管理员', '00', '164770707@qq.com', '15888888888', '0', '', '$2a$10$0Duw0QB6s7YnQEaNSdSVWeXHMmSa090pG15ZXpf.CQEzEhgxyr7IO', '0', '0', '127.0.0.1', '2022-04-16 22:36:03', 'admin', '2021-12-15 21:36:18', '', '2022-04-16 22:36:03', '管理员'); INSERT INTO `sys_user` VALUES (1, 103, 'admin', '物美智能管理员', '00', '164770707@qq.com', '15888888888', '0', '', '$2a$10$0Duw0QB6s7YnQEaNSdSVWeXHMmSa090pG15ZXpf.CQEzEhgxyr7IO', '0', '0', '127.0.0.1', '2022-04-22 00:50:41', 'admin', '2021-12-15 21:36:18', '', '2022-04-22 00:50:41', '管理员');
INSERT INTO `sys_user` VALUES (2, 100, 'wumei-tenant', '物美智能租户', '00', '15666666@qq.com', '15666666666', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '2', '27.23.210.211', '2022-01-15 21:00:33', 'admin', '2021-12-15 21:36:18', 'admin', '2022-03-09 17:04:33', '租户');
INSERT INTO `sys_user` VALUES (3, 100, 'common', '普通用户', '00', '', '', '0', '', '$2a$10$kKeZptrTnSlm0fencX4U2eq.QiaukDs.DckiUsMCwVTxh0IS2LRQ.', '0', '2', '127.0.0.1', '2022-04-10 15:10:47', 'admin', '2022-03-09 16:49:19', 'admin', '2022-04-10 15:10:47', '普通用户');
INSERT INTO `sys_user` VALUES (118, 100, 'wumei', '游客账号', '00', '', '', '0', '', '$2a$10$kKeZptrTnSlm0fencX4U2eq.QiaukDs.DckiUsMCwVTxh0IS2LRQ.', '0', '0', '127.0.0.1', '2022-03-15 23:25:38', 'admin', '2022-03-09 16:49:19', 'admin', '2022-03-15 23:25:38', NULL); INSERT INTO `sys_user` VALUES (118, 100, 'wumei', '游客账号', '00', '', '', '0', '', '$2a$10$kKeZptrTnSlm0fencX4U2eq.QiaukDs.DckiUsMCwVTxh0IS2LRQ.', '0', '0', '127.0.0.1', '2022-03-15 23:25:38', 'admin', '2022-03-09 16:49:19', 'admin', '2022-03-15 23:25:38', NULL);
INSERT INTO `sys_user` VALUES (119, 100, 'wumei-user', '物美-用户A', '00', '', '', '0', '', '$2a$10$bkep9sszOTEgVPSZWx7P1ONPPC8dMvYY9ujFF7dJTrFVXfKZYdRBO', '0', '2', '', NULL, 'admin', '2022-04-15 14:09:00', 'admin', '2022-04-15 14:09:16', NULL);
INSERT INTO `sys_user` VALUES (120, NULL, 'wumei-one', 'wumei-one', '00', '', 'wumei-one', '0', '', '$2a$10$lWyuVQQlP3TDSm1nqsW1V.KBbFX57jk3b9NlOtBE/.F2oTVWjqYrO', '0', '2', '127.0.0.1', '2022-04-15 14:12:41', '', '2022-04-15 14:12:24', '', '2022-04-15 14:12:40', NULL);
INSERT INTO `sys_user` VALUES (121, 100, 'wumei-tenant-one', '物美租户壹', '00', '', '15888888880', '0', '', '$2a$10$BAWId9C2Nrcwklzl1Ikoau4iqL8XRGvfRjq6Wl.PXWpzwAw0sXMdK', '0', '0', '127.0.0.1', '2022-04-16 12:37:50', 'admin', '2022-04-15 16:21:25', '', '2022-04-16 12:37:49', NULL); INSERT INTO `sys_user` VALUES (121, 100, 'wumei-tenant-one', '物美租户壹', '00', '', '15888888880', '0', '', '$2a$10$BAWId9C2Nrcwklzl1Ikoau4iqL8XRGvfRjq6Wl.PXWpzwAw0sXMdK', '0', '0', '127.0.0.1', '2022-04-16 12:37:50', 'admin', '2022-04-15 16:21:25', '', '2022-04-16 12:37:49', NULL);
INSERT INTO `sys_user` VALUES (122, 100, 'wumei-tenant-two', '物美租户贰', '00', '', '15988888880', '0', '', '$2a$10$1zMlbW7hGpzA59gpzWGO/ObeASziQ296evjMjHrYdZnxKBLU4WUum', '0', '0', '127.0.0.1', '2022-04-16 12:48:57', 'admin', '2022-04-15 16:22:08', '', '2022-04-16 12:48:57', NULL); INSERT INTO `sys_user` VALUES (122, 100, 'wumei-tenant-two', '物美租户贰', '00', '', '15988888880', '0', '', '$2a$10$1zMlbW7hGpzA59gpzWGO/ObeASziQ296evjMjHrYdZnxKBLU4WUum', '0', '0', '127.0.0.1', '2022-04-16 12:48:57', 'admin', '2022-04-15 16:22:08', '', '2022-04-16 12:48:57', NULL);
INSERT INTO `sys_user` VALUES (123, 100, 'wumei-user-one', '物美用户壹', '00', '', '13988888880', '0', '', '$2a$10$691RJMXZ9HM4sgNTExLPfO5Nw6J6cWgCvcoF9V.jKMnPk5o/8c9VS', '0', '0', '127.0.0.1', '2022-04-16 12:42:29', 'admin', '2022-04-15 16:22:37', 'admin', '2022-04-16 12:42:29', NULL); INSERT INTO `sys_user` VALUES (123, 100, 'wumei-user-one', '物美用户壹', '00', '', '13988888880', '0', '', '$2a$10$691RJMXZ9HM4sgNTExLPfO5Nw6J6cWgCvcoF9V.jKMnPk5o/8c9VS', '0', '0', '127.0.0.1', '2022-04-16 12:42:29', 'admin', '2022-04-15 16:22:37', 'admin', '2022-04-16 12:42:29', NULL);

View File

@@ -1,58 +1,57 @@
# 数据源配置 # 数据源配置
spring: spring:
datasource: datasource:
type: com.alibaba.druid.pool.DruidDataSource type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver driverClassName: com.mysql.cj.jdbc.Driver
druid: druid:
# 主库数据源 # 主库数据源
master: master:
url: jdbc:mysql://localhost/wumei-smart?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 url: jdbc:mysql://localhost/wumei-smart?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root username: root
password: wumei-smart password: admin
# password: 123456 # 从库数据源
# 从库数据源 slave:
slave: # 从数据源开关/默认关闭
# 从数据源开关/默认关闭 enabled: false
enabled: false url:
url: username:
username: password:
password: # 初始连接数
# 初始连接数 initialSize: 5
initialSize: 5 # 最小连接池数量
# 最小连接池数量 minIdle: 10
minIdle: 10 # 最大连接池数量
# 最大连接池数量 maxActive: 20
maxActive: 20 # 配置获取连接等待超时的时间
# 配置获取连接等待超时的时间 maxWait: 60000
maxWait: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 timeBetweenEvictionRunsMillis: 60000
timeBetweenEvictionRunsMillis: 60000 # 配置一个连接在池中最小生存的时间,单位是毫秒
# 配置一个连接在池中最小生存的时间,单位是毫秒 minEvictableIdleTimeMillis: 300000
minEvictableIdleTimeMillis: 300000 # 配置一个连接在池中最大生存的时间,单位是毫秒
# 配置一个连接在池中最大生存的时间,单位是毫秒 maxEvictableIdleTimeMillis: 900000
maxEvictableIdleTimeMillis: 900000 # 配置检测连接是否有效
# 配置检测连接是否有效 validationQuery: SELECT 1 FROM DUAL
validationQuery: SELECT 1 FROM DUAL testWhileIdle: true
testWhileIdle: true testOnBorrow: false
testOnBorrow: false testOnReturn: false
testOnReturn: false webStatFilter:
webStatFilter: enabled: true
enabled: true statViewServlet:
statViewServlet: enabled: true
enabled: true # 设置白名单,不填则允许所有访问
# 设置白名单,不填则允许所有访问 allow:
allow: url-pattern: /druid/*
url-pattern: /druid/* # 控制台管理用户名和密码
# 控制台管理用户名和密码 login-username: wumei-smart
login-username: wumei-smart login-password: wumei-smart
login-password: wumei-smart filter:
filter: stat:
stat: enabled: true
enabled: true # 慢SQL记录
# 慢SQL记录 log-slow-sql: true
log-slow-sql: true slow-sql-millis: 1000
slow-sql-millis: 1000 merge-sql: true
merge-sql: true wall:
wall: config:
config: multi-statement-allow: true
multi-statement-allow: true

View File

@@ -48,9 +48,9 @@ spring:
servlet: servlet:
multipart: multipart:
# 单个文件大小 # 单个文件大小
max-file-size: 10MB max-file-size: 10MB
# 设置总上传的文件大小 # 设置总上传的文件大小
max-request-size: 20MB max-request-size: 20MB
# 服务模块 # 服务模块
devtools: devtools:
restart: restart:
@@ -65,8 +65,7 @@ spring:
# 数据库索引 # 数据库索引
database: 0 database: 0
# 密码 # 密码
# password: wumei-smart password: wumei-smart
# password: 123456
# 连接超时时间 # 连接超时时间
timeout: 10s timeout: 10s
lettuce: lettuce:
@@ -83,7 +82,7 @@ spring:
mqtt: mqtt:
username: wumei-smart # 账号 username: wumei-smart # 账号
password: wumei-smart # 密码 password: wumei-smart # 密码
host-url: tcp://localhost:1883 # mqtt连接tcp地址 host-url: tcp://localhost:1883 # mqtt连接tcp地址
client-id: ${random.int} # 客户端Id不能相同采用随机数 ${random.value} client-id: ${random.int} # 客户端Id不能相同采用随机数 ${random.value}
default-topic: test # 默认主题 default-topic: test # 默认主题
timeout: 30000 # 超时时间 timeout: 30000 # 超时时间
@@ -134,4 +133,4 @@ xss:
# 排除链接(多个用逗号分隔) # 排除链接(多个用逗号分隔)
excludes: /system/notice excludes: /system/notice
# 匹配链接 # 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/* urlPatterns: /system/*,/monitor/*,/tool/*

View File

@@ -39,6 +39,7 @@
"@riophae/vue-treeselect": "0.4.0", "@riophae/vue-treeselect": "0.4.0",
"axios": "0.24.0", "axios": "0.24.0",
"clipboard": "2.0.6", "clipboard": "2.0.6",
"codemirror": "^5.65.2",
"core-js": "3.19.1", "core-js": "3.19.1",
"echarts": "^5.3.1", "echarts": "^5.3.1",
"element-ui": "2.15.6", "element-ui": "2.15.6",
@@ -48,13 +49,19 @@
"js-beautify": "1.13.0", "js-beautify": "1.13.0",
"js-cookie": "3.0.1", "js-cookie": "3.0.1",
"jsencrypt": "3.2.1", "jsencrypt": "3.2.1",
"jshint": "^2.13.4",
"jsonlint": "^1.6.3",
"less-loader": "^10.2.0",
"mqtt": "^4.3.3", "mqtt": "^4.3.3",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"quill": "^1.3.7", "quill": "^1.3.7",
"screenfull": "5.0.2", "screenfull": "5.0.2",
"script-loader": "^0.7.2",
"sortablejs": "1.10.2", "sortablejs": "1.10.2",
"sql-formatter": "^4.0.2",
"vue": "2.6.12", "vue": "2.6.12",
"vue-clipboard2": "^0.3.3", "vue-clipboard2": "^0.3.3",
"vue-codemirror": "^4.0.6",
"vue-count-to": "1.0.13", "vue-count-to": "1.0.13",
"vue-cropper": "0.5.5", "vue-cropper": "0.5.5",
"vue-json-viewer": "^2.2.21", "vue-json-viewer": "^2.2.21",

View File

@@ -122,3 +122,257 @@ export function getMqttStats() {
}, },
}) })
} }
//断开客户端连接
export function eliminateClient(clientId){
var url = "/api/v4/clients/"+clientId;
return axios({
method: 'delete',
url: url,
auth: {
username: username,
password: password
},
})
}
//查看客户端详情
export function getClientDetails(clientId){
var url = "/api/v4/clients/"+clientId;
return axios({
method: 'get',
url: url,
auth: {
username: username,
password: password
},
})
}
//查看集群下指定客户端的订阅信息
export function getSubscriptionsByClientId(clientId){
var url = "/api/v4/subscriptions/"+clientId;
return axios({
method: 'get',
url: url,
auth: {
username: username,
password: password
},
})
}
//取消该客户端订阅
export function unsubscribe(query){
var url = "/api/v4/mqtt/unsubscribe";
return axios({
method: 'post',
url: url,
auth: {
username: username,
password: password
},
params: query
})
}
//添加该客户端订阅
export function addSubscribe(query){
var url = "/api/v4/mqtt/subscribe";
return axios({
method: 'post',
url: url,
auth: {
username: username,
password: password
},
params: query
})
}
//获取所有规则引擎的动作
export function getRules(ruleid){
let url = "";
if(typeof(ruleid) == 'undefined' || ruleid == '' ||ruleid == null){
url = "/api/v4/rules";
}else{
url = "/api/v4/rules/"+ruleid;
}
return axios({
method: 'get',
url: url,
auth: {
username: username,
password: password
},
})
}
//删除规则
export function deleteRule(ruleid){
var url = "/api/v4/rules/"+ruleid;
return axios({
method: 'delete',
url: url,
auth: {
username: username,
password: password
},
})
}
//获取资源列表或详情
export function getResources(resourceid){
let url = "";
if(typeof(resourceid) == 'undefined' || resourceid == '' ||resourceid == null){
url = "/api/v4/resources";
}else{
url = "/api/v4/resources/"+resourceid;
}
return axios({
method: 'get',
url: url,
auth: {
username: username,
password: password
},
})
}
//获取资源状态
export function getResourcesStatus(resourceid){
let url = "/api/v4/resources/"+resourceid;
return axios({
method: 'get',
url: url,
auth: {
username: username,
password: password
},
})
}
//连接资源
export function getConnectResource(resourceid){
let url = "/api/v4/resources/"+resourceid;
return axios({
method: 'post',
url: url,
auth: {
username: username,
password: password
},
})
}
//删除资源
export function deleteResource(resourceid){
let url = "/api/v4/resources/"+resourceid;
return axios({
method: 'delete',
url: url,
auth: {
username: username,
password: password
},
})
}
//获取资源类型
export function getResourcesType(){
let url = "/api/v4/resource_types";
return axios({
method: 'get',
url: url,
auth: {
username: username,
password: password
},
})
}
//资源测试连接
export function getResourcesConnect(query){
let url = "/api/v4/resources?test=true";
return axios({
method: 'post',
url: url,
auth: {
username: username,
password: password
},
data: query
})
}
//新增资源
export function saveResources(query){
let url = "/api/v4/resources";
return axios({
method: 'post',
url: url,
auth: {
username: username,
password: password
},
data: query
})
}
//获取规则消息类型
export function getRulesEvent(){
let url = "/api/v4/rule_events";
return axios({
method: 'get',
url: url,
auth: {
username: username,
password: password
},
})
}
//获取响应动作类型
export function getActionsEvent(){
let url = "/api/v4/actions";
return axios({
method: 'get',
url: url,
auth: {
username: username,
password: password
},
})
}
//新增规则引擎
export function saveRule(query){
let url = "/api/v4/rules";
return axios({
method: 'post',
url: url,
auth: {
username: username,
password: password
},
data: query
})
}
//测试规则引擎
export function testConnectRule(query){
let url = "/api/v4/rules?test=true";
return axios({
method: 'post',
url: url,
auth: {
username: username,
password: password
},
data: query
})
}

View File

@@ -0,0 +1,129 @@
<template>
<div class="json-editor">
<textarea ref="textarea" />
</div>
</template>
<script>
// import sqlFormatter from "sql-formatter";
import CodeMirror from "codemirror";
import "codemirror/lib/codemirror.css";
import "codemirror/mode/sql/sql.js";
// 替换主题这里需修改名称
import "codemirror/theme/idea.css";
// 支持代码自动补全
import "codemirror/addon/hint/show-hint.css";
import "codemirror/addon/hint/show-hint.js";
import "codemirror/addon/hint/anyword-hint.js";
// JSON代码高亮需要由JavaScript插件支持
import "codemirror/mode/javascript/javascript.js";
// 支持各种代码折叠
import "codemirror/addon/fold/foldgutter.css";
import "codemirror/addon/fold/foldcode.js";
import "codemirror/addon/fold/foldgutter.js";
import "codemirror/addon/fold/brace-fold.js";
import "codemirror/addon/fold/comment-fold.js";
// 支持括号自动匹配
import "codemirror/addon/edit/matchbrackets.js";
import "codemirror/addon/edit/closebrackets.js";
// 行注释
import "codemirror/addon/comment/comment.js";
// JSON错误检查
import "codemirror/addon/lint/lint.css";
import "codemirror/addon/lint/lint.js";
export default {
props: {
value: '',
height: {
type: String,
required: true,
},
myMode: {
type: String,
required: true,
},
},
data() {
return {
editor: false,
};
},
watch: {
value(value) {
const editorValue = this.editor.getValue();
if (value !== editorValue) {
if (typeof this.value !== "undefined") {
this.editor.setValue(this.value);
} else {
this.editor.setValue("");
}
}
},
},
mounted() {
this.editor = CodeMirror.fromTextArea(this.$refs.textarea, {
mode: this.myMode, //语言
smartIndent: true, // 是否智能缩进
styleActiveLine: true, // 当前行高亮
lineNumbers: true, // 是否显示行数
indentUnit: 2, // 缩进单位默认2
gutters: [
"CodeMirror-linenumbers",
"CodeMirror-foldgutter",
"CodeMirror-lint-markers", // CodeMirror-lint-markers是实现语法报错功能
],
lint: true,
//lineWrapping: true, // 自动换行
matchBrackets: true, // 括号匹配显示
autoCloseBrackets: true, // 输入和退格时成对
readOnly: false, // 只读
foldGutter: true,
autoRefresh: true,
});
//代码自动提示功能记住使用cursorActivity事件不要使用change事件这是一个坑那样页面直接会卡死
this.editor.on("inputRead", () => {
this.editor.showHint();
});
debugger;
this.editor.setSize("auto", this.height);
if (typeof this.value !== "undefined") {
this.editor.setValue(this.value);
} else {
this.editor.setValue("");
}
},
methods: {
getValue() {
return this.editor.getValue();
},
// formatSql() {
// /*sql编辑器内容绑定content参数 将sql内容进行格式后放入编辑器中*/
// this.content = sqlFormatter.format(this.content);
// },
},
};
</script>
<style scoped>
.json-editor {
height: 100%;
}
.json-editor >>> .CodeMirror {
font-size: 14px;
/* overflow-y:auto; */
font-weight: normal;
}
.json-editor >>> .CodeMirror-scroll {
}
.json-editor >>> .cm-s-rubyblue span.cm-string {
color: #f08047;
}
</style>

View File

@@ -40,6 +40,10 @@ import echarts from 'echarts'
// 一键复制粘贴板组件 // 一键复制粘贴板组件
import VueClipboard from 'vue-clipboard2' import VueClipboard from 'vue-clipboard2'
// 全局方法挂载 // 全局方法挂载
Vue.prototype.getDicts = getDicts Vue.prototype.getDicts = getDicts
Vue.prototype.getConfigKey = getConfigKey Vue.prototype.getConfigKey = getConfigKey
@@ -65,6 +69,7 @@ Vue.use(plugins)
Vue.use(VueMeta) Vue.use(VueMeta)
DictData.install() DictData.install()
/** /**
* If you don't want to use mock-server * If you don't want to use mock-server
* you want to use MockJs for mock api * you want to use MockJs for mock api

View File

@@ -31,8 +31,6 @@ export default {
}, },
data() { data() {
return { return {
// 设备信息
deviceInfo: {},
}; };
}, },
created() { created() {
@@ -47,11 +45,11 @@ export default {
cleanSession: false, cleanSession: false,
keepAlive: 30, keepAlive: 30,
clientId: 'web-' + Math.random().toString(16).substr(2), clientId: 'web-' + Math.random().toString(16).substr(2),
connectTimeout: 60000 connectTimeout: 10000
} }
// 配置Mqtt地址 // 配置Mqtt地址
//let url = "wss://iot.wumei.live/mqtt" let url = "wss://iot.wumei.live/mqtt"
let url = "ws://" + window.location.hostname + ":8083/mqtt"; // let url = "ws://" + window.location.hostname + ":8083/mqtt";
console.log("mqtt地址", url); console.log("mqtt地址", url);
this.client = mqtt.connect(url, options); this.client = mqtt.connect(url, options);
this.client.on("connect", (e) => { this.client.on("connect", (e) => {

View File

@@ -1,7 +1,7 @@
<template> <template>
<div style="padding:6px;"> <div style="padding: 6px">
<el-card v-show="showSearch" style="margin-bottom:6px;"> <el-card v-show="showSearch" style="margin-bottom: 6px">
<el-form :model="queryParams" ref="queryForm" :inline="true" label-width="68px" style="margin-bottom:-20px;"> <el-form :model="queryParams" ref="queryForm" :inline="true" label-width="68px" style="margin-bottom: -20px">
<el-form-item label="客户端" prop="categoryName"> <el-form-item label="客户端" prop="categoryName">
<el-input v-model="queryParams.categoryName" placeholder="请输入客户端ID" clearable size="small" @keyup.enter.native="handleQuery" /> <el-input v-model="queryParams.categoryName" placeholder="请输入客户端ID" clearable size="small" @keyup.enter.native="handleQuery" />
</el-form-item> </el-form-item>
@@ -12,21 +12,21 @@
</el-form> </el-form>
</el-card> </el-card>
<el-card style="padding-bottom:100px;"> <el-card style="padding-bottom: 100px">
<el-table v-loading="loading" :data="clientList"> <el-table v-loading="loading" :data="clientList">
<el-table-column label="客户端ID" align="left" header-align="center" prop="clientid"> <el-table-column label="客户端ID" align="left" header-align="center" prop="clientid">
<template slot-scope="scope"> <template slot-scope="scope">
<el-link :underline="false" type="primary">{{scope.row.clientid}}</el-link> <el-link :underline="false" type="primary" @click.native="handleOpen(scope.row)">{{ scope.row.clientid }}</el-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="节点" align="center" prop="node" width="120" /> <el-table-column label="节点" align="center" prop="node" width="120" />
<el-table-column label="IP地址" align="center" prop="ip_address" /> <el-table-column label="IP地址" align="center" prop="ip_address" />
<el-table-column label="类型" align="center" prop="type"> <el-table-column label="类型" align="center" prop="type">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag type="danger" v-if="scope.row.clientid.indexOf('server')==0">服务端</el-tag> <el-tag type="danger" v-if="scope.row.clientid.indexOf('server') == 0">服务端</el-tag>
<el-tag type="success" v-else-if="scope.row.clientid.indexOf('web')==0">Web端</el-tag> <el-tag type="success" v-else-if="scope.row.clientid.indexOf('web') == 0">Web端</el-tag>
<el-tag type="warning" v-else-if="scope.row.clientid.indexOf('phone')==0">移动端</el-tag> <el-tag type="warning" v-else-if="scope.row.clientid.indexOf('phone') == 0">移动端</el-tag>
<el-tag type="info" v-else-if="scope.row.clientid.indexOf('test')==0">测试端</el-tag> <el-tag type="info" v-else-if="scope.row.clientid.indexOf('test') == 0">测试端</el-tag>
<el-tag type="primary" v-else>设备端</el-tag> <el-tag type="primary" v-else>设备端</el-tag>
</template> </template>
</el-table-column> </el-table-column>
@@ -43,46 +43,137 @@
<el-table-column label="会话创建时间" align="center" prop="created_at" /> <el-table-column label="会话创建时间" align="center" prop="created_at" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button size="small" type="danger" v-if="scope.row.connected" style="padding:5px;" v-hasPermi="['monitor:online:edit']"> <el-button size="small" type="danger" v-if="scope.row.connected" style="padding: 5px" v-hasPermi="['monitor:client:edit']" @click="handleDelete(scope.row)">
<svg-icon icon-class="disconnect" /> 断开连接 <svg-icon icon-class="disconnect" /> 断开连接
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams._page" :limit.sync="queryParams._limit" @pagination="getList" /> <pagination v-show="total > 0" :total="total" :page.sync="queryParams._page" :limit.sync="queryParams._limit" @pagination="getList" />
<!-- 添加或修改产品分类对话框 --> <!-- MQTT客户端详细 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> <el-dialog :title="title" :visible.sync="open" width="50%" append-to-body>
<el-form ref="form" :model="form" label-width="80px"> <el-tabs v-model="activeName" tab-position="top" style="padding: 10px">
<el-form-item label="分类名称" prop="categoryName"> <el-tab-pane name="basic">
<el-input v-model="form.categoryName" placeholder="请输入产品分类名称" /> <span slot="label">基本信息</span>
</el-form-item> <el-form ref="form" :model="form" label-width="120px" size="mini">
<el-form-item label="显示顺序" prop="orderNum">
<el-input v-model="form.orderNum" placeholder="请输入显示顺序" /> <el-descriptions class="margin-top" title="基本信息" :column="4" direction="vertical">
</el-form-item> <el-descriptions-item label="节点">{{form.node }}</el-descriptions-item>
</el-form> <el-descriptions-item label="客户端ID">{{form.clientid}}</el-descriptions-item>
<div slot="footer" class="dialog-footer"> <el-descriptions-item label="Clean Session">{{form.clean_start}}</el-descriptions-item>
<el-button type="primary" @click="cancel"> </el-button> <el-descriptions-item label="会话过期间隔(秒)">{{form.expiry_interval}}</el-descriptions-item>
<el-button @click="cancel"> </el-button> <el-descriptions-item label="用户名">{{form.username}}</el-descriptions-item>
</div> <el-descriptions-item label="协议类型">{{form.proto_ver}}</el-descriptions-item>
<el-descriptions-item label="会话创建时间">{{form.created_at}}</el-descriptions-item>
<el-descriptions-item label="订阅数量">{{ form.subscriptions_cnt }}/{{form.max_subscriptions}}</el-descriptions-item>
<el-descriptions-item label="IP地址">{{form.ip_address}}</el-descriptions-item>
<el-descriptions-item label="端口">{{form.port}}</el-descriptions-item>
<el-descriptions-item label="最大订阅数量">{{form.max_subscriptions}}</el-descriptions-item>
<el-descriptions-item label="飞行窗口">{{ form.inflight }}/{{ form.max_inflight }}</el-descriptions-item>
<el-descriptions-item label="心跳(秒)">{{form.keepalive}}</el-descriptions-item>
<el-descriptions-item label="是否为桥接">{{form.is_bridge}}</el-descriptions-item>
<el-descriptions-item label="最大飞行窗口">{{form.max_inflight}}</el-descriptions-item>
<el-descriptions-item label="消息队列">{{ form.mqueue_len }}/{{ form.max_mqueue }}</el-descriptions-item>
<el-descriptions-item label="连接时间">{{form.connected_at}}</el-descriptions-item>
<el-descriptions-item label="连接状态">
<div v-if="form.connected == true" style="color: green">
已连接
</div>
<div v-else-if="form.connected == false" style="color: red">
已断开
</div>
</el-descriptions-item>
<el-descriptions-item label="最大消息队列">{{form.max_mqueue}}</el-descriptions-item>
<el-descriptions-item label="未确认的PUBREC数据包计数">{{form.awaiting_rel}}</el-descriptions-item>
<el-descriptions-item label="Zone">{{form.zone}}</el-descriptions-item>
<el-descriptions-item label="最大未确认的PUBREC数据包计数">{{form.max_awaiting_rel}}</el-descriptions-item>
<el-descriptions-item label="接收的TCP报文数量">{{form.recv_cnt}}</el-descriptions-item>
<el-descriptions-item label="接收的PUBLISH报文数量">{{form.recv_msg}}</el-descriptions-item>
<el-descriptions-item label="接收的字节数量">{{form.recv_oct}}</el-descriptions-item>
<el-descriptions-item label="接收的MQTT报文数量">{{form.recv_pkt}}</el-descriptions-item>
<el-descriptions-item label="发送的TCP报文数量">{{form.send_cnt}}</el-descriptions-item>
<el-descriptions-item label="发送的PUBLISH报文数量">{{form.send_msg}}</el-descriptions-item>
<el-descriptions-item label="发送的字节数量">{{form.send_oct}}</el-descriptions-item>
<el-descriptions-item label="发送的MQTT报文数量">{{form.send_pkt}}</el-descriptions-item>
</el-descriptions>
</el-form>
</el-tab-pane>
<el-tab-pane name="subscribe">
<span slot="label">订阅列表</span>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-refresh" size="mini" @click="handleRefresh" v-hasPermi="['monitor:subscribe:refresh']">刷新</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-plus" size="mini" :disabled="single" @click="handleAdd" v-hasPermi="['monitor:subscribe:add']">添加订阅</el-button>
</el-col>
</el-row>
<el-table v-loading="loadSubscribeing" :data="subscribeList">
<el-table-column label="主题" align="center" prop="topic" />
<el-table-column label="QoS" align="center" prop="qos" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
<template slot-scope="scope">
<el-button size="small" type="danger" style="padding: 5px" v-hasPermi="['monitor:subscribe:delete']" @click="handleUnsubscribe(scope.row)">
<svg-icon icon-class="disconnect" /> 取消订阅
</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</el-dialog> </el-dialog>
</el-card> </el-card>
<!-- 添加或修改订阅对话框 -->
<el-dialog title="添加订阅" :visible.sync="subscribeOpen" width="800px" append-to-body>
<el-form ref="subscribeForm" :model="subscribeForm" :rules="rules" label-width="60px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="主题" prop="topic">
<el-input v-model="subscribeForm.topic" placeholder="请输入主题" />
</el-form-item>
<el-form-item label="Qos" prop="qos">
<el-select v-model="subscribeForm.qos" placeholder="请选择消息类型">
<el-option key="0" label="0" value="0"></el-option>
<el-option key="1" label="1" value="1"></el-option>
<el-option key="2" label="2" value="2"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancelSubscribe"> </el-button>
</div>
</el-dialog>
</div> </div>
</template> </template>
<script> <script>
import { import {
listMqttClient listMqttClient,
} from "@/api/iot/emqx" eliminateClient,
getClientDetails,
getSubscriptionsByClientId,
unsubscribe,
addSubscribe,
} from "@/api/iot/emqx";
export default { export default {
name: "Category", name: "Category",
data() { data() {
return { return {
// 非单个禁用
single: true,
// 遮罩层 // 遮罩层
loading: true, loading: true,
loadSubscribeing: true,
// 显示搜索条件 // 显示搜索条件
showSearch: true, showSearch: true,
// 总条数 // 总条数
@@ -93,6 +184,8 @@ export default {
title: "", title: "",
// 是否显示弹出层 // 是否显示弹出层
open: false, open: false,
// 是否显示添加订阅弹出层
subscribeOpen: false,
// 查询参数 // 查询参数
queryParams: { queryParams: {
_limit: 10, _limit: 10,
@@ -100,22 +193,54 @@ export default {
}, },
// 表单参数 // 表单参数
form: {}, form: {},
// 选中选项卡
activeName: "basic",
//订阅列表数据
subscribeList: [],
//订阅数据
subscribe: {
topic: "",
clientid: "",
},
//添加订阅表单参数
subscribeForm: {
qos: "0",
},
//客户端ID
clientid: "",
// 表单校验
rules: {
topic: [{
required: true,
message: "主题不能为空",
trigger: "blur",
}, ],
},
}; };
}, },
created() { created() {
this.getList(); this.getList();
}, },
methods: { methods: {
/** 查询客户端列表 */ /** 查询客户端列表 */
getList() { getList() {
this.loading = true; this.loading = true;
listMqttClient(this.queryParams).then(response => { listMqttClient(this.queryParams).then((response) => {
this.clientList = response.data.data; this.clientList = response.data.data;
this.total = response.data.meta.count; this.total = response.data.meta.count;
this.loading = false; this.loading = false;
}); });
}, },
/** 查询客户端订阅列表 */
getSubscribeList(clientid) {
this.clientid = clientid;
this.loadSubscribeing = true;
getSubscriptionsByClientId(clientid).then((res) => {
this.subscribeList = res.data.data;
this.loadSubscribeing = false;
});
},
// 取消按钮 // 取消按钮
cancel() { cancel() {
this.open = false; this.open = false;
@@ -131,6 +256,76 @@ export default {
this.resetForm("queryForm"); this.resetForm("queryForm");
this.handleQuery(); this.handleQuery();
}, },
} /** 断开客户端连接 */
handleDelete(row) {
const clientid = row.clientid;
this.$modal
.confirm('是否确认删除MQTT客户端编号为"' + clientid + '"的数据项?')
.then(function () {
return eliminateClient(clientid);
})
.then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
})
.catch(() => {});
},
//取消主题订阅
handleUnsubscribe(row) {
const clientid = row.clientid;
const topic = row.topic;
this.$modal
.confirm('是否确认取消订阅主题为"' + topic + '"的数据项?')
.then(function () {
const param = {};
param.topic = topic;
param.clientid = clientid;
return unsubscribe(param);
})
.then(() => {
this.getSubscribeList(clientid);
this.$modal.msgSuccess("取消订阅成功");
})
.catch(() => {});
},
//查看客户端详情
handleOpen(row) {
const clientid = row.clientid;
this.getSubscribeList(clientid);
getClientDetails(clientid).then((response) => {
this.form = response.data.data[0];
this.open = true;
this.title = "详情";
});
},
//刷新订阅列表
handleRefresh() {
this.getSubscribeList(this.clientid);
},
//添加订阅
handleAdd() {
this.subscribeOpen = true;
},
//提交添加订阅表单
submitForm() {
this.subscribeForm.clientid = this.clientid;
console.log(this.subscribeForm);
this.$refs["subscribeForm"].validate((valid) => {
if (valid) {
addSubscribe(this.subscribeForm).then((response) => {
this.$modal.msgSuccess("新增订阅成功");
this.subscribeOpen = false;
this.getSubscribeList(this.clientid);
});
}
});
},
cancelSubscribe() {
this.subscribeOpen = false;
this.resetForm("subscribeForm");
//刷新列表
this.getSubscribeList(this.clientid);
},
},
}; };
</script> </script>

View File

@@ -0,0 +1,867 @@
<template>
<div style="padding: 6px">
<el-card style="padding-bottom: 100px">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-refresh" size="mini" @click="getList" v-hasPermi="['monitor:resource:refresh']">刷新</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="addResource" v-hasPermi="['monitor:resource:add']">新增</el-button>
</el-col>
</el-row>
<el-table v-loading="loading" :data="resourceList">
<el-table-column label="ID" align="center" header-align="center" prop="id">
<template slot-scope="scope">
<el-link :underline="false" type="primary">{{ scope.row.id }}</el-link>
</template>
</el-table-column>
<el-table-column label="资源类型" align="center" prop="type" />
<el-table-column label="备注" align="center" prop="description" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="small" type="text" style="padding: 5px" v-hasPermi="['monitor:resource:query']" @click="handleQuery(scope.row)">
查看
</el-button>
<el-button size="small" type="text" icon="el-icon-delete" style="padding: 5px" v-hasPermi="['monitor:resource:delete']" @click="handleDelete(scope.row)">删除
</el-button>
<el-button size="small" type="text" icon="el-icon-delete" style="padding: 5px" v-hasPermi="['monitor:resource:checkStatus']" @click="checkStatus(scope.row)">状态
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 资源详细 -->
<el-dialog title="资源详细" :visible.sync="openView" width="50%" append-to-body>
<el-form ref="form" :model="form" label-width="180px" size="mini">
<el-card style="padding-bottom: 10px">
<div slot="header" class="clearfix">
<span>基础信息</span>
</div>
<el-row>
<el-col :span="20">
<el-form-item label="ID">{{ form.id }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="资源类型:">{{ form.type }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="备注:">{{ form.description }}</el-form-item>
</el-col>
</el-row>
</el-card>
<el-card style="padding-bottom: 10px; margin-top: 10px">
<div slot="header" class="clearfix">
<span>配置信息</span>
</div>
<el-row>
<el-col :span="20">
<el-form-item label="reconnect_interval">{{ form.config.reconnect_interval}}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="pool_size">{{ form.config.pool_size }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="mountpoint">{{ form.config.mountpoint}}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="disk_cache">{{ form.config.disk_cache }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="batch_size">{{ form.config.batch_size }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="address">{{ form.config.address}}</el-form-item>
</el-col>
</el-row>
</el-card>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="openView = false" type="success"> </el-button>
</div>
</el-dialog>
<!-- 测试重连 -->
<el-dialog title="检测状态" :visible.sync="openStatusView" width="40%" append-to-body>
<el-form ref="statusForm" :model="statusForm" label-width="180px" size="mini">
<el-row>
<el-col :span="12" v-if="statusForm.status[0]">
{{ statusForm.status[0].node }}
<el-tag type="success" v-if="statusForm.status[0].is_alive == true" style="margin-left: 10px">可用</el-tag>
<el-tag type="danger" v-if="statusForm.status[0].is_alive == false" style="margin-left: 10px">不可用</el-tag>
<el-button size="small" type="primary" icon="el-icon-connection" style="padding: 5px; margin-left: 10px" v-hasPermi="['monitor:resource:connect']" @click="checkNode(statusForm.id)">重新连接
</el-button>
</el-col>
</el-row>
</el-form>
</el-dialog>
<!-- 添加资源 -->
<el-dialog title="资源管理" :visible.sync="openAddView" width="50%" append-to-body :before-close="cancel">
<el-form ref="addResourceForm" :model="addResourceForm" label-width="180px" :rules="rule">
<el-card style="padding-bottom: 10px">
<div slot="header" class="clearfix">
<span>择取资源类型</span>
</div>
<el-row>
<el-col :span="12">
<el-form-item label="资源类型" prop="resource.title">
<el-select v-model="addResourceForm.resource.title" @change="selectTitle" placeholder="请选择资源类型">
<el-option v-for="(resource, index) in addResourceForm.resource" :key="index" :label="resource.title.zh" :value="resource.name"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<el-button type="success" @click="testConnect('addResourceForm')">测试连接</el-button>
</el-form-item>
</el-col>
</el-row>
</el-card>
</el-form>
<el-form ref="EMQXForm" :model="EMQXForm" label-width="180px" :rules="ruleEMQX">
<el-card style="padding-bottom: 10px; margin-top: 10px" v-if="EMQXForm.params" v-show="openEMQXView">
<div slot="header" class="clearfix">
<span>具体信息</span>
</div>
<el-row>
<el-col :span="10">
<el-form-item prop="params.address.default">
<span slot="label">
<el-tooltip :content="EMQXForm.params.address.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
EMQ X节点名称
</span>
<el-input v-model="EMQXForm.params.address.default" />
</el-form-item>
<el-form-item prop="params.pool_size.default">
<span slot="label">
<el-tooltip :content="EMQXForm.params.pool_size.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
连接池大小
</span>
<el-input v-model="EMQXForm.params.pool_size.default" />
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item prop="params.mountpoint.default">
<span slot="label">
<el-tooltip :content="EMQXForm.params.mountpoint.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
桥接挂载点
</span>
<el-input v-model="EMQXForm.params.mountpoint.default" />
</el-form-item>
<el-form-item prop="EMQreconnect_interval">
<span slot="label">
<el-tooltip :content="EMQXForm.params.reconnect_interval.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
重连间隔
</span>
<el-input v-model="EMQXForm.params.reconnect_interval.default" />
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item prop="EMQbatch_size">
<span slot="label">
<el-tooltip :content="EMQXForm.params.batch_size.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
批处理大小
</span>
<el-input v-model="EMQXForm.params.batch_size.default" />
</el-form-item>
<el-form-item label="备注:">
<el-input v-model="EMQXForm.description" />
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item prop="EMQdisk_cache">
<span slot="label">
<el-tooltip :content="EMQXForm.params.disk_cache.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
磁盘缓存
</span>
<el-select v-model="EMQXForm.params.disk_cache.default">
<el-option v-for="(enums, index) in EMQXForm.params.disk_cache.enum" :key="index" :label="enums" :value="enums"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-card>
</el-form>
<el-form ref="MQTTForm" :model="MQTTForm" label-width="180px" :rules="ruleMQTT">
<el-card style="padding-bottom: 10px; margin-top: 10px" v-if="MQTTForm.params" v-show="openMQTTView">
<div slot="header" class="clearfix">
<span>具体信息</span>
</div>
<el-row>
<el-col :span="10">
<el-form-item prop="params.address.default">
<span slot="label">
<el-tooltip :content="MQTTForm.params.address.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
远程broker地址
</span>
<el-input v-model="MQTTForm.params.address.default" />
</el-form-item>
<el-form-item>
<span slot="label">
<el-tooltip :content="MQTTForm.params.disk_cache.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
磁盘缓存
</span>
<el-select v-model="MQTTForm.params.disk_cache.default">
<el-option v-for="(enums, index) in MQTTForm.params.disk_cache.enum" :key="index" :label="enums" :value="enums"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item>
<span slot="label">
<el-tooltip :content="MQTTForm.params.proto_ver.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
协议版本
</span>
<el-select v-model="MQTTForm.params.proto_ver.default">
<el-option v-for="(enums, index) in MQTTForm.params.proto_ver.enum" :key="index" :label="enums" :value="enums"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<span slot="label">
<el-tooltip :content="MQTTForm.params.clientid.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
客户端ID
</span>
<el-input v-model="MQTTForm.params.clientid.default" />
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item>
<span slot="label">
<el-tooltip :content="MQTTForm.params.username.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
用户名
</span>
<el-input v-model="MQTTForm.params.username.default" />
</el-form-item>
<el-form-item prop="params.mountpoint.default">
<span slot="label">
<el-tooltip :content="MQTTForm.params.mountpoint.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
桥接挂载点
</span>
<el-input v-model="MQTTForm.params.mountpoint.default" />
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item>
<span slot="label">
<el-tooltip :content="MQTTForm.params.password.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
密码
</span>
<el-input v-model="MQTTForm.params.password.default" />
</el-form-item>
<el-form-item prop="params.keepalive.default">
<span slot="label">
<el-tooltip :content="MQTTForm.params.keepalive.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
心跳间隔
</span>
<el-input v-model="MQTTForm.params.keepalive.default" />
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item>
<span slot="label">
<el-tooltip :content="MQTTForm.params.reconnect_interval.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
重连间隔
</span>
<el-input v-model="MQTTForm.params.reconnect_interval.default" />
</el-form-item>
<el-form-item>
<span slot="label">
<el-tooltip :content="MQTTForm.params.bridge_mode.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
桥接模式
</span>
<el-select v-model="MQTTForm.params.bridge_mode.default">
<el-option key="false" label="false" value="false"></el-option>
<el-option key="true" label="true" value="true"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item>
<span slot="label">
<el-tooltip :content="MQTTForm.params.retry_interval.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
重传间隔
</span>
<el-input v-model="MQTTForm.params.retry_interval.default" />
</el-form-item>
<el-form-item prop="params.ssl.default">
<span slot="label">
<el-tooltip :content="MQTTForm.params.ssl.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
Bridge SSL
</span>
<el-select v-model="MQTTForm.params.ssl.default">
<el-option v-for="(enums, index) in MQTTForm.params.ssl.enum" :key="index" :label="enums" :value="enums"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item>
<span slot="label">
<el-tooltip :content="MQTTForm.params.cacertfile.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
CA证书
</span>
<el-input v-model="MQTTForm.params.cacertfile.default" />
</el-form-item>
<el-form-item>
<span slot="label">
<el-tooltip :content="MQTTForm.params.keyfile.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
SSL 密钥文件
</span>
<el-input v-model="MQTTForm.params.keyfile.default" />
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item>
<span slot="label">
<el-tooltip :content="MQTTForm.params.certfile.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
SSL 客户端证书
</span>
<el-input v-model="MQTTForm.params.certfile.default" />
</el-form-item>
<el-form-item>
<span slot="label">
<el-tooltip :content="MQTTForm.params.ciphers.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
SSL 加密算法
</span>
<el-input v-model="MQTTForm.params.ciphers.default" />
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="备注:">
<el-input v-model="MQTTForm.description" />
</el-form-item>
</el-col>
</el-row>
</el-card>
</el-form>
<el-form ref="WebHookForm" :model="WebHookForm" label-width="180px" :rules="ruleWebHook">
<el-card style="padding-bottom: 10px; margin-top: 10px" v-if="WebHookForm.params" v-show="openWebView">
<div slot="header" class="clearfix">
<span>具体信息</span>
</div>
<el-row>
<el-col :span="12">
<el-form-item prop="params.url.default">
<span slot="label">
<el-tooltip :content="WebHookForm.params.url.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
请求 URL
</span>
<el-input v-model="WebHookForm.params.url.default" placeholder="http://" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="请求方法:">
<span slot="label">
<el-tooltip :content="WebHookForm.params.method.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
请求方法
</span>
<el-select v-model="WebHookForm.params.method.default">
<el-option v-for="(enums, index) in WebHookForm.params.method.enum" :key="index" :label="enums" :value="enums"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="20">
<el-form-item label="请求头:" prop="ket_value">
<el-row v-for="(item, index) in ket_value" :key="index" style="margin-bottom: 10px">
<el-col :span="8">
<el-input v-model="item.key" placeholder="键" />
</el-col>
<el-col :span="12" :offset="1">
<el-input v-model="item.value" placeholder="值" />
</el-col>
<el-col :span="2" :offset="1" v-if="index != 0"><a style="color: #f56c6c" @click="removeHeaderItem(index)">删除</a></el-col>
</el-row>
<div>
+
<a style="color: #409eff" @click="addHeader()">添加请求头</a>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备注:">
<el-input v-model="WebHookForm.description" />
</el-form-item>
</el-col>
</el-row>
</el-card>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="cancel"> </el-button>
<el-button type="success" @click="saveResource('EMQXForm')" v-if="openEMQXView" :loading="showloading">新建</el-button>
<el-button type="success" @click="saveResource('MQTTForm')" v-if="openMQTTView" :loading="showloading">新建</el-button>
<el-button type="success" @click="saveResource('WebHookForm')" v-if="openWebView" :loading="showloading">新建</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
getResources,
getResourcesStatus,
getConnectResource,
deleteResource,
getResourcesType,
getResourcesConnect,
saveResources,
} from "@/api/iot/emqx";
export default {
name: "Resource",
data() {
return {
// 遮罩层
loading: true,
// 新建按钮等待效果
showloading: false,
// 总条数
total: 0,
// 规则表格数据
resourceList: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 是否显示详细弹出层
openView: false,
// 是否显示检测状态弹出层
openStatusView: false,
// 是否显示添加资源弹出层
openAddView: false,
// 是否显示EMQX Bridge弹出层
openEMQXView: false,
// 是否显示MQTT Bridge弹出层
openMQTTView: false,
// 是否显示WebHook弹出层
openWebView: false,
//表单数据
form: {
config: {},
},
statusForm: {
status: [],
},
//添加资源表单数据
addResourceForm: {
resource: [],
type: "",
},
//添加EMQX资源表单数据
EMQXForm: {
description: "",
},
//添加MQTT资源表单数据
MQTTForm: {
description: "",
},
//添加WebHook资源表单数据
WebHookForm: {
description: "",
},
ket_value: [],
//添加资源表单数据
emqxParam: {
description: "",
name: "",
type: "",
config: {},
},
ruleEMQX: {
params: {
address: {
default: [{
required: true,
message: "请输入EMQ X节点名称",
trigger: "blur",
}, ],
},
pool_size: {
default: [{
required: true,
message: "请输入连接池大小",
trigger: "blur",
}, ],
},
mountpoint: {
default: [{
required: true,
message: "请输入桥接挂载点",
trigger: "blur",
}, ],
},
},
},
ruleMQTT: {
params: {
address: {
default: [{
required: true,
message: "请输入远程 broker 地址",
trigger: "blur",
}, ],
},
mountpoint: {
default: [{
required: true,
message: "请输入桥接挂载点",
trigger: "blur",
}, ],
},
ssl: {
default: [{
required: true,
message: "请选择Bridge SSL",
trigger: "change",
}, ],
},
keepalive: {
default: [{
required: true,
message: "请输入心跳间隔",
trigger: "blur",
}, ],
},
},
},
ruleWebHook: {
params: {
url: {
default: [{
required: true,
message: "请输入请求 URL",
trigger: "blur",
}, ],
},
},
},
rule: {
resource: {
title: [{
required: true,
message: "请选择资源类型",
trigger: "change"
}, ],
},
},
};
},
created() {
this.getList();
},
methods: {
/** 查询规则列表 */
getList() {
this.loading = true;
getResources("").then((response) => {
this.resourceList = response.data.data;
this.loading = false;
});
},
/** 查看按钮操作 */
handleQuery(row) {
this.form = row;
this.openView = true;
},
/** 状态按钮操作 */
checkStatus(row) {
let resourceId = row.id;
getResourcesStatus(resourceId).then((response) => {
this.statusForm = response.data.data;
this.openStatusView = true;
});
},
//删除按钮操作
handleDelete(row) {
let resourceId = row.id;
this.$modal
.confirm('是否确认删除ID为"' + resourceId + '"的规则引擎?')
.then(function () {
return deleteResource(resourceId);
})
.then(() => {
this.getList();
this.$modal.msgSuccess("删除资源成功");
})
.catch(() => {});
},
/** 重新连接资源按钮操作 */
checkNode(resourceId) {
getConnectResource(resourceId).then((response) => {
let code = response.data.code;
if (code !== 0) {
this.$modal.msgError(response.data.message);
} else {
this.$modal.msgSuccess("连接资源成功");
}
});
},
/** 跳转添加资源页面 */
addResource() {
getResourcesType().then((res) => {
this.addResourceForm.resource = res.data.data;
this.EMQXForm.params = res.data.data[0].params;
this.MQTTForm.params = res.data.data[1].params;
this.WebHookForm.params = res.data.data[2].params;
this.openAddView = true;
});
},
/** 选择资源类型 */
selectTitle(val) {
this.addResourceForm.type = val;
if ("bridge_rpc" === val) {
this.openEMQXView = true;
this.openMQTTView = false;
this.openWebView = false;
} else if ("bridge_mqtt" === val) {
this.openEMQXView = false;
this.openMQTTView = true;
this.openWebView = false;
} else if ("web_hook" === val) {
this.openEMQXView = false;
this.openMQTTView = false;
this.openWebView = true;
}
},
// 取消按钮
cancel() {
//初始化
this.openEMQXView = false;
this.openMQTTView = false;
this.openWebView = false;
this.openAddView = false;
this.EMQXForm = {};
this.MQTTForm = {};
this.WebHookForm = {};
this.addResourceForm.resource = [];
this.addResourceForm.description = "";
this.$refs['addResourceForm'].resetFields();
},
//测试连接
testConnect(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.insertOrTestResourceUtils('test');
} else {
return false;
}
});
},
/** 添加请求头 */
addHeader() {
this.ket_value.push({
key: "",
value: "",
});
},
/** 删除请求头 */
removeHeaderItem(index) {
this.ket_value.splice(index, 1);
},
//新建资源
saveResource(formName) {
this.showloading = true;
//校验表单
this.$refs[formName].validate((valid) => {
if (valid) {
this.insertOrTestResourceUtils('save');
} else {
return false;
}
});
},
// 新增资源或者测试连接公共方法
insertOrTestResourceUtils(type) {
this.emqxParam.type = this.addResourceForm.type;
//判断选择的资源类型
if ("bridge_rpc" === this.addResourceForm.type) {
this.emqxParam.description = this.EMQXForm.description;
this.emqxParam.config.address = this.EMQXForm.params.address.default;
this.emqxParam.config.batch_size =
this.EMQXForm.params.batch_size.default;
this.emqxParam.config.disk_cache =
this.EMQXForm.params.disk_cache.default;
this.emqxParam.config.mountpoint =
this.EMQXForm.params.mountpoint.default;
this.emqxParam.config.pool_size =
this.EMQXForm.params.pool_size.default;
this.emqxParam.config.reconnect_interval =
this.EMQXForm.params.reconnect_interval.default;
if ("test" == type) {
getResourcesConnect(this.emqxParam).then((res) => {
let code = res.data.code;
if (0 == code) {
this.$modal.msgSuccess("连接成功");
} else {
this.$modal.msgError(res.data.message);
}
});
} else {
saveResources(this.emqxParam).then((res) => {
this.showloading = false;
let code = res.data.code;
if (0 == code) {
this.$modal.msgSuccess("添加资源成功");
this.getList();
this.cancel();
} else {
this.$modal.msgError(res.data.message);
}
});
}
} else if ("bridge_mqtt" === this.addResourceForm.type) {
this.emqxParam.description = this.MQTTForm.description;
this.emqxParam.config.address = this.MQTTForm.params.address.default;
this.emqxParam.config.bridge_mode =
this.MQTTForm.params.bridge_mode.default;
this.emqxParam.config.cacertfile =
this.MQTTForm.params.cacertfile.default;
this.emqxParam.config.certfile = this.MQTTForm.params.certfile.default;
this.emqxParam.config.ciphers = this.MQTTForm.params.ciphers.default;
this.emqxParam.config.clientid = this.MQTTForm.params.clientid.default;
this.emqxParam.config.disk_cache =
this.MQTTForm.params.disk_cache.default;
this.emqxParam.config.keepalive =
this.MQTTForm.params.keepalive.default;
this.emqxParam.config.keyfile = this.MQTTForm.params.keyfile.default;
this.emqxParam.config.mountpoint =
this.MQTTForm.params.mountpoint.default;
this.emqxParam.config.password = this.MQTTForm.params.password.default;
this.emqxParam.config.proto_ver =
this.MQTTForm.params.proto_ver.default;
this.emqxParam.config.reconnect_interval =
this.MQTTForm.params.reconnect_interval.default;
this.emqxParam.config.retry_interval =
this.MQTTForm.params.retry_interval.default;
this.emqxParam.config.ssl = this.MQTTForm.params.ssl.default;
this.emqxParam.config.username = this.MQTTForm.params.username.default;
if ("test" == type) {
getResourcesConnect(this.emqxParam).then((res) => {
let code = res.data.code;
if (0 == code) {
this.$modal.msgSuccess("连接成功");
} else {
this.$modal.msgError(res.data.message);
}
});
} else {
saveResources(this.emqxParam).then((res) => {
this.showloading = false;
let code = res.data.code;
if (0 == code) {
this.$modal.msgSuccess("添加资源成功");
this.getList();
this.cancel();
} else {
this.$modal.msgError(res.data.message);
}
});
}
} else if ("web_hook" === this.addResourceForm.type) {
this.emqxParam.description = this.WebHookForm.description;
this.emqxParam.config.url = this.WebHookForm.params.url.default;
this.emqxParam.config.method = this.WebHookForm.params.method.default;
//解析数据转换成后端需要的数据格式
let headers = {};
this.ket_value.forEach((item) => {
headers[item.key] = item.value;
});
this.emqxParam.config.headers = headers;
if ("test" == type) {
getResourcesConnect(this.emqxParam).then((res) => {
let code = res.data.code;
if (0 == code) {
this.$modal.msgSuccess("连接成功");
} else {
this.$modal.msgError(res.data.message);
}
});
} else {
saveResources(this.emqxParam).then((res) => {
this.showloading = false;
let code = res.data.code;
if (0 == code) {
this.$modal.msgSuccess("添加资源成功");
this.getList();
this.cancel();
} else {
this.$modal.msgError(res.data.message);
}
});
}
}
},
},
};
</script>

View File

@@ -0,0 +1,815 @@
<template>
<div style="padding: 6px">
<el-card style="padding-bottom: 100px">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-refresh" size="mini" @click="getList" v-hasPermi="['monitor:rules:refresh']">刷新</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="getAddRules" v-hasPermi="['monitor:rules:add']">新增</el-button>
</el-col>
</el-row>
<el-table v-loading="loading" :data="rulesList">
<el-table-column label="ID" align="center" header-align="center" prop="id">
<template slot-scope="scope">
<el-link :underline="false" type="primary">{{scope.row.id }}</el-link>
</template>
</el-table-column>
<el-table-column label="主题" align="center" prop="for" width="120">
<template slot-scope="scope">
<p v-for="(topic, index) in scope.row.for" :key="index">
<el-tag type="success">{{ topic }}</el-tag>
</p>
</template>
</el-table-column>
<el-table-column label="SQL" align="center" prop="rawsql" />
<el-table-column label="响应动作" align="center" prop="actions">
<template slot-scope="scope">
<p v-for="(action, index) in scope.row.actions" :key="index">
<el-tag type="success">{{ action.name }}</el-tag>
</p>
</template>
</el-table-column>
<el-table-column label="已命中" align="center" prop="matched">
<template slot-scope="scope">
{{ scope.row.metrics[0].matched }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
<template slot-scope="scope">
<el-button size="small" type="text" style="padding: 5px" v-hasPermi="['monitor:rules:query']" @click="handleQuery(scope.row)">
查看
</el-button>
<el-button size="small" type="text" icon="el-icon-delete" style="padding: 5px" v-hasPermi="['monitor:rules:delete']" @click="handleDelete(scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 规则引擎详细 -->
<el-dialog title="规则引擎详细" :visible.sync="openView" width="50%" append-to-body>
<el-form ref="form" :model="form" label-width="120px" size="mini">
<el-card style="padding-bottom: 10px">
<div slot="header" class="clearfix">
<span>基本信息</span>
</div>
<el-row>
<el-col :span="20">
<el-form-item label="主题:">
<el-tag type="success" v-for="(topic, index) in form.for" :key="index">{{ topic }}</el-tag>
</el-form-item>
</el-col>
<el-col :span="20">
<el-form-item label="备注:">{{ form.description }}</el-form-item>
</el-col>
<el-col :span="20">
<el-form-item label="规则SQL:">{{ form.rawsql }}</el-form-item>
</el-col>
</el-row>
</el-card>
<el-card style="padding-bottom: 10px; margin-top: 10px">
<div slot="header" class="clearfix">
<span>度量指标</span>
</div>
<el-table :data="form.metrics">
<el-table-column label="节点" align="center" prop="node" />
<el-table-column label="已命中" align="center" prop="matched" />
<el-table-column label="命中速度" align="center" prop="speed" />
<el-table-column label="最大命中速度" align="center" prop="speed_max" />
<el-table-column label="5分钟平均速度" align="center" prop="speed_last5m" />
</el-table>
</el-card>
<el-card style="padding-bottom: 10px; margin-top: 10px">
<div slot="header" class="clearfix">
<span>响应动作</span>
</div>
<el-table :data="form.actions">
<el-table-column label="类型" align="center" prop="name" />
<el-table-column label="参数" align="center" prop="params">
<template slot-scope="scope">
{{scope.row.params}}
</template>
</el-table-column>
<el-table-column label="度量指标" align="center">
<template slot-scope="scope">
<el-tag type="success">{{ scope.row.metrics[0].node }}</el-tag><br />
合计 成功<el-tag type="success">{{ scope.row.metrics[0].success }}</el-tag>
失败<el-tag type="danger">{{ scope.row.metrics[0].failed }}</el-tag>
</template>
</el-table-column>
</el-table>
</el-card>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="openView = false"> </el-button>
</div>
</el-dialog>
<!-- 添加规则引擎 -->
<el-dialog title="资源管理" :visible.sync="openAddView" width="60%" append-to-body :before-close="cancel">
<el-form ref="form" :model="form" label-width="180px">
<el-card style="padding-bottom: 10px">
<div slot="header" class="clearfix">
<span>
<h2>条件</h2>
<h6>使用 SQL 定义规则条件与数据处理方式</h6>
</span>
</div>
<el-row :gutter="50">
<el-col :span="12">
<el-form-item prop="sql_example">
<span slot="label"> 规则 SQL </span>
<CodeMirrorEditor :value="form.sql_example" myMode="text/x-mysql" height="420" />
</el-form-item>
<el-form-item prop="sql_example">
<span slot="label"> 备注 </span>
<el-input v-model="form.note" placeholder="e.g.消息转发到WebHook" />
</el-form-item>
<el-form-item prop="SQLtest">
<span slot="label">
SQL测试
<el-tooltip content="自定义模拟数据进行 SQL 命令测试,仅用于测试功能" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</span>
<el-switch v-model="form.SQLtest" active-text="" inactive-text="" :active-value="true" :inactive-value="false" active-color="#13ce66">
</el-switch>
</el-form-item>
<div v-if="form.SQLtest">
<el-form-item prop="test_columns.username" v-if="form.test_columns">
<span slot="label"> username </span>
<el-input v-model="form.test_columns.username" />
</el-form-item>
<el-form-item prop="test_columns.topic" v-if="form.test_columns">
<span slot="label"> topic </span>
<el-input v-model="form.test_columns.topic" />
</el-form-item>
<el-form-item prop="test_columns.qos" v-if="form.test_columns">
<span slot="label"> qos </span>
<el-input v-model="form.test_columns.qos" />
</el-form-item>
<el-form-item prop="test_columns.clientid" v-if="form.test_columns">
<span slot="label"> clientid </span>
<el-input v-model="form.test_columns.clientid" />
</el-form-item>
<el-form-item prop="test_columns.payload" v-if="form.test_columns">
<span slot="label"> payload </span>
<CodeMirrorEditor :value="form.test_columns.payload" myMode="application/json" height="150" />
</el-form-item>
<el-form-item v-if="form.test_columns">
<el-button @click="testConnect" type="success" size="mini"> </el-button>
</el-form-item>
<el-form-item v-if="form.test_columns">
<span slot="label"> 测试结果 </span>
<textarea style="width: 497px; height: 70px" v-model="content">
</textarea>
</el-form-item>
</div>
</el-col>
<el-col :span="10">
<div class="sql-tips">
<div class="title">编写 SQL 进行条件过滤与数据处理</div>
<div class="el-scrollbar">
<div class="doc-wrapper" style="margin-bottom: -17px; margin-right: -17px">
<div class="el-scrollbar__view">
<div>
<p>
EMQ X
在消息发布事件触发时将触发规则引擎满足触发条件的规则将执行各自的
SQL 语句筛选并处理消息和事件的上下文信息
</p>
<p class="item">
规则引擎借助响应动作可将特定主题的消息处理结果存储到数据库发送到
HTTP Server转发到消息队列 Kafka
RabbitMQ重新发布到新的主题甚至是另一个 Broker
集群中每个规则可以配置多个响应动作
</p>
<p>
1. 选择发布到 't/#' 主题的消息并筛选出全部字段
</p>
<div class="code">
<code>SELECT * FROM "t/#"</code>
</div>
<p>
2. 选择发布到 't/a' 主题的消息并从 JSON
格式的消息内容中筛选出 "x" 字段
</p>
<div class="code">
<code>SELECT payload.x as x FROM "t/a"</code>
</div>
<p class="item">
规则引擎使用 $events/ 开头的虚拟主题事件主题处理
EMQ X
内置事件内置事件提供更精细的消息控制和客户端动作处理能力可用在
QoS 1 QoS 2 的消息抵达记录设备上下线记录等业务中
</p>
<p>
1. 选择客户端连接事件筛选 Username 'emqx'
的设备并获取连接信息
</p>
<div class="code">
<code>SELECT clientid, connected_at FROM
"$events/client_connected" WHERE username =
'emqx'</code>
</div>
<p>规则引擎和 SQL 语句的详细教程参见 EMQ X 文档</p>
</div>
</div>
</div>
</div>
</div>
</el-col>
</el-row>
</el-card>
</el-form>
<el-card style="padding-bottom: 10px">
<div slot="header" class="clearfix">
<span>
<h2>响应动作</h2>
<h6>处理命中规则的消息</h6>
</span>
</div>
<el-table ref="singleTable" :data="actions" highlight-current-row>
<el-table-column property="name" label="类型"> </el-table-column>
<el-table-column property="param" label="参数">
<template slot-scope="scope">
{{ scope.row.param }}
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" type="danger" @click="deleteAction(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 20px">
<el-button @click="addActionPage()">添加</el-button>
</div>
</el-card>
<div slot="footer" class="dialog-footer">
<el-button @click="cancel"> </el-button>
<el-button type="success" @click="saveRule" :loading="addRuleLoading">新建</el-button>
</div>
</el-dialog>
<!-- 添加响应动作 -->
<el-dialog title="响应动作" :visible.sync="openAddActionView" width="40%" append-to-body :before-close="cancelAction">
<el-form ref="actionForm" :model="actionForm" label-width="180px" v-if="actionForm.actions" :rules="ruleActions">
<el-row>
<el-col :span="20">
<el-form-item prop="actions.title">
<span slot="label">
动作
<el-tooltip :content="prompt" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</span>
<el-select v-model="actionForm.actions.title" @change="selectTitle" placeholder="请选择">
<el-option v-for="(action, index) in actionForm.actions" :key="index" :label="action.title.zh" :value="action.name"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-form ref="republishForm" :model="republishForm" label-width="180px" v-if="republishForm.params" v-show="republishView" :rules="ruleRepublish">
<el-row>
<el-col :span="20">
<el-form-item prop="params.target_topic.default">
<span slot="label">
目的主题
<el-tooltip :content="republishForm.params.target_topic.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</span>
<el-input v-model="republishForm.params.target_topic.default" />
</el-form-item>
<el-form-item prop="params.target_qos.default">
<span slot="label">
目的 QoS
<el-tooltip :content="republishForm.params.target_qos.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</span>
<el-input v-model="republishForm.params.target_qos.default" />
</el-form-item>
<el-form-item prop="params.payload_tmpl.default">
<span slot="label">
消息内容模板
<el-tooltip :content="republishForm.params.payload_tmpl.description.zh" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</span>
<textarea style="width: 300px; height: 120px" v-model="republishForm.params.payload_tmpl.default"></textarea>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-form ref="data_to_mqtt_broker_Form" :model="data_to_mqtt_broker_Form" label-width="180px" v-if="data_to_mqtt_broker_Form.params" v-show="data_to_mqtt_broker_View" :rules="rule_data_to_mqtt_broker">
<el-row>
<el-col :span="20">
<el-form-item prop="resourceId">
<span slot="label"> 关联资源 </span>
<el-select v-model="data_to_mqtt_broker_Form.resourceId" placeholder="请选择">
<el-option v-for="(
resource, index
) in data_to_mqtt_broker_Form.resources" :key="index" :label="resource.id" :value="resource.id"></el-option>
</el-select>
</el-form-item>
<el-form-item prop="params.payload_tmpl.default" v-if="data_to_mqtt_broker_Form.params.payload_tmpl">
<span slot="label">
消息内容模板:
<el-tooltip :content="
data_to_mqtt_broker_Form.params.payload_tmpl.description.zh
" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</span>
<textarea style="width: 300px; height: 120px" v-model="data_to_mqtt_broker_Form.params.payload_tmpl.default"></textarea>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-form ref="data_to_webserver_Form" :model="data_to_webserver_Form" label-width="180px" v-if="data_to_webserver_Form.params" v-show="data_to_webserver_View" :rules="rule_data_to_webserver">
<el-row>
<el-col :span="20">
<el-form-item prop="resourceId">
<span slot="label"> 关联资源: </span>
<el-select v-model="data_to_webserver_Form.resourceId" placeholder="请选择">
<el-option v-for="(resource, index) in data_to_webserver_Form.resources" :key="index" :label="resource.id" :value="resource.id"></el-option>
</el-select>
</el-form-item>
<el-form-item prop="params.payload_tmpl.default" v-if="data_to_webserver_Form.params.payload_tmpl">
<span slot="label">
消息内容模板:
<el-tooltip :content="
data_to_webserver_Form.params.payload_tmpl.description.zh
" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</span>
<textarea style="width: 300px; height: 120px" v-model="data_to_webserver_Form.params.payload_tmpl.default"></textarea>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="cancelAction">取 消</el-button>
<el-button type="success" @click="saveAction('republishForm')" v-if="republishView" :loading="showloading">新建</el-button>
<el-button type="success" @click="saveAction('data_to_mqtt_broker_Form')" v-if="data_to_mqtt_broker_View" :loading="showloading">新建</el-button>
<el-button type="success" @click="saveAction('data_to_webserver_Form')" v-if="data_to_webserver_View" :loading="showloading">新建</el-button>
<el-button type="success" @click="saveAction('do_nothing_Form')" v-if="do_nothing_View" :loading="showloading">新建</el-button>
<el-button type="success" @click="saveAction('inspectForm')" v-if="inspectView" :loading="showloading">新建</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
getRules,
deleteRule,
getRulesEvent,
getActionsEvent,
getResources,
saveRule,
testConnectRule,
} from "@/api/iot/emqx";
import CodeMirrorEditor from "@/components/Codemirror/index";
export default {
name: "Rules",
components: {
CodeMirrorEditor,
},
data() {
return {
// 遮罩层
loading: true,
//遮罩层
showloading: false,
// 遮罩层
addRuleLoading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 规则表格数据
rulesList: [],
// 弹出层标题
title: "",
// 是否显示详细弹出层
openView: false,
// 是否显示添加规则弹出层
openAddView: false,
// 是否显示添加响应动作弹出层
openAddActionView: false,
//是否显示消息重新发布表单参数
republishView: false,
//是否显示桥接数据到 MQTT Broker表单参数
data_to_mqtt_broker_View: false,
//是否显示发送数据到 Web 服务表单参数
data_to_webserver_View: false,
//是否显示空动作 (调试)表单按钮
do_nothing_View: false,
//是否显示检查 (调试)表单按钮
inspectView: false,
// 添加规则表单参数
form: {
note: "",
SQLtest: false,
},
//测试结果
content: "",
// 响应动作列表
actions: [],
//响应动作列表表单参数
actionForm: {
actions: [],
},
//动作的提示语
prompt: "动作类型",
// 响应动作规则
ruleActions: {
actions: {
title: [{
required: true,
message: "请选择动作类型",
trigger: "change"
}, ],
},
},
ruleRepublish: {
params: {
target_topic: {
default: [{
required: true,
message: "请输入目的主题",
trigger: "blur"
}, ],
},
target_qos: {
default: [{
required: true,
message: "请输入目的 QoS",
trigger: "blur"
}, ],
},
payload_tmpl: {
default: [{
required: true,
message: "请输入消息内容模板",
trigger: "blur",
}, ],
},
},
},
rule_data_to_mqtt_broker: {
resourceId: [{
required: true,
message: "请关联资源类型",
trigger: "blur"
}, ],
},
rule_data_to_webserver: {
resourceId: [{
required: true,
message: "请关联资源类型",
trigger: "change"
}, ],
},
//空动作 (调试)表单参数
do_nothing_Form: {},
//检查 (调试)表单参数
inspectForm: {},
//消息重新发布表单参数
republishForm: {
},
//桥接数据到 MQTT Broker表单参数
data_to_mqtt_broker_Form: {
resources: [],
resourceId: "",
},
//发送数据到 Web 服务表单参数
data_to_webserver_Form: {
resources: [],
resourceId: "",
},
};
},
created() {
this.getList();
},
methods: {
/** 查询规则列表 */
getList() {
this.loading = true;
getRules("").then((response) => {
this.rulesList = response.data.data;
// this.total = response.data.meta.count;
this.loading = false;
});
},
// 取消按钮
cancel() {
//初始化
this.openAddView = false;
this.actions = [];
this.$refs["form"].resetFields();
},
//关闭动作页面
cancelAction() {
//初始化所有数据
this.do_nothing_View = false;
this.republishView = false;
this.data_to_mqtt_broker_View = false;
this.data_to_webserver_View = false;
this.inspectView = false;
this.openAddActionView = false;
this.do_nothing_Form = {};
this.inspectForm = {};
this.republishForm = {};
this.data_to_mqtt_broker_Form.resources = [];
this.data_to_mqtt_broker_Form.resourceId = "";
this.data_to_mqtt_broker_Form = {};
this.data_to_webserver_Form.resources = [];
this.data_to_webserver_Form.resourceId = "";
this.data_to_webserver_Form = {};
this.actionForm.actions = [];
this.$refs["actionForm"].resetFields();
},
/** 查看按钮操作 */
handleQuery(row) {
let ruleId = row.id;
getRules(ruleId).then((response) => {
this.form = response.data.data;
this.openView = true;
});
},
//删除规则
handleDelete(row) {
debugger;
let ruleId = row.id;
this.$modal
.confirm('是否确认删除ID为"' + ruleId + '"的规则引擎?')
.then(function () {
return deleteRule(ruleId);
})
.then(() => {
this.getList();
this.$modal.msgSuccess("删除规则引擎成功");
})
.catch(() => {});
},
//跳转到添加规则引擎页面
getAddRules() {
getRulesEvent().then((res) => {
this.form = res.data.data[0];
this.openAddView = true;
});
},
//添加响应动作
addActionPage() {
getActionsEvent().then((res) => {
//赋值
this.actionForm.actions = res.data.data;
this.do_nothing_Form = res.data.data[0];
//检查 (调试)表单参数
this.inspectForm = res.data.data[2];
//消息重新发布表单参数
this.republishForm = res.data.data[1];
//桥接数据到 MQTT Broker表单参数
this.data_to_mqtt_broker_Form = res.data.data[3];
//发送数据到 Web 服务表单参数
this.data_to_webserver_Form = res.data.data[4];
this.openAddActionView = true;
});
},
/** 选择响应动作类型 */
selectTitle(val) {
if ("do_nothing" === val) {
this.data_to_webserver_View = false;
this.data_to_mqtt_broker_View = false;
this.republishView = false;
this.inspectView = false;
this.do_nothing_View = true;
this.prompt = this.actionForm.actions[0].description.zh;
} else if ("republish" === val) {
this.data_to_webserver_View = false;
this.data_to_mqtt_broker_View = false;
this.do_nothing_View = false;
this.inspectView = false;
this.republishView = true;
this.prompt = this.actionForm.actions[1].description.zh;
} else if ("inspect" === val) {
this.data_to_webserver_View = false;
this.data_to_mqtt_broker_View = false;
this.do_nothing_View = false;
this.republishView = false;
this.inspectView = true;
this.prompt = this.actionForm.actions[2].description.zh;
} else if ("data_to_mqtt_broker" === val) {
getResources("").then((res) => {
this.data_to_mqtt_broker_Form.resources = res.data.data;
this.prompt = this.actionForm.actions[3].description.zh;
this.data_to_webserver_View = false;
this.do_nothing_View = false;
this.republishView = false;
this.inspectView = false;
this.data_to_mqtt_broker_View = true;
});
} else if ("data_to_webserver" === val) {
getResources("").then((res) => {
this.data_to_webserver_Form.resources = res.data.data;
this.prompt = this.actionForm.actions[4].description.zh;
this.data_to_mqtt_broker_View = false;
this.do_nothing_View = false;
this.republishView = false;
this.inspectView = false;
this.data_to_webserver_View = true;
});
}
},
//添加响应动作
saveAction(formName) {
const action = {};
const param = {};
if ("do_nothing_Form" === formName) {
action.name = this.do_nothing_Form.name;
action.params = {};
this.actions.push(action);
this.cancelAction();
} else if ("republishForm" === formName) {
//校验表单
this.$refs[formName].validate((valid) => {
if (valid) {
param.payload_tmpl = this.republishForm.params.payload_tmpl.default;
param.target_topic = this.republishForm.params.target_topic.default;
param.target_qos = this.republishForm.params.target_qos.default;
action.name = this.republishForm.name;
action.params = param;
this.actions.push(action);
this.cancelAction();
} else {
return false;
}
});
} else if ("inspectForm" === formName) {
action.name = this.inspectForm.name;
action.params = {};
this.actions.push(action);
this.cancelAction();
} else if ("data_to_mqtt_broker_Form" === formName) {
//校验表单
this.$refs[formName].validate((valid) => {
if (valid) {
param.$resource = this.data_to_mqtt_broker_Form.resourceId;
if(this.data_to_mqtt_broker_Form.params.payload_tmpl != null){
param.payload_tmpl =
this.data_to_mqtt_broker_Form.params.payload_tmpl.default;
}
action.params = param;
action.name = this.data_to_mqtt_broker_Form.name;
this.actions.push(action);
this.cancelAction();
} else {
return false;
}
});
} else if ("data_to_webserver_Form" === formName) {
//校验表单
this.$refs[formName].validate((valid) => {
if (valid) {
param.$resource = this.data_to_webserver_Form.resourceId;
if(this.data_to_webserver_Form.params.payload_tmpl != null){
param.payload_tmpl =
this.data_to_webserver_Form.params.payload_tmpl.default;
}
action.params = param;
action.name = this.data_to_webserver_Form.name;
this.actions.push(action);
this.cancelAction();
} else {
return false;
}
});
}
},
//删除动作参数
deleteAction(index) {
this.actions.splice(index, 1);
},
//添加规则引擎
saveRule() {
this.addRuleLoading = true;
this.insertOrTestRule("insert");
},
testConnect() {
this.insertOrTestRule("test");
},
//公共方法
insertOrTestRule(type) {
//将需要的参数进行赋值
const ruleParam = {
ctx: {}
};
ruleParam.description = this.form.note;
ruleParam.rawsql = this.form.sql_example;
ruleParam.actions = this.actions;
ruleParam.ctx.clientid = this.form.test_columns.clientid;
ruleParam.ctx.payload = this.form.test_columns.payload;
ruleParam.ctx.qos = this.form.test_columns.qos;
ruleParam.ctx.topic = this.form.test_columns.topic;
ruleParam.ctx.username = this.form.test_columns.username;
ruleParam.ctx.clientid = this.form.test_columns.clientid;
if ("test" === type) {
testConnectRule(ruleParam).then((res) => {
const code = res.data.code;
if (0 === code) {
this.content = JSON.stringify(res.data.data);
} else {
this.$modal.msgError(res.data.message);
}
});
} else {
saveRule(ruleParam).then((res) => {
const code = res.data.code;
if (0 === code) {
this.$modal.msgSuccess("添加规则引擎成功");
this.cancel();
this.getList();
} else {
this.$modal.msgError(res.data.message);
}
this.addRuleLoading = false;
});
}
},
},
};
</script>
<style scoped>
.sql-tips {
border: 4px dashed #d8d8d8;
color: #71737d;
}
.sql-tips {
padding: 20px 0;
border-radius: 4px;
font-size: 15px;
max-height: 480px;
}
.sql-tips .title {
padding: 0 20px 12px;
}
.el-scrollbar {
overflow: hidden;
position: relative;
}
.sql-tips .doc-wrapper {
max-height: 400px;
padding: 0 20px;
}
.sql-tips .el-scrollbar__wrap {
overflow-x: hidden;
}
.el-scrollbar__wrap {
overflow: scroll;
height: 100%;
}
p {
display: block;
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0px;
margin-inline-end: 0px;
}
.code {
line-height: 1.4;
padding: 6px;
border-radius: 4px;
margin-bottom: 12px;
}
.code {
background-color: hsla(0, 0%, 87%, 0.8);
}
</style>

View File

@@ -40,7 +40,7 @@ module.exports = {
} }
}, },
['/api/v4']: { ['/api/v4']: {
target: `http://localhost:8081`, target: `http://wumei.live:8081`,
changeOrigin: true, changeOrigin: true,
// logLevel: 'debug', // logLevel: 'debug',
}, },