new
This commit is contained in:
584
internal/infrastructure/statistics/cache/redis_statistics_cache.go
vendored
Normal file
584
internal/infrastructure/statistics/cache/redis_statistics_cache.go
vendored
Normal 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
|
||||
}
|
||||
404
internal/infrastructure/statistics/cron/statistics_cron_job.go
Normal file
404
internal/infrastructure/statistics/cron/statistics_cron_job.go
Normal 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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user