package service import ( "context" "fmt" "time" "cidservice/dao" "cidservice/model/dto" "cidservice/model/entity" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" ) type StatReportService struct{} var StatReport = &StatReportService{} // 生成日报表 func (s *StatReportService) GenerateDailyReport(ctx context.Context, req *dto.ReportGenerateReq) (*dto.ReportGenerateResp, error) { // 获取统计日期 reportDate := time.Now() if req.Date != "" { parsedDate, err := time.Parse("2006-01-02", req.Date) if err == nil { reportDate = parsedDate } } // 生成日报表数据 reportData, err := s.generateReportData(ctx, req.TenantID, req.AppID, "daily", reportDate) if err != nil { return nil, err } // 保存报表 report := &entity.StatReport{ TenantId: req.TenantID, AppID: req.AppID, ReportType: "daily", ReportDate: reportDate, ReportData: gconv.String(reportData), GeneratedAt: time.Now(), Status: "completed", } _, err = dao.StatReport.Create(ctx, report) if err != nil { return nil, err } return &dto.ReportGenerateResp{ ReportID: report.Id, ReportType: "daily", ReportDate: reportDate.Format("2006-01-02"), Data: reportData, }, nil } // 生成月报表 func (s *StatReportService) GenerateMonthlyReport(ctx context.Context, req *dto.ReportGenerateReq) (*dto.ReportGenerateResp, error) { reportDate := time.Now() if req.Date != "" { parsedDate, err := time.Parse("2006-01", req.Date) if err == nil { reportDate = parsedDate } } reportData, err := s.generateReportData(ctx, req.TenantID, req.AppID, "monthly", reportDate) if err != nil { return nil, err } report := &entity.StatReport{ TenantId: req.TenantID, AppID: req.AppID, ReportType: "monthly", ReportDate: reportDate, ReportData: gconv.String(reportData), GeneratedAt: time.Now(), Status: "completed", } _, err = dao.StatReport.Create(ctx, report) if err != nil { return nil, err } return &dto.ReportGenerateResp{ ReportID: report.Id, ReportType: "monthly", ReportDate: reportDate.Format("2006-01"), Data: reportData, }, nil } // 生成季度报表 func (s *StatReportService) GenerateQuarterlyReport(ctx context.Context, req *dto.ReportGenerateReq) (*dto.ReportGenerateResp, error) { reportDate := time.Now() if req.Date != "" { parsedDate, err := time.Parse("2006-Q1", req.Date) if err == nil { reportDate = parsedDate } } reportData, err := s.generateReportData(ctx, req.TenantID, req.AppID, "quarterly", reportDate) if err != nil { return nil, err } report := &entity.StatReport{ TenantId: req.TenantID, AppID: req.AppID, ReportType: "quarterly", ReportDate: reportDate, ReportData: gconv.String(reportData), GeneratedAt: time.Now(), Status: "completed", } _, err = dao.StatReport.Create(ctx, report) if err != nil { return nil, err } return &dto.ReportGenerateResp{ ReportID: report.Id, ReportType: "quarterly", ReportDate: reportDate.Format("2006-Q1"), Data: reportData, }, nil } // 生成年报表 func (s *StatReportService) GenerateYearlyReport(ctx context.Context, req *dto.ReportGenerateReq) (*dto.ReportGenerateResp, error) { reportDate := time.Now() if req.Date != "" { parsedDate, err := time.Parse("2006", req.Date) if err == nil { reportDate = parsedDate } } reportData, err := s.generateReportData(ctx, req.TenantID, req.AppID, "yearly", reportDate) if err != nil { return nil, err } report := &entity.StatReport{ TenantId: req.TenantID, AppID: req.AppID, ReportType: "yearly", ReportDate: reportDate, ReportData: gconv.String(reportData), GeneratedAt: time.Now(), Status: "completed", } _, err = dao.StatReport.Create(ctx, report) if err != nil { return nil, err } return &dto.ReportGenerateResp{ ReportID: report.Id, ReportType: "yearly", ReportDate: reportDate.Format("2006"), Data: reportData, }, nil } // 生成报表数据 func (s *StatReportService) generateReportData(ctx context.Context, tenantID, appID int64, reportType string, reportDate time.Time) (map[string]interface{}, error) { // 构建查询条件 where := g.Map{"tenant_id": tenantID} if appID > 0 { where["app_id"] = appID } // 根据报表类型确定时间范围 startTime, endTime := s.getReportTimeRange(reportType, reportDate) where["created_at between ? and ?"] = g.Slice{startTime, endTime} // 查询基础统计数据 var stats []map[string]interface{} err := g.DB().Model("ad_statistics").Where(where).Scan(&stats) if err != nil { return nil, err } // 计算环比数据 yoyData, err := s.calculateYearOverYear(ctx, tenantID, appID, reportType, reportDate) if err != nil { return nil, err } // 计算同比数据 momData, err := s.calculateMonthOverMonth(ctx, tenantID, appID, reportType, reportDate) if err != nil { return nil, err } // 按广告类型分组统计 adTypeStats, err := s.groupByAdType(stats) if err != nil { return nil, err } // 按地区分组统计 regionStats := s.groupByRegion(stats) // 按终端类型分组统计 platformStats := s.groupByPlatform(stats) return map[string]interface{}{ "basic_stats": map[string]interface{}{ "total_impressions": s.sumField(stats, "impressions"), "total_clicks": s.sumField(stats, "clicks"), "total_revenue": s.sumField(stats, "revenue"), "avg_ctr": s.calculateCTR(stats), "avg_play_duration": s.avgField(stats, "play_duration"), }, "ad_type_stats": adTypeStats, "region_stats": regionStats, "platform_stats": platformStats, "year_over_year": yoyData, "month_over_month": momData, "time_range": map[string]string{ "start": startTime.Format("2006-01-02 15:04:05"), "end": endTime.Format("2006-01-02 15:04:05"), }, }, nil } // 获取报表时间范围 func (s *StatReportService) getReportTimeRange(reportType string, reportDate time.Time) (time.Time, time.Time) { switch reportType { case "daily": start := time.Date(reportDate.Year(), reportDate.Month(), reportDate.Day(), 0, 0, 0, 0, time.Local) end := start.AddDate(0, 0, 1).Add(-time.Second) return start, end case "monthly": start := time.Date(reportDate.Year(), reportDate.Month(), 1, 0, 0, 0, 0, time.Local) end := start.AddDate(0, 1, 0).Add(-time.Second) return start, end case "quarterly": quarter := (reportDate.Month()-1)/3 + 1 startMonth := time.Month((quarter-1)*3 + 1) start := time.Date(reportDate.Year(), startMonth, 1, 0, 0, 0, 0, time.Local) end := start.AddDate(0, 3, 0).Add(-time.Second) return start, end case "yearly": start := time.Date(reportDate.Year(), 1, 1, 0, 0, 0, 0, time.Local) end := start.AddDate(1, 0, 0).Add(-time.Second) return start, end default: return reportDate, reportDate } } // 计算同比数据 func (s *StatReportService) calculateYearOverYear(ctx context.Context, tenantID, appID int64, reportType string, reportDate time.Time) (map[string]interface{}, error) { lastYearDate := reportDate.AddDate(-1, 0, 0) lastYearData, err := s.getComparisonData(ctx, tenantID, appID, reportType, lastYearDate) if err != nil { return nil, err } return map[string]interface{}{ "last_year": lastYearData, "growth_rate": s.calculateGrowthRate(lastYearData, s.getCurrentPeriodData(ctx, tenantID, appID, reportType, reportDate)), }, nil } // 计算环比数据 func (s *StatReportService) calculateMonthOverMonth(ctx context.Context, tenantID, appID int64, reportType string, reportDate time.Time) (map[string]interface{}, error) { var lastPeriodDate time.Time switch reportType { case "daily": lastPeriodDate = reportDate.AddDate(0, 0, -1) case "monthly": lastPeriodDate = reportDate.AddDate(0, -1, 0) case "quarterly": lastPeriodDate = reportDate.AddDate(0, -3, 0) case "yearly": lastPeriodDate = reportDate.AddDate(-1, 0, 0) } lastPeriodData, err := s.getComparisonData(ctx, tenantID, appID, reportType, lastPeriodDate) if err != nil { return nil, err } return map[string]interface{}{ "last_period": lastPeriodData, "growth_rate": s.calculateGrowthRate(lastPeriodData, s.getCurrentPeriodData(ctx, tenantID, appID, reportType, reportDate)), }, nil } // 获取对比数据 func (s *StatReportService) getComparisonData(ctx context.Context, tenantID, appID int64, reportType string, date time.Time) (map[string]float64, error) { // 这里简化实现,实际应该查询数据库 return map[string]float64{ "impressions": 1000, "clicks": 50, "revenue": 500.0, "ctr": 0.05, }, nil } // 获取当前周期数据 func (s *StatReportService) getCurrentPeriodData(ctx context.Context, tenantID, appID int64, reportType string, date time.Time) map[string]float64 { // 这里简化实现,实际应该查询数据库 return map[string]float64{ "impressions": 1200, "clicks": 60, "revenue": 600.0, "ctr": 0.05, } } // 计算增长率 func (s *StatReportService) calculateGrowthRate(lastData, currentData map[string]float64) map[string]float64 { growthRate := make(map[string]float64) for key, lastValue := range lastData { currentValue := currentData[key] if lastValue == 0 { growthRate[key] = 0 } else { growthRate[key] = (currentValue - lastValue) / lastValue * 100 } } return growthRate } // 按广告类型分组统计 func (s *StatReportService) groupByAdType(stats []map[string]interface{}) (map[string]interface{}, error) { result := make(map[string]interface{}) for _, stat := range stats { adType := gconv.String(stat["ad_type"]) if adType == "" { adType = "unknown" } if _, exists := result[adType]; !exists { result[adType] = map[string]float64{ "impressions": 0, "clicks": 0, "revenue": 0, } } adTypeStat, ok := result[adType].(map[string]float64) if !ok { return nil, fmt.Errorf("invalid adTypeStat type") } adTypeStat["impressions"] += gconv.Float64(stat["impressions"]) adTypeStat["clicks"] += gconv.Float64(stat["clicks"]) adTypeStat["revenue"] += gconv.Float64(stat["revenue"]) } // 计算每个广告类型的CTR for adType, stat := range result { adTypeStat, ok := stat.(map[string]float64) if !ok { return nil, fmt.Errorf("invalid adTypeStat type for adType: %s", adType) } if adTypeStat["impressions"] > 0 { adTypeStat["ctr"] = adTypeStat["clicks"] / adTypeStat["impressions"] * 100 } else { adTypeStat["ctr"] = 0 } } return result, nil } // 按地区分组统计 func (s *StatReportService) groupByRegion(stats []map[string]interface{}) map[string]interface{} { result := make(map[string]interface{}) for _, stat := range stats { region := gconv.String(stat["region"]) if region == "" { region = "unknown" } if _, exists := result[region]; !exists { result[region] = map[string]float64{ "impressions": 0, "clicks": 0, "revenue": 0, } } regionStat := result[region].(map[string]float64) regionStat["impressions"] += gconv.Float64(stat["impressions"]) regionStat["clicks"] += gconv.Float64(stat["clicks"]) regionStat["revenue"] += gconv.Float64(stat["revenue"]) } return result } // 按终端类型分组统计 func (s *StatReportService) groupByPlatform(stats []map[string]interface{}) map[string]interface{} { result := make(map[string]interface{}) for _, stat := range stats { platform := gconv.String(stat["platform"]) if platform == "" { platform = "unknown" } if _, exists := result[platform]; !exists { result[platform] = map[string]float64{ "impressions": 0, "clicks": 0, "revenue": 0, } } platformStat := result[platform].(map[string]float64) platformStat["impressions"] += gconv.Float64(stat["impressions"]) platformStat["clicks"] += gconv.Float64(stat["clicks"]) platformStat["revenue"] += gconv.Float64(stat["revenue"]) } return result } // 计算字段总和 func (s *StatReportService) sumField(stats []map[string]interface{}, field string) float64 { total := 0.0 for _, stat := range stats { total += gconv.Float64(stat[field]) } return total } // 计算字段平均值 func (s *StatReportService) avgField(stats []map[string]interface{}, field string) float64 { if len(stats) == 0 { return 0 } return s.sumField(stats, field) / float64(len(stats)) } // 计算平均CTR func (s *StatReportService) calculateCTR(stats []map[string]interface{}) float64 { totalImpressions := s.sumField(stats, "impressions") totalClicks := s.sumField(stats, "clicks") if totalImpressions == 0 { return 0 } return totalClicks / totalImpressions * 100 } // 查询报表列表 func (s *StatReportService) GetReportList(ctx context.Context, req *dto.ReportListReq) (*dto.ReportListResp, error) { // 使用DAO的List方法 reports, count, err := dao.StatReport.List(ctx, req.TenantID, req.AppID, req.ReportType, req.StartDate, req.EndDate, req.Page, req.PageSize) if err != nil { return nil, err } // 转换为DTO var reportDTOs []*dto.ReportDTO for _, report := range reports { reportDTOs = append(reportDTOs, &dto.ReportDTO{ ID: report.Id, TenantID: report.TenantId, AppID: report.AppID, ReportType: report.ReportType, ReportDate: report.ReportDate.Format("2006-01-02"), GeneratedAt: report.GeneratedAt.Format("2006-01-02 15:04:05"), }) } return &dto.ReportListResp{ Reports: reportDTOs, Total: count, Page: req.Page, PageSize: req.PageSize, }, nil } // 获取报表详情 func (s *StatReportService) GetReportDetail(ctx context.Context, reportID int64) (*dto.ReportDetailResp, error) { var report *entity.StatReport report, err := dao.StatReport.GetByID(ctx, reportID) if err != nil { return nil, err } if report == nil { return nil, fmt.Errorf("报表不存在") } // 解析报表数据 var reportData map[string]interface{} if err := gconv.Struct(report.ReportData, &reportData); err != nil { return nil, err } return &dto.ReportDetailResp{ ID: report.Id, TenantID: report.TenantId, AppID: report.AppID, ReportType: report.ReportType, ReportDate: report.ReportDate.Format("2006-01-02"), GeneratedAt: report.GeneratedAt.Format("2006-01-02 15:04:05"), Data: reportData, }, nil }