Bläddra i källkod

添加mqtt_binding

yjp 1 år sedan
förälder
incheckning
1a9c793218

+ 2 - 0
go.mod

@@ -6,6 +6,7 @@ require (
 	git.sxidc.com/service-supports/fserr v0.3.2
 	git.sxidc.com/service-supports/fslog v0.5.5
 	git.sxidc.com/service-supports/websocket v1.3.1
+	github.com/eclipse/paho.mqtt.golang v1.4.3
 	github.com/gin-gonic/gin v1.9.1
 	github.com/goccy/go-json v0.10.2
 )
@@ -36,6 +37,7 @@ require (
 	golang.org/x/arch v0.5.0 // indirect
 	golang.org/x/crypto v0.13.0 // indirect
 	golang.org/x/net v0.15.0 // indirect
+	golang.org/x/sync v0.1.0 // indirect
 	golang.org/x/sys v0.12.0 // indirect
 	golang.org/x/text v0.13.0 // indirect
 	google.golang.org/protobuf v1.31.0 // indirect

+ 4 - 0
go.sum

@@ -19,6 +19,8 @@ github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLI
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik=
+github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
 github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
 github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
 github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
@@ -92,6 +94,8 @@ golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
 golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
 golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
 golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=

+ 1 - 1
http_binding/http_binding.go

@@ -96,7 +96,7 @@ type SimpleBindItem[I any, O any] struct {
 	Path                string               // 请求路径
 	ResponseFunc        response.SendFunc[O] // 响应泛型函数
 	BusinessFunc        BusinessFunc[I, O]   // 业务泛型函数
-	OptionalBindingFunc BindingFunc[O]       //
+	OptionalBindingFunc BindingFunc[O]       // 可选的绑定函数
 }
 
 func (item *SimpleBindItem[I, O]) bind(routerGroup *gin.RouterGroup, method string, middlewares ...middleware.Func) {

+ 103 - 0
mqtt_binding/mqtt_binding.go

@@ -0,0 +1,103 @@
+package mqtt_binding
+
+import (
+	"git.sxidc.com/go-tools/api_binding/mqtt_binding/mqtt_client"
+	"git.sxidc.com/go-tools/api_binding/mqtt_binding/mqtt_client/router"
+	"git.sxidc.com/go-tools/api_binding/mqtt_binding/request"
+	"git.sxidc.com/go-tools/api_binding/mqtt_binding/response"
+	"git.sxidc.com/go-tools/api_binding/utils"
+	"reflect"
+)
+
+type BusinessFunc[I any, O any] func(c *mqtt_client.MqttClient, inputModel I) (O, error)
+type BindingFunc[O any] func(c *mqtt_client.MqttClient, item *router.Item, request any, sendFunc response.SendFunc[O]) bool
+
+type Binding struct {
+	router *router.Router
+}
+
+// NewBinding 创建版本对应的binding
+func NewBinding(apiVersion string, handlers ...router.Handler) *Binding {
+	var group string
+
+	if utils.IsStringNotEmpty(apiVersion) && apiVersion != "root" {
+		group = apiVersion
+	}
+
+	r := mqttClientInstance.GetRouter(group, handlers)
+
+	return &Binding{router: r}
+}
+
+// BindItem 路由条目结构
+type BindItem[I any, O any] struct {
+	Topic               string               // 请求路径
+	Qos                 byte                 // QOS
+	Retained            bool                 // 是否保留响应
+	ResponseFunc        response.SendFunc[O] // 响应泛型函数
+	BusinessFunc        BusinessFunc[I, O]   // 业务泛型函数
+	OptionalBindingFunc BindingFunc[O]       // 可选的绑定函数
+}
+
+func (item *BindItem[I, O]) bind(r *router.Router, handlers ...router.Handler) {
+	if utils.IsStringEmpty(item.Topic) {
+		panic("需要指定主题")
+	}
+
+	if item.ResponseFunc == nil {
+		panic("需要指定响应函数")
+	}
+
+	var inputCheckModel I
+	inputType := reflect.TypeOf(inputCheckModel)
+	if inputType != nil {
+		if inputType.Kind() == reflect.Pointer {
+			panic("输入对象不能使用指针类型")
+		}
+
+		if inputType.Kind() != reflect.Struct {
+			panic("输入对象必须是结构")
+		}
+	}
+
+	// 给单个路由增加中间件
+	handlers = append(handlers, func(routerItem *router.Item, data []byte) {
+		var inputModel I
+
+		// 请求的结构类型不为any
+		if inputType != nil {
+			// 将请求数据解析到inputModel中
+			if item.OptionalBindingFunc != nil {
+				ok := item.OptionalBindingFunc(mqttClientInstance, routerItem, &inputModel, item.ResponseFunc)
+				if !ok {
+					return
+				}
+			} else {
+				ok := request.BindingJson(mqttClientInstance, routerItem, &inputModel, item.ResponseFunc)
+				if !ok {
+					return
+				}
+			}
+		}
+
+		// 执行业务函数
+		if item.BusinessFunc != nil {
+			outputModel, err := item.BusinessFunc(mqttClientInstance, inputModel)
+			item.ResponseFunc(mqttClientInstance, routerItem, outputModel, err)
+			return
+		}
+	})
+
+	// 所有的函数加入到执行链中
+	routerItem, err := router.NewItem(item.Topic, item.Qos, item.Retained)
+	if err != nil {
+		panic("创建路由条目失败: " + err.Error())
+		return
+	}
+
+	err = r.AddItem(routerItem)
+	if err != nil {
+		panic("添加路由条目失败: " + err.Error())
+		return
+	}
+}

+ 188 - 0
mqtt_binding/mqtt_client/mqtt_client.go

@@ -0,0 +1,188 @@
+package mqtt_client
+
+import (
+	"errors"
+	"fmt"
+	"git.sxidc.com/go-tools/api_binding/mqtt_binding/mqtt_client/router"
+	"git.sxidc.com/go-tools/api_binding/utils"
+	mqtt "github.com/eclipse/paho.mqtt.golang"
+	"sync"
+	"time"
+)
+
+type MqttClientOptions struct {
+	UserName       string
+	Password       string
+	Address        string
+	ClientID       string
+	KeepAliveSec   time.Duration
+	PingTimeoutSec time.Duration
+}
+
+func (opt *MqttClientOptions) check() error {
+	if utils.IsStringEmpty(opt.UserName) {
+		return errors.New("必须传递用户名")
+	}
+
+	if utils.IsStringEmpty(opt.Password) {
+		return errors.New("必须传递密码")
+	}
+
+	if utils.IsStringEmpty(opt.Address) {
+		return errors.New("必须传递地址")
+	}
+
+	if utils.IsStringEmpty(opt.ClientID) {
+		return errors.New("必须传递客户端ID")
+	}
+
+	return nil
+}
+
+type MqttClient struct {
+	client        mqtt.Client
+	clientOptions *mqtt.ClientOptions
+
+	routersMutex *sync.Mutex
+	routers      []router.Router
+}
+
+func NewMqttClient(opts *MqttClientOptions) (*MqttClient, error) {
+	if opts == nil {
+		return nil, errors.New("没有传递mqtt客户端选项")
+	}
+
+	err := opts.check()
+	if err != nil {
+		return nil, err
+	}
+
+	mqttOptions := mqtt.NewClientOptions().
+		SetAutoReconnect(true).
+		SetUsername(opts.UserName).
+		SetPassword(opts.Password).
+		AddBroker(opts.Address).
+		SetClientID(opts.ClientID).
+		SetKeepAlive(opts.KeepAliveSec).
+		SetPingTimeout(opts.PingTimeoutSec).
+		SetWill(opts.ClientID+"/will", "dead", 2, true)
+
+	return &MqttClient{
+		client:        mqtt.NewClient(mqttOptions),
+		clientOptions: mqttOptions,
+		routersMutex:  &sync.Mutex{},
+		routers:       make([]router.Router, 0),
+	}, nil
+}
+
+func DestroyMqttClient(c *MqttClient) {
+	if c != nil {
+		c.client = nil
+
+		c.routersMutex.Lock()
+		for _, r := range c.routers {
+			router.DestroyRouter(&r)
+		}
+		c.routers = nil
+		c.routersMutex.Unlock()
+	}
+
+	c = nil
+}
+
+func (c *MqttClient) Connect() error {
+	c.clientOptions.SetOnConnectHandler(func(client mqtt.Client) {
+		c.routersMutex.Lock()
+		defer c.routersMutex.Unlock()
+
+		err := c.rangeRouters(func(r *router.Router) error {
+			err := r.RangeItem(func(item router.Item) error {
+				topic := r.Group + item.Topic
+				token := c.client.Subscribe(topic, item.Qos, func(client mqtt.Client, message mqtt.Message) {
+					item.CallHandlers(message.Payload())
+				})
+				if token.Wait(); token.Error() != nil {
+					return token.Error()
+				}
+
+				return nil
+			})
+			if err != nil {
+				return errors.New("SetOnConnectHandler订阅失败: " + err.Error())
+			}
+
+			return nil
+		})
+		if err != nil {
+			fmt.Println(err)
+			return
+		}
+	})
+
+	token := c.client.Connect()
+	if token.Wait(); token.Error() != nil {
+		return token.Error()
+	}
+
+	return nil
+}
+
+func (c *MqttClient) Disconnect() {
+	c.client.Disconnect(10000)
+}
+
+func (c *MqttClient) GetRouter(group string, handlers []router.Handler) *router.Router {
+	r := router.NewRouter(group, handlers, func(item router.Item) error {
+		for {
+			if c.client.IsConnected() {
+				break
+			}
+
+			time.Sleep(1 * time.Second)
+		}
+
+		topic := group + item.Topic
+		token := c.client.Subscribe(topic, item.Qos, func(client mqtt.Client, message mqtt.Message) {
+			item.CallHandlers(message.Payload())
+		})
+		if token.Wait(); token.Error() != nil {
+			return token.Error()
+		}
+
+		return nil
+	})
+
+	c.addRouter(r)
+
+	return r
+}
+
+func (c *MqttClient) Response(item *router.Item, data []byte) error {
+	token := c.client.Publish(item.Topic+"/reply", item.Qos, item.ResponseRetained, data)
+	if token.Wait(); token.Error() != nil {
+		return token.Error()
+	}
+
+	return nil
+}
+
+func (c *MqttClient) addRouter(router *router.Router) {
+	c.routersMutex.Lock()
+	defer c.routersMutex.Unlock()
+
+	c.routers = append(c.routers, *router)
+}
+
+func (c *MqttClient) rangeRouters(rangeFunc func(router *router.Router) error) error {
+	c.routersMutex.Lock()
+	defer c.routersMutex.Unlock()
+
+	for _, r := range c.routers {
+		err := rangeFunc(&r)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}

+ 134 - 0
mqtt_binding/mqtt_client/router/router.go

@@ -0,0 +1,134 @@
+package router
+
+import (
+	"errors"
+	"git.sxidc.com/go-tools/api_binding/utils"
+	"sync"
+)
+
+type Handler func(item *Item, data []byte)
+type OnAddItemFunc func(item Item) error
+
+type Router struct {
+	Group string
+
+	itemsMutex    *sync.Mutex
+	items         []Item
+	handlers      []Handler
+	onAddItemFunc OnAddItemFunc
+}
+
+func NewRouter(group string, handlers []Handler, onAddItemFunc OnAddItemFunc) *Router {
+	return &Router{
+		Group:         group,
+		itemsMutex:    &sync.Mutex{},
+		items:         make([]Item, 0),
+		onAddItemFunc: onAddItemFunc,
+		handlers:      handlers,
+	}
+}
+
+func DestroyRouter(router *Router) {
+	if router != nil {
+		return
+	}
+
+	router.itemsMutex.Lock()
+	for _, item := range router.items {
+		DestroyItem(&item)
+	}
+	router.items = nil
+	router.itemsMutex.Unlock()
+
+	router = nil
+}
+
+func (router *Router) AddItem(item *Item) error {
+	if item == nil {
+		return nil
+	}
+
+	router.itemsMutex.Lock()
+	defer router.itemsMutex.Unlock()
+
+	item.handlers = append(item.handlers, router.handlers...)
+
+	router.items = append(router.items, *item)
+
+	if router.onAddItemFunc != nil {
+		err := router.onAddItemFunc(*item)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (router *Router) RangeItem(rangeFunc func(item Item) error) error {
+	if rangeFunc == nil {
+		return nil
+	}
+
+	router.itemsMutex.Lock()
+	defer router.itemsMutex.Unlock()
+
+	for _, item := range router.items {
+		err := rangeFunc(item)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+type Item struct {
+	Topic            string
+	Qos              byte
+	ResponseRetained bool
+
+	handlers            []Handler
+	currentHandlerIndex int
+	currentData         []byte
+}
+
+func NewItem(topic string, qos byte, responseRetained bool) (*Item, error) {
+	if utils.IsStringEmpty(topic) {
+		return nil, errors.New("没有传递主题")
+	}
+
+	return &Item{
+		Topic:               topic,
+		Qos:                 qos,
+		ResponseRetained:    responseRetained,
+		handlers:            make([]Handler, 0),
+		currentHandlerIndex: 0,
+		currentData:         make([]byte, 0),
+	}, nil
+}
+
+func DestroyItem(item *Item) {
+	if item == nil {
+		return
+	}
+
+	item = nil
+}
+
+func (item *Item) CallHandlers(data []byte) {
+	item.currentHandlerIndex = 0
+	item.currentData = data
+	item.handlers[item.currentHandlerIndex](item, item.currentData)
+}
+
+func (item *Item) Next() {
+	item.currentHandlerIndex++
+	if item.currentHandlerIndex < len(item.handlers) {
+		item.handlers[item.currentHandlerIndex](item, item.currentData)
+	}
+}
+
+func (item *Item) GetData() []byte {
+	return item.currentData
+}

+ 25 - 0
mqtt_binding/mqtt_init.go

@@ -0,0 +1,25 @@
+package mqtt_binding
+
+import (
+	"git.sxidc.com/go-tools/api_binding/mqtt_binding/mqtt_client"
+)
+
+var mqttClientInstance *mqtt_client.MqttClient
+
+func Init(opts *mqtt_client.MqttClientOptions) error {
+	mqttClient, err := mqtt_client.NewMqttClient(opts)
+	if err != nil {
+		return err
+	}
+
+	mqttClientInstance = mqttClient
+
+	return nil
+}
+
+func Destroy() {
+	if mqttClientInstance != nil {
+		mqtt_client.DestroyMqttClient(mqttClientInstance)
+		mqttClientInstance = nil
+	}
+}

+ 19 - 0
mqtt_binding/request/request.go

@@ -0,0 +1,19 @@
+package request
+
+import (
+	"encoding/json"
+	"git.sxidc.com/go-tools/api_binding/mqtt_binding/mqtt_client"
+	"git.sxidc.com/go-tools/api_binding/mqtt_binding/mqtt_client/router"
+	"git.sxidc.com/go-tools/api_binding/mqtt_binding/response"
+)
+
+func BindingJson[O any](c *mqtt_client.MqttClient, item *router.Item, request any, sendFunc response.SendFunc[O]) bool {
+	err := json.Unmarshal(item.GetData(), request)
+	if err != nil {
+		var zero O
+		sendFunc(c, item, zero, err)
+		return false
+	}
+
+	return true
+}

+ 7 - 0
mqtt_binding/response/data.go

@@ -0,0 +1,7 @@
+package response
+
+type InfosData[T any] struct {
+	Infos      []T   `json:"infos"`
+	TotalCount int64 `json:"totalCount"`
+	PageNo     int   `json:"pageNo"`
+}

+ 166 - 0
mqtt_binding/response/response.go

@@ -0,0 +1,166 @@
+package response
+
+import (
+	"encoding/json"
+	"fmt"
+	"git.sxidc.com/go-tools/api_binding/mqtt_binding/mqtt_client"
+	"git.sxidc.com/go-tools/api_binding/mqtt_binding/mqtt_client/router"
+	"git.sxidc.com/service-supports/fserr"
+	"git.sxidc.com/service-supports/fslog"
+)
+
+func NoResponse(_ any, _ error) {
+	return
+}
+
+func SendMsgResponse(c *mqtt_client.MqttClient, item *router.Item, _ any, err error) {
+	msgResp := formMsgResponse(err)
+	jsonResponse(c, item, msgResp)
+}
+
+type IDResponse[T IDType] struct {
+	MsgResponse
+	ID T `json:"id"`
+}
+
+func SendIDResponse[T IDType](c *mqtt_client.MqttClient, item *router.Item, id T, err error) {
+	msgResp := formMsgResponse(err)
+	jsonResponse(c, item, IDResponse[T]{
+		MsgResponse: msgResp,
+		ID:          id,
+	})
+}
+
+type InfoResponse[T any] struct {
+	MsgResponse
+	Info T `json:"info"`
+}
+
+func SendInfoResponse[T any](c *mqtt_client.MqttClient, item *router.Item, info T, err error) {
+	msgResp := formMsgResponse(err)
+	jsonResponse(c, item, InfoResponse[T]{
+		MsgResponse: msgResp,
+		Info:        info,
+	})
+}
+
+type InfosResponse[T any] struct {
+	MsgResponse
+	InfosData[T]
+}
+
+func SendInfosResponse[T any](c *mqtt_client.MqttClient, item *router.Item, data InfosData[T], err error) {
+	msgResp := formMsgResponse(err)
+	jsonResponse(c, item, InfosResponse[T]{
+		MsgResponse: msgResp,
+		InfosData:   data,
+	})
+}
+
+func StructToMap(originStruct any) map[string]any {
+	jsonBytes, err := json.Marshal(originStruct)
+	if err != nil {
+		panic(err)
+	}
+
+	retMap := make(map[string]any)
+	err = json.Unmarshal(jsonBytes, &retMap)
+	if err != nil {
+		panic(err)
+	}
+
+	return retMap
+}
+
+func SendMapResponse(c *mqtt_client.MqttClient, item *router.Item, data map[string]any, err error) {
+	msgRespMap := formMapMsgResponse(err)
+	for key, value := range data {
+		msgRespMap[key] = value
+	}
+
+	jsonResponse(c, item, msgRespMap)
+}
+
+type MsgResponse struct {
+	Success bool   `json:"success"`
+	ErrCode int    `json:"errCode"`
+	Msg     string `json:"msg"`
+}
+
+func formMsgResponse(err error) MsgResponse {
+	if err != nil {
+		fslog.Error(err)
+		serviceErr := fserr.ParseCode(err)
+
+		return MsgResponse{
+			Success: false,
+			ErrCode: serviceErr.BusinessCode,
+			Msg:     serviceErr.Msg,
+		}
+	}
+
+	return MsgResponse{
+		Success: true,
+		ErrCode: 0,
+		Msg:     "操作成功",
+	}
+}
+
+func formMapMsgResponse(err error) map[string]any {
+	resp := make(map[string]any)
+	if err != nil {
+		fslog.Error(err)
+		serviceErr := fserr.ParseCode(err)
+
+		resp["success"] = false
+		resp["errCode"] = serviceErr.BusinessCode
+		resp["msg"] = serviceErr.Msg
+
+		return resp
+	}
+
+	resp["success"] = true
+	resp["errCode"] = 0
+	resp["msg"] = "操作成功"
+
+	return resp
+}
+
+func SendString(c *mqtt_client.MqttClient, item *router.Item, data string, err error) {
+	if err != nil {
+		bytesResponse(c, item, []byte(err.Error()))
+		return
+	}
+
+	bytesResponse(c, item, []byte(data))
+}
+
+func WriteBytes(c *mqtt_client.MqttClient, item *router.Item, bytes []byte, err error) {
+	if err != nil {
+		bytesResponse(c, item, []byte(err.Error()))
+		return
+	}
+
+	bytesResponse(c, item, bytes)
+}
+
+func jsonResponse(c *mqtt_client.MqttClient, item *router.Item, obj any) {
+	jsonBytes, err := json.Marshal(obj)
+	if err != nil {
+		panic(err)
+	}
+
+	err = c.Response(item, jsonBytes)
+	if err != nil {
+		fmt.Println("发送mqtt json响应失败")
+		return
+	}
+}
+
+func bytesResponse(c *mqtt_client.MqttClient, item *router.Item, obj []byte) {
+	err := c.Response(item, []byte(obj))
+	if err != nil {
+		fmt.Println("发送mqtt bytes响应失败")
+		return
+	}
+}

+ 12 - 0
mqtt_binding/response/type.go

@@ -0,0 +1,12 @@
+package response
+
+import (
+	"git.sxidc.com/go-tools/api_binding/mqtt_binding/mqtt_client"
+	"git.sxidc.com/go-tools/api_binding/mqtt_binding/mqtt_client/router"
+)
+
+type SendFunc[T any] func(c *mqtt_client.MqttClient, item *router.Item, data T, err error)
+
+type IDType interface {
+	~string | ~uint64
+}