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

使用Express与Multer的NodeJS应用无法保存图片至磁盘的问题

图片上传功能调试问题

我正在尝试让图片上传的示例代码正常运行,但遇到了困难。我对示例中使用的所有技术都不熟悉,也不知道该采取哪些步骤进行调试,添加console.log日志的尝试也未成功。从日志和错误信息来看一切似乎正常,但图片并未保存到服务器目录,我无法找到原因,也不知道该从何处开始调试,比如如何在服务端添加日志检查图片数据是否存在。


服务端代码

const express = require("express");
const multer = require("multer");
const cors = require("cors"); // 为什么需要这个?我查了但还是不懂它的作用
const fs = require("fs");

const app = express();
const port = 3000;
app.use(express.json());
app.use(cors()); // 它到底是干嘛的?

const storage = multer.diskStorage({
  destination: (request, file, cb) => {
    cb(null, "uploads_test/"); // 按我的理解应该保存到`./uploads_test/`,这个文件夹已经存在了
  },
  filename: (request, file, cb) => {
    cb(null, file.fieldname + "-" + Date.now() + ".jpg");
  },
});

const upload = multer({ storage: storage });

app.post("/upload", upload.single("image"), async (request, response) => {
  try {
    await fs.promises.mkdir("uploads_test", { recursive: true });

    // 这些日志都没起到作用
    //console.log(request.body);
    //console.log(request.body.formData);
    // 这个直接报错
    //console.log(request.body.formData[0]);

    response.status(200).json({
      message: "image upload success",
      file: request.file,
    });
  } catch (error) {
    console.error("Error handling image upload:", error.message);

    // 返回失败响应
    response.status(500).json({ error: "Internal Server Error" });
  }
});

async function main() {
  if (!fs.existsSync("uploads_test")) {
    fs.mkdirSync("uploads_test");
  }

  app.listen(port, () => {
    console.log(
      `server imageServer (example) is listening at http://localhost:${port}`
    );
  });
}

main().catch((error) => {
  console.error(error);
});

React-Native端代码

import React, { useState, useEffect } from "react";
import { View, Text, Image, Button, StyleSheet } from "react-native";
import * as ImagePicker from "expo-image-picker";
import axios from "axios";

const ImageUploadScreenExpo = () => {
  const [hasGallaryPermissions, setHasGalleryPermissions] = useState(null);
  const [selectedImage, setSelectedImage] = useState(null);

  useEffect(() => {
    (async () => {
      const galleryStatus = await ImagePicker.requestCameraPermissionsAsync();
      setHasGalleryPermissions(galleryStatus.status === "granted");

      if (galleryStatus.status !== "granted") {
        alert("权限被拒绝");
      }
    })();
  }, []);

  const handleImageUploadAsync = async () => {
    if (!selectedImage) {
      alert("请先选择一张图片");
      return;
    }

    const fileExtension = selectedImage.split(".").pop();
    const fileType = `image/${fileExtension}`;

    const formData = new FormData();
    formData.append("image", {
      uri: selectedImage,
      type: fileType,
      name: `image.${fileExtension}`,
    });

    try {
      const response = await axios.post("http://192.168.0.11:3000/upload", {
        formData,
        headers: {
          "Content-Type": "multipart/form-data",
        },
      });

      // 日志里一直显示这个,但图片根本没保存
      console.log("图片上传成功:", response.data);
    } catch (error) {
      console.error(error);
    }
  };

  const selectImageAsync = async () => {
    let result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
      allowsEditing: true,
      aspect: [1, 1],
      quality: 1,
    });

    if (!result.canceled) {
      setSelectedImage(result.assets[0].uri);
    } else {
      alert("未选择图片");
    }
  };

  if (hasGallaryPermissions === false) {
    return <Text>无内部存储访问权限</Text>;
  }

  return (
    <View style={styles.container}>
      <Text>选择一张图片上传:</Text>
      <Button title="选择图片" onPress={selectImageAsync} />
      <Button title="上传图片" onPress={handleImageUploadAsync} />
      {selectedImage && (
        <View style={styles.imageContainer}>
          <Image
            source={{ uri: selectedImage }}
            style={{ width: 200, height: 200 }}
          />
        </View>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "white",
    alignItems: "center",
    justifyContent: "center",
  },
});

export default ImageUploadScreenExpo;

调试补充(编辑内容)

我添加了以下代码获取到了有效日志:

console.log(request.body.formData);
for (let key in request.body.formData) {
  console.log(key);
  console.log(request.body.formData[key]);
}

输出结果:

{ _parts: [ [ 'image', [Object] ] ] }
_parts
[
  [
    'image',
    {
      uri: 'file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252Ftestapp-cac352d9-fc7a-41dc-9f00-ac0cc0ed4542/ImagePicker/3005453c.jpeg',
      type: 'image/jpeg',
      name: 'image.jpeg'
    }
  ]
]

输出里看不到图片数据,所以图片没保存也在意料之中,希望有人能指明后续调试方向。


Wireshark抓包结果

我用Wireshark捕获了TCP流,发现请求中没有图片数据,且表单数据被错误编码为JSON格式,HTTP头声明的Content-Type为application/json而非正确的multipart/form-data。

请求内容

POST /upload HTTP/1.1
accept: application/json, text/plain, */*
Content-Type: application/json
Content-Length: 320
Host: 192.168.0.232:3000
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/4.9.2

{"formData":{"_parts":[["image",{"uri":"file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252Fpicky-cac352d9-fc7a-41dc-9f00-ac0cc0ed4542/ImagePicker/740ec546-cf6d-40fd-982d-88722cafb53c.jpeg","type":"image/jpeg","name":"image.jpeg"}]]},"config":{"headers":{"Content-Type":"multipart/form-data"}}}

响应内容

HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8
Content-Length: 34
ETag: W/"22-or0YmP0zqfrLr/aFLTWZuX2LMys"
Date: Tue, 12 Dec 2023 15:39:05 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"message":"image upload success"}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 08:35:08