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

583 lines
18 KiB
Go

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
}