mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-04 07:06:36 +08:00
refactor(admin): decouple system cleanup with event bus and enforce single owner principle
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"Wavelet/core/contracts"
|
||||
"Wavelet/pkg/logger"
|
||||
"Wavelet/plugins/domain/admin/model"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// SystemCleanupTask 系统定期垃圾清理任务标识
|
||||
SystemCleanupTask = "system:cleanup"
|
||||
// TaskTypeSystemCleanup 系统定期垃圾清理管理类型
|
||||
TaskTypeSystemCleanup = "system_cleanup"
|
||||
taskQueueDefault = "default"
|
||||
)
|
||||
|
||||
// SystemCleanupMeta describes the system-wide cleanup task metadata.
|
||||
var SystemCleanupMeta = contracts.TaskMetaDTO{
|
||||
Type: TaskTypeSystemCleanup,
|
||||
AsynqTask: SystemCleanupTask,
|
||||
Name: "系统垃圾清理",
|
||||
DisplayName: "系统垃圾清理",
|
||||
Description: "定期清理过期任务执行记录,并通过领域事件广播触发各业务域自治清理(临时文件、历史推送等)",
|
||||
Category: "maintenance",
|
||||
SupportsTime: false,
|
||||
MaxRetry: 3,
|
||||
Queue: taskQueueDefault,
|
||||
Retryable: true,
|
||||
}
|
||||
|
||||
// SystemCleanupHandler handles the system-wide garbage cleanup task.
|
||||
type SystemCleanupHandler struct{}
|
||||
|
||||
// Execute executes system cleanup: clears old task executions and emits EventTopicSystemCleanup.
|
||||
func (h *SystemCleanupHandler) Execute(ctx context.Context, _ []byte) (*contracts.TaskResultDTO, error) {
|
||||
db := GetDB(ctx)
|
||||
if db == nil {
|
||||
return nil, errors.New("database service not available")
|
||||
}
|
||||
|
||||
// 1. 清理自身域(admin 域)的过期任务执行记录(7 天前)
|
||||
var deletedExecutions int64
|
||||
sevenDaysAgo := time.Now().Add(-7 * 24 * time.Hour)
|
||||
res := db.Where("created_at < ?", sevenDaysAgo).Delete(&model.TaskExecution{})
|
||||
if err := res.Error; err != nil {
|
||||
logger.WarnF(ctx, "清理过期任务执行日志失败: %v", err)
|
||||
} else {
|
||||
deletedExecutions = res.RowsAffected
|
||||
logger.InfoF(ctx, "已清理 7 天前任务执行日志,共 %d 条", deletedExecutions)
|
||||
}
|
||||
|
||||
// 2. 广播 EventTopicSystemCleanup 领域事件,由各业务域插件(upload, msg_gateway, user 等)自治执行各自的清理逻辑
|
||||
nowStr := time.Now().Format(time.RFC3339)
|
||||
if err := EmitEvent(ctx, contracts.EventTopicSystemCleanup, contracts.SystemCleanupEvent{
|
||||
TriggeredAt: nowStr,
|
||||
}); err != nil {
|
||||
logger.WarnF(ctx, "广播系统清理领域事件失败: %v", err)
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("系统垃圾清理完成,已清理过期任务执行日志 %d 条,并已广播领域清理事件", deletedExecutions)
|
||||
logger.InfoF(ctx, "%s", msg)
|
||||
return &contracts.TaskResultDTO{Message: msg}, nil
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package service_test
|
||||
|
||||
import (
|
||||
"Wavelet/core/contracts"
|
||||
"Wavelet/plugins/domain/admin/model"
|
||||
"Wavelet/plugins/domain/admin/service"
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestSystemCleanupHandler_Execute(t *testing.T) {
|
||||
sqliteDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, sqliteDB.AutoMigrate(&model.TaskExecution{}))
|
||||
|
||||
service.SetDBService(&testDBService{db: sqliteDB})
|
||||
defer service.ResetServices()
|
||||
|
||||
now := time.Now()
|
||||
oldTime := now.Add(-10 * 24 * time.Hour)
|
||||
recentTime := now.Add(-1 * time.Hour)
|
||||
|
||||
// Seed old execution (should be cleaned)
|
||||
oldExec := model.TaskExecution{
|
||||
ID: 1,
|
||||
TaskID: "task-old",
|
||||
TaskType: "sample_task",
|
||||
Status: "success",
|
||||
CreatedAt: oldTime,
|
||||
}
|
||||
require.NoError(t, sqliteDB.Create(&oldExec).Error)
|
||||
|
||||
// Seed recent execution (should remain)
|
||||
recentExec := model.TaskExecution{
|
||||
ID: 2,
|
||||
TaskID: "task-recent",
|
||||
TaskType: "sample_task",
|
||||
Status: "success",
|
||||
CreatedAt: recentTime,
|
||||
}
|
||||
require.NoError(t, sqliteDB.Create(&recentExec).Error)
|
||||
|
||||
var eventFired atomic.Bool
|
||||
service.SetEventEmitter(func(ctx context.Context, topic string, payload any) error {
|
||||
if topic == contracts.EventTopicSystemCleanup {
|
||||
eventFired.Store(true)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
handler := &service.SystemCleanupHandler{}
|
||||
res, err := handler.Execute(context.Background(), nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res)
|
||||
assert.Contains(t, res.Message, "系统垃圾清理完成")
|
||||
|
||||
// Verify old execution is deleted and recent remains
|
||||
var count int64
|
||||
sqliteDB.Model(&model.TaskExecution{}).Count(&count)
|
||||
assert.Equal(t, int64(1), count)
|
||||
|
||||
var remaining model.TaskExecution
|
||||
sqliteDB.First(&remaining)
|
||||
assert.Equal(t, uint64(2), remaining.ID)
|
||||
|
||||
assert.True(t, eventFired.Load(), "EventTopicSystemCleanup must be emitted")
|
||||
}
|
||||
Reference in New Issue
Block a user