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

React Native + Expo应用通过Azure Functions(Node.js)上传图片至Azure Blob Storage后无法正常打开的问题及正确上传方法问询

React Native Expo + Azure Functions: Fix Corrupted JPEG Uploads to Azure Blob Storage

我之前也碰到过一模一样的问题,你的排查思路完全正确——服务端解析后的二进制数据开头多出来的0d 0a(回车换行)破坏了JPEG的文件头(JPEG必须以FF D8开头),导致图片无法正常打开。下面给你两个可行的解决方案,以及问题的根源分析:

解决方案1:修正Multipart/Form-Data的解析流程

这个方案适配你当前的FormData上传方式,主要从客户端和服务端两方面调整:

客户端(React Native + Expo)修改

问题出在手动设置空headers,导致fetch无法自动生成带正确boundary的Content-Type头,服务端解析时出现异常。修改代码如下:

const formData = new FormData();
const imageUri = params.uri;
const dataType = mime.getType(imageUri);
const fileName = imageUri.split("/").pop();

// 移除不必要的类型断言,确保字段格式正确
formData.append('image', { 
  uri: imageUri, 
  name: fileName || "a.jpg", 
  type: dataType || "image/jpeg" 
});

const url = 'xxxxx';
// 不要手动设置headers,让fetch自动处理multipart的Content-Type和boundary
await fetch(url, { 
  method: 'POST', 
  body: formData 
});

服务端(Azure Functions Node.js)修改

替换parse-multipart库为更可靠的busboy,它能准确解析multipart/form-data,避免混入额外字符:

  1. 先安装busboy:npm install busboy
  2. 修改函数代码:
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
  try {
    const { BlobServiceClient } = require("@azure/storage-blob");
    const busboy = require("busboy");
    const containerName = "ContainerName";
    const connectionString = "your-storage-connection-string";

    return new Promise((resolve, reject) => {
      const bb = busboy({ headers: req.headers });
      let blobName: string;
      let blobData: Buffer[] = [];

      // 监听文件上传事件
      bb.on("file", (name: string, file: any, info: { filename: string }) => {
        blobName = info.filename;
        file.on("data", (data: Buffer) => {
          blobData.push(data);
        });
        file.on("end", async () => {
          // 拼接二进制数据
          const buffer = Buffer.concat(blobData);
          // 上传到Blob Storage
          const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
          const containerClient = blobServiceClient.getContainerClient(containerName);
          const blockBlobClient = containerClient.getBlockBlobClient(blobName);
          const uploadResponse = await blockBlobClient.upload(buffer, buffer.length);
          
          context.log(`Blob uploaded successfully, requestId: ${uploadResponse.requestId}`);
          context.res = { status: 200, body: "Upload successful" };
          resolve();
        });
      });

      // 处理解析错误
      bb.on("error", (err: Error) => {
        context.log(`Error parsing form data: ${err.message}`);
        context.res = { status: 500, body: "Error processing upload" };
        reject(err);
      });

      req.pipe(bb);
    });
  } catch (error) {
    context.log(`Error: ${error.message}`);
    context.res = { status: 500, body: "Internal server error" };
  }
};

解决方案2:直接上传Base64数据(更简单)

既然你已经在ImagePicker中开启了base64: true,可以跳过multipart解析,直接上传base64字符串,避免解析问题:

客户端修改

const url = 'xxxxx';
const fileName = params.uri.split("/").pop();

await fetch(url, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    imageBase64: params.base64,
    fileName: fileName || "a.jpg"
  })
});

服务端修改

const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
  try {
    const { BlobServiceClient } = require("@azure/storage-blob");
    const containerName = "ContainerName";
    const connectionString = "your-storage-connection-string";
    
    const { imageBase64, fileName } = req.body;
    // 将base64转成Buffer
    const buffer = Buffer.from(imageBase64, 'base64');
    
    // 上传到Blob Storage
    const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
    const containerClient = blobServiceClient.getContainerClient(containerName);
    const blockBlobClient = containerClient.getBlockBlobClient(fileName);
    const uploadResponse = await blockBlobClient.upload(buffer, buffer.length);
    
    context.log(`Blob uploaded successfully, requestId: ${uploadResponse.requestId}`);
    context.res = { status: 200, body: "Upload successful" };
  } catch (error) {
    context.log(`Error: ${error.message}`);
    context.res = { status: 500, body: "Internal server error" };
  }
};

问题根源说明

  1. 客户端:手动设置空headers会导致fetch无法自动添加Content-Type: multipart/form-data; boundary=xxx,服务端解析multipart数据时无法正确识别分隔符,从而引入额外的换行符。
  2. 服务端:parse-multipart库在处理二进制数据时,对boundary的换行处理不够严谨,导致0d 0a被混入JPEG文件头,破坏了文件结构。而Postman上传正常是因为它会自动生成标准的multipart请求头,服务端解析时没有异常。

这两个方案都能解决你的问题,方案2更简单,适合小尺寸图片;方案1更适合大文件上传(避免base64的体积膨胀)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 14:14:05