Files
cid/service/stat_report_service.go
2025-12-06 15:24:30 +08:00

515 lines
14 KiB
Go

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
}