2026-04-17 14:09:02 +08:00
|
|
|
|
// Package handler provides HTTP handlers for various endpoints.
|
|
|
|
|
|
package handler
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
2026-04-28 17:41:28 +08:00
|
|
|
|
"modelRT/constants"
|
2026-04-17 14:09:02 +08:00
|
|
|
|
"modelRT/database"
|
|
|
|
|
|
"modelRT/logger"
|
feat: add dedicated message-exchange for task lifecycle notifications
- add constants/message.go with MessageTask* categories and message-exchange /
message-queue / dead-letter routing constants
- add mq/publish_message.go with PushMessageToRabbitMQ (confirm mode,
dead-letter queue) separate from the existing event-exchange publisher
- add mq/emit.go with TryEmitMessage for non-blocking, OTel-traced dispatch
- add mq/event/task_event_gen.go with NewTaskSubmitted/Running/Completed/
Failed/CancelledMessage constructors
- wire TryEmitMessage into task worker and create/cancel handlers so all 5
lifecycle transitions are published (previously task.* routed to
event-exchange with no matching binding, causing silent drops)
- harden Dockerfile: scratch final image, pinned alpine:3.21 certs stage,
apk upgrade in builder, add -trimpath -mod=readonly go build flags
- add full K8s manifests under deploy/k8s/ for Redis, RabbitMQ (mTLS),
ModelRT (Downward API, scratch image, readOnlyRootFilesystem), Jaeger,
Loki, Promtail, Grafana
- expand deploy.md with async_task SQL schema, TLS cert generation steps,
K8s deployment procedures, and SSH tunnel configuration
2026-05-13 16:58:36 +08:00
|
|
|
|
"modelRT/mq"
|
|
|
|
|
|
"modelRT/mq/event"
|
2026-04-17 14:09:02 +08:00
|
|
|
|
"modelRT/orm"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
|
"github.com/gofrs/uuid"
|
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// AsyncTaskCancelHandler handles cancellation of an async task
|
|
|
|
|
|
// @Summary 取消异步任务
|
|
|
|
|
|
// @Description 取消指定ID的异步任务(如果任务尚未开始执行)
|
|
|
|
|
|
// @Tags AsyncTask
|
|
|
|
|
|
// @Accept json
|
|
|
|
|
|
// @Produce json
|
|
|
|
|
|
// @Param task_id path string true "任务ID"
|
|
|
|
|
|
// @Success 200 {object} network.SuccessResponse "任务取消成功"
|
2026-04-28 17:41:28 +08:00
|
|
|
|
// @Failure 200 {object} network.FailureResponse "请求参数错误或任务无法取消"
|
2026-04-17 14:09:02 +08:00
|
|
|
|
// @Router /task/async/{task_id}/cancel [post]
|
|
|
|
|
|
func AsyncTaskCancelHandler(c *gin.Context) {
|
|
|
|
|
|
ctx := c.Request.Context()
|
|
|
|
|
|
|
|
|
|
|
|
taskIDStr := c.Param("task_id")
|
|
|
|
|
|
if taskIDStr == "" {
|
|
|
|
|
|
logger.Error(ctx, "task_id parameter is required")
|
2026-04-28 17:41:28 +08:00
|
|
|
|
renderRespFailure(c, constants.RespCodeInvalidParams, "task_id parameter is required", nil)
|
2026-04-17 14:09:02 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
taskID, err := uuid.FromString(taskIDStr)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
logger.Error(ctx, "invalid task ID format", "task_id", taskIDStr, "error", err)
|
2026-04-28 17:41:28 +08:00
|
|
|
|
renderRespFailure(c, constants.RespCodeInvalidParams, "invalid task ID format", nil)
|
2026-04-17 14:09:02 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pgClient := database.GetPostgresDBClient()
|
|
|
|
|
|
if pgClient == nil {
|
|
|
|
|
|
logger.Error(ctx, "database connection not found in context")
|
2026-04-28 17:41:28 +08:00
|
|
|
|
renderRespFailure(c, constants.RespCodeServerError, "database connection error", nil)
|
2026-04-17 14:09:02 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
asyncTask, err := database.GetAsyncTaskByID(ctx, pgClient, taskID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
if err == gorm.ErrRecordNotFound {
|
|
|
|
|
|
logger.Error(ctx, "async task not found", "task_id", taskID)
|
2026-04-28 17:41:28 +08:00
|
|
|
|
renderRespFailure(c, constants.RespCodeInvalidParams, "task not found", nil)
|
2026-04-17 14:09:02 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
logger.Error(ctx, "failed to query async task from database", "error", err)
|
2026-04-28 17:41:28 +08:00
|
|
|
|
renderRespFailure(c, constants.RespCodeServerError, "failed to query task", nil)
|
2026-04-17 14:09:02 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if asyncTask.Status != orm.AsyncTaskStatusSubmitted {
|
|
|
|
|
|
logger.Error(ctx, "task cannot be cancelled", "task_id", taskID, "status", asyncTask.Status)
|
2026-04-28 17:41:28 +08:00
|
|
|
|
renderRespFailure(c, constants.RespCodeInvalidParams, "task cannot be cancelled, already running or completed", nil)
|
2026-04-17 14:09:02 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
timestamp := time.Now().Unix()
|
|
|
|
|
|
err = database.FailAsyncTask(ctx, pgClient, taskID, timestamp)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
logger.Error(ctx, "failed to cancel async task", "task_id", taskID, "error", err)
|
2026-04-28 17:41:28 +08:00
|
|
|
|
renderRespFailure(c, constants.RespCodeServerError, "failed to cancel task", nil)
|
2026-04-17 14:09:02 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: add dedicated message-exchange for task lifecycle notifications
- add constants/message.go with MessageTask* categories and message-exchange /
message-queue / dead-letter routing constants
- add mq/publish_message.go with PushMessageToRabbitMQ (confirm mode,
dead-letter queue) separate from the existing event-exchange publisher
- add mq/emit.go with TryEmitMessage for non-blocking, OTel-traced dispatch
- add mq/event/task_event_gen.go with NewTaskSubmitted/Running/Completed/
Failed/CancelledMessage constructors
- wire TryEmitMessage into task worker and create/cancel handlers so all 5
lifecycle transitions are published (previously task.* routed to
event-exchange with no matching binding, causing silent drops)
- harden Dockerfile: scratch final image, pinned alpine:3.21 certs stage,
apk upgrade in builder, add -trimpath -mod=readonly go build flags
- add full K8s manifests under deploy/k8s/ for Redis, RabbitMQ (mTLS),
ModelRT (Downward API, scratch image, readOnlyRootFilesystem), Jaeger,
Loki, Promtail, Grafana
- expand deploy.md with async_task SQL schema, TLS cert generation steps,
K8s deployment procedures, and SSH tunnel configuration
2026-05-13 16:58:36 +08:00
|
|
|
|
if record, evtErr := event.NewTaskCancelledMessage(taskID.String(), string(asyncTask.TaskType)); evtErr == nil {
|
|
|
|
|
|
mq.TryEmitMessage(ctx, record)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 17:41:28 +08:00
|
|
|
|
err = database.UpdateAsyncTaskResultWithError(ctx, pgClient, taskID, 40009, "task cancelled by user", orm.JSONMap{
|
2026-04-17 14:09:02 +08:00
|
|
|
|
"cancelled_at": timestamp,
|
|
|
|
|
|
"cancelled_by": "user",
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
logger.Error(ctx, "failed to update task result with cancellation error", "task_id", taskID, "error", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 17:41:28 +08:00
|
|
|
|
renderRespSuccess(c, constants.RespCodeSuccess, "task cancelled successfully", nil)
|
2026-04-17 14:09:02 +08:00
|
|
|
|
}
|