Files
tyapi-server/internal/domains/statistics/services/statistics_aggregate_service.go
2025-09-12 01:15:09 +08:00

389 lines
13 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 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]
}
}
}
}