package services import ( "context" "encoding/json" "fmt" "time" "go.uber.org/zap" "tyapi-server/internal/domains/statistics/entities" "tyapi-server/internal/domains/statistics/repositories" ) // StatisticsReportService 报告生成服务接口 // 负责统计报告的生成和管理 type StatisticsReportService interface { // 报告生成 GenerateDashboardReport(ctx context.Context, userRole string, period string) (*entities.StatisticsReport, error) GenerateSummaryReport(ctx context.Context, period string, startDate, endDate time.Time) (*entities.StatisticsReport, error) GenerateDetailedReport(ctx context.Context, reportType string, startDate, endDate time.Time, filters map[string]interface{}) (*entities.StatisticsReport, error) // 报告管理 GetReport(ctx context.Context, reportID string) (*entities.StatisticsReport, error) GetReportsByUser(ctx context.Context, userID string, limit, offset int) ([]*entities.StatisticsReport, error) DeleteReport(ctx context.Context, reportID string) error // 报告状态管理 StartReportGeneration(ctx context.Context, reportID, generatedBy string) error CompleteReportGeneration(ctx context.Context, reportID string, content string) error FailReportGeneration(ctx context.Context, reportID string, reason string) error // 报告清理 CleanupExpiredReports(ctx context.Context) error } // StatisticsReportServiceImpl 报告生成服务实现 type StatisticsReportServiceImpl struct { reportRepo repositories.StatisticsReportRepository metricRepo repositories.StatisticsRepository calcService StatisticsCalculationService logger *zap.Logger } // NewStatisticsReportService 创建报告生成服务 func NewStatisticsReportService( reportRepo repositories.StatisticsReportRepository, metricRepo repositories.StatisticsRepository, calcService StatisticsCalculationService, logger *zap.Logger, ) StatisticsReportService { return &StatisticsReportServiceImpl{ reportRepo: reportRepo, metricRepo: metricRepo, calcService: calcService, logger: logger, } } // GenerateDashboardReport 生成仪表板报告 func (s *StatisticsReportServiceImpl) GenerateDashboardReport(ctx context.Context, userRole string, period string) (*entities.StatisticsReport, error) { if userRole == "" { return nil, fmt.Errorf("用户角色不能为空") } if period == "" { return nil, fmt.Errorf("统计周期不能为空") } // 创建报告实体 title := fmt.Sprintf("%s仪表板报告 - %s", s.getRoleDisplayName(userRole), s.getPeriodDisplayName(period)) report, err := entities.NewStatisticsReport("dashboard", title, period, userRole) if err != nil { s.logger.Error("创建仪表板报告失败", zap.String("user_role", userRole), zap.String("period", period), zap.Error(err)) return nil, fmt.Errorf("创建仪表板报告失败: %w", err) } // 保存报告 err = s.reportRepo.Save(ctx, report) if err != nil { s.logger.Error("保存仪表板报告失败", zap.String("report_id", report.ID), zap.Error(err)) return nil, fmt.Errorf("保存仪表板报告失败: %w", err) } s.logger.Info("仪表板报告创建成功", zap.String("report_id", report.ID), zap.String("user_role", userRole), zap.String("period", period)) return report, nil } // GenerateSummaryReport 生成汇总报告 func (s *StatisticsReportServiceImpl) GenerateSummaryReport(ctx context.Context, period string, startDate, endDate time.Time) (*entities.StatisticsReport, error) { if period == "" { return nil, fmt.Errorf("统计周期不能为空") } // 创建报告实体 title := fmt.Sprintf("汇总报告 - %s (%s 至 %s)", s.getPeriodDisplayName(period), startDate.Format("2006-01-02"), endDate.Format("2006-01-02")) report, err := entities.NewStatisticsReport("summary", title, period, "admin") if err != nil { s.logger.Error("创建汇总报告失败", zap.String("period", period), zap.Error(err)) return nil, fmt.Errorf("创建汇总报告失败: %w", err) } // 生成报告内容 content, err := s.generateSummaryContent(ctx, startDate, endDate) if err != nil { s.logger.Error("生成汇总报告内容失败", zap.String("report_id", report.ID), zap.Error(err)) return nil, fmt.Errorf("生成汇总报告内容失败: %w", err) } // 完成报告生成 err = report.CompleteGeneration(content) if err != nil { s.logger.Error("完成汇总报告生成失败", zap.String("report_id", report.ID), zap.Error(err)) return nil, fmt.Errorf("完成汇总报告生成失败: %w", err) } // 保存报告 err = s.reportRepo.Save(ctx, report) if err != nil { s.logger.Error("保存汇总报告失败", zap.String("report_id", report.ID), zap.Error(err)) return nil, fmt.Errorf("保存汇总报告失败: %w", err) } s.logger.Info("汇总报告生成成功", zap.String("report_id", report.ID), zap.String("period", period)) return report, nil } // GenerateDetailedReport 生成详细报告 func (s *StatisticsReportServiceImpl) GenerateDetailedReport(ctx context.Context, reportType string, startDate, endDate time.Time, filters map[string]interface{}) (*entities.StatisticsReport, error) { if reportType == "" { return nil, fmt.Errorf("报告类型不能为空") } // 创建报告实体 title := fmt.Sprintf("详细报告 - %s (%s 至 %s)", reportType, startDate.Format("2006-01-02"), endDate.Format("2006-01-02")) report, err := entities.NewStatisticsReport("detailed", title, "custom", "admin") if err != nil { s.logger.Error("创建详细报告失败", zap.String("report_type", reportType), zap.Error(err)) return nil, fmt.Errorf("创建详细报告失败: %w", err) } // 生成报告内容 content, err := s.generateDetailedContent(ctx, reportType, startDate, endDate, filters) if err != nil { s.logger.Error("生成详细报告内容失败", zap.String("report_id", report.ID), zap.Error(err)) return nil, fmt.Errorf("生成详细报告内容失败: %w", err) } // 完成报告生成 err = report.CompleteGeneration(content) if err != nil { s.logger.Error("完成详细报告生成失败", zap.String("report_id", report.ID), zap.Error(err)) return nil, fmt.Errorf("完成详细报告生成失败: %w", err) } // 保存报告 err = s.reportRepo.Save(ctx, report) if err != nil { s.logger.Error("保存详细报告失败", zap.String("report_id", report.ID), zap.Error(err)) return nil, fmt.Errorf("保存详细报告失败: %w", err) } s.logger.Info("详细报告生成成功", zap.String("report_id", report.ID), zap.String("report_type", reportType)) return report, nil } // GetReport 获取报告 func (s *StatisticsReportServiceImpl) GetReport(ctx context.Context, reportID string) (*entities.StatisticsReport, error) { if reportID == "" { return nil, fmt.Errorf("报告ID不能为空") } report, err := s.reportRepo.FindByID(ctx, reportID) if err != nil { s.logger.Error("查询报告失败", zap.String("report_id", reportID), zap.Error(err)) return nil, fmt.Errorf("查询报告失败: %w", err) } return report, nil } // GetReportsByUser 获取用户的报告列表 func (s *StatisticsReportServiceImpl) GetReportsByUser(ctx context.Context, userID string, limit, offset int) ([]*entities.StatisticsReport, error) { if userID == "" { return nil, fmt.Errorf("用户ID不能为空") } reports, err := s.reportRepo.FindByUser(ctx, userID, limit, offset) if err != nil { s.logger.Error("查询用户报告失败", zap.String("user_id", userID), zap.Error(err)) return nil, fmt.Errorf("查询用户报告失败: %w", err) } return reports, nil } // DeleteReport 删除报告 func (s *StatisticsReportServiceImpl) DeleteReport(ctx context.Context, reportID string) error { if reportID == "" { return fmt.Errorf("报告ID不能为空") } err := s.reportRepo.Delete(ctx, reportID) if err != nil { s.logger.Error("删除报告失败", zap.String("report_id", reportID), zap.Error(err)) return fmt.Errorf("删除报告失败: %w", err) } s.logger.Info("报告删除成功", zap.String("report_id", reportID)) return nil } // StartReportGeneration 开始报告生成 func (s *StatisticsReportServiceImpl) StartReportGeneration(ctx context.Context, reportID, generatedBy string) error { if reportID == "" { return fmt.Errorf("报告ID不能为空") } if generatedBy == "" { return fmt.Errorf("生成者ID不能为空") } report, err := s.reportRepo.FindByID(ctx, reportID) if err != nil { return fmt.Errorf("查询报告失败: %w", err) } err = report.StartGeneration(generatedBy) if err != nil { s.logger.Error("开始报告生成失败", zap.String("report_id", reportID), zap.Error(err)) return fmt.Errorf("开始报告生成失败: %w", err) } err = s.reportRepo.Save(ctx, report) if err != nil { s.logger.Error("保存报告状态失败", zap.String("report_id", reportID), zap.Error(err)) return fmt.Errorf("保存报告状态失败: %w", err) } s.logger.Info("报告生成开始", zap.String("report_id", reportID), zap.String("generated_by", generatedBy)) return nil } // CompleteReportGeneration 完成报告生成 func (s *StatisticsReportServiceImpl) CompleteReportGeneration(ctx context.Context, reportID string, content string) error { if reportID == "" { return fmt.Errorf("报告ID不能为空") } if content == "" { return fmt.Errorf("报告内容不能为空") } report, err := s.reportRepo.FindByID(ctx, reportID) if err != nil { return fmt.Errorf("查询报告失败: %w", err) } err = report.CompleteGeneration(content) if err != nil { s.logger.Error("完成报告生成失败", zap.String("report_id", reportID), zap.Error(err)) return fmt.Errorf("完成报告生成失败: %w", err) } err = s.reportRepo.Save(ctx, report) if err != nil { s.logger.Error("保存报告内容失败", zap.String("report_id", reportID), zap.Error(err)) return fmt.Errorf("保存报告内容失败: %w", err) } s.logger.Info("报告生成完成", zap.String("report_id", reportID)) return nil } // FailReportGeneration 报告生成失败 func (s *StatisticsReportServiceImpl) FailReportGeneration(ctx context.Context, reportID string, reason string) error { if reportID == "" { return fmt.Errorf("报告ID不能为空") } report, err := s.reportRepo.FindByID(ctx, reportID) if err != nil { return fmt.Errorf("查询报告失败: %w", err) } err = report.FailGeneration(reason) if err != nil { s.logger.Error("标记报告生成失败", zap.String("report_id", reportID), zap.Error(err)) return fmt.Errorf("标记报告生成失败: %w", err) } err = s.reportRepo.Save(ctx, report) if err != nil { s.logger.Error("保存报告状态失败", zap.String("report_id", reportID), zap.Error(err)) return fmt.Errorf("保存报告状态失败: %w", err) } s.logger.Info("报告生成失败", zap.String("report_id", reportID), zap.String("reason", reason)) return nil } // CleanupExpiredReports 清理过期报告 func (s *StatisticsReportServiceImpl) CleanupExpiredReports(ctx context.Context) error { s.logger.Info("开始清理过期报告") // 获取所有已完成的报告 reports, err := s.reportRepo.FindByStatus(ctx, "completed") if err != nil { s.logger.Error("查询已完成报告失败", zap.Error(err)) return fmt.Errorf("查询已完成报告失败: %w", err) } var deletedCount int for _, report := range reports { if report.IsExpired() { err = report.MarkAsExpired() if err != nil { s.logger.Error("标记报告过期失败", zap.String("report_id", report.ID), zap.Error(err)) continue } err = s.reportRepo.Save(ctx, report) if err != nil { s.logger.Error("保存过期报告状态失败", zap.String("report_id", report.ID), zap.Error(err)) continue } deletedCount++ } } s.logger.Info("过期报告清理完成", zap.Int("deleted_count", deletedCount)) return nil } // generateSummaryContent 生成汇总报告内容 func (s *StatisticsReportServiceImpl) generateSummaryContent(ctx context.Context, startDate, endDate time.Time) (string, error) { content := make(map[string]interface{}) // API调用统计 apiCallsTotal, err := s.calcService.CalculateTotal(ctx, "api_calls", "total_count", startDate, endDate) if err != nil { s.logger.Warn("计算API调用总数失败", zap.Error(err)) } apiCallsSuccess, err := s.calcService.CalculateTotal(ctx, "api_calls", "success_count", startDate, endDate) if err != nil { s.logger.Warn("计算API调用成功数失败", zap.Error(err)) } // 用户统计 usersTotal, err := s.calcService.CalculateTotal(ctx, "users", "total_count", startDate, endDate) if err != nil { s.logger.Warn("计算用户总数失败", zap.Error(err)) } usersCertified, err := s.calcService.CalculateTotal(ctx, "users", "certified_count", startDate, endDate) if err != nil { s.logger.Warn("计算认证用户数失败", zap.Error(err)) } // 财务统计 financeTotal, err := s.calcService.CalculateTotal(ctx, "finance", "total_amount", startDate, endDate) if err != nil { s.logger.Warn("计算财务总额失败", zap.Error(err)) } content["api_calls"] = map[string]interface{}{ "total": apiCallsTotal, "success": apiCallsSuccess, "rate": s.calculateRate(apiCallsSuccess, apiCallsTotal), } content["users"] = map[string]interface{}{ "total": usersTotal, "certified": usersCertified, "rate": s.calculateRate(usersCertified, usersTotal), } content["finance"] = map[string]interface{}{ "total_amount": financeTotal, } content["period"] = map[string]interface{}{ "start_date": startDate.Format("2006-01-02"), "end_date": endDate.Format("2006-01-02"), } content["generated_at"] = time.Now().Format("2006-01-02 15:04:05") // 转换为JSON字符串 jsonContent, err := json.Marshal(content) if err != nil { return "", fmt.Errorf("序列化报告内容失败: %w", err) } return string(jsonContent), nil } // generateDetailedContent 生成详细报告内容 func (s *StatisticsReportServiceImpl) generateDetailedContent(ctx context.Context, reportType string, startDate, endDate time.Time, filters map[string]interface{}) (string, error) { content := make(map[string]interface{}) // 根据报告类型生成不同的内容 switch reportType { case "api_calls": content = s.generateApiCallsDetailedContent(ctx, startDate, endDate, filters) case "users": content = s.generateUsersDetailedContent(ctx, startDate, endDate, filters) case "finance": content = s.generateFinanceDetailedContent(ctx, startDate, endDate, filters) default: return "", fmt.Errorf("不支持的报告类型: %s", reportType) } content["report_type"] = reportType content["period"] = map[string]interface{}{ "start_date": startDate.Format("2006-01-02"), "end_date": endDate.Format("2006-01-02"), } content["generated_at"] = time.Now().Format("2006-01-02 15:04:05") // 转换为JSON字符串 jsonContent, err := json.Marshal(content) if err != nil { return "", fmt.Errorf("序列化报告内容失败: %w", err) } return string(jsonContent), nil } // generateApiCallsDetailedContent 生成API调用详细内容 func (s *StatisticsReportServiceImpl) generateApiCallsDetailedContent(ctx context.Context, startDate, endDate time.Time, filters map[string]interface{}) map[string]interface{} { content := make(map[string]interface{}) // 获取API调用统计数据 totalCalls, _ := s.calcService.CalculateTotal(ctx, "api_calls", "total_count", startDate, endDate) successCalls, _ := s.calcService.CalculateTotal(ctx, "api_calls", "success_count", startDate, endDate) failedCalls, _ := s.calcService.CalculateTotal(ctx, "api_calls", "failed_count", startDate, endDate) avgResponseTime, _ := s.calcService.CalculateAverage(ctx, "api_calls", "response_time", startDate, endDate) content["total_calls"] = totalCalls content["success_calls"] = successCalls content["failed_calls"] = failedCalls content["success_rate"] = s.calculateRate(successCalls, totalCalls) content["avg_response_time"] = avgResponseTime return content } // generateUsersDetailedContent 生成用户详细内容 func (s *StatisticsReportServiceImpl) generateUsersDetailedContent(ctx context.Context, startDate, endDate time.Time, filters map[string]interface{}) map[string]interface{} { content := make(map[string]interface{}) // 获取用户统计数据 totalUsers, _ := s.calcService.CalculateTotal(ctx, "users", "total_count", startDate, endDate) certifiedUsers, _ := s.calcService.CalculateTotal(ctx, "users", "certified_count", startDate, endDate) activeUsers, _ := s.calcService.CalculateTotal(ctx, "users", "active_count", startDate, endDate) content["total_users"] = totalUsers content["certified_users"] = certifiedUsers content["active_users"] = activeUsers content["certification_rate"] = s.calculateRate(certifiedUsers, totalUsers) content["retention_rate"] = s.calculateRate(activeUsers, totalUsers) return content } // generateFinanceDetailedContent 生成财务详细内容 func (s *StatisticsReportServiceImpl) generateFinanceDetailedContent(ctx context.Context, startDate, endDate time.Time, filters map[string]interface{}) map[string]interface{} { content := make(map[string]interface{}) // 获取财务统计数据 totalAmount, _ := s.calcService.CalculateTotal(ctx, "finance", "total_amount", startDate, endDate) rechargeAmount, _ := s.calcService.CalculateTotal(ctx, "finance", "recharge_amount", startDate, endDate) deductAmount, _ := s.calcService.CalculateTotal(ctx, "finance", "deduct_amount", startDate, endDate) content["total_amount"] = totalAmount content["recharge_amount"] = rechargeAmount content["deduct_amount"] = deductAmount content["net_amount"] = rechargeAmount - deductAmount return content } // calculateRate 计算比率 func (s *StatisticsReportServiceImpl) calculateRate(numerator, denominator float64) float64 { if denominator == 0 { return 0 } return (numerator / denominator) * 100 } // getRoleDisplayName 获取角色显示名称 func (s *StatisticsReportServiceImpl) getRoleDisplayName(role string) string { roleNames := map[string]string{ "admin": "管理员", "user": "用户", "manager": "经理", "analyst": "分析师", } if name, exists := roleNames[role]; exists { return name } return role } // getPeriodDisplayName 获取周期显示名称 func (s *StatisticsReportServiceImpl) getPeriodDisplayName(period string) string { periodNames := map[string]string{ "today": "今日", "week": "本周", "month": "本月", "quarter": "本季度", "year": "本年", } if name, exists := periodNames[period]; exists { return name } return period }