368 lines
9.7 KiB
Go
368 lines
9.7 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"sort"
|
||
"time"
|
||
|
||
"cidService/dao"
|
||
"cidService/model/dto"
|
||
"cidService/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
|
||
}
|