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

使用Remix Run UploadHandler上传文件至WhatsApp API遇参数错误

解决WhatsApp Cloud API上传时的"(#100) The parameter messaging_product is required"错误

核心问题定位

你遇到的错误并非messaging_product字段未提交,而是手动设置Content-Type: multipart/form-data导致FormData无法被WhatsApp API正确解析。当用fetch发送FormData时,浏览器会自动生成包含boundary参数的Content-Type头(格式类似multipart/form-data; boundary=----WebKitFormBoundaryxxxx),手动设置该头会丢失关键的boundary信息,API无法识别表单中的字段,因此抛出必填参数缺失的提示。

修正后的代码

export async function action({ request, params }: ActionArgs) {
  const uploadHandler = unstable_composeUploadHandlers(
    async ({ name, contentType, data, filename }) => {
      const whatsAppPhoneId = process.env.WHATSAPP_PHONE_ID;
      const whatsAppToken = process.env.WHATSAPP_ACCESS_TOKEN;

      // 高效收集文件二进制数据
      const chunks: Uint8Array[] = [];
      for await (const chunk of data) {
        chunks.push(chunk);
      }
      const fileBuffer = Buffer.concat(chunks);
      const file = new File([fileBuffer], filename, { type: contentType });

      const graphApiUrl = `https://graph.facebook.com/v15.0/${whatsAppPhoneId}/media`;
      const formData = new FormData();
      formData.append("file", file);
      formData.append("messaging_product", "whatsapp");
      formData.append("type", contentType);

      try {
        const imageMediaResponse = await fetch(graphApiUrl, {
          method: "POST",
          headers: {
            Authorization: `Bearer ${whatsAppToken}`,
            // 移除手动设置的Content-Type,让fetch自动生成带boundary的正确头
          },
          body: formData,
        });

        // 主动检查API响应状态,捕获非成功响应
        if (!imageMediaResponse.ok) {
          const errorDetail = await imageMediaResponse.json();
          throw new Error(`API请求失败: ${JSON.stringify(errorDetail)}`);
        }

        const imageMedia = await imageMediaResponse.json();
        return imageMedia.id;
      } catch (error) {
        console.error("文件上传至WhatsApp失败:", error);
        throw error; // 抛出错误以便Remix统一处理
      }
    }
  );

  try {
    const formData = await unstable_parseMultipartFormData(request, uploadHandler);
    // 根据表单文件输入的name获取返回的媒体ID
    const mediaId = formData.get("你的文件输入name属性值");
    return json({ success: true, mediaId });
  } catch (error) {
    return json({ success: false, error: (error as Error).message }, { status: 500 });
  }
}

额外优化说明

  • 二进制数据处理:改用Buffer.concat合并文件块,比单纯数组push更适合处理二进制流,避免潜在的数据损坏。
  • 错误处理强化:添加API响应状态校验,捕获非2xx状态并提取详细错误信息,便于调试。
  • 响应规范:遵循Remix的action返回标准,返回JSON格式结果,方便前端接收处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 11:50:32