339 lines
10 KiB
Go
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})
|
|
}
|