dashboard.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. package handler
  2. import (
  3. "context"
  4. "time"
  5. "spider/internal/model"
  6. "spider/internal/store"
  7. "github.com/gin-gonic/gin"
  8. "github.com/redis/go-redis/v9"
  9. )
  10. // DashboardHandler serves the dashboard summary endpoint.
  11. type DashboardHandler struct {
  12. store *store.Store
  13. rdb *redis.Client
  14. }
  15. // Get returns aggregated dashboard data.
  16. func (h *DashboardHandler) Get(c *gin.Context) {
  17. db := h.store.DB
  18. // Total counts (combined query for clean)
  19. var rawTotal int64
  20. db.Model(&model.MerchantRaw{}).Count(&rawTotal)
  21. // Single query for clean total and valid total
  22. var cleanTotal, validTotal int64
  23. db.Model(&model.MerchantClean{}).Count(&cleanTotal)
  24. db.Model(&model.MerchantClean{}).Where("status = ?", "valid").Count(&validTotal)
  25. // By level
  26. type kv struct {
  27. Key string `gorm:"column:key"`
  28. Count int64 `gorm:"column:count"`
  29. }
  30. var levelRows []kv
  31. db.Model(&model.MerchantClean{}).
  32. Select("level as `key`, count(*) as `count`").
  33. Group("level").
  34. Find(&levelRows)
  35. byLevel := gin.H{}
  36. for _, r := range levelRows {
  37. if r.Key == "" {
  38. r.Key = "Unknown"
  39. }
  40. byLevel[r.Key] = r.Count
  41. }
  42. // By status
  43. var statusRows []kv
  44. db.Model(&model.MerchantClean{}).
  45. Select("status as `key`, count(*) as `count`").
  46. Group("status").
  47. Find(&statusRows)
  48. byStatus := gin.H{}
  49. for _, r := range statusRows {
  50. byStatus[r.Key] = r.Count
  51. }
  52. // By source
  53. var sourceRows []kv
  54. db.Model(&model.MerchantRaw{}).
  55. Select("source_type as `key`, count(*) as `count`").
  56. Group("source_type").
  57. Find(&sourceRows)
  58. bySource := gin.H{}
  59. for _, r := range sourceRows {
  60. if r.Key == "" {
  61. r.Key = "unknown"
  62. }
  63. bySource[r.Key] = r.Count
  64. }
  65. // By industry
  66. var industryRows []kv
  67. db.Model(&model.MerchantClean{}).
  68. Select("industry_tag as `key`, count(*) as `count`").
  69. Where("industry_tag != ''").
  70. Group("industry_tag").
  71. Find(&industryRows)
  72. byIndustry := gin.H{}
  73. for _, r := range industryRows {
  74. byIndustry[r.Key] = r.Count
  75. }
  76. // Today added
  77. now := time.Now()
  78. todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
  79. var todayAdded int64
  80. db.Model(&model.MerchantRaw{}).Where("created_at >= ?", todayStart).Count(&todayAdded)
  81. // Week added
  82. weekStart := todayStart.AddDate(0, 0, -int(now.Weekday()))
  83. if now.Weekday() == 0 {
  84. weekStart = todayStart.AddDate(0, 0, -6)
  85. } else {
  86. weekStart = todayStart.AddDate(0, 0, -int(now.Weekday())+1)
  87. }
  88. var weekAdded int64
  89. db.Model(&model.MerchantRaw{}).Where("created_at >= ?", weekStart).Count(&weekAdded)
  90. // Recent tasks (last 5)
  91. var recentTasks []model.TaskLog
  92. db.Order("created_at DESC").Limit(5).Find(&recentTasks)
  93. // Daily trend (last 14 days)
  94. type trend struct {
  95. Date string `gorm:"column:date" json:"date"`
  96. Count int64 `gorm:"column:count" json:"count"`
  97. }
  98. var dailyTrend []trend
  99. fourteenDaysAgo := todayStart.AddDate(0, 0, -13)
  100. db.Model(&model.MerchantRaw{}).
  101. Select("DATE(created_at) as date, count(*) as `count`").
  102. Where("created_at >= ?", fourteenDaysAgo).
  103. Group("DATE(created_at)").
  104. Order("date ASC").
  105. Find(&dailyTrend)
  106. OK(c, gin.H{
  107. "raw_total": rawTotal,
  108. "clean_total": cleanTotal,
  109. "valid_total": validTotal,
  110. "by_level": byLevel,
  111. "by_status": byStatus,
  112. "by_source": bySource,
  113. "by_industry": byIndustry,
  114. "today_added": todayAdded,
  115. "week_added": weekAdded,
  116. "recent_tasks": recentTasks,
  117. "daily_trend": dailyTrend,
  118. })
  119. }
  120. // Health returns system component health status.
  121. func (h *DashboardHandler) Health(c *gin.Context) {
  122. db := h.store.DB
  123. health := gin.H{}
  124. // MySQL
  125. sqlDB, err := db.DB()
  126. if err != nil {
  127. health["mysql"] = gin.H{"status": "error", "error": err.Error()}
  128. } else {
  129. ctx, cancel := context.WithTimeout(c.Request.Context(), 3*time.Second)
  130. defer cancel()
  131. if err := sqlDB.PingContext(ctx); err != nil {
  132. health["mysql"] = gin.H{"status": "error", "error": err.Error()}
  133. } else {
  134. stats := sqlDB.Stats()
  135. health["mysql"] = gin.H{
  136. "status": "ok",
  137. "open_conns": stats.OpenConnections,
  138. "in_use": stats.InUse,
  139. "idle": stats.Idle,
  140. "max_open": stats.MaxOpenConnections,
  141. }
  142. }
  143. }
  144. // Redis
  145. ctx2, cancel2 := context.WithTimeout(c.Request.Context(), 3*time.Second)
  146. defer cancel2()
  147. if err := h.rdb.Ping(ctx2).Err(); err != nil {
  148. health["redis"] = gin.H{"status": "error", "error": err.Error()}
  149. } else {
  150. health["redis"] = gin.H{"status": "ok"}
  151. }
  152. // TG accounts
  153. type tgStatus struct {
  154. Status string
  155. Cnt int64
  156. }
  157. var tgRows []tgStatus
  158. db.Model(&model.TgAccount{}).Select("status, count(*) as cnt").Group("status").Scan(&tgRows)
  159. tgSummary := gin.H{}
  160. for _, r := range tgRows {
  161. tgSummary[r.Status] = r.Cnt
  162. }
  163. health["tg_accounts"] = tgSummary
  164. // Tasks last 24h
  165. yesterday := time.Now().Add(-24 * time.Hour)
  166. type taskStatus struct {
  167. Status string
  168. Cnt int64
  169. }
  170. var taskRows []taskStatus
  171. db.Model(&model.TaskLog{}).Select("status, count(*) as cnt").
  172. Where("created_at >= ?", yesterday).Group("status").Scan(&taskRows)
  173. taskSummary := gin.H{}
  174. for _, r := range taskRows {
  175. taskSummary[r.Status] = r.Cnt
  176. }
  177. health["tasks_24h"] = taskSummary
  178. // Data counts
  179. var rawCount, cleanCount, detailCount int64
  180. db.Model(&model.MerchantRaw{}).Count(&rawCount)
  181. db.Model(&model.MerchantClean{}).Count(&cleanCount)
  182. db.Model(&model.TaskDetail{}).Count(&detailCount)
  183. health["data"] = gin.H{
  184. "merchants_raw": rawCount,
  185. "merchants_clean": cleanCount,
  186. "task_details": detailCount,
  187. }
  188. OK(c, health)
  189. }