package analytics import ( "backend/internal/dto" "backend/internal/model" "time" "gorm.io/gorm" ) // AnalyticsService 数据分析业务逻辑层 // Logan.Li type AnalyticsService struct { db *gorm.DB } // NewAnalyticsService 创建 service 实例 // Logan.Li func NewAnalyticsService(db *gorm.DB) *AnalyticsService { return &AnalyticsService{db: db} } // GetDashboardStats 仪表板统计 // Logan.Li func (s *AnalyticsService) GetDashboardStats() (*dto.DashboardStats, error) { var stats dto.DashboardStats s.db.Model(&model.License{}).Count(&stats.TotalLicenses) s.db.Model(&model.License{}).Where("status = ?", model.LicenseStatusActive).Count(&stats.ActiveLicenses) // 本月新增 // Logan.Li now := time.Now() monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) s.db.Model(&model.License{}).Where("created_at >= ?", monthStart).Count(&stats.NewThisMonth) // 即将到期(30 天内) // Logan.Li thirtyDaysLater := now.AddDate(0, 0, 30) s.db.Model(&model.License{}). Where("status = ? AND end_date > ? AND end_date < ?", model.LicenseStatusActive, now, thirtyDaysLater). Count(&stats.ExpiringSoon) s.db.Model(&model.Customer{}).Count(&stats.TotalCustomers) s.db.Model(&model.Product{}).Count(&stats.TotalProducts) // 在线设备(5 分钟内有心跳) // Logan.Li fiveMinAgo := now.Add(-5 * time.Minute) s.db.Model(&model.Activation{}).Where("is_online = ? AND last_seen_at >= ?", true, fiveMinAgo).Count(&stats.OnlineDevices) // 本月收入 // Logan.Li s.db.Model(&model.Order{}). Where("status = ? AND paid_at >= ?", "paid", monthStart). Select("COALESCE(SUM(amount), 0)"). Scan(&stats.MonthlyRevenue) return &stats, nil } // GetExpiringLicenses 即将到期 License 列表 // Logan.Li func (s *AnalyticsService) GetExpiringLicenses(days int) ([]dto.ExpiringLicenseItem, error) { if days <= 0 { days = 30 } now := time.Now() deadline := now.AddDate(0, 0, days) var licenses []model.License s.db.Preload("Customer").Preload("Product"). Where("status = ? AND end_date > ? AND end_date < ?", model.LicenseStatusActive, now, deadline). Order("end_date ASC").Limit(20).Find(&licenses) results := make([]dto.ExpiringLicenseItem, 0, len(licenses)) for _, l := range licenses { results = append(results, dto.ExpiringLicenseItem{ ID: l.ID, LicenseKey: l.LicenseKey, CustomerName: l.Customer.Name, ProductName: l.Product.Name, EndDate: l.EndDate.Format("2006-01-02"), DaysLeft: int(l.EndDate.Sub(now).Hours() / 24), }) } return results, nil } // GetRecentActivities 最近操作活动 // Logan.Li func (s *AnalyticsService) GetRecentActivities(limit int) ([]dto.RecentActivityItem, error) { if limit <= 0 { limit = 10 } var logs []model.AuditLog s.db.Order("created_at DESC").Limit(limit).Find(&logs) results := make([]dto.RecentActivityItem, 0, len(logs)) for _, l := range logs { results = append(results, dto.RecentActivityItem{ ID: l.ID, Username: l.Username, Action: l.Action, TargetType: l.TargetType, TargetName: l.TargetName, Detail: l.Detail, CreatedAt: l.CreatedAt.Format("2006-01-02 15:04:05"), }) } return results, nil } // GetOnlineStats 在线统计 // Logan.Li func (s *AnalyticsService) GetOnlineStats() (map[string]interface{}, error) { now := time.Now() fiveMinAgo := now.Add(-5 * time.Minute) var onlineDevices int64 var totalActivations int64 s.db.Model(&model.Activation{}).Where("is_online = ? AND last_seen_at >= ?", true, fiveMinAgo).Count(&onlineDevices) s.db.Model(&model.Activation{}).Where("status = ?", "active").Count(&totalActivations) return map[string]interface{}{ "online_devices": onlineDevices, "total_activations": totalActivations, "offline_devices": totalActivations - onlineDevices, }, nil } // GetRevenueStats 收入统计(近 6 个月) // Logan.Li func (s *AnalyticsService) GetRevenueStats() ([]dto.RevenuePoint, error) { now := time.Now() results := make([]dto.RevenuePoint, 0, 6) for i := 5; i >= 0; i-- { monthStart := time.Date(now.Year(), now.Month()-time.Month(i), 1, 0, 0, 0, 0, now.Location()) nextMonth := monthStart.AddDate(0, 1, 0) var amount float64 s.db.Model(&model.Order{}). Where("status = ? AND created_at >= ? AND created_at < ?", "paid", monthStart, nextMonth). Select("COALESCE(SUM(amount), 0)"). Scan(&amount) results = append(results, dto.RevenuePoint{ Period: monthStart.Format("2006-01"), Amount: amount, }) } return results, nil } // GetLicenseGrowth License 增长趋势(近 6 个月) // Logan.Li func (s *AnalyticsService) GetLicenseGrowth() ([]dto.LicenseGrowthPoint, error) { now := time.Now() results := make([]dto.LicenseGrowthPoint, 0, 6) for i := 5; i >= 0; i-- { monthEnd := time.Date(now.Year(), now.Month()-time.Month(i)+1, 0, 23, 59, 59, 0, now.Location()) var count int64 s.db.Model(&model.License{}).Where("created_at <= ?", monthEnd).Count(&count) results = append(results, dto.LicenseGrowthPoint{ Date: monthEnd.Format("2006-01"), Count: count, }) } return results, nil } // GetProductDistribution 产品分布 // Logan.Li func (s *AnalyticsService) GetProductDistribution() ([]dto.ProductDistribution, error) { var results []dto.ProductDistribution s.db.Model(&model.License{}). Select("products.name as product_name, count(licenses.id) as count"). Joins("LEFT JOIN products ON products.id = licenses.product_id"). Group("licenses.product_id"). Scan(&results) return results, nil }