├── .gitignore ├── LICENSE ├── README.md ├── WeMQ.sql ├── mm-common ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── dromara │ └── wemq │ └── common │ ├── annotation │ └── RepeatSubmit.java │ ├── constant │ ├── PageConstants.java │ └── UserConstants.java │ ├── core │ ├── controller │ │ └── BaseController.java │ ├── domain │ │ └── AjaxResult.java │ └── page │ │ └── TableData.java │ ├── enums │ └── HttpStatus.java │ └── exception │ └── MMException.java ├── mm-framework ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── dromara │ └── wemq │ └── framework │ ├── aspectj │ └── RepeatSubmitAspect.java │ ├── config │ ├── BigNumberSerializer.java │ ├── JacksonConfig.java │ ├── MvcConfiguration.java │ ├── MybatisPlusConfig.java │ └── TemplateResolverConfiguration.java │ ├── interceptor │ ├── ExceptionInterceptor.java │ ├── LoginInterceptor.java │ └── RepeatSubmitInterceptor.java │ └── utils │ ├── Convert.java │ ├── IpUtils.java │ ├── ServletUtils.java │ ├── StrFormatter.java │ └── StringUtils.java ├── mm-web ├── pom.xml └── src │ └── main │ ├── java │ └── org │ │ └── dromara │ │ └── wemq │ │ ├── WeMQApplication.java │ │ ├── controller │ │ ├── AdminController.java │ │ ├── CustomerController.java │ │ ├── ErrorController.java │ │ ├── LoginLogController.java │ │ ├── MqPageController.java │ │ ├── NmqsController.java │ │ └── ViewController.java │ │ ├── mapper │ │ ├── AdminMapper.java │ │ ├── CustomerMapper.java │ │ ├── LoginLogMapper.java │ │ ├── MqPageMapper.java │ │ ├── MqParamMapper.java │ │ └── NmqsMapper.java │ │ ├── model │ │ ├── dto │ │ │ ├── LoginLogDto.java │ │ │ └── MqPageDto.java │ │ ├── pojo │ │ │ ├── Admin.java │ │ │ ├── Customer.java │ │ │ ├── LoginLog.java │ │ │ ├── MQPage.java │ │ │ ├── MQParam.java │ │ │ └── NmqsToken.java │ │ └── vo │ │ │ └── MQPageVo.java │ │ └── service │ │ ├── AdminService.java │ │ ├── CustomerService.java │ │ ├── LoginLogService.java │ │ ├── MqPageService.java │ │ ├── MqParamService.java │ │ ├── NmqsService.java │ │ └── impl │ │ ├── AdminServiceImpl.java │ │ ├── CustomerServiceImpl.java │ │ ├── LoginLogServiceImpl.java │ │ ├── MqPageServiceImpl.java │ │ ├── MqParamServiceImpl.java │ │ └── NmqsServiceImpl.java │ └── resources │ ├── application.yml │ ├── banner.txt │ ├── mapper │ ├── MqPageMapper.xml │ ├── MqParamMapper.xml │ └── NmqsMapper.xml │ ├── static │ ├── bootstrap │ │ ├── 4.6.0 │ │ │ ├── bootstrap.min.css │ │ │ └── bootstrap.min.js │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.min.css │ │ └── table │ │ │ ├── bootstrap-table-zh-CN.min.js │ │ │ ├── bootstrap-table.min.css │ │ │ └── bootstrap-table.min.js │ ├── crypto-js.min.js │ ├── dayjs.min.js │ ├── font │ │ ├── iconfont.eot │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ ├── images │ │ ├── head.gif │ │ ├── logo.gif │ │ ├── logo.png │ │ └── mqttlogo.png │ ├── jquery-1.12.4.min.js │ ├── jquery.qrcode.min.js │ ├── layer │ │ ├── layer.js │ │ ├── mobile │ │ │ ├── layer.js │ │ │ └── need │ │ │ │ └── layer.css │ │ └── theme │ │ │ └── default │ │ │ ├── icon-ext.png │ │ │ ├── icon.png │ │ │ ├── layer.css │ │ │ ├── loading-0.gif │ │ │ ├── loading-1.gif │ │ │ └── loading-2.gif │ ├── layui │ │ ├── layui.css │ │ └── layui.js │ ├── system │ │ ├── common.css │ │ ├── pages │ │ │ └── menu.js │ │ └── utils.js │ ├── tabler │ │ ├── tabler.min.css │ │ └── tabler.min.js │ └── toastify │ │ ├── toastify-js.js │ │ └── toastify.min.css │ └── templates │ ├── about.html │ ├── admin.html │ ├── common.html │ ├── common │ └── header.html │ ├── custom_mqtt.html │ ├── customers.html │ ├── error │ └── 404.html │ ├── index.html │ ├── login.html │ ├── loginlog.html │ ├── mqtt.html │ ├── nmqs.html │ ├── pages.html │ ├── tools.html │ └── viewer_pages.html ├── pages ├── GPS.html └── 电表调试.html └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | .DS_Store 3 | target/ 4 | .gradle/ 5 | .idea/ 6 | build/ 7 | classes/ 8 | out/ 9 | *.db 10 | *.log 11 | *.iml 12 | .classpath 13 | .factorypath 14 | bin/ 15 | .settings/ 16 | .project 17 | */test/ 18 | */META-INF/ 19 | *.ipr 20 | *.iws 21 | .kotlintest 22 | */.kotlintest/ 23 | rebel.xml 24 | */.idea/ 25 | */.id 26 | logs/ 27 | ./pages/ 28 | /mm-web/src/test/java/Password.java 29 | .fastRequest/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WeMQ - 物联网调试管理平台 2 | 3 | 4 | >#### WeMQ 目前已加入 [Dromara 社区 ](https://gitee.com/dromara) 孵化项目 5 | 6 | ## 📂 1. 项目介绍 7 | 8 | ### 1.1 项目简介 9 | 10 | **WeMQ是一款基于SpringBoot开发的一款物联网设备调试管理平台。** 11 | 12 | 其功能主要是对客户MQTT调试页面进行集中管理(连接信息、发送信息),系统管理员可在后台添加客户和调试页面,并设置调试页面的连接信息、发送消息和对应的发送按钮文字,并设置分享链接以及页面的开启状态,用户可通过分享链接打开配置好对应信息的页面,实现对自己设备的管理调试。 13 | 14 | ![GitHub](https://img.shields.io/github/license/NicholasLD/WeMQ) 15 | [![star](https://gitee.com/nicholasld/WeMQ/badge/star.svg?theme=dark)](https://gitee.com/nicholasld/WeMQ/stargazers) 16 | [![fork](https://gitee.com/nicholasld/WeMQ/badge/fork.svg?theme=dark)](https://gitee.com/nicholasld/WeMQ/members) 17 | 18 | #### 项目截图: 19 | 控制台 20 | 21 | ![D7CCC3CA-0B97-4A0F-8A03-8DC8A91F58A2.png](https://img.nicholasld.cn/i/2023/09/22/650d5384475a7.jpg) 22 | 23 | 用户调试界面 24 | 25 | ![1734341718866.png](https://img.nicholasld.cn/i/2024/12/16/675ff45a0cb00.png) 26 | 27 | 调试页面参数 28 | 29 | ![1734341778540.png](https://img.nicholasld.cn/i/2024/12/16/675ff49543812.png) 30 | ![1734341830917.png](https://img.nicholasld.cn/i/2024/12/16/675ff4c99b7f3.png) 31 | 32 | 快捷调试页面 33 | 34 | ![1734341886530.png](https://img.nicholasld.cn/i/2024/12/16/675ff5012193f.png) 35 | 36 | 自定义调试页面 37 | 38 | ![1734342031472.png](https://img.nicholasld.cn/i/2024/12/16/675ff592837fa.png) 39 | 40 | ### 1.2 技术选型 41 | 42 | ##### 1. 系统环境 43 | 44 | - Java 21 45 | - Apache Maven 3 46 | 47 | ##### 2. 主框架 48 | 49 | - Spring Boot 3.4.x 50 | - Spring Framework 6.x 51 | - Spring MVC 6.x 52 | 53 | ##### 3. 持久层 54 | 55 | - Mybatis Plus 3.5.x 56 | - HikariCP 5.x 57 | - Hibernate Validation 6.x 58 | - Java MySQL Connector 8.3.x 59 | 60 | ##### 4. 视图层 61 | 62 | - Thymeleaf 3.x 63 | - Bootstrap 5.x 64 | - Layui 2.x 65 | 66 | ##### 5. 工具类 67 | 68 | - Apache Commons 69 | - Hutool 5.x 70 | 71 | ### 1.3 主要功能 72 | 73 | - 调试页面管理 74 | - 调试页面自定义拓展 75 | - MQTT主机管理 76 | - 管理员管理 77 | - 客户分组管理 78 | - 物联网常用工具 79 | 80 | ### 1.4 项目结构 81 | ``` 82 | org.dromara.wemq 83 | ├── mm-common // 工具类 84 | │ └── annotation // 自定义注解 85 | │ └── constant // 通用常量 86 | │ └── core // 核心控制 87 | │ └── enums // 通用枚举 88 | │ └── exception // 通用异常 89 | ├── mm-framework // 框架核心 90 | │ └── aspectj // 注解实现 91 | │ └── interceptor // 拦截器 92 | │ └── manager // 异步处理 93 | │ └── web // 前端控制 94 | └── mm-web // Web服务 95 | │ └── controller // 控制器 96 | │ └── mapper // 数据库操作 97 | │ └── model // 数据模型 98 | │ └── service // 服务接口 99 | ``` 100 | 101 | ## ⚙️ 2. 部署文档 102 | 103 | ### 环境要求 104 | - OpenJDK 17/21+ 105 | - MySQL 8.0+ 106 | - Maven 3.6+ 107 | 108 | ### 2.1 如何部署 109 | 110 | 首先导入项目根目录的 WeMQ.sql 文件到数据库,数据库名为 `WeMQ`,然后对数据库连接信息进行配置,在`mm-web`模块的`/src/main/resources/application.yml`文件中进行数据库连接信息的配置 111 | 112 | ### 2.2 如何修改 Nmqs服务地址 113 | 114 | 本项目依赖于开源通信层项目 Nmqs 来实现对MQTT的转发和连接,如需部署Nmqs,[请访问](https://gitee.com/nicholasld/nmqs)。 115 | 116 | 在`application.yml`中修改wemq下的属性即可,根据WeMQ项目地址自动适配http或https、ws或wss 117 | 118 | ```yaml 119 | wemq: 120 | nmqs: 121 | host: localhost #地址必须外网可以访问,生产环境不能使用127.0.0.1/localhost之类的地址 122 | port: 8081 123 | ``` 124 | 125 | ### 2.3 启动项目 126 | 启动`mm-web`中的`WeMQApplication`,访问`http://<你的项目地址>:8080`即可 127 | 128 | 初始账号密码为`admin`/`admin` 129 | 130 | ## 💡 Issues & Pull Requests 131 | 欢迎提交Issues和Pull Requests,开源大门永远向所有人敞开。 132 | 133 | ## ✉️ 联系作者 134 | - Email: 878639947@qq.com 135 | - QQ: 878639947 136 | - WeChat: NicholasLD505 137 | 138 | ## 🔰 License(开源许可证) 139 | Apache License Version 2.0 see http://www.apache.org/licenses/LICENSE-2.0.html 140 | 141 | ## ©️ 版权使用说明 142 | WeMQ遵循Apache2.0开源协议,可用于个人学习、毕设、公司项目、商业产品等,但必须保留版权信息。 143 | 144 | ## 🪐 知识星球 145 | ![img_1.png](https://img.nicholasld.cn/i/2023/09/22/650d482f3072c.png) 146 | 147 | 148 | ## 🎉 特别鸣谢 149 | 感谢 **JetBrains** 为本项目提供的免费开源许可证支持。 150 | 151 | 152 | JetBrains 153 | 154 | -------------------------------------------------------------------------------- /WeMQ.sql: -------------------------------------------------------------------------------- 1 | SET NAMES utf8mb4; 2 | SET FOREIGN_KEY_CHECKS = 0; 3 | 4 | -- ---------------------------- 5 | -- Table structure for mq_customer 6 | -- ---------------------------- 7 | DROP TABLE IF EXISTS `mq_customer`; 8 | CREATE TABLE `mq_customer` ( 9 | `id` int NOT NULL AUTO_INCREMENT, 10 | `name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, 11 | PRIMARY KEY (`id`) USING BTREE 12 | ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; 13 | 14 | -- ---------------------------- 15 | -- Table structure for mq_nmqs_token 16 | -- ---------------------------- 17 | DROP TABLE IF EXISTS `mq_nmqs_token`; 18 | CREATE TABLE `mq_nmqs_token` ( 19 | `id` bigint NOT NULL COMMENT 'TokenID', 20 | `name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT 'Token名称', 21 | `token` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT 'Token', 22 | `protocol` int NOT NULL COMMENT '协议 0:ws 1:tcp', 23 | `mqtt_server` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT 'MQTT服务器', 24 | `mqtt_port` int NULL DEFAULT NULL COMMENT 'MQTT端口', 25 | `mqtt_username` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT 'MQTT用户名', 26 | `mqtt_password` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT 'MQTT密码', 27 | PRIMARY KEY (`id`) USING BTREE 28 | ) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; 29 | 30 | -- ---------------------------- 31 | -- Table structure for mq_page 32 | -- ---------------------------- 33 | DROP TABLE IF EXISTS `mq_page`; 34 | CREATE TABLE `mq_page` ( 35 | `id` int NOT NULL AUTO_INCREMENT COMMENT '页面ID', 36 | `page_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '页面名称', 37 | `page_url` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '访问URL', 38 | `page_filename` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '', 39 | `nmqs_id` bigint NOT NULL COMMENT 'NmqsTokenID', 40 | `status` int NOT NULL COMMENT '页面启用状态', 41 | `mqtt_sendtopic` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT 'MQTT发布订阅', 42 | `mqtt_receivetopic` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT 'MQTT接收订阅', 43 | `mqtt_qos` int NOT NULL DEFAULT 0 COMMENT 'QoS', 44 | `customer_id` bigint NULL DEFAULT NULL, 45 | PRIMARY KEY (`id`) USING BTREE 46 | ) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; 47 | 48 | -- ---------------------------- 49 | -- Table structure for mq_param 50 | -- ---------------------------- 51 | DROP TABLE IF EXISTS `mq_param`; 52 | CREATE TABLE `mq_param` ( 53 | `id` int NOT NULL AUTO_INCREMENT, 54 | `page_id` bigint NOT NULL, 55 | `message` varchar(1000) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, 56 | `button` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, 57 | `sort` int NULL DEFAULT NULL, 58 | PRIMARY KEY (`id`) USING BTREE 59 | ) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; 60 | 61 | -- ---------------------------- 62 | -- Table structure for sys_admin 63 | -- ---------------------------- 64 | DROP TABLE IF EXISTS `sys_admin`; 65 | CREATE TABLE `sys_admin` ( 66 | `id` int NOT NULL AUTO_INCREMENT COMMENT '管理员ID', 67 | `username` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '管理员用户名', 68 | `nickname` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '管理员昵称', 69 | `password` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '登录密码', 70 | `status` int NOT NULL COMMENT '启用状态', 71 | PRIMARY KEY (`id`) USING BTREE 72 | ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; 73 | 74 | INSERT INTO `sys_admin` VALUES (1, 'admin', '系统管理员', '$2a$10$YC4Pe1xIqBdsfKmVOql1burSsOYbKQr3XKocquaM5HmFO7Byw53Mi', 0); 75 | 76 | -- ---------------------------- 77 | -- Table structure for sys_login_log 78 | -- ---------------------------- 79 | DROP TABLE IF EXISTS `sys_login_log`; 80 | CREATE TABLE `sys_login_log` ( 81 | `id` int NOT NULL AUTO_INCREMENT COMMENT '日志ID', 82 | `admin_id` int NOT NULL COMMENT '管理员ID', 83 | `admin_ip` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '登录IP', 84 | `admin_os` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '登录操作系统', 85 | `login_time` datetime NOT NULL COMMENT '登录时间', 86 | `login_status` int NOT NULL COMMENT '登录状态', 87 | PRIMARY KEY (`id`) USING BTREE 88 | ) ENGINE = InnoDB AUTO_INCREMENT = 31 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; 89 | 90 | SET FOREIGN_KEY_CHECKS = 1; -------------------------------------------------------------------------------- /mm-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.dromara 8 | WeMQ 9 | ${revision} 10 | 11 | 12 | mm-common 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /mm-common/src/main/java/org/dromara/wemq/common/annotation/RepeatSubmit.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.common.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * 自定义注解防止表单重复提交 11 | */ 12 | @Target(ElementType.METHOD) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Documented 15 | public @interface RepeatSubmit { 16 | /** 17 | * 间隔时间(ms),小于此时间视为重复提交 18 | */ 19 | int interval() default 5000; 20 | 21 | /** 22 | * 提示消息 23 | */ 24 | String message() default "不允许重复提交,请稍后再试"; 25 | } 26 | -------------------------------------------------------------------------------- /mm-common/src/main/java/org/dromara/wemq/common/constant/PageConstants.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.common.constant; 2 | 3 | /** 4 | * 分页常量 5 | * @author NicholasLD 6 | * @createTime 2023/4/4 05:51 7 | */ 8 | public interface PageConstants { 9 | int DEFAULT_PAGE_NUM = 1; 10 | int DEFAULT_PAGE_SIZE = 10; 11 | } 12 | -------------------------------------------------------------------------------- /mm-common/src/main/java/org/dromara/wemq/common/constant/UserConstants.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.common.constant; 2 | 3 | /** 4 | * 用户常量 5 | * @author NicholasLD 6 | * @createTime 2023/4/3 10:58 7 | */ 8 | public interface UserConstants { 9 | String USER_SESSION = "userSession"; 10 | } 11 | -------------------------------------------------------------------------------- /mm-common/src/main/java/org/dromara/wemq/common/core/controller/BaseController.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.common.core.controller; 2 | 3 | import org.springframework.beans.propertyeditors.CustomDateEditor; 4 | import org.springframework.web.bind.WebDataBinder; 5 | import org.springframework.web.bind.annotation.InitBinder; 6 | 7 | import java.text.SimpleDateFormat; 8 | import java.util.Date; 9 | 10 | /** 11 | * @author NicholasLD 12 | * @createTime 2023/4/3 09:40 13 | */ 14 | public class BaseController { 15 | @InitBinder 16 | public void webDataBinder(WebDataBinder webDataBinder){ 17 | webDataBinder.registerCustomEditor(Date.class,new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"),true)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mm-common/src/main/java/org/dromara/wemq/common/core/domain/AjaxResult.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.common.core.domain; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import org.dromara.wemq.common.enums.HttpStatus; 5 | 6 | import java.util.HashMap; 7 | import java.util.Objects; 8 | 9 | /** 10 | * 操作消息提醒 11 | */ 12 | public class AjaxResult extends HashMap 13 | { 14 | private static final long serialVersionUID = 1L; 15 | 16 | /** 状态码 */ 17 | public static final String CODE_TAG = "code"; 18 | 19 | /** 返回内容 */ 20 | public static final String MSG_TAG = "msg"; 21 | 22 | /** 数据对象 */ 23 | public static final String DATA_TAG = "data"; 24 | 25 | /** 26 | * 状态类型 27 | */ 28 | public enum Type 29 | { 30 | /** 成功 */ 31 | SUCCESS(200), 32 | /** 警告 */ 33 | WARN(301), 34 | /** 错误 */ 35 | ERROR(500); 36 | private final int value; 37 | 38 | Type(int value) 39 | { 40 | this.value = value; 41 | } 42 | 43 | public int value() 44 | { 45 | return this.value; 46 | } 47 | } 48 | 49 | /** 50 | * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。 51 | */ 52 | public AjaxResult() 53 | { 54 | } 55 | 56 | /** 57 | * 初始化一个新创建的 AjaxResult 对象 58 | * 59 | * @param type 状态类型 60 | * @param msg 返回内容 61 | */ 62 | public AjaxResult(Type type, String msg) 63 | { 64 | super.put(CODE_TAG, type.value); 65 | super.put(MSG_TAG, msg); 66 | } 67 | 68 | public AjaxResult(HttpStatus httpStatus) { 69 | super.put(CODE_TAG, httpStatus.getCode()); 70 | super.put(MSG_TAG, httpStatus.getMessage()); 71 | } 72 | 73 | public AjaxResult(Integer code, String msg) { 74 | super.put(CODE_TAG, code); 75 | super.put(MSG_TAG, msg); 76 | } 77 | 78 | /** 79 | * 初始化一个新创建的 AjaxResult 对象 80 | * 81 | * @param type 状态类型 82 | * @param msg 返回内容 83 | * @param data 数据对象 84 | */ 85 | public AjaxResult(Type type, String msg, Object data) 86 | { 87 | super.put(CODE_TAG, type.value); 88 | super.put(MSG_TAG, msg); 89 | if (!StrUtil.isEmptyIfStr(data)) 90 | { 91 | super.put(DATA_TAG, data); 92 | } 93 | } 94 | 95 | /** 96 | * 返回成功消息 97 | * 98 | * @return 成功消息 99 | */ 100 | public static AjaxResult success() 101 | { 102 | return AjaxResult.success("操作成功"); 103 | } 104 | 105 | /** 106 | * 返回成功数据 107 | * 108 | * @return 成功消息 109 | */ 110 | public static AjaxResult success(Object data) 111 | { 112 | return AjaxResult.success("操作成功", data); 113 | } 114 | 115 | /** 116 | * 返回成功消息 117 | * 118 | * @param msg 返回内容 119 | * @return 成功消息 120 | */ 121 | public static AjaxResult success(String msg) 122 | { 123 | return AjaxResult.success(msg, null); 124 | } 125 | 126 | /** 127 | * 返回成功消息 128 | * 129 | * @param msg 返回内容 130 | * @param data 数据对象 131 | * @return 成功消息 132 | */ 133 | public static AjaxResult success(String msg, Object data) 134 | { 135 | return new AjaxResult(Type.SUCCESS, msg, data); 136 | } 137 | 138 | /** 139 | * 返回警告消息 140 | * 141 | * @param msg 返回内容 142 | * @return 警告消息 143 | */ 144 | public static AjaxResult warn(String msg) 145 | { 146 | return AjaxResult.warn(msg, null); 147 | } 148 | 149 | /** 150 | * 返回警告消息 151 | * 152 | * @param msg 返回内容 153 | * @param data 数据对象 154 | * @return 警告消息 155 | */ 156 | public static AjaxResult warn(String msg, Object data) 157 | { 158 | return new AjaxResult(Type.WARN, msg, data); 159 | } 160 | 161 | /** 162 | * 返回错误消息 163 | * 164 | * @return 165 | */ 166 | public static AjaxResult error() 167 | { 168 | return AjaxResult.error("操作失败"); 169 | } 170 | 171 | /** 172 | * 返回错误消息 173 | * 174 | * @param msg 返回内容 175 | * @return 警告消息 176 | */ 177 | public static AjaxResult error(String msg) 178 | { 179 | return AjaxResult.error(msg, null); 180 | } 181 | 182 | /** 183 | * 返回错误消息 184 | * 185 | * @param msg 返回内容 186 | * @param data 数据对象 187 | * @return 警告消息 188 | */ 189 | public static AjaxResult error(String msg, Object data) 190 | { 191 | return new AjaxResult(Type.ERROR, msg, data); 192 | } 193 | 194 | /** 195 | * 是否为成功消息 196 | * 197 | * @return 结果 198 | */ 199 | public boolean isSuccess() 200 | { 201 | return !isError(); 202 | } 203 | 204 | /** 205 | * 是否为错误消息 206 | * 207 | * @return 结果 208 | */ 209 | public boolean isError() 210 | { 211 | return Objects.equals(Type.ERROR.value, this.get(CODE_TAG)); 212 | } 213 | 214 | /** 215 | * 方便链式调用 216 | * 217 | * @param key 键 218 | * @param value 值 219 | * @return 数据对象 220 | */ 221 | @Override 222 | public AjaxResult put(String key, Object value) 223 | { 224 | super.put(key, value); 225 | return this; 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /mm-common/src/main/java/org/dromara/wemq/common/core/page/TableData.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.common.core.page; 2 | 3 | import org.dromara.wemq.common.constant.PageConstants; 4 | 5 | import java.io.Serializable; 6 | import java.util.List; 7 | 8 | /** 9 | * 表格分页数据对象 10 | * @author NicholasLD 11 | * @createTime 2023/4/4 05:47 12 | */ 13 | public class TableData implements Serializable { 14 | private static final long serialVersionUID = 1L; 15 | 16 | /** 总记录数 */ 17 | private long total; 18 | 19 | /** 列表数据 */ 20 | private List rows; 21 | 22 | /** 当前页码 */ 23 | private int pageNum; 24 | 25 | /** 每页显示记录数 */ 26 | private int pageSize; 27 | 28 | /** 总页数 */ 29 | private int pages; 30 | 31 | /** 32 | * 表格数据对象 33 | */ 34 | public TableData() 35 | { 36 | } 37 | 38 | /** 39 | * 分页 40 | * @param list 列表数据 41 | * @param pageNum 当前页码 42 | */ 43 | public TableData(List list, int pageNum, int pages, long total) 44 | { 45 | this.rows = list; 46 | this.total = total; 47 | this.pageNum = pageNum; 48 | this.pageSize = PageConstants.DEFAULT_PAGE_SIZE; 49 | this.pages = pages; 50 | } 51 | 52 | public long getTotal() 53 | { 54 | return total; 55 | } 56 | 57 | public void setTotal(long total) 58 | { 59 | this.total = total; 60 | } 61 | 62 | public List getRows() 63 | { 64 | return rows; 65 | } 66 | 67 | public void setRows(List rows) 68 | { 69 | this.rows = rows; 70 | } 71 | 72 | public int getPageNum() { 73 | return pageNum; 74 | } 75 | 76 | public void setPageNum(int pageNum) { 77 | this.pageNum = pageNum; 78 | } 79 | 80 | public int getPageSize() { 81 | return pageSize; 82 | } 83 | 84 | public void setPageSize(int pageSize) { 85 | this.pageSize = pageSize; 86 | } 87 | 88 | public int getPages() { 89 | return pages; 90 | } 91 | 92 | public void setPages(int pages) { 93 | this.pages = pages; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /mm-common/src/main/java/org/dromara/wemq/common/enums/HttpStatus.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.common.enums; 2 | 3 | /** 4 | * 状态码 5 | * @author NicholasLD 6 | * @createTime 2023/4/3 16:57 7 | */ 8 | public enum HttpStatus { 9 | OK(200, "成功"), 10 | FAIL(0,"失败"), 11 | COMMON_EXCEPTION(500, "系统异常"), 12 | 13 | // 1xxx 用户相关 14 | USER_NOT_EXIST(1001, "用户不存在"), 15 | USER_NOT_LOGIN(1002, "用户未登录"), 16 | USER_NOT_AUTH(1003, "用户未授权"), 17 | USER_NOT_PERMISSION(1004, "用户无权限"), 18 | USER_NOT_EXIST_OR_PASSWORD_ERROR(1005, "用户名或密码错误"), 19 | 20 | // 2xxx 业务相关 21 | BUSINESS_EXCEPTION(2001, "业务异常"), 22 | BUSINESS_NOT_EXIST(2002, "业务不存在"), 23 | BUSINESS_NOT_AUTH(2003, "业务未授权"), 24 | 25 | // 3xxx 系统相关 26 | REPEAT_SUBMIT(3001, "重复提交"); 27 | 28 | 29 | private final int value; 30 | private final String message; 31 | 32 | HttpStatus(int value, String message) { 33 | this.value = value; 34 | this.message = message; 35 | } 36 | 37 | public int getCode() { 38 | return value; 39 | } 40 | 41 | public String getMessage() { 42 | return message; 43 | } 44 | 45 | public static HttpStatus valueOf(int value) { 46 | for (HttpStatus httpStatus : values()) { 47 | if (httpStatus.value == value) { 48 | return httpStatus; 49 | } 50 | } 51 | throw new IllegalArgumentException("No matching constant for [" + value + "]"); 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /mm-common/src/main/java/org/dromara/wemq/common/exception/MMException.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.common.exception; 2 | 3 | import org.dromara.wemq.common.enums.HttpStatus; 4 | 5 | /** 6 | * 自定义异常 7 | * @author NicholasLD 8 | * @createTime 2023/4/3 17:03 9 | */ 10 | public class MMException extends RuntimeException{ 11 | private Integer code; // 错误码 12 | private String message; // 错误信息 13 | 14 | public MMException(Integer code, String message) { 15 | this.code = code; 16 | this.message = message; 17 | } 18 | 19 | public MMException(HttpStatus httpStatus) { 20 | this.code = httpStatus.getCode(); 21 | this.message = httpStatus.getMessage(); 22 | } 23 | 24 | public Integer getCode() { 25 | return code; 26 | } 27 | 28 | public void setCode(Integer code) { 29 | this.code = code; 30 | } 31 | 32 | public String getMessage() { 33 | return message; 34 | } 35 | 36 | public void setMessage(String message) { 37 | this.message = message; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /mm-framework/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.dromara 8 | WeMQ 9 | ${revision} 10 | 11 | 12 | mm-framework 13 | 14 | 15 | org.dromara 16 | mm-common 17 | ${revision} 18 | compile 19 | 20 | 21 | com.fasterxml.jackson.core 22 | jackson-databind 23 | 2.15.0-rc2 24 | 25 | 26 | org.apache.commons 27 | commons-lang3 28 | 29 | 30 | org.projectlombok 31 | lombok 32 | true 33 | 34 | 35 | com.alibaba.fastjson2 36 | fastjson2 37 | 2.0.37 38 | 39 | 40 | org.springframework 41 | spring-tx 42 | 43 | 44 | com.baomidou 45 | mybatis-plus-spring-boot3-starter 46 | 3.5.9 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /mm-framework/src/main/java/org/dromara/wemq/framework/aspectj/RepeatSubmitAspect.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.framework.aspectj; 2 | 3 | import org.aspectj.lang.annotation.Aspect; 4 | import org.aspectj.lang.annotation.Before; 5 | import org.aspectj.lang.annotation.Pointcut; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.stereotype.Component; 9 | 10 | /** 11 | * 防止重复提交注解切面 12 | * @author NicholasLD 13 | * @createTime 2023/4/3 13:06 14 | */ 15 | @Aspect 16 | @Component 17 | public class RepeatSubmitAspect { 18 | private static final Logger logger = LoggerFactory.getLogger(RepeatSubmitAspect.class); 19 | 20 | // 配置织入点 21 | @Pointcut("@annotation(org.dromara.wemq.common.annotation.RepeatSubmit)") 22 | public void repeatSubmitPointCut() { 23 | } 24 | 25 | @Before("repeatSubmitPointCut()") 26 | public void doBefore() { 27 | logger.info("[防重复提交] 介入切面,开始检测重复提交"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mm-framework/src/main/java/org/dromara/wemq/framework/config/BigNumberSerializer.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.framework.config; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.SerializerProvider; 5 | import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; 6 | import com.fasterxml.jackson.databind.ser.std.NumberSerializer; 7 | 8 | import java.io.IOException; 9 | 10 | /** 11 | * @author NicholasLD 12 | * @date 2024/12/13 13 | */ 14 | @JacksonStdImpl 15 | public class BigNumberSerializer extends NumberSerializer { 16 | 17 | /** 18 | * 根据 JS Number.MAX_SAFE_INTEGER 与 Number.MIN_SAFE_INTEGER 得来 19 | */ 20 | private static final long MAX_SAFE_INTEGER = 9007199254740991L; 21 | private static final long MIN_SAFE_INTEGER = -9007199254740991L; 22 | 23 | /** 24 | * 提供实例 25 | */ 26 | public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class); 27 | 28 | public BigNumberSerializer(Class rawType) { 29 | super(rawType); 30 | } 31 | 32 | @Override 33 | public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException { 34 | // 超出范围 序列化位字符串 35 | if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) { 36 | super.serialize(value, gen, provider); 37 | } else { 38 | gen.writeString(value.toString()); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mm-framework/src/main/java/org/dromara/wemq/framework/config/JacksonConfig.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.framework.config; 2 | 3 | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 4 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 5 | import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; 6 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.core.Ordered; 11 | import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; 12 | import org.springframework.stereotype.Component; 13 | 14 | import java.math.BigDecimal; 15 | import java.math.BigInteger; 16 | import java.time.LocalDateTime; 17 | import java.time.format.DateTimeFormatter; 18 | import java.util.TimeZone; 19 | 20 | /** 21 | * @author NicholasLD 22 | * @date 2024/12/13 23 | */ 24 | @Slf4j 25 | @Component 26 | public class JacksonConfig implements Jackson2ObjectMapperBuilderCustomizer, Ordered { 27 | @Bean 28 | public Jackson2ObjectMapperBuilderCustomizer customizer() { 29 | return builder -> { 30 | // 全局配置序列化返回 JSON 处理 31 | JavaTimeModule javaTimeModule = new JavaTimeModule(); 32 | javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE); 33 | javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE); 34 | javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE); 35 | javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance); 36 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); 37 | javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); 38 | javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter)); 39 | builder.modules(javaTimeModule); 40 | builder.timeZone(TimeZone.getDefault()); 41 | log.info("初始化 jackson 配置"); 42 | }; 43 | } 44 | 45 | @Override 46 | public int getOrder() { 47 | return 1; 48 | } 49 | 50 | @Override 51 | public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) { 52 | log.info("customize jackson"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /mm-framework/src/main/java/org/dromara/wemq/framework/config/MvcConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.framework.config; 2 | 3 | import org.dromara.wemq.framework.interceptor.LoginInterceptor; 4 | import org.dromara.wemq.framework.interceptor.RepeatSubmitInterceptor; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 7 | import org.springframework.web.servlet.config.annotation.*; 8 | 9 | /** 10 | * @author NicholasLD 11 | * @createTime 2023/6/19 00:18 12 | */ 13 | @EnableAspectJAutoProxy 14 | @Configuration 15 | public class MvcConfiguration implements WebMvcConfigurer { 16 | 17 | @Override 18 | public void addInterceptors(InterceptorRegistry registry) { 19 | // 配置LoginInterceptor拦截器 20 | registry.addInterceptor(new LoginInterceptor()) 21 | .addPathPatterns("/**") 22 | .excludePathPatterns("/statics/**", "/dologin", "/login", "/mq/**", "/error/**","/static/**"); 23 | 24 | // 配置RepeatSubmitInterceptor拦截器 25 | registry.addInterceptor(new RepeatSubmitInterceptor()) 26 | .addPathPatterns("/**"); 27 | 28 | } 29 | 30 | @Override 31 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 32 | registry.addResourceHandler("/statics/**") 33 | .addResourceLocations("classpath:/static/"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mm-framework/src/main/java/org/dromara/wemq/framework/config/MybatisPlusConfig.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.framework.config; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.transaction.annotation.EnableTransactionManagement; 5 | 6 | /** 7 | * @author NicholasLD 8 | * @date 2024/12/16 9 | */ 10 | @EnableTransactionManagement(proxyTargetClass = true) 11 | @MapperScan("org.dromara.wemq.mapper") 12 | public class MybatisPlusConfig { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /mm-framework/src/main/java/org/dromara/wemq/framework/config/TemplateResolverConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.framework.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver; 6 | import org.thymeleaf.templatemode.TemplateMode; 7 | import org.thymeleaf.templateresolver.FileTemplateResolver; 8 | 9 | /** 10 | * @author NicholasLD 11 | * @createTime 2023/7/11 22:39 12 | */ 13 | @Configuration 14 | public class TemplateResolverConfiguration { 15 | @Bean 16 | public SpringResourceTemplateResolver templateResolver() { 17 | SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); 18 | templateResolver.setPrefix("classpath:/templates/"); 19 | templateResolver.setSuffix(".html"); 20 | templateResolver.setCacheable(false); 21 | templateResolver.setTemplateMode("HTML5"); 22 | templateResolver.setCharacterEncoding("UTF-8"); 23 | templateResolver.setOrder(0); 24 | templateResolver.setCheckExistence(true); 25 | return templateResolver; 26 | } 27 | 28 | @Bean 29 | public FileTemplateResolver secondTemplateResolver() { 30 | FileTemplateResolver templateResolver = new FileTemplateResolver(); 31 | templateResolver.setPrefix("pages/"); 32 | templateResolver.setSuffix(".html"); 33 | templateResolver.setCacheable(false); 34 | templateResolver.setTemplateMode(TemplateMode.HTML); 35 | templateResolver.setCharacterEncoding("UTF-8"); 36 | templateResolver.setOrder(1); 37 | templateResolver.setCheckExistence(true); 38 | 39 | return templateResolver; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mm-framework/src/main/java/org/dromara/wemq/framework/interceptor/ExceptionInterceptor.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.framework.interceptor; 2 | 3 | import jakarta.servlet.http.HttpServletRequest; 4 | import jakarta.servlet.http.HttpServletResponse; 5 | import org.dromara.wemq.common.core.domain.AjaxResult; 6 | import org.dromara.wemq.common.exception.MMException; 7 | import org.springframework.web.bind.annotation.ControllerAdvice; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.ResponseBody; 10 | 11 | /** 12 | * 全局异常处理 13 | * @author NicholasLD 14 | * @createTime 2023/4/3 17:08 15 | */ 16 | @ControllerAdvice 17 | public class ExceptionInterceptor { 18 | @ResponseBody 19 | @ExceptionHandler(MMException.class) 20 | public AjaxResult processQYException(Throwable cause, HttpServletRequest request, HttpServletResponse response){ 21 | MMException mmException = null; 22 | if(cause instanceof MMException){ 23 | mmException = (MMException) cause; 24 | } 25 | if (mmException != null) { 26 | return new AjaxResult(mmException.getCode(),mmException.getMessage()); 27 | } 28 | return AjaxResult.error("未知错误"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mm-framework/src/main/java/org/dromara/wemq/framework/interceptor/LoginInterceptor.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.framework.interceptor; 2 | 3 | import jakarta.servlet.http.HttpServletRequest; 4 | import jakarta.servlet.http.HttpServletResponse; 5 | import org.dromara.wemq.common.constant.UserConstants; 6 | import org.slf4j.Logger; 7 | import org.springframework.web.servlet.HandlerInterceptor; 8 | 9 | /** 10 | * 登录拦截器 11 | * @author NicholasLD 12 | * @createTime 2023/4/3 10:55 13 | */ 14 | public class LoginInterceptor implements HandlerInterceptor { 15 | Logger logger = org.slf4j.LoggerFactory.getLogger(LoginInterceptor.class); 16 | 17 | @Override 18 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 19 | logger.info("检测到请求:{}", request.getRequestURI()); 20 | 21 | if(request.getRequestURI().equals("/login") || request.getRequestURI().equals("/dologin")){ 22 | //判断用户是否已经登录 23 | if (request.getSession().getAttribute(UserConstants.USER_SESSION) != null) { 24 | logger.info("[登录] 用户已登录,跳转到主页"); 25 | response.sendRedirect("/"); 26 | return false; 27 | } 28 | return true; 29 | } 30 | 31 | if (request.getRequestURI().equals("/logout")) { 32 | //判断用户是否已经登录 33 | if (request.getSession().getAttribute(UserConstants.USER_SESSION) != null) { 34 | logger.info("[注销] 用户已登录,放行"); 35 | return true; 36 | } 37 | response.sendRedirect("/login"); 38 | return false; 39 | } 40 | 41 | // 判断session中是否有用户信息 42 | if (request.getSession().getAttribute(UserConstants.USER_SESSION) == null) { 43 | logger.info("用户未登录,跳转到登录页面"); 44 | response.sendRedirect("/login?redirect="+request.getRequestURI()); 45 | return false; 46 | } 47 | 48 | // 已登录,放行 49 | return true; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /mm-framework/src/main/java/org/dromara/wemq/framework/interceptor/RepeatSubmitInterceptor.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.framework.interceptor; 2 | 3 | import jakarta.servlet.http.HttpServletRequest; 4 | import jakarta.servlet.http.HttpServletResponse; 5 | import jakarta.servlet.http.HttpSession; 6 | import org.dromara.wemq.common.annotation.RepeatSubmit; 7 | import org.dromara.wemq.common.enums.HttpStatus; 8 | import org.dromara.wemq.common.exception.MMException; 9 | import com.fasterxml.jackson.databind.ObjectWriter; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.web.method.HandlerMethod; 14 | import org.springframework.web.servlet.HandlerInterceptor; 15 | 16 | import java.lang.reflect.Method; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | /** 21 | * 防止重复提交拦截器 22 | * @author NicholasLD 23 | * @createTime 2023/4/3 13:11 24 | */ 25 | @Component 26 | public class RepeatSubmitInterceptor implements HandlerInterceptor { 27 | private static final Logger logger = LoggerFactory.getLogger(RepeatSubmitInterceptor.class); 28 | public final String REPEAT_PARAMS = "repeatParams"; 29 | public final String REPEAT_TIME = "repeatTime"; 30 | public final String SESSION_REPEAT_KEY = "repeatData"; 31 | private static final com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper(); 32 | private static final ObjectWriter objectWriter = objectMapper.writerWithDefaultPrettyPrinter(); 33 | 34 | @Override 35 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 36 | if (handler instanceof HandlerMethod) { 37 | HandlerMethod handlerMethod = (HandlerMethod) handler; 38 | Method method = handlerMethod.getMethod(); 39 | RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); 40 | if (annotation != null) { 41 | if (this.isRepeatSubmit(request, annotation)) { 42 | logger.info("[防重复提交] 检测到重复提交请求:" + request.getRequestURI()); 43 | throw new MMException(HttpStatus.REPEAT_SUBMIT); 44 | } 45 | } 46 | } 47 | return true; 48 | } 49 | 50 | public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) throws Exception { 51 | // 本次参数及系统时间 52 | String nowParams = objectWriter.writeValueAsString(request.getParameterMap()); 53 | Map nowDataMap = new HashMap<>(); 54 | nowDataMap.put(REPEAT_PARAMS, nowParams); 55 | nowDataMap.put(REPEAT_TIME, System.currentTimeMillis()); 56 | 57 | // 请求地址(作为存放session的key值) 58 | String url = request.getRequestURI(); 59 | 60 | HttpSession session = request.getSession(); 61 | Object sessionObj = session.getAttribute(SESSION_REPEAT_KEY); 62 | if (sessionObj != null) { 63 | Map sessionMap = (Map) sessionObj; 64 | if (sessionMap.containsKey(url)) { 65 | Map preDataMap = (Map) sessionMap.get(url); 66 | if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval())) { 67 | return true; 68 | } 69 | } 70 | } 71 | Map sessionMap = new HashMap<>(); 72 | sessionMap.put(url, nowDataMap); 73 | session.setAttribute(SESSION_REPEAT_KEY, sessionMap); 74 | return false; 75 | } 76 | 77 | /** 78 | * 判断参数是否相同 79 | */ 80 | private boolean compareParams(Map nowMap, Map preMap) { 81 | String nowParams = (String) nowMap.get(REPEAT_PARAMS); 82 | String preParams = (String) preMap.get(REPEAT_PARAMS); 83 | return nowParams.equals(preParams); 84 | } 85 | 86 | /** 87 | * 判断两次间隔时间 88 | */ 89 | private boolean compareTime(Map nowMap, Map preMap, int interval) { 90 | long time1 = (Long) nowMap.get(REPEAT_TIME); 91 | long time2 = (Long) preMap.get(REPEAT_TIME); 92 | if ((time1 - time2) < interval) { 93 | return true; 94 | } 95 | return false; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /mm-framework/src/main/java/org/dromara/wemq/framework/utils/ServletUtils.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.framework.utils; 2 | 3 | import jakarta.servlet.ServletRequest; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import jakarta.servlet.http.HttpServletResponse; 6 | import jakarta.servlet.http.HttpSession; 7 | import org.springframework.web.context.request.RequestAttributes; 8 | import org.springframework.web.context.request.RequestContextHolder; 9 | import org.springframework.web.context.request.ServletRequestAttributes; 10 | 11 | import java.io.IOException; 12 | import java.io.UnsupportedEncodingException; 13 | import java.net.URLDecoder; 14 | import java.net.URLEncoder; 15 | import java.util.*; 16 | 17 | /** 18 | * @from ruoyi 19 | * @author NicholasLD 20 | * @createTime 2023/9/5 14:11 21 | */ 22 | public class ServletUtils { 23 | /** 24 | * 获取String参数 25 | */ 26 | public static String getParameter(String name) 27 | { 28 | return getRequest().getParameter(name); 29 | } 30 | 31 | /** 32 | * 获取String参数 33 | */ 34 | public static String getParameter(String name, String defaultValue) 35 | { 36 | return Convert.toStr(getRequest().getParameter(name), defaultValue); 37 | } 38 | 39 | /** 40 | * 获取Integer参数 41 | */ 42 | public static Integer getParameterToInt(String name) 43 | { 44 | return Convert.toInt(getRequest().getParameter(name)); 45 | } 46 | 47 | /** 48 | * 获取Integer参数 49 | */ 50 | public static Integer getParameterToInt(String name, Integer defaultValue) 51 | { 52 | return Convert.toInt(getRequest().getParameter(name), defaultValue); 53 | } 54 | 55 | /** 56 | * 获取Boolean参数 57 | */ 58 | public static Boolean getParameterToBool(String name) 59 | { 60 | return Convert.toBool(getRequest().getParameter(name)); 61 | } 62 | 63 | /** 64 | * 获取Boolean参数 65 | */ 66 | public static Boolean getParameterToBool(String name, Boolean defaultValue) 67 | { 68 | return Convert.toBool(getRequest().getParameter(name), defaultValue); 69 | } 70 | 71 | /** 72 | * 获得所有请求参数 73 | * 74 | * @param request 请求对象{@link ServletRequest} 75 | * @return Map 76 | */ 77 | public static Map getParams(ServletRequest request) 78 | { 79 | final Map map = request.getParameterMap(); 80 | return Collections.unmodifiableMap(map); 81 | } 82 | 83 | /** 84 | * 获得所有请求参数 85 | * 86 | * @param request 请求对象{@link ServletRequest} 87 | * @return Map 88 | */ 89 | public static Map getParamMap(ServletRequest request) 90 | { 91 | Map params = new HashMap<>(); 92 | for (Map.Entry entry : getParams(request).entrySet()) 93 | { 94 | params.put(entry.getKey(), StringUtils.join(Arrays.asList(entry.getValue()), ',')); 95 | } 96 | return params; 97 | } 98 | 99 | /** 100 | * 获取request 101 | */ 102 | public static HttpServletRequest getRequest() 103 | { 104 | return getRequestAttributes().getRequest(); 105 | } 106 | 107 | /** 108 | * 获取response 109 | */ 110 | public static HttpServletResponse getResponse() 111 | { 112 | return getRequestAttributes().getResponse(); 113 | } 114 | 115 | /** 116 | * 获取session 117 | */ 118 | public static HttpSession getSession() 119 | { 120 | return getRequest().getSession(); 121 | } 122 | 123 | public static ServletRequestAttributes getRequestAttributes() 124 | { 125 | RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); 126 | return (ServletRequestAttributes) attributes; 127 | } 128 | 129 | /** 130 | * 将字符串渲染到客户端 131 | * 132 | * @param response 渲染对象 133 | * @param string 待渲染的字符串 134 | */ 135 | public static void renderString(HttpServletResponse response, String string) 136 | { 137 | try 138 | { 139 | response.setStatus(200); 140 | response.setContentType("application/json"); 141 | response.setCharacterEncoding("utf-8"); 142 | response.getWriter().print(string); 143 | } 144 | catch (IOException e) 145 | { 146 | e.printStackTrace(); 147 | } 148 | } 149 | 150 | /** 151 | * 是否是Ajax异步请求 152 | * 153 | * @param request 154 | */ 155 | public static boolean isAjaxRequest(HttpServletRequest request) 156 | { 157 | String accept = request.getHeader("accept"); 158 | if (accept != null && accept.contains("application/json")) 159 | { 160 | return true; 161 | } 162 | 163 | String xRequestedWith = request.getHeader("X-Requested-With"); 164 | if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) 165 | { 166 | return true; 167 | } 168 | 169 | String uri = request.getRequestURI(); 170 | if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml")) 171 | { 172 | return true; 173 | } 174 | 175 | String ajax = request.getParameter("__ajax"); 176 | return StringUtils.inStringIgnoreCase(ajax, "json", "xml"); 177 | } 178 | 179 | /** 180 | * 内容编码 181 | * 182 | * @param str 内容 183 | * @return 编码后的内容 184 | */ 185 | public static String urlEncode(String str) 186 | { 187 | try 188 | { 189 | return URLEncoder.encode(str, "UTF-8"); 190 | } 191 | catch (UnsupportedEncodingException e) 192 | { 193 | return StringUtils.EMPTY; 194 | } 195 | } 196 | 197 | /** 198 | * 内容解码 199 | * 200 | * @param str 内容 201 | * @return 解码后的内容 202 | */ 203 | public static String urlDecode(String str) 204 | { 205 | try 206 | { 207 | return URLDecoder.decode(str, "UTF-8"); 208 | } 209 | catch (UnsupportedEncodingException e) 210 | { 211 | return StringUtils.EMPTY; 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /mm-framework/src/main/java/org/dromara/wemq/framework/utils/StrFormatter.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.framework.utils; 2 | 3 | /** 4 | * @from ruoyi 5 | * @author NicholasLD 6 | * @createTime 2023/9/5 14:13 7 | */ 8 | public class StrFormatter { 9 | public static final String EMPTY_JSON = "{}"; 10 | public static final char C_BACKSLASH = '\\'; 11 | public static final char C_DELIM_START = '{'; 12 | public static final char C_DELIM_END = '}'; 13 | 14 | /** 15 | * 格式化字符串
16 | * 此方法只是简单将占位符 {} 按照顺序替换为参数
17 | * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
18 | * 例:
19 | * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
20 | * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
21 | * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
22 | * 23 | * @param strPattern 字符串模板 24 | * @param argArray 参数列表 25 | * @return 结果 26 | */ 27 | public static String format(final String strPattern, final Object... argArray) 28 | { 29 | if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray)) 30 | { 31 | return strPattern; 32 | } 33 | final int strPatternLength = strPattern.length(); 34 | 35 | // 初始化定义好的长度以获得更好的性能 36 | StringBuilder sbuf = new StringBuilder(strPatternLength + 50); 37 | 38 | int handledPosition = 0; 39 | int delimIndex;// 占位符所在位置 40 | for (int argIndex = 0; argIndex < argArray.length; argIndex++) 41 | { 42 | delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition); 43 | if (delimIndex == -1) 44 | { 45 | if (handledPosition == 0) 46 | { 47 | return strPattern; 48 | } 49 | else 50 | { // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果 51 | sbuf.append(strPattern, handledPosition, strPatternLength); 52 | return sbuf.toString(); 53 | } 54 | } 55 | else 56 | { 57 | if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH) 58 | { 59 | if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH) 60 | { 61 | // 转义符之前还有一个转义符,占位符依旧有效 62 | sbuf.append(strPattern, handledPosition, delimIndex - 1); 63 | sbuf.append(Convert.utf8Str(argArray[argIndex])); 64 | handledPosition = delimIndex + 2; 65 | } 66 | else 67 | { 68 | // 占位符被转义 69 | argIndex--; 70 | sbuf.append(strPattern, handledPosition, delimIndex - 1); 71 | sbuf.append(C_DELIM_START); 72 | handledPosition = delimIndex + 1; 73 | } 74 | } 75 | else 76 | { 77 | // 正常占位符 78 | sbuf.append(strPattern, handledPosition, delimIndex); 79 | sbuf.append(Convert.utf8Str(argArray[argIndex])); 80 | handledPosition = delimIndex + 2; 81 | } 82 | } 83 | } 84 | // 加入最后一个占位符后所有的字符 85 | sbuf.append(strPattern, handledPosition, strPattern.length()); 86 | 87 | return sbuf.toString(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /mm-web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.dromara 8 | WeMQ 9 | ${revision} 10 | 11 | 12 | mm-web 13 | jar 14 | 15 | 16 | 17 | org.dromara 18 | mm-common 19 | ${revision} 20 | 21 | 22 | org.dromara 23 | mm-framework 24 | ${revision} 25 | 26 | 27 | com.baomidou 28 | mybatis-plus-spring-boot3-starter 29 | 3.5.9 30 | 31 | 32 | 33 | org.apache.commons 34 | commons-fileupload2-jakarta-servlet6 35 | 2.0.0-M2 36 | 37 | 38 | commons-io 39 | commons-io 40 | 2.18.0 41 | 42 | 43 | com.github.pagehelper 44 | pagehelper-spring-boot-starter 45 | 2.1.0 46 | 47 | 48 | org.projectlombok 49 | lombok 50 | provided 51 | 52 | 53 | com.fasterxml.jackson.core 54 | jackson-annotations 55 | 2.18.2 56 | compile 57 | 58 | 59 | com.github.oshi 60 | oshi-core-java11 61 | 6.6.5 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-maven-plugin 70 | 3.4.0 71 | 72 | org.dromara.wemq.WeMQApplication 73 | false 74 | 75 | 76 | 77 | repackage 78 | 79 | repackage 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/WeMQApplication.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * @author NicholasLD 8 | * @createTime 2023/6/18 23:31 9 | */ 10 | @SpringBootApplication 11 | public class WeMQApplication { 12 | public static void main(String[] args) { 13 | SpringApplication.run(WeMQApplication.class, args); 14 | System.out.println( 15 | "WeMQ启动成功!\n" + 16 | "██╗ ██╗███████╗███╗ ███╗ ██████╗\n" + 17 | "██║ ██║██╔════╝████╗ ████║██╔═══██╗\n" + 18 | "██║ █╗ ██║█████╗ ██╔████╔██║██║ ██║\n" + 19 | "██║███╗██║██╔══╝ ██║╚██╔╝██║██║▄▄ ██║\n" + 20 | "╚███╔███╔╝███████╗██║ ╚═╝ ██║╚██████╔╝\n" + 21 | " ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝ ╚══▀▀═╝"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/controller/AdminController.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.controller; 2 | 3 | import org.dromara.wemq.common.annotation.RepeatSubmit; 4 | import org.dromara.wemq.common.core.controller.BaseController; 5 | import org.dromara.wemq.common.core.domain.AjaxResult; 6 | import org.dromara.wemq.common.core.page.TableData; 7 | import org.dromara.wemq.model.pojo.Admin; 8 | import org.dromara.wemq.service.AdminService; 9 | import com.github.pagehelper.Page; 10 | import com.github.pagehelper.page.PageMethod; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.stereotype.Controller; 13 | import org.springframework.web.bind.annotation.*; 14 | 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | /** 20 | * @author NicholasLD 21 | * @createTime 2023/4/27 19:21 22 | */ 23 | @Controller 24 | @RequestMapping("/admin") 25 | @RequiredArgsConstructor 26 | public class AdminController extends BaseController { 27 | private final AdminService adminService; 28 | 29 | @RequestMapping("/list") 30 | @ResponseBody 31 | public AjaxResult list(@RequestParam("pageNum") int pageNum, 32 | @RequestParam("pageSize") int pageSize) { 33 | Page page = PageMethod.startPage(pageNum, pageSize); 34 | List list = adminService.getAdminListByMap(null); 35 | 36 | return AjaxResult.success(new TableData(list, pageNum, page.getPages(), page.getTotal())); 37 | } 38 | 39 | @RequestMapping("/info/{id}") 40 | @ResponseBody 41 | public AjaxResult info(@PathVariable("id") Long id) { 42 | Map params = new HashMap<>(); 43 | params.put("id", id); 44 | Admin admin = adminService.getAdminByMap(params); 45 | return AjaxResult.success(admin); 46 | } 47 | 48 | @RepeatSubmit(interval = 1000, message = "请勿重复提交") 49 | @PostMapping("/add") 50 | @ResponseBody 51 | public AjaxResult add(@RequestBody Admin admin) { 52 | return adminService.insertAdmin(admin) > 0 ? AjaxResult.success() : AjaxResult.error(); 53 | } 54 | 55 | @RepeatSubmit(interval = 1000, message = "请勿重复提交") 56 | @PostMapping("/update") 57 | @ResponseBody 58 | public AjaxResult update(@RequestBody Admin admin) { 59 | return adminService.updateAdminById(admin) > 0 ? AjaxResult.success() : AjaxResult.error(); 60 | } 61 | 62 | @RepeatSubmit(interval = 1000, message = "请勿重复提交") 63 | @PostMapping("/delete/{id}") 64 | @ResponseBody 65 | public AjaxResult delete(@PathVariable("id") Long id) { 66 | return adminService.deleteAdminById(id) > 0 ? AjaxResult.success() : AjaxResult.error(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/controller/CustomerController.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.controller; 2 | 3 | import org.dromara.wemq.common.annotation.RepeatSubmit; 4 | import org.dromara.wemq.common.core.controller.BaseController; 5 | import org.dromara.wemq.common.core.domain.AjaxResult; 6 | import org.dromara.wemq.common.core.page.TableData; 7 | import org.dromara.wemq.model.pojo.Customer; 8 | import org.dromara.wemq.service.CustomerService; 9 | import com.github.pagehelper.Page; 10 | import com.github.pagehelper.page.PageMethod; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.stereotype.Controller; 13 | import org.springframework.web.bind.annotation.*; 14 | 15 | import java.util.List; 16 | 17 | /** 18 | * @author NicholasLD 19 | * @createTime 2023/4/17 14:24 20 | */ 21 | @Controller 22 | @RequestMapping("/customer") 23 | @RequiredArgsConstructor 24 | public class CustomerController extends BaseController { 25 | private final CustomerService customerService; 26 | 27 | @RequestMapping("/list") 28 | @ResponseBody 29 | public AjaxResult list(@RequestParam("pageNum") int pageNum, 30 | @RequestParam("pageSize") int pageSize) { 31 | Page page = PageMethod.startPage(pageNum, pageSize); 32 | List list = customerService.getCustomers(); 33 | return AjaxResult.success(new TableData(list, pageNum, page.getPages(), page.getTotal())); 34 | } 35 | 36 | @RepeatSubmit(interval = 1000, message = "请勿重复提交") 37 | @PostMapping("/add") 38 | @ResponseBody 39 | public AjaxResult add(@RequestBody Customer customer) { 40 | return customerService.insertClient(customer) > 0 ? AjaxResult.success() : AjaxResult.error(); 41 | } 42 | 43 | @RepeatSubmit(interval = 1000, message = "请勿重复提交") 44 | @PostMapping("/update") 45 | @ResponseBody 46 | public AjaxResult update(@RequestBody Customer customer) { 47 | return customerService.updateClient(customer) > 0 ? AjaxResult.success() : AjaxResult.error(); 48 | } 49 | 50 | @RepeatSubmit(interval = 1000, message = "请勿重复提交") 51 | @PostMapping("/delete/{id}") 52 | @ResponseBody 53 | public AjaxResult delete(@PathVariable("id") Long id) { 54 | return customerService.deleteClientById(id) > 0 ? AjaxResult.success() : AjaxResult.error(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/controller/ErrorController.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.controller; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | 6 | /** 7 | * @author NicholasLD 8 | * @createTime 2023/4/13 21:33 9 | */ 10 | @Controller 11 | @RequestMapping("/error") 12 | public class ErrorController { 13 | 14 | @RequestMapping("/404") 15 | public String error404() { 16 | return "error/404"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/controller/LoginLogController.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.controller; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import org.dromara.wemq.common.core.controller.BaseController; 5 | import org.dromara.wemq.common.core.domain.AjaxResult; 6 | import org.dromara.wemq.common.core.page.TableData; 7 | import org.dromara.wemq.model.dto.LoginLogDto; 8 | import org.dromara.wemq.service.LoginLogService; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.stereotype.Controller; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * @author NicholasLD 17 | * @createTime 2023/4/30 00:41 18 | */ 19 | @Controller 20 | @RequestMapping("/loginLog") 21 | @RequiredArgsConstructor 22 | public class LoginLogController extends BaseController { 23 | private final LoginLogService loginLogService; 24 | 25 | @GetMapping("/list") 26 | @ResponseBody 27 | public AjaxResult getLoginLogList(@RequestParam(required = false) String time) { 28 | List loginLogList; 29 | if (StrUtil.isEmpty(time)) { 30 | loginLogList = loginLogService.getLoginLogList(null); 31 | return AjaxResult.success(new TableData(loginLogList, 1, 1, loginLogList.size())); 32 | } 33 | loginLogList = loginLogService.getLoginLogList(time); 34 | return AjaxResult.success(new TableData(loginLogList, 1, 1, loginLogList.size())); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/controller/MqPageController.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.controller; 2 | 3 | import org.dromara.wemq.common.annotation.RepeatSubmit; 4 | import org.dromara.wemq.common.core.domain.AjaxResult; 5 | import org.dromara.wemq.common.core.page.TableData; 6 | import org.dromara.wemq.model.dto.MqPageDto; 7 | import org.dromara.wemq.model.pojo.MQParam; 8 | import org.dromara.wemq.model.vo.MQPageVo; 9 | import org.dromara.wemq.service.MqPageService; 10 | import org.dromara.wemq.service.MqParamService; 11 | import com.github.pagehelper.Page; 12 | import com.github.pagehelper.PageHelper; 13 | import lombok.RequiredArgsConstructor; 14 | import org.springframework.stereotype.Controller; 15 | import org.springframework.web.bind.annotation.*; 16 | 17 | import java.util.HashMap; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | /** 22 | * @author NicholasLD 23 | * @createTime 2023/4/13 14:51 24 | */ 25 | @Controller 26 | @RequestMapping("/page") 27 | @RequiredArgsConstructor 28 | public class MqPageController { 29 | private final MqPageService mqPageService; 30 | private final MqParamService mqParamService; 31 | 32 | @GetMapping("/list") 33 | @ResponseBody 34 | public AjaxResult list(@RequestParam("pageNum") int pageNum, 35 | @RequestParam("pageSize") int pageSize, 36 | @RequestParam(required = false) Integer customerId , 37 | @RequestParam(required = false) Integer pageId, 38 | @RequestParam(required = false) String pageName, 39 | @RequestParam(required = false) Integer commonPage) { 40 | Map params = new HashMap<>(); 41 | params.put("id", pageId); 42 | params.put("pageName", pageName); 43 | params.put("customerID", customerId); 44 | params.put("commonPage", commonPage); 45 | Page page = PageHelper.startPage(pageNum, pageSize); 46 | List list = mqPageService.select(params); 47 | 48 | return AjaxResult.success(new TableData(list, pageNum, page.getPages(), page.getTotal())); 49 | } 50 | 51 | @GetMapping("/info/{id}") 52 | @ResponseBody 53 | public AjaxResult info(@PathVariable("id") Long id) { 54 | MqPageDto pageDto = mqPageService.selectById(id); 55 | return AjaxResult.success(pageDto); 56 | } 57 | 58 | @RepeatSubmit(interval = 1000, message = "请勿重复提交") 59 | @PostMapping("/add") 60 | @ResponseBody 61 | public AjaxResult add(@RequestBody MQPageVo page) { 62 | return mqPageService.insert(page) > 0 ? AjaxResult.success() : AjaxResult.error(); 63 | } 64 | 65 | @RepeatSubmit(interval = 1000, message = "请勿重复提交") 66 | @PostMapping("/update") 67 | @ResponseBody 68 | public AjaxResult update(@RequestBody MQPageVo page) { 69 | return mqPageService.update(page) > 0 ? AjaxResult.success() : AjaxResult.error(); 70 | } 71 | 72 | @RepeatSubmit(interval = 1000, message = "请勿重复提交") 73 | @PostMapping("/updateTopic") 74 | @ResponseBody 75 | public AjaxResult updateTopic(@RequestBody MQPageVo page) { 76 | return mqPageService.updateTopic(page) > 0 ? AjaxResult.success() : AjaxResult.error(); 77 | } 78 | 79 | @RepeatSubmit(interval = 1000, message = "请勿重复提交") 80 | @PostMapping("/delete/{id}") 81 | @ResponseBody 82 | public AjaxResult delete(@PathVariable("id") Long id) { 83 | return mqPageService.deleteById(id) > 0 ? AjaxResult.success() : AjaxResult.error(); 84 | } 85 | 86 | @RepeatSubmit(interval = 1000, message = "请勿重复提交") 87 | @PostMapping("/{pageId}/param/add") 88 | @ResponseBody 89 | public AjaxResult addParam(@PathVariable("pageId") Long pageId, @RequestBody MQParam param) { 90 | return mqParamService.insert(param,pageId) > 0 ? AjaxResult.success() : AjaxResult.error(); 91 | } 92 | 93 | @RepeatSubmit(interval = 1000, message = "请勿重复提交") 94 | @PostMapping("/param/delete/{id}") 95 | @ResponseBody 96 | public AjaxResult deleteParam(@PathVariable("id") Long id) { 97 | return mqParamService.delete(id) > 0 ? AjaxResult.success() : AjaxResult.error(); 98 | } 99 | 100 | @RepeatSubmit(interval = 1000, message = "请勿重复提交") 101 | @PostMapping("/param/update") 102 | @ResponseBody 103 | public AjaxResult updateParam(@RequestBody MQParam param) { 104 | return mqParamService.update(param) > 0 ? AjaxResult.success() : AjaxResult.error(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/controller/NmqsController.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.controller; 2 | 3 | import org.dromara.wemq.common.annotation.RepeatSubmit; 4 | import org.dromara.wemq.common.core.domain.AjaxResult; 5 | import org.dromara.wemq.common.core.page.TableData; 6 | import org.dromara.wemq.model.pojo.NmqsToken; 7 | import org.dromara.wemq.service.NmqsService; 8 | import com.github.pagehelper.Page; 9 | import com.github.pagehelper.PageHelper; 10 | import lombok.RequiredArgsConstructor; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.web.bind.annotation.*; 13 | 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | /** 19 | * @author NicholasLD 20 | * @createTime 2023/4/16 14:22 21 | */ 22 | @Controller 23 | @RequestMapping("/nmqs") 24 | @RequiredArgsConstructor 25 | public class NmqsController { 26 | private final NmqsService nmqsService; 27 | 28 | @GetMapping("/list") 29 | @ResponseBody 30 | public AjaxResult list(@RequestParam("pageNum") int pageNum, 31 | @RequestParam("pageSize") int pageSize, 32 | @RequestParam(required = false) Long pageId, 33 | @RequestParam(required = false) String pageName) { 34 | Map params = new HashMap<>(); 35 | params.put("id", pageId); 36 | params.put("pageName", pageName); 37 | Page page = PageHelper.startPage(pageNum, pageSize); 38 | List list = nmqsService.select(params); 39 | 40 | return AjaxResult.success(new TableData(list, pageNum, page.getPages(), page.getTotal())); 41 | } 42 | 43 | @GetMapping("/info/{id}") 44 | @ResponseBody 45 | public AjaxResult info(@PathVariable("id") String id) { 46 | NmqsToken nmqsToken = nmqsService.selectById(Long.valueOf(id)); 47 | return AjaxResult.success(nmqsToken); 48 | } 49 | 50 | @RepeatSubmit(interval = 1000, message = "请勿重复提交") 51 | @PostMapping("/add") 52 | @ResponseBody 53 | public AjaxResult add(@RequestBody NmqsToken nmqsToken) { 54 | return nmqsService.insert(nmqsToken) > 0 ? AjaxResult.success() : AjaxResult.error(); 55 | } 56 | 57 | @RepeatSubmit(interval = 1000, message = "请勿重复提交") 58 | @PostMapping("/update") 59 | @ResponseBody 60 | public AjaxResult update(@RequestBody NmqsToken nmqsToken) { 61 | return nmqsService.update(nmqsToken) > 0 ? AjaxResult.success() : AjaxResult.error(); 62 | } 63 | 64 | @RepeatSubmit(interval = 1000, message = "请勿重复提交") 65 | @PostMapping("/delete/{id}") 66 | @ResponseBody 67 | public AjaxResult delete(@PathVariable("id") Long id) { 68 | return nmqsService.deleteById(id) > 0 ? AjaxResult.success() : AjaxResult.error("删除失败,该token正在被页面使用"); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/controller/ViewController.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.controller; 2 | 3 | import cn.hutool.core.text.CharSequenceUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import cn.hutool.system.oshi.CpuInfo; 6 | import cn.hutool.system.oshi.OshiUtil; 7 | import jakarta.servlet.http.HttpServletRequest; 8 | import jakarta.servlet.http.HttpSession; 9 | import org.dromara.wemq.common.constant.UserConstants; 10 | import org.dromara.wemq.common.core.controller.BaseController; 11 | import org.dromara.wemq.mapper.NmqsMapper; 12 | import org.dromara.wemq.model.dto.MqPageDto; 13 | import org.dromara.wemq.model.pojo.Admin; 14 | import org.dromara.wemq.model.pojo.NmqsToken; 15 | import org.dromara.wemq.service.AdminService; 16 | import org.dromara.wemq.service.LoginLogService; 17 | import org.dromara.wemq.service.MqPageService; 18 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 19 | import lombok.RequiredArgsConstructor; 20 | import org.springframework.core.env.Environment; 21 | import org.springframework.http.ResponseEntity; 22 | import org.springframework.stereotype.Controller; 23 | import org.springframework.ui.Model; 24 | import org.springframework.web.bind.annotation.GetMapping; 25 | import org.springframework.web.bind.annotation.PathVariable; 26 | import org.springframework.web.bind.annotation.PostMapping; 27 | import org.springframework.web.servlet.ModelAndView; 28 | import oshi.PlatformEnum; 29 | import oshi.SystemInfo; 30 | 31 | import java.util.HashMap; 32 | import java.util.Map; 33 | 34 | /** 35 | * @author NicholasLD 36 | * @createTime 2023/4/13 14:49 37 | */ 38 | @Controller 39 | @RequiredArgsConstructor 40 | public class ViewController extends BaseController { 41 | private final MqPageService mqPageService; 42 | private final NmqsMapper nmqsMapper; 43 | private final AdminService adminService; 44 | private final LoginLogService loginLogService; 45 | private final Environment environment; 46 | 47 | @GetMapping("/") 48 | public String index(Model model) { 49 | PlatformEnum currentPlatform = SystemInfo.getCurrentPlatform(); 50 | CpuInfo cpuInfo = OshiUtil.getCpuInfo(); 51 | 52 | // 设置 CPU 信息 53 | model.addAttribute("platform", currentPlatform); 54 | model.addAttribute("CPU", cpuInfo.getCpuModel()); 55 | model.addAttribute("CPUUsage", cpuInfo.getUsed()); 56 | 57 | // 获取内存信息 58 | long totalBytes = OshiUtil.getMemory().getTotal(); 59 | long availableBytes = OshiUtil.getMemory().getAvailable(); 60 | 61 | double total = Math.round(((double) totalBytes / 1024 / 1024 / 1024) * 100) / 100.0; 62 | double available = Math.round(((double) availableBytes / 1024 / 1024 / 1024) * 100) / 100.0; 63 | double used = Math.round((total - available) * 100.0) / 100.0; 64 | 65 | model.addAttribute("Memory", total); 66 | model.addAttribute("MemoryAvailable", available); 67 | model.addAttribute("MemoryUsage", ((used / total) * 100)); 68 | model.addAttribute("MemoryUsed", used); 69 | 70 | // 获取 JRE 版本 71 | model.addAttribute("JRE", System.getProperty("java.version")); 72 | 73 | // 获取页面和管理员数量 74 | model.addAttribute("pageCount", mqPageService.count(new QueryWrapper<>())); 75 | model.addAttribute("adminCount", adminService.getAdminCountByMap(null)); 76 | 77 | return "index"; 78 | } 79 | 80 | @GetMapping("/login") 81 | public String login(String redirect, Model model) { 82 | if (!CharSequenceUtil.isEmpty(redirect)) { 83 | model.addAttribute("redirect", redirect); 84 | } 85 | return "login"; 86 | } 87 | 88 | @PostMapping("/dologin") 89 | public String dologin(String username, String password, String redirect, HttpSession session, Model model, HttpServletRequest request) { 90 | if (CharSequenceUtil.isEmpty(username) || CharSequenceUtil.isEmpty(password)) { 91 | model.addAttribute("msg", "用户名或密码不能为空"); 92 | return "login"; 93 | } 94 | 95 | Map map = new HashMap<>(); 96 | map.put("username", username); 97 | Admin admin = adminService.getAdminByMap(map); 98 | if (admin == null) { 99 | model.addAttribute("msg", "用户名或密码错误"); 100 | return "login"; 101 | } 102 | 103 | if (!adminService.checkPassword(password, admin.getPassword())) { 104 | model.addAttribute("msg", "用户名或密码错误"); 105 | loginLogService.log(request, admin, 1); 106 | return "login"; 107 | } 108 | 109 | //记录登录日志 110 | loginLogService.log(request, admin, 0); 111 | 112 | session.setAttribute(UserConstants.USER_SESSION, admin); 113 | model.addAttribute(UserConstants.USER_SESSION, admin); 114 | //设置session不过期 115 | session.setMaxInactiveInterval(-1); 116 | 117 | 118 | if (!StrUtil.isEmpty(redirect)) { 119 | return "redirect:" + redirect; 120 | } 121 | return "redirect:/"; 122 | } 123 | 124 | @GetMapping("/logout") 125 | public String logout(HttpSession session) { 126 | session.removeAttribute(UserConstants.USER_SESSION); 127 | return "redirect:/login"; 128 | } 129 | 130 | @GetMapping("/customers") 131 | public String customer() { 132 | return "customers"; 133 | } 134 | 135 | @GetMapping("/pages") 136 | public String page() { 137 | return "pages"; 138 | } 139 | 140 | @GetMapping("/tokens") 141 | public String nmqs() { 142 | return "nmqs"; 143 | } 144 | 145 | @GetMapping("/admins") 146 | public String admin() { 147 | return "admin"; 148 | } 149 | 150 | @GetMapping("/loginlogs") 151 | public String loginLog() { 152 | return "loginlog"; 153 | } 154 | 155 | @GetMapping("/custommqtt") 156 | public String customMqtt() { 157 | return "custom_mqtt"; 158 | } 159 | 160 | @GetMapping("/viewer") 161 | public String viewer() { 162 | return "viewer_pages"; 163 | } 164 | 165 | @GetMapping("/common") 166 | public String common() { 167 | return "common"; 168 | } 169 | 170 | @GetMapping("/about") 171 | public String about() { 172 | return "about"; 173 | } 174 | 175 | @GetMapping("/tools") 176 | public String tools() { 177 | return "tools"; 178 | } 179 | 180 | @GetMapping("/mq/{url}") 181 | public ModelAndView mqttPage(@PathVariable("url") String url, HttpServletRequest request) { 182 | ModelAndView mav = new ModelAndView(); 183 | if (CharSequenceUtil.isEmpty(url)){ 184 | mav.setViewName("error/404"); 185 | return mav; 186 | } 187 | MqPageDto mqPageDto = mqPageService.selectByURL(url); 188 | if (mqPageDto == null){ 189 | mav.setViewName("error/404"); 190 | return mav; 191 | } 192 | 193 | NmqsToken nmqsToken = nmqsMapper.selectById(mqPageDto.getNmqsTokenId()); 194 | if (nmqsToken == null){ 195 | mav.setViewName("error/404"); 196 | return mav; 197 | } 198 | 199 | mav.addObject("pageId", mqPageDto.getId()); 200 | mav.addObject("pageName", mqPageDto.getPageName()); 201 | mav.addObject("params", mqPageDto.getMqParams()); 202 | mav.addObject("token", nmqsToken.getToken()); 203 | 204 | mav.addObject("sendTopic", mqPageDto.getSendTopic()); 205 | mav.addObject("receiveTopic", mqPageDto.getReceiveTopic()); 206 | mav.addObject("qos", mqPageDto.getQos()); 207 | 208 | mav.addObject("serverInfo", (nmqsToken.getProtocol()==0?"ws://":"mqtt://")+nmqsToken.getMqttServer()+":"+nmqsToken.getMqttPort()); 209 | 210 | if (StrUtil.isEmpty(mqPageDto.getPageFileName())){ 211 | mav.setViewName("mqtt"); 212 | } else { 213 | mav.setViewName(mqPageDto.getPageFileName()); 214 | } 215 | 216 | //如果用户已登录,将已登录传递到前端 217 | Object attribute = request.getSession().getAttribute(UserConstants.USER_SESSION); 218 | if (attribute != null){ 219 | mav.addObject("login", "true"); 220 | } 221 | 222 | 223 | return mav; 224 | 225 | } 226 | 227 | @GetMapping("/statics/system/common.js") 228 | public ResponseEntity commonJs() { 229 | String host = environment.getProperty("wemq.nmqs.host"); 230 | String port = environment.getProperty("wemq.nmqs.port"); 231 | 232 | String url = host + ":" + port; 233 | 234 | String js = "const url = \"" + url + "\";\n" + 235 | "function getNmqsAPI() {\n" + 236 | " if (window.location.protocol === 'https:') {\n" + 237 | " return `https://${url}`;\n" + 238 | " }\n" + 239 | " return `http://${url}`;\n" + 240 | "}\n" + 241 | "\n" + 242 | "function getNmqsWebsocket() {\n" + 243 | " if (window.location.protocol === 'https:') {\n" + 244 | " return `wss://${url}`;\n" + 245 | " }\n" + 246 | " return `ws://${url}`;\n" + 247 | "}"; 248 | return ResponseEntity.ok(js); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/mapper/AdminMapper.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.mapper; 2 | 3 | 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.dromara.wemq.model.pojo.Admin; 7 | 8 | /** 9 | * @author RR 10 | * @since 2023/4/8 15:49 11 | */ 12 | @Mapper 13 | public interface AdminMapper extends BaseMapper { 14 | } 15 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/mapper/CustomerMapper.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.dromara.wemq.model.pojo.Customer; 6 | 7 | @Mapper 8 | public interface CustomerMapper extends BaseMapper { 9 | } 10 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/mapper/LoginLogMapper.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.dromara.wemq.model.pojo.LoginLog; 6 | 7 | /** 8 | * 登录日志Mapper 9 | * @author NicholasLD 10 | * @createTime 2023/4/8 15:16 11 | */ 12 | @Mapper 13 | public interface LoginLogMapper extends BaseMapper { 14 | } 15 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/mapper/MqPageMapper.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.dromara.wemq.model.dto.MqPageDto; 6 | import org.dromara.wemq.model.pojo.MQPage; 7 | 8 | /** 9 | * 调试页面Mapper 10 | * @author NicholasLD 11 | * @createTime 2023/4/13 08:22 12 | */ 13 | @Mapper 14 | public interface MqPageMapper extends BaseMapper { 15 | 16 | /** 17 | * 根据id删除调试页面 18 | * @param id 调试页面id 19 | * @return 删除结果 20 | */ 21 | int deleteById(Long id); 22 | 23 | int count(int type); 24 | 25 | MqPageDto selectByURL(String url); 26 | } 27 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/mapper/MqParamMapper.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.dromara.wemq.model.pojo.MQParam; 6 | 7 | /** 8 | * 调试参数Mapper 9 | * @author 梁晓惠、袁祎阳、纪雨佳 10 | * @createTime 2023/4/8 15:16 11 | */ 12 | @Mapper 13 | public interface MqParamMapper extends BaseMapper { 14 | /** 15 | * 添加 16 | * @param mqParam 参数实体类 17 | * @return 返回值 18 | */ 19 | int add(MQParam mqParam); 20 | 21 | /** 22 | * 通过id修改 23 | * @param mqParam 参数实体类 24 | * @return 返回值 25 | */ 26 | int update(MQParam mqParam); 27 | 28 | /** 29 | * 查询 30 | * @param id 参数id 31 | * @return 返回值 32 | */ 33 | MQParam select (Long id); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/mapper/NmqsMapper.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.dromara.wemq.model.pojo.NmqsToken; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * NmqsMapper 12 | * @author NicholasLD 13 | * @createTime 2023/4/13 14:18 14 | */ 15 | @Mapper 16 | public interface NmqsMapper extends BaseMapper { 17 | /** 18 | * 查询所有的Nmqs 19 | * @return 查询结果 20 | */ 21 | List select(Map params); 22 | 23 | /** 24 | * 根据id查询NmqsToken详细信息 25 | * @param id NmqsToken id 26 | * @return 查询结果 27 | */ 28 | NmqsToken selectById(Long id); 29 | 30 | /** 31 | * 更新NmqsToken 32 | * @param nmqsToken NmqsToken对象 33 | * @return 更新结果 34 | */ 35 | int update(NmqsToken nmqsToken); 36 | 37 | /** 38 | * 根据id删除NmqsToken 39 | * @param id NmqsToken id 40 | * @return 删除结果 41 | */ 42 | int deleteById(Long id); 43 | 44 | NmqsToken getInfoByToken(String token); 45 | } 46 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/model/dto/LoginLogDto.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.model.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | import java.sql.Timestamp; 10 | 11 | /** 12 | * @author NicholasLD 13 | * @createTime 2023/4/8 15:24 14 | */ 15 | @Data 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | public class LoginLogDto implements Serializable { 19 | private static final long serialVersionUID = 1L; 20 | 21 | private Long id; 22 | 23 | /** 管理员ID */ 24 | private Long adminId; 25 | 26 | /** 管理员IP */ 27 | private String adminIP; 28 | 29 | /** 管理员操作系统 */ 30 | private String adminOS; 31 | 32 | /** 登录时间 */ 33 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 34 | private Timestamp loginTime; 35 | 36 | /** 登录状态 */ 37 | private int loginStatus; 38 | 39 | private String username; 40 | } 41 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/model/dto/MqPageDto.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.model.dto; 2 | 3 | import lombok.Data; 4 | import org.dromara.wemq.model.pojo.Customer; 5 | import org.dromara.wemq.model.pojo.MQParam; 6 | import org.dromara.wemq.model.pojo.NmqsToken; 7 | 8 | import java.io.Serializable; 9 | import java.util.List; 10 | 11 | /** 12 | * 页面实体类 13 | * @author NicholasLD 14 | * @createTime 2023/4/8 13:20 15 | */ 16 | @Data 17 | public class MqPageDto implements Serializable { 18 | private static final long serialVersionUID = 1L; 19 | 20 | private Long id; 21 | 22 | /** 页面名称 */ 23 | private String pageName; 24 | 25 | /** 页面URL */ 26 | private String pageUrl; 27 | 28 | /** 页面文件名 */ 29 | private String pageFileName; 30 | 31 | /** Nmqs Token ID */ 32 | private Long nmqsTokenId; 33 | 34 | /** 服务器设置 */ 35 | private NmqsToken settings; 36 | 37 | /** 发布订阅 */ 38 | private String sendTopic; 39 | 40 | /** 接收订阅 */ 41 | private String receiveTopic; 42 | 43 | /** QoS */ 44 | private Integer qos; 45 | 46 | /** 客户 */ 47 | private Customer customer; 48 | 49 | /** 页面状态 */ 50 | private Integer status; 51 | 52 | /** 页面参数 */ 53 | private List mqParams; 54 | } 55 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/model/pojo/Admin.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.model.pojo; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | /** 11 | * 管理员实体类 12 | * @author NicholasLD 13 | * @createTime 2023/4/8 13:16 14 | */ 15 | @Data 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | @TableName("sys_admin") 19 | public class Admin { 20 | /** 管理员ID */ 21 | @TableId(type = IdType.AUTO) 22 | private Long id; 23 | 24 | /** 管理员用户名 */ 25 | private String username; 26 | 27 | /** 管理员昵称 */ 28 | private String nickname; 29 | 30 | /** 管理员密码 */ 31 | private String password; 32 | 33 | /** 管理员状态 */ 34 | private int status; 35 | } 36 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/model/pojo/Customer.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.model.pojo; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import lombok.Data; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * 客户实体类 13 | * @author NicholasLD 14 | * @createTime 2023/4/8 13:28 15 | */ 16 | @Data 17 | @TableName("mq_customer") 18 | public class Customer { 19 | /** 客户ID */ 20 | @TableId(type = IdType.AUTO) 21 | private Long id; 22 | 23 | /** 客户名称 */ 24 | private String name; 25 | 26 | /** 客户的页面 */ 27 | @TableField(exist=false) 28 | private transient List mqPages; 29 | } 30 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/model/pojo/LoginLog.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.model.pojo; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import com.fasterxml.jackson.annotation.JsonFormat; 8 | import lombok.Data; 9 | 10 | import java.sql.Timestamp; 11 | 12 | /** 13 | * 登录日志实体类 14 | * @author NicholasLD 15 | * @createTime 2023/4/8 13:18 16 | */ 17 | @Data 18 | @TableName("sys_login_log") 19 | public class LoginLog { 20 | /** 日志ID */ 21 | @TableId(type = IdType.AUTO) 22 | private Long id; 23 | 24 | /** 管理员ID */ 25 | private Long adminId; 26 | 27 | /** 管理员IP */ 28 | @TableField(value = "admin_ip") 29 | private String adminIP; 30 | 31 | /** 管理员操作系统 */ 32 | @TableField(value = "admin_os") 33 | private String adminOS; 34 | 35 | /** 登录时间 */ 36 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 37 | private Timestamp loginTime; 38 | 39 | /** 登录状态 */ 40 | private int loginStatus; 41 | } 42 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/model/pojo/MQPage.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.model.pojo; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import lombok.Data; 8 | 9 | /** 10 | * 页面实体类 11 | * @author NicholasLD 12 | * @createTime 2023/4/8 13:20 13 | */ 14 | @Data 15 | @TableName("mq_page") 16 | public class MQPage { 17 | /** 页面ID */ 18 | @TableId(type = IdType.AUTO) 19 | private Long id; 20 | 21 | /** 页面名称 */ 22 | private String pageName; 23 | 24 | /** 页面URL */ 25 | private String pageUrl; 26 | 27 | /** 页面文件名 */ 28 | @TableField(value = "page_filename") 29 | private String pageFileName; 30 | 31 | /** Nmqs Token ID */ 32 | @TableField(value = "nmqs_id") 33 | private Long nmqsTokenId; 34 | 35 | /** 发布订阅 */ 36 | @TableField(value = "mqtt_sendtopic") 37 | private String sendTopic; 38 | 39 | /** 接收订阅 */ 40 | @TableField(value = "mqtt_receivetopic") 41 | private String receiveTopic; 42 | 43 | /** QoS */ 44 | @TableField(value = "mqtt_qos") 45 | private int qos; 46 | 47 | /** 客户 */ 48 | private Long customerId; 49 | 50 | /** 页面状态 */ 51 | private int status; 52 | } 53 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/model/pojo/MQParam.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.model.pojo; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.Data; 7 | 8 | /** 9 | * 页面参数实体类 10 | * @author NicholasLD 11 | * @createTime 2023/4/8 13:25 12 | */ 13 | @Data 14 | @TableName("mq_param") 15 | public class MQParam { 16 | /** 参数ID */ 17 | @TableId(type = IdType.AUTO) 18 | private Long id; 19 | 20 | /** 页面ID */ 21 | private Long pageId; 22 | 23 | /** 调试消息 */ 24 | private String message; 25 | 26 | /** 按钮消息 */ 27 | private String button; 28 | 29 | /** 排序 */ 30 | private Integer sort; 31 | } 32 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/model/pojo/NmqsToken.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.model.pojo; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 7 | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 8 | import lombok.Data; 9 | 10 | /** 11 | * NmqsToken实体类 12 | * @author NicholasLD 13 | * @createTime 2023/4/8 13:29 14 | */ 15 | @Data 16 | @TableName("mq_nmqs_token") 17 | public class NmqsToken { 18 | /** TokenID */ 19 | @TableId(type = IdType.ASSIGN_ID) 20 | @JsonSerialize(using= ToStringSerializer.class) 21 | private Long id; 22 | 23 | /** Token名称 */ 24 | private String name; 25 | 26 | /** Token */ 27 | private String token; 28 | 29 | /** 协议 **/ 30 | private int protocol; 31 | 32 | /** 服务器地址 */ 33 | private String mqttServer; 34 | 35 | /** 服务器端口 */ 36 | private int mqttPort; 37 | 38 | /** 服务器用户名 */ 39 | private String mqttUsername; 40 | 41 | /** 服务器密码 */ 42 | private String mqttPassword; 43 | } 44 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/model/vo/MQPageVo.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.model.vo; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import lombok.Data; 7 | 8 | /** 9 | * 页面实体类 10 | * @author NicholasLD 11 | * @createTime 2023/4/8 13:20 12 | */ 13 | @Data 14 | public class MQPageVo { 15 | /** 页面ID */ 16 | @TableId(type = IdType.AUTO) 17 | private Long id; 18 | 19 | /** 页面名称 */ 20 | private String pageName; 21 | 22 | /** 页面URL */ 23 | private String pageUrl; 24 | 25 | /** 页面文件名 */ 26 | @TableField(value = "page_filename") 27 | private String pageFileName; 28 | 29 | /** Nmqs Token ID */ 30 | @TableField(value = "nmqs_id") 31 | private Long nmqsTokenId; 32 | 33 | /** 发布订阅 */ 34 | @TableField(value = "mqtt_sendtopic") 35 | private String sendTopic; 36 | 37 | /** 接收订阅 */ 38 | @TableField(value = "mqtt_receivetopic") 39 | private String receiveTopic; 40 | 41 | /** QoS */ 42 | @TableField(value = "mqtt_qos") 43 | private int qos; 44 | 45 | /** 客户 */ 46 | private Long customerId; 47 | 48 | /** 页面状态 */ 49 | private int status; 50 | 51 | /** 服务器信息 */ 52 | private Integer protocol; 53 | private String mqttServer; 54 | private Integer mqttPort; 55 | private String mqttUsername; 56 | private String mqttPassword; 57 | } 58 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/service/AdminService.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import org.dromara.wemq.model.pojo.Admin; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * @author NicholasLD 11 | * @createTime 2023/4/27 19:21 12 | */ 13 | public interface AdminService extends IService { 14 | /** 15 | * 分页查询 16 | * @param params 参数条件 17 | * @return 返回查询到的结果 18 | */ 19 | List getAdminListByMap(Map params); 20 | 21 | /** 22 | * 根据条件获取数据信息的数量 23 | * @param params 条件集合 24 | * @return 返回查询到数据的数量 25 | */ 26 | Integer getAdminCountByMap(Map params); 27 | 28 | /** 29 | * 增加管理员 30 | * @param admin 管理员对象 31 | * @return 返回受影响的行数 32 | */ 33 | int insertAdmin(Admin admin); 34 | 35 | /** 36 | * 根据id修改管理员信息 37 | * @param admin 要修改的管理员数据 38 | * @return 返回受影响的行数 39 | */ 40 | int updateAdminById(Admin admin); 41 | 42 | /** 43 | * 根据id删除对应的管理员信息 44 | * @param id 条件id 45 | * @return 返回受影响的行数 46 | */ 47 | int deleteAdminById(Long id); 48 | 49 | /** 50 | * 根据条件查询管理员信息 51 | * @param params 条件集合 52 | * @return 返回查询到的管理员信息 53 | */ 54 | Admin getAdminByMap(Map params); 55 | 56 | /** 57 | * 校验密码 58 | */ 59 | boolean checkPassword(String password, String hashPassword); 60 | } 61 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/service/CustomerService.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import org.apache.ibatis.annotations.Param; 5 | import org.dromara.wemq.model.pojo.Customer; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author NicholasLD 11 | * @createTime 2023/4/17 14:22 12 | */ 13 | public interface CustomerService extends IService { 14 | /** 15 | * Add a Customer 16 | * 17 | * @param customer 18 | * @return customer 19 | */ 20 | Integer insertClient( Customer customer ); 21 | 22 | /** 23 | * Delete a customer 24 | * 25 | * @param id 26 | * @return id 27 | */ 28 | Integer deleteClientById( @Param(value = "id") Long id ); 29 | 30 | /** 31 | * Modify customer 32 | * 33 | * @param customer 34 | * @return customer 35 | */ 36 | Integer updateClient( Customer customer ); 37 | 38 | /** 39 | * Delete a customer 40 | * @return 41 | */ 42 | List getCustomers(); 43 | Integer getCustomerCount(); 44 | } 45 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/service/LoginLogService.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import org.dromara.wemq.model.dto.LoginLogDto; 6 | import org.dromara.wemq.model.pojo.Admin; 7 | import org.dromara.wemq.model.pojo.LoginLog; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author NicholasLD 13 | * @createTime 2023/4/30 00:16 14 | */ 15 | public interface LoginLogService extends IService { 16 | /** 17 | * 记录登录日志 18 | * @param request 请求 19 | * @param admin 用户 20 | * @param status 状态 21 | * @return int 22 | */ 23 | int log(HttpServletRequest request, Admin admin, int status); 24 | 25 | List getLoginLogList(String time); 26 | } 27 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/service/MqPageService.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import org.dromara.wemq.model.dto.MqPageDto; 5 | import org.dromara.wemq.model.pojo.MQPage; 6 | import org.dromara.wemq.model.vo.MQPageVo; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * @author NicholasLD 13 | * @createTime 2023/4/13 08:22 14 | */ 15 | public interface MqPageService extends IService { 16 | /** 17 | * 根据条件查询所有 18 | * @param params 查询条件 19 | * @return 查询结果 20 | */ 21 | List select(Map params); 22 | 23 | /** 24 | * 根据id查询调试页面详细信息 25 | * @param id 调试页面id 26 | * @return 查询结果 27 | */ 28 | MqPageDto selectById(Long id); 29 | 30 | /** 31 | * 插入调试页面 32 | * @param mqPage 调试页面对象 33 | * @return 插入结果 34 | */ 35 | int insert(MQPageVo mqPage); 36 | 37 | /** 38 | * 更新调试页面 39 | * @param mqPage 调试页面对象 40 | * @return 更新结果 41 | */ 42 | int update(MQPageVo mqPage); 43 | 44 | /** 45 | * 根据id删除调试页面 46 | * @param id 调试页面id 47 | * @return 删除结果 48 | */ 49 | int deleteById(Long id); 50 | 51 | MqPageDto selectByURL(String url); 52 | 53 | /** 54 | * 更新topic 55 | * @param mqPage 56 | * @return 57 | */ 58 | int updateTopic(MQPageVo mqPage); 59 | } 60 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/service/MqParamService.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import org.dromara.wemq.model.pojo.MQParam; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author NicholasLD 10 | * @createTime 2023/4/20 14:23 11 | */ 12 | public interface MqParamService extends IService { 13 | MQParam selectById(Long id); 14 | 15 | int insert(MQParam mqParam, Long pageId); 16 | 17 | int delete(Long id); 18 | 19 | int update(MQParam param); 20 | 21 | List selectByPageId(Long id); 22 | } 23 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/service/NmqsService.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import org.dromara.wemq.model.pojo.NmqsToken; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * NMQS服务接口 11 | * @author NicholasLD 12 | * @createTime 2023/4/13 14:35 13 | */ 14 | public interface NmqsService extends IService { 15 | /** 16 | * 查询所有的Nmqs 17 | * @return 查询结果 18 | */ 19 | List select(Map params); 20 | 21 | /** 22 | * 根据id查询NmqsToken详细信息 23 | * @param id NmqsToken id 24 | * @return 查询结果 25 | */ 26 | NmqsToken selectById(Long id); 27 | 28 | /** 29 | * 插入NmqsToken 30 | * @param nmqsToken NmqsToken对象 31 | * @return 插入结果 32 | */ 33 | int insert(NmqsToken nmqsToken); 34 | 35 | /** 36 | * 更新NmqsToken 37 | * @param nmqsToken NmqsToken对象 38 | * @return 更新结果 39 | */ 40 | int update(NmqsToken nmqsToken); 41 | 42 | /** 43 | * 根据id删除NmqsToken 44 | * @param id NmqsToken id 45 | * @return 删除结果 46 | */ 47 | int deleteById(Long id); 48 | } 49 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/service/impl/AdminServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.service.impl; 2 | 3 | import cn.hutool.crypto.digest.BCrypt; 4 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | import lombok.RequiredArgsConstructor; 7 | import org.dromara.wemq.mapper.AdminMapper; 8 | import org.dromara.wemq.model.pojo.Admin; 9 | import org.dromara.wemq.service.AdminService; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * @author NicholasLD 18 | * @createTime 2023/4/27 19:21 19 | */ 20 | @Service 21 | @RequiredArgsConstructor 22 | public class AdminServiceImpl extends ServiceImpl implements AdminService { 23 | private final AdminMapper adminMapper; 24 | 25 | @Override 26 | public List getAdminListByMap(Map params) { 27 | QueryWrapper queryWrapper = new QueryWrapper<>(); 28 | 29 | if (params == null) { 30 | params = new HashMap<>(); 31 | } 32 | 33 | for (Map.Entry entry : params.entrySet()) { 34 | queryWrapper.eq(entry.getKey(), entry.getValue()); 35 | } 36 | return adminMapper.selectList(queryWrapper); 37 | } 38 | 39 | @Override 40 | public Integer getAdminCountByMap(Map params) { 41 | QueryWrapper queryWrapper = new QueryWrapper<>(); 42 | 43 | if (params == null) { 44 | params = new HashMap<>(); 45 | } 46 | 47 | for (Map.Entry entry : params.entrySet()) { 48 | queryWrapper.eq(entry.getKey(), entry.getValue()); 49 | } 50 | return Math.toIntExact(adminMapper.selectCount(queryWrapper)); 51 | } 52 | 53 | @Override 54 | public int insertAdmin(Admin admin) { 55 | String password = admin.getPassword(); 56 | String hashPassword = BCrypt.hashpw(password, BCrypt.gensalt()); 57 | admin.setPassword(hashPassword); 58 | return adminMapper.insert(admin); 59 | } 60 | 61 | @Override 62 | public int updateAdminById(Admin admin) { 63 | String password = admin.getPassword(); 64 | String hashPassword = BCrypt.hashpw(password, BCrypt.gensalt()); 65 | admin.setPassword(hashPassword); 66 | return adminMapper.updateById(admin); 67 | } 68 | 69 | @Override 70 | public int deleteAdminById(Long id) { 71 | return adminMapper.deleteById(id); 72 | } 73 | 74 | @Override 75 | public Admin getAdminByMap(Map params) { 76 | if (params == null) { 77 | params = new HashMap<>(); 78 | } 79 | 80 | return adminMapper.selectOne(new QueryWrapper().allEq(params)); 81 | } 82 | 83 | @Override 84 | public boolean checkPassword(String password, String hashPassword) { 85 | return BCrypt.checkpw(password, hashPassword); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/service/impl/CustomerServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import lombok.RequiredArgsConstructor; 5 | import org.dromara.wemq.mapper.CustomerMapper; 6 | import org.dromara.wemq.mapper.MqPageMapper; 7 | import org.dromara.wemq.model.pojo.Customer; 8 | import org.dromara.wemq.service.CustomerService; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * @author NicholasLD 15 | * @createTime 2023/4/17 14:23 16 | */ 17 | @Service 18 | @RequiredArgsConstructor 19 | public class CustomerServiceImpl extends ServiceImpl implements CustomerService { 20 | private final CustomerMapper customerMapper; 21 | private final MqPageMapper mqPageMapper; 22 | 23 | @Override 24 | public Integer insertClient(Customer customer) { 25 | return customerMapper.insert(customer); 26 | } 27 | 28 | @Override 29 | public Integer deleteClientById(Long id) { 30 | return customerMapper.deleteById(id); 31 | } 32 | 33 | @Override 34 | public Integer updateClient(Customer customer) { 35 | return customerMapper.updateById(customer); 36 | } 37 | 38 | @Override 39 | public List getCustomers() { 40 | return customerMapper.selectList(null); 41 | } 42 | 43 | @Override 44 | public Integer getCustomerCount() { 45 | return Math.toIntExact(customerMapper.selectCount(null)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/service/impl/LoginLogServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.service.impl; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import cn.hutool.http.useragent.UserAgent; 5 | import cn.hutool.http.useragent.UserAgentUtil; 6 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 7 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 8 | import jakarta.servlet.http.HttpServletRequest; 9 | import lombok.RequiredArgsConstructor; 10 | import org.dromara.wemq.mapper.LoginLogMapper; 11 | import org.dromara.wemq.framework.utils.IpUtils; 12 | import org.dromara.wemq.model.dto.LoginLogDto; 13 | import org.dromara.wemq.model.pojo.Admin; 14 | import org.dromara.wemq.model.pojo.LoginLog; 15 | import org.dromara.wemq.service.LoginLogService; 16 | import org.springframework.stereotype.Service; 17 | 18 | import java.sql.Timestamp; 19 | import java.util.ArrayList; 20 | import java.util.Date; 21 | import java.util.List; 22 | 23 | /** 24 | * @author NicholasLD 25 | * @createTime 2023/4/30 00:20 26 | */ 27 | @Service 28 | @RequiredArgsConstructor 29 | public class LoginLogServiceImpl extends ServiceImpl implements LoginLogService { 30 | private final LoginLogMapper loginLogMapper; 31 | private final AdminServiceImpl adminService; 32 | 33 | @Override 34 | public int log(HttpServletRequest request, Admin admin, int status) { 35 | //获取User-Agent 36 | String userAgent = request.getHeader("User-Agent"); 37 | //解析User-Agent 38 | UserAgent ua = UserAgentUtil.parse(userAgent); 39 | //获取登录时间 40 | Date loginTime = new Date(); 41 | 42 | //通过User-Agent读取操作系统 43 | String os = "Unknown"; 44 | if (ua != null) { 45 | os = ua.getOs().toString(); 46 | } 47 | 48 | LoginLog loginLog = new LoginLog(); 49 | loginLog.setAdminId(admin.getId()); 50 | loginLog.setAdminIP(IpUtils.getIpAddr()); 51 | loginLog.setAdminOS(os); 52 | loginLog.setLoginTime(new Timestamp(loginTime.getTime())); 53 | loginLog.setLoginStatus(status); 54 | 55 | return loginLogMapper.insert(loginLog); 56 | } 57 | 58 | @Override 59 | public List getLoginLogList(String time) { 60 | QueryWrapper queryWrapper = new QueryWrapper<>(); 61 | if (time != null) { 62 | queryWrapper.like("login_time", time); 63 | } 64 | List loginLogs = loginLogMapper.selectList(queryWrapper); 65 | 66 | List loginLogDtos = new ArrayList<>(); 67 | for (LoginLog loginLog : loginLogs) { 68 | LoginLogDto loginLogDto = new LoginLogDto(); 69 | BeanUtil.copyProperties(loginLog, loginLogDto); 70 | 71 | //通过adminId获取管理员信息 72 | Admin admin = adminService.getById(loginLog.getAdminId()); 73 | loginLogDto.setUsername(admin.getUsername()); 74 | 75 | loginLogDtos.add(loginLogDto); 76 | } 77 | 78 | return loginLogDtos; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/service/impl/MqPageServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.service.impl; 2 | 3 | import cn.hutool.core.util.RandomUtil; 4 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | import lombok.RequiredArgsConstructor; 7 | import org.dromara.wemq.mapper.CustomerMapper; 8 | import org.dromara.wemq.mapper.MqPageMapper; 9 | import org.dromara.wemq.mapper.NmqsMapper; 10 | import org.dromara.wemq.model.dto.MqPageDto; 11 | import org.dromara.wemq.model.pojo.MQPage; 12 | import org.dromara.wemq.model.pojo.NmqsToken; 13 | import org.dromara.wemq.model.vo.MQPageVo; 14 | import org.dromara.wemq.service.MqPageService; 15 | import org.dromara.wemq.service.MqParamService; 16 | import org.springframework.beans.BeanUtils; 17 | import org.springframework.stereotype.Service; 18 | import org.springframework.transaction.annotation.Transactional; 19 | 20 | import java.util.ArrayList; 21 | import java.util.Collections; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | /** 26 | * @author NicholasLD 27 | * @createTime 2023/4/13 09:09 28 | */ 29 | @Service 30 | @RequiredArgsConstructor 31 | public class MqPageServiceImpl extends ServiceImpl implements MqPageService { 32 | private final MqPageMapper mqPageMapper; 33 | private final MqParamService mqParamService; 34 | private final CustomerMapper customerMapper; 35 | private final NmqsMapper nmqsMapper; 36 | 37 | @Override 38 | public List select(Map params) { 39 | QueryWrapper queryWrapper = new QueryWrapper<>(); 40 | queryWrapper.orderByDesc("id"); 41 | 42 | if (params != null){ 43 | if (params.get("id") != null){ 44 | queryWrapper.eq("id", params.get("id")); 45 | } 46 | if (params.get("pageName") != null){ 47 | queryWrapper.like("pageName", params.get("pageName")); 48 | } 49 | if (params.get("customerID") != null){ 50 | queryWrapper.eq("customer_id", params.get("customerID")); 51 | } 52 | if (params.get("commonPage") != null){ 53 | // 判断page_filename是否有值,如果为1,筛选出page_filename不为空和不为null的数据 54 | if (params.get("commonPage").equals(1)){ 55 | queryWrapper.ne("page_filename", ""); 56 | } else { 57 | queryWrapper.eq("page_filename", ""); 58 | } 59 | } 60 | } 61 | 62 | List mqPages = mqPageMapper.selectList(queryWrapper); 63 | if (mqPages == null){ 64 | return Collections.emptyList(); 65 | } 66 | 67 | List mqPageDtos = new ArrayList<>(); 68 | for (MQPage mqPage : mqPages){ 69 | MqPageDto mqPageDto = new MqPageDto(); 70 | BeanUtils.copyProperties(mqPage, mqPageDto); 71 | 72 | mqPageDto.setCustomer(customerMapper.selectById(mqPage.getCustomerId())); 73 | mqPageDto.setMqParams(mqParamService.selectByPageId(mqPage.getId())); 74 | mqPageDto.setSettings(nmqsMapper.selectById(mqPage.getNmqsTokenId())); 75 | mqPageDtos.add(mqPageDto); 76 | } 77 | 78 | return mqPageDtos; 79 | } 80 | 81 | @Override 82 | public MqPageDto selectById(Long id) { 83 | QueryWrapper queryWrapper = new QueryWrapper<>(); 84 | queryWrapper.eq("id", id); 85 | MQPage mqPage = mqPageMapper.selectOne(queryWrapper); 86 | if (mqPage == null){ 87 | return null; 88 | } 89 | 90 | MqPageDto mqPageDto = new MqPageDto(); 91 | BeanUtils.copyProperties(mqPage, mqPageDto); 92 | 93 | mqPageDto.setCustomer(customerMapper.selectById(mqPage.getCustomerId())); 94 | mqPageDto.setMqParams(mqParamService.selectByPageId(id)); 95 | mqPageDto.setSettings(nmqsMapper.selectById(mqPage.getNmqsTokenId())); 96 | 97 | return mqPageDto; 98 | } 99 | 100 | @Override 101 | @Transactional(rollbackFor = Exception.class) 102 | public int insert(MQPageVo mqPage) { 103 | MQPage mq = new MQPage(); 104 | BeanUtils.copyProperties(mqPage, mq); 105 | 106 | mq.setNmqsTokenId(mqPage.getNmqsTokenId()); 107 | 108 | // 如果为自定义 109 | if (mqPage.getNmqsTokenId() != 0) { 110 | return mqPageMapper.insert(mq); 111 | } 112 | 113 | NmqsToken nmqsToken = new NmqsToken(); 114 | nmqsToken.setName(mqPage.getPageName() + "_" + RandomUtil.randomString(6)); 115 | nmqsToken.setToken(RandomUtil.randomString(12)); 116 | nmqsToken.setProtocol(mqPage.getProtocol()); 117 | nmqsToken.setMqttServer(mqPage.getMqttServer()); 118 | nmqsToken.setMqttPort(mqPage.getMqttPort()); 119 | nmqsToken.setMqttUsername(mqPage.getMqttUsername()); 120 | nmqsToken.setMqttPassword(mqPage.getMqttPassword()); 121 | 122 | int insert = nmqsMapper.insert(nmqsToken); 123 | 124 | if (insert > 0){ 125 | mq.setNmqsTokenId(nmqsToken.getId()); 126 | return mqPageMapper.insert(mq); 127 | } 128 | return 0; 129 | } 130 | 131 | @Override 132 | @Transactional(rollbackFor = Exception.class) 133 | public int update(MQPageVo mqPage) { 134 | MQPage mq = new MQPage(); 135 | BeanUtils.copyProperties(mqPage, mq); 136 | 137 | // 如果为自定义 138 | if (mqPage.getNmqsTokenId() != 0) { 139 | return mqPageMapper.updateById(mq); 140 | } 141 | 142 | NmqsToken nmqsToken = new NmqsToken(); 143 | nmqsToken.setName(mqPage.getPageName() + "_" + RandomUtil.randomString(6)); 144 | nmqsToken.setToken(RandomUtil.randomString(12)); 145 | nmqsToken.setProtocol(mqPage.getProtocol()); 146 | nmqsToken.setMqttServer(mqPage.getMqttServer()); 147 | nmqsToken.setMqttPort(mqPage.getMqttPort()); 148 | nmqsToken.setMqttUsername(mqPage.getMqttUsername()); 149 | nmqsToken.setMqttPassword(mqPage.getMqttPassword()); 150 | 151 | int insert = nmqsMapper.insert(nmqsToken); 152 | 153 | if (insert > 0){ 154 | mq.setNmqsTokenId(nmqsToken.getId()); 155 | return mqPageMapper.updateById(mq); 156 | } 157 | return 0; 158 | } 159 | 160 | @Override 161 | @Transactional(rollbackFor = Exception.class) 162 | public int deleteById(Long id) { 163 | return mqPageMapper.deleteById(id); 164 | } 165 | 166 | @Override 167 | public MqPageDto selectByURL(String url) { 168 | QueryWrapper queryWrapper = new QueryWrapper<>(); 169 | queryWrapper.eq("page_url", url); 170 | MQPage mqPage = mqPageMapper.selectOne(queryWrapper); 171 | if (mqPage == null){ 172 | return null; 173 | } 174 | 175 | MqPageDto mqPageDto = new MqPageDto(); 176 | BeanUtils.copyProperties(mqPage, mqPageDto); 177 | 178 | mqPageDto.setCustomer(customerMapper.selectById(mqPage.getCustomerId())); 179 | mqPageDto.setMqParams(mqParamService.selectByPageId(mqPage.getId())); 180 | mqPageDto.setSettings(nmqsMapper.selectById(mqPage.getNmqsTokenId())); 181 | 182 | return mqPageDto; 183 | } 184 | 185 | @Override 186 | public int updateTopic(MQPageVo mqPage) { 187 | //只更新topic 188 | MQPage mq = new MQPage(); 189 | mq.setId(mqPage.getId()); 190 | 191 | //如果没有,则不动 192 | if (mqPage.getSendTopic() != null){ 193 | mq.setSendTopic(mqPage.getSendTopic()); 194 | } 195 | 196 | if (mqPage.getReceiveTopic() != null){ 197 | mq.setReceiveTopic(mqPage.getReceiveTopic()); 198 | } 199 | 200 | //更新 201 | return mqPageMapper.updateById(mq); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/service/impl/MqParamServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 5 | import lombok.RequiredArgsConstructor; 6 | import org.dromara.wemq.mapper.MqParamMapper; 7 | import org.dromara.wemq.model.pojo.MQParam; 8 | import org.dromara.wemq.service.MqParamService; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * @author NicholasLD 16 | * @createTime 2023/4/20 14:25 17 | */ 18 | @Service 19 | @RequiredArgsConstructor 20 | public class MqParamServiceImpl extends ServiceImpl implements MqParamService { 21 | private final MqParamMapper mqParamMapper; 22 | 23 | @Override 24 | public MQParam selectById(Long id) { 25 | return mqParamMapper.select(id); 26 | } 27 | 28 | @Override 29 | @Transactional(rollbackFor = Exception.class) 30 | public int insert(MQParam mqParam, Long pageId) { 31 | mqParam.setPageId(pageId); 32 | return mqParamMapper.insert(mqParam); 33 | } 34 | 35 | @Override 36 | @Transactional(rollbackFor = Exception.class) 37 | public int delete(Long id) { 38 | return mqParamMapper.deleteById(id); 39 | } 40 | 41 | @Override 42 | public int update(MQParam param) { 43 | return mqParamMapper.update(param); 44 | } 45 | 46 | @Override 47 | public List selectByPageId(Long id) { 48 | QueryWrapper queryWrapper = new QueryWrapper<>(); 49 | 50 | //通过sort字段进行排序 51 | queryWrapper.orderByAsc("sort"); 52 | 53 | queryWrapper.eq("page_id", id); 54 | return mqParamMapper.selectList(queryWrapper); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /mm-web/src/main/java/org/dromara/wemq/service/impl/NmqsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.dromara.wemq.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 5 | import lombok.RequiredArgsConstructor; 6 | import org.dromara.wemq.mapper.MqPageMapper; 7 | import org.dromara.wemq.mapper.NmqsMapper; 8 | import org.dromara.wemq.model.pojo.MQPage; 9 | import org.dromara.wemq.model.pojo.NmqsToken; 10 | import org.dromara.wemq.service.NmqsService; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * @author NicholasLD 18 | * @createTime 2023/4/13 14:36 19 | */ 20 | @Service 21 | @RequiredArgsConstructor 22 | public class NmqsServiceImpl extends ServiceImpl implements NmqsService { 23 | private final NmqsMapper nmqsMapper; 24 | private final MqPageMapper mqPageMapper; 25 | 26 | @Override 27 | public List select(Map params) { 28 | return nmqsMapper.select(params); 29 | } 30 | 31 | @Override 32 | public NmqsToken selectById(Long id) { 33 | return nmqsMapper.selectById(id); 34 | } 35 | 36 | @Override 37 | public int insert(NmqsToken nmqsToken) { 38 | return nmqsMapper.insert(nmqsToken); 39 | } 40 | 41 | @Override 42 | public int update(NmqsToken nmqsToken) { 43 | return nmqsMapper.update(nmqsToken); 44 | } 45 | 46 | @Override 47 | public int deleteById(Long id) { 48 | //查询id下的token是否有页面正在使用 49 | QueryWrapper queryWrapper = new QueryWrapper<>(); 50 | queryWrapper.eq("nmqs_id", id); 51 | List mqPages = mqPageMapper.selectList(queryWrapper); 52 | if (!mqPages.isEmpty()) { 53 | return -1; 54 | } 55 | return nmqsMapper.deleteById(id); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /mm-web/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | wemq: 4 | nmqs: 5 | host: localhost 6 | port: 8081 7 | spring: 8 | mvc: 9 | pathmatch: 10 | matching-strategy: ant_path_matcher 11 | datasource: 12 | driver-class-name: com.mysql.cj.jdbc.Driver 13 | url: jdbc:mysql://localhost:3306/wemq?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8 14 | username: root 15 | password: 88888888 16 | mybatis-plus: 17 | mapper-locations: classpath*:/mapper/**.xml 18 | type-aliases-package: org.dromara.wemq.model 19 | configuration: 20 | # MyBatis 配置 21 | map-underscore-to-camel-case: true 22 | pagehelper: 23 | helperDialect: mysql 24 | reasonable: true 25 | supportMethodsArguments: true 26 | params: count=countSql 27 | pageSizeZero: true 28 | logging: 29 | file: 30 | name: logs/logs.log 31 | level: 32 | root: info 33 | org.dromara.wemq: debug 34 | -------------------------------------------------------------------------------- /mm-web/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | 2 | ███╗ ██╗██╗ ██████╗██╗ ██╗ ██████╗ ██╗ █████╗ ███████╗██╗ ██████╗ 3 | ████╗ ██║██║██╔════╝██║ ██║██╔═══██╗██║ ██╔══██╗██╔════╝██║ ██╔══██╗ 4 | ██╔██╗ ██║██║██║ ███████║██║ ██║██║ ███████║███████╗██║ ██║ ██║ 5 | ██║╚██╗██║██║██║ ██╔══██║██║ ██║██║ ██╔══██║╚════██║██║ ██║ ██║ 6 | ██║ ╚████║██║╚██████╗██║ ██║╚██████╔╝███████╗██║ ██║███████║███████╗██████╔╝ 7 | ╚═╝ ╚═══╝╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚══════╝╚═════╝ 8 | 9 | -------------------------------------------------------------------------------- /mm-web/src/main/resources/mapper/MqPageMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 83 | 84 | 85 | update mq_page 86 | 87 | 88 | page_name = #{pageName}, 89 | 90 | 91 | page_url = #{pageUrl}, 92 | 93 | 94 | page_filename = #{pageFileName}, 95 | 96 | 97 | nmqs_id = #{nmqsTokenID}, 98 | 99 | 100 | mqtt_sendtopic = #{sendTopic}, 101 | 102 | 103 | mqtt_receivetopic = #{receiveTopic}, 104 | 105 | 106 | mqtt_qos = #{qos}, 107 | 108 | 109 | status = #{status}, 110 | 111 | 112 | batch_send = #{batchSend}, 113 | 114 | 115 | batch_command = #{batchCommand}, 116 | 117 | 118 | batch_delay = #{batchDelay}, 119 | 120 | 121 | where id = #{id} 122 | 123 | 124 | 125 | delete from mq_page where id = #{id} 126 | 127 | 128 | 146 | 147 | 152 | 153 | 156 | 157 | 160 | 161 | -------------------------------------------------------------------------------- /mm-web/src/main/resources/mapper/MqParamMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | INSERT INTO mq_param(message,button) 7 | values (#{message},#{button}) 8 | 9 | 10 | 11 | update mq_param 12 | 13 | 14 | message=#{message}, 15 | 16 | 17 | button=#{button}, 18 | 19 | 20 | sort=#{sort}, 21 | 22 | 23 | 24 | id=#{id} 25 | 26 | 27 | 28 | 29 | 30 | delete from mq_param 31 | 32 | id=#{id} 33 | 34 | 35 | 36 | 39 | 40 | 41 | delete from mq_page_param 42 | 43 | param_id=#{paramId} 44 | 45 | 46 | 47 | 48 | insert into mq_page_param(page_id,param_id) 49 | values (#{pageId},#{paramId}) 50 | 51 | 52 | -------------------------------------------------------------------------------- /mm-web/src/main/resources/mapper/NmqsMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 29 | 30 | 33 | 34 | 37 | 38 | 39 | insert into mq_nmqs_token (id, name, token, protocol, mqtt_server, mqtt_port, mqtt_username, mqtt_password) 40 | values (#{id}, #{name}, #{token}, #{protocol}, #{mqttServer}, #{mqttPort}, #{mqttUsername}, #{mqttPassword}) 41 | 42 | 43 | 44 | update mq_nmqs_token 45 | 46 | 47 | name = #{name}, 48 | 49 | 50 | token = #{token}, 51 | 52 | 53 | protocol = #{protocol}, 54 | 55 | 56 | mqtt_server = #{mqttServer}, 57 | 58 | 59 | mqtt_port = #{mqttPort}, 60 | 61 | 62 | mqtt_username = #{mqttUsername}, 63 | 64 | 65 | mqtt_password = #{mqttPassword}, 66 | 67 | 68 | where id = #{id} 69 | 70 | 71 | 72 | delete from mq_nmqs_token where id = #{id} 73 | 74 | 75 | -------------------------------------------------------------------------------- /mm-web/src/main/resources/static/bootstrap/table/bootstrap-table.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) 3 | * 4 | * @version v1.21.4 5 | * @homepage https://bootstrap-table.com 6 | * @author wenzhixin (http://wenzhixin.net.cn/) 7 | * @license MIT 8 | */ 9 | 10 | .bootstrap-table .fixed-table-toolbar::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-toolbar .bs-bars,.bootstrap-table .fixed-table-toolbar .columns,.bootstrap-table .fixed-table-toolbar .search{position:relative;margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group{display:inline-block;margin-left:-1px!important}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group>.btn{border-radius:0}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:first-child>.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:last-child>.btn{border-top-right-radius:4px;border-bottom-right-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .dropdown-menu{text-align:left;max-height:300px;overflow:auto;-ms-overflow-style:scrollbar;z-index:1001}.bootstrap-table .fixed-table-toolbar .columns label{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.4286}.bootstrap-table .fixed-table-toolbar .columns-left{margin-right:5px}.bootstrap-table .fixed-table-toolbar .columns-right{margin-left:5px}.bootstrap-table .fixed-table-toolbar .pull-right .dropdown-menu{right:0;left:auto}.bootstrap-table .fixed-table-container{position:relative;clear:both}.bootstrap-table .fixed-table-container .table{width:100%;margin-bottom:0!important}.bootstrap-table .fixed-table-container .table td,.bootstrap-table .fixed-table-container .table th{vertical-align:middle;box-sizing:border-box}.bootstrap-table .fixed-table-container .table thead th{vertical-align:bottom;padding:0;margin:0}.bootstrap-table .fixed-table-container .table thead th:focus{outline:0 solid transparent}.bootstrap-table .fixed-table-container .table thead th.detail{width:30px}.bootstrap-table .fixed-table-container .table thead th .th-inner{padding:.75rem;vertical-align:bottom;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bootstrap-table .fixed-table-container .table thead th .sortable{cursor:pointer;background-position:right;background-repeat:no-repeat;padding-right:30px!important}.bootstrap-table .fixed-table-container .table thead th .sortable.sortable-center{padding-left:20px!important;padding-right:20px!important}.bootstrap-table .fixed-table-container .table thead th .both{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7X QMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC")}.bootstrap-table .fixed-table-container .table thead th .asc{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg==")}.bootstrap-table .fixed-table-container .table thead th .desc{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII= ")}.bootstrap-table .fixed-table-container .table tbody tr.selected td{background-color:rgba(0,0,0,.075)}.bootstrap-table .fixed-table-container .table tbody tr.no-records-found td{text-align:center}.bootstrap-table .fixed-table-container .table tbody tr .card-view{display:flex}.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-title{font-weight:700;display:inline-block;min-width:30%;width:auto!important;text-align:left!important}.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-value{width:100%!important;text-align:left!important}.bootstrap-table .fixed-table-container .table .bs-checkbox{text-align:center}.bootstrap-table .fixed-table-container .table .bs-checkbox label{margin-bottom:0}.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=checkbox],.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=radio]{margin:0 auto!important}.bootstrap-table .fixed-table-container .table.table-sm .th-inner{padding:.3rem}.bootstrap-table .fixed-table-container.fixed-height:not(.has-footer){border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height.has-card-view{border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .fixed-table-border{border-left:1px solid #dee2e6;border-right:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table thead th{border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table-dark thead th{border-bottom:1px solid #32383e}.bootstrap-table .fixed-table-container .fixed-table-header{overflow:hidden}.bootstrap-table .fixed-table-container .fixed-table-body{overflow-x:auto;overflow-y:auto;height:100%}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading{align-items:center;background:#fff;display:flex;justify-content:center;position:absolute;bottom:0;width:100%;max-width:100%;z-index:1000;transition:visibility 0s,opacity .15s ease-in-out;opacity:0;visibility:hidden}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.open{visibility:visible;opacity:1}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap{align-items:baseline;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .loading-text{margin-right:6px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap{align-items:center;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::before{content:"";animation-duration:1.5s;animation-iteration-count:infinite;animation-name:loading;background:#212529;border-radius:50%;display:block;height:5px;margin:0 4px;opacity:0;width:5px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot{animation-delay:.3s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after{animation-delay:.6s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark{background:#212529}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::before{background:#fff}.bootstrap-table .fixed-table-container .fixed-table-footer{overflow:hidden}.bootstrap-table .fixed-table-pagination::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-pagination>.pagination,.bootstrap-table .fixed-table-pagination>.pagination-detail{margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-pagination>.pagination-detail .pagination-info{line-height:34px;margin-right:5px}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list{display:inline-block}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group{position:relative;display:inline-block;vertical-align:middle}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group .dropdown-menu{margin-bottom:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination{margin:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a{color:#c8c8c8}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::before{content:"\2B05"}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::after{content:"\27A1"}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.disabled a{pointer-events:none;cursor:default}.bootstrap-table.fullscreen{position:fixed;top:0;left:0;z-index:1050;width:100%!important;background:#fff;height:calc(100vh);overflow-y:scroll}.bootstrap-table.bootstrap4 .pagination-lg .page-link,.bootstrap-table.bootstrap5 .pagination-lg .page-link{padding:.5rem 1rem}.bootstrap-table.bootstrap5 .float-left{float:left}.bootstrap-table.bootstrap5 .float-right{float:right}div.fixed-table-scroll-inner{width:100%;height:200px}div.fixed-table-scroll-outer{top:0;left:0;visibility:hidden;width:200px;height:150px;overflow:hidden}@keyframes loading{0%{opacity:0}50%{opacity:1}100%{opacity:0}} -------------------------------------------------------------------------------- /mm-web/src/main/resources/static/dayjs.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs=e()}(this,(function(){"use strict";var t=1e3,e=6e4,n=36e5,r="millisecond",i="second",s="minute",u="hour",a="day",o="week",c="month",f="quarter",h="year",d="date",l="Invalid Date",$=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,y=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,M={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(t){var e=["th","st","nd","rd"],n=t%100;return"["+t+(e[(n-20)%10]||e[n]||e[0])+"]"}},m=function(t,e,n){var r=String(t);return!r||r.length>=e?t:""+Array(e+1-r.length).join(n)+t},v={s:m,z:function(t){var e=-t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),i=n%60;return(e<=0?"+":"-")+m(r,2,"0")+":"+m(i,2,"0")},m:function t(e,n){if(e.date()1)return t(u[0])}else{var a=e.name;D[a]=e,i=a}return!r&&i&&(g=i),i||!r&&g},O=function(t,e){if(S(t))return t.clone();var n="object"==typeof e?e:{};return n.date=t,n.args=arguments,new _(n)},b=v;b.l=w,b.i=S,b.w=function(t,e){return O(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var _=function(){function M(t){this.$L=w(t.locale,null,!0),this.parse(t),this.$x=this.$x||t.x||{},this[p]=!0}var m=M.prototype;return m.parse=function(t){this.$d=function(t){var e=t.date,n=t.utc;if(null===e)return new Date(NaN);if(b.u(e))return new Date;if(e instanceof Date)return new Date(e);if("string"==typeof e&&!/Z$/i.test(e)){var r=e.match($);if(r){var i=r[2]-1||0,s=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)}}return new Date(e)}(t),this.init()},m.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds()},m.$utils=function(){return b},m.isValid=function(){return!(this.$d.toString()===l)},m.isSame=function(t,e){var n=O(t);return this.startOf(e)<=n&&n<=this.endOf(e)},m.isAfter=function(t,e){return O(t)'+(e?n.title[0]:n.title)+"":""}(),c=function(){"string"==typeof n.btn&&(n.btn=[n.btn]);var e,t=(n.btn||[]).length;return 0!==t&&n.btn?(e=''+n.btn[0]+"",2===t&&(e=''+n.btn[1]+""+e),'
'+e+"
"):""}();if(n.fixed||(n.top=n.hasOwnProperty("top")?n.top:100,n.style=n.style||"",n.style+=" top:"+(t.body.scrollTop+n.top)+"px"),2===n.type&&(n.content='

'+(n.content||"")+"

"),n.skin&&(n.anim="up"),"msg"===n.skin&&(n.shade=!1),s.innerHTML=(n.shade?"
':"")+'
"+l+'
'+n.content+"
"+c+"
",!n.type||2===n.type){var d=t[i](o[0]+n.type),y=d.length;y>=1&&layer.close(d[0].getAttribute("index"))}document.body.appendChild(s);var u=e.elem=a("#"+e.id)[0];n.success&&n.success(u),e.index=r++,e.action(n,u)},c.prototype.action=function(e,t){var n=this;e.time&&(l.timer[n.index]=setTimeout(function(){layer.close(n.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),layer.close(n.index)):e.yes?e.yes(n.index):layer.close(n.index)};if(e.btn)for(var s=t[i]("layui-m-layerbtn")[0].children,r=s.length,o=0;odiv{line-height:22px;padding-top:7px;margin-bottom:20px;font-size:14px}.layui-m-layerbtn{display:box;display:-moz-box;display:-webkit-box;width:100%;height:50px;line-height:50px;font-size:0;border-top:1px solid #D0D0D0;background-color:#F2F2F2}.layui-m-layerbtn span{display:block;-moz-box-flex:1;box-flex:1;-webkit-box-flex:1;font-size:14px;cursor:pointer}.layui-m-layerbtn span[yes]{color:#40AFFE}.layui-m-layerbtn span[no]{border-right:1px solid #D0D0D0;border-radius:0 0 0 5px}.layui-m-layerbtn span:active{background-color:#F6F6F6}.layui-m-layerend{position:absolute;right:7px;top:10px;width:30px;height:30px;border:0;font-weight:400;background:0 0;cursor:pointer;-webkit-appearance:none;font-size:30px}.layui-m-layerend::after,.layui-m-layerend::before{position:absolute;left:5px;top:15px;content:'';width:18px;height:1px;background-color:#999;transform:rotate(45deg);-webkit-transform:rotate(45deg);border-radius:3px}.layui-m-layerend::after{transform:rotate(-45deg);-webkit-transform:rotate(-45deg)}body .layui-m-layer .layui-m-layer-footer{position:fixed;width:95%;max-width:100%;margin:0 auto;left:0;right:0;bottom:10px;background:0 0}.layui-m-layer-footer .layui-m-layercont{padding:20px;border-radius:5px 5px 0 0;background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn{display:block;height:auto;background:0 0;border-top:none}.layui-m-layer-footer .layui-m-layerbtn span{background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn span[no]{color:#FD482C;border-top:1px solid #c2c2c2;border-radius:0 0 5px 5px}.layui-m-layer-footer .layui-m-layerbtn span[yes]{margin-top:10px;border-radius:5px}body .layui-m-layer .layui-m-layer-msg{width:auto;max-width:90%;margin:0 auto;bottom:-150px;background-color:rgba(0,0,0,.7);color:#fff}.layui-m-layer-msg .layui-m-layercont{padding:10px 20px} -------------------------------------------------------------------------------- /mm-web/src/main/resources/static/layer/theme/default/icon-ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dromara/WeMQ/6e98c73ec9309c499cbcfd7e9922feb8e3b09798/mm-web/src/main/resources/static/layer/theme/default/icon-ext.png -------------------------------------------------------------------------------- /mm-web/src/main/resources/static/layer/theme/default/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dromara/WeMQ/6e98c73ec9309c499cbcfd7e9922feb8e3b09798/mm-web/src/main/resources/static/layer/theme/default/icon.png -------------------------------------------------------------------------------- /mm-web/src/main/resources/static/layer/theme/default/loading-0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dromara/WeMQ/6e98c73ec9309c499cbcfd7e9922feb8e3b09798/mm-web/src/main/resources/static/layer/theme/default/loading-0.gif -------------------------------------------------------------------------------- /mm-web/src/main/resources/static/layer/theme/default/loading-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dromara/WeMQ/6e98c73ec9309c499cbcfd7e9922feb8e3b09798/mm-web/src/main/resources/static/layer/theme/default/loading-1.gif -------------------------------------------------------------------------------- /mm-web/src/main/resources/static/layer/theme/default/loading-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dromara/WeMQ/6e98c73ec9309c499cbcfd7e9922feb8e3b09798/mm-web/src/main/resources/static/layer/theme/default/loading-2.gif -------------------------------------------------------------------------------- /mm-web/src/main/resources/static/system/common.css: -------------------------------------------------------------------------------- 1 | .pagination { 2 | margin-right: 50px; 3 | } 4 | 5 | .page-item.page-next { 6 | margin-left: auto; 7 | text-align: left; 8 | } 9 | -------------------------------------------------------------------------------- /mm-web/src/main/resources/static/system/pages/menu.js: -------------------------------------------------------------------------------- 1 | function openParamMenu(id) { 2 | layui.use(['table', 'dropdown'], function(){ 3 | const table = layui.table; 4 | const dropdown = layui.dropdown; 5 | const layer = layui.layer; 6 | 7 | // 创建渲染实例 8 | let inst = table.render({ 9 | elem: '#test', 10 | url:`/page/info/${id}`, // 此处为静态模拟数据,实际使用时需换成真实接口 11 | parseData: function(res){ // res 即为原始返回的数据 12 | return { 13 | "code": res.code, // 解析接口状态 14 | "msg": res.msg, // 解析提示文本 15 | "data": res.data.mqParams // 解析数据列表 16 | }; 17 | }, 18 | response: { 19 | statusCode: 200 // 规定成功的状态码,默认:0 20 | }, 21 | defaultToolbar: ['filter', 'exports', 'print', { 22 | title: '提示', 23 | layEvent: 'LAYTABLE_TIPS', 24 | icon: 'layui-icon-tips' 25 | }], 26 | height: '315', // 最大高度减去其他容器已占有的高度差 27 | css: [ // 重设当前表格样式 28 | '.layui-table-tool-temp{padding-right: 145px;}' 29 | ].join(''), 30 | totalRow: false, // 开启合计行 31 | page: false, 32 | cols: [[ 33 | {field:'id', width:80, title: 'ID', sort: true,unresize: true}, 34 | {field: 'message', width: 338, title: '指令', unresize: true, edit: 'textarea'}, 35 | {field:'button',minWidth:100, title:'按钮名称', edit: 'text',unresize: true}, 36 | {field:'sort',minWidth:80, title:'排序', edit: 'text',unresize: true}, 37 | {fixed: 'right', title:'操作', width: 65, toolbar: '#barDemo',unresize: true} 38 | ]], 39 | done: function(){ 40 | // 单元格编辑事件 41 | table.on('edit(test)', function(obj){ 42 | let update; 43 | var field = obj.field; // 得到字段 44 | var value = obj.value; // 得到修改后的值 45 | var data = obj.data; // 得到所在行所有键值 46 | 47 | // 值的校验 48 | if(value == null || value === ''){ 49 | layer.msg('值不能为空', {icon: 2}); 50 | // 还原单元格编辑之前的值 51 | update = {}; 52 | update[field] = obj.oldValue; // 使用旧值 53 | obj.update(update); 54 | return; 55 | } 56 | if(field === 'sort'){ 57 | if(!/^[0-9]*$/.test(value)){ 58 | layer.msg('排序只能为数字', {icon: 2}); 59 | // 还原单元格编辑之前的值 60 | update = {}; 61 | update[field] = obj.oldValue; // 使用旧值 62 | obj.update(update); 63 | return; 64 | } 65 | } 66 | 67 | $.ajax({ 68 | url: '/page/param/update', 69 | type: 'post', 70 | data: JSON.stringify(data), 71 | dataType: 'json', 72 | contentType: "application/json", 73 | success: function(res){ 74 | if(res.code !== 200){ 75 | layer.msg(res.msg, {icon: 2}); 76 | // 还原单元格编辑之前的值 77 | update = {}; 78 | update[field] = obj.oldValue; // 使用旧值 79 | obj.update(update); 80 | } else { 81 | layer.msg('编辑成功', {icon: 1}); 82 | // 重新加载表格 83 | inst.reloadData(); 84 | } 85 | console.log(data); 86 | } 87 | }); 88 | }); 89 | }, 90 | error: function(res, msg){ 91 | console.log(res, msg) 92 | } 93 | }); 94 | 95 | layer.open({ 96 | type: 1, // 页面层 97 | title: '调试参数编辑', 98 | area: ['800px', '650px'], // 宽高 99 | content: $('#modal-message'), // 引用上面的内容 100 | }); 101 | 102 | // 触发单元格工具事件 103 | table.on('tool(test)', function(obj){ // 双击 toolDouble 104 | var data = obj.data; // 获得当前行数据 105 | // console.log(obj) 106 | if(obj.event === 'del'){ 107 | layer.confirm('真的删除行 [id: '+ data.id +'] 么', function(index){ 108 | submitDeleteMessage(data.id); 109 | }); 110 | } 111 | }); 112 | 113 | // 行单击事件 114 | table.on('row(test)', function(obj){ 115 | //console.log(obj); 116 | //layer.closeAll('tips'); 117 | }); 118 | // 行双击事件 119 | table.on('rowDouble(test)', function(obj){ 120 | console.log(obj); 121 | }); 122 | }); 123 | 124 | } 125 | -------------------------------------------------------------------------------- /mm-web/src/main/resources/static/system/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 生成随机字符串 3 | * @param len 长度 4 | * @returns {string} 随机字符串 5 | */ 6 | function randomString(len) { 7 | len = len || 32; 8 | const $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 9 | const maxPos = $chars.length; 10 | let result = ''; 11 | for (let i = 0; i < len; i++) { 12 | result += $chars.charAt(Math.floor(Math.random() * maxPos)); 13 | } 14 | return result; 15 | } 16 | 17 | /** 18 | * 判断是否为空 19 | * @param obj 对象 20 | * @returns {boolean} 是否为空 21 | */ 22 | function isBlank(obj) { 23 | return obj === undefined || obj === null || obj === ''; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /mm-web/src/main/resources/static/toastify/toastify-js.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Minified by jsDelivr using Terser v5.14.1. 3 | * Original file: /npm/toastify-js@1.12.0/src/toastify.js 4 | * 5 | * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files 6 | */ 7 | /*! 8 | * Toastify js 1.12.0 9 | * https://github.com/apvarun/toastify-js 10 | * @license MIT licensed 11 | * 12 | * Copyright (C) 2018 Varun A P 13 | */ 14 | !function(t,o){"object"==typeof module&&module.exports?module.exports=o():t.Toastify=o()}(this,(function(t){var o=function(t){return new o.lib.init(t)};function i(t,o){return o.offset[t]?isNaN(o.offset[t])?o.offset[t]:o.offset[t]+"px":"0px"}function s(t,o){return!(!t||"string"!=typeof o)&&!!(t.className&&t.className.trim().split(/\s+/gi).indexOf(o)>-1)}return o.defaults={oldestFirst:!0,text:"Toastify is awesome!",node:void 0,duration:3e3,selector:void 0,callback:function(){},destination:void 0,newWindow:!1,close:!1,gravity:"toastify-top",positionLeft:!1,position:"",backgroundColor:"",avatar:"",className:"",stopOnFocus:!0,onClick:function(){},offset:{x:0,y:0},escapeMarkup:!0,ariaLive:"polite",style:{background:""}},o.lib=o.prototype={toastify:"1.12.0",constructor:o,init:function(t){return t||(t={}),this.options={},this.toastElement=null,this.options.text=t.text||o.defaults.text,this.options.node=t.node||o.defaults.node,this.options.duration=0===t.duration?0:t.duration||o.defaults.duration,this.options.selector=t.selector||o.defaults.selector,this.options.callback=t.callback||o.defaults.callback,this.options.destination=t.destination||o.defaults.destination,this.options.newWindow=t.newWindow||o.defaults.newWindow,this.options.close=t.close||o.defaults.close,this.options.gravity="bottom"===t.gravity?"toastify-bottom":o.defaults.gravity,this.options.positionLeft=t.positionLeft||o.defaults.positionLeft,this.options.position=t.position||o.defaults.position,this.options.backgroundColor=t.backgroundColor||o.defaults.backgroundColor,this.options.avatar=t.avatar||o.defaults.avatar,this.options.className=t.className||o.defaults.className,this.options.stopOnFocus=void 0===t.stopOnFocus?o.defaults.stopOnFocus:t.stopOnFocus,this.options.onClick=t.onClick||o.defaults.onClick,this.options.offset=t.offset||o.defaults.offset,this.options.escapeMarkup=void 0!==t.escapeMarkup?t.escapeMarkup:o.defaults.escapeMarkup,this.options.ariaLive=t.ariaLive||o.defaults.ariaLive,this.options.style=t.style||o.defaults.style,t.backgroundColor&&(this.options.style.background=t.backgroundColor),this},buildToast:function(){if(!this.options)throw"Toastify is not initialized";var t=document.createElement("div");for(var o in t.className="toastify on "+this.options.className,this.options.position?t.className+=" toastify-"+this.options.position:!0===this.options.positionLeft?(t.className+=" toastify-left",console.warn("Property `positionLeft` will be depreciated in further versions. Please use `position` instead.")):t.className+=" toastify-right",t.className+=" "+this.options.gravity,this.options.backgroundColor&&console.warn('DEPRECATION NOTICE: "backgroundColor" is being deprecated. Please use the "style.background" property.'),this.options.style)t.style[o]=this.options.style[o];if(this.options.ariaLive&&t.setAttribute("aria-live",this.options.ariaLive),this.options.node&&this.options.node.nodeType===Node.ELEMENT_NODE)t.appendChild(this.options.node);else if(this.options.escapeMarkup?t.innerText=this.options.text:t.innerHTML=this.options.text,""!==this.options.avatar){var s=document.createElement("img");s.src=this.options.avatar,s.className="toastify-avatar","left"==this.options.position||!0===this.options.positionLeft?t.appendChild(s):t.insertAdjacentElement("afterbegin",s)}if(!0===this.options.close){var e=document.createElement("button");e.type="button",e.setAttribute("aria-label","Close"),e.className="toast-close",e.innerHTML="✖",e.addEventListener("click",function(t){t.stopPropagation(),this.removeElement(this.toastElement),window.clearTimeout(this.toastElement.timeOutValue)}.bind(this));var n=window.innerWidth>0?window.innerWidth:screen.width;("left"==this.options.position||!0===this.options.positionLeft)&&n>360?t.insertAdjacentElement("afterbegin",e):t.appendChild(e)}if(this.options.stopOnFocus&&this.options.duration>0){var a=this;t.addEventListener("mouseover",(function(o){window.clearTimeout(t.timeOutValue)})),t.addEventListener("mouseleave",(function(){t.timeOutValue=window.setTimeout((function(){a.removeElement(t)}),a.options.duration)}))}if(void 0!==this.options.destination&&t.addEventListener("click",function(t){t.stopPropagation(),!0===this.options.newWindow?window.open(this.options.destination,"_blank"):window.location=this.options.destination}.bind(this)),"function"==typeof this.options.onClick&&void 0===this.options.destination&&t.addEventListener("click",function(t){t.stopPropagation(),this.options.onClick()}.bind(this)),"object"==typeof this.options.offset){var l=i("x",this.options),r=i("y",this.options),p="left"==this.options.position?l:"-"+l,d="toastify-top"==this.options.gravity?r:"-"+r;t.style.transform="translate("+p+","+d+")"}return t},showToast:function(){var t;if(this.toastElement=this.buildToast(),!(t="string"==typeof this.options.selector?document.getElementById(this.options.selector):this.options.selector instanceof HTMLElement||"undefined"!=typeof ShadowRoot&&this.options.selector instanceof ShadowRoot?this.options.selector:document.body))throw"Root element is not defined";var i=o.defaults.oldestFirst?t.firstChild:t.lastChild;return t.insertBefore(this.toastElement,i),o.reposition(),this.options.duration>0&&(this.toastElement.timeOutValue=window.setTimeout(function(){this.removeElement(this.toastElement)}.bind(this),this.options.duration)),this},hideToast:function(){this.toastElement.timeOutValue&&clearTimeout(this.toastElement.timeOutValue),this.removeElement(this.toastElement)},removeElement:function(t){t.className=t.className.replace(" on",""),window.setTimeout(function(){this.options.node&&this.options.node.parentNode&&this.options.node.parentNode.removeChild(this.options.node),t.parentNode&&t.parentNode.removeChild(t),this.options.callback.call(t),o.reposition()}.bind(this),400)}},o.reposition=function(){for(var t,o={top:15,bottom:15},i={top:15,bottom:15},e={top:15,bottom:15},n=document.getElementsByClassName("toastify"),a=0;a0?window.innerWidth:screen.width)<=360?(n[a].style[t]=e[t]+"px",e[t]+=l+15):!0===s(n[a],"toastify-left")?(n[a].style[t]=o[t]+"px",o[t]+=l+15):(n[a].style[t]=i[t]+"px",i[t]+=l+15)}return this},o.lib.init.prototype=o.lib,o})); 15 | //# sourceMappingURL=/sm/e1ebbfe1bf0b0061f0726ebc83434e1c2f8308e6354c415fd05ecccdaad47617.map -------------------------------------------------------------------------------- /mm-web/src/main/resources/static/toastify/toastify.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Minified by jsDelivr using clean-css v5.3.0. 3 | * Original file: /npm/toastify-js@1.12.0/src/toastify.css 4 | * 5 | * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files 6 | */ 7 | /*! 8 | * Toastify js 1.12.0 9 | * https://github.com/apvarun/toastify-js 10 | * @license MIT licensed 11 | * 12 | * Copyright (C) 2018 Varun A P 13 | */ 14 | .toastify{padding:12px 20px;color:#fff;display:inline-block;box-shadow:0 3px 6px -1px rgba(0,0,0,.12),0 10px 36px -4px rgba(77,96,232,.3);background:-webkit-linear-gradient(315deg,#73a5ff,#5477f5);background:linear-gradient(135deg,#73a5ff,#5477f5);position:fixed;opacity:0;transition:all .4s cubic-bezier(.215, .61, .355, 1);border-radius:2px;cursor:pointer;text-decoration:none;max-width:calc(50% - 20px);z-index:2147483647}.toastify.on{opacity:1}.toast-close{background:0 0;border:0;color:#fff;cursor:pointer;font-family:inherit;font-size:1em;opacity:.4;padding:0 5px}.toastify-right{right:15px}.toastify-left{left:15px}.toastify-top{top:-150px}.toastify-bottom{bottom:-150px}.toastify-rounded{border-radius:25px}.toastify-avatar{width:1.5em;height:1.5em;margin:-7px 5px;border-radius:2px}.toastify-center{margin-left:auto;margin-right:auto;left:0;right:0;max-width:fit-content;max-width:-moz-fit-content}@media only screen and (max-width:360px){.toastify-left,.toastify-right{margin-left:auto;margin-right:auto;left:0;right:0;max-width:fit-content}} 15 | /*# sourceMappingURL=/sm/cb4335d1b03e933ed85cb59fffa60cf51f07567ed09831438c60f59afd166464.map */ -------------------------------------------------------------------------------- /mm-web/src/main/resources/templates/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 关于项目 - 物联网调试管理系统 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 |
17 | 31 | 32 |
33 |
34 |
35 | 36 |

WeMQ 目前已加入 Dromara 社区 孵化项目

37 |
38 |

项目介绍

39 | 40 |

项目简介

41 | 42 |

基于SpringBoot开发的物联网设备调试管理平台。

43 | 44 |

WeMQ是一款面向物联网设备运营商的开源物联网设备调试系统,提供完整的物联网设备调试方案,集成设备管理、MQTT服务器管理、客户管理等功能,自研Nmqs通信层组件,实现了连接信息的加密,保证了数据的安全性。

45 | 46 |

starfork

47 | 48 |

技术选型

49 | 50 |
1. 系统环境
51 | 52 |
    53 |
  • Java 8
  • 54 |
  • Servlet 3.0
  • 55 |
  • Apache Maven 3
  • 56 |
57 | 58 |
2. 主框架
59 | 60 |
    61 |
  • Spring Boot 2.7.x
  • 62 |
  • Spring Framework 5.3.x
  • 63 |
  • Spring MVC 5.3.x
  • 64 |
65 | 66 |
3. 持久层
67 | 68 |
    69 |
  • Mybatis 3.5.x
  • 70 |
  • Alibaba Druid 1.2.x
  • 71 |
  • Hibernate Validation 6.0.x
  • 72 |
  • Java MySQL Connector 8.0.x
  • 73 |
74 | 75 |
4. 视图层
76 | 77 |
    78 |
  • Thymeleaf 3.x
  • 79 |
  • Bootstrap 5.x
  • 80 |
  • Layui 2.x
  • 81 |
82 | 83 |
5. 工具类
84 | 85 |
    86 |
  • Apache Commons
  • 87 |
  • Hutool 5.x
  • 88 |
89 | 90 |

主要功能

91 | 92 |
    93 |
  • 系统管理员管理
  • 94 |
  • 客户管理
  • 95 |
  • 调试页面管理
  • 96 |
  • 对接Nmqs(NicholasLD's Message Queue Service)
  • 97 |
  • 客户调试页面
  • 98 |
  • 登录日志
  • 99 |
100 | 101 |

项目结构

102 | 103 |
cn.mmanager
104 | ├── mm-common            // 工具类
105 | │       └── annotation                    // 自定义注解
106 | │       └── constant                      // 通用常量
107 | │       └── core                          // 核心控制
108 | │       └── enums                         // 通用枚举
109 | │       └── exception                     // 通用异常
110 | ├── mm-framework         // 框架核心
111 | │       └── aspectj                       // 注解实现
112 | │       └── interceptor                   // 拦截器
113 | │       └── manager                       // 异步处理
114 | │       └── web                           // 前端控制
115 | ├── mm-web       	 // Web服务
116 | ├── mm-dao      	 // 数据访问层
117 | ├── mm-service       // 业务层
118 | ├── mm-model         // 模型
119 | 
120 | 121 |

Issues & Pull Requests

122 | 123 |

欢迎提交Issues和Pull Requests,开源大门永远向所有人敞开。

124 | 125 |

联系作者

126 | 127 |
    128 |
  • Email: 878639947@qq.com
  • 129 |
  • QQ: 878639947
  • 130 |
  • WeChat: NicholasLD505
  • 131 |
132 | 133 |

License(开源许可证)

134 | 135 |

Apache License Version 2.0 see http://www.apache.org/licenses/LICENSE-2.0.html

136 | 137 |

版权使用说明

138 | 139 |

WeMQ遵循Apache2.0开源协议,可用于个人学习、毕设、公司项目、商业产品等,但必须保留版权信息。

140 | 141 |

知识星球

142 | 143 |

144 |
145 |
146 |
147 |
148 | 149 | 150 | -------------------------------------------------------------------------------- /mm-web/src/main/resources/templates/customers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 客户 - 物联网调试管理系统 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | 20 |
21 | 44 | 45 |
46 |
47 | 50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | 67 | 206 | 212 | 213 | -------------------------------------------------------------------------------- /mm-web/src/main/resources/templates/error/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 - 物联网调试管理系统 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
404
15 |

您要访问的页面不存在

16 |
17 |
18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /mm-web/src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 登录 - 物联网调试管理系统 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 | 25 |
26 |

WeMQ - 管理员后台

27 |
28 | 29 |
30 | 31 | 32 |
33 |
34 | 37 |
38 | 39 |
40 |
41 | 44 |
45 |
46 |
47 |
48 |
49 | 50 | 51 | -------------------------------------------------------------------------------- /mm-web/src/main/resources/templates/loginlog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 登录日志 - 物联网调试管理系统 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | 20 |
21 | 43 | 44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | 54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | 63 | 140 | 145 | 146 | -------------------------------------------------------------------------------- /pages/GPS.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 物联网设备调试 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |

GPS在线调试

13 | 16 | 17 | 18 | 19 |
20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 |
28 |
29 |
30 | 预设调试参数 31 |
32 |
33 |
34 |
35 |
36 | 37 | 38 |
39 |
40 |
41 | 42 |
43 |
44 |
45 |
46 |
47 |
48 | 电表调试 49 |
50 |
51 |
52 |
53 |
54 | 55 | 59 |
60 |
61 |
62 | 63 |
64 |
65 |
66 | 67 | 68 |
69 | 70 | 71 | 72 | 73 | 74 | 151 | -------------------------------------------------------------------------------- /pages/电表调试.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 物联网设备调试 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |

电表调试(自定义页面测试)

13 | 16 | 17 | 18 | 19 |
20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 |
28 |
29 |
30 | 预设调试参数 31 |
32 |
33 |
34 |
35 |
36 | 37 | 38 |
39 |
40 |
41 | 42 |
43 |
44 |
45 |
46 |
47 |
48 | 电表调试 49 |
50 |
51 |
52 |
53 |
54 | 55 | 59 |
60 |
61 |
62 | 63 |
64 |
65 |
66 | 67 | 68 |
69 | 70 | 71 | 72 | 73 | 74 | 151 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.dromara 8 | WeMQ 9 | ${revision} 10 | pom 11 | 12 | mm-common 13 | mm-framework 14 | mm-web 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-parent 20 | 3.4.0 21 | 22 | 23 | 24 | 25 | 3.0.0 26 | 21 27 | 21 28 | UTF-8 29 | 30 | 31 | 32 | 33 | org.hibernate.validator 34 | hibernate-validator 35 | 8.0.2.Final 36 | 37 | 38 | org.projectlombok 39 | lombok 40 | 1.18.36 41 | provided 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-aop 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-thymeleaf 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-web 54 | 55 | 56 | com.mysql 57 | mysql-connector-j 58 | 8.3.0 59 | 60 | 61 | org.mindrot 62 | jbcrypt 63 | 0.4 64 | 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-starter-test 69 | test 70 | 71 | 72 | 73 | cn.hutool 74 | hutool-all 75 | 5.8.34 76 | 77 | 78 | 79 | 80 | 81 | 82 | org.apache.maven.plugins 83 | maven-compiler-plugin 84 | 3.8.1 85 | 86 | 21 87 | 21 88 | UTF-8 89 | 90 | 91 | 92 | 93 | 94 | 95 | public 96 | aliyun nexus 97 | https://maven.aliyun.com/repository/public 98 | 99 | true 100 | 101 | 102 | 103 | central 104 | https://maven.aliyun.com/repository/central 105 | 106 | true 107 | 108 | 109 | true 110 | 111 | 112 | 113 | 114 | 115 | 116 | public 117 | aliyun nexus 118 | https://maven.aliyun.com/repository/public 119 | 120 | true 121 | 122 | 123 | false 124 | 125 | 126 | 127 | 128 | 129 | 130 | --------------------------------------------------------------------------------