如何用Axios/Node.js的multipart/form-data PUT本地文件?
用Node.js + Axios实现PlayCanvas的CURL文件上传请求
原CURL请求
需要转换的可正常运行的CURL命令:
curl --request PUT \ --url https://playcanvas.com/api/assets/ASSET_ID \ --header 'Authorization: Bearer TOKEN_HERE' \ --header 'Content-Type: multipart/form-data' \ --form file=@/Users/ilia/Development/playcanvas-rivalry/files/test.script.js \ --form branchId=BRANCH_ID
用户尝试的代码
const fs = require('fs') const path = require('path') const router = require('express').Router({ mergeParams: true }) const axios = require('axios') const buildUrl = require('build-url') const FormData = require('form-data') router.put('/:id', async (req, res, next) => { const url = buildUrl(process.env.BASE_URL, { path: `/projects/${process.env.PROJECT_ID}/assets/${req.params.id}`, }) const { file_name } = req.query const filePath = path.join(__dirname, `../files/${file_name}`) try { const formData = new FormData() formData.append('branchId', req.query.branchId || process.env.BRANCH_ID) formData.append('file', fs.readFile(filePath)) const update = await axios.put(url, formData, { headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'multipart/form-data', // ...formData.getHeaders() }, }) console.log(update); res.send(update) } catch (error) { res.send(error) } })
用户认为问题出在fs.readFile,尝试过fs.createReadStream但无效,需解决如何用Node.js + Axios复制该CURL请求。
解决方案
错误分析
fs.readFile异步问题:fs.readFile是异步方法,直接传入会返回Promise而非文件内容,无法被FormData正确解析;即使改用fs.createReadStream,错误的头部设置也会导致请求失败。- Content-Type头部错误:手动设置
'Content-Type: multipart/form-data'会丢失FormData自动生成的boundary分隔符,服务端无法解析表单数据。 - URL路径不匹配:原CURL的API路径是
/api/assets/ASSET_ID,用户代码中路径为/projects/${PROJECT_ID}/assets/${id},不符合PlayCanvas API的实际路径要求。
修正后的代码
const fs = require('fs') const path = require('path') const router = require('express').Router({ mergeParams: true }) const axios = require('axios') const buildUrl = require('build-url') const FormData = require('form-data') router.put('/:id', async (req, res, next) => { // 修正URL路径,匹配原CURL的API结构 const url = buildUrl(process.env.BASE_URL, { path: `/api/assets/${req.params.id}`, }) const { file_name, branchId } = req.query const filePath = path.join(__dirname, `../files/${file_name}`) try { const formData = new FormData() // 添加branchId字段 formData.append('branchId', branchId || process.env.BRANCH_ID) // 使用流式上传文件,同时指定文件名(和CURL的@效果一致) formData.append('file', fs.createReadStream(filePath), { filename: path.basename(filePath) }) const update = await axios.put(url, formData, { headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, // 合并FormData自动生成的头部(包含正确的boundary) ...formData.getHeaders() }, }) res.status(200).json(update.data) } catch (error) { // 返回详细错误信息便于调试 res.status(error.response?.status || 500).json({ message: error.message, details: error.response?.data || '无详细错误信息' }) } })
关键修正点
- 用
fs.createReadStream替代fs.readFile,流式上传文件并指定filename,确保服务端识别文件名。 - 移除手动设置的
Content-Type,改用...formData.getHeaders()让FormData自动生成包含boundary的正确头部。 - 修正URL路径为原CURL的
/api/assets/ASSET_ID结构,匹配PlayCanvas API要求。 - 优化错误处理,返回更详细的错误信息,方便调试。
内容的提问来源于stack exchange,提问作者ilrein
相关产品推荐
相关产品推荐

