uptime_kuma.go 4.0 KB

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