fwh_mqtt_client.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. package fwh_mqtt
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "crypto/x509"
  6. "encoding/json"
  7. "fmt"
  8. "fwh-toolbox-server/config"
  9. "fwh-toolbox-server/utils/mqttClient"
  10. "os"
  11. "strconv"
  12. "time"
  13. "go.uber.org/zap"
  14. )
  15. const (
  16. fwhClientIdPrefix = "fwh_xlog_mqtt_client_"
  17. sleepTimeout = 5 * time.Minute
  18. sendMessageTimeout = 1 * time.Minute
  19. topicPrefixS2C = "fwh/s2c/"
  20. topicPrefixC2S = "fwh/c2s/"
  21. )
  22. type FwhMqttResult struct {
  23. Err error
  24. Data *FwhMqttMsg
  25. }
  26. type FwhMqttMsg struct {
  27. CmdType uint `json:"cmdType"` // 业务类型标识
  28. Crc string `json:"crc"` // dataArea 数据字符串的CRC
  29. DataArea interface{} `json:"dataArea"` // 数据域
  30. EquipNo string `json:"equipNo"` // 设备编号 SN
  31. Len int `json:"len"` // dataArea 数据长度
  32. Snno uint `json:"snno"` // 序号
  33. TimeStamp int64 `json:"timeStamp"` // 时间戳
  34. Type uint `json:"type"` // 设备类型 0:设备网关IBG
  35. }
  36. type MqttMsg struct {
  37. Topic string // 发送 Topic
  38. Msg FwhMqttMsg
  39. ResTopic *string // 响应 Topic
  40. ResCmdType *uint
  41. ResultCh chan FwhMqttResult
  42. Ctx context.Context
  43. }
  44. type ResultChan struct {
  45. ch chan FwhMqttMsg
  46. ctx context.Context
  47. }
  48. type FwhMqttClient struct {
  49. Client *mqttClient.MqttClient
  50. TaskCh chan MqttMsg
  51. }
  52. var fwhMqttClient = FwhMqttClient{
  53. Client: nil,
  54. TaskCh: make(chan MqttMsg, 1000),
  55. }
  56. func InitFwhMqttClient(cfg *config.FwhMqttConfig) error {
  57. // 证书配置
  58. caCert, err := os.ReadFile(cfg.FwhMqttCaUrl)
  59. if err != nil {
  60. panic(err)
  61. }
  62. caCertPool := x509.NewCertPool()
  63. caCertPool.AppendCertsFromPEM(caCert)
  64. cert, err := tls.LoadX509KeyPair(cfg.FwhMqttCertUrl, cfg.FwhMqttKeyUrl)
  65. if err != nil {
  66. panic(err)
  67. }
  68. client, err := mqttClient.CreateMqttClient(mqttClient.MqttClientOpt{
  69. Broker: cfg.FwhMqttBroker,
  70. ClientID: fwhClientIdPrefix + strconv.FormatInt(time.Now().Unix(), 10),
  71. TLS: &tls.Config{
  72. Certificates: []tls.Certificate{cert},
  73. RootCAs: caCertPool,
  74. },
  75. })
  76. if err != nil {
  77. zap.L().Error("Init FWH MQTT client failed.")
  78. return err
  79. }
  80. fwhMqttClient.Client = client
  81. zap.L().Info("Init FWH MQTT client success.")
  82. go fwhMqttClient.fwhMqttWorker()
  83. return nil
  84. }
  85. func (c *FwhMqttClient) fwhMqttWorker() {
  86. idleTimer := time.NewTimer(sleepTimeout)
  87. defer idleTimer.Stop()
  88. for {
  89. select {
  90. case task, ok := <-c.TaskCh:
  91. if !ok {
  92. zap.L().Error("Channel closed, worker exiting, recreate.")
  93. c.TaskCh = make(chan MqttMsg, 1000)
  94. continue
  95. }
  96. // 重置空闲计时器
  97. if !idleTimer.Stop() {
  98. select {
  99. case <-idleTimer.C:
  100. default:
  101. }
  102. }
  103. idleTimer.Reset(sleepTimeout)
  104. // 处理任务
  105. c.handleTask(task)
  106. case <-idleTimer.C:
  107. if c.Client.IsConnected() {
  108. zap.L().Info("FWH MQTT no task received within timeout, disconnect.")
  109. c.Client.Disconnect()
  110. }
  111. idleTimer.Reset(sleepTimeout)
  112. }
  113. }
  114. }
  115. func (c *FwhMqttClient) handleTask(task MqttMsg) {
  116. // 1. 建立连接
  117. if err := c.Client.Connect(); err != nil {
  118. zap.L().Error("FWH MQTT connect failed.",
  119. zap.String("status", c.Client.GetClientStatus()),
  120. zap.String("err", err.Error()),
  121. )
  122. task.ResultCh <- FwhMqttResult{Err: fmt.Errorf("FWH MQTT connect failed: %w", err)}
  123. return
  124. }
  125. // 2. 如果需要响应,订阅响应 topic
  126. var resultCh chan FwhMqttResult
  127. if task.ResTopic != nil {
  128. resultCh = make(chan FwhMqttResult, 1)
  129. if err := c.Client.SubscribeMessage(*task.ResTopic, func(payload []byte) {
  130. var fwhMqttMsg FwhMqttMsg
  131. if err := json.Unmarshal(payload, &fwhMqttMsg); err != nil {
  132. zap.L().Error("Result Unmarshal failed.(FwhMqttMsg)",
  133. zap.String("topic", task.Topic),
  134. zap.String("payload", string(payload)),
  135. zap.String("err", err.Error()),
  136. )
  137. return
  138. }
  139. // 过滤 CmdType
  140. if task.ResCmdType != nil && fwhMqttMsg.CmdType != *task.ResCmdType {
  141. return
  142. }
  143. // 非阻塞写入,防止回调卡死
  144. select {
  145. case resultCh <- FwhMqttResult{Err: nil, Data: &fwhMqttMsg}:
  146. default:
  147. zap.L().Warn("[MQTT] resultCh full, dropping duplicate response")
  148. }
  149. }); err != nil {
  150. zap.L().Error("FWH MQTT subscribe failed.", zap.String("err", err.Error()), zap.String("topic", *task.ResTopic))
  151. task.ResultCh <- FwhMqttResult{Err: fmt.Errorf("subscribe failed: %w", err)}
  152. return
  153. }
  154. // 确保退出时取消订阅
  155. defer func() {
  156. if err := c.Client.UnSubscribeMessage(*task.ResTopic); err != nil {
  157. zap.L().Error("FWH MQTT unsubscribe failed.", zap.String("err", err.Error()), zap.String("topic", *task.ResTopic))
  158. }
  159. }()
  160. }
  161. // 3. 发送消息
  162. msgByte, err := json.Marshal(task.Msg)
  163. if err != nil {
  164. zap.L().Error("FWH MQTT message marshal failed.", zap.String("err", err.Error()))
  165. task.ResultCh <- FwhMqttResult{Err: err}
  166. return
  167. }
  168. if err := c.Client.SendMessageOnce(task.Topic, msgByte); err != nil {
  169. zap.L().Error("FWH MQTT send message failed.", zap.String("err", err.Error()))
  170. task.ResultCh <- FwhMqttResult{Err: err}
  171. return
  172. }
  173. // 4. 等待响应 或 直接返回
  174. if resultCh == nil {
  175. // 只发送,不等响应
  176. task.ResultCh <- FwhMqttResult{Err: nil, Data: nil}
  177. return
  178. }
  179. // 一问一答:等待响应或超时
  180. select {
  181. case res := <-resultCh:
  182. task.ResultCh <- res
  183. case <-task.Ctx.Done():
  184. task.ResultCh <- FwhMqttResult{Err: fmt.Errorf("FWH MQTT received message timeout")}
  185. }
  186. }
  187. func (c *FwhMqttClient) sendMessage(ctx context.Context, topicName string, msg FwhMqttMsg, resTopicName *string, resCmdType *uint) (*FwhMqttMsg, error) {
  188. if c.Client == nil {
  189. return nil, fmt.Errorf("Fwh Mqtt Client Uninitialized.")
  190. }
  191. ctxSend, cancel := context.WithTimeout(ctx, sendMessageTimeout)
  192. defer cancel()
  193. topic := topicPrefixS2C + topicName
  194. var resTopic *string
  195. if resTopicName != nil {
  196. resTopicStr := topicPrefixC2S + *resTopicName
  197. resTopic = &resTopicStr
  198. }
  199. taskMsg := MqttMsg{
  200. Topic: topic,
  201. Msg: msg,
  202. ResTopic: resTopic,
  203. ResCmdType: resCmdType,
  204. ResultCh: make(chan FwhMqttResult, 1),
  205. Ctx: ctxSend,
  206. }
  207. c.TaskCh <- taskMsg
  208. for {
  209. select {
  210. case result, ok := <-taskMsg.ResultCh:
  211. if !ok {
  212. zap.L().Error("Result channel closed, worker exiting, recreate.", zap.String("topic", topic), zap.String("msg", fmt.Sprintf("%+v", msg)))
  213. return nil, fmt.Errorf("Result channel closed, worker exiting, recreate.")
  214. }
  215. if result.Err != nil {
  216. return nil, result.Err
  217. }
  218. return result.Data, nil
  219. case <-ctxSend.Done():
  220. zap.L().Info("Result received timeout.", zap.String("topic", topic), zap.String("msg", fmt.Sprintf("%+v", msg)))
  221. return nil, fmt.Errorf("Result received timeout.")
  222. }
  223. }
  224. }
  225. // 仅发送不等待响应结果
  226. func (c *FwhMqttClient) sendMessageOnce(ctx context.Context, topicName string, msg FwhMqttMsg) error {
  227. _, err := c.sendMessage(ctx, topicName, msg, nil, nil)
  228. return err
  229. }
  230. // 发送消息后,等待响应结果
  231. func (c *FwhMqttClient) sendMessageWait(ctx context.Context, topicName string, msg FwhMqttMsg, resTopicName string, resCmdType uint) (*FwhMqttMsg, error) {
  232. res, err := c.sendMessage(ctx, topicName, msg, &resTopicName, &resCmdType)
  233. return res, err
  234. }