package migrations import ( "fmt" "time" "gorm.io/gorm" "tyapi-server/internal/domains/statistics/entities" ) // StatisticsMigrationComplete 统计模块完整数据迁移 type StatisticsMigrationComplete struct { db *gorm.DB } // NewStatisticsMigrationComplete 创建统计模块完整数据迁移 func NewStatisticsMigrationComplete(db *gorm.DB) *StatisticsMigrationComplete { return &StatisticsMigrationComplete{ db: db, } } // Migrate 执行完整的数据迁移 func (m *StatisticsMigrationComplete) Migrate() error { fmt.Println("开始执行统计模块完整数据迁移...") // 1. 迁移表结构 err := m.migrateTables() if err != nil { return fmt.Errorf("迁移表结构失败: %w", err) } // 2. 创建索引 err = m.createIndexes() if err != nil { return fmt.Errorf("创建索引失败: %w", err) } // 3. 插入初始数据 err = m.insertInitialData() if err != nil { return fmt.Errorf("插入初始数据失败: %w", err) } fmt.Println("统计模块完整数据迁移完成") return nil } // migrateTables 迁移表结构 func (m *StatisticsMigrationComplete) migrateTables() error { fmt.Println("迁移统计模块表结构...") // 迁移统计指标表 err := m.db.AutoMigrate(&entities.StatisticsMetric{}) if err != nil { return fmt.Errorf("迁移统计指标表失败: %w", err) } // 迁移统计报告表 err = m.db.AutoMigrate(&entities.StatisticsReport{}) if err != nil { return fmt.Errorf("迁移统计报告表失败: %w", err) } // 迁移统计仪表板表 err = m.db.AutoMigrate(&entities.StatisticsDashboard{}) if err != nil { return fmt.Errorf("迁移统计仪表板表失败: %w", err) } fmt.Println("统计模块表结构迁移完成") return nil } // createIndexes 创建索引 func (m *StatisticsMigrationComplete) createIndexes() error { fmt.Println("创建统计模块索引...") // 统计指标表索引 err := m.createStatisticsMetricsIndexes() if err != nil { return fmt.Errorf("创建统计指标表索引失败: %w", err) } // 统计报告表索引 err = m.createStatisticsReportsIndexes() if err != nil { return fmt.Errorf("创建统计报告表索引失败: %w", err) } // 统计仪表板表索引 err = m.createStatisticsDashboardsIndexes() if err != nil { return fmt.Errorf("创建统计仪表板表索引失败: %w", err) } fmt.Println("统计模块索引创建完成") return nil } // createStatisticsMetricsIndexes 创建统计指标表索引 func (m *StatisticsMigrationComplete) createStatisticsMetricsIndexes() error { indexes := []string{ // 复合索引:metric_type + date `CREATE INDEX IF NOT EXISTS idx_statistics_metrics_type_date ON statistics_metrics (metric_type, date)`, // 复合索引:metric_type + dimension + date `CREATE INDEX IF NOT EXISTS idx_statistics_metrics_type_dimension_date ON statistics_metrics (metric_type, dimension, date)`, // 复合索引:metric_type + metric_name + date `CREATE INDEX IF NOT EXISTS idx_statistics_metrics_type_name_date ON statistics_metrics (metric_type, metric_name, date)`, // 单列索引:dimension `CREATE INDEX IF NOT EXISTS idx_statistics_metrics_dimension ON statistics_metrics (dimension)`, // 单列索引:metric_name `CREATE INDEX IF NOT EXISTS idx_statistics_metrics_name ON statistics_metrics (metric_name)`, // 单列索引:value(用于范围查询) `CREATE INDEX IF NOT EXISTS idx_statistics_metrics_value ON statistics_metrics (value)`, } for _, indexSQL := range indexes { err := m.db.Exec(indexSQL).Error if err != nil { return fmt.Errorf("创建索引失败: %w", err) } } return nil } // createStatisticsReportsIndexes 创建统计报告表索引 func (m *StatisticsMigrationComplete) createStatisticsReportsIndexes() error { indexes := []string{ // 复合索引:report_type + created_at `CREATE INDEX IF NOT EXISTS idx_statistics_reports_type_created ON statistics_reports (report_type, created_at)`, // 复合索引:user_role + created_at `CREATE INDEX IF NOT EXISTS idx_statistics_reports_role_created ON statistics_reports (user_role, created_at)`, // 复合索引:status + created_at `CREATE INDEX IF NOT EXISTS idx_statistics_reports_status_created ON statistics_reports (status, created_at)`, // 单列索引:generated_by `CREATE INDEX IF NOT EXISTS idx_statistics_reports_generated_by ON statistics_reports (generated_by)`, // 单列索引:expires_at `CREATE INDEX IF NOT EXISTS idx_statistics_reports_expires_at ON statistics_reports (expires_at)`, // 单列索引:period `CREATE INDEX IF NOT EXISTS idx_statistics_reports_period ON statistics_reports (period)`, } for _, indexSQL := range indexes { err := m.db.Exec(indexSQL).Error if err != nil { return fmt.Errorf("创建索引失败: %w", err) } } return nil } // createStatisticsDashboardsIndexes 创建统计仪表板表索引 func (m *StatisticsMigrationComplete) createStatisticsDashboardsIndexes() error { indexes := []string{ // 复合索引:user_role + is_active `CREATE INDEX IF NOT EXISTS idx_statistics_dashboards_role_active ON statistics_dashboards (user_role, is_active)`, // 复合索引:user_role + is_default `CREATE INDEX IF NOT EXISTS idx_statistics_dashboards_role_default ON statistics_dashboards (user_role, is_default)`, // 单列索引:created_by `CREATE INDEX IF NOT EXISTS idx_statistics_dashboards_created_by ON statistics_dashboards (created_by)`, // 单列索引:access_level `CREATE INDEX IF NOT EXISTS idx_statistics_dashboards_access_level ON statistics_dashboards (access_level)`, // 单列索引:name(用于搜索) `CREATE INDEX IF NOT EXISTS idx_statistics_dashboards_name ON statistics_dashboards (name)`, } for _, indexSQL := range indexes { err := m.db.Exec(indexSQL).Error if err != nil { return fmt.Errorf("创建索引失败: %w", err) } } return nil } // insertInitialData 插入初始数据 func (m *StatisticsMigrationComplete) insertInitialData() error { fmt.Println("插入统计模块初始数据...") // 插入默认仪表板 err := m.insertDefaultDashboards() if err != nil { return fmt.Errorf("插入默认仪表板失败: %w", err) } // 插入初始指标数据 err = m.insertInitialMetrics() if err != nil { return fmt.Errorf("插入初始指标数据失败: %w", err) } fmt.Println("统计模块初始数据插入完成") return nil } // insertDefaultDashboards 插入默认仪表板 func (m *StatisticsMigrationComplete) insertDefaultDashboards() error { // 检查是否已存在默认仪表板 var count int64 err := m.db.Model(&entities.StatisticsDashboard{}).Where("is_default = ?", true).Count(&count).Error if err != nil { return fmt.Errorf("检查默认仪表板失败: %w", err) } // 如果已存在默认仪表板,跳过插入 if count > 0 { fmt.Println("默认仪表板已存在,跳过插入") return nil } // 管理员默认仪表板 adminDashboard := &entities.StatisticsDashboard{ Name: "管理员仪表板", Description: "系统管理员专用仪表板,包含所有统计信息", UserRole: "admin", IsDefault: true, IsActive: true, AccessLevel: "private", RefreshInterval: 300, CreatedBy: "system", Layout: `{"columns": 3, "rows": 4}`, Widgets: `[{"type": "api_calls", "position": {"x": 0, "y": 0}}, {"type": "users", "position": {"x": 1, "y": 0}}, {"type": "finance", "position": {"x": 2, "y": 0}}]`, Settings: `{"theme": "dark", "auto_refresh": true}`, } err = m.db.Create(adminDashboard).Error if err != nil { return fmt.Errorf("创建管理员仪表板失败: %w", err) } // 用户默认仪表板 userDashboard := &entities.StatisticsDashboard{ Name: "用户仪表板", Description: "普通用户专用仪表板,包含基础统计信息", UserRole: "user", IsDefault: true, IsActive: true, AccessLevel: "private", RefreshInterval: 600, CreatedBy: "system", Layout: `{"columns": 2, "rows": 3}`, Widgets: `[{"type": "api_calls", "position": {"x": 0, "y": 0}}, {"type": "users", "position": {"x": 1, "y": 0}}]`, Settings: `{"theme": "light", "auto_refresh": false}`, } err = m.db.Create(userDashboard).Error if err != nil { return fmt.Errorf("创建用户仪表板失败: %w", err) } // 经理默认仪表板 managerDashboard := &entities.StatisticsDashboard{ Name: "经理仪表板", Description: "经理专用仪表板,包含管理相关统计信息", UserRole: "manager", IsDefault: true, IsActive: true, AccessLevel: "private", RefreshInterval: 300, CreatedBy: "system", Layout: `{"columns": 3, "rows": 3}`, Widgets: `[{"type": "api_calls", "position": {"x": 0, "y": 0}}, {"type": "users", "position": {"x": 1, "y": 0}}, {"type": "finance", "position": {"x": 2, "y": 0}}]`, Settings: `{"theme": "dark", "auto_refresh": true}`, } err = m.db.Create(managerDashboard).Error if err != nil { return fmt.Errorf("创建经理仪表板失败: %w", err) } // 分析师默认仪表板 analystDashboard := &entities.StatisticsDashboard{ Name: "分析师仪表板", Description: "数据分析师专用仪表板,包含详细分析信息", UserRole: "analyst", IsDefault: true, IsActive: true, AccessLevel: "private", RefreshInterval: 180, CreatedBy: "system", Layout: `{"columns": 4, "rows": 4}`, Widgets: `[{"type": "api_calls", "position": {"x": 0, "y": 0}}, {"type": "users", "position": {"x": 1, "y": 0}}, {"type": "finance", "position": {"x": 2, "y": 0}}, {"type": "products", "position": {"x": 3, "y": 0}}]`, Settings: `{"theme": "dark", "auto_refresh": true, "show_trends": true}`, } err = m.db.Create(analystDashboard).Error if err != nil { return fmt.Errorf("创建分析师仪表板失败: %w", err) } fmt.Println("默认仪表板创建完成") return nil } // insertInitialMetrics 插入初始指标数据 func (m *StatisticsMigrationComplete) insertInitialMetrics() error { now := time.Now() today := now.Truncate(24 * time.Hour) // 检查是否已存在今日指标数据 var count int64 err := m.db.Model(&entities.StatisticsMetric{}).Where("date = ?", today).Count(&count).Error if err != nil { return fmt.Errorf("检查指标数据失败: %w", err) } // 如果已存在今日指标数据,跳过插入 if count > 0 { fmt.Println("今日指标数据已存在,跳过插入") return nil } // 插入初始API调用指标 apiMetrics := []*entities.StatisticsMetric{ { MetricType: "api_calls", MetricName: "total_count", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "api_calls", MetricName: "success_count", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "api_calls", MetricName: "failed_count", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "api_calls", MetricName: "response_time", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "api_calls", MetricName: "avg_response_time", Dimension: "realtime", Value: 0, Date: today, }, } // 插入初始用户指标 userMetrics := []*entities.StatisticsMetric{ { MetricType: "users", MetricName: "total_count", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "users", MetricName: "certified_count", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "users", MetricName: "active_count", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "users", MetricName: "new_users_today", Dimension: "realtime", Value: 0, Date: today, }, } // 插入初始财务指标 financeMetrics := []*entities.StatisticsMetric{ { MetricType: "finance", MetricName: "total_amount", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "finance", MetricName: "recharge_amount", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "finance", MetricName: "deduct_amount", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "finance", MetricName: "recharge_count", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "finance", MetricName: "deduct_count", Dimension: "realtime", Value: 0, Date: today, }, } // 插入初始产品指标 productMetrics := []*entities.StatisticsMetric{ { MetricType: "products", MetricName: "total_products", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "products", MetricName: "active_products", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "products", MetricName: "total_subscriptions", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "products", MetricName: "active_subscriptions", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "products", MetricName: "new_subscriptions_today", Dimension: "realtime", Value: 0, Date: today, }, } // 插入初始认证指标 certificationMetrics := []*entities.StatisticsMetric{ { MetricType: "certification", MetricName: "total_certifications", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "certification", MetricName: "completed_certifications", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "certification", MetricName: "pending_certifications", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "certification", MetricName: "failed_certifications", Dimension: "realtime", Value: 0, Date: today, }, { MetricType: "certification", MetricName: "certification_rate", Dimension: "realtime", Value: 0, Date: today, }, } // 批量插入所有指标 allMetrics := append(apiMetrics, userMetrics...) allMetrics = append(allMetrics, financeMetrics...) allMetrics = append(allMetrics, productMetrics...) allMetrics = append(allMetrics, certificationMetrics...) err = m.db.CreateInBatches(allMetrics, 100).Error if err != nil { return fmt.Errorf("批量插入初始指标失败: %w", err) } fmt.Println("初始指标数据创建完成") return nil } // Rollback 回滚迁移 func (m *StatisticsMigrationComplete) Rollback() error { fmt.Println("开始回滚统计模块数据迁移...") // 删除表 err := m.db.Migrator().DropTable(&entities.StatisticsDashboard{}) if err != nil { return fmt.Errorf("删除统计仪表板表失败: %w", err) } err = m.db.Migrator().DropTable(&entities.StatisticsReport{}) if err != nil { return fmt.Errorf("删除统计报告表失败: %w", err) } err = m.db.Migrator().DropTable(&entities.StatisticsMetric{}) if err != nil { return fmt.Errorf("删除统计指标表失败: %w", err) } fmt.Println("统计模块数据迁移回滚完成") return nil } // GetTableInfo 获取表信息 func (m *StatisticsMigrationComplete) GetTableInfo() map[string]interface{} { info := make(map[string]interface{}) // 获取表统计信息 tables := []string{"statistics_metrics", "statistics_reports", "statistics_dashboards"} for _, table := range tables { var count int64 m.db.Table(table).Count(&count) info[table] = count } return info }