Files
cid/service/ad_statistics_service.go
2025-12-09 16:10:45 +08:00

368 lines
9.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"context"
"fmt"
"sort"
"time"
"cid/dao"
"cid/model/dto"
"cid/model/entity"
)
var AdStatistics = new(adStatistics)
type adStatistics struct{}
// List 获取统计数据列表
func (s *adStatistics) List(ctx context.Context, req *dto.GetAdStatisticsReq) (res *dto.GetAdStatisticsRes, err error) {
list, total, err := dao.AdStatistics.List(ctx, req)
if err != nil {
return nil, err
}
res = &dto.GetAdStatisticsRes{
Statistics: list,
Total: int(total),
}
return
}
// GetStatistics 获取统计数据
func (s *adStatistics) GetStatistics(ctx context.Context, req *dto.GetAdStatisticsReq) (res *dto.GetAdStatisticsRes, err error) {
list, total, err := dao.AdStatistics.GetStatistics(ctx, req)
if err != nil {
return nil, err
}
res = &dto.GetAdStatisticsRes{
Statistics: list,
Total: int(total),
}
return
}
// GetDashboard 获取仪表盘数据
func (s *adStatistics) GetDashboard(ctx context.Context, req *dto.GetDashboardReq) (res *dto.GetDashboardRes, err error) {
// 构建统计查询请求
statReq := &dto.GetAdStatisticsReq{
StartDate: req.StartDate,
EndDate: req.EndDate,
StatDimension: req.Dimension,
}
// 获取所有统计数据
stats, _, err := dao.AdStatistics.GetStatistics(ctx, statReq)
if err != nil {
return nil, err
}
// 计算总览数据
overview := s.calculateOverview(stats)
// 计算趋势数据
trends := s.calculateTrends(stats, req.Dimension)
// 计算排行数据
topAdvertisers := s.calculateTopAdvertisers(stats)
topAds := s.calculateTopAds(stats)
topPositions := s.calculateTopPositions(stats)
res = &dto.GetDashboardRes{
Overview: overview,
Trends: trends,
TopAdvertisers: topAdvertisers,
TopAds: topAds,
TopPositions: topPositions,
}
return
}
// GenerateDailyStatistics 生成每日统计数据
func (s *adStatistics) GenerateDailyStatistics(ctx context.Context, date int64) (err error) {
// 转换日期
t := time.Unix(date, 0)
startOfDay := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
endOfDay := startOfDay.Add(24 * time.Hour)
// 生成广告主统计数据
err = s.generateAdvertiserStatistics(ctx, startOfDay.Unix(), endOfDay.Unix())
if err != nil {
return fmt.Errorf("生成广告主统计数据失败: %v", err)
}
// 生成广告统计数据
err = s.generateAdvertisementStatistics(ctx, startOfDay.Unix(), endOfDay.Unix())
if err != nil {
return fmt.Errorf("生成广告统计数据失败: %v", err)
}
// 生成广告位统计数据
err = s.generateAdPositionStatistics(ctx, startOfDay.Unix(), endOfDay.Unix())
if err != nil {
return fmt.Errorf("生成广告位统计数据失败: %v", err)
}
return
}
// generateAdvertiserStatistics 生成广告主统计数据
func (s *adStatistics) generateAdvertiserStatistics(ctx context.Context, startDate int64, _ int64) (err error) {
// 这里简化处理,实际项目中应该从日志表或实时数据中聚合
advertiserStats := &entity.AdStatistics{
StatType: "advertiser",
StatDimension: "day",
TargetId: "example_advertiser_id",
TargetName: "示例广告主",
StatDate: startDate,
Impressions: 10000,
Clicks: 500,
Conversions: 50,
Cost: 50000, // 500元单位分
CTR: 0.05, // 5%
CVR: 0.1, // 10%
CPM: 5000, // 50元/千次展示
CPC: 100, // 1元/点击
}
err = dao.AdStatistics.Upsert(ctx, advertiserStats)
return
}
// generateAdvertisementStatistics 生成广告统计数据
func (s *adStatistics) generateAdvertisementStatistics(ctx context.Context, startDate int64, _ int64) (err error) {
// 这里简化处理,实际项目中应该从日志表或实时数据中聚合
adStats := &entity.AdStatistics{
StatType: "advertisement",
StatDimension: "day",
TargetId: "example_ad_id",
TargetName: "示例广告",
StatDate: startDate,
Impressions: 1000,
Clicks: 50,
Conversions: 5,
Cost: 5000, // 50元单位分
CTR: 0.05, // 5%
CVR: 0.1, // 10%
CPM: 5000, // 50元/千次展示
CPC: 100, // 1元/点击
}
err = dao.AdStatistics.Upsert(ctx, adStats)
return
}
// generateAdPositionStatistics 生成广告位统计数据
func (s *adStatistics) generateAdPositionStatistics(ctx context.Context, startDate int64, _ int64) (err error) {
// 这里简化处理,实际项目中应该从日志表或实时数据中聚合
positionStats := &entity.AdStatistics{
StatType: "adPosition",
StatDimension: "day",
TargetId: "example_position_id",
TargetName: "示例广告位",
StatDate: startDate,
Impressions: 5000,
Clicks: 250,
Revenue: 6000, // 60元单位分
CTR: 0.05, // 5%
}
err = dao.AdStatistics.Upsert(ctx, positionStats)
return
}
// calculateOverview 计算总览数据
func (s *adStatistics) calculateOverview(stats []*entity.AdStatistics) dto.OverviewData {
var totalImpressions, totalClicks, totalCost, totalRevenue int64
var totalCTR, totalCVR float64
var count int
// 统计不同类型的数据
advertiserCount := make(map[string]bool)
adCount := make(map[string]bool)
positionCount := make(map[string]bool)
for _, stat := range stats {
totalImpressions += stat.Impressions
totalClicks += stat.Clicks
totalCost += stat.Cost
totalRevenue += stat.Revenue
totalCTR += stat.CTR
totalCVR += stat.CVR
count++
// 统计不同实体的数量
if stat.StatType == "advertiser" {
advertiserCount[stat.TargetId] = true
} else if stat.StatType == "advertisement" {
adCount[stat.TargetId] = true
} else if stat.StatType == "adPosition" {
positionCount[stat.TargetId] = true
}
}
// 计算平均值
averageCTR := 0.0
averageCVR := 0.0
if count > 0 {
averageCTR = totalCTR / float64(count)
averageCVR = totalCVR / float64(count)
}
return dto.OverviewData{
TotalAdvertisers: int64(len(advertiserCount)),
TotalAds: int64(len(adCount)),
TotalPositions: int64(len(positionCount)),
TotalImpressions: totalImpressions,
TotalClicks: totalClicks,
TotalCost: totalCost,
TotalRevenue: totalRevenue,
AverageCTR: averageCTR,
AverageCVR: averageCVR,
}
}
// calculateTrends 计算趋势数据
func (s *adStatistics) calculateTrends(stats []*entity.AdStatistics, dimension string) []dto.TrendData {
trends := make([]dto.TrendData, 0)
// 按日期分组统计数据
dateMap := make(map[int64]*dto.TrendData)
for _, stat := range stats {
if _, exists := dateMap[stat.StatDate]; !exists {
dateMap[stat.StatDate] = &dto.TrendData{
Date: stat.StatDate,
Impressions: 0,
Clicks: 0,
Cost: 0,
Revenue: 0,
}
}
trend := dateMap[stat.StatDate]
trend.Impressions += stat.Impressions
trend.Clicks += stat.Clicks
trend.Cost += stat.Cost
trend.Revenue += stat.Revenue
}
// 转换为切片并排序
for _, trend := range dateMap {
trends = append(trends, *trend)
}
// 按日期排序
sort.Slice(trends, func(i, j int) bool {
return trends[i].Date < trends[j].Date
})
return trends
}
// calculateTopAdvertisers 计算广告主排行
func (s *adStatistics) calculateTopAdvertisers(stats []*entity.AdStatistics) []dto.RankData {
advertiserMap := make(map[string]*dto.RankData)
for _, stat := range stats {
if stat.StatType == "advertiser" {
if _, exists := advertiserMap[stat.TargetId]; !exists {
advertiserMap[stat.TargetId] = &dto.RankData{
Id: stat.TargetId,
Name: stat.TargetName,
Impressions: 0,
Clicks: 0,
Cost: 0,
Revenue: 0,
}
}
rank := advertiserMap[stat.TargetId]
rank.Impressions += stat.Impressions
rank.Clicks += stat.Clicks
rank.Cost += stat.Cost
rank.Revenue += stat.Revenue
}
}
return s.sortRankData(advertiserMap)
}
// calculateTopAds 计算广告排行
func (s *adStatistics) calculateTopAds(stats []*entity.AdStatistics) []dto.RankData {
adMap := make(map[string]*dto.RankData)
for _, stat := range stats {
if stat.StatType == "advertisement" {
if _, exists := adMap[stat.TargetId]; !exists {
adMap[stat.TargetId] = &dto.RankData{
Id: stat.TargetId,
Name: stat.TargetName,
Impressions: 0,
Clicks: 0,
Cost: 0,
Revenue: 0,
}
}
rank := adMap[stat.TargetId]
rank.Impressions += stat.Impressions
rank.Clicks += stat.Clicks
rank.Cost += stat.Cost
rank.Revenue += stat.Revenue
}
}
return s.sortRankData(adMap)
}
// calculateTopPositions 计算广告位排行
func (s *adStatistics) calculateTopPositions(stats []*entity.AdStatistics) []dto.RankData {
positionMap := make(map[string]*dto.RankData)
for _, stat := range stats {
if stat.StatType == "adPosition" {
if _, exists := positionMap[stat.TargetId]; !exists {
positionMap[stat.TargetId] = &dto.RankData{
Id: stat.TargetId,
Name: stat.TargetName,
Impressions: 0,
Clicks: 0,
Cost: 0,
Revenue: 0,
}
}
rank := positionMap[stat.TargetId]
rank.Impressions += stat.Impressions
rank.Clicks += stat.Clicks
rank.Cost += stat.Cost
rank.Revenue += stat.Revenue
}
}
return s.sortRankData(positionMap)
}
// sortRankData 对排行数据进行排序
func (s *adStatistics) sortRankData(dataMap map[string]*dto.RankData) []dto.RankData {
rankList := make([]dto.RankData, 0, len(dataMap))
for _, rank := range dataMap {
rankList = append(rankList, *rank)
}
// 按收入降序排序
sort.Slice(rankList, func(i, j int) bool {
return rankList[i].Revenue > rankList[j].Revenue
})
// 只返回前10名
if len(rankList) > 10 {
rankList = rankList[:10]
}
return rankList
}