This commit is contained in:
2025-09-12 01:15:09 +08:00
parent c563b2266b
commit e05ad9e223
103 changed files with 20034 additions and 1041 deletions

View File

@@ -0,0 +1,584 @@
package cache
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/redis/go-redis/v9"
"tyapi-server/internal/domains/statistics/entities"
)
// RedisStatisticsCache Redis统计缓存实现
type RedisStatisticsCache struct {
client *redis.Client
prefix string
}
// NewRedisStatisticsCache 创建Redis统计缓存
func NewRedisStatisticsCache(client *redis.Client) *RedisStatisticsCache {
return &RedisStatisticsCache{
client: client,
prefix: "statistics:",
}
}
// ================ 指标缓存 ================
// SetMetric 设置指标缓存
func (c *RedisStatisticsCache) SetMetric(ctx context.Context, metric *entities.StatisticsMetric, expiration time.Duration) error {
if metric == nil {
return fmt.Errorf("统计指标不能为空")
}
key := c.getMetricKey(metric.ID)
data, err := json.Marshal(metric)
if err != nil {
return fmt.Errorf("序列化指标失败: %w", err)
}
err = c.client.Set(ctx, key, data, expiration).Err()
if err != nil {
return fmt.Errorf("设置指标缓存失败: %w", err)
}
return nil
}
// GetMetric 获取指标缓存
func (c *RedisStatisticsCache) GetMetric(ctx context.Context, metricID string) (*entities.StatisticsMetric, error) {
if metricID == "" {
return nil, fmt.Errorf("指标ID不能为空")
}
key := c.getMetricKey(metricID)
data, err := c.client.Get(ctx, key).Result()
if err != nil {
if err == redis.Nil {
return nil, nil // 缓存未命中
}
return nil, fmt.Errorf("获取指标缓存失败: %w", err)
}
var metric entities.StatisticsMetric
err = json.Unmarshal([]byte(data), &metric)
if err != nil {
return nil, fmt.Errorf("反序列化指标失败: %w", err)
}
return &metric, nil
}
// DeleteMetric 删除指标缓存
func (c *RedisStatisticsCache) DeleteMetric(ctx context.Context, metricID string) error {
if metricID == "" {
return fmt.Errorf("指标ID不能为空")
}
key := c.getMetricKey(metricID)
err := c.client.Del(ctx, key).Err()
if err != nil {
return fmt.Errorf("删除指标缓存失败: %w", err)
}
return nil
}
// SetMetricsByType 设置按类型分组的指标缓存
func (c *RedisStatisticsCache) SetMetricsByType(ctx context.Context, metricType string, metrics []*entities.StatisticsMetric, expiration time.Duration) error {
if metricType == "" {
return fmt.Errorf("指标类型不能为空")
}
key := c.getMetricsByTypeKey(metricType)
data, err := json.Marshal(metrics)
if err != nil {
return fmt.Errorf("序列化指标列表失败: %w", err)
}
err = c.client.Set(ctx, key, data, expiration).Err()
if err != nil {
return fmt.Errorf("设置指标列表缓存失败: %w", err)
}
return nil
}
// GetMetricsByType 获取按类型分组的指标缓存
func (c *RedisStatisticsCache) GetMetricsByType(ctx context.Context, metricType string) ([]*entities.StatisticsMetric, error) {
if metricType == "" {
return nil, fmt.Errorf("指标类型不能为空")
}
key := c.getMetricsByTypeKey(metricType)
data, err := c.client.Get(ctx, key).Result()
if err != nil {
if err == redis.Nil {
return nil, nil // 缓存未命中
}
return nil, fmt.Errorf("获取指标列表缓存失败: %w", err)
}
var metrics []*entities.StatisticsMetric
err = json.Unmarshal([]byte(data), &metrics)
if err != nil {
return nil, fmt.Errorf("反序列化指标列表失败: %w", err)
}
return metrics, nil
}
// DeleteMetricsByType 删除按类型分组的指标缓存
func (c *RedisStatisticsCache) DeleteMetricsByType(ctx context.Context, metricType string) error {
if metricType == "" {
return fmt.Errorf("指标类型不能为空")
}
key := c.getMetricsByTypeKey(metricType)
err := c.client.Del(ctx, key).Err()
if err != nil {
return fmt.Errorf("删除指标列表缓存失败: %w", err)
}
return nil
}
// ================ 实时指标缓存 ================
// SetRealtimeMetrics 设置实时指标缓存
func (c *RedisStatisticsCache) SetRealtimeMetrics(ctx context.Context, metricType string, metrics map[string]float64, expiration time.Duration) error {
if metricType == "" {
return fmt.Errorf("指标类型不能为空")
}
key := c.getRealtimeMetricsKey(metricType)
data, err := json.Marshal(metrics)
if err != nil {
return fmt.Errorf("序列化实时指标失败: %w", err)
}
err = c.client.Set(ctx, key, data, expiration).Err()
if err != nil {
return fmt.Errorf("设置实时指标缓存失败: %w", err)
}
return nil
}
// GetRealtimeMetrics 获取实时指标缓存
func (c *RedisStatisticsCache) GetRealtimeMetrics(ctx context.Context, metricType string) (map[string]float64, error) {
if metricType == "" {
return nil, fmt.Errorf("指标类型不能为空")
}
key := c.getRealtimeMetricsKey(metricType)
data, err := c.client.Get(ctx, key).Result()
if err != nil {
if err == redis.Nil {
return nil, nil // 缓存未命中
}
return nil, fmt.Errorf("获取实时指标缓存失败: %w", err)
}
var metrics map[string]float64
err = json.Unmarshal([]byte(data), &metrics)
if err != nil {
return nil, fmt.Errorf("反序列化实时指标失败: %w", err)
}
return metrics, nil
}
// UpdateRealtimeMetric 更新实时指标
func (c *RedisStatisticsCache) UpdateRealtimeMetric(ctx context.Context, metricType, metricName string, value float64, expiration time.Duration) error {
if metricType == "" || metricName == "" {
return fmt.Errorf("指标类型和名称不能为空")
}
// 获取现有指标
metrics, err := c.GetRealtimeMetrics(ctx, metricType)
if err != nil {
return fmt.Errorf("获取实时指标失败: %w", err)
}
if metrics == nil {
metrics = make(map[string]float64)
}
// 更新指标值
metrics[metricName] = value
// 保存更新后的指标
err = c.SetRealtimeMetrics(ctx, metricType, metrics, expiration)
if err != nil {
return fmt.Errorf("更新实时指标失败: %w", err)
}
return nil
}
// ================ 报告缓存 ================
// SetReport 设置报告缓存
func (c *RedisStatisticsCache) SetReport(ctx context.Context, report *entities.StatisticsReport, expiration time.Duration) error {
if report == nil {
return fmt.Errorf("统计报告不能为空")
}
key := c.getReportKey(report.ID)
data, err := json.Marshal(report)
if err != nil {
return fmt.Errorf("序列化报告失败: %w", err)
}
err = c.client.Set(ctx, key, data, expiration).Err()
if err != nil {
return fmt.Errorf("设置报告缓存失败: %w", err)
}
return nil
}
// GetReport 获取报告缓存
func (c *RedisStatisticsCache) GetReport(ctx context.Context, reportID string) (*entities.StatisticsReport, error) {
if reportID == "" {
return nil, fmt.Errorf("报告ID不能为空")
}
key := c.getReportKey(reportID)
data, err := c.client.Get(ctx, key).Result()
if err != nil {
if err == redis.Nil {
return nil, nil // 缓存未命中
}
return nil, fmt.Errorf("获取报告缓存失败: %w", err)
}
var report entities.StatisticsReport
err = json.Unmarshal([]byte(data), &report)
if err != nil {
return nil, fmt.Errorf("反序列化报告失败: %w", err)
}
return &report, nil
}
// DeleteReport 删除报告缓存
func (c *RedisStatisticsCache) DeleteReport(ctx context.Context, reportID string) error {
if reportID == "" {
return fmt.Errorf("报告ID不能为空")
}
key := c.getReportKey(reportID)
err := c.client.Del(ctx, key).Err()
if err != nil {
return fmt.Errorf("删除报告缓存失败: %w", err)
}
return nil
}
// ================ 仪表板缓存 ================
// SetDashboard 设置仪表板缓存
func (c *RedisStatisticsCache) SetDashboard(ctx context.Context, dashboard *entities.StatisticsDashboard, expiration time.Duration) error {
if dashboard == nil {
return fmt.Errorf("统计仪表板不能为空")
}
key := c.getDashboardKey(dashboard.ID)
data, err := json.Marshal(dashboard)
if err != nil {
return fmt.Errorf("序列化仪表板失败: %w", err)
}
err = c.client.Set(ctx, key, data, expiration).Err()
if err != nil {
return fmt.Errorf("设置仪表板缓存失败: %w", err)
}
return nil
}
// GetDashboard 获取仪表板缓存
func (c *RedisStatisticsCache) GetDashboard(ctx context.Context, dashboardID string) (*entities.StatisticsDashboard, error) {
if dashboardID == "" {
return nil, fmt.Errorf("仪表板ID不能为空")
}
key := c.getDashboardKey(dashboardID)
data, err := c.client.Get(ctx, key).Result()
if err != nil {
if err == redis.Nil {
return nil, nil // 缓存未命中
}
return nil, fmt.Errorf("获取仪表板缓存失败: %w", err)
}
var dashboard entities.StatisticsDashboard
err = json.Unmarshal([]byte(data), &dashboard)
if err != nil {
return nil, fmt.Errorf("反序列化仪表板失败: %w", err)
}
return &dashboard, nil
}
// DeleteDashboard 删除仪表板缓存
func (c *RedisStatisticsCache) DeleteDashboard(ctx context.Context, dashboardID string) error {
if dashboardID == "" {
return fmt.Errorf("仪表板ID不能为空")
}
key := c.getDashboardKey(dashboardID)
err := c.client.Del(ctx, key).Err()
if err != nil {
return fmt.Errorf("删除仪表板缓存失败: %w", err)
}
return nil
}
// SetDashboardData 设置仪表板数据缓存
func (c *RedisStatisticsCache) SetDashboardData(ctx context.Context, userRole string, data interface{}, expiration time.Duration) error {
if userRole == "" {
return fmt.Errorf("用户角色不能为空")
}
key := c.getDashboardDataKey(userRole)
jsonData, err := json.Marshal(data)
if err != nil {
return fmt.Errorf("序列化仪表板数据失败: %w", err)
}
err = c.client.Set(ctx, key, jsonData, expiration).Err()
if err != nil {
return fmt.Errorf("设置仪表板数据缓存失败: %w", err)
}
return nil
}
// GetDashboardData 获取仪表板数据缓存
func (c *RedisStatisticsCache) GetDashboardData(ctx context.Context, userRole string) (interface{}, error) {
if userRole == "" {
return nil, fmt.Errorf("用户角色不能为空")
}
key := c.getDashboardDataKey(userRole)
data, err := c.client.Get(ctx, key).Result()
if err != nil {
if err == redis.Nil {
return nil, nil // 缓存未命中
}
return nil, fmt.Errorf("获取仪表板数据缓存失败: %w", err)
}
var result interface{}
err = json.Unmarshal([]byte(data), &result)
if err != nil {
return nil, fmt.Errorf("反序列化仪表板数据失败: %w", err)
}
return result, nil
}
// DeleteDashboardData 删除仪表板数据缓存
func (c *RedisStatisticsCache) DeleteDashboardData(ctx context.Context, userRole string) error {
if userRole == "" {
return fmt.Errorf("用户角色不能为空")
}
key := c.getDashboardDataKey(userRole)
err := c.client.Del(ctx, key).Err()
if err != nil {
return fmt.Errorf("删除仪表板数据缓存失败: %w", err)
}
return nil
}
// ================ 缓存键生成 ================
// getMetricKey 获取指标缓存键
func (c *RedisStatisticsCache) getMetricKey(metricID string) string {
return c.prefix + "metric:" + metricID
}
// getMetricsByTypeKey 获取按类型分组的指标缓存键
func (c *RedisStatisticsCache) getMetricsByTypeKey(metricType string) string {
return c.prefix + "metrics:type:" + metricType
}
// getRealtimeMetricsKey 获取实时指标缓存键
func (c *RedisStatisticsCache) getRealtimeMetricsKey(metricType string) string {
return c.prefix + "realtime:" + metricType
}
// getReportKey 获取报告缓存键
func (c *RedisStatisticsCache) getReportKey(reportID string) string {
return c.prefix + "report:" + reportID
}
// getDashboardKey 获取仪表板缓存键
func (c *RedisStatisticsCache) getDashboardKey(dashboardID string) string {
return c.prefix + "dashboard:" + dashboardID
}
// getDashboardDataKey 获取仪表板数据缓存键
func (c *RedisStatisticsCache) getDashboardDataKey(userRole string) string {
return c.prefix + "dashboard:data:" + userRole
}
// ================ 批量操作 ================
// BatchDeleteMetrics 批量删除指标缓存
func (c *RedisStatisticsCache) BatchDeleteMetrics(ctx context.Context, metricIDs []string) error {
if len(metricIDs) == 0 {
return nil
}
keys := make([]string, len(metricIDs))
for i, id := range metricIDs {
keys[i] = c.getMetricKey(id)
}
err := c.client.Del(ctx, keys...).Err()
if err != nil {
return fmt.Errorf("批量删除指标缓存失败: %w", err)
}
return nil
}
// BatchDeleteReports 批量删除报告缓存
func (c *RedisStatisticsCache) BatchDeleteReports(ctx context.Context, reportIDs []string) error {
if len(reportIDs) == 0 {
return nil
}
keys := make([]string, len(reportIDs))
for i, id := range reportIDs {
keys[i] = c.getReportKey(id)
}
err := c.client.Del(ctx, keys...).Err()
if err != nil {
return fmt.Errorf("批量删除报告缓存失败: %w", err)
}
return nil
}
// BatchDeleteDashboards 批量删除仪表板缓存
func (c *RedisStatisticsCache) BatchDeleteDashboards(ctx context.Context, dashboardIDs []string) error {
if len(dashboardIDs) == 0 {
return nil
}
keys := make([]string, len(dashboardIDs))
for i, id := range dashboardIDs {
keys[i] = c.getDashboardKey(id)
}
err := c.client.Del(ctx, keys...).Err()
if err != nil {
return fmt.Errorf("批量删除仪表板缓存失败: %w", err)
}
return nil
}
// ================ 缓存清理 ================
// ClearAllStatisticsCache 清理所有统计缓存
func (c *RedisStatisticsCache) ClearAllStatisticsCache(ctx context.Context) error {
pattern := c.prefix + "*"
keys, err := c.client.Keys(ctx, pattern).Result()
if err != nil {
return fmt.Errorf("获取缓存键失败: %w", err)
}
if len(keys) > 0 {
err = c.client.Del(ctx, keys...).Err()
if err != nil {
return fmt.Errorf("清理统计缓存失败: %w", err)
}
}
return nil
}
// ClearMetricsCache 清理指标缓存
func (c *RedisStatisticsCache) ClearMetricsCache(ctx context.Context) error {
pattern := c.prefix + "metric:*"
keys, err := c.client.Keys(ctx, pattern).Result()
if err != nil {
return fmt.Errorf("获取指标缓存键失败: %w", err)
}
if len(keys) > 0 {
err = c.client.Del(ctx, keys...).Err()
if err != nil {
return fmt.Errorf("清理指标缓存失败: %w", err)
}
}
return nil
}
// ClearRealtimeCache 清理实时缓存
func (c *RedisStatisticsCache) ClearRealtimeCache(ctx context.Context) error {
pattern := c.prefix + "realtime:*"
keys, err := c.client.Keys(ctx, pattern).Result()
if err != nil {
return fmt.Errorf("获取实时缓存键失败: %w", err)
}
if len(keys) > 0 {
err = c.client.Del(ctx, keys...).Err()
if err != nil {
return fmt.Errorf("清理实时缓存失败: %w", err)
}
}
return nil
}
// ClearReportsCache 清理报告缓存
func (c *RedisStatisticsCache) ClearReportsCache(ctx context.Context) error {
pattern := c.prefix + "report:*"
keys, err := c.client.Keys(ctx, pattern).Result()
if err != nil {
return fmt.Errorf("获取报告缓存键失败: %w", err)
}
if len(keys) > 0 {
err = c.client.Del(ctx, keys...).Err()
if err != nil {
return fmt.Errorf("清理报告缓存失败: %w", err)
}
}
return nil
}
// ClearDashboardsCache 清理仪表板缓存
func (c *RedisStatisticsCache) ClearDashboardsCache(ctx context.Context) error {
pattern := c.prefix + "dashboard:*"
keys, err := c.client.Keys(ctx, pattern).Result()
if err != nil {
return fmt.Errorf("获取仪表板缓存键失败: %w", err)
}
if len(keys) > 0 {
err = c.client.Del(ctx, keys...).Err()
if err != nil {
return fmt.Errorf("清理仪表板缓存失败: %w", err)
}
}
return nil
}

View File

@@ -0,0 +1,404 @@
package cron
import (
"context"
"fmt"
"time"
"github.com/robfig/cron/v3"
"go.uber.org/zap"
"tyapi-server/internal/application/statistics"
)
// StatisticsCronJob 统计定时任务
type StatisticsCronJob struct {
appService statistics.StatisticsApplicationService
logger *zap.Logger
cron *cron.Cron
}
// NewStatisticsCronJob 创建统计定时任务
func NewStatisticsCronJob(
appService statistics.StatisticsApplicationService,
logger *zap.Logger,
) *StatisticsCronJob {
return &StatisticsCronJob{
appService: appService,
logger: logger,
cron: cron.New(cron.WithLocation(time.UTC)),
}
}
// Start 启动定时任务
func (j *StatisticsCronJob) Start() error {
j.logger.Info("启动统计定时任务")
// 每小时聚合任务 - 每小时的第5分钟执行
_, err := j.cron.AddFunc("5 * * * *", j.hourlyAggregationJob)
if err != nil {
return fmt.Errorf("添加小时聚合任务失败: %w", err)
}
// 每日聚合任务 - 每天凌晨1点执行
_, err = j.cron.AddFunc("0 1 * * *", j.dailyAggregationJob)
if err != nil {
return fmt.Errorf("添加日聚合任务失败: %w", err)
}
// 每周聚合任务 - 每周一凌晨2点执行
_, err = j.cron.AddFunc("0 2 * * 1", j.weeklyAggregationJob)
if err != nil {
return fmt.Errorf("添加周聚合任务失败: %w", err)
}
// 每月聚合任务 - 每月1号凌晨3点执行
_, err = j.cron.AddFunc("0 3 1 * *", j.monthlyAggregationJob)
if err != nil {
return fmt.Errorf("添加月聚合任务失败: %w", err)
}
// 数据清理任务 - 每天凌晨4点执行
_, err = j.cron.AddFunc("0 4 * * *", j.dataCleanupJob)
if err != nil {
return fmt.Errorf("添加数据清理任务失败: %w", err)
}
// 缓存预热任务 - 每天早上6点执行
_, err = j.cron.AddFunc("0 6 * * *", j.cacheWarmupJob)
if err != nil {
return fmt.Errorf("添加缓存预热任务失败: %w", err)
}
// 启动定时器
j.cron.Start()
j.logger.Info("统计定时任务启动成功")
return nil
}
// Stop 停止定时任务
func (j *StatisticsCronJob) Stop() {
j.logger.Info("停止统计定时任务")
j.cron.Stop()
j.logger.Info("统计定时任务已停止")
}
// ================ 定时任务实现 ================
// hourlyAggregationJob 小时聚合任务
func (j *StatisticsCronJob) hourlyAggregationJob() {
ctx := context.Background()
now := time.Now()
// 聚合上一小时的数据
lastHour := now.Add(-1 * time.Hour).Truncate(time.Hour)
j.logger.Info("开始执行小时聚合任务", zap.Time("target_hour", lastHour))
err := j.appService.ProcessHourlyAggregation(ctx, lastHour)
if err != nil {
j.logger.Error("小时聚合任务执行失败",
zap.Time("target_hour", lastHour),
zap.Error(err))
return
}
j.logger.Info("小时聚合任务执行成功", zap.Time("target_hour", lastHour))
}
// dailyAggregationJob 日聚合任务
func (j *StatisticsCronJob) dailyAggregationJob() {
ctx := context.Background()
now := time.Now()
// 聚合昨天的数据
yesterday := now.AddDate(0, 0, -1).Truncate(24 * time.Hour)
j.logger.Info("开始执行日聚合任务", zap.Time("target_date", yesterday))
err := j.appService.ProcessDailyAggregation(ctx, yesterday)
if err != nil {
j.logger.Error("日聚合任务执行失败",
zap.Time("target_date", yesterday),
zap.Error(err))
return
}
j.logger.Info("日聚合任务执行成功", zap.Time("target_date", yesterday))
}
// weeklyAggregationJob 周聚合任务
func (j *StatisticsCronJob) weeklyAggregationJob() {
ctx := context.Background()
now := time.Now()
// 聚合上一周的数据
lastWeek := now.AddDate(0, 0, -7).Truncate(24 * time.Hour)
j.logger.Info("开始执行周聚合任务", zap.Time("target_week", lastWeek))
err := j.appService.ProcessWeeklyAggregation(ctx, lastWeek)
if err != nil {
j.logger.Error("周聚合任务执行失败",
zap.Time("target_week", lastWeek),
zap.Error(err))
return
}
j.logger.Info("周聚合任务执行成功", zap.Time("target_week", lastWeek))
}
// monthlyAggregationJob 月聚合任务
func (j *StatisticsCronJob) monthlyAggregationJob() {
ctx := context.Background()
now := time.Now()
// 聚合上个月的数据
lastMonth := now.AddDate(0, -1, 0).Truncate(24 * time.Hour)
j.logger.Info("开始执行月聚合任务", zap.Time("target_month", lastMonth))
err := j.appService.ProcessMonthlyAggregation(ctx, lastMonth)
if err != nil {
j.logger.Error("月聚合任务执行失败",
zap.Time("target_month", lastMonth),
zap.Error(err))
return
}
j.logger.Info("月聚合任务执行成功", zap.Time("target_month", lastMonth))
}
// dataCleanupJob 数据清理任务
func (j *StatisticsCronJob) dataCleanupJob() {
ctx := context.Background()
j.logger.Info("开始执行数据清理任务")
err := j.appService.CleanupExpiredData(ctx)
if err != nil {
j.logger.Error("数据清理任务执行失败", zap.Error(err))
return
}
j.logger.Info("数据清理任务执行成功")
}
// cacheWarmupJob 缓存预热任务
func (j *StatisticsCronJob) cacheWarmupJob() {
ctx := context.Background()
j.logger.Info("开始执行缓存预热任务")
// 预热仪表板数据
err := j.warmupDashboardCache(ctx)
if err != nil {
j.logger.Error("仪表板缓存预热失败", zap.Error(err))
}
// 预热实时指标
err = j.warmupRealtimeMetricsCache(ctx)
if err != nil {
j.logger.Error("实时指标缓存预热失败", zap.Error(err))
}
j.logger.Info("缓存预热任务执行完成")
}
// ================ 缓存预热辅助方法 ================
// warmupDashboardCache 预热仪表板缓存
func (j *StatisticsCronJob) warmupDashboardCache(ctx context.Context) error {
// 获取所有用户角色
userRoles := []string{"admin", "user", "manager", "analyst"}
for _, role := range userRoles {
// 获取仪表板数据
query := &statistics.GetDashboardDataQuery{
UserRole: role,
Period: "today",
StartDate: time.Now().Truncate(24 * time.Hour),
EndDate: time.Now(),
}
_, err := j.appService.GetDashboardData(ctx, query)
if err != nil {
j.logger.Error("预热仪表板缓存失败",
zap.String("user_role", role),
zap.Error(err))
continue
}
j.logger.Info("仪表板缓存预热成功", zap.String("user_role", role))
}
return nil
}
// warmupRealtimeMetricsCache 预热实时指标缓存
func (j *StatisticsCronJob) warmupRealtimeMetricsCache(ctx context.Context) error {
// 获取所有指标类型
metricTypes := []string{"api_calls", "users", "finance", "products", "certification"}
for _, metricType := range metricTypes {
// 获取实时指标
query := &statistics.GetRealtimeMetricsQuery{
MetricType: metricType,
TimeRange: "last_hour",
}
_, err := j.appService.GetRealtimeMetrics(ctx, query)
if err != nil {
j.logger.Error("预热实时指标缓存失败",
zap.String("metric_type", metricType),
zap.Error(err))
continue
}
j.logger.Info("实时指标缓存预热成功", zap.String("metric_type", metricType))
}
return nil
}
// ================ 手动触发任务 ================
// TriggerHourlyAggregation 手动触发小时聚合
func (j *StatisticsCronJob) TriggerHourlyAggregation(targetHour time.Time) error {
ctx := context.Background()
j.logger.Info("手动触发小时聚合任务", zap.Time("target_hour", targetHour))
err := j.appService.ProcessHourlyAggregation(ctx, targetHour)
if err != nil {
j.logger.Error("手动小时聚合任务执行失败",
zap.Time("target_hour", targetHour),
zap.Error(err))
return err
}
j.logger.Info("手动小时聚合任务执行成功", zap.Time("target_hour", targetHour))
return nil
}
// TriggerDailyAggregation 手动触发日聚合
func (j *StatisticsCronJob) TriggerDailyAggregation(targetDate time.Time) error {
ctx := context.Background()
j.logger.Info("手动触发日聚合任务", zap.Time("target_date", targetDate))
err := j.appService.ProcessDailyAggregation(ctx, targetDate)
if err != nil {
j.logger.Error("手动日聚合任务执行失败",
zap.Time("target_date", targetDate),
zap.Error(err))
return err
}
j.logger.Info("手动日聚合任务执行成功", zap.Time("target_date", targetDate))
return nil
}
// TriggerWeeklyAggregation 手动触发周聚合
func (j *StatisticsCronJob) TriggerWeeklyAggregation(targetWeek time.Time) error {
ctx := context.Background()
j.logger.Info("手动触发周聚合任务", zap.Time("target_week", targetWeek))
err := j.appService.ProcessWeeklyAggregation(ctx, targetWeek)
if err != nil {
j.logger.Error("手动周聚合任务执行失败",
zap.Time("target_week", targetWeek),
zap.Error(err))
return err
}
j.logger.Info("手动周聚合任务执行成功", zap.Time("target_week", targetWeek))
return nil
}
// TriggerMonthlyAggregation 手动触发月聚合
func (j *StatisticsCronJob) TriggerMonthlyAggregation(targetMonth time.Time) error {
ctx := context.Background()
j.logger.Info("手动触发月聚合任务", zap.Time("target_month", targetMonth))
err := j.appService.ProcessMonthlyAggregation(ctx, targetMonth)
if err != nil {
j.logger.Error("手动月聚合任务执行失败",
zap.Time("target_month", targetMonth),
zap.Error(err))
return err
}
j.logger.Info("手动月聚合任务执行成功", zap.Time("target_month", targetMonth))
return nil
}
// TriggerDataCleanup 手动触发数据清理
func (j *StatisticsCronJob) TriggerDataCleanup() error {
ctx := context.Background()
j.logger.Info("手动触发数据清理任务")
err := j.appService.CleanupExpiredData(ctx)
if err != nil {
j.logger.Error("手动数据清理任务执行失败", zap.Error(err))
return err
}
j.logger.Info("手动数据清理任务执行成功")
return nil
}
// TriggerCacheWarmup 手动触发缓存预热
func (j *StatisticsCronJob) TriggerCacheWarmup() error {
j.logger.Info("手动触发缓存预热任务")
// 预热仪表板缓存
err := j.warmupDashboardCache(context.Background())
if err != nil {
j.logger.Error("手动仪表板缓存预热失败", zap.Error(err))
}
// 预热实时指标缓存
err = j.warmupRealtimeMetricsCache(context.Background())
if err != nil {
j.logger.Error("手动实时指标缓存预热失败", zap.Error(err))
}
j.logger.Info("手动缓存预热任务执行完成")
return nil
}
// ================ 任务状态查询 ================
// GetCronEntries 获取定时任务条目
func (j *StatisticsCronJob) GetCronEntries() []cron.Entry {
return j.cron.Entries()
}
// GetNextRunTime 获取下次运行时间
func (j *StatisticsCronJob) GetNextRunTime() time.Time {
entries := j.cron.Entries()
if len(entries) == 0 {
return time.Time{}
}
// 返回最近的运行时间
nextRun := entries[0].Next
for _, entry := range entries[1:] {
if entry.Next.Before(nextRun) {
nextRun = entry.Next
}
}
return nextRun
}
// IsRunning 检查任务是否正在运行
func (j *StatisticsCronJob) IsRunning() bool {
return j.cron != nil
}

View File

@@ -0,0 +1,498 @@
package events
import (
"context"
"encoding/json"
"fmt"
"time"
"go.uber.org/zap"
"tyapi-server/internal/domains/statistics/events"
"tyapi-server/internal/domains/statistics/repositories"
"tyapi-server/internal/infrastructure/statistics/cache"
)
// StatisticsEventHandler 统计事件处理器
type StatisticsEventHandler struct {
metricRepo repositories.StatisticsRepository
reportRepo repositories.StatisticsReportRepository
dashboardRepo repositories.StatisticsDashboardRepository
cache *cache.RedisStatisticsCache
logger *zap.Logger
}
// NewStatisticsEventHandler 创建统计事件处理器
func NewStatisticsEventHandler(
metricRepo repositories.StatisticsRepository,
reportRepo repositories.StatisticsReportRepository,
dashboardRepo repositories.StatisticsDashboardRepository,
cache *cache.RedisStatisticsCache,
logger *zap.Logger,
) *StatisticsEventHandler {
return &StatisticsEventHandler{
metricRepo: metricRepo,
reportRepo: reportRepo,
dashboardRepo: dashboardRepo,
cache: cache,
logger: logger,
}
}
// HandleMetricCreatedEvent 处理指标创建事件
func (h *StatisticsEventHandler) HandleMetricCreatedEvent(ctx context.Context, event *events.MetricCreatedEvent) error {
h.logger.Info("处理指标创建事件",
zap.String("metric_id", event.MetricID),
zap.String("metric_type", event.MetricType),
zap.String("metric_name", event.MetricName),
zap.Float64("value", event.Value))
// 更新实时指标缓存
err := h.cache.UpdateRealtimeMetric(ctx, event.MetricType, event.MetricName, event.Value, 1*time.Hour)
if err != nil {
h.logger.Error("更新实时指标缓存失败", zap.Error(err))
// 不返回错误,避免影响主流程
}
// 清理相关缓存
err = h.cache.DeleteMetricsByType(ctx, event.MetricType)
if err != nil {
h.logger.Error("清理指标类型缓存失败", zap.Error(err))
}
return nil
}
// HandleMetricUpdatedEvent 处理指标更新事件
func (h *StatisticsEventHandler) HandleMetricUpdatedEvent(ctx context.Context, event *events.MetricUpdatedEvent) error {
h.logger.Info("处理指标更新事件",
zap.String("metric_id", event.MetricID),
zap.Float64("old_value", event.OldValue),
zap.Float64("new_value", event.NewValue))
// 获取指标信息
metric, err := h.metricRepo.FindByID(ctx, event.MetricID)
if err != nil {
h.logger.Error("查询指标失败", zap.Error(err))
return err
}
// 更新实时指标缓存
err = h.cache.UpdateRealtimeMetric(ctx, metric.MetricType, metric.MetricName, event.NewValue, 1*time.Hour)
if err != nil {
h.logger.Error("更新实时指标缓存失败", zap.Error(err))
}
// 清理相关缓存
err = h.cache.DeleteMetric(ctx, event.MetricID)
if err != nil {
h.logger.Error("清理指标缓存失败", zap.Error(err))
}
err = h.cache.DeleteMetricsByType(ctx, metric.MetricType)
if err != nil {
h.logger.Error("清理指标类型缓存失败", zap.Error(err))
}
return nil
}
// HandleMetricAggregatedEvent 处理指标聚合事件
func (h *StatisticsEventHandler) HandleMetricAggregatedEvent(ctx context.Context, event *events.MetricAggregatedEvent) error {
h.logger.Info("处理指标聚合事件",
zap.String("metric_type", event.MetricType),
zap.String("dimension", event.Dimension),
zap.Int("record_count", event.RecordCount),
zap.Float64("total_value", event.TotalValue))
// 清理相关缓存
err := h.cache.ClearRealtimeCache(ctx)
if err != nil {
h.logger.Error("清理实时缓存失败", zap.Error(err))
}
err = h.cache.ClearMetricsCache(ctx)
if err != nil {
h.logger.Error("清理指标缓存失败", zap.Error(err))
}
return nil
}
// HandleReportCreatedEvent 处理报告创建事件
func (h *StatisticsEventHandler) HandleReportCreatedEvent(ctx context.Context, event *events.ReportCreatedEvent) error {
h.logger.Info("处理报告创建事件",
zap.String("report_id", event.ReportID),
zap.String("report_type", event.ReportType),
zap.String("title", event.Title))
// 获取报告信息
report, err := h.reportRepo.FindByID(ctx, event.ReportID)
if err != nil {
h.logger.Error("查询报告失败", zap.Error(err))
return err
}
// 设置报告缓存
err = h.cache.SetReport(ctx, report, 24*time.Hour)
if err != nil {
h.logger.Error("设置报告缓存失败", zap.Error(err))
}
return nil
}
// HandleReportGenerationStartedEvent 处理报告生成开始事件
func (h *StatisticsEventHandler) HandleReportGenerationStartedEvent(ctx context.Context, event *events.ReportGenerationStartedEvent) error {
h.logger.Info("处理报告生成开始事件",
zap.String("report_id", event.ReportID),
zap.String("generated_by", event.GeneratedBy))
// 获取报告信息
report, err := h.reportRepo.FindByID(ctx, event.ReportID)
if err != nil {
h.logger.Error("查询报告失败", zap.Error(err))
return err
}
// 更新报告缓存
err = h.cache.SetReport(ctx, report, 24*time.Hour)
if err != nil {
h.logger.Error("更新报告缓存失败", zap.Error(err))
}
return nil
}
// HandleReportCompletedEvent 处理报告完成事件
func (h *StatisticsEventHandler) HandleReportCompletedEvent(ctx context.Context, event *events.ReportCompletedEvent) error {
h.logger.Info("处理报告完成事件",
zap.String("report_id", event.ReportID),
zap.Int("content_size", event.ContentSize))
// 获取报告信息
report, err := h.reportRepo.FindByID(ctx, event.ReportID)
if err != nil {
h.logger.Error("查询报告失败", zap.Error(err))
return err
}
// 更新报告缓存
err = h.cache.SetReport(ctx, report, 7*24*time.Hour) // 报告完成后缓存7天
if err != nil {
h.logger.Error("更新报告缓存失败", zap.Error(err))
}
return nil
}
// HandleReportFailedEvent 处理报告失败事件
func (h *StatisticsEventHandler) HandleReportFailedEvent(ctx context.Context, event *events.ReportFailedEvent) error {
h.logger.Info("处理报告失败事件",
zap.String("report_id", event.ReportID),
zap.String("reason", event.Reason))
// 获取报告信息
report, err := h.reportRepo.FindByID(ctx, event.ReportID)
if err != nil {
h.logger.Error("查询报告失败", zap.Error(err))
return err
}
// 更新报告缓存
err = h.cache.SetReport(ctx, report, 1*time.Hour) // 失败报告只缓存1小时
if err != nil {
h.logger.Error("更新报告缓存失败", zap.Error(err))
}
return nil
}
// HandleDashboardCreatedEvent 处理仪表板创建事件
func (h *StatisticsEventHandler) HandleDashboardCreatedEvent(ctx context.Context, event *events.DashboardCreatedEvent) error {
h.logger.Info("处理仪表板创建事件",
zap.String("dashboard_id", event.DashboardID),
zap.String("name", event.Name),
zap.String("user_role", event.UserRole))
// 获取仪表板信息
dashboard, err := h.dashboardRepo.FindByID(ctx, event.DashboardID)
if err != nil {
h.logger.Error("查询仪表板失败", zap.Error(err))
return err
}
// 设置仪表板缓存
err = h.cache.SetDashboard(ctx, dashboard, 24*time.Hour)
if err != nil {
h.logger.Error("设置仪表板缓存失败", zap.Error(err))
}
// 清理仪表板数据缓存
err = h.cache.DeleteDashboardData(ctx, event.UserRole)
if err != nil {
h.logger.Error("清理仪表板数据缓存失败", zap.Error(err))
}
return nil
}
// HandleDashboardUpdatedEvent 处理仪表板更新事件
func (h *StatisticsEventHandler) HandleDashboardUpdatedEvent(ctx context.Context, event *events.DashboardUpdatedEvent) error {
h.logger.Info("处理仪表板更新事件",
zap.String("dashboard_id", event.DashboardID),
zap.String("updated_by", event.UpdatedBy))
// 获取仪表板信息
dashboard, err := h.dashboardRepo.FindByID(ctx, event.DashboardID)
if err != nil {
h.logger.Error("查询仪表板失败", zap.Error(err))
return err
}
// 更新仪表板缓存
err = h.cache.SetDashboard(ctx, dashboard, 24*time.Hour)
if err != nil {
h.logger.Error("更新仪表板缓存失败", zap.Error(err))
}
// 清理仪表板数据缓存
err = h.cache.DeleteDashboardData(ctx, dashboard.UserRole)
if err != nil {
h.logger.Error("清理仪表板数据缓存失败", zap.Error(err))
}
return nil
}
// HandleDashboardActivatedEvent 处理仪表板激活事件
func (h *StatisticsEventHandler) HandleDashboardActivatedEvent(ctx context.Context, event *events.DashboardActivatedEvent) error {
h.logger.Info("处理仪表板激活事件",
zap.String("dashboard_id", event.DashboardID),
zap.String("activated_by", event.ActivatedBy))
// 获取仪表板信息
dashboard, err := h.dashboardRepo.FindByID(ctx, event.DashboardID)
if err != nil {
h.logger.Error("查询仪表板失败", zap.Error(err))
return err
}
// 更新仪表板缓存
err = h.cache.SetDashboard(ctx, dashboard, 24*time.Hour)
if err != nil {
h.logger.Error("更新仪表板缓存失败", zap.Error(err))
}
// 清理仪表板数据缓存
err = h.cache.DeleteDashboardData(ctx, dashboard.UserRole)
if err != nil {
h.logger.Error("清理仪表板数据缓存失败", zap.Error(err))
}
return nil
}
// HandleDashboardDeactivatedEvent 处理仪表板停用事件
func (h *StatisticsEventHandler) HandleDashboardDeactivatedEvent(ctx context.Context, event *events.DashboardDeactivatedEvent) error {
h.logger.Info("处理仪表板停用事件",
zap.String("dashboard_id", event.DashboardID),
zap.String("deactivated_by", event.DeactivatedBy))
// 获取仪表板信息
dashboard, err := h.dashboardRepo.FindByID(ctx, event.DashboardID)
if err != nil {
h.logger.Error("查询仪表板失败", zap.Error(err))
return err
}
// 更新仪表板缓存
err = h.cache.SetDashboard(ctx, dashboard, 24*time.Hour)
if err != nil {
h.logger.Error("更新仪表板缓存失败", zap.Error(err))
}
// 清理仪表板数据缓存
err = h.cache.DeleteDashboardData(ctx, dashboard.UserRole)
if err != nil {
h.logger.Error("清理仪表板数据缓存失败", zap.Error(err))
}
return nil
}
// ================ 事件分发器 ================
// EventDispatcher 事件分发器
type EventDispatcher struct {
handlers map[string][]func(context.Context, interface{}) error
logger *zap.Logger
}
// NewEventDispatcher 创建事件分发器
func NewEventDispatcher(logger *zap.Logger) *EventDispatcher {
return &EventDispatcher{
handlers: make(map[string][]func(context.Context, interface{}) error),
logger: logger,
}
}
// RegisterHandler 注册事件处理器
func (d *EventDispatcher) RegisterHandler(eventType string, handler func(context.Context, interface{}) error) {
if d.handlers[eventType] == nil {
d.handlers[eventType] = make([]func(context.Context, interface{}) error, 0)
}
d.handlers[eventType] = append(d.handlers[eventType], handler)
}
// Dispatch 分发事件
func (d *EventDispatcher) Dispatch(ctx context.Context, event interface{}) error {
// 获取事件类型
eventType := d.getEventType(event)
if eventType == "" {
return fmt.Errorf("无法确定事件类型")
}
// 获取处理器
handlers := d.handlers[eventType]
if len(handlers) == 0 {
d.logger.Warn("没有找到事件处理器", zap.String("event_type", eventType))
return nil
}
// 执行所有处理器
for _, handler := range handlers {
err := handler(ctx, event)
if err != nil {
d.logger.Error("事件处理器执行失败",
zap.String("event_type", eventType),
zap.Error(err))
// 继续执行其他处理器
}
}
return nil
}
// getEventType 获取事件类型
func (d *EventDispatcher) getEventType(event interface{}) string {
switch event.(type) {
case *events.MetricCreatedEvent:
return string(events.MetricCreatedEventType)
case *events.MetricUpdatedEvent:
return string(events.MetricUpdatedEventType)
case *events.MetricAggregatedEvent:
return string(events.MetricAggregatedEventType)
case *events.ReportCreatedEvent:
return string(events.ReportCreatedEventType)
case *events.ReportGenerationStartedEvent:
return string(events.ReportGenerationStartedEventType)
case *events.ReportCompletedEvent:
return string(events.ReportCompletedEventType)
case *events.ReportFailedEvent:
return string(events.ReportFailedEventType)
case *events.DashboardCreatedEvent:
return string(events.DashboardCreatedEventType)
case *events.DashboardUpdatedEvent:
return string(events.DashboardUpdatedEventType)
case *events.DashboardActivatedEvent:
return string(events.DashboardActivatedEventType)
case *events.DashboardDeactivatedEvent:
return string(events.DashboardDeactivatedEventType)
default:
return ""
}
}
// ================ 事件监听器 ================
// EventListener 事件监听器
type EventListener struct {
dispatcher *EventDispatcher
logger *zap.Logger
}
// NewEventListener 创建事件监听器
func NewEventListener(dispatcher *EventDispatcher, logger *zap.Logger) *EventListener {
return &EventListener{
dispatcher: dispatcher,
logger: logger,
}
}
// Listen 监听事件
func (l *EventListener) Listen(ctx context.Context, eventData []byte) error {
// 解析事件数据
var baseEvent events.BaseStatisticsEvent
err := json.Unmarshal(eventData, &baseEvent)
if err != nil {
return fmt.Errorf("解析事件数据失败: %w", err)
}
// 根据事件类型创建具体事件
event, err := l.createEventByType(baseEvent.Type, eventData)
if err != nil {
return fmt.Errorf("创建事件失败: %w", err)
}
// 分发事件
err = l.dispatcher.Dispatch(ctx, event)
if err != nil {
return fmt.Errorf("分发事件失败: %w", err)
}
return nil
}
// createEventByType 根据事件类型创建具体事件
func (l *EventListener) createEventByType(eventType string, eventData []byte) (interface{}, error) {
switch eventType {
case string(events.MetricCreatedEventType):
var event events.MetricCreatedEvent
err := json.Unmarshal(eventData, &event)
return &event, err
case string(events.MetricUpdatedEventType):
var event events.MetricUpdatedEvent
err := json.Unmarshal(eventData, &event)
return &event, err
case string(events.MetricAggregatedEventType):
var event events.MetricAggregatedEvent
err := json.Unmarshal(eventData, &event)
return &event, err
case string(events.ReportCreatedEventType):
var event events.ReportCreatedEvent
err := json.Unmarshal(eventData, &event)
return &event, err
case string(events.ReportGenerationStartedEventType):
var event events.ReportGenerationStartedEvent
err := json.Unmarshal(eventData, &event)
return &event, err
case string(events.ReportCompletedEventType):
var event events.ReportCompletedEvent
err := json.Unmarshal(eventData, &event)
return &event, err
case string(events.ReportFailedEventType):
var event events.ReportFailedEvent
err := json.Unmarshal(eventData, &event)
return &event, err
case string(events.DashboardCreatedEventType):
var event events.DashboardCreatedEvent
err := json.Unmarshal(eventData, &event)
return &event, err
case string(events.DashboardUpdatedEventType):
var event events.DashboardUpdatedEvent
err := json.Unmarshal(eventData, &event)
return &event, err
case string(events.DashboardActivatedEventType):
var event events.DashboardActivatedEvent
err := json.Unmarshal(eventData, &event)
return &event, err
case string(events.DashboardDeactivatedEventType):
var event events.DashboardDeactivatedEvent
err := json.Unmarshal(eventData, &event)
return &event, err
default:
return nil, fmt.Errorf("未知的事件类型: %s", eventType)
}
}

View File

@@ -0,0 +1,557 @@
package migrations
import (
"fmt"
"time"
"gorm.io/gorm"
"tyapi-server/internal/domains/statistics/entities"
)
// StatisticsMigration 统计模块数据迁移
type StatisticsMigration struct {
db *gorm.DB
}
// NewStatisticsMigration 创建统计模块数据迁移
func NewStatisticsMigration(db *gorm.DB) *StatisticsMigration {
return &StatisticsMigration{
db: db,
}
}
// Migrate 执行数据迁移
func (m *StatisticsMigration) Migrate() error {
fmt.Println("开始执行统计模块数据迁移...")
// 迁移统计指标表
err := m.migrateStatisticsMetrics()
if err != nil {
return fmt.Errorf("迁移统计指标表失败: %w", err)
}
// 迁移统计报告表
err = m.migrateStatisticsReports()
if err != nil {
return fmt.Errorf("迁移统计报告表失败: %w", err)
}
// 迁移统计仪表板表
err = m.migrateStatisticsDashboards()
if err != nil {
return fmt.Errorf("迁移统计仪表板表失败: %w", err)
}
// 创建索引
err = m.createIndexes()
if err != nil {
return fmt.Errorf("创建索引失败: %w", err)
}
// 插入初始数据
err = m.insertInitialData()
if err != nil {
return fmt.Errorf("插入初始数据失败: %w", err)
}
fmt.Println("统计模块数据迁移完成")
return nil
}
// migrateStatisticsMetrics 迁移统计指标表
func (m *StatisticsMigration) migrateStatisticsMetrics() error {
fmt.Println("迁移统计指标表...")
// 自动迁移表结构
err := m.db.AutoMigrate(&entities.StatisticsMetric{})
if err != nil {
return fmt.Errorf("自动迁移统计指标表失败: %w", err)
}
fmt.Println("统计指标表迁移完成")
return nil
}
// migrateStatisticsReports 迁移统计报告表
func (m *StatisticsMigration) migrateStatisticsReports() error {
fmt.Println("迁移统计报告表...")
// 自动迁移表结构
err := m.db.AutoMigrate(&entities.StatisticsReport{})
if err != nil {
return fmt.Errorf("自动迁移统计报告表失败: %w", err)
}
fmt.Println("统计报告表迁移完成")
return nil
}
// migrateStatisticsDashboards 迁移统计仪表板表
func (m *StatisticsMigration) migrateStatisticsDashboards() error {
fmt.Println("迁移统计仪表板表...")
// 自动迁移表结构
err := m.db.AutoMigrate(&entities.StatisticsDashboard{})
if err != nil {
return fmt.Errorf("自动迁移统计仪表板表失败: %w", err)
}
fmt.Println("统计仪表板表迁移完成")
return nil
}
// createIndexes 创建索引
func (m *StatisticsMigration) 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 *StatisticsMigration) createStatisticsMetricsIndexes() error {
// 复合索引metric_type + date
err := m.db.Exec(`
CREATE INDEX IF NOT EXISTS idx_statistics_metrics_type_date
ON statistics_metrics (metric_type, date)
`).Error
if err != nil {
return fmt.Errorf("创建复合索引失败: %w", err)
}
// 复合索引metric_type + dimension + date
err = m.db.Exec(`
CREATE INDEX IF NOT EXISTS idx_statistics_metrics_type_dimension_date
ON statistics_metrics (metric_type, dimension, date)
`).Error
if err != nil {
return fmt.Errorf("创建复合索引失败: %w", err)
}
// 复合索引metric_type + metric_name + date
err = m.db.Exec(`
CREATE INDEX IF NOT EXISTS idx_statistics_metrics_type_name_date
ON statistics_metrics (metric_type, metric_name, date)
`).Error
if err != nil {
return fmt.Errorf("创建复合索引失败: %w", err)
}
// 单列索引dimension
err = m.db.Exec(`
CREATE INDEX IF NOT EXISTS idx_statistics_metrics_dimension
ON statistics_metrics (dimension)
`).Error
if err != nil {
return fmt.Errorf("创建维度索引失败: %w", err)
}
return nil
}
// createStatisticsReportsIndexes 创建统计报告表索引
func (m *StatisticsMigration) createStatisticsReportsIndexes() error {
// 复合索引report_type + created_at
err := m.db.Exec(`
CREATE INDEX IF NOT EXISTS idx_statistics_reports_type_created
ON statistics_reports (report_type, created_at)
`).Error
if err != nil {
return fmt.Errorf("创建复合索引失败: %w", err)
}
// 复合索引user_role + created_at
err = m.db.Exec(`
CREATE INDEX IF NOT EXISTS idx_statistics_reports_role_created
ON statistics_reports (user_role, created_at)
`).Error
if err != nil {
return fmt.Errorf("创建复合索引失败: %w", err)
}
// 复合索引status + created_at
err = m.db.Exec(`
CREATE INDEX IF NOT EXISTS idx_statistics_reports_status_created
ON statistics_reports (status, created_at)
`).Error
if err != nil {
return fmt.Errorf("创建复合索引失败: %w", err)
}
// 单列索引generated_by
err = m.db.Exec(`
CREATE INDEX IF NOT EXISTS idx_statistics_reports_generated_by
ON statistics_reports (generated_by)
`).Error
if err != nil {
return fmt.Errorf("创建生成者索引失败: %w", err)
}
// 单列索引expires_at
err = m.db.Exec(`
CREATE INDEX IF NOT EXISTS idx_statistics_reports_expires_at
ON statistics_reports (expires_at)
`).Error
if err != nil {
return fmt.Errorf("创建过期时间索引失败: %w", err)
}
return nil
}
// createStatisticsDashboardsIndexes 创建统计仪表板表索引
func (m *StatisticsMigration) createStatisticsDashboardsIndexes() error {
// 复合索引user_role + is_active
err := m.db.Exec(`
CREATE INDEX IF NOT EXISTS idx_statistics_dashboards_role_active
ON statistics_dashboards (user_role, is_active)
`).Error
if err != nil {
return fmt.Errorf("创建复合索引失败: %w", err)
}
// 复合索引user_role + is_default
err = m.db.Exec(`
CREATE INDEX IF NOT EXISTS idx_statistics_dashboards_role_default
ON statistics_dashboards (user_role, is_default)
`).Error
if err != nil {
return fmt.Errorf("创建复合索引失败: %w", err)
}
// 单列索引created_by
err = m.db.Exec(`
CREATE INDEX IF NOT EXISTS idx_statistics_dashboards_created_by
ON statistics_dashboards (created_by)
`).Error
if err != nil {
return fmt.Errorf("创建创建者索引失败: %w", err)
}
// 单列索引access_level
err = m.db.Exec(`
CREATE INDEX IF NOT EXISTS idx_statistics_dashboards_access_level
ON statistics_dashboards (access_level)
`).Error
if err != nil {
return fmt.Errorf("创建访问级别索引失败: %w", err)
}
return nil
}
// insertInitialData 插入初始数据
func (m *StatisticsMigration) 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 *StatisticsMigration) insertDefaultDashboards() error {
// 管理员默认仪表板
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 *StatisticsMigration) insertInitialMetrics() error {
now := time.Now()
today := now.Truncate(24 * time.Hour)
// 插入初始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,
},
}
// 插入初始用户指标
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,
},
}
// 插入初始财务指标
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,
},
}
// 插入初始产品指标
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,
},
}
// 插入初始认证指标
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,
},
}
// 批量插入所有指标
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 *StatisticsMigration) 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
}

View File

@@ -0,0 +1,590 @@
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
}