页面混显三种来源却只有 goodsId 和状态两个筛选,批量取消时无法限定范围。 - 服务端 AdminList 增加 source 过滤,非法值报参数错误而非静默忽略。 - 前端增加来源筛选与来源列;状态补 cancelled(info 色,取消不是错误,不与 failed 共用红色)。 - 「取消未开始的任务」按钮带实时数量,确认框列出将被取消的任务,并写明范围是 整个筛选条件而非当前页。hasMore 时提示还有未处理的任务。 `[必须]` 取消范围包含 goodsId。少了这一维,按 goods_id 筛出两条、按钮却取消 三十几条——那正是当初放弃「两个固定按钮」、改用「筛选 + 一个按钮」想避免的事。 BatchCancel 的 goodsId 与 AdminList 用同一种匹配方式,否则两边范围会悄悄错开。 实施:sonnet 子代理;goodsId 范围一致性由复核补入。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
235 lines
5.8 KiB
Go
235 lines
5.8 KiB
Go
package task
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
func (handler Handler) AdminList(c *gin.Context) {
|
|
page, err := positiveQuery(c.Query("page"), 1)
|
|
if err != nil {
|
|
writeError(c, serviceError("INVALID_REQUEST", "page 无效"))
|
|
return
|
|
}
|
|
pageSize, err := positiveQuery(c.Query("pageSize"), 20)
|
|
if err != nil {
|
|
writeError(c, serviceError("INVALID_REQUEST", "pageSize 无效"))
|
|
return
|
|
}
|
|
deviceID, err := optionalUint(c.Query("deviceId"))
|
|
if err != nil {
|
|
writeError(c, serviceError("INVALID_REQUEST", "deviceId 无效"))
|
|
return
|
|
}
|
|
service, _, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.AdminList(c.Request.Context(), AdminListRequest{
|
|
Page: page, PageSize: pageSize, Status: strings.TrimSpace(c.Query("status")),
|
|
GoodsID: strings.TrimSpace(c.Query("goodsId")), Source: strings.TrimSpace(c.Query("source")), DeviceID: deviceID,
|
|
})
|
|
if err != nil {
|
|
writeError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
|
|
}
|
|
|
|
func (handler Handler) AdminCreate(c *gin.Context) {
|
|
var request CreateRequest
|
|
if err := decodeStrict(c, &request); err != nil {
|
|
writeError(c, serviceError("INVALID_REQUEST", "请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, _, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.Create(c.Request.Context(), request)
|
|
if err != nil {
|
|
writeError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
|
|
}
|
|
|
|
func (handler Handler) AdminBatchCreate(c *gin.Context) {
|
|
var request BatchCreateRequest
|
|
if err := decodeStrict(c, &request); err != nil {
|
|
writeError(c, serviceError("INVALID_REQUEST", "请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, _, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.BatchCreate(c.Request.Context(), request)
|
|
if err != nil {
|
|
writeError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
|
|
}
|
|
|
|
func (handler Handler) AdminBatchCreateImageSearch(c *gin.Context) {
|
|
var request ImageSearchBatchRequest
|
|
if err := decodeStrict(c, &request); err != nil {
|
|
writeError(c, serviceError("INVALID_REQUEST", "请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, _, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.BatchCreateImageSearch(c.Request.Context(), request)
|
|
if err != nil {
|
|
writeError(c, err)
|
|
return
|
|
}
|
|
c.Header("Cache-Control", "no-store")
|
|
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
|
|
}
|
|
|
|
func (handler Handler) AdminDetail(c *gin.Context) {
|
|
id, err := taskID(c)
|
|
if err != nil || id == 0 {
|
|
writeError(c, serviceError("INVALID_REQUEST", "taskId 无效"))
|
|
return
|
|
}
|
|
service, _, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.Detail(c.Request.Context(), id)
|
|
if err != nil {
|
|
writeError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
|
|
}
|
|
|
|
func (handler Handler) AdminReset(c *gin.Context) {
|
|
id, err := taskID(c)
|
|
if err != nil || id == 0 {
|
|
writeError(c, serviceError("INVALID_REQUEST", "taskId 无效"))
|
|
return
|
|
}
|
|
var request ActionRequest
|
|
if err := decodeStrict(c, &request); err != nil {
|
|
writeError(c, serviceError("INVALID_REQUEST", "请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, _, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.Reset(c.Request.Context(), id, request)
|
|
if err != nil {
|
|
writeError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
|
|
}
|
|
|
|
func (handler Handler) AdminDelete(c *gin.Context) {
|
|
id, err := taskID(c)
|
|
if err != nil || id == 0 {
|
|
writeError(c, serviceError("INVALID_REQUEST", "taskId 无效"))
|
|
return
|
|
}
|
|
var request ActionRequest
|
|
if err := decodeStrict(c, &request); err != nil {
|
|
writeError(c, serviceError("INVALID_REQUEST", "请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, _, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.DeleteFailed(c.Request.Context(), id, request)
|
|
if err != nil {
|
|
writeError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
|
|
}
|
|
|
|
func (handler Handler) AdminCancel(c *gin.Context) {
|
|
id, err := taskID(c)
|
|
if err != nil || id == 0 {
|
|
writeError(c, serviceError("INVALID_REQUEST", "taskId 无效"))
|
|
return
|
|
}
|
|
var request ActionRequest
|
|
if err := decodeStrict(c, &request); err != nil {
|
|
writeError(c, serviceError("INVALID_REQUEST", "请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, _, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.Cancel(c.Request.Context(), id, request)
|
|
if err != nil {
|
|
writeError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
|
|
}
|
|
|
|
func (handler Handler) AdminBatchCancel(c *gin.Context) {
|
|
var request BatchCancelRequest
|
|
if err := decodeStrict(c, &request); err != nil {
|
|
writeError(c, serviceError("INVALID_REQUEST", "请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, _, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.BatchCancel(c.Request.Context(), request)
|
|
if err != nil {
|
|
writeError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
|
|
}
|
|
|
|
func decodeStrict(c *gin.Context, destination any) error {
|
|
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 1<<20)
|
|
decoder := json.NewDecoder(c.Request.Body)
|
|
decoder.DisallowUnknownFields()
|
|
if err := decoder.Decode(destination); err != nil {
|
|
return err
|
|
}
|
|
if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) {
|
|
return errors.New("one object required")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func positiveQuery(raw string, fallback int) (int, error) {
|
|
if strings.TrimSpace(raw) == "" {
|
|
return fallback, nil
|
|
}
|
|
value, err := strconv.Atoi(raw)
|
|
if err != nil || value < 1 {
|
|
return 0, errors.New("invalid positive integer")
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func optionalUint(raw string) (uint64, error) {
|
|
if strings.TrimSpace(raw) == "" {
|
|
return 0, nil
|
|
}
|
|
return strconv.ParseUint(raw, 10, 64)
|
|
}
|