| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- package handler
- import (
- "context"
- "time"
- "spider/internal/model"
- "spider/internal/store"
- "github.com/gin-gonic/gin"
- "github.com/redis/go-redis/v9"
- )
- // DashboardHandler serves the dashboard summary endpoint.
- type DashboardHandler struct {
- store *store.Store
- rdb *redis.Client
- }
- // Get returns aggregated dashboard data.
- func (h *DashboardHandler) Get(c *gin.Context) {
- db := h.store.DB
- // Total counts (combined query for clean)
- var rawTotal int64
- db.Model(&model.MerchantRaw{}).Count(&rawTotal)
- // Single query for clean total and valid total
- var cleanTotal, validTotal int64
- db.Model(&model.MerchantClean{}).Count(&cleanTotal)
- db.Model(&model.MerchantClean{}).Where("status = ?", "valid").Count(&validTotal)
- // By level
- type kv struct {
- Key string `gorm:"column:key"`
- Count int64 `gorm:"column:count"`
- }
- var levelRows []kv
- db.Model(&model.MerchantClean{}).
- Select("level as `key`, count(*) as `count`").
- Group("level").
- Find(&levelRows)
- byLevel := gin.H{}
- for _, r := range levelRows {
- if r.Key == "" {
- r.Key = "Unknown"
- }
- byLevel[r.Key] = r.Count
- }
- // By status
- var statusRows []kv
- db.Model(&model.MerchantClean{}).
- Select("status as `key`, count(*) as `count`").
- Group("status").
- Find(&statusRows)
- byStatus := gin.H{}
- for _, r := range statusRows {
- byStatus[r.Key] = r.Count
- }
- // By source
- var sourceRows []kv
- db.Model(&model.MerchantRaw{}).
- Select("source_type as `key`, count(*) as `count`").
- Group("source_type").
- Find(&sourceRows)
- bySource := gin.H{}
- for _, r := range sourceRows {
- if r.Key == "" {
- r.Key = "unknown"
- }
- bySource[r.Key] = r.Count
- }
- // By industry
- var industryRows []kv
- db.Model(&model.MerchantClean{}).
- Select("industry_tag as `key`, count(*) as `count`").
- Where("industry_tag != ''").
- Group("industry_tag").
- Find(&industryRows)
- byIndustry := gin.H{}
- for _, r := range industryRows {
- byIndustry[r.Key] = r.Count
- }
- // Today added
- now := time.Now()
- todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
- var todayAdded int64
- db.Model(&model.MerchantRaw{}).Where("created_at >= ?", todayStart).Count(&todayAdded)
- // Week added
- weekStart := todayStart.AddDate(0, 0, -int(now.Weekday()))
- if now.Weekday() == 0 {
- weekStart = todayStart.AddDate(0, 0, -6)
- } else {
- weekStart = todayStart.AddDate(0, 0, -int(now.Weekday())+1)
- }
- var weekAdded int64
- db.Model(&model.MerchantRaw{}).Where("created_at >= ?", weekStart).Count(&weekAdded)
- // Recent tasks (last 5)
- var recentTasks []model.TaskLog
- db.Order("created_at DESC").Limit(5).Find(&recentTasks)
- // Daily trend (last 14 days)
- type trend struct {
- Date string `gorm:"column:date" json:"date"`
- Count int64 `gorm:"column:count" json:"count"`
- }
- var dailyTrend []trend
- fourteenDaysAgo := todayStart.AddDate(0, 0, -13)
- db.Model(&model.MerchantRaw{}).
- Select("DATE(created_at) as date, count(*) as `count`").
- Where("created_at >= ?", fourteenDaysAgo).
- Group("DATE(created_at)").
- Order("date ASC").
- Find(&dailyTrend)
- OK(c, gin.H{
- "raw_total": rawTotal,
- "clean_total": cleanTotal,
- "valid_total": validTotal,
- "by_level": byLevel,
- "by_status": byStatus,
- "by_source": bySource,
- "by_industry": byIndustry,
- "today_added": todayAdded,
- "week_added": weekAdded,
- "recent_tasks": recentTasks,
- "daily_trend": dailyTrend,
- })
- }
- // Health returns system component health status.
- func (h *DashboardHandler) Health(c *gin.Context) {
- db := h.store.DB
- health := gin.H{}
- // MySQL
- sqlDB, err := db.DB()
- if err != nil {
- health["mysql"] = gin.H{"status": "error", "error": err.Error()}
- } else {
- ctx, cancel := context.WithTimeout(c.Request.Context(), 3*time.Second)
- defer cancel()
- if err := sqlDB.PingContext(ctx); err != nil {
- health["mysql"] = gin.H{"status": "error", "error": err.Error()}
- } else {
- stats := sqlDB.Stats()
- health["mysql"] = gin.H{
- "status": "ok",
- "open_conns": stats.OpenConnections,
- "in_use": stats.InUse,
- "idle": stats.Idle,
- "max_open": stats.MaxOpenConnections,
- }
- }
- }
- // Redis
- ctx2, cancel2 := context.WithTimeout(c.Request.Context(), 3*time.Second)
- defer cancel2()
- if err := h.rdb.Ping(ctx2).Err(); err != nil {
- health["redis"] = gin.H{"status": "error", "error": err.Error()}
- } else {
- health["redis"] = gin.H{"status": "ok"}
- }
- // TG accounts
- type tgStatus struct {
- Status string
- Cnt int64
- }
- var tgRows []tgStatus
- db.Model(&model.TgAccount{}).Select("status, count(*) as cnt").Group("status").Scan(&tgRows)
- tgSummary := gin.H{}
- for _, r := range tgRows {
- tgSummary[r.Status] = r.Cnt
- }
- health["tg_accounts"] = tgSummary
- // Tasks last 24h
- yesterday := time.Now().Add(-24 * time.Hour)
- type taskStatus struct {
- Status string
- Cnt int64
- }
- var taskRows []taskStatus
- db.Model(&model.TaskLog{}).Select("status, count(*) as cnt").
- Where("created_at >= ?", yesterday).Group("status").Scan(&taskRows)
- taskSummary := gin.H{}
- for _, r := range taskRows {
- taskSummary[r.Status] = r.Cnt
- }
- health["tasks_24h"] = taskSummary
- // Data counts
- var rawCount, cleanCount, detailCount int64
- db.Model(&model.MerchantRaw{}).Count(&rawCount)
- db.Model(&model.MerchantClean{}).Count(&cleanCount)
- db.Model(&model.TaskDetail{}).Count(&detailCount)
- health["data"] = gin.H{
- "merchants_raw": rawCount,
- "merchants_clean": cleanCount,
- "task_details": detailCount,
- }
- OK(c, health)
- }
|