feat(framework): 回灌 OpenFlare 分层、安全与运行时改进

将平台域持久化收敛为 repository 唯一入口,model 去掉 IO。
邮件头写入前清除 CR/LF,防止 header 注入。
httppool 支持可配置 Transport;batchwriter 增加 MinBatchSize/Stats,flush 失败交回批次;任务 PermanentError 作为 SkipRetry 终态。
设置与推送页的确认改为 AlertDialog;axios 去尾斜杠并按 Gin 数组序列化查询参数。
升级共享 Go 依赖(Gin、Asynq、OTel、GORM、Redis 等)。
This commit is contained in:
ryan
2026-08-16 11:07:20 +08:00
parent b9b42e3174
commit 6a53619dd2
58 changed files with 2201 additions and 1175 deletions
+20 -13
View File
@@ -6,11 +6,13 @@ package task
import (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/repository"
"github.com/Rain-kl/Wavelet/internal/testhelper"
"github.com/hibiken/asynq"
"github.com/stretchr/testify/assert"
@@ -120,7 +122,7 @@ func TestAppendLogWithTaskID(t *testing.T) {
Status: model.TaskExecutionStatusRunning,
TriggeredBy: "manual",
}
err := model.CreateTaskExecution(ctx, execution)
err := repository.CreateTaskExecution(ctx, execution)
require.NoError(t, err)
// 注入 taskID 并追加日志
@@ -129,7 +131,7 @@ func TestAppendLogWithTaskID(t *testing.T) {
AppendLog(ctx, "处理了 %d 条数据", 50)
// 验证日志
found, err := model.GetTaskExecutionByTaskID(ctx, "log_test_001")
found, err := repository.GetTaskExecutionByTaskID(ctx, "log_test_001")
require.NoError(t, err)
assert.Contains(t, found.Log, "第一条日志")
assert.Contains(t, found.Log, "处理了 50 条数据")
@@ -188,7 +190,7 @@ func TestProcessTaskSuccess(t *testing.T) {
MaxRetry: 3,
TriggeredBy: "manual",
}
err := model.CreateTaskExecution(ctx, execution)
err := repository.CreateTaskExecution(ctx, execution)
require.NoError(t, err)
// 通过 asynq 的 Task 不能直接设置 taskID,ProcessTask 通过 t.ResultWriter().TaskID() 获取
@@ -206,7 +208,7 @@ func TestProcessTaskSuccess(t *testing.T) {
assert.Equal(t, "处理完成,共 100 条", result.Message)
// 验证日志被追加
found, err := model.GetTaskExecutionByTaskID(ctx, "process_success_001")
found, err := repository.GetTaskExecutionByTaskID(ctx, "process_success_001")
require.NoError(t, err)
assert.Contains(t, found.Log, "执行成功,处理了 100 条数据")
}
@@ -229,7 +231,7 @@ func TestProcessTaskFailure(t *testing.T) {
MaxRetry: 3,
TriggeredBy: "manual",
}
err := model.CreateTaskExecution(ctx, execution)
err := repository.CreateTaskExecution(ctx, execution)
require.NoError(t, err)
// 直接调用 handler
@@ -242,7 +244,7 @@ func TestProcessTaskFailure(t *testing.T) {
assert.Contains(t, err.Error(), "模拟执行失败")
// 验证日志
found, err := model.GetTaskExecutionByTaskID(ctx, "process_fail_001")
found, err := repository.GetTaskExecutionByTaskID(ctx, "process_fail_001")
require.NoError(t, err)
assert.Contains(t, found.Log, "开始执行任务")
}
@@ -259,7 +261,7 @@ func TestCompleteTaskExecutionFlushesLog(t *testing.T) {
Status: model.TaskExecutionStatusRunning,
TriggeredBy: "manual",
}
err := model.CreateTaskExecution(ctx, execution)
err := repository.CreateTaskExecution(ctx, execution)
require.NoError(t, err)
ctx = withTaskID(ctx, execution.TaskID)
@@ -277,13 +279,18 @@ func TestCompleteTaskExecutionFlushesLog(t *testing.T) {
trace.SpanFromContext(ctx),
)
found, err := model.GetTaskExecutionByTaskID(ctx, execution.TaskID)
found, err := repository.GetTaskExecutionByTaskID(ctx, execution.TaskID)
require.NoError(t, err)
assert.Equal(t, model.TaskExecutionStatusSucceeded, found.Status)
assert.Contains(t, found.Log, "任务执行中的日志")
assert.Contains(t, found.Log, "任务执行成功")
}
func TestPermanentErrorIsTerminalForLogFlush(t *testing.T) {
assert.True(t, isTerminalTaskExecutionError(PermanentError("配置无效")))
assert.False(t, isTerminalTaskExecutionError(errors.New("temporary failure")))
}
func TestRetryTask(t *testing.T) {
cleanup := setupTest(t)
defer cleanup()
@@ -305,7 +312,7 @@ func TestRetryTask(t *testing.T) {
Duration: 100,
TriggeredBy: "manual",
}
err := model.CreateTaskExecution(ctx, execution)
err := repository.CreateTaskExecution(ctx, execution)
require.NoError(t, err)
// 重试
@@ -315,7 +322,7 @@ func TestRetryTask(t *testing.T) {
assert.Contains(t, newTaskID, "retry_1_")
// 验证新记录
newExecution, err := model.GetTaskExecutionByTaskID(ctx, newTaskID)
newExecution, err := repository.GetTaskExecutionByTaskID(ctx, newTaskID)
require.NoError(t, err)
assert.Equal(t, model.TaskExecutionStatusPending, newExecution.Status)
assert.Equal(t, 1, newExecution.RetryCount)
@@ -324,7 +331,7 @@ func TestRetryTask(t *testing.T) {
assert.True(t, newExecution.Retryable)
// 原记录不变
original, err := model.GetTaskExecutionByID(ctx, execution.ID)
original, err := repository.GetTaskExecutionByID(ctx, execution.ID)
require.NoError(t, err)
assert.Equal(t, model.TaskExecutionStatusFailed, original.Status)
assert.Equal(t, 0, original.RetryCount)
@@ -345,7 +352,7 @@ func TestRetryTaskNotFailed(t *testing.T) {
MaxRetry: 3,
TriggeredBy: "manual",
}
err := model.CreateTaskExecution(ctx, execution)
err := repository.CreateTaskExecution(ctx, execution)
require.NoError(t, err)
// 尝试重试成功的任务
@@ -368,7 +375,7 @@ func TestRetryTaskNotRetryable(t *testing.T) {
MaxRetry: 0,
TriggeredBy: "manual",
}
err := model.CreateTaskExecution(ctx, execution)
err := repository.CreateTaskExecution(ctx, execution)
require.NoError(t, err)
_, err = RetryTask(ctx, execution.ID)