new
This commit is contained in:
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user