mqttDataManageControllers.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. /*
  2. * MQTT 报文管理 API List(数据来自 iotcore-stat Elasticsearch,站点即 equipNo)
  3. *
  4. * 看板:
  5. * /mqttDataManage/total/daily - GET 日总量 query: date=yyyy.MM.dd
  6. * /mqttDataManage/total/monthly - GET 月总量 query: year, month
  7. * /mqttDataManage/total/dailyTrend - GET 月度每日总量 query: year, month
  8. * /mqttDataManage/site/daily - GET 站点日总量 query: date, page, size
  9. * /mqttDataManage/site/cmdType - GET 站点当日命令类型总量 query: equipNo, date, page, size
  10. * 查询:
  11. * /mqttDataManage/messages - GET 逐条报文 query: equipNo, cmdType, startTime, endTime, page, size
  12. *
  13. * 注:站点月总量列表(site/monthly)因查询非常慢、非常耗资源已下线,见 fwh_es_mqtt.GetSiteMonthlyTotals 备注。
  14. *
  15. * 时区:全部统一使用 UTC。date 参数(yyyy.MM.dd)对应 UTC 日索引;
  16. * startTime/endTime(yyyy-MM-dd HH:mm:ss)按 UTC 解析;聚合按 UTC 分桶。
  17. */
  18. package controllers
  19. import (
  20. "errors"
  21. "fmt"
  22. "fwh-toolbox-server/repositories"
  23. "fwh-toolbox-server/services"
  24. "regexp"
  25. "strconv"
  26. "time"
  27. "github.com/gofiber/fiber/v2"
  28. "go.uber.org/zap"
  29. )
  30. // 日期格式 yyyy.MM.dd(对应 ES 日索引)
  31. var esDateRegexp = regexp.MustCompile(`^\d{4}\.(0[1-9]|1[0-2])\.(0[1-9]|[12]\d|3[01])$`)
  32. // 时间格式 yyyy-MM-dd HH:mm:ss
  33. const mqttTimeLayout = "2006-01-02 15:04:05"
  34. // 解析分页参数(page 默认 1,size 默认 20)
  35. func parsePaging(c *fiber.Ctx) (int, int, error) {
  36. page, err := strconv.Atoi(c.Query("page", "1"))
  37. if err != nil || page <= 0 {
  38. return 0, 0, err
  39. }
  40. size, err := strconv.Atoi(c.Query("size", "20"))
  41. if err != nil || size <= 0 {
  42. return 0, 0, err
  43. }
  44. return page, size, nil
  45. }
  46. // 解析年月
  47. func parseYearMonth(c *fiber.Ctx) (int, int, bool) {
  48. year, err := strconv.Atoi(c.Query("year", ""))
  49. if err != nil || year < 2000 || year > 9999 {
  50. return 0, 0, false
  51. }
  52. month, err := strconv.Atoi(c.Query("month", ""))
  53. if err != nil || month < 1 || month > 12 {
  54. return 0, 0, false
  55. }
  56. return year, month, true
  57. }
  58. // GetMqttDailyTotal 日总量
  59. // /mqttDataManage/total/daily
  60. func GetMqttDailyTotal(c *fiber.Ctx) error {
  61. date := c.Query("date", "")
  62. if !esDateRegexp.MatchString(date) {
  63. zap.L().Warn("date parameter is in the wrong format", zap.String("date", date))
  64. return Fail(c, "date parameter is in the wrong format (yyyy.MM.dd)")
  65. }
  66. res, err := services.GetMqttDailyTotal(date)
  67. if err != nil {
  68. return Error(c, "Get MQTT daily total failed.")
  69. }
  70. return Success(c, res)
  71. }
  72. // GetMqttMonthlyTotal 月总量
  73. // /mqttDataManage/total/monthly
  74. func GetMqttMonthlyTotal(c *fiber.Ctx) error {
  75. year, month, ok := parseYearMonth(c)
  76. if !ok {
  77. return Fail(c, "year/month parameter is invalid")
  78. }
  79. res, err := services.GetMqttMonthlyTotal(year, month)
  80. if err != nil {
  81. return Error(c, "Get MQTT monthly total failed.")
  82. }
  83. return Success(c, res)
  84. }
  85. // GetMqttMonthlyDailyTrend 月度每日总量(看板柱状图,整月补零)
  86. // /mqttDataManage/total/dailyTrend
  87. func GetMqttMonthlyDailyTrend(c *fiber.Ctx) error {
  88. year, month, ok := parseYearMonth(c)
  89. if !ok {
  90. return Fail(c, "year/month parameter is invalid")
  91. }
  92. res, err := services.GetMqttMonthlyDailyTrend(year, month)
  93. if err != nil {
  94. return Error(c, "Get MQTT monthly daily trend failed.")
  95. }
  96. return Success(c, res)
  97. }
  98. // GetMqttSiteDailyTotals 站点日总量(分页)
  99. // /mqttDataManage/site/daily
  100. func GetMqttSiteDailyTotals(c *fiber.Ctx) error {
  101. date := c.Query("date", "")
  102. if !esDateRegexp.MatchString(date) {
  103. return Fail(c, "date parameter is in the wrong format (yyyy.MM.dd)")
  104. }
  105. page, size, err := parsePaging(c)
  106. if err != nil {
  107. return Fail(c, "page/size parameter is invalid")
  108. }
  109. res, err := services.GetMqttSiteDailyTotals(date, page, size)
  110. if err != nil {
  111. return Error(c, "Get MQTT site daily totals failed.")
  112. }
  113. return Success(c, res)
  114. }
  115. // GetMqttSiteCmdTypeTotals 站点当日命令类型总量(下钻,分页)
  116. // /mqttDataManage/site/cmdType
  117. func GetMqttSiteCmdTypeTotals(c *fiber.Ctx) error {
  118. equipNo := c.Query("equipNo", "")
  119. if equipNo == "" {
  120. return Fail(c, "equipNo parameter is empty")
  121. }
  122. date := c.Query("date", "")
  123. if !esDateRegexp.MatchString(date) {
  124. return Fail(c, "date parameter is in the wrong format (yyyy.MM.dd)")
  125. }
  126. page, size, err := parsePaging(c)
  127. if err != nil {
  128. return Fail(c, "page/size parameter is invalid")
  129. }
  130. res, err := services.GetMqttSiteCmdTypeTotals(equipNo, date, page, size)
  131. if err != nil {
  132. return Error(c, "Get MQTT site cmdType totals failed.")
  133. }
  134. return Success(c, res)
  135. }
  136. // QueryMqttMessages 逐条报文查询
  137. // /mqttDataManage/messages
  138. func QueryMqttMessages(c *fiber.Ctx) error {
  139. // 可选:equipNo
  140. var equipNoPtr *string
  141. if v := c.Query("equipNo", ""); v != "" {
  142. equipNoPtr = &v
  143. }
  144. // 可选:cmdType
  145. var cmdTypePtr *int64
  146. if v := c.Query("cmdType", ""); v != "" {
  147. cmdType, err := strconv.ParseInt(v, 10, 64)
  148. if err != nil {
  149. return Fail(c, "cmdType parameter is invalid")
  150. }
  151. cmdTypePtr = &cmdType
  152. }
  153. // 可选:startTime / endTime(yyyy-MM-dd HH:mm:ss,按 UTC 解析)
  154. var startPtr, endPtr *time.Time
  155. if v := c.Query("startTime", ""); v != "" {
  156. t, err := time.ParseInLocation(mqttTimeLayout, v, time.UTC)
  157. if err != nil {
  158. return Fail(c, "startTime parameter is in the wrong format (yyyy-MM-dd HH:mm:ss)")
  159. }
  160. startPtr = &t
  161. }
  162. if v := c.Query("endTime", ""); v != "" {
  163. t, err := time.ParseInLocation(mqttTimeLayout, v, time.UTC)
  164. if err != nil {
  165. return Fail(c, "endTime parameter is in the wrong format (yyyy-MM-dd HH:mm:ss)")
  166. }
  167. endPtr = &t
  168. }
  169. page, size, err := parsePaging(c)
  170. if err != nil {
  171. return Fail(c, "page/size parameter is invalid")
  172. }
  173. res, err := services.QueryMqttMessages(equipNoPtr, cmdTypePtr, startPtr, endPtr, page, size)
  174. if err != nil {
  175. // 深分页超出 ES 窗口上限:返回 400 友好提示,而非 500
  176. if errors.Is(err, repositories.ErrEsDeepPaging) {
  177. return Fail(c, fmt.Sprintf("查询超出可翻页范围:最多可浏览前 %d 条,请缩小时间范围或增加设备号/命令类型等过滤条件。",
  178. repositories.EsMaxMessagePageWindow))
  179. }
  180. return Error(c, "Query MQTT messages failed.")
  181. }
  182. return Success(c, res)
  183. }