背景
硬件设备需要将数据传输到服务器
设计思路
使用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
}