整合SpringBoot+ MySQL+ Uniapp+MQTT前后端完整IOT作品无偿分享(内有思路讲解+成品展示)—— 物联网大赛一等奖作品

前言

本文所用的所有资料均存于百度网盘,开箱即用!!!
链接:https://pan.baidu.com/s/1MP8B9R5OgoAQkuiVcszLnw?pwd=lik6
提取码:lik6
在这里插入图片描述

一、 MQTT服务器

MQTT介绍

MQTT(Message Queuing Telemetry Transport)是一种轻量级、基于发布-订阅模式的消息传输协议,适用于资源受限的设备和低带宽、高延迟或不稳定的网络环境。它在物联网应用中广受欢迎,能够实现传感器、执行器和其它设备之间的高效通信。

MQTT组成

MQTT 通过如下定义客户端和代理来实施发布/订阅模型。

MQTT 客户端

MQTT 客户端是从服务器到运行 MQTT 库的微控制器的任何设备。如果客户端正在发送消息,它将充当发布者;如果它正在接收消息,它将充当接收者。基本上,任何通过网络使用 MQTT 进行通信的设备都可以称为 MQTT 客户端设备。

MQTT 代理

MQTT 代理是协调不同客户端之间消息的后端系统。代理的职责包括接收和筛选消息、识别订阅每条消息的客户端,以及向他们发送消息。它还负责其他任务,例如:

  • 授权 MQTT 客户端以及对其进行身份验证
  • 将消息传递给其他系统以进行进一步分析
  • 处理错过的消息和客户端会话
MQTT 连接

客户端和代理开始使用 MQTT 连接进行通信。客户端通过向 MQTT 代理发送 CONNECT 消息来启动连接。代理通过响应 CONNACK 消息来确认已建立连接。MQTT 客户端和代理都需要 TCP/IP 堆栈进行通信。客户端从不相互联系,它们只与代理联系。
Clip_2024-09-18_16-41-26.png

MQTT 的工作原理

  1. MQTT 客户端与 MQTT 代理建立连接。
  2. 连接后,客户端可以发布消息、订阅特定消息或同时执行这两项操作。
  3. MQTT 代理收到一条消息后,会将其转发给对此感兴趣的订阅者。
MQTT 主题(Topic)

“主题”是指 MQTT 代理用于为客户端筛选消息的关键词。
例子:以文件夹结构为例,“home/livingroom/temperature”是一个主题,表示客厅的温度信息。如果空调订阅了这个主题,就会收到关于客厅温度的所有更新消息。

MQTT 发布(Publish)

MQTT 客户端可以发布包含主题和数据的消息。
例子:温度传感器发布当前室内温度为“22°C”,并将此数据附带在“home/livingroom/temperature”主题下,代理会将此消息发送给所有对此主题感兴趣的客户端。

MQTT 订阅(Subscribe)

MQTT 客户端通过发送 SUBSCRIBE 消息来订阅某个主题。
例子:空调控制系统发送一个 SUBSCRIBE 消息,订阅了“home/livingroom/temperature”主题。当温度传感器发布新温度时,空调系统会收到并根据新温度调整运行状态。

QoS等级

MQTT 提供了三种服务质量(QoS),在不同网络环境下保证消息的可靠性。

服务质量等级
QoS 0消息最多传送一次。如果当前客户端不可用,它将丢失这条消息
QoS 1消息至少传送一次
QoS 2消息只传送一次

本地搭建EMQX服务器

安装包见网盘

MQTT服务用的是EMQX
到bin目录下,打开cmd输入以下命令启动EMQX服务器

emqx start

image.png
顺便提下停止命令

emqx stop

image.png

访问地址: http://localhost:18083/ (默认账号为“admin”,默认密码为“public”)
image.png
image.png

二、上、下位机实现基本通信

必要依赖与配置文件

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.hua</groupId>
    <artifactId>iot-mqtt-02</artifactId>
    <version>0.0.2-SNAPSHOT</version>
    <name>iot-mqtt-02</name>
    <description>iot-mqtt-02</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-integration</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-stream</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-mqtt</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>9</source>
                    <target>9</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

application.yml

server:
  port: 8082
publish:
  mqtt:
    host: tcp://localhost:1883
#    host: tcp://192.168.31.242:1883
    username: admin
    password: public
    cleansession: false
    clientid: mqtt_publish
    default_topic: ??
    timeout: 1000
    keepalive: 10
    connectionTimeout: 3000


下位机:使用mqtt_publish模拟发送数据

下位机指的是与机器相连接的计算机或者单片机,一般用于接收和反馈上位机的指令,并根据指令控制机器执行动作以及从机器传感器读取数据。
典型设备:PLC、stm32、51、FPGA、ARM等各类可编程芯片。

控制类: 定义请求格式与响应格式
package com.emqx.demo.config.controller;


import com.emqx.demo.config.mqtt.MQTTServer;
import com.emqx.demo.model.ApiResponse;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.Map;

@RestController
@RequestMapping(value = "/testPublish")
public class PublishController {

    @Resource
    private MQTTServer mqttserver;

    /**
     * 测试
     * @return JSON格式的响应
     */
    @GetMapping(value = "/hello")
    public ApiResponse<String> test() {
        return ApiResponse.success("测试", "请求成功");
    }

    /**
     * 发布消息
     * @param payload JSON格式的请求体,包括topic, msg和qos
     * @return JSON格式的响应
     */
    @PostMapping(value = "/publish")
    public ApiResponse<String> testPublish(@RequestBody Map<String, Object> payload) {
        String topic = (String) payload.get("topic");
        String msg = (String) payload.get("msg");
        int qos = (int) payload.get("qos");

        mqttserver.sendMQTTMessage(topic, msg, qos);

        String data = String.format("发送了一条主题是‘%s’,内容是: %s,消息级别 %d", topic, msg, qos);
        return ApiResponse.success(data, "消息发布成功");
    }

    /**
     * 订阅主题
     * @param payload JSON格式的请求体,包括topic和qos
     * @return JSON格式的响应
     */
    @PostMapping(value = "/subscribe")
    public ApiResponse<String> testSubscribe(@RequestBody Map<String, Object> payload) {
        String topic = (String) payload.get("topic");
        int qos = (int) payload.get("qos");

        mqttserver.init(topic, qos);

        String data = String.format("订阅主题'%s'成功", topic);
        return ApiResponse.success(data, "主题订阅成功");
    }
}
MQTT配置类
package com.openx.mqtt_subsribe.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(MQTTConfig.PREFIX)
public class MQTTConfig {
    //指定配置文件application-local.properties中的属性名前缀
    public static final String PREFIX = "publish.mqtt";
    private String host;
    private String clientid;
    private String username;
    private String password;
    private boolean cleansession;
    private String default_topic;
    private int timeout;
    private int keepalive;
    private int connectionTimeout;

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public String getClientid() {
        return clientid;
    }

    public void setClientid(String clientid) {
        this.clientid = clientid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getDefault_topic() {
        return default_topic;
    }

    public void setDefault_topic(String default_topic) {
        this.default_topic = default_topic;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    public int getKeepalive() {
        return keepalive;
    }

    public void setKeepalive(int keepalive) {
        this.keepalive = keepalive;
    }

    public int getConnectionTimeout() {
        return connectionTimeout;
    }

    public void setConnectionTimeout(int connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    public boolean isCleansession() {
        return cleansession;
    }

    public void setCleansession(boolean cleansession) {
        this.cleansession = cleansession;
    }
}

创建MqttConnectOptions连接对象
package com.emqx.demo.config.mqtt;

import com.emqx.demo.config.MQTTConfig;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 创建MqttConnectOptions连接对象
 */
@Component
public class MqttConnect {

    @Autowired
    private MQTTConfig config;

    public MqttConnect(MQTTConfig config) {
        this.config = config;
    }

    public MqttConnectOptions getOptions() {
        MqttConnectOptions options = new MqttConnectOptions();
        options.setCleanSession(config.isCleansession());
        options.setUserName(config.getUsername());
        options.setPassword(config.getPassword().toCharArray());
        options.setConnectionTimeout(config.getConnectionTimeout());
        //设置心跳
        options.setKeepAliveInterval(config.getKeepalive());
        return options;
    }

    public MqttConnectOptions getOptions(MqttConnectOptions options) {

        options.setCleanSession(options.isCleanSession());
        options.setUserName(options.getUserName());
        options.setPassword(options.getPassword());
        options.setConnectionTimeout(options.getConnectionTimeout());
        options.setKeepAliveInterval(options.getKeepAliveInterval());
        return options;
    }
}

image.png

发布端服务类:主要实现发布消息和订阅主题

要发布消息的时候只需要调用sendMQTTMessage方法就OK了

package com.emqx.demo.config.mqtt;

import com.emqx.demo.config.MQTTConfig;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 发布端:主要实现发布消息和订阅主题,接收消息在回调类PushCallback中
 * 要发布消息的时候只需要调用sendMQTTMessage方法就OK了
 */
@Service
public class MQTTServer {
    private static final Logger LOGGER = LoggerFactory.getLogger(MQTTServer.class);

    /* 订阅者客户端对象 */
    private MqttClient subsribeClient;

    /**
     * 发布者客户端对象
     * 这里订阅者和发布者的MqttClient对象分别命名是为了让发布者和订阅者分开,
     * 如果订阅者和发布者都用一个MqttClient链接对象,则会出现两方都订阅了某个主题后,
     * 谁发送了消息,都会自己接收到自己发的消息,所以分开写,里面主要就是回调类的设置setCallback
     * */
    private MqttClient publishClient;

    /* 主题对象 */
    public MqttTopic topic;

    /* 消息内容对象 */
    public MqttMessage message;

    @Autowired
    private MqttConnect mqttConnect;

    @Autowired
    private MQTTConfig config;

    public MQTTServer() {
        LOGGER.info("8082上线了");
    }

    /**
     * 发布者客户端和服务端建立连接
     */
    public MqttClient publishConnect() {
        //防止重复创建MQTTClient实例
        try {
            if (publishClient==null) {
                //先让客户端和服务器建立连接,MemoryPersistence设置clientid的保存形式,默认为以内存保存
                publishClient = new MqttClient(config.getHost(), config.getClientid(), new MemoryPersistence());
                //发布消息不需要回调连接
                //client.setCallback(new PushCallback());
            }

            MqttConnectOptions options = mqttConnect.getOptions();
            //判断拦截状态,这里注意一下,如果没有这个判断,是非常坑的
            if (!publishClient.isConnected()) {
                publishClient.connect(options);
                LOGGER.info("---------------------连接成功");
            }else {//这里的逻辑是如果连接成功就重新连接
                publishClient.disconnect();
                publishClient.connect(mqttConnect.getOptions(options));
                LOGGER.info("---------------------连接成功");
            }
        } catch (MqttException e) {
            LOGGER.info(e.toString());
        }
        return publishClient;
    }

    /**
     * 订阅端的链接方法,关键是回调类的设置,要对订阅的主题消息进行处理
     * 断线重连方法,如果是持久订阅,重连时不需要再次订阅
     * 如果是非持久订阅,重连是需要重新订阅主题 取决于options.setCleanSession(true);
     * true为非持久订阅
     */
    public void subsribeConnect() {
        try {
            //防止重复创建MQTTClient实例
            if (subsribeClient==null) {
                //clientId不能和其它的clientId一样,否则会出现频繁断开连接和重连的问题
                subsribeClient = new MqttClient(config.getHost(), config.getClientid(), new MemoryPersistence());// MemoryPersistence设置clientid的保存形式,默认为以内存保存
                //如果是订阅者则添加回调类,发布不需要,PushCallback类在后面,继续往下看
                subsribeClient.setCallback(new PushCallback(MQTTServer.this));
            }
            MqttConnectOptions options = mqttConnect.getOptions();
            //判断拦截状态,这里注意一下,如果没有这个判断,是非常坑的
            if (!subsribeClient.isConnected()) {
                subsribeClient.connect(options);
            }else {//这里的逻辑是如果连接成功就重新连接
                subsribeClient.disconnect();
                subsribeClient.connect(mqttConnect.getOptions(options));
            }
            LOGGER.info("----------客户端连接成功");
        } catch (MqttException e) {
            LOGGER.info(e.getMessage(), e);
        }
    }

    /**
     * 把组装好的消息发出去
     * @param topic
     * @param message
     * @return
     */
    public boolean publish(MqttTopic topic , MqttMessage message) {

        MqttDeliveryToken token = null;
        try {
            //把消息发送给对应的主题
            token = topic.publish(message);
            token.waitForCompletion();
            //检查发送是否成功
            boolean flag = token.isComplete();

            StringBuffer sbf = new StringBuffer(200);
            sbf.append("给主题为'"+topic.getName());
            sbf.append("'发布消息:");
            if (flag) {
                sbf.append("成功!消息内容是:"+new String(message.getPayload()));
            } else {
                sbf.append("失败!");
            }
            LOGGER.info(sbf.toString());
        } catch (MqttException e) {
            LOGGER.info(e.toString());
        }
        return token.isComplete();
    }

    /**
     * MQTT发送指令:主要是组装消息体
     * @param topic 主题
     * @param data 消息内容
     * @param qos 消息级别
     */
    public void sendMQTTMessage(String topic, String data, int qos) {

        try {
            this.publishClient = publishConnect();
            this.topic = this.publishClient.getTopic(topic);
            message = new MqttMessage();
            //消息等级
            //level 0:消息最多传递一次,不再关心它有没有发送到对方,也不设置任何重发机制
            //level 1:包含了简单的重发机制,发送消息之后等待接收者的回复,如果没收到回复则重新发送消息。这种模式能保证消息至少能到达一次,但无法保证消息重复
            //level 2: 有了重发和重复消息发现机制,保证消息到达对方并且严格只到达一次
            message.setQos(qos);
            //如果重复消费,则把值改为true,然后发送一条空的消息,之前的消息就会覆盖,然后在改为false
            message.setRetained(false);

            message.setPayload(data.getBytes());

            //将组装好的消息发出去
            publish(this.topic, message);
        } catch (Exception e) {
            LOGGER.info(e.toString());
            e.printStackTrace();
        }

    }

    /**
     * 订阅端订阅消息
     * @param topic 要订阅的主题
     * @param qos 订阅消息的级别
     */
    public void init(String topic, int qos) {
        //建立连接
        subsribeConnect();
        //以某个消息级别订阅某个主题
        try {
            subsribeClient.subscribe(topic, qos);
        } catch (MqttException e) {
            LOGGER.info(e.getMessage(), e);
        }
    }

    /**
     * 订阅端取消订阅消息
     * @param topic 要订阅的主题
     */
    public void unionInit(String topic) {
        //建立连接
        subsribeConnect();
        //取消订阅某个主题
        try {
            //MQTT 协议中订阅关系是持久化的,因此如果不需要订阅某些 Topic,需要调用 unsubscribe 方法取消订阅关系。
            subsribeClient.unsubscribe(topic);
        } catch (MqttException e) {
            LOGGER.info(e.getMessage(), e);
        }
    }

}

上位机:使用mqtt_subsribe订阅

上位机指的是可以直接发送操作指令的计算机或者单片机,一般提供用户操作交互界面并向用户展示反馈数据。
典型设备:电脑、平板、手机、面板、触摸屏
上位机软件是用于完成上位机操作交互的软件

SubsribeController控制类: 定义请求格式与响应格式

SubsribeController.java

package com.openx.mqtt_subsribe.controller;
import com.alibaba.fastjson.JSON;
import com.openx.mqtt_subsribe.entity.Message;
import com.openx.mqtt_subsribe.entity.Topic;
import com.openx.mqtt_subsribe.model.Result;
import com.openx.mqtt_subsribe.mqtt.MQTTSubsribe;
import com.openx.mqtt_subsribe.service.MessageService;
import com.openx.mqtt_subsribe.service.TopicService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.codehaus.jettison.json.JSONString;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping(value = "/SubsribeController")
@Api(tags = "mqtt订阅相关接口")
public class SubsribeController {
    @Autowired
    private TopicService topicService;
    @Autowired
    private MQTTSubsribe mqttSubsribe;
    @Autowired
    private MessageService messageService;
    /**
     * 订阅主题
     * @param payload JSON格式的请求体,包括topic和qos
     * @return JSON格式的响应
     */
    @PostMapping(value = "/subscribe")
    @ApiOperation(value="订阅主题")
    public Result<String> subscribTopic(@RequestBody Map<String, Object> payload) {
        String topic = (String) payload.get("topic");
        int qos = (int) payload.get("qos");
//        mqttSubsribe.init(topic, qos) ;
        Boolean b = topicService.subscribTopic(topic, qos);
        String data = String.format("订阅'%s'成功", topic);
        return Result.success(data);
    }
    /**
     * 退订主题
     * @param payload JSON格式的请求体,包括topic
     * @return JSON格式的响应
     */
    @PostMapping(value = "/unsubscribe")
    @ApiOperation("退订主题")
    public Result<String> testUnsvSubsribe(@RequestBody Map<String, Object> payload) {
        String topic = (String) payload.get("topic");

        mqttSubsribe.unionInit(topic);

        String data = String.format("取消订阅'%s'成功", topic);
        return Result.success(data);
    }
    /**
     * 测试
     * @return JSON格式的响应
     */
    @GetMapping(value = "/hello")
    public Result<String> test() {
        return Result.success("hello");
    }
}
MQTT配置类

与上同

MqttConnect创建MqttConnectOptions连接对象

与上同

MQTTSubsribe订阅端服务类: 实现初始化、收、发等基础功能

MQTTSubsribe.java

package com.openx.mqtt_subsribe.mqtt;

import com.openx.mqtt_subsribe.config.MQTTConfig;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class MQTTSubsribe {
    private static final Logger LOGGER = LoggerFactory.getLogger(MQTTSubsribe.class);

    @Autowired
    private MQTTConfig mqttConfig;

    @Autowired
    private MqttConnect mqttConnect;

    @Autowired
    private PushCallback pushCallback;

    @Autowired
    private MQTTSubsribe mqttSubsribe;

    @Autowired
    private MQTTSubsribe mqttServer;

    private MqttClient subsribeClient;
    private MqttClient publishClient;
    public MqttTopic topic;
    public MqttMessage message;

    public MQTTSubsribe() {
        LOGGER.info("8088上线了");
    }

    public MqttClient publishConnect() {
        try {
            if (publishClient == null) {
                publishClient = new MqttClient(mqttConfig.getHost(), mqttConfig.getClientid(), new MemoryPersistence());
            }
            MqttConnectOptions options = mqttConnect.getOptions();
            if (!publishClient.isConnected()) {
                publishClient.connect(options);
            } else {
                publishClient.disconnect();
                publishClient.connect(mqttConnect.getOptions(options));
            }
            LOGGER.info("-----回调-----客户端连接成功");
        } catch (MqttException e) {
            LOGGER.info(e.getMessage(), e);
        }
        return publishClient;
    }

    public void subsribeConnect() {
        try {
            if (subsribeClient == null) {
                subsribeClient = new MqttClient(mqttConfig.getHost(), mqttConfig.getClientid(), new MemoryPersistence());
                subsribeClient.setCallback(pushCallback);
            }
            MqttConnectOptions options = mqttConnect.getOptions();
            if (!subsribeClient.isConnected()) {
                subsribeClient.connect(options);
                mqttServer.subsribeConnect();
                subscribe("local_info", 0);
                log.info("----------local_info订阅成功");
            } else {
                subsribeClient.disconnect();
                subsribeClient.connect(mqttConnect.getOptions(options));
            }
//            LOGGER.info("----------客户端连接成功");
        } catch (MqttException e) {
            LOGGER.info(e.getMessage(), e);
        }
    }

    public void init(String topic, int qos) {
        subsribeConnect();
        subscribe(topic, qos);
    }

    public void unionInit(String topic) {
        subsribeConnect();
        unsuSubscribe(topic);
    }

    public void subscribe(String topic, int qos) {
        try {
            subsribeClient.subscribe(topic, qos);
        } catch (MqttException e) {
            LOGGER.info(e.getMessage(), e);
        }
    }

    public void unsuSubscribe(String topic) {
        try {
            subsribeClient.unsubscribe(topic);
        } catch (MqttException e) {
            LOGGER.info(e.getMessage(), e);
        }
    }

    public boolean publish(MqttTopic topic, MqttMessage message) {
        MqttDeliveryToken token = null;
        try {
            token = topic.publish(message);
            token.waitForCompletion();
            boolean flag = token.isComplete();
            StringBuffer sbf = new StringBuffer(200);
            sbf.append("给主题为'" + topic.getName());
            sbf.append("'发布消息:");
            if (flag) {
                sbf.append("成功!消息内容是:" + new String(message.getPayload()));
            } else {
                sbf.append("失败!");
            }
            LOGGER.info(sbf.toString());
        } catch (MqttException e) {
            LOGGER.info(e.toString());
        }
        if (token != null) {
            return token.isComplete();
        }
        return false;
    }

    public void sendMQTTMessage(String topic, String data, int qos) {
        try {
            this.publishClient = this.publishConnect();
            this.topic = this.publishClient.getTopic(topic);
            this.message = new MqttMessage();
            message.setQos(qos);
            message.setRetained(false);
            message.setPayload(data.getBytes());
            publish(this.topic, message);
        } catch (Exception e) {
            LOGGER.info(e.toString());
            e.printStackTrace();
        }
    }
}

PushCallback用于处理 MQTT 客户端的回调事件

可以订阅多个主题接收不同类型的消息, 当时时间紧迫且队里没有专门负责通信的队友, 所以我就采用单个主题接收不同类型的消息。
PushCallback.java

package com.openx.mqtt_subsribe.mqtt;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.openx.mqtt_subsribe.entity.*;
import com.openx.mqtt_subsribe.mapper.ChargeRecordMapper;
import com.openx.mqtt_subsribe.mapper.UserMapper;
import com.openx.mqtt_subsribe.service.MessageService;
import com.openx.mqtt_subsribe.service.SensorService;
import com.openx.mqtt_subsribe.service.TopicService;
import com.openx.mqtt_subsribe.service.UserService;
import com.openx.mqtt_subsribe.utils.UniqueUserNameGenerator;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.Timestamp;
import java.util.Map;

@Component
@Slf4j
public class PushCallback implements MqttCallback {
    @Autowired
    private TopicService topicService;
    @Autowired
    private MessageService messageService;
    @Autowired
    private MQTTSubsribe mqttServer;
    @Autowired
    private SensorService sensorService;
    @Autowired
    private UserService userService;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private ChargeRecordMapper chargeRecordMapper;

    public PushCallback() {
    }

    @Override
    public void connectionLost(Throwable cause) {
        log.info("---------------------连接断开,可以做重连");
        mqttServer.subsribeConnect();
//        mqttSubsribe.init("local_info", 0) ;
//        log.info("----------local_info订阅成功");
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
        // 消息发送完成时的回调方法
        System.out.println("deliveryComplete---------" + token.isComplete());
    }
 
    @Override
    public void messageArrived(String topic, MqttMessage message) throws Exception {
        // 消息到达时的回调方法
        String result = new String(message.getPayload(), "UTF-8");
        int qos = message.getQos();
//        if(!(result.contains("light")&&result.contains("temp1")&&result.contains("light2")&&result.contains("volt"))){
            System.out.println("接收消息主题 : " + topic);
            System.out.println("接收消息Qos : " + qos);
            System.out.println("接收消息内容 : " + result);
//        }

        // 处理接收到的主题
        Topic existingTopic = topicService.findByTopic(topic,qos);
        if(existingTopic == null) {
            Topic topic1 = new Topic();
            topic1.setTopic(topic);
            topic1.setQos(qos);
            Timestamp timestamp = new Timestamp(System.currentTimeMillis());
            topic1.setCreatedAt(timestamp);
            topic1.setUpdatedAt(timestamp);
            topicService.saveTopic(topic1);
        }
        // 处理接收到的用户信息
        if(result.contains("UserID")&&result.contains("UserName")&&result.contains("Password")){
            Map<String, Object> map=null;
            String UserName="",Password="",UserID="",C="";

            try {
                ObjectMapper objectMapper = new ObjectMapper();
                map = objectMapper.readValue(result, Map.class);
                UserID = (String) map.get("UserID");
                C = (String) map.get("C");
            } catch (Exception e) {
                e.printStackTrace();
            }
             UserName= UniqueUserNameGenerator.generateUserName();
             Password = "123456";
             User user = new User();
             user.setUserId(UserID);
             user.setUserName(UserName);
             user.setPassword(Password);
             log.info("userId: {}, userName: {}, password: {}, C: {}", UserID, UserName, Password);
             if(userService.findByUserId(String.valueOf(UserID))==null){
                 userService.register(user);
             }
        }
         // 处理接收到的用户信息
         if(result.contains("light")&&result.contains("temp1")&&result.contains("light2")||result.contains("volt")){
          ObjectMapper objectMapper = new ObjectMapper();
          Map<String, Object> map = objectMapper.readValue(result, Map.class);
             Sensor sensor = new Sensor();
             if(result.contains("light")&&result.contains("temp1")&&result.contains("temp")&&result.contains("temp2")&&result.contains("light2")){
                String light = (String) map.get("light");
                 String light2 = (String) map.get("light2");
                 String temp = (String) map.get("temp");
                 String temp1 = (String) map.get("temp1");
                 String temp2 = (String) map.get("temp2");
                 sensor.setLight(Integer.valueOf(light));
                 sensor.setLight2(Integer.valueOf(light2));
                 sensor.setTemp(Integer.valueOf(temp));
                 sensor.setTemp1(Integer.valueOf(temp1));
                 sensor.setTemp2(Integer.valueOf(temp2));
             }else{
                 String volt = (String) map.get("volt");
                 String volt1 = (String) map.get("volt1");
                 String volt2 = (String) map.get("volt2");
                 String C1 = (String) map.get("C1");
                 String C2 = (String) map.get("C2");
                 String C3 = (String) map.get("C3");
                 sensor.setVolt(Double.valueOf(volt));
                 sensor.setVolt1(Double.valueOf(volt1));
                 sensor.setVolt2(Double.valueOf(volt2));
                 sensor.setC1(Double.valueOf(C1));
                 sensor.setC2(Double.valueOf(C2));
                 sensor.setC3(Double.valueOf(C3));
             }
          sensorService.save(sensor);
        }
        /**
         * 接收用户充值信息
         */
        if(result.contains("C")&&result.contains("UserID")){
            ObjectMapper objectMapper = new ObjectMapper();
            Map<String, Object> map = objectMapper.readValue(result, Map.class);
            String UserID = (String) map.get("UserID");
            Double C = (Double) map.get("C");
            try {
//                userMapper.addC(UserID,C);
                ChargeRecord record = new ChargeRecord();
                record.setUserId(UserID);
                record.setAmount(C);
                record.setTimestamp(new Timestamp(System.currentTimeMillis()));
                chargeRecordMapper.insert(record);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        if(result.contains("UserID")&&result.contains("temp_warning")&&result.contains("C_warining")){
            Map<String, Object> map=null;
            String UserName="",Password="",UserID="";
            Integer tempWaring=0,CWaring=0;

            try {
                ObjectMapper objectMapper = new ObjectMapper();
                map = objectMapper.readValue(result, Map.class);
                UserID = (String) map.get("UserID");
                tempWaring = (Integer) map.get("temp_warning");
                CWaring = (Integer) map.get("C_warining");
            } catch (Exception e) {
                e.printStackTrace();
            }
            User user = userService.findByUserId(UserID);
//            user.setUserId("52211255109");
            user.setTempWaring(tempWaring);
            user.setCWaring(CWaring);
            log.info("userId: {}, userName: {}, password: {}, C: {}", UserID, UserName, Password);
            userMapper.updateById(user);
        }
        Message mqttMessage = new Message();
        mqttMessage.setTopicId(existingTopic.getId());
        mqttMessage.setMsg(result);
        mqttMessage.setQos(qos);
        messageService.saveMessage(mqttMessage);
    }
}

上、下位机通信测试

  • 上位机给下位机发送控制命令,下位机接收到此命令并执行相应的动作;
  • 上位机给下位机发送状态获取命令,下位机接收到此命令后调用传感器测量,然后转化为数字信息反馈给上位机;
  • 下位机主动发送状态信息或者报警信息给上位机。

image.png

image.png

三、完整项目介绍

声明

  • 项目所有功能点都已全部连通测试过,但由于本项目只单纯用于物联网应用创新设计大赛所以很多功能自参加比赛后便没有继续优化了,还处于demo阶段,仅供参考。
  • 通信这一块每个团队所采用的方案都不一致,建议根据实际情况修改
  • 由于当时不知道是需要做APP还是网页更合适,所以本作品全部采用响应式布局,Web和APP通用。

后端技术栈

  • SpringBoot
  • MyBatisPlus
  • Swagger
  • MQTT
  • MySQL(用于数据持久化)
数据库

完整SQL已全部导出,见网盘

接口文档

image.png

前端技术栈

  • uniapp
  • echarts(图表)
    官方文档:https://uniapp.dcloud.net.cn/
    插件市场:https://ext.dcloud.net.cn/
uni-app 的 API 请求
// common/api.js
// const BASE_URL = 'http://114.132.83.76:1666';
const BASE_URL = 'http://localhost:8088';
const request = (url, method, data) => {
  return new Promise((resolve, reject) => {
    uni.request({
      url: `${BASE_URL}${url}`,
      method: method,
      data: data,
      header: {
        'Content-Type': 'application/json'
      },
      success: (res) => {
        if (res.statusCode === 200) {
          resolve(res);
        } else {
          reject(res);
        }
      },
      fail: (err) => {
        reject(err);
      }
    });
  });
};
const uploadFile = (url, filePath, name) => {
  return new Promise((resolve, reject) => {
    uni.uploadFile({
      url: `${BASE_URL}${url}`,
      filePath: filePath,
      name: name,
      success: (uploadFileRes) => {
        if (uploadFileRes.statusCode === 200) {
          resolve(JSON.parse(uploadFileRes.data));
        } else {
          reject(uploadFileRes);
        }
      },
      fail: (error) => {
        reject(error);
      }
    });
  });
};
export const subscribeTopic = (data) => request('/SubsribeController/subscribe', 'POST', data);
export const unsubscribeTopic = (data) => request('/SubsribeController/unsubscribe', 'POST', data);
export const publishMessage = (data) => request('/publish', 'POST', data);
export const hello = () => request('/SubsribeController/hello', 'GET');
export const login = (data) => request('/user/login', 'POST', data);  // 登录
export const register = (data) => request('/user/register', 'POST', data);  // 注册
export const findByUserId = (userId) => request(`/user/find/${userId}`, 'GET');  // 查找
export const update = (data) => request('/user/update', 'PUT', data);  // 更新
export const sensorData = (data) => request('/sensor/getAll', 'GET', data); 
export const getAllUsers = () => request('/user/getAll', 'GET');//获取所有用户
export const getAllRecord = () => request('/user/getAllRecord', 'GET');//获取所有用户消费记录
export const getRecordByUserId = (userId) => request(`/user/getRecordByUserId/${userId}`, 'GET');//获取指定用户消费记录
// 新增的文件上传方法
export const upload = (filePath) => uploadFile('/file/upload', filePath, 'file');
页面配置
{
  "pages": [
	  {
	    "path": "pages/login/login",
	    "style": {
	      "navigationBarTitleText": "登录"
	    }
	  },
	{
	  "path": "pages/index/index",
	  "style": {
	    "navigationBarTitleText": "首页"
	  }
	},
	{
	    "path": "pages/register/register",
	    "style": {
	      "navigationBarTitleText": "注册"
	    }
	},
	{
	  "path": "pages/list/list",
	  "style": {
	  	"navigationBarTitleText": "功能"
	  	// "navigationBarBackgroundColor": "#404453",
	  	// "navigationBarTextStyle": "white"
		}
	},
	{
		"path": "pages/my/my",
		"style": {
			"navigationBarTitleText": "我的"
			// "enablePullDownRefresh": false
		}
	},
	{
		"path" : "pages/users/users",
		"style" : 
		{
			"navigationBarTitleText" : "用户信息"
		}
	},
	{
		"path" : "pages/moneyRecord/moneyRecord",
		"style" : 
		{
			"navigationBarTitleText" : "用户消费记录"
		}
	},
	{
		"path" : "pages/uploadFile/uploadFile",
		"style" : 
		{
			"navigationBarTitleText" : "上传文件"
		}
	},
	{
		"path" : "pages/map/map",
		"style" : 
		{
			"navigationBarTitleText" : ""
		}
	},
	{
		"path" : "pages/camera/camera",
		"style" : 
		{
			"navigationBarTitleText" : ""
		}
	},
	{
		"path" : "pages/camera1/camera1",
		"style" : 
		{
			"navigationBarTitleText" : ""
		}
	}
],
  "globalStyle": {
    "navigationBarTextStyle": "black",
    "navigationBarTitleText": "简洁又美观的登录页面",
    "navigationBarBackgroundColor": "#F8F8F8",
    "backgroundColor": "#F8F8F8"
  },
  "globalStyle": {
  		"navigationBarTextStyle": "black",
  		"navigationBarTitleText": "uni系列课程",
  		"navigationBarBackgroundColor": "#F7F7F7",
  		"backgroundColor": "#F8F8F8"
  	},
  	"uniIdRouter": {},
  	"tabBar": {
  		"color": "#cdcdcd",
  		"selectedColor": "#2c2c2c",
  		"borderStyle": "black",
  		"backgroundColor": "#fff",
  		"list": [
  			{
  				"text": "首页",
  				"pagePath": "pages/index/index",
  				"iconPath": "static/bottomBar/首页1.png",
  				"selectedIconPath": "static/bottomBar/首页.png"
  			},
  			{
  				"text": "功能",
  				"pagePath": "pages/list/list",
  				"iconPath": "static/bottomBar/功能1.png",
  				"selectedIconPath": "static/bottomBar/功能.png"
  			},
			{
				"text": "我的",
				"pagePath": "pages/my/my",
				"iconPath": "static/bottomBar/我的1.png",
				"selectedIconPath": "static/bottomBar/我的.png"
			}
  		]
  	}
}

四、实际页面

image.png
image.png

image.png

image.png
image.png
image.png
image.png
image.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值