【golang实现mqtt处理数据】

背景

硬件设备需要将数据传输到服务器

设计思路

使用goalng接收mqtt的数据

1、启动时 循环订阅
2、启动一个定时任务,检查数据库是否有新设备添加,如果有新设备,则添加该设备的订阅主题,如果有删除,则删除该设备的订阅猪蹄😂
3、将接收来的数据处理保存到数据库

上代码

package main

import (
	"database/sql"
	"fmt"
	mqtt "github.com/eclipse/paho.mqtt.golang"
	_ "github.com/go-sql-driver/mysql"
	"log"
	"time"
)

// 数据库连接配置
const (
	DBHost     = "localhost"
	DBPort     = 3306
	DBUser     = "smart_home_sxclk"
	DBPassword = "HhdDJhsbkakYejXx"
	DBName     = "smart_home_sxclk"
)

var messagePubHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
	fmt.Printf("已收到消息: %s 来自主题: %s\n", msg.Payload(), msg.Topic())
}

var connectHandler mqtt.OnConnectHandler = func(client mqtt.Client) {
	fmt.Println("已连接")
}

var connectLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) {
	fmt.Printf("连接丢失: %v", err)
}

// Device 设备
type Device struct {
	id      int    // 编号
	name    string // 设备名称
	pub_h   string // 发布主题头部
	sub_h   string // 订阅主题头部
	model_d string // 设备型号(用于订阅的中间值)
	code    string // 设备编号(序列号)
}

func main() {

	// MQTT服务器配置
	var broker = "192.168.1.123"
	var port = 1883

	opts := mqtt.NewClientOptions()
	opts.AddBroker(fmt.Sprintf("tcp://%s:%d", broker, port))
	opts.SetKeepAlive(20 * time.Second)
	opts.SetClientID("go_mqtt_client")
	opts.SetUsername("qing")
	opts.SetPassword("qs123456")
	opts.SetDefaultPublishHandler(messagePubHandler)
	opts.OnConnect = connectHandler
	opts.OnConnectionLost = connectLostHandler
	client := mqtt.NewClient(opts)
	if token := client.Connect(); token.Wait() && token.Error() != nil {
		panic(token.Error())
	}

	// 数据库配置
	// 构建DSN(Data Source Name)
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", DBUser, DBPassword, DBHost, DBPort, DBName)

	// 创建数据库连接
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		log.Fatal(err)
		return
	}
	defer db.Close() //3.一定要关闭rows  rows.scan里 没有 自带关闭

	// 检查连接是否成功
	err = db.Ping()
	if err != nil {
		log.Fatal(err)
	} else {
		fmt.Println("已成功连接到数据库!")
	}

	// 查询设备列表
	sql := "SELECT id,name,pub_h,sub_h,model_d,code FROM sw_r_device WHERE deletetime is null"
	cc := searchDevicelist(db, sql)

	var oldSerialList []string
	for _, v := range cc {
		//fmt.Println(v)

		sub_ := v["sub_h"].(string) + "/" + v["code"].(string)
		//判断newSerialList中是否存在sub_title,如果存在则跳过,否则执行订阅操作
		if !contains(oldSerialList, sub_) {
			oldSerialList = append(oldSerialList, sub_)
			sub(client, sub_)
		}
	}
	//fmt.Println(oldSerialList)

	ticker := time.NewTicker(10 * time.Second)
	defer ticker.Stop()

	for range ticker.C {
		fmt.Println("定时检测新设备:", time.Now())
		// 这里需要对比新数组newSerialList和旧数组oldSerialList的差集,
		// 如果新数组和旧数组的差集不为空,则说明有新设备上线,需要订阅新设备
		var newSerialList []string
		//查询数据库
		newcc := searchDevicelist(db, sql)
		for _, v := range newcc {
			sub_title := v["sub_h"].(string) + "/" + v["code"].(string)
			//判断newSerialList中是否存在sub_title,如果存在则跳过,否则执行订阅操作
			if !contains(newSerialList, v["sub_h"].(string)+"/"+v["code"].(string)) {
				newSerialList = append(newSerialList, sub_title)
			}
		}
		//fmt.Println(newSerialList)
		if len(newSerialList) != len(oldSerialList) {
			// 新旧差集
			diff := difference(newSerialList, oldSerialList)
			fmt.Println("新旧差集:", diff)
			// 遍历差集,执行订阅操作
			for _, v := range diff {
				sub(client, v)
				fmt.Println("订阅新设备:", v)
			}

			// 旧新差集
			diff2 := difference(oldSerialList, newSerialList)
			fmt.Println("旧新差集:", diff2)
			for _, v := range diff2 {
				unsub(client, v)
				fmt.Println("取消订阅旧设备:", v)
			}

			// 更新旧数组
			oldSerialList = newSerialList
		}
	}

	// 发送心跳
	go func() {
		t := time.NewTicker(30 * time.Second)
		defer t.Stop()

		for range t.C {
			if token := client.Publish("heartbeat", 0, false, "ping"); token.Wait() && token.Error() != nil {
				fmt.Println(token.Error())
				client.Disconnect(0)
				return
			}
		}
	}()

	 阻塞主goroutine,以保持客户端连接
	//select {}

	//publish(client)

	//client.Disconnect(250)
	// 长连接保持
	for {
		time.Sleep(10 * time.Second)
	}

}

func contains(list []string, s string) bool {
	for _, a := range list {
		if a == s {
			return true
		}
	}
	return false
}

// 求并集
func union(slice1, slice2 []string) []string {
	m := make(map[string]int)
	for _, v := range slice1 {
		m[v]++
	}

	for _, v := range slice2 {
		times, _ := m[v]
		if times == 0 {
			slice1 = append(slice1, v)
		}
	}
	return slice1
}

// 求交集
func intersect(slice1, slice2 []string) []string {
	m := make(map[string]int)
	nn := make([]string, 0)
	for _, v := range slice1 {
		m[v]++
	}

	for _, v := range slice2 {
		times, _ := m[v]
		if times == 1 {
			nn = append(nn, v)
		}
	}
	return nn
}

// 求差集 slice1-并集
func difference(slice1, slice2 []string) []string {
	m := make(map[string]int)
	nn := make([]string, 0)
	inter := intersect(slice1, slice2)
	for _, v := range inter {
		m[v]++
	}

	for _, value := range slice1 {
		times, _ := m[value]
		if times == 0 {
			nn = append(nn, value)
		}
	}
	return nn
}

// 订阅
func sub(client mqtt.Client, sub_title string) {
	topic := sub_title
	token := client.Subscribe(topic, 1, nil)
	token.Wait()
	fmt.Printf("订阅主题: %s \r\n", topic)
}

// 取消订阅
func unsub(client mqtt.Client, unsub_title string) {
	topic := unsub_title
	token := client.Unsubscribe(topic)
	token.Wait()
	fmt.Printf("取消---订阅主题: %s \r\n", topic)
}

// 发布消息
func publish(client mqtt.Client) {
	num := 10
	for i := 0; i < num; i++ {
		text := fmt.Sprintf("Message %d", i)
		topic := "topic/test/00000000001"
		token := client.Publish(topic, 0, false, text)
		token.Wait()
		time.Sleep(time.Second)
	}
}

// 采用切片类型的结构体接受查询数据库信息返回的参数
func searchDevicelist(db *sql.DB, sSql string) []map[string]interface{} {
	// 准备查询语句
	stmt, err := db.Prepare(sSql)
	if err != nil {
		log.Println(err)
		return nil
	}
	defer stmt.Close()

	// 查询
	rows, err := stmt.Query()
	if err != nil {
		log.Println(err)
		return nil
	}
	defer rows.Close()
	// 数据列
	columns, err := rows.Columns()
	if err != nil {
		log.Println(err)
		return nil
	}

	// 列的个数
	count := len(columns)

	// 返回值 Map切片
	mData := make([]map[string]interface{}, 0)
	// 一条数据的各列的值(需要指定长度为列的个数,以便获取地址)
	values := make([]interface{}, count)
	// 一条数据的各列的值的地址
	valPointers := make([]interface{}, count)
	for rows.Next() {

		// 获取各列的值的地址
		for i := 0; i < count; i++ {
			valPointers[i] = &values[i]
		}

		// 获取各列的值,放到对应的地址中
		rows.Scan(valPointers...)
		//fmt.Println(valPointers)
		//fmt.Println(values)
		// 一条数据的Map (列名和值的键值对)
		entry := make(map[string]interface{})

		// Map 赋值
		for i, col := range columns {
			var v interface{}

			// 值复制给val(所以Scan时指定的地址可重复使用)
			val := values[i]
			//fmt.Println(val)
			b, ok := val.([]byte)
			if ok {
				// 字符切片转为字符串
				v = string(b)
			} else {
				v = val
			}
			entry[col] = v
			//fmt.Println(entry)
		}

		mData = append(mData, entry)
	}

	return mData
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

炼气三千年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值