Cloudinary文件上传报错Cannot read property 'public_id' of undefined
Cloudinary上传文件时报错
Cannot read property 'public_id' of undefined修复方案 问题现象
首次对接Cloudinary实现文件上传,排查完前置问题后上传功能失效,控制台在uploadResult逻辑处抛出如下错误:
Cannot read property 'public_id' of undefined
前端控制台可正常打印获取到的文件对象,但接口请求触发上述报错,无法完成文件上传和新闻数据入库流程。
涉及的问题代码
后端路由实现
router.post("/", verifyTokenAndAuthorization, async (req,res)=>{ const {img,ressort, theme, title, content} = req.body; try{ const uploadResult = await cloudinary.uploader.upload(img,{ folder: MainNews }, (error, img)=>{ if(error){ console.log(error) } console.log("* Same image, uploaded with a custom public_id"); console.log("*" + img.public_id); console.log("* " + img.url); waitForAllUploads("MainNews", error, img); }); const newMainNews = new MainNews({ ressort, theme, title, content, img: { public_id: uploadResult.public_id, url: uploadResult.secure_url } }); const savedMainNews = await newMainNews.save(); res.status(200).json(savedMainNews); } catch(error){ res.status(403) throw new Error("Action failed"); } });
Redux Slice异步逻辑
export const createMainNews = createAsyncThunk("mainNews/create", async (mainnewsData, thunkAPI)=>{ try{ const token = thunkAPI.getState().auth.user.accessToken; return await mainnewsService.createMainNews(mainnewsData, token); } catch(error){ const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString() return thunkAPI.rejectWithValue(message); } })
Redux Service请求层
const API_URL = "http://localhost:5000/api/mainNews/"; const createMainNews = async (mainnewsData, token)=>{ const config = { headers: { 'Content-Type': `multipart/form-data`, token: `Bearer ${token}`, }, } const response = await axios.post(API_URL, mainnewsData, config); return response.data; }
前端表单实现
const [formdata, setFormdata] = useState( { ressort:"", theme:"", title:"", content:"", } ) const {ressort,theme,title,content} = formdata; const [fileData, setFileData] = useState({ img:"" }) const {img} = fileData; const fileInput = useRef(img); const fileChange = (e)=>{ console.log(fileInput.current.files); setFileData(fileInput.current.files[0]) } console.log(fileData); //works and it is a file const handleChange = (e)=>{ setFormdata((prevState)=>({ ...prevState, [e.target.name]: e.target.value })) } const onSubmit = (e)=>{ e.preventDefault(); const data = new FormData(); data.append("img", fileData); data.append("ressort", formdata.ressort); data.append("theme", formdata.theme); data.append("title", formdata.title); data.append("content", formdata.content) const mainnewsData ={ data, } dispatch(createMainNews(mainnewsData)); }
问题根因
共有3处直接触发报错的代码问题:
- 前端传参格式错误:组装完
FormData实例后额外包了一层{data: data}对象传给axios,导致后端收到的请求体不是标准FormData格式,无法解析到img字段,最终传给Cloudinary的img参数实际为undefined。 - 后端缺少文件解析中间件:
multipart/form-data格式的文件上传请求,没有配置multer等对应中间件做解析,就算前端传参正确,也无法从请求中拿到上传的文件对象。 - Cloudinary调用方式冲突:同时给
cloudinary.uploader.upload传入回调函数、又用await接收返回值,混用写法会导致await拿到的uploadResult为undefined,后续访问public_id属性直接报错。
修复步骤
按顺序修改对应代码即可:
1. 修正前端传参逻辑
去掉FormData外层的多余包装,直接把FormData实例作为请求参数传递;删除手动设置的Content-Type请求头,浏览器会自动为multipart请求带上正确的boundary标识,避免后端解析失败:
// service层修改 const createMainNews = async (mainnewsData, token)=>{ const config = { headers: { token: `Bearer ${token}`, }, } const response = await axios.post(API_URL, mainnewsData, config); return response.data; } // 前端提交逻辑修改 const onSubmit = (e)=>{ e.preventDefault(); const data = new FormData(); data.append("img", fileData); data.append("ressort", formdata.ressort); data.append("theme", formdata.theme); data.append("title", formdata.title); data.append("content", formdata.content) // 直接传入FormData实例,去掉多余包装 dispatch(createMainNews(data)); }
2. 后端配置multer中间件解析文件
安装multer依赖,在路由文件中引入并配置内存存储(无需把文件存到本地服务器,直接转码后传给Cloudinary即可):
import multer from 'multer'; const storage = multer.memoryStorage(); const upload = multer({ storage: storage }); // 路由加上single文件解析中间件,匹配前端append的img字段 router.post("/", verifyTokenAndAuthorization, upload.single('img'), async (req,res)=>{ // 上传的文件存在req.file中,其他文本字段仍从req.body获取 const {ressort, theme, title, content} = req.body; const imgFile = req.file; // 后续上传逻辑 })
3. 修正Cloudinary上传调用逻辑
去掉回调函数,统一用await接收返回结果;上传前把multer拿到的文件buffer转成dataURI格式传给Cloudinary,注意folder参数如果是固定字符串需要加引号:
// 工具函数:把文件buffer转成Cloudinary支持的dataURI格式 const fileToDataUri = (file) => `data:${file.mimetype};base64,${file.buffer.toString('base64')}`; router.post("/", verifyTokenAndAuthorization, upload.single('img'), async (req,res)=>{ const {ressort, theme, title, content} = req.body; try{ // 仅用await接收返回值,不混用回调 const uploadResult = await cloudinary.uploader.upload(fileToDataUri(req.file),{ folder: "MainNews" }); const newMainNews = new MainNews({ ressort, theme, title, content, img: { public_id: uploadResult.public_id, url: uploadResult.secure_url } }); const savedMainNews = await newMainNews.save(); res.status(200).json(savedMainNews); } catch(error){ res.status(500).json({message: "上传操作失败"}) } });
内容的提问来源于stack exchange,提问作者Roman
相关产品推荐
相关产品推荐

