Files
tyapi-server/internal/infrastructure/statistics/cron/statistics_cron_job.go
2025-09-12 01:15:09 +08:00

405 lines
11 KiB
Go

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
}