From f4e1db127969183b3859f545e96ea9f4b91f49f5 Mon Sep 17 00:00:00 2001 From: chengcheng Date: Thu, 25 Jun 2026 17:01:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(task):=20=E6=9A=B4=E9=9C=B2=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E5=8F=96=E6=B6=88=E6=8E=A7=E5=88=B6=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/internal/httpapi/handlers.go | 4 ++++ apps/api/internal/runner/task_cancel.go | 19 ++++++++++++++++ apps/api/internal/runner/task_cancel_test.go | 23 ++++++++++++++++++++ apps/api/internal/store/postgres.go | 3 +++ 4 files changed, 49 insertions(+) diff --git a/apps/api/internal/httpapi/handlers.go b/apps/api/internal/httpapi/handlers.go index 373ef97..6d6adda 100644 --- a/apps/api/internal/httpapi/handlers.go +++ b/apps/api/internal/httpapi/handlers.go @@ -1598,6 +1598,10 @@ func boolValue(body map[string]any, key string) bool { func (s *Server) getTask(w http.ResponseWriter, r *http.Request) { task, err := s.store.GetTask(r.Context(), r.PathValue("taskID")) if err == nil { + cancelState := runner.DescribeTaskCancellation(task) + task.Cancellable = &cancelState.Cancellable + task.Submitted = &cancelState.Submitted + task.Message = cancelState.Message writeJSON(w, http.StatusOK, task) return } diff --git a/apps/api/internal/runner/task_cancel.go b/apps/api/internal/runner/task_cancel.go index e35400d..600f778 100644 --- a/apps/api/internal/runner/task_cancel.go +++ b/apps/api/internal/runner/task_cancel.go @@ -20,6 +20,25 @@ type TaskCancelResult struct { Message string `json:"message"` } +func DescribeTaskCancellation(task store.GatewayTask) TaskCancelResult { + if taskCancelTerminalStatus(task.Status) { + return taskCancelUnavailable(task, "任务已结束,无法取消") + } + if strings.TrimSpace(task.RemoteTaskID) != "" { + return taskCancelUnavailable(task, "任务已提交上游,当前不可取消,请继续查询结果") + } + if strings.TrimSpace(task.Status) != "queued" { + return taskCancelUnavailable(task, "任务已开始执行,当前阶段不可取消,请继续查询结果") + } + return TaskCancelResult{ + TaskID: task.ID, + Cancelled: false, + Cancellable: true, + Submitted: false, + Message: "任务仍在本地队列中,可取消", + } +} + func (s *Service) CancelTask(ctx context.Context, taskID string, user *auth.User) (TaskCancelResult, error) { task, err := s.store.GetTask(ctx, taskID) if err != nil { diff --git a/apps/api/internal/runner/task_cancel_test.go b/apps/api/internal/runner/task_cancel_test.go index 15b90aa..680e719 100644 --- a/apps/api/internal/runner/task_cancel_test.go +++ b/apps/api/internal/runner/task_cancel_test.go @@ -76,3 +76,26 @@ func TestTaskCancelUnavailableReportsSubmittedFromRemoteTaskID(t *testing.T) { t.Fatalf("remote task id should report submitted: %+v", result) } } + +func TestDescribeTaskCancellationReportsQueuedTaskAsCancellable(t *testing.T) { + result := DescribeTaskCancellation(store.GatewayTask{ID: "task-queued", Status: "queued"}) + + if result.TaskID != "task-queued" { + t.Fatalf("unexpected task id: %+v", result) + } + if result.Cancelled || !result.Cancellable || result.Submitted { + t.Fatalf("queued local task should be cancellable and not submitted: %+v", result) + } +} + +func TestDescribeTaskCancellationReportsRemoteTaskAsSubmitted(t *testing.T) { + result := DescribeTaskCancellation(store.GatewayTask{ + ID: "task-remote", + Status: "running", + RemoteTaskID: "remote-1", + }) + + if result.Cancelled || result.Cancellable || !result.Submitted { + t.Fatalf("remote task should be submitted and non-cancellable: %+v", result) + } +} diff --git a/apps/api/internal/store/postgres.go b/apps/api/internal/store/postgres.go index 82b11b1..e06342e 100644 --- a/apps/api/internal/store/postgres.go +++ b/apps/api/internal/store/postgres.go @@ -446,6 +446,9 @@ type GatewayTask struct { AsyncMode bool `json:"asyncMode"` RiverJobID int64 `json:"riverJobId,omitempty"` Status string `json:"status"` + Cancellable *bool `json:"cancellable,omitempty"` + Submitted *bool `json:"submitted,omitempty"` + Message string `json:"message,omitempty"` AttemptCount int `json:"attemptCount"` RemoteTaskID string `json:"remoteTaskId,omitempty"` RemoteTaskPayload map[string]any `json:"remoteTaskPayload,omitempty"`