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]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|