mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 22:06:38 +08:00
218 lines
6.0 KiB
Go
218 lines
6.0 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package service
|
|
|
|
import (
|
|
"Wavelet/core/contracts"
|
|
"Wavelet/pkg/logger"
|
|
"Wavelet/plugins/domain/admin/errs"
|
|
"Wavelet/plugins/domain/admin/model"
|
|
"Wavelet/plugins/domain/admin/repository"
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/robfig/cron/v3"
|
|
)
|
|
|
|
// ListTaskTypes returns every dispatchable task type declared in the task registry.
|
|
func ListTaskTypes() []contracts.TaskMetaDTO {
|
|
taskSvc := GetTaskService()
|
|
if taskSvc == nil {
|
|
return []contracts.TaskMetaDTO{}
|
|
}
|
|
return taskSvc.ListTasks()
|
|
}
|
|
|
|
// DispatchTask validates and enqueues a manual task run, returning the new task id.
|
|
func DispatchTask(ctx context.Context, req model.DispatchTaskRequest) (string, error) {
|
|
taskSvc, err := requireTaskService()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if _, ok := taskSvc.GetTaskMeta(req.TaskType); !ok {
|
|
return "", errs.ErrInvalidTaskType
|
|
}
|
|
|
|
validated, err := validateTaskPayload(taskSvc, req.TaskType, req.Payload)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
taskID, err := taskSvc.Dispatch(ctx, req.TaskType, validated, "manual")
|
|
if err != nil {
|
|
return "", fmt.Errorf("%s: %w", errs.TaskDispatchFailed, err)
|
|
}
|
|
return taskID, nil
|
|
}
|
|
|
|
// validateTaskPayload normalises an optional raw payload through the task registry.
|
|
func validateTaskPayload(taskSvc contracts.TaskService, name, payload string) ([]byte, error) {
|
|
var payloadBytes []byte
|
|
if strings.TrimSpace(payload) != "" {
|
|
payloadBytes = []byte(payload)
|
|
}
|
|
|
|
validated, err := taskSvc.ValidatePayload(name, payloadBytes)
|
|
if err != nil {
|
|
return nil, errs.NewInvalidInputError(err.Error())
|
|
}
|
|
return validated, nil
|
|
}
|
|
|
|
// ListTaskExecutions pages task execution records for the console.
|
|
func ListTaskExecutions(
|
|
ctx context.Context,
|
|
req model.ListTaskExecutionsRequest,
|
|
) ([]model.TaskExecution, int64, error) {
|
|
if req.TaskType != "" {
|
|
if taskSvc := GetTaskService(); taskSvc != nil {
|
|
if meta, ok := taskSvc.GetTaskMeta(req.TaskType); ok {
|
|
req.TaskType = meta.Name
|
|
}
|
|
}
|
|
}
|
|
|
|
executions, total, err := repository.ListTaskExecutionRecords(ctx, req)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
return executions, total, nil
|
|
}
|
|
|
|
// TaskExecution loads a single execution record including its buffered log.
|
|
func TaskExecution(ctx context.Context, id uint64) (*model.TaskExecution, error) {
|
|
return repository.GetTaskExecutionByID(ctx, id)
|
|
}
|
|
|
|
// RetryTask re-dispatches a failed execution as a new task run.
|
|
func RetryTask(ctx context.Context, id uint64) (string, error) {
|
|
taskSvc, err := requireTaskService()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
newTaskID, err := taskSvc.Retry(ctx, id)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return newTaskID, nil
|
|
}
|
|
|
|
// IsRetryConflictError reports whether the task registry rejected the retry request
|
|
// because of the record state rather than an infrastructure failure.
|
|
func IsRetryConflictError(err error) bool {
|
|
msg := err.Error()
|
|
return strings.Contains(msg, errs.RemoteTaskNotFailedMsg) ||
|
|
strings.Contains(msg, errs.RemoteTaskNotRetryableMsg) ||
|
|
strings.Contains(msg, errs.RemoteTaskMaxRetryMsg)
|
|
}
|
|
|
|
// IsRetryMissingError reports whether the referenced execution record is absent.
|
|
func IsRetryMissingError(err error) bool {
|
|
return strings.Contains(err.Error(), errs.RemoteTaskNotFoundMsg)
|
|
}
|
|
|
|
// ListSchedules returns every dynamic schedule definition.
|
|
func ListSchedules(ctx context.Context) ([]model.Schedule, error) {
|
|
return repository.ListSchedulesRecord(ctx)
|
|
}
|
|
|
|
// CreateSchedule validates a schedule definition, persists it and reloads the scheduler.
|
|
func CreateSchedule(ctx context.Context, req model.CreateScheduleRequest) (*model.Schedule, error) {
|
|
if _, err := cron.ParseStandard(req.Cron); err != nil {
|
|
return nil, errs.ErrInvalidCronExpression
|
|
}
|
|
|
|
taskSvc, err := requireTaskService()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
meta, ok := taskSvc.GetTaskMeta(req.TaskType)
|
|
if !ok {
|
|
return nil, errs.ErrInvalidTaskType
|
|
}
|
|
|
|
validated, err := validateTaskPayload(taskSvc, meta.Name, req.Payload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
schedule := &model.Schedule{
|
|
Name: req.Name,
|
|
TaskType: req.TaskType,
|
|
Cron: req.Cron,
|
|
Payload: string(validated),
|
|
IsActive: *req.IsActive,
|
|
}
|
|
|
|
if err := repository.CreateScheduleRecord(ctx, schedule); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", errs.ScheduleSaveFailed, err)
|
|
}
|
|
|
|
reloadScheduler(ctx, taskSvc)
|
|
return schedule, nil
|
|
}
|
|
|
|
// UpdateSchedule rewrites an existing schedule definition and reloads the scheduler.
|
|
func UpdateSchedule(ctx context.Context, id uint64, req model.UpdateScheduleRequest) (*model.Schedule, error) {
|
|
schedule, err := repository.GetScheduleByID(ctx, id)
|
|
if err != nil {
|
|
return nil, errs.ErrScheduleNotFound
|
|
}
|
|
|
|
if _, err := cron.ParseStandard(req.Cron); err != nil {
|
|
return nil, errs.ErrInvalidCronExpression
|
|
}
|
|
|
|
taskSvc, err := requireTaskService()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
meta, ok := taskSvc.GetTaskMeta(req.TaskType)
|
|
if !ok {
|
|
return nil, errs.ErrInvalidTaskType
|
|
}
|
|
|
|
validated, err := validateTaskPayload(taskSvc, meta.Name, req.Payload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
schedule.Name = req.Name
|
|
schedule.TaskType = req.TaskType
|
|
schedule.Cron = req.Cron
|
|
schedule.Payload = string(validated)
|
|
schedule.IsActive = *req.IsActive
|
|
|
|
if err := repository.UpdateScheduleRecord(ctx, schedule); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", errs.ScheduleSaveFailed, err)
|
|
}
|
|
|
|
reloadScheduler(ctx, taskSvc)
|
|
return schedule, nil
|
|
}
|
|
|
|
// DeleteSchedule removes a schedule definition and reloads the scheduler.
|
|
func DeleteSchedule(ctx context.Context, id uint64) error {
|
|
if err := repository.DeleteScheduleRecord(ctx, id); err != nil {
|
|
return fmt.Errorf("%s: %w", errs.ScheduleDeleteFailed, err)
|
|
}
|
|
|
|
if taskSvc := GetTaskService(); taskSvc != nil {
|
|
reloadScheduler(ctx, taskSvc)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// reloadScheduler triggers the hot reload, degrading gracefully when the scheduler rejects it.
|
|
func reloadScheduler(ctx context.Context, taskSvc contracts.TaskService) {
|
|
if err := taskSvc.ReloadScheduler(); err != nil {
|
|
logger.ErrorF(ctx, "[TaskAdmin] 重载调度器失败: %v", err)
|
|
}
|
|
}
|