You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

API Platform中Multipart/Form-Data反序列化问题求助

解决API Platform + Symfony处理multipart/form-data反序列化错误

错误原因

你遇到的Deserialization for the format "multipart" is not supported错误有两个核心原因:

  1. API Platform默认序列化系统不支持直接将multipart/form-data请求反序列化为实体对象,即便在配置中声明了multipart格式,也需要结合文件上传组件实现特定逻辑。
  2. 你自定义的/api/messagesPOST路由和API Platform为Message实体自动生成的路由冲突,请求被API Platform的处理管道接管,而非进入你写的控制器代码,从而触发了反序列化错误。

解决方案一:使用API Platform原生文件上传(推荐)

遵循API Platform规范,结合VichUploaderBundle实现文件上传,无需自定义控制器:

1. 安装依赖

composer require vich/uploader-bundle

2. 修改Message实体

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Serializer\Annotation\Groups;
use Vich\UploaderBundle\Mapping\Annotation as Vich;

#[ORM\Entity]
#[ApiResource(
    normalizationContext: ['groups' => ['message:read']],
    denormalizationContext: ['groups' => ['message:write']],
    operations: [
        new Post(
            uriTemplate: '/api/messages',
            openapiContext: [
                'requestBody' => [
                    'content' => [
                        'multipart/form-data' => [
                            'schema' => [
                                'type' => 'object',
                                'properties' => [
                                    'description' => ['type' => 'string'],
                                    'keywords' => ['type' => 'string'],
                                    'category' => ['type' => 'string', 'format' => 'iri'],
                                    'audioFile' => [
                                        'type' => 'string',
                                        'format' => 'binary'
                                    ]
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        )
    ]
)]
#[Vich\Uploadable]
class Message
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    #[Groups(['message:read'])]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    #[Groups(['message:read', 'message:write'])]
    private ?string $description = null;

    #[ORM\Column(length: 255)]
    #[Groups(['message:read', 'message:write'])]
    private ?string $keywords = null;

    #[ORM\ManyToOne(targetEntity: Category::class)]
    #[ORM\JoinColumn(nullable: false)]
    #[Groups(['message:read', 'message:write'])]
    private ?Category $category = null;

    #[ORM\Column(length: 255, nullable: true)]
    #[Groups(['message:read'])]
    private ?string $audioFileName = null;

    #[Vich\UploadableField(mapping: 'message_audio', fileNameProperty: 'audioFileName')]
    #[Groups(['message:write'])]
    private ?File $audioFile = null;

    // Getter & Setter
    public function getId(): ?int
    {
        return $this->id;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(string $description): self
    {
        $this->description = $description;
        return $this;
    }

    public function getKeywords(): ?string
    {
        return $this->keywords;
    }

    public function setKeywords(string $keywords): self
    {
        $this->keywords = $keywords;
        return $this;
    }

    public function getCategory(): ?Category
    {
        return $this->category;
    }

    public function setCategory(?Category $category): self
    {
        $this->category = $category;
        return $this;
    }

    public function getAudioFileName(): ?string
    {
        return $this->audioFileName;
    }

    public function setAudioFileName(?string $audioFileName): self
    {
        $this->audioFileName = $audioFileName;
        return $this;
    }

    public function getAudioFile(): ?File
    {
        return $this->audioFile;
    }

    public function setAudioFile(?File $audioFile): self
    {
        $this->audioFile = $audioFile;
        return $this;
    }
}

3. 配置VichUploaderBundle

在config/packages/vich_uploader.yaml添加:

vich_uploader:
    db_driver: orm

    mappings:
        message_audio:
            uri_prefix: /uploads/audio
            upload_destination: '%kernel.project_dir%/public/uploads/audio'
            namer: Vich\UploaderBundle\Naming\UniqidNamer

4. 移除自定义控制器

API Platform会自动处理POST请求,包括multipart/form-data的解析和文件上传,你的Angular服务代码无需修改。


解决方案二:保留自定义控制器,避免API Platform拦截

如果你坚持使用自定义控制器,需要确保请求直接进入你的代码:

1. 禁用Message实体的自动路由

在Message实体上添加注解,关闭API Platform自动生成的路由:

#[ApiResource(operations: [])]

2. 修改自定义控制器路由

添加优先级和ApiIgnore注解,确保路由优先且被API Platform忽略:

namespace App\Controller;

use App\Entity\Message;
use ApiPlatform\Core\Bridge\Symfony\Annotation\ApiIgnore;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Doctrine\ORM\EntityManagerInterface;

class MessageController extends AbstractController
{
    #[ApiIgnore]
    #[Route('/api/messages', name: 'api_message_create', methods: ['POST'], priority: 10)]
    public function create(Request $request, EntityManagerInterface $entityManager): Response
    {
        // 原控制器代码不变
    }
}

这样请求会直接进入你的控制器,绕过API Platform的序列化管道,不会触发反序列化错误。


内容的提问来源于stack exchange,提问作者fatimazahra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 04:19:54