mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 00:26:37 +08:00
优化定时任务
This commit is contained in:
@@ -24,6 +24,8 @@ Wavelet 使用 `github.com/pressly/goose/v3` 执行 SQL 迁移。迁移入口是
|
|||||||
```
|
```
|
||||||
|
|
||||||
- 不要把表结构、默认系统配置、默认模板、默认管理员初始化写回 Go 代码。
|
- 不要把表结构、默认系统配置、默认模板、默认管理员初始化写回 Go 代码。
|
||||||
|
- 编辑表结构(DDL)和插入表数据(DML/Seed)不要放在同一个 SQL 文件里,必须分成两个独立的 SQL 文件完成(例如,先通过一个文件修改表结构,再通过下一个递增版本号的文件插入/初始化数据)。
|
||||||
|
- 插入定时任务(schedules 表数据)时绝对不能指定 `id`,必须依靠数据库自增(Identity 或 AUTOINCREMENT)自动分配,防止与用户手动或后续插入的定时任务产生 ID 冲突。
|
||||||
- 不要添加物理外键;关系字段使用显式索引。
|
- 不要添加物理外键;关系字段使用显式索引。
|
||||||
- 数据库默认值应匹配 Go model 零值或业务兜底值。
|
- 数据库默认值应匹配 Go model 零值或业务兜底值。
|
||||||
- 系统配置仍然保存字符串值;布尔值写 `"true"` / `"false"`,数字写十进制字符串,复杂结构写合法 JSON 字符串。
|
- 系统配置仍然保存字符串值;布尔值写 `"true"` / `"false"`,数字写十进制字符串,复杂结构写合法 JSON 字符串。
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE schedules ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH 100);
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE schedules ALTER COLUMN id DROP IDENTITY IF EXISTS;
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
-- 1. Rename existing schedules table
|
||||||
|
ALTER TABLE schedules RENAME TO schedules_old;
|
||||||
|
|
||||||
|
-- 2. Create new schedules table with AUTOINCREMENT
|
||||||
|
CREATE TABLE schedules (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name VARCHAR(128) NOT NULL,
|
||||||
|
task_type VARCHAR(64) NOT NULL,
|
||||||
|
cron VARCHAR(64) NOT NULL,
|
||||||
|
payload TEXT,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 3. Copy existing data
|
||||||
|
INSERT INTO schedules (id, name, task_type, cron, payload, is_active, created_at, updated_at)
|
||||||
|
SELECT id, name, task_type, cron, payload, is_active, created_at, updated_at FROM schedules_old;
|
||||||
|
|
||||||
|
-- 4. Drop the old table
|
||||||
|
DROP TABLE schedules_old;
|
||||||
|
|
||||||
|
-- 5. Recreate index
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_schedules_is_active ON schedules (is_active);
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
-- Re-creating table with AUTOINCREMENT cannot be undone simply without recreating table again.
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Rain-kl/Wavelet/internal/db"
|
"github.com/Rain-kl/Wavelet/internal/db"
|
||||||
"github.com/Rain-kl/Wavelet/internal/db/idgen"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Schedule 定时任务配置表
|
// Schedule 定时任务配置表
|
||||||
@@ -30,7 +29,6 @@ func (Schedule) TableName() string {
|
|||||||
|
|
||||||
// CreateSchedule 创建定时任务
|
// CreateSchedule 创建定时任务
|
||||||
func CreateSchedule(ctx context.Context, schedule *Schedule) error {
|
func CreateSchedule(ctx context.Context, schedule *Schedule) error {
|
||||||
schedule.ID = idgen.NextUint64ID()
|
|
||||||
return db.DB(ctx).Create(schedule).Error
|
return db.DB(ctx).Create(schedule).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+41
-14
@@ -128,6 +128,8 @@ func DispatchTask(ctx context.Context, taskType string, payload []byte, triggere
|
|||||||
return "", fmt.Errorf(errTaskEnqueueFailed, err)
|
return "", fmt.Errorf(errTaskEnqueueFailed, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = model.AppendTaskExecutionLog(ctx, taskID, fmt.Sprintf("[系统] 任务已成功入队,等待调度执行 (队列: %s, 最大重试次数: %d)", meta.Queue, meta.MaxRetry))
|
||||||
|
|
||||||
return taskID, nil
|
return taskID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +189,8 @@ func RetryTask(ctx context.Context, id uint64) (string, error) {
|
|||||||
return "", fmt.Errorf(errRetryTaskEnqueueFailed, err)
|
return "", fmt.Errorf(errRetryTaskEnqueueFailed, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = model.AppendTaskExecutionLog(ctx, newTaskID, fmt.Sprintf("[系统] 手动触发重试,已重新创建任务并入队 (原任务ID: %s, 重试次数: %d/%d)", execution.TaskID, execution.RetryCount+1, execution.MaxRetry))
|
||||||
|
|
||||||
return newTaskID, nil
|
return newTaskID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,6 +233,13 @@ func ProcessTask(ctx context.Context, t *asynq.Task) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if execution != nil {
|
||||||
|
AppendLog(ctx, "[系统] 开始执行异步任务 [名称: %s, 类型: %s],重试次数: %d/%d",
|
||||||
|
execution.TaskName, t.Type(), execution.RetryCount, execution.MaxRetry)
|
||||||
|
} else {
|
||||||
|
AppendLog(ctx, "[系统] 开始执行异步任务 [类型: %s]", t.Type())
|
||||||
|
}
|
||||||
|
|
||||||
// 开始计时
|
// 开始计时
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
@@ -292,21 +303,9 @@ func completeTaskExecution(ctx context.Context, execution *model.TaskExecution,
|
|||||||
execution.FinishedAt = &finishTime
|
execution.FinishedAt = &finishTime
|
||||||
|
|
||||||
if execErr != nil {
|
if execErr != nil {
|
||||||
execution.Status = model.TaskExecutionStatusFailed
|
handleFailedTask(ctx, execution, t, duration, execErr, span)
|
||||||
execution.ErrorMessage = execErr.Error()
|
|
||||||
logger.ErrorF(ctx, "[TaskExecutor] 任务处理失败 Type: %s TaskID: %s Duration: %d ms Error: %v", t.Type(), execution.TaskID, duration.Milliseconds(), execErr)
|
|
||||||
span.SetStatus(codes.Error, execErr.Error())
|
|
||||||
span.RecordError(execErr)
|
|
||||||
} else {
|
} else {
|
||||||
execution.Status = model.TaskExecutionStatusSucceeded
|
handleSuccessfulTask(ctx, execution, t, duration, result)
|
||||||
execution.ErrorMessage = "" // 清除历史重试失败遗留的错误信息
|
|
||||||
if result != nil {
|
|
||||||
execution.Result = result.Message
|
|
||||||
if result.Detail != "" {
|
|
||||||
execution.Result = fmt.Sprintf("%s\n%s", result.Message, result.Detail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.InfoF(ctx, "[TaskExecutor] 任务处理完成 Type: %s TaskID: %s Duration: %d ms", t.Type(), execution.TaskID, duration.Milliseconds())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := model.UpdateTaskExecution(ctx, execution); err != nil {
|
if err := model.UpdateTaskExecution(ctx, execution); err != nil {
|
||||||
@@ -314,6 +313,34 @@ func completeTaskExecution(ctx context.Context, execution *model.TaskExecution,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleFailedTask(ctx context.Context, execution *model.TaskExecution, t *asynq.Task, duration time.Duration, execErr error, span trace.Span) {
|
||||||
|
execution.Status = model.TaskExecutionStatusFailed
|
||||||
|
execution.ErrorMessage = execErr.Error()
|
||||||
|
logger.ErrorF(ctx, "[TaskExecutor] 任务处理失败 Type: %s TaskID: %s Duration: %d ms Error: %v", t.Type(), execution.TaskID, duration.Milliseconds(), execErr)
|
||||||
|
span.SetStatus(codes.Error, execErr.Error())
|
||||||
|
span.RecordError(execErr)
|
||||||
|
|
||||||
|
AppendLog(ctx, "[系统] 任务执行失败,耗时: %d ms,错误原因: %v", duration.Milliseconds(), execErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSuccessfulTask(ctx context.Context, execution *model.TaskExecution, t *asynq.Task, duration time.Duration, result *TaskResult) {
|
||||||
|
execution.Status = model.TaskExecutionStatusSucceeded
|
||||||
|
execution.ErrorMessage = "" // 清除历史重试失败遗留的错误信息
|
||||||
|
if result != nil {
|
||||||
|
execution.Result = result.Message
|
||||||
|
if result.Detail != "" {
|
||||||
|
execution.Result = fmt.Sprintf("%s\n%s", result.Message, result.Detail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.InfoF(ctx, "[TaskExecutor] 任务处理完成 Type: %s TaskID: %s Duration: %d ms", t.Type(), execution.TaskID, duration.Milliseconds())
|
||||||
|
|
||||||
|
resultMsg := "成功"
|
||||||
|
if result != nil {
|
||||||
|
resultMsg = result.Message
|
||||||
|
}
|
||||||
|
AppendLog(ctx, "[系统] 任务执行成功,耗时: %d ms,执行结果: %s", duration.Milliseconds(), resultMsg)
|
||||||
|
}
|
||||||
|
|
||||||
// generateTaskID 生成任务 ID
|
// generateTaskID 生成任务 ID
|
||||||
func generateTaskID(taskType string, triggeredBy string) string {
|
func generateTaskID(taskType string, triggeredBy string) string {
|
||||||
uniqueID := idgen.NextUint64ID()
|
uniqueID := idgen.NextUint64ID()
|
||||||
|
|||||||
Reference in New Issue
Block a user