Files
goauto/server/app/goauto/task/handler.go
T

339 lines
10 KiB
Go

package task
import (
stdcontext "context"
"encoding/json"
"errors"
"io"
"net/http"
"strconv"
"strings"
"go-admin/app/goauto/device"
"go-admin/app/goauto/replacement"
"github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
"gorm.io/gorm"
)
type Handler struct{ DB *gorm.DB }
func (handler Handler) CreateCurrentPage(context *gin.Context) {
var request CurrentPageCreateRequest
if err := decodeStrict(context, &request); err != nil {
writeError(context, serviceError(device.CodeInvalidRequest, "请求 JSON 无效"))
return
}
service, token, ok := handler.service(context)
if !ok {
return
}
payload, err := service.CreateCurrentPage(context.Request.Context(), request, token)
if err != nil {
writeError(context, err)
return
}
context.Header("Cache-Control", "no-store")
context.JSON(http.StatusCreated, gin.H{"data": payload})
}
func (handler Handler) IdentifyCurrentPage(context *gin.Context) {
id, err := taskID(context)
if err != nil || id == 0 {
writeError(context, serviceError(device.CodeInvalidRequest, "taskId 无效"))
return
}
var request CurrentPageIdentifyRequest
if err := decodeStrict(context, &request); err != nil {
writeError(context, serviceError(device.CodeInvalidRequest, "请求 JSON 无效"))
return
}
service, token, ok := handler.service(context)
if !ok {
return
}
payload, err := service.IdentifyCurrentPage(context.Request.Context(), id, request, token)
if err != nil {
writeError(context, err)
return
}
context.Header("Cache-Control", "no-store")
context.JSON(http.StatusOK, gin.H{"data": payload})
}
func (handler Handler) Next(context *gin.Context) {
service, token, ok := handler.service(context)
if !ok {
return
}
payload, err := service.Next(context.Request.Context(), token)
if err != nil {
writeError(context, err)
return
}
if payload == nil {
context.Status(http.StatusNoContent)
return
}
context.JSON(http.StatusOK, gin.H{"data": payload})
}
func (handler Handler) AgentHistory(context *gin.Context) {
page, err := positiveQuery(context.Query("page"), 1)
if err != nil {
writeError(context, serviceError(device.CodeInvalidRequest, "page 无效"))
return
}
pageSize, err := positiveQuery(context.Query("pageSize"), 20)
if err != nil {
writeError(context, serviceError(device.CodeInvalidRequest, "pageSize 无效"))
return
}
days, err := positiveQuery(context.Query("days"), agentHistoryDays)
if err != nil || days > agentHistoryDays {
writeError(context, serviceError(device.CodeInvalidRequest, "days 必须在 1 到 30 之间"))
return
}
service, token, ok := handler.service(context)
if !ok {
return
}
result, err := service.AgentHistory(context.Request.Context(), AgentHistoryRequest{
Page: page, PageSize: pageSize, Days: days, Status: strings.TrimSpace(context.Query("status")), TaskNo: context.Query("taskNo"),
}, token)
if err != nil {
writeError(context, err)
return
}
context.Header("Cache-Control", "no-store")
context.JSON(http.StatusOK, gin.H{"data": result})
}
func (handler Handler) AgentHistoryDetail(context *gin.Context) {
id, err := taskID(context)
if err != nil || id == 0 {
writeError(context, serviceError(device.CodeInvalidRequest, "taskId 无效"))
return
}
service, token, ok := handler.service(context)
if !ok {
return
}
result, err := service.AgentHistoryDetail(context.Request.Context(), id, token)
if err != nil {
writeError(context, err)
return
}
context.Header("Cache-Control", "no-store")
context.JSON(http.StatusOK, gin.H{"data": result})
}
func (handler Handler) AgentReset(context *gin.Context) {
id, err := taskID(context)
if err != nil || id == 0 {
writeError(context, serviceError(device.CodeInvalidRequest, "taskId 无效"))
return
}
var request ActionRequest
if err := decodeStrict(context, &request); err != nil {
writeError(context, serviceError(device.CodeInvalidRequest, "请求 JSON 无效"))
return
}
service, token, ok := handler.service(context)
if !ok {
return
}
result, err := service.ResetForDevice(context.Request.Context(), id, request, token)
if err != nil {
writeError(context, err)
return
}
context.Header("Cache-Control", "no-store")
context.JSON(http.StatusOK, gin.H{"data": result})
}
func (handler Handler) Claim(context *gin.Context) { handler.action(context, (*Service).Claim) }
func (handler Handler) Start(context *gin.Context) { handler.action(context, (*Service).Start) }
func (handler Handler) Result(context *gin.Context) {
id, err := taskID(context)
if err != nil || id == 0 {
writeError(context, serviceError(device.CodeInvalidRequest, "taskId 无效"))
return
}
var request ResultRequest
if err := decodeStrict(context, &request); err != nil {
writeError(context, serviceError(device.CodeInvalidRequest, "请求 JSON 无效"))
return
}
service, token, ok := handler.service(context)
if !ok {
return
}
payload, err := service.SubmitResult(context.Request.Context(), id, request, token)
if err != nil {
writeError(context, err)
return
}
context.Header("Cache-Control", "no-store")
context.JSON(http.StatusOK, gin.H{"data": payload})
}
func (handler Handler) ColorImage(context *gin.Context) {
id, err := taskID(context)
if err != nil || id == 0 {
writeError(context, serviceError(device.CodeInvalidRequest, "taskId 无效"))
return
}
context.Request.Body = http.MaxBytesReader(context.Writer, context.Request.Body, colorImageRequestMaxBytes)
file, err := context.FormFile("file")
if err != nil {
writeError(context, serviceError(device.CodeInvalidRequest, "file 必填"))
return
}
stream, err := file.Open()
if err != nil {
writeError(context, internalError(err))
return
}
defer stream.Close()
content, err := io.ReadAll(io.LimitReader(stream, colorImageMaxBytes+1))
if err != nil {
writeError(context, internalError(err))
return
}
service, token, ok := handler.service(context)
if !ok {
return
}
result, err := service.UploadColorImage(context.Request.Context(), id, strings.TrimSpace(context.PostForm("color")), content, token)
if err != nil {
writeError(context, err)
return
}
context.Header("Cache-Control", "no-store")
context.JSON(http.StatusCreated, gin.H{"data": result})
}
func (handler Handler) Fail(context *gin.Context) {
id, err := taskID(context)
if err != nil || id == 0 {
writeError(context, serviceError(device.CodeInvalidRequest, "taskId 无效"))
return
}
var request FailRequest
if err := decodeStrict(context, &request); err != nil {
writeError(context, serviceError(device.CodeInvalidRequest, "请求 JSON 无效"))
return
}
service, token, ok := handler.service(context)
if !ok {
return
}
payload, err := service.SubmitFailure(context.Request.Context(), id, request, token)
if err != nil {
writeError(context, err)
return
}
context.Header("Cache-Control", "no-store")
context.JSON(http.StatusOK, gin.H{"data": payload})
}
func (handler Handler) action(context *gin.Context, action func(*Service, stdcontext.Context, uint64, ActionRequest, string) (TaskPayload, error)) {
id, err := taskID(context)
if err != nil || id == 0 {
writeError(context, serviceError(device.CodeInvalidRequest, "taskId 无效"))
return
}
request, err := decodeAction(context)
if err != nil {
writeError(context, serviceError(device.CodeInvalidRequest, "请求 JSON 无效"))
return
}
service, token, ok := handler.service(context)
if !ok {
return
}
payload, err := action(service, context.Request.Context(), id, request, token)
if err != nil {
writeError(context, err)
return
}
context.Header("Cache-Control", "no-store")
context.JSON(http.StatusOK, gin.H{"data": payload})
}
func (handler Handler) service(context *gin.Context) (*Service, string, bool) {
db := handler.DB
var err error
if db == nil {
db, err = pkg.GetOrm(context)
if err != nil {
writeError(context, internalError(err))
return nil, "", false
}
}
return NewService(db), bearerToken(context.GetHeader("Authorization")), true
}
func decodeAction(context *gin.Context) (ActionRequest, error) {
var request ActionRequest
context.Request.Body = http.MaxBytesReader(context.Writer, context.Request.Body, 8<<10)
decoder := json.NewDecoder(context.Request.Body)
decoder.DisallowUnknownFields()
if err := decoder.Decode(&request); err != nil {
return request, err
}
if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) {
return request, errors.New("request body must contain one JSON object")
}
return request, nil
}
func taskID(context *gin.Context) (uint64, error) {
return strconv.ParseUint(context.Param("taskId"), 10, 64)
}
func bearerToken(header string) string {
parts := strings.Fields(header)
if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") {
return ""
}
return parts[1]
}
func writeError(context *gin.Context, err error) {
code, message, retryable, status := device.CodeInternal, "服务端处理失败", true, http.StatusInternalServerError
var taskError *ServiceError
var deviceError *device.ServiceError
if errors.As(err, &taskError) {
code, message, retryable = taskError.Code, taskError.Message, taskError.Retryable
} else if errors.As(err, &deviceError) {
code, message, retryable = deviceError.Code, deviceError.Message, deviceError.Retryable
}
switch code {
case device.CodeInvalidRequest, "RESULT_SPEC_INVALID", "RESULT_GOODS_ID_MISMATCH", CodePDDShareLinkInvalid:
status = http.StatusUnprocessableEntity
case device.CodeTokenInvalid:
status = http.StatusUnauthorized
case device.CodeDeviceDisabled:
status = http.StatusForbidden
case CodeTaskNotFound:
status = http.StatusNotFound
case CodeColorImageTooLarge:
status = http.StatusRequestEntityTooLarge
case CodeColorImageUnsupported:
status = http.StatusUnsupportedMediaType
case CodeTaskAlreadyClaimed, CodeTaskAssignedOther, CodeDeviceBusy, CodeDeviceOffline, CodeTaskStateConflict, CodeTaskLeaseExpired, CodeCurrentPageIdentityRequired, CodeCurrentPageIdentityConflict:
status = http.StatusConflict
case replacement.CodeOriginNotEligible, CodeReplacementActivationFailed:
status = http.StatusConflict
case CodeProductTaskActive, CodeProductDisabled:
status = http.StatusConflict
case CodeProductNotFound, CodeRuleNotFound, CodeDeviceNotFound, CodeAgentManualRuleNotConfigured:
status = http.StatusNotFound
}
context.JSON(status, gin.H{"code": code, "message": message, "retryable": retryable})
}