基于MQTT通信开发的失物招领小程序

news2024/11/17 17:40:39

项目架构设计

这个项目采用前后端分离的方式,重新设计了两条链路来支撑程序的信息获取和传递

  1. 前端的小程序页面再启动页面渲染时,直接通过DBAPI从后端数据库获取信息,直接渲染在小程序中
  2. 项目中给DBAPI的定位是快速从后端获取信息,但是无法修改数据库,DBAPI和数据库同时及都部署在后端服务器,可以实现信息的快速交互,其中没有网络通信也保证信息安全
  3. 当小程序用户发布失物,或者是提交寻得失物,删除物品或者是用户注册等需要修改数据库的场景时,他们的信息会通过MQTT通信的方式经过小程序专用的socket连接发送给服务器
  4. 然后MQTT服务器会收到这些消息,然后分发给订阅了对应主题消息的客户端
  5. 我们开发的客户端订阅了所有小程序的消息,这个客户端会基于小程序发送的消息主题,选择对应函数,然后基于消息内容进一步去操作后端数据库,实现数据库的存储和修改

数据库设计

设计了以下两个表来存储项目的业务数据

失物表存储了丢失物品相关的信息:失物ID-id;丢失用户名-username;当前状态-type;丢失地点-area;照片-photo

用户表存储了用户相关的数据:学生ID-studentid;用户名-username;手机号-phonenumber

MQTT通信客户端

MQTT服务器所谓一个独立的中枢服务器,依赖于EMQX部署。而基于MQTT通信和数据库做交互,需要编写一个新的通信客户端。

客户端基于配置文件config.json运行,需先填写相关配置信息,客户端会读取并运行

	{  
	    "database server":{  
	        "host":"",  
	        "port":3306,  
	        "user":"",  
	        "password":"",  
	        "database":""  
	    },  
	    "mqtt server":{  
	        "host":"",  
	        "port":1883,  
	        "other_topic":[  
	              
	        ]  
	    }  
	}  

客户端要具备连接MQTT服务器和操作数据库的功能,为此做了以下设计:

1. 程序会识别系统和输出当前平台的运行状况,然后尝试连接到MySQL数据库。如果连接失败,程序会抛出错误并停止运行。

2. 然后,程序创建了一个MQTT客户端,尝试连接到MQTT代理服务器。如果连接失败,程序会记录错误并停止运行。

3. 定义了一个消息处理函数,该函数会在接收到MQTT消息时被调用。这个函数根据消息的主题进行不同的处理,包括插入数据到数据库、更新数据库中的数据、打印日志等。

4. 客户端程序将订阅一些MQTT主题,包括"lost"、"find"、"signup"、"delete"、"exit"、"error"。当这些主题有新的消息时,消息处理函数会被调用。这里设计了高并发处理,当由多个消息时会自动生成子线程去处理,同时对数据库的操作加锁,防止重复操作数据库

5. 当接程序收到中断信号时,程序会断开MQTT连接并退出。

6. 程序带有详细的日志功能,除了在终端会实时输出信息之外还会保存日志文件,方便后续追踪错误

7. 通用性也是我们开发这个客户端的目标,程序交叉编译后的程序可以运行在不同的操作系统和处理器架构上,利于部署到物联网设备

处理消息和SQL语句相关代码

import (
	"database/sql"
	"log"
	"strconv"
	"strings"

	mqtt "github.com/eclipse/paho.mqtt.golang"
)

// 处理 "lost" 主题的消息
func handleLostTopic(payload string, db *sql.DB) {
	parts := strings.Split(payload, ",")
	if len(parts) == 4 {
		user := parts[0]
		datatype := "lost"
		name := parts[1]
		area := parts[2]
		photo := parts[3]

		dbMutex.Lock() // 加锁
		// 往数据库中存储失物信息
		_, err := db.Exec(
			"INSERT INTO sutff (username, type, name, area, photo) VALUES (?, ?, ?, ?, ?)",
			user,
			datatype,
			name,
			area,
			photo,
		)
		dbMutex.Unlock() // 解锁
		if err != nil {
			log.Fatal(err)
		}
		log.Printf(
			"Lost item add to database {User: %s, Name: %s, Area: %s}\n",
			user,
			name,
			area,
		)
	}
}

// 处理 "delete" 主题的消息
func handleDeleteTopic(payload string, db *sql.DB) {
    id, err := strconv.Atoi(payload)
    if err != nil {
        log.Fatal(err)
    }

    // 删除数据库中指定物品
    dbMutex.Lock()
    _, err = db.Exec(
        "DELETE FROM sutff WHERE id = ?", 
        id,
    )
    dbMutex.Unlock()
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("Lost item with ID:%d has been deleted\n", id)
}

// 处理 "find" 主题的消息
func handleFindTopic(payload string, db *sql.DB) {
	id, err := strconv.Atoi(payload)
	if err != nil {
		log.Fatal(err)
	}

	// 更新数据库中的数据
	dbMutex.Lock()
	_, err = db.Exec(
		"UPDATE sutff SET type = ? WHERE id = ?",
		"find",
		id,
	)
	dbMutex.Unlock()
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("Lost item with ID:%d has been marked as found\n", id)
}

// 处理 "signup" 主题的消息
func handleSignupTopic(payload string, db *sql.DB) {
    parts := strings.Split(payload, ",")
    if len(parts) == 3 {
        studentid := parts[0]
        username := parts[1]
        phonenumber := parts[2]
        dbMutex.Lock()

        // 查询数据库中是否已经存在具有相同 studentid 的用户
        var existingUser string
        err := db.QueryRow("SELECT studentid FROM user WHERE studentid = ?", studentid).Scan(&existingUser)

        // 如果查询结果返回了一个或多个记录,那么我们就不需要再插入新的用户
        if err == nil {
            dbMutex.Unlock()
            log.Printf("User with studentid %s already exists\n", studentid)
            return
        }

        // 往数据库中存储新用户信息
        _, err = db.Exec(
            "INSERT INTO user (studentid, username, phonenumber) VALUES (?, ?, ?)",
            studentid,
            username,
            phonenumber,
        )
        dbMutex.Unlock()
        if err != nil {
            log.Fatal(err)
        }
        log.Printf(
            "New user add to database {User: %s, Name: %s, Phone: %s}\n",
            studentid,
            username,
            phonenumber,
        )
    }
}

// 处理接收到的消息
func handleMessage(client mqtt.Client, msg mqtt.Message, db *sql.DB) {
	getmsg := string(msg.Payload())
	if msg.Topic() == "lost" {
		lastComma := strings.LastIndex(getmsg, ",")
		if lastComma != -1 {
			getmsg = getmsg[:lastComma] + ",BASE64(photo)"
		}
	}
	log.Printf("Recevie topic[%s]  message: %s\n", msg.Topic(), getmsg)
	payload := string(msg.Payload())

	// 根据主题处理消息
	switch msg.Topic() {
	case "lost":
		handleLostTopic(payload, db)
	case "delete":
		handleDeleteTopic(payload, db)
	case "find":
		handleFindTopic(payload, db)
	case "signup":
		handleSignupTopic(payload, db)
	case "exit":
		log.Println("remot-eclient log out safely.")
	case "error":
		log.Println("remot-eclient lost connection, please try again later.")
	}
	// 可以基于此位置进一步开发,处理更多的主题
}

构建MQTT通信相关代码

import (
	"database/sql"
	"strconv"
	"strings"
	"sync"
	"time"

	"log"

	mqtt "github.com/eclipse/paho.mqtt.golang"
)

// 项目必须的主题
var extraTopics = []string{
	"lost",
	"delete",
	"find",
	"signup",
	"exit",
	"error",
}


// 创建MQTT客户端
func createMqttClient(config Config) mqtt.Client {
	opts := mqtt.NewClientOptions()
	broker := strings.Join(
		[]string{
			config.MqttServer.Host,
			strconv.Itoa(config.MqttServer.Port),
		},
		":",
	)
	opts.AddBroker(broker)
	timestamp := time.Now().Unix()
	clientID := strings.Join(
		[]string{
			"receiveclient_",
			strconv.FormatInt(timestamp, 10),
		},
		"",
	)
	opts.SetClientID(clientID)
	client := mqtt.NewClient(opts)
	log.Printf("Created MQTT client with ID: %s\n", clientID)
	// 建立连接
	token := client.Connect()
	if token.Wait() && token.Error() != nil {
		log.Fatal(token.Error())
	}
	// 打印连接到服务器的 IP 和端口
	log.Printf("Connected to MQTT broker at: %s\n", broker)
	return client
}

// 订阅主题
func subscribeTopics(client mqtt.Client, config Config, db *sql.DB) {
	messageHandler := func(client mqtt.Client, msg mqtt.Message) {
		// 启动一个新的goroutine来处理这个消息
		go handleMessage(client, msg, db)
	}
	// 创建一个新的切片,包含config.MqttServer.Topic和extraTopics的所有元素
	allTopics := append([]string{}, config.MqttServer.Topic...)
	allTopics = append(allTopics, extraTopics...)

	var wg sync.WaitGroup
	for _, topic := range allTopics {
		wg.Add(1)
		go func(t string) {
			defer wg.Done()
			token := client.Subscribe(t, 0, messageHandler)
			if token.Wait() && token.Error() != nil {
				log.Fatal(token.Error())
			}
			log.Printf("Subscribe: %s\n", t)
		}(topic)
	}
	wg.Wait()
}

失物招领小程序

总共为这个失物招领小程序设计了五个页面:

主页:

是小程序的入口,也是所有功能页面的入口

用户页:

显示用户信息,所有用户都需要在这个页面先登录,校验用户名,学号和手机号。如果没有账号的用户也可以在这里注册登录,登录这里采用了DBAPI来校验数据库的信息,直接在同一个节点做数据库查找和比对,效率高

丢失页:

用户可以在这个页面上传和管理他们的失物,这里只有检测到用户登录了之后才会从后端获取该用户的失物记录,页面启动时渲染的数据来自DBAPI的直接获取,若用户上传失物,失物信息通过MQTT客户端传递,在MQTT服务器接收后MQTT客户端才能收到,然后客户端进一步操作修改数据库,保证数据安全。

寻得页:

找到了失物的用户可以在这个页面更新信息,这里和丢失页面一样,初始的数据渲染来自后端DBAPI的直接调,而当用户寻得物品,他的的消息通过MQTT服务器传递,由我们开发的MQTT客户端接收后,进一步操作修改数据库信息,保证信息安全

总结页:

程序运行历史记录总结,信息直接用DBAPI获取

项目运行

小程序直接从源码运行依赖于微信小程序的开发这工具,直接把wxapp文件夹导入开发者工具即可运行。MQTT客户端已经编译好了exe版本,亦可以交叉编译其他的版本,相关命令:

	go mod init client  
	go mod tidy  
	GOOS={$YOUR_SYSTEM} GOARCH={$YOUR_CPU} go build -o {$EXE_FILE_NAME} -ldflags '-w -s' ./*.go  
	./{$EXE_FILE_NAME}  

GitHub地址

JJLi0427/MQTT_LostFind_WXAPP: Bulid MQTT connection with all arch plantform through our client. Build a lost and found weapp for BJTU use this client. (github.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1642852.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

信息系统项目管理师0087:组织系统(6项目管理概论—6.2项目基本要素—6.2.6组织系统)

点击查看专栏目录 文章目录 6.2.6组织系统1.治理框架2.管理要素3.组织结构类型6.2.6组织系统 项目运行时会受到项目所在的组织结构和治理框架的影响与制约。为有效且高效地开展项目,项目经理需要了解组织内的组织机构及职责分配情况,帮助自己有效地利用其权力、影响力、能力、…

5分钟速通大语言模型(LLM)的发展与基础知识

✍️ 作者:哈哥撩编程(视频号同名) 博客专家全国博客之星第四名超级个体COC上海社区主理人特约讲师谷歌亚马逊演讲嘉宾科技博主极星会首批签约作者 🏆 推荐专栏: 🏅 程序员:职场关键角色通识宝…

【编程题-错题集】chika 和蜜柑(排序 / topK)

牛客对于题目链接&#xff1a;chika和蜜柑 (nowcoder.com) 一、分析题目 排序 &#xff1a;将每个橘⼦按照甜度由高到低排序&#xff0c;相同甜度的橘子按照酸度由低到高排序&#xff0c; 然后提取排序后的前 k 个橘子就好了。 二、代码 1、看题解之前AC的代码 #include <…

ttkbootstrap界面美化系列之PanedWindow(七)

在界面设计中经常用PanedWindow控件来对整个界面进行切割布局&#xff0c;让整个界面看上去有层次感&#xff0c;不至于说杂乱无章。在我之前的文章中有对tkinter的该控件做了详细的介绍&#xff0c;链接如下基于Tkinter的PanedWindow组件进行窗口布局-CSDN博客 本文主要是介绍…

Python-VBA函数之旅-oct函数

目录 一、oct函数的常见应用场景 二、oct函数使用注意事项 三、如何用好oct函数&#xff1f; 1、oct函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a;神奇夜光杯-CSDN博客 一、oct函数的常见应用场景 oc…

批量抓取某电影网站的下载链接

思路&#xff1a; 进入电影天堂首页&#xff0c;提取到主页面中的每一个电影的背后的那个urL地址 a. 拿到“2024必看热片”那一块的HTML代码 b. 从刚才拿到的HTML代码中提取到href的值访问子页面&#xff0c;提取到电影的名称以及下载地址 a. 拿到子页面的页面源代码 b. 数据提…

Linux理解文件操作 文件描述符fd 理解重定向 dup2 缓冲区 C语言实现自己的shell

文章目录 前言一、文件相关概念与操作1.1 open()1.2 close()1.3 write()1.4 read()1.4 写入的时候先清空文件内容再写入1.5 追加&#xff08;a && a&#xff09; 二、文件描述符2.1 文件描述符 fd 0 1 2 的理解2.2 FILE结构体&#xff1a;的源代码 三、深入理解文件描述…

Vue 开发中的一些问题简单记录,Cannot find module ‘webpack/lib/RuleSet‘

您好&#xff0c;我是码农飞哥&#xff08;wei158556&#xff09;&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精…

【C语言】项目实践-贪吃蛇小游戏(Windows环境的控制台下)

一.游戏要实现基本的功能&#xff1a; • 贪吃蛇地图绘制 • 蛇吃食物的功能 &#xff08;上、下、左、右方向键控制蛇的动作&#xff09; • 蛇撞墙死亡 • 蛇撞自身死亡 • 计算得分 • 蛇身加速、减速 • 暂停游戏 二.技术要点 C语言函数、枚举、结构体、动态内存管…

【Java探索之旅】内部类 静态、实例、局部、匿名内部类全面解析

文章目录 &#x1f4d1;前言一、内部类1.1 概念1.2 静态内部类1.3 实例内部类1.4 局部内部类1.5 匿名内部类 &#x1f324;️全篇总结 &#x1f4d1;前言 在Java编程中&#xff0c;内部类是一种强大的特性&#xff0c;允许在一个类的内部定义另一个类&#xff0c;从而实现更好的…

Rust web简单实战

一、使用async搭建简单的web服务 1、修改cargo.toml文件添加依赖 [dependencies] futures "0.3" tokio { version "1", features ["full"] } [dependencies.async-std] version "1.6" features ["attributes"]2、搭…

网络攻击(Cyber Attacks)

目录 1.概念 2.分类 3.总结 1.概念 网络攻击&#xff08;Cyber Attacks&#xff0c;也称赛博攻击&#xff09;是指针对计算机信息系统、基础设施、计算机网络或个人计算机设备的&#xff0c;任何类型的进攻动作。对于计算机和计算机网络来说&#xff0c;破坏、揭露、修改、使…

【C++】STL — List的接口讲解 +详细模拟实现

前言&#xff1a; 本章我们将学习STL中另一个重要的类模板list… list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。list的底层是带头双向循环链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xf…

从零到屎山系列-游戏开发(Day2)

简介 这次就来一个比较简单的小游戏贪吃蛇 贪吃蛇 游戏规则就是一串珠子不断的移动&#xff0c;碰到场景里面的食物变长一点&#xff0c;碰到墙壁游戏结束。 开始动手 设计绘制设备 首先我计划从一个控制台游戏开始&#xff0c;需要一个控制台下的绘图机制&#xff0c;希…

基于Sen+MK的多站点不同季节和年尺度的SPEI趋势分析.md

再大的风浪&#xff0c;不过只短暂喧哗。 文章目录 前言1. 概述2.1 问题情景2.2 说明 2. 版本2.1 天津&#xff0c;2024年5月4日&#xff0c;Version1 3. 微信公众号GISRSGeography 一、数据1. 输入数据2. 输出数据 二、程序代码三、参考资料 前言 1. 概述 2.1 问题情景 假…

java-Spring-mvc-(请求和响应)

目录 &#x1f4cc;HTTP协议 超文本传输协议 请求 Request 响应 Response &#x1f3a8;请求方法 GET请求 POST请求 &#x1f4cc;HTTP协议 超文本传输协议 HTTP协议是浏览器与服务器通讯的应用层协议&#xff0c;规定了浏览器与服务器之间的交互规则以及交互数据的格式…

thinkphp6 workerman无法使用框架Db/model等类库方法解决方案

thinkphp6 workerman无法使用框架Db/model相关操作解决 执行安装相关扩展 composer require webman/gateway-worker引入成功后编辑服务类文件,直接展示代码 <?phpnamespace app\server\controller;use GatewayWorker\BusinessWorker; use GatewayWorker\Gateway; use Gate…

java异常.day30(Error,Exception)

Error和Exception说明 Error Error类及其子类表示的是Java虚拟机&#xff08;JVM&#xff09;无法或不应该尝试恢复的严重问题。这些问题通常是由JVM本身的问题、系统资源耗尽、或其他不可控的环境因素引起的。由于Error是不可恢复的&#xff0c;因此应用程序不应该尝试捕获和…

Cisco WLC 2504控制器重启后所有AP掉线故障-系统日期时间

1 故障描述 现场1台WLC 2504控制器掉电重启后&#xff0c;所有AP均无线上线&#xff0c; 正常时共有18个AP在线&#xff0c;而当前为0 AP在线数量为0 (Cisco Controller) >show ap sumNumber of APs.................................... 0Global AP User Name..........…

细胞自动机与森林火灾与燃烧模拟

基于 元胞自动机-森林火灾模拟_vonneumann邻域-CSDN博客 进行略微修改&#xff0c;解决固定方向着火问题&#xff0c;用了一个meshv2数组记录下一状态&#xff0c;避免旧状态重叠数据失效。 参数调整 澳洲森林火灾蔓延数学建模&#xff0c;基于元胞自动机模拟多模式下火灾蔓延…