Next.js API路由转发文件至Strapi上传接口失败,Strapi返回“Files are empty”错误
Next.js API路由转发文件至Strapi上传接口失败,Strapi返回“Files are empty”错误
看起来你卡在这个文件转发问题上已经两周了,太折磨人了!我帮你梳理下可能的问题点和解决方案,应该能帮你搞定这个恼人的错误。
可能的问题原因及对应解决方案
1. Formidable解析后的文件结构不符合预期
你代码里直接用了files.files.filepath,但Formidable解析文件后的结构可能和你想的不一样:如果前端React Dropzone上传的是单个文件,files.files可能是一个对象;如果是多文件上传,它会变成数组。
先在代码里加个日志确认实际结构:
console.log('Formidable解析后的文件结构:', files);
如果输出是类似{ files: [File] }的数组格式,就得调整取文件的方式:
const targetFile = Array.isArray(files.files) ? files.files[0] : files.files; if (!targetFile) { return res.status(400).json({ error: '未找到上传的文件' }); }
之后用targetFile.filepath来创建文件流就对了。
2. Formidable临时文件被过早删除
Formidable默认会把上传文件存在系统临时目录,并且在请求结束后自动删除。当你用fs.createReadStream读取文件并通过fetch转发时,可能文件已经被删掉了,导致Strapi接收到空内容。
解决方法是修改Formidable的配置,关闭自动删除并指定自定义临时目录:
const form = new formidable.IncomingForm({ keepExtensions: true, // 保留文件扩展名 autoDelete: false, // 禁止自动删除临时文件 uploadDir: './tmp', // 指定自定义临时目录(记得提前在项目根目录创建这个文件夹) });
文件转发完成后,记得手动删除临时文件避免堆积:
// 在获取imageData之后执行 fs.unlinkSync(targetFile.filepath);
3. 文件流传递的潜在问题
有时候直接用文件流传递可能因为流未正确读取就被发送,导致Strapi接收不到内容。对于头像这类小文件,可以改用读取文件Buffer的方式传递,更稳定:
// 替代createReadStream的方式 const fileBuffer = fs.readFileSync(targetFile.filepath); formData.append('files', fileBuffer, targetFile.originalFilename);
4. 确认Strapi上传参数的正确性
再核对下传给Strapi的几个关键参数:
ref: 'plugin::users-permissions.user':确认Strapi用户模型的UID是否正确(默认是这个,若自定义过要对应修改)refId: user_id:确保user_id是有效的用户ID,且当前JWT用户有权限修改该用户field: 'profile_picture':确认用户模型中确实存在名为profile_picture的媒体类型字段
修改后的完整API路由代码示例
整合了上述修改后的代码,你可以直接参考:
import formidable from 'formidable'; import FormData from 'form-data'; import fs from 'fs'; import path from 'path'; // 确保临时目录存在 const tmpDir = path.join(process.cwd(), 'tmp'); if (!fs.existsSync(tmpDir)) { fs.mkdirSync(tmpDir); } export default async function handler(req, res) { const { cookies: { jwt }, query: { user_id } } = req; if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }); } const form = new formidable.IncomingForm({ keepExtensions: true, autoDelete: false, uploadDir: tmpDir, }); try { const { fields, files } = await new Promise((resolve, reject) => { form.parse(req, (err, fields, files) => { if (err) reject(err); else resolve({ fields, files }); }); }); // 处理用户字段更新 if (Object.keys(fields).length) { const resp = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/users/${user_id}`, { method: "PUT", headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" }, body: JSON.stringify(fields) }); const data = await resp.json(); console.log('Fields data', data); } // 处理文件上传 const targetFile = Array.isArray(files.files) ? files.files[0] : files.files; if (targetFile) { console.log('准备上传文件:', targetFile.originalFilename); const formData = new FormData(); // 用Buffer方式传递文件 const fileBuffer = fs.readFileSync(targetFile.filepath); formData.append('files', fileBuffer, targetFile.originalFilename); formData.append('ref', 'plugin::users-permissions.user'); formData.append('refId', user_id); formData.append('field', 'profile_picture'); const imageResp = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/upload`, { method: "POST", headers: { Authorization: `Bearer ${jwt}`, // 不要手动设置Content-Type,FormData会自动生成带boundary的正确值 }, body: formData }); const imageData = await imageResp.json(); console.log('imageData', imageResp.status, imageData); // 删除临时文件 fs.unlinkSync(targetFile.filepath); } res.status(200).json({ fields, files }); } catch (err) { console.error('处理请求出错:', err); res.status(500).json({ error: err.message }); } } export const config = { api: { bodyParser: false, }, };
备注:内容来源于stack exchange,提问作者Kofi Talent
相关产品推荐
相关产品推荐

