uptime_kuma.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package controller
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "net/http"
  8. "one-api/setting/console_setting"
  9. "strings"
  10. "time"
  11. "github.com/gin-gonic/gin"
  12. "golang.org/x/sync/errgroup"
  13. )
  14. type UptimeKumaMonitor struct {
  15. ID int `json:"id"`
  16. Name string `json:"name"`
  17. Type string `json:"type"`
  18. }
  19. type UptimeKumaGroup struct {
  20. ID int `json:"id"`
  21. Name string `json:"name"`
  22. Weight int `json:"weight"`
  23. MonitorList []UptimeKumaMonitor `json:"monitorList"`
  24. }
  25. type UptimeKumaHeartbeat struct {
  26. Status int `json:"status"`
  27. Time string `json:"time"`
  28. Msg string `json:"msg"`
  29. Ping *float64 `json:"ping"`
  30. }
  31. type UptimeKumaStatusResponse struct {
  32. PublicGroupList []UptimeKumaGroup `json:"publicGroupList"`
  33. }
  34. type UptimeKumaHeartbeatResponse struct {
  35. HeartbeatList map[string][]UptimeKumaHeartbeat `json:"heartbeatList"`
  36. UptimeList map[string]float64 `json:"uptimeList"`
  37. }
  38. type MonitorStatus struct {
  39. Name string `json:"name"`
  40. Uptime float64 `json:"uptime"`
  41. Status int `json:"status"`
  42. }
  43. var (
  44. ErrUpstreamNon200 = errors.New("upstream non-200")
  45. ErrTimeout = errors.New("context deadline exceeded")
  46. )
  47. func getAndDecode(ctx context.Context, client *http.Client, url string, dest interface{}) error {
  48. req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
  49. if err != nil {
  50. return err
  51. }
  52. resp, err := client.Do(req)
  53. if err != nil {
  54. if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
  55. return ErrTimeout
  56. }
  57. return err
  58. }
  59. defer resp.Body.Close()
  60. if resp.StatusCode != http.StatusOK {
  61. return ErrUpstreamNon200
  62. }
  63. return json.NewDecoder(resp.Body).Decode(dest)
  64. }
  65. func GetUptimeKumaStatus(c *gin.Context) {
  66. cs := console_setting.GetConsoleSetting()
  67. uptimeKumaUrl := cs.UptimeKumaUrl
  68. slug := cs.UptimeKumaSlug
  69. if uptimeKumaUrl == "" || slug == "" {
  70. c.JSON(http.StatusOK, gin.H{
  71. "success": true,
  72. "message": "",
  73. "data": []MonitorStatus{},
  74. })
  75. return
  76. }
  77. uptimeKumaUrl = strings.TrimSuffix(uptimeKumaUrl, "/")
  78. ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Second)
  79. defer cancel()
  80. client := &http.Client{}
  81. statusPageUrl := fmt.Sprintf("%s/api/status-page/%s", uptimeKumaUrl, slug)
  82. heartbeatUrl := fmt.Sprintf("%s/api/status-page/heartbeat/%s", uptimeKumaUrl, slug)
  83. var (
  84. statusData UptimeKumaStatusResponse
  85. heartbeatData UptimeKumaHeartbeatResponse
  86. )
  87. g, gCtx := errgroup.WithContext(ctx)
  88. g.Go(func() error {
  89. return getAndDecode(gCtx, client, statusPageUrl, &statusData)
  90. })
  91. g.Go(func() error {
  92. return getAndDecode(gCtx, client, heartbeatUrl, &heartbeatData)
  93. })
  94. if err := g.Wait(); err != nil {
  95. switch err {
  96. case ErrUpstreamNon200:
  97. c.JSON(http.StatusBadRequest, gin.H{
  98. "success": false,
  99. "message": "上游接口出现问题",
  100. })
  101. case ErrTimeout:
  102. c.JSON(http.StatusRequestTimeout, gin.H{
  103. "success": false,
  104. "message": "请求上游接口超时",
  105. })
  106. default:
  107. c.JSON(http.StatusBadRequest, gin.H{
  108. "success": false,
  109. "message": err.Error(),
  110. })
  111. }
  112. return
  113. }
  114. var monitors []MonitorStatus
  115. for _, group := range statusData.PublicGroupList {
  116. for _, monitor := range group.MonitorList {
  117. monitorStatus := MonitorStatus{
  118. Name: monitor.Name,
  119. Uptime: 0.0,
  120. Status: 0,
  121. }
  122. uptimeKey := fmt.Sprintf("%d_24", monitor.ID)
  123. if uptime, exists := heartbeatData.UptimeList[uptimeKey]; exists {
  124. monitorStatus.Uptime = uptime
  125. }
  126. heartbeatKey := fmt.Sprintf("%d", monitor.ID)
  127. if heartbeats, exists := heartbeatData.HeartbeatList[heartbeatKey]; exists && len(heartbeats) > 0 {
  128. latestHeartbeat := heartbeats[0]
  129. monitorStatus.Status = latestHeartbeat.Status
  130. }
  131. monitors = append(monitors, monitorStatus)
  132. }
  133. }
  134. c.JSON(http.StatusOK, gin.H{
  135. "success": true,
  136. "message": "",
  137. "data": monitors,
  138. })
  139. }