基于MQTT的多隧道管理系统省中心端API实现

项目背景

现有的隧道管理软件未实现中心化,即不同的隧道采用独立的隧道管理软件,省中心无法实时监测管理各隧道,随即需要开发中心服务器实现对各个隧道的管理。隧道内的设备种类较多,但可分为监视类、监控类。监视类设备信息传递为单向的,即设备状态单向传递给服务器,监控类设备信息传递则为双向的,即设备状态传递给服务器的同时,还需要能够远程对设备进行控制。为了高效的实现中心服务器对隧道的远程监控,拟采用MQTT协议实现隧道到省中心的消息传递。

基本功能

在这里插入图片描述

基本要求

(1)secretKey和secretData的隧道到省中心的鉴权;
(2)省中心向隧道的设备列表请求(包含状态);
(3)省中心向隧道设备下达控制指令;
(4)省中心向隧道服务器的日报、月报请求;
(5)面向省中心隧道管理软件的graohQL协议。

鉴权和加密实现方式

在这里插入图片描述

主代码实现

在这里插入图片描述

Branch_name 该文件存放所有分公司的公钥
CAPHCHA.txt 该文件存省中心校验码
encrpty 该文件夹内 encrpty.go为RSA加密代码实现
mqtt 为主功能函数实现文件
publickey_center 省中心公钥
secretkey_center 省中心秘钥

主功能实现
//mqtt.go

package mqtt

import (
	"fmt"
	"log"
	en "mqtt_B/encrpty"
	"os"
	"strconv"
	"strings"
	"time"

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


//省中心的RSA加密解密文件名
type RSA struct{
	publickey_Center string	//省中心公钥
	secretkey_Center string //省中心私钥
}

//默认加密钥文件名称
func NewDefaultRSA() RSA {
	return RSA{
		publickey_Center : "publickey_Center",
		secretkey_Center : "secretkey_Center",
	}
}

func NewRSA(p_C,s_C string) RSA {
	return RSA{
		publickey_Center  : p_C,
		secretkey_Center  : s_C,
	}
}

type Message struct {
	Name string //分公司名
	id string	//设备id
	CAPTCHA string	//省中心校验码
	body string		//命令主体
}

//默认省中心校验码
func (m *Message) SetCAPTCHA() bool {
	file, err := os.Open("CAPTCHA.txt")
	if err != nil {
		log.Println(err)
		return false
	}
	defer file.Close()
	//获取文件内容
	info, _ := file.Stat()
	buf := make([]byte, info.Size())
	file.Read(buf)
	m.CAPTCHA = string(buf)
	return true
}

//时间差有有效性判断
func CheckTime(t_start, t_end int64) bool{
	if t_end - t_start < 10 {
		return true
	}
	return false
}

//组装要发送的消息内容
func Message_assembly(msg Message,rs RSA) string{

	//Name_id_CAPTCHA_timestamp_body

	//先放分公司名称
	s := msg.Name
	//再将设备id放在最前
	s = s + "_" + msg.id

	//用分中心的公钥对省中心验证码签名
	esc := "Branch_name/" + msg.Name
	m_CAPTCHA := string(en.RSA_Encrypt([]byte(msg.CAPTCHA),esc))
	//m_CAPTCHA := string(en.RSA_Encrypt([]byte(msg.CAPTCHA),rs.publickey_Filiale))

	//对消息主体用分中心公钥进行签名
	m_body := string(en.RSA_Encrypt([]byte(msg.body),esc))
	//m_body := string(en.RSA_Encrypt([]byte(msg.body),rs.publickey_Filiale))

	//获取当前时间戳
	ti := time.Now().Unix()
	//用省中心公钥对时间戳签名
	m_timestamp := string(en.RSA_Encrypt([]byte(string(ti)),rs.publickey_Center))

	//按照 id_CAPTCHA_timestamp_body 顺序进行组合

	s = s + "_" + m_CAPTCHA + "_" + m_timestamp + "_" + m_body

	return s
}

//解析收到的回复
func Message_cracker(msg []byte,rs RSA) (string,bool) {

	t_now := time.Now().Unix()
	//回复回来的消息内容分五部分 branchName_id_timestamp_(true/false)_body
	//body为获取到的数据,以二进制的形式输入文本文件,提供给省中心使用
	var s []string
	s = strings.Split(string(msg),"_")

	if len(s) != 5 {
		log.Println("Now instead of three messages on %s's machine %s, some messages are lost or duplicated",s[0],s[1])
		return "",false
	}

	timestamp := en.RSA_Decrypt([]byte(s[1]),rs.secretkey_Center)

	timetmp, err := strconv.Atoi(string(timestamp))

	if err != nil{
		log.Println("machine %s timestamp is err",s[0])
		return s[0],false
	}

	if CheckTime(int64(timetmp),t_now) != true {
		log.Println("machine: %s time is out",s[0])
		return s[0],false
	}

	if s[2] == "false" {
		log.Println("machine: %s Task is err",s[0])
		return s[0],false
	}

	//创建文件进行存储获取到的数据信息 文件名为设备id+时间(年月日)
	filename := s[0]
	bodyFile,err := os.Create(filename)
	if err != nil {
		log.Println(err)
	}
	defer bodyFile.Close()
	bodyFile.Seek(0,2)
	bodyFile.WriteString(s[3])

	return s[0],true
 }
 
//启动一个执行任务
func Task(name, id string,choice int,rsa RSA,ch chan bool) {

	topic_s := id + "_" + "Sub"
	topic_p := "Machine_" + id

	var f MQTT.MessageHandler = func(client MQTT.Client,msg MQTT.Message){
		fmt.Printf("It's default topic: %s $$ Msg: %s\n",msg.Topic(),msg.Payload())
	}
	//配置MQTT基本配置
	opts := MQTT.NewClientOptions().AddBroker("tcp://mqtt.eclipse.org:1883")
	//遗嘱:topic名称,消息,Qos,LWT是否保持
	opts.SetWill("last_corner", "Program is err,please start again", 1, true)
	//对接收的消息处理默认模块
	opts.SetDefaultPublishHandler(f)
	s := MQTT.NewClient(opts)

	//配置MQTT基本配置
	optp := MQTT.NewClientOptions().AddBroker("tcp://mqtt.eclipse.org:1883")
	//遗嘱:topic名称,消息,Qos,LWT是否保持
	optp.SetWill("last_corner", "Program is err,please start again", 1, true)
	//对接收的消息处理默认模块
	optp.SetDefaultPublishHandler(f)
	p := MQTT.NewClient(optp)

	if token := s.Connect(); token.Wait() && token.Error() != nil {
		log.Printf("Error on Client.Connect(): %v", token.Error())
	}

	var f_s MQTT.MessageHandler = func(client MQTT.Client, message MQTT.Message) {
		Task_id,Task_result := Message_cracker(message.Payload(),rsa)
		if Task_result == false {
			log.Println("Machine %s was faild handled",Task_id)
		}
		ch <- true
	}
	if token := s.Subscribe(topic_s,2,f_s); token.Wait() && token.Error() !=nil {
		log.Printf("Error on Client.Subscribe(): %v", token.Error())
	}

	var msg Message
	err := msg.SetCAPTCHA()
	if err != true {
		log.Printf("fial to get CAPTCHA")
	}
	msg.Name = name
	msg.id = id
	msg.body = strconv.Itoa(choice)
	m := Message_assembly(msg,rsa)

	if token := p.Connect(); token.Wait() && token.Error() != nil {
		log.Printf("Error on Client.'Connect'(): %v", token.Error())
	}

	p.Publish(topic_p,0,false,m)

	defer s.Disconnect(250)
	defer p.Disconnect(250)

}

/*
//分公司设备端测试代码
func Machine_test(branch_name,id string) {

	topic_s := "Machine_" + id
	var f MQTT.MessageHandler = func(client MQTT.Client,msg MQTT.Message){
		fmt.Printf("It's default topic: %s $$ Msg: %s\n",msg.Topic(),msg.Payload())
	}
	//配置MQTT基本配置
	opts := MQTT.NewClientOptions().AddBroker("tcp://mqtt.eclipse.org:1883")
	//遗嘱:topic名称,消息,Qos,LWT是否保持
	opts.SetWill("last_corner", "Program is err,please start again", 1, true)
	//对接收的消息处理默认模块
	opts.SetDefaultPublishHandler(f)
	s := MQTT.NewClient(opts)

	if token := s.Connect(); token.Wait() && token.Error() != nil {
		log.Printf("Error on Client.Connect(): %v", token.Error())
	}
	var f_s MQTT.MessageHandler = func(client MQTT.Client, message MQTT.Message) {
		var ss []string
		ss = strings.Split(string(message.Payload()),"_")
		//Name_id_CAPTCHA_timestamp_body
		if len(ss) != 5 {
			log.Println(" %s's machine %s, messages are lost or duplicated",ss[0],ss[1])
			return
		}
		if ss[0] != branch_name {
			log.Fatalln("branch is err")
		}
		if ss[1] != id {
			log.Fatalln("machine %s is err",ss[1])
		}
		CAPTCHA := en.RSA_Decrypt([]byte(ss[2]),"secretkey_Filiale")
		if string(CAPTCHA) != "It's only a CAPTCHA" {
			log.Println("Machine %s's CAPTCHA is faild",ss[1])
		}
		timestamp := ss[3]
		//处理任务此处省略,默认按成功对待

		topic_p := id + "Sub"
		//回复回来的消息内容分五部分 branchName_id_timestamp_(true/false)_body
		//配置MQTT基本配置
		optp := MQTT.NewClientOptions().AddBroker("tcp://mqtt.eclipse.org:1883")
		//遗嘱:topic名称,消息,Qos,LWT是否保持
		optp.SetWill("last_corner", "Program is err,please start again", 1, true)
		//对接收的消息处理默认模块
		optp.SetDefaultPublishHandler(f)
		p := MQTT.NewClient(optp)
		if token := p.Connect(); token.Wait() && token.Error() != nil {
			log.Printf("Error on Client.'Connect'(): %v", token.Error())
		}
		msg_rquest := branch_name + "_" + id + "_" + timestamp + "true" + "_" + "Nothings"
		p.Publish(topic_p,2,false,msg_rquest)

		defer p.Disconnect(250)
	}
	if token := s.Subscribe(topic_s,2,f_s); token.Wait() && token.Error() !=nil {
		log.Printf("Error on Client.Subscribe(): %v", token.Error())
	}

	defer s.Disconnect(250)
}
*/
RSA加密实现
//encrpty.go
package encrypt

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"os"
)

//生成RSA私钥和公钥,保存到文件中
func GenerateRSAKey(bits int) {
	//GenerateKey函数使用随机数据生成器random生成一对具有指定字位数的RSA密钥
	//Reader是一个全局、共享的密码用强随机数生成器
	privateKey, err := rsa.GenerateKey(rand.Reader, bits)
	if err != nil {
		panic(err)
	}
	//保存私钥
	//通过x509标准将得到的ras私钥序列化为ASN.1 的 DER编码字符串
	X509PrivateKey := x509.MarshalPKCS1PrivateKey(privateKey)
	//使用pem格式对x509输出的内容进行编码
	//创建文件保存私钥
	privateFile, err := os.Create("private.pem")
	if err != nil {
		panic(err)
	}
	defer privateFile.Close()
	//构建一个pem.Block结构体对象
	privateBlock := pem.Block{Type: "RSA Private Key", Bytes: X509PrivateKey}
	//将数据保存到文件
	pem.Encode(privateFile, &privateBlock)

	//保存公钥
	//获取公钥的数据
	publicKey := privateKey.PublicKey
	//X509对公钥编码
	X509PublicKey, err := x509.MarshalPKIXPublicKey(&publicKey)
	if err != nil {
		panic(err)
	}
	//pem格式编码
	//创建用于保存公钥的文件
	publicFile, err := os.Create("public.pem")
	if err != nil {
		panic(err)
	}
	defer publicFile.Close()
	//创建一个pem.Block结构体对象
	publicBlock := pem.Block{Type: "RSA Public Key", Bytes: X509PublicKey}
	//保存到文件
	pem.Encode(publicFile, &publicBlock)
}

//RSA加密
func RSA_Encrypt(plainText []byte, path string) []byte {
	//打开文件
	file, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer file.Close()
	//读取文件的内容
	info, _ := file.Stat()
	buf := make([]byte, info.Size())
	file.Read(buf)
	//pem解码
	block, _ := pem.Decode(buf)
	//x509解码

	publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		panic(err)
	}
	//类型断言
	publicKey := publicKeyInterface.(*rsa.PublicKey)
	//对明文进行加密
	cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText)
	if err != nil {
		panic(err)
	}
	//返回密文
	return cipherText
}

//RSA解密
func RSA_Decrypt(cipherText []byte, path string) []byte {
	//打开文件
	file, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer file.Close()
	//获取文件内容
	info, _ := file.Stat()
	buf := make([]byte, info.Size())
	file.Read(buf)
	//pem解码
	block, _ := pem.Decode(buf)
	//X509解码
	privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		panic(err)
	}
	//对密文进行解密
	plainText, _ := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherText)
	//返回明文
	return plainText
}

测试文件
//main.go 
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"time"

	//"time"

	m "mqtt_B/mqtt"
	//"time"
	"github.com/graphql-go/graphql"
)

//与设备协商的任务处理方案
/*
查询 设备列表 1
查询 设备日报 2
查询 设备月报 3
查询 设备状态 4
控制 设备启动 5
控制 设备暂停 6
控制 设备重启 7
设备 设备关机 8(危险操作--需人工启动)
*/
func test_task(branch_name, id string, choice int) {

	var rsa m.RSA
	ch := make(chan bool)
	go m.Task(branch_name, id, choice, rsa, ch)

	defer close(ch)

	select {
	case <-ch:
		fmt.Println("Task %s was successful", id)
	case <-time.After(15 * time.Second):
		fmt.Println("Task Timeout")
	}
}

func main() {
	test_task("shanxi-061",1)
	//启动监测等待省中心的调用
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

quchen528

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

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

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

打赏作者

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

抵扣说明:

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

余额充值