fwh_mqtt_client.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  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. "sync"
  13. "time"
  14. "go.uber.org/zap"
  15. )
  16. const (
  17. fwhClientIdPrefix = "fwh_xlog_mqtt_client_"
  18. sleepTimeout = 5 * time.Minute
  19. sendMessageTimeout = 1 * time.Minute
  20. topicPrefixS2C = "fwh/s2c/"
  21. topicPrefixC2S = "fwh/c2s/"
  22. )
  23. type FwhMqttResult struct {
  24. Err error
  25. Data *FwhMqttMsg
  26. }
  27. type FwhMqttMsg struct {
  28. CmdType uint `json:"cmdType"` // 业务类型标识
  29. Crc string `json:"crc"` // dataArea 数据字符串的CRC
  30. DataArea interface{} `json:"dataArea"` // 数据域
  31. EquipNo string `json:"equipNo"` // 设备编号 SN
  32. Len int `json:"len"` // dataArea 数据长度
  33. Snno uint `json:"snno"` // 序号
  34. TimeStamp int64 `json:"timeStamp"` // 时间戳
  35. Type uint `json:"type"` // 设备类型 0:设备网关IBG
  36. }
  37. type MqttMsg struct {
  38. Topic string // 发送 Topic
  39. Msg FwhMqttMsg
  40. ResTopic *string // 响应 Topic
  41. ResCmdType *uint
  42. ResultCh chan FwhMqttResult
  43. Ctx context.Context
  44. }
  45. type ResultChan struct {
  46. ch chan FwhMqttMsg
  47. ctx context.Context
  48. }
  49. type FwhMqttClient struct {
  50. Client *mqttClient.MqttClient
  51. TaskCh chan MqttMsg
  52. }
  53. // Env FWH 服务器环境,每个环境使用独立的 broker、证书和客户端实例
  54. type Env string
  55. const (
  56. EnvProd Env = "prod" // 正式服
  57. EnvTest Env = "test" // 测试服
  58. )
  59. // ParseEnv 解析请求中的环境参数,空值默认为正式服
  60. func ParseEnv(s string) (Env, error) {
  61. switch Env(s) {
  62. case "", EnvProd:
  63. return EnvProd, nil
  64. case EnvTest:
  65. return EnvTest, nil
  66. default:
  67. return "", fmt.Errorf("unknown FWH MQTT env: %s", s)
  68. }
  69. }
  70. var (
  71. fwhMqttClients = map[Env]*FwhMqttClient{}
  72. fwhMqttClientsMu sync.RWMutex
  73. )
  74. // InitFwhMqttClient 初始化指定环境的 MQTT 客户端;失败时该环境不可用,不影响其他环境
  75. func InitFwhMqttClient(env Env, cfg *config.FwhMqttConfig) error {
  76. if cfg.FwhMqttBroker == "" {
  77. zap.L().Warn("FWH MQTT broker not configured, skip init.", zap.String("env", string(env)))
  78. return fmt.Errorf("FWH MQTT broker not configured (env=%s)", env)
  79. }
  80. // 证书配置
  81. caCert, err := os.ReadFile(cfg.FwhMqttCaUrl)
  82. if err != nil {
  83. zap.L().Error("Read FWH MQTT CA failed.", zap.String("env", string(env)), zap.String("err", err.Error()))
  84. return err
  85. }
  86. caCertPool := x509.NewCertPool()
  87. caCertPool.AppendCertsFromPEM(caCert)
  88. cert, err := tls.LoadX509KeyPair(cfg.FwhMqttCertUrl, cfg.FwhMqttKeyUrl)
  89. if err != nil {
  90. zap.L().Error("Load FWH MQTT cert failed.", zap.String("env", string(env)), zap.String("err", err.Error()))
  91. return err
  92. }
  93. client, err := mqttClient.CreateMqttClient(mqttClient.MqttClientOpt{
  94. Broker: cfg.FwhMqttBroker,
  95. ClientID: fwhClientIdPrefix + strconv.FormatInt(time.Now().Unix(), 10),
  96. TLS: &tls.Config{
  97. Certificates: []tls.Certificate{cert},
  98. RootCAs: caCertPool,
  99. },
  100. })
  101. if err != nil {
  102. zap.L().Error("Init FWH MQTT client failed.", zap.String("env", string(env)))
  103. return err
  104. }
  105. c := &FwhMqttClient{
  106. Client: client,
  107. TaskCh: make(chan MqttMsg, 1000),
  108. }
  109. fwhMqttClientsMu.Lock()
  110. if _, exists := fwhMqttClients[env]; exists {
  111. fwhMqttClientsMu.Unlock()
  112. return fmt.Errorf("FWH MQTT client already initialized (env=%s)", env)
  113. }
  114. fwhMqttClients[env] = c
  115. fwhMqttClientsMu.Unlock()
  116. zap.L().Info("Init FWH MQTT client success.", zap.String("env", string(env)))
  117. go c.fwhMqttWorker()
  118. return nil
  119. }
  120. // getFwhMqttClient 获取指定环境的 MQTT 客户端
  121. func getFwhMqttClient(env Env) (*FwhMqttClient, error) {
  122. fwhMqttClientsMu.RLock()
  123. defer fwhMqttClientsMu.RUnlock()
  124. c, ok := fwhMqttClients[env]
  125. if !ok {
  126. return nil, fmt.Errorf("Fwh Mqtt Client Uninitialized (env=%s).", env)
  127. }
  128. return c, nil
  129. }
  130. func (c *FwhMqttClient) fwhMqttWorker() {
  131. idleTimer := time.NewTimer(sleepTimeout)
  132. defer idleTimer.Stop()
  133. for {
  134. select {
  135. case task, ok := <-c.TaskCh:
  136. if !ok {
  137. zap.L().Error("Channel closed, worker exiting, recreate.")
  138. c.TaskCh = make(chan MqttMsg, 1000)
  139. continue
  140. }
  141. // 重置空闲计时器
  142. if !idleTimer.Stop() {
  143. select {
  144. case <-idleTimer.C:
  145. default:
  146. }
  147. }
  148. idleTimer.Reset(sleepTimeout)
  149. // 处理任务
  150. c.handleTask(task)
  151. case <-idleTimer.C:
  152. if c.Client.IsConnected() {
  153. zap.L().Info("FWH MQTT no task received within timeout, disconnect.")
  154. c.Client.Disconnect()
  155. }
  156. idleTimer.Reset(sleepTimeout)
  157. }
  158. }
  159. }
  160. func (c *FwhMqttClient) handleTask(task MqttMsg) {
  161. // 1. 建立连接
  162. if err := c.Client.Connect(); err != nil {
  163. zap.L().Error("FWH MQTT connect failed.",
  164. zap.String("status", c.Client.GetClientStatus()),
  165. zap.String("err", err.Error()),
  166. )
  167. task.ResultCh <- FwhMqttResult{Err: fmt.Errorf("FWH MQTT connect failed: %w", err)}
  168. return
  169. }
  170. // 2. 如果需要响应,订阅响应 topic
  171. var resultCh chan FwhMqttResult
  172. if task.ResTopic != nil {
  173. resultCh = make(chan FwhMqttResult, 1)
  174. if err := c.Client.SubscribeMessage(*task.ResTopic, func(payload []byte) {
  175. var fwhMqttMsg FwhMqttMsg
  176. if err := json.Unmarshal(payload, &fwhMqttMsg); err != nil {
  177. zap.L().Error("Result Unmarshal failed.(FwhMqttMsg)",
  178. zap.String("topic", task.Topic),
  179. zap.String("payload", string(payload)),
  180. zap.String("err", err.Error()),
  181. )
  182. return
  183. }
  184. // 过滤 CmdType
  185. if task.ResCmdType != nil && fwhMqttMsg.CmdType != *task.ResCmdType {
  186. return
  187. }
  188. // 非阻塞写入,防止回调卡死
  189. select {
  190. case resultCh <- FwhMqttResult{Err: nil, Data: &fwhMqttMsg}:
  191. default:
  192. zap.L().Warn("[MQTT] resultCh full, dropping duplicate response")
  193. }
  194. }); err != nil {
  195. zap.L().Error("FWH MQTT subscribe failed.", zap.String("err", err.Error()), zap.String("topic", *task.ResTopic))
  196. task.ResultCh <- FwhMqttResult{Err: fmt.Errorf("subscribe failed: %w", err)}
  197. return
  198. }
  199. // 确保退出时取消订阅
  200. defer func() {
  201. if err := c.Client.UnSubscribeMessage(*task.ResTopic); err != nil {
  202. zap.L().Error("FWH MQTT unsubscribe failed.", zap.String("err", err.Error()), zap.String("topic", *task.ResTopic))
  203. }
  204. }()
  205. }
  206. // 3. 发送消息
  207. msgByte, err := json.Marshal(task.Msg)
  208. if err != nil {
  209. zap.L().Error("FWH MQTT message marshal failed.", zap.String("err", err.Error()))
  210. task.ResultCh <- FwhMqttResult{Err: err}
  211. return
  212. }
  213. if err := c.Client.SendMessageOnce(task.Topic, msgByte); err != nil {
  214. zap.L().Error("FWH MQTT send message failed.", zap.String("err", err.Error()))
  215. task.ResultCh <- FwhMqttResult{Err: err}
  216. return
  217. }
  218. // 4. 等待响应 或 直接返回
  219. if resultCh == nil {
  220. // 只发送,不等响应
  221. task.ResultCh <- FwhMqttResult{Err: nil, Data: nil}
  222. return
  223. }
  224. // 一问一答:等待响应或超时
  225. select {
  226. case res := <-resultCh:
  227. task.ResultCh <- res
  228. case <-task.Ctx.Done():
  229. task.ResultCh <- FwhMqttResult{Err: fmt.Errorf("FWH MQTT received message timeout")}
  230. }
  231. }
  232. func (c *FwhMqttClient) sendMessage(ctx context.Context, topicName string, msg FwhMqttMsg, resTopicName *string, resCmdType *uint) (*FwhMqttMsg, error) {
  233. if c.Client == nil {
  234. return nil, fmt.Errorf("Fwh Mqtt Client Uninitialized.")
  235. }
  236. ctxSend, cancel := context.WithTimeout(ctx, sendMessageTimeout)
  237. defer cancel()
  238. topic := topicPrefixS2C + topicName
  239. var resTopic *string
  240. if resTopicName != nil {
  241. resTopicStr := topicPrefixC2S + *resTopicName
  242. resTopic = &resTopicStr
  243. }
  244. taskMsg := MqttMsg{
  245. Topic: topic,
  246. Msg: msg,
  247. ResTopic: resTopic,
  248. ResCmdType: resCmdType,
  249. ResultCh: make(chan FwhMqttResult, 1),
  250. Ctx: ctxSend,
  251. }
  252. c.TaskCh <- taskMsg
  253. for {
  254. select {
  255. case result, ok := <-taskMsg.ResultCh:
  256. if !ok {
  257. zap.L().Error("Result channel closed, worker exiting, recreate.", zap.String("topic", topic), zap.String("msg", fmt.Sprintf("%+v", msg)))
  258. return nil, fmt.Errorf("Result channel closed, worker exiting, recreate.")
  259. }
  260. if result.Err != nil {
  261. return nil, result.Err
  262. }
  263. return result.Data, nil
  264. case <-ctxSend.Done():
  265. zap.L().Info("Result received timeout.", zap.String("topic", topic), zap.String("msg", fmt.Sprintf("%+v", msg)))
  266. return nil, fmt.Errorf("Result received timeout.")
  267. }
  268. }
  269. }
  270. // 仅发送不等待响应结果
  271. func (c *FwhMqttClient) sendMessageOnce(ctx context.Context, topicName string, msg FwhMqttMsg) error {
  272. _, err := c.sendMessage(ctx, topicName, msg, nil, nil)
  273. return err
  274. }
  275. // 发送消息后,等待响应结果
  276. func (c *FwhMqttClient) sendMessageWait(ctx context.Context, topicName string, msg FwhMqttMsg, resTopicName string, resCmdType uint) (*FwhMqttMsg, error) {
  277. res, err := c.sendMessage(ctx, topicName, msg, &resTopicName, &resCmdType)
  278. return res, err
  279. }