uptime_kuma.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. package controller
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "net/http"
  8. "one-api/common"
  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. common.OptionMapRWMutex.RLock()
  67. uptimeKumaUrl := common.OptionMap["UptimeKumaUrl"]
  68. slug := common.OptionMap["UptimeKumaSlug"]
  69. common.OptionMapRWMutex.RUnlock()
  70. if uptimeKumaUrl == "" || slug == "" {
  71. c.JSON(http.StatusOK, gin.H{
  72. "success": true,
  73. "message": "",
  74. "data": []MonitorStatus{},
  75. })
  76. return
  77. }
  78. uptimeKumaUrl = strings.TrimSuffix(uptimeKumaUrl, "/")
  79. ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Second)
  80. defer cancel()
  81. client := &http.Client{}
  82. statusPageUrl := fmt.Sprintf("%s/api/status-page/%s", uptimeKumaUrl, slug)
  83. heartbeatUrl := fmt.Sprintf("%s/api/status-page/heartbeat/%s", uptimeKumaUrl, slug)
  84. var (
  85. statusData UptimeKumaStatusResponse
  86. heartbeatData UptimeKumaHeartbeatResponse
  87. )
  88. g, gCtx := errgroup.WithContext(ctx)
  89. g.Go(func() error {
  90. return getAndDecode(gCtx, client, statusPageUrl, &statusData)
  91. })
  92. g.Go(func() error {
  93. return getAndDecode(gCtx, client, heartbeatUrl, &heartbeatData)
  94. })
  95. if err := g.Wait(); err != nil {
  96. switch err {
  97. case ErrUpstreamNon200:
  98. c.JSON(http.StatusBadRequest, gin.H{
  99. "success": false,
  100. "message": "上游接口出现问题",
  101. })
  102. case ErrTimeout:
  103. c.JSON(http.StatusRequestTimeout, gin.H{
  104. "success": false,
  105. "message": "请求上游接口超时",
  106. })
  107. default:
  108. c.JSON(http.StatusBadRequest, gin.H{
  109. "success": false,
  110. "message": err.Error(),
  111. })
  112. }
  113. return
  114. }
  115. var monitors []MonitorStatus
  116. for _, group := range statusData.PublicGroupList {
  117. for _, monitor := range group.MonitorList {
  118. monitorStatus := MonitorStatus{
  119. Name: monitor.Name,
  120. Uptime: 0.0,
  121. Status: 0,
  122. }
  123. uptimeKey := fmt.Sprintf("%d_24", monitor.ID)
  124. if uptime, exists := heartbeatData.UptimeList[uptimeKey]; exists {
  125. monitorStatus.Uptime = uptime
  126. }
  127. heartbeatKey := fmt.Sprintf("%d", monitor.ID)
  128. if heartbeats, exists := heartbeatData.HeartbeatList[heartbeatKey]; exists && len(heartbeats) > 0 {
  129. latestHeartbeat := heartbeats[0]
  130. monitorStatus.Status = latestHeartbeat.Status
  131. }
  132. monitors = append(monitors, monitorStatus)
  133. }
  134. }
  135. c.JSON(http.StatusOK, gin.H{
  136. "success": true,
  137. "message": "",
  138. "data": monitors,
  139. })
  140. }