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

React Native向Heroku API发送图片持续返回400错误如何解决

React Native 拍照上传接口返回400错误修复

问题背景

正在开发一款支持拍摄照片、返回图片内检测物体列表的Expo React Native应用,已将API地址替换为insert api url here做脱敏处理,实际使用的API地址已在Postman中验证可正常运行。
现有逻辑为:拍摄照片后将图片本地URI存入state,通过axios将该URI作为参数提交给接口,调用时持续返回400错误:

[AxiosError: Request failed with status code 400]

原有项目完整代码

import React, { useState, useEffect, useRef } from "react";
import { Text, View, StyleSheet, TouchableOpacity, Image } from "react-native";
import Constants from "expo-constants";
import { Camera, CameraType } from "expo-camera";
import * as MediaLibrary from "expo-media-library";
import { MaterialIcons } from "@expo/vector-icons";
import Button from "./src/components/Button";
import axios from "axios";
export default function App() {
  const [hasCameraPermission, setHasCameraPermission] = useState(null);
  const [image, setImage] = useState(null);
  const [type, setType] = useState(Camera.Constants.Type.back);
  const [flash, setFlash] = useState(Camera.Constants.FlashMode.off);
  const cameraRef = useRef(null);

  useEffect(() => {
    (async () => {
      MediaLibrary.requestPermissionsAsync();
      const cameraStatus = await Camera.requestCameraPermissionsAsync();
      setHasCameraPermission(cameraStatus.status === "granted");
    })();
  }, []);

  const takePicture = async () => {
    if (cameraRef) {
      try {
        const data = await cameraRef.current.takePictureAsync();
        console.log(data);
        setImage(data.uri);
      } catch (error) {
        console.log(error);
      }
    }
  };

  const savePicture = async () => {
    if (image) {
      try {
        const asset = await MediaLibrary.createAssetAsync(image);

        axios
          .post("insert api url here", {
            Imagee: image,
          })
          .then((response) => {
            console.log(response);
          })
          .catch((error) => {
            console.log(error);
          });

        alert("done");
        setImage(null);
        console.log("saved successfully");
      } catch (error) {
        console.log(error);
      }
    }
  };

  if (hasCameraPermission === false) {
    return <Text>No access to camera</Text>;
  }

  return (
    <View style={styles.container}>
      {!image ? (
        <Camera
          style={styles.camera}
          type={type}
          ref={cameraRef}
          flashMode={flash}
        >
          <View
            style={{
              flexDirection: "row",
              justifyContent: "space-between",
              paddingHorizontal: 30,
            }}
          >
            <Button
              title=""
              icon="retweet"
              onPress={() => {
                setType(
                  type === CameraType.back ? CameraType.front : CameraType.back
                );
              }}
            />
            <Button
              onPress={() =>
                setFlash(
                  flash === Camera.Constants.FlashMode.off
                    ? Camera.Constants.FlashMode.on
                    : Camera.Constants.FlashMode.off
                )
              }
              icon="flash"
              color={flash === Camera.Constants.FlashMode.off ? "gray" : "#fff"}
            />
          </View>
        </Camera>
      ) : (
        <Image source={{ uri: image }} style={styles.camera} />
      )}

      <View style={styles.controls}>
        {image ? (
          <View
            style={{
              flexDirection: "row",
              justifyContent: "space-between",
              paddingHorizontal: 50,
            }}
          >
            <Button
              title="Re-take"
              onPress={() => setImage(null)}
              icon="retweet"
            />

            <Button
              type="submit"
              title="Save"
              onPress={savePicture}
              icon="check"
            />
          </View>
        ) : (
          <Button title="Take a picture" onPress={takePicture} icon="camera" />
        )}
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    paddingTop: Constants.statusBarHeight,
    backgroundColor: "#000",
    padding: 8,
  },
  controls: {
    flex: 0.5,
  },
  button: {
    height: 40,
    borderRadius: 6,
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "center",
  },
  text: {
    fontWeight: "bold",
    fontSize: 16,
    color: "#E9730F",
    marginLeft: 10,
  },
  camera: {
    flex: 5,
    borderRadius: 20,
  },
  topControls: {
    flex: 1,
  },
});

核心问题原因

400错误本质是请求格式不符合接口要求:

  • 代码中提交的image是Expo相机返回的本地文件路径字符串(格式类似file:///本地存储路径/xxx.jpg),不是接口需要的图片文件实体
  • Postman调用成功是因为Postman上传时默认使用multipart/form-data格式传输真实图片二进制内容,和现有代码发送JSON传本地路径的请求格式完全不匹配
  • 现有代码还存在逻辑缺陷:没有等待接口请求完成就弹出成功提示、清空图片状态,请求失败也会误报成功;同时混用async/await和.then/.catch写法,异常捕获不完整

修复步骤

  1. 安装文件读取依赖,用于获取本地图片信息:
expo install expo-file-system
  1. 在文件头部引入依赖:
import * as FileSystem from 'expo-file-system';
  1. 替换原有savePicture方法,使用FormData格式上传真实图片文件:
const savePicture = async () => {
  if (!image) return;
  try {
    // 保存图片到系统相册
    await MediaLibrary.createAssetAsync(image);
    
    // 构造FormData上传格式
    const formData = new FormData();
    formData.append('Imagee', {
      uri: image,
      name: `photo_${Date.now()}.jpg`,
      type: 'image/jpeg',
    });

    // 发起请求,指定正确的Content-Type
    const response = await axios.post(
      "insert api url here",
      formData,
      {
        headers: {
          'Content-Type': 'multipart/form-data',
        },
      }
    );
    console.log('接口返回:', response.data);
    
    alert("上传成功");
    setImage(null);
  } catch (error) {
    console.log('请求错误:', error);
    alert(`上传失败:${error.response?.data?.message || error.message}`);
  }
};

校验点

  • 确认FormData中append的字段名Imagee和接口要求的字段名完全一致,注意大小写
  • 如果Postman中使用的是其他请求格式(比如base64传参),对应调整传参格式即可,绝大多数图片识别接口默认接收multipart/form-data格式
  • 安卓端打包正式版时,需要在app.json中声明文件读取、相机权限,否则会出现本地文件访问失败问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 00:01:20