Golang中如何处理不可处理实体错误

Golang中如何处理不可处理实体错误 我在Go Fiber中创建了一个用于创建提示的POST API。从前端传递的载荷中,creator字段是字符串类型,但在我的prompt_model中,它被定义为primitive.ObjectId类型。当我运行函数c.BodyParser(&prompt)时,会抛出错误“bad request Unprocessable Entity”。

以下是Prompt模型:

type Prompts struct{
	Id primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"`
	Creator primitive.ObjectID `json:"creator,omitempty" bson:"creator,omitempty"`
	CreatorUser User `json:"creatorUser,omitempty" bson:"-"`
	Prompt string `json:"prompt,omitempty" validate:"required"`
	Tag string `json:"tag,omitempty" validate:"required"`
}

以下是创建提示控制器的代码:

func CreatePrompt(c *fiber.Ctx) error {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	var prompt models.Prompts

	// 从URL参数中提取会话ID
	// sessionID := c.FormValue("creator")
	// sessionObjectID, _ := primitive.ObjectIDFromHex(sessionID)
	// fmt.Printf("ss %v \n ",sessionID)

	// 验证请求体
	fmt.Println("dd", c.BodyParser(&prompt))
	if err := c.BodyParser(&prompt); err != nil {
		return c.Status(http.StatusBadRequest).JSON(responses.UserResponse{
			Status:  http.StatusBadRequest,
			Message: "error",
			Data:    &fiber.Map{"data": err.Error()},
		})
	}

	// 假设会话ID存储在Creator字段中
	newPrompt := models.Prompts{
		Id:          primitive.NewObjectID(),
		Creator:     prompt.Creator,
		Prompt:      prompt.Prompt,
		Tag:         prompt.Tag,
	}

	result, err := promptCollection.InsertOne(ctx, newPrompt)
	if err != nil {
		return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{
			Status:  http.StatusInternalServerError,
			Message: "error",
			Data:    &fiber.Map{"data": err.Error()},
		})
	}

	return c.Status(http.StatusCreated).JSON(responses.UserResponse{
		Status:  http.StatusCreated,
		Message: "success",
		Data:    &fiber.Map{"data": result},
	})
}

以下是来自前端的API调用:

const response = await fetch("http://localhost:8000/create-prompt", {
        method: "POST",
        body: JSON.stringify({
          creator: session?.user.id,
          prompt: post.prompt,
          tag: post.tag,
        }),

以下是载荷数据的日志:

{
  "creator":"64ea57ad19b714af26ef3756",
  "prompt":"ads",
   "tag":"as"
}

我尝试在使用bodyParser方法之前将creator从字符串类型转换为objectId类型,但没有成功。我到底做错了什么?


更多关于Golang中如何处理不可处理实体错误的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

你能分享你用于此的代码吗?

更多关于Golang中如何处理不可处理实体错误的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢,它确实运行良好,你真是我的救星。

嗨,它起作用了,但我仍然有一个小问题。

Screenshot from 2023-09-12 01-40-47

如图所示,我希望 creator 字段能像第一部分那样,填入来自用户集合的用户ID以引用用户对象。但如您在第二个文档中所见,我并没有得到 creator 字段。

我尝试复现了这个问题,以下是我的发现。

  1. 请检查以下代码,确认我使用了正确的包且没有破坏原有逻辑:
package main

import (
	"fmt"

	fiber "github.com/gofiber/fiber/v2"
	"go.mongodb.org/mongo-driver/bson/primitive"
)

type Prompts struct {
	Id      primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"`
	Creator primitive.ObjectID `json:"creator,omitempty" bson:"creator,omitempty"`
	Prompt  string             `json:"prompt,omitempty" validate:"required"`
	Tag     string             `json:"tag,omitempty" validate:"required"`
}

func CreatePrompt(c *fiber.Ctx) error {
	var prompt Prompts

	// Validate the request body
	fmt.Println("dd", c.BodyParser(&prompt))
	if err := c.BodyParser(&prompt); err != nil {
		return err
	}

	// Assuming the session ID is stored in the Creator field
	newPrompt := Prompts{
		Id:      primitive.NewObjectID(),
		Creator: prompt.Creator,
		Prompt:  prompt.Prompt,
		Tag:     prompt.Tag,
	}

	fmt.Printf("%#v\n", newPrompt)

	return nil
}

func main() {
	f := fiber.New()

	f.Post("/test", CreatePrompt)

	f.Listen(":8080")
}
  1. 如果代码没问题,我运行了这个应用并尝试使用 Postman 发送一个 POST 请求,请求体如下:

image

得到的响应是同样的错误:Unprocessable Entity

  1. 在调试了 c.BodyParser 函数后,我发现了可能的问题:解析器实际上不支持请求体的类型 “text/plain”。但是,当我在 Postman 请求中将请求体类型改为 “application/json” 后,它就如预期般正常工作,没有报错。我的建议是,在你的前端调用中明确指定内容类型,添加 contentType: 'application/json',

问题在于你的模型定义中Creator字段是primitive.ObjectID类型,但前端传递的是字符串类型的十六进制值。BodyParser无法自动将字符串转换为ObjectID类型。

以下是解决方案:

方法1:使用中间结构体接收字符串,然后转换

type CreatePromptRequest struct {
    Creator string `json:"creator" validate:"required"`
    Prompt  string `json:"prompt" validate:"required"`
    Tag     string `json:"tag" validate:"required"`
}

func CreatePrompt(c *fiber.Ctx) error {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    var req CreatePromptRequest

    // 解析请求体到中间结构体
    if err := c.BodyParser(&req); err != nil {
        return c.Status(http.StatusBadRequest).JSON(responses.UserResponse{
            Status:  http.StatusBadRequest,
            Message: "error",
            Data:    &fiber.Map{"data": err.Error()},
        })
    }

    // 将字符串转换为ObjectID
    creatorID, err := primitive.ObjectIDFromHex(req.Creator)
    if err != nil {
        return c.Status(http.StatusBadRequest).JSON(responses.UserResponse{
            Status:  http.StatusBadRequest,
            Message: "error",
            Data:    &fiber.Map{"data": "invalid creator ID format"},
        })
    }

    // 创建新的Prompt对象
    newPrompt := models.Prompts{
        Id:      primitive.NewObjectID(),
        Creator: creatorID,
        Prompt:  req.Prompt,
        Tag:     req.Tag,
    }

    result, err := promptCollection.InsertOne(ctx, newPrompt)
    if err != nil {
        return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{
            Status:  http.StatusInternalServerError,
            Message: "error",
            Data:    &fiber.Map{"data": err.Error()},
        })
    }

    return c.Status(http.StatusCreated).JSON(responses.UserResponse{
        Status:  http.StatusCreated,
        Message: "success",
        Data:    &fiber.Map{"data": result},
    })
}

方法2:为ObjectID实现自定义JSON反序列化

type JSONObjectID primitive.ObjectID

func (id *JSONObjectID) UnmarshalJSON(data []byte) error {
    var s string
    if err := json.Unmarshal(data, &s); err != nil {
        return err
    }
    
    objectID, err := primitive.ObjectIDFromHex(s)
    if err != nil {
        return err
    }
    
    *id = JSONObjectID(objectID)
    return nil
}

func (id JSONObjectID) MarshalJSON() ([]byte, error) {
    return json.Marshal(primitive.ObjectID(id).Hex())
}

// 更新你的模型
type Prompts struct {
    Id          primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"`
    Creator     JSONObjectID       `json:"creator,omitempty" bson:"creator,omitempty"`
    CreatorUser User               `json:"creatorUser,omitempty" bson:"-"`
    Prompt      string             `json:"prompt,omitempty" validate:"required"`
    Tag         string             `json:"tag,omitempty" validate:"required"`
}

// 使用时需要类型转换
newPrompt := models.Prompts{
    Id:      primitive.NewObjectID(),
    Creator: models.JSONObjectID(creatorID),
    Prompt:  prompt.Prompt,
    Tag:     prompt.Tag,
}

方法3:使用自定义BodyParser(推荐)

type CreatePromptRequest struct {
    Creator primitive.ObjectID `json:"creator" validate:"required"`
    Prompt  string             `json:"prompt" validate:"required"`
    Tag     string             `json:"tag" validate:"required"`
}

func (r *CreatePromptRequest) UnmarshalJSON(data []byte) error {
    type Alias CreatePromptRequest
    aux := &struct {
        Creator string `json:"creator"`
        *Alias
    }{
        Alias: (*Alias)(r),
    }
    
    if err := json.Unmarshal(data, &aux); err != nil {
        return err
    }
    
    if aux.Creator != "" {
        objectID, err := primitive.ObjectIDFromHex(aux.Creator)
        if err != nil {
            return fmt.Errorf("invalid creator ID: %v", err)
        }
        r.Creator = objectID
    }
    
    return nil
}

func CreatePrompt(c *fiber.Ctx) error {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    var req CreatePromptRequest

    if err := c.BodyParser(&req); err != nil {
        return c.Status(http.StatusBadRequest).JSON(responses.UserResponse{
            Status:  http.StatusBadRequest,
            Message: "error",
            Data:    &fiber.Map{"data": err.Error()},
        })
    }

    newPrompt := models.Prompts{
        Id:      primitive.NewObjectID(),
        Creator: req.Creator,
        Prompt:  req.Prompt,
        Tag:     req.Tag,
    }

    result, err := promptCollection.InsertOne(ctx, newPrompt)
    if err != nil {
        return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{
            Status:  http.StatusInternalServerError,
            Message: "error",
            Data:    &fiber.Map{"data": err.Error()},
        })
    }

    return c.Status(http.StatusCreated).JSON(responses.UserResponse{
        Status:  http.StatusCreated,
        Message: "success",
        Data:    &fiber.Map{"data": result},
    })
}

方法1是最直接简单的解决方案,推荐使用。它清晰地分离了请求解析和类型转换,便于错误处理和调试。

回到顶部