389 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			389 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | package services | |||
|  | 
 | |||
|  | import ( | |||
|  | 	"context" | |||
|  | 	"fmt" | |||
|  | 	"time" | |||
|  | 
 | |||
|  | 	"go.uber.org/zap" | |||
|  | 
 | |||
|  | 	"tyapi-server/internal/domains/statistics/entities" | |||
|  | 	"tyapi-server/internal/domains/statistics/repositories" | |||
|  | ) | |||
|  | 
 | |||
|  | // StatisticsAggregateService 统计聚合服务接口 | |||
|  | // 负责统计数据的聚合和计算 | |||
|  | type StatisticsAggregateService interface { | |||
|  | 	// 实时统计 | |||
|  | 	UpdateRealtimeMetric(ctx context.Context, metricType, metricName string, value float64) error | |||
|  | 	GetRealtimeMetrics(ctx context.Context, metricType string) (map[string]float64, error) | |||
|  | 	 | |||
|  | 	// 历史统计聚合 | |||
|  | 	AggregateHourlyMetrics(ctx context.Context, date time.Time) error | |||
|  | 	AggregateDailyMetrics(ctx context.Context, date time.Time) error | |||
|  | 	AggregateWeeklyMetrics(ctx context.Context, date time.Time) error | |||
|  | 	AggregateMonthlyMetrics(ctx context.Context, date time.Time) error | |||
|  | 	 | |||
|  | 	// 统计查询 | |||
|  | 	GetMetricsByType(ctx context.Context, metricType string, startDate, endDate time.Time) ([]*entities.StatisticsMetric, error) | |||
|  | 	GetMetricsByDimension(ctx context.Context, metricType, dimension string, startDate, endDate time.Time) ([]*entities.StatisticsMetric, error) | |||
|  | 	 | |||
|  | 	// 统计计算 | |||
|  | 	CalculateGrowthRate(ctx context.Context, metricType, metricName string, currentPeriod, previousPeriod time.Time) (float64, error) | |||
|  | 	CalculateTrend(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (string, error) | |||
|  | } | |||
|  | 
 | |||
|  | // StatisticsAggregateServiceImpl 统计聚合服务实现 | |||
|  | type StatisticsAggregateServiceImpl struct { | |||
|  | 	metricRepo repositories.StatisticsRepository | |||
|  | 	logger     *zap.Logger | |||
|  | } | |||
|  | 
 | |||
|  | // NewStatisticsAggregateService 创建统计聚合服务 | |||
|  | func NewStatisticsAggregateService( | |||
|  | 	metricRepo repositories.StatisticsRepository, | |||
|  | 	logger *zap.Logger, | |||
|  | ) StatisticsAggregateService { | |||
|  | 	return &StatisticsAggregateServiceImpl{ | |||
|  | 		metricRepo: metricRepo, | |||
|  | 		logger:     logger, | |||
|  | 	} | |||
|  | } | |||
|  | 
 | |||
|  | // UpdateRealtimeMetric 更新实时统计指标 | |||
|  | func (s *StatisticsAggregateServiceImpl) UpdateRealtimeMetric(ctx context.Context, metricType, metricName string, value float64) error { | |||
|  | 	if metricType == "" { | |||
|  | 		return fmt.Errorf("指标类型不能为空") | |||
|  | 	} | |||
|  | 	if metricName == "" { | |||
|  | 		return fmt.Errorf("指标名称不能为空") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 创建或更新实时指标 | |||
|  | 	metric, err := entities.NewStatisticsMetric(metricType, metricName, "realtime", value, time.Now()) | |||
|  | 	if err != nil { | |||
|  | 		s.logger.Error("创建统计指标失败",  | |||
|  | 			zap.String("metric_type", metricType), | |||
|  | 			zap.String("metric_name", metricName), | |||
|  | 			zap.Error(err)) | |||
|  | 		return fmt.Errorf("创建统计指标失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 保存到数据库 | |||
|  | 	err = s.metricRepo.Save(ctx, metric) | |||
|  | 	if err != nil { | |||
|  | 		s.logger.Error("保存统计指标失败",  | |||
|  | 			zap.String("metric_id", metric.ID), | |||
|  | 			zap.Error(err)) | |||
|  | 		return fmt.Errorf("保存统计指标失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	s.logger.Info("实时统计指标更新成功", | |||
|  | 		zap.String("metric_type", metricType), | |||
|  | 		zap.String("metric_name", metricName), | |||
|  | 		zap.Float64("value", value)) | |||
|  | 
 | |||
|  | 	return nil | |||
|  | } | |||
|  | 
 | |||
|  | // GetRealtimeMetrics 获取实时统计指标 | |||
|  | func (s *StatisticsAggregateServiceImpl) GetRealtimeMetrics(ctx context.Context, metricType string) (map[string]float64, error) { | |||
|  | 	if metricType == "" { | |||
|  | 		return nil, fmt.Errorf("指标类型不能为空") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 获取今天的实时指标 | |||
|  | 	today := time.Now().Truncate(24 * time.Hour) | |||
|  | 	tomorrow := today.Add(24 * time.Hour) | |||
|  | 
 | |||
|  | 	metrics, err := s.metricRepo.FindByTypeAndDateRange(ctx, metricType, today, tomorrow) | |||
|  | 	if err != nil { | |||
|  | 		s.logger.Error("查询实时统计指标失败",  | |||
|  | 			zap.String("metric_type", metricType), | |||
|  | 			zap.Error(err)) | |||
|  | 		return nil, fmt.Errorf("查询实时统计指标失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 转换为map格式 | |||
|  | 	result := make(map[string]float64) | |||
|  | 	for _, metric := range metrics { | |||
|  | 		if metric.Dimension == "realtime" { | |||
|  | 			result[metric.MetricName] = metric.Value | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return result, nil | |||
|  | } | |||
|  | 
 | |||
|  | // AggregateHourlyMetrics 聚合小时级统计指标 | |||
|  | func (s *StatisticsAggregateServiceImpl) AggregateHourlyMetrics(ctx context.Context, date time.Time) error { | |||
|  | 	s.logger.Info("开始聚合小时级统计指标", zap.Time("date", date)) | |||
|  | 
 | |||
|  | 	// 获取指定小时的所有实时指标 | |||
|  | 	startTime := date.Truncate(time.Hour) | |||
|  | 	endTime := startTime.Add(time.Hour) | |||
|  | 
 | |||
|  | 	// 聚合不同类型的指标 | |||
|  | 	metricTypes := []string{"api_calls", "users", "finance", "products", "certification"} | |||
|  | 	 | |||
|  | 	for _, metricType := range metricTypes { | |||
|  | 		err := s.aggregateMetricsByType(ctx, metricType, startTime, endTime, "hourly") | |||
|  | 		if err != nil { | |||
|  | 			s.logger.Error("聚合小时级指标失败",  | |||
|  | 				zap.String("metric_type", metricType), | |||
|  | 				zap.Error(err)) | |||
|  | 			return fmt.Errorf("聚合小时级指标失败: %w", err) | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	s.logger.Info("小时级统计指标聚合完成", zap.Time("date", date)) | |||
|  | 	return nil | |||
|  | } | |||
|  | 
 | |||
|  | // AggregateDailyMetrics 聚合日级统计指标 | |||
|  | func (s *StatisticsAggregateServiceImpl) AggregateDailyMetrics(ctx context.Context, date time.Time) error { | |||
|  | 	s.logger.Info("开始聚合日级统计指标", zap.Time("date", date)) | |||
|  | 
 | |||
|  | 	// 获取指定日期的所有小时级指标 | |||
|  | 	startTime := date.Truncate(24 * time.Hour) | |||
|  | 	endTime := startTime.Add(24 * time.Hour) | |||
|  | 
 | |||
|  | 	// 聚合不同类型的指标 | |||
|  | 	metricTypes := []string{"api_calls", "users", "finance", "products", "certification"} | |||
|  | 	 | |||
|  | 	for _, metricType := range metricTypes { | |||
|  | 		err := s.aggregateMetricsByType(ctx, metricType, startTime, endTime, "daily") | |||
|  | 		if err != nil { | |||
|  | 			s.logger.Error("聚合日级指标失败",  | |||
|  | 				zap.String("metric_type", metricType), | |||
|  | 				zap.Error(err)) | |||
|  | 			return fmt.Errorf("聚合日级指标失败: %w", err) | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	s.logger.Info("日级统计指标聚合完成", zap.Time("date", date)) | |||
|  | 	return nil | |||
|  | } | |||
|  | 
 | |||
|  | // AggregateWeeklyMetrics 聚合周级统计指标 | |||
|  | func (s *StatisticsAggregateServiceImpl) AggregateWeeklyMetrics(ctx context.Context, date time.Time) error { | |||
|  | 	s.logger.Info("开始聚合周级统计指标", zap.Time("date", date)) | |||
|  | 
 | |||
|  | 	// 获取指定周的所有日级指标 | |||
|  | 	startTime := date.Truncate(24 * time.Hour) | |||
|  | 	endTime := startTime.Add(7 * 24 * time.Hour) | |||
|  | 
 | |||
|  | 	// 聚合不同类型的指标 | |||
|  | 	metricTypes := []string{"api_calls", "users", "finance", "products", "certification"} | |||
|  | 	 | |||
|  | 	for _, metricType := range metricTypes { | |||
|  | 		err := s.aggregateMetricsByType(ctx, metricType, startTime, endTime, "weekly") | |||
|  | 		if err != nil { | |||
|  | 			s.logger.Error("聚合周级指标失败",  | |||
|  | 				zap.String("metric_type", metricType), | |||
|  | 				zap.Error(err)) | |||
|  | 			return fmt.Errorf("聚合周级指标失败: %w", err) | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	s.logger.Info("周级统计指标聚合完成", zap.Time("date", date)) | |||
|  | 	return nil | |||
|  | } | |||
|  | 
 | |||
|  | // AggregateMonthlyMetrics 聚合月级统计指标 | |||
|  | func (s *StatisticsAggregateServiceImpl) AggregateMonthlyMetrics(ctx context.Context, date time.Time) error { | |||
|  | 	s.logger.Info("开始聚合月级统计指标", zap.Time("date", date)) | |||
|  | 
 | |||
|  | 	// 获取指定月的所有日级指标 | |||
|  | 	startTime := date.Truncate(24 * time.Hour) | |||
|  | 	endTime := startTime.AddDate(0, 1, 0) | |||
|  | 
 | |||
|  | 	// 聚合不同类型的指标 | |||
|  | 	metricTypes := []string{"api_calls", "users", "finance", "products", "certification"} | |||
|  | 	 | |||
|  | 	for _, metricType := range metricTypes { | |||
|  | 		err := s.aggregateMetricsByType(ctx, metricType, startTime, endTime, "monthly") | |||
|  | 		if err != nil { | |||
|  | 			s.logger.Error("聚合月级指标失败",  | |||
|  | 				zap.String("metric_type", metricType), | |||
|  | 				zap.Error(err)) | |||
|  | 			return fmt.Errorf("聚合月级指标失败: %w", err) | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	s.logger.Info("月级统计指标聚合完成", zap.Time("date", date)) | |||
|  | 	return nil | |||
|  | } | |||
|  | 
 | |||
|  | // GetMetricsByType 根据类型获取统计指标 | |||
|  | func (s *StatisticsAggregateServiceImpl) GetMetricsByType(ctx context.Context, metricType string, startDate, endDate time.Time) ([]*entities.StatisticsMetric, error) { | |||
|  | 	if metricType == "" { | |||
|  | 		return nil, fmt.Errorf("指标类型不能为空") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	metrics, err := s.metricRepo.FindByTypeAndDateRange(ctx, metricType, startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		s.logger.Error("查询统计指标失败",  | |||
|  | 			zap.String("metric_type", metricType), | |||
|  | 			zap.Time("start_date", startDate), | |||
|  | 			zap.Time("end_date", endDate), | |||
|  | 			zap.Error(err)) | |||
|  | 		return nil, fmt.Errorf("查询统计指标失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return metrics, nil | |||
|  | } | |||
|  | 
 | |||
|  | // GetMetricsByDimension 根据维度获取统计指标 | |||
|  | func (s *StatisticsAggregateServiceImpl) GetMetricsByDimension(ctx context.Context, metricType, dimension string, startDate, endDate time.Time) ([]*entities.StatisticsMetric, error) { | |||
|  | 	if metricType == "" { | |||
|  | 		return nil, fmt.Errorf("指标类型不能为空") | |||
|  | 	} | |||
|  | 	if dimension == "" { | |||
|  | 		return nil, fmt.Errorf("统计维度不能为空") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	metrics, err := s.metricRepo.FindByTypeDimensionAndDateRange(ctx, metricType, dimension, startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		s.logger.Error("查询统计指标失败",  | |||
|  | 			zap.String("metric_type", metricType), | |||
|  | 			zap.String("dimension", dimension), | |||
|  | 			zap.Time("start_date", startDate), | |||
|  | 			zap.Time("end_date", endDate), | |||
|  | 			zap.Error(err)) | |||
|  | 		return nil, fmt.Errorf("查询统计指标失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return metrics, nil | |||
|  | } | |||
|  | 
 | |||
|  | // CalculateGrowthRate 计算增长率 | |||
|  | func (s *StatisticsAggregateServiceImpl) CalculateGrowthRate(ctx context.Context, metricType, metricName string, currentPeriod, previousPeriod time.Time) (float64, error) { | |||
|  | 	if metricType == "" || metricName == "" { | |||
|  | 		return 0, fmt.Errorf("指标类型和名称不能为空") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 获取当前周期的指标值 | |||
|  | 	currentMetrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, currentPeriod, currentPeriod.Add(24*time.Hour)) | |||
|  | 	if err != nil { | |||
|  | 		return 0, fmt.Errorf("查询当前周期指标失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 获取上一周期的指标值 | |||
|  | 	previousMetrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, previousPeriod, previousPeriod.Add(24*time.Hour)) | |||
|  | 	if err != nil { | |||
|  | 		return 0, fmt.Errorf("查询上一周期指标失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 计算总值 | |||
|  | 	var currentValue, previousValue float64 | |||
|  | 	for _, metric := range currentMetrics { | |||
|  | 		currentValue += metric.Value | |||
|  | 	} | |||
|  | 	for _, metric := range previousMetrics { | |||
|  | 		previousValue += metric.Value | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 计算增长率 | |||
|  | 	if previousValue == 0 { | |||
|  | 		if currentValue > 0 { | |||
|  | 			return 100, nil // 从0增长到正数,增长率为100% | |||
|  | 		} | |||
|  | 		return 0, nil // 都是0,增长率为0% | |||
|  | 	} | |||
|  | 
 | |||
|  | 	growthRate := ((currentValue - previousValue) / previousValue) * 100 | |||
|  | 	return growthRate, nil | |||
|  | } | |||
|  | 
 | |||
|  | // CalculateTrend 计算趋势 | |||
|  | func (s *StatisticsAggregateServiceImpl) CalculateTrend(ctx context.Context, metricType, metricName string, startDate, endDate time.Time) (string, error) { | |||
|  | 	if metricType == "" || metricName == "" { | |||
|  | 		return "", fmt.Errorf("指标类型和名称不能为空") | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 获取时间范围内的指标 | |||
|  | 	metrics, err := s.metricRepo.FindByTypeNameAndDateRange(ctx, metricType, metricName, startDate, endDate) | |||
|  | 	if err != nil { | |||
|  | 		return "", fmt.Errorf("查询指标失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	if len(metrics) < 2 { | |||
|  | 		return "insufficient_data", nil // 数据不足 | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 按时间排序 | |||
|  | 	sortMetricsByDate(metrics) | |||
|  | 
 | |||
|  | 	// 计算趋势 | |||
|  | 	firstValue := metrics[0].Value | |||
|  | 	lastValue := metrics[len(metrics)-1].Value | |||
|  | 
 | |||
|  | 	if lastValue > firstValue { | |||
|  | 		return "increasing", nil // 上升趋势 | |||
|  | 	} else if lastValue < firstValue { | |||
|  | 		return "decreasing", nil // 下降趋势 | |||
|  | 	} else { | |||
|  | 		return "stable", nil // 稳定趋势 | |||
|  | 	} | |||
|  | } | |||
|  | 
 | |||
|  | // aggregateMetricsByType 按类型聚合指标 | |||
|  | func (s *StatisticsAggregateServiceImpl) aggregateMetricsByType(ctx context.Context, metricType string, startTime, endTime time.Time, dimension string) error { | |||
|  | 	// 获取源数据(实时或小时级数据) | |||
|  | 	sourceDimension := "realtime" | |||
|  | 	if dimension == "daily" { | |||
|  | 		sourceDimension = "hourly" | |||
|  | 	} else if dimension == "weekly" || dimension == "monthly" { | |||
|  | 		sourceDimension = "daily" | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 查询源数据 | |||
|  | 	sourceMetrics, err := s.metricRepo.FindByTypeDimensionAndDateRange(ctx, metricType, sourceDimension, startTime, endTime) | |||
|  | 	if err != nil { | |||
|  | 		return fmt.Errorf("查询源数据失败: %w", err) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 按指标名称分组聚合 | |||
|  | 	metricGroups := make(map[string][]*entities.StatisticsMetric) | |||
|  | 	for _, metric := range sourceMetrics { | |||
|  | 		metricGroups[metric.MetricName] = append(metricGroups[metric.MetricName], metric) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 聚合每个指标 | |||
|  | 	for metricName, metrics := range metricGroups { | |||
|  | 		var totalValue float64 | |||
|  | 		for _, metric := range metrics { | |||
|  | 			totalValue += metric.Value | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// 创建聚合后的指标 | |||
|  | 		aggregatedMetric, err := entities.NewStatisticsMetric(metricType, metricName, dimension, totalValue, startTime) | |||
|  | 		if err != nil { | |||
|  | 			return fmt.Errorf("创建聚合指标失败: %w", err) | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// 保存聚合指标 | |||
|  | 		err = s.metricRepo.Save(ctx, aggregatedMetric) | |||
|  | 		if err != nil { | |||
|  | 			return fmt.Errorf("保存聚合指标失败: %w", err) | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return nil | |||
|  | } | |||
|  | 
 | |||
|  | // sortMetricsByDate 按日期排序指标 | |||
|  | func sortMetricsByDate(metrics []*entities.StatisticsMetric) { | |||
|  | 	// 简单的冒泡排序 | |||
|  | 	n := len(metrics) | |||
|  | 	for i := 0; i < n-1; i++ { | |||
|  | 		for j := 0; j < n-i-1; j++ { | |||
|  | 			if metrics[j].Date.After(metrics[j+1].Date) { | |||
|  | 				metrics[j], metrics[j+1] = metrics[j+1], metrics[j] | |||
|  | 			} | |||
|  | 		} | |||
|  | 	} | |||
|  | } | |||
|  | 
 |