feat(workflow): 恢复例程 + 心跳 + 启动/周期扫描
This commit is contained in:
@@ -156,9 +156,26 @@ func (d *execWorkflowDao) UpdateRetry(ctx context.Context, id int64, retryable i
|
||||
return err
|
||||
}
|
||||
|
||||
// ListRecoverable 返回可恢复执行:僵尸运行中(status=1 且心跳陈旧)或可重试失败(status=3 且 retryable=1 且未耗尽)
|
||||
// GetByIdNoTenant 跨租户按 id 读取执行记录(不追加 tenant_id 过滤)。
|
||||
// 供恢复例程抢锁后、尚未知晓租户时重读 exec 使用;用户路径请继续用租户隔离的 GetById。
|
||||
func (d *execWorkflowDao) GetByIdNoTenant(ctx context.Context, id int64) (res *entity.ExecWorkflow, err error) {
|
||||
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameExecWorkflow).
|
||||
NoTenantId(ctx).
|
||||
Where(entity.ExecWorkflowCol.Id, id).
|
||||
One()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = r.Struct(&res)
|
||||
return
|
||||
}
|
||||
|
||||
// ListRecoverable 返回可恢复执行:僵尸运行中(status=1 且心跳陈旧)或可重试失败(status=3 且 retryable=1 且未耗尽)。
|
||||
// 调用方是跨租户的恢复扫描(无 HTTP 用户),显式 NoTenantId 走系统级扫描,避免僵尸执行因租户过滤漏检。
|
||||
// ctx 必须携带 OTel span(NoTenantId 依赖 traceID 作为 gcache 标记键),恢复扫描由 scanAndRecover 保证。
|
||||
func (d *execWorkflowDao) ListRecoverable(ctx context.Context, now int64, staleBefore int64, maxRetry int) (res []*entity.ExecWorkflow, err error) {
|
||||
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameExecWorkflow).
|
||||
NoTenantId(ctx).
|
||||
Where(fmt.Sprintf("(%s = ? AND %s < ?) OR (%s = ? AND %s = 1 AND %s < ?)",
|
||||
entity.ExecWorkflowCol.Status, entity.ExecWorkflowCol.LastHeartbeat,
|
||||
entity.ExecWorkflowCol.Status, entity.ExecWorkflowCol.Retryable, entity.ExecWorkflowCol.RetryCount),
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
|
||||
"github.com/gogf/gf/v2/net/gtrace"
|
||||
)
|
||||
|
||||
// TestExecWorkflowRetryColsRoundTrip 验证三新列读写 + ListRecoverable 三类判定(需 AI_AGENT_TEST_DB=1 且本地 PG 可用)
|
||||
@@ -19,8 +20,11 @@ func TestExecWorkflowRetryColsRoundTrip(t *testing.T) {
|
||||
if !isTestDBEnabled(t) {
|
||||
t.Skip("skip: AI_AGENT_TEST_DB not set")
|
||||
}
|
||||
// gfdb 的 Hook 依赖 ctx 中注入用户信息(租户/创建人),否则 Insert 报 "token 数据为空"
|
||||
ctx := context.WithValue(context.Background(), "user", &beans.User{
|
||||
// gfdb 的 Hook 依赖 ctx 中注入用户信息(租户/创建人),否则 Insert 报 "token 数据为空"。
|
||||
// 先造 span:ListRecoverable 链 NoTenantId 依赖 traceID 作 gcache 标记键,无 span 时 getTraceID 返回空 → NoTenantId 返回 nil 会 panic
|
||||
ctx, span := gtrace.NewSpan(context.Background(), "test.recover")
|
||||
defer span.End()
|
||||
ctx = context.WithValue(ctx, "user", &beans.User{
|
||||
UserName: "test-retry", TenantId: 1,
|
||||
})
|
||||
id, err := ExecWorkflowDao.Insert(ctx, &sessionDto.CreateWorkflowReq{
|
||||
|
||||
Reference in New Issue
Block a user