feat(t215): add admin order authorization

This commit is contained in:
QiuSW
2026-07-28 12:50:42 +08:00
parent 16c2ea410a
commit 827afc7257
24 changed files with 2688 additions and 113 deletions
+121 -25
View File
@@ -66,6 +66,11 @@ func (h *Handler) RegisterProtected(routes gin.IRoutes) {
routes.POST("/tasks", SecurityHeaders(), h.CreateTask)
routes.GET("/tasks/:id", SecurityHeaders(), h.TaskDetail)
routes.POST("/tasks/:id/cancel", SecurityHeaders(), h.CancelTask)
routes.POST(
"/tasks/:id/order-authorizations",
SecurityHeaders(),
h.AuthorizeOrder,
)
}
func SecurityHeaders() gin.HandlerFunc {
@@ -264,20 +269,85 @@ func (h *Handler) TaskDetail(ctx *gin.Context) {
h.renderError(ctx, http.StatusInternalServerError, "页面暂时无法打开", "请稍后重试。")
return
}
authorizationKey, keyErr := newToken()
if keyErr != nil {
h.renderError(ctx, http.StatusInternalServerError, "页面暂时无法打开", "请稍后重试。")
return
}
page := taskDetailPage{
Page: pageView{
Title: "任务详情",
TasksCurrent: true,
CSRFToken: token,
},
Task: taskDetailViewFrom(task),
CSRFToken: token,
CancelKey: cancelKey,
Notice: detailNotice(ctx.Query("notice")),
Task: taskDetailViewFrom(task),
CSRFToken: token,
CancelKey: cancelKey,
AuthorizationKey: authorizationKey,
Notice: detailNotice(ctx.Query("notice")),
}
h.render(ctx, http.StatusOK, "task-detail", page)
}
func (h *Handler) AuthorizeOrder(ctx *gin.Context) {
if !validCSRF(ctx) {
h.renderError(
ctx,
http.StatusForbidden,
"请求已失效",
"请返回任务详情后重新操作。",
)
return
}
taskID := strings.TrimSpace(ctx.Param("id"))
authorizationKey := strings.TrimSpace(
ctx.PostForm("authorization_key"),
)
expectedVersion, versionErr := strconv.ParseInt(
strings.TrimSpace(ctx.PostForm("expected_task_version")),
10,
64,
)
if !validToken(authorizationKey) || versionErr != nil ||
expectedVersion < 1 {
h.renderError(
ctx,
http.StatusForbidden,
"请求已失效",
"请返回任务详情后重新操作。",
)
return
}
_, err := h.service.AuthorizeOrder(
ctx.Request.Context(),
AuthorizeOrderInput{
TaskID: taskID,
IdempotencyKey: authorizationKey,
ExpectedTaskVersion: expectedVersion,
CandidateKey: strings.TrimSpace(ctx.PostForm("candidate_key")),
SelectedReasonCode: strings.TrimSpace(ctx.PostForm("selected_reason_code")),
RejectedReasonCode: strings.TrimSpace(ctx.PostForm("rejected_reason_code")),
Note: strings.TrimSpace(ctx.PostForm("authorization_note")),
SupersedesAuthorizationID: strings.TrimSpace(ctx.PostForm("supersedes_authorization_id")),
},
)
if err != nil {
if errors.Is(err, ErrConflict) || errors.Is(err, ErrValidation) {
ctx.Redirect(
http.StatusSeeOther,
"/tasks/"+pathEscape(taskID)+"?notice=authorization-conflict",
)
return
}
h.renderServiceError(ctx, err, "授权失败,请稍后重试。")
return
}
ctx.Redirect(
http.StatusSeeOther,
"/tasks/"+pathEscape(taskID)+"?notice=authorization-created",
)
}
func (h *Handler) CancelTask(ctx *gin.Context) {
if !validCSRF(ctx) {
h.renderError(
@@ -652,6 +722,10 @@ func detailNotice(value string) string {
return "已请求设备安全停止;设备确认前任务仍保持当前执行状态。"
case "cancel-conflict":
return "任务状态已变化,当前不能取消。"
case "authorization-created":
return "候选已确认,待投递下单授权已创建。"
case "authorization-conflict":
return "候选或任务状态已变化,请检查最新证据后重新授权。"
default:
return ""
}
@@ -742,29 +816,35 @@ type newTaskPageView struct {
}
type taskDetailView struct {
ID string
Title string
SKU string
Description string
Quantity int64
MaxBudget string
Status string
StatusLabel string
StatusClass string
ReferenceAssetID string
CreatedAt time.Time
UpdatedAt time.Time
CanCancel bool
CancelRequiresAck bool
ExecutionReport *ExecutionReport
ID string
Title string
SKU string
Description string
Quantity int64
MaxBudget string
Status string
Version int64
StatusLabel string
StatusClass string
ReferenceAssetID string
CreatedAt time.Time
UpdatedAt time.Time
CanCancel bool
CancelRequiresAck bool
ExecutionReport *ExecutionReport
Candidates []AuthorizationCandidate
OrderAuthorizations []OrderAuthorization
CanAuthorizeOrder bool
ActiveAuthorizationID string
}
type taskDetailPage struct {
Page pageView
Task taskDetailView
CSRFToken string
CancelKey string
Notice string
Page pageView
Task taskDetailView
CSRFToken string
CancelKey string
AuthorizationKey string
Notice string
}
type errorPage struct {
@@ -774,6 +854,17 @@ type errorPage struct {
}
func taskDetailViewFrom(task Task) taskDetailView {
activeAuthorizationID := ""
canAuthorizeOrder := task.Status == "WAITING_CONFIRMATION" &&
len(task.Candidates) > 0
for _, authorization := range task.OrderAuthorizations {
switch authorization.Status {
case "PENDING_DELIVERY":
activeAuthorizationID = authorization.ID
case "DELIVERED", "ACKNOWLEDGED", "EXECUTING":
canAuthorizeOrder = false
}
}
return taskDetailView{
ID: task.ID,
Title: task.Title,
@@ -782,6 +873,7 @@ func taskDetailViewFrom(task Task) taskDetailView {
Quantity: task.Quantity,
MaxBudget: task.MaxBudget,
Status: task.Status,
Version: task.Version,
StatusLabel: statusLabel(task.Status),
StatusClass: statusClass(task.Status),
ReferenceAssetID: task.ReferenceAssetID,
@@ -790,7 +882,11 @@ func taskDetailViewFrom(task Task) taskDetailView {
CanCancel: canCancelTaskStatus(task.Status),
CancelRequiresAck: task.Status == "RUNNING" ||
task.Status == "WAITING_CONFIRMATION",
ExecutionReport: task.ExecutionReport,
ExecutionReport: task.ExecutionReport,
Candidates: task.Candidates,
OrderAuthorizations: task.OrderAuthorizations,
CanAuthorizeOrder: canAuthorizeOrder,
ActiveAuthorizationID: activeAuthorizationID,
}
}