问:
再打文件上传过程中遇到了问题:
javascript
下面是我的前端代码:
<script setup lang='ts'>
import { ref } from "vue";
import api from '@/plugins/axios.js';
import { BASE_URL } from '../plugins/baseUrl'; // 确保路径正确
const selectedFile = ref(null);
const responseMessage = ref('');
const chunkSize = 1024 * 1024; // 1 MB per chunk
const handleFileChange = (event) => {
selectedFile.value = event.target.files[0]; // 获取选中的文件
};
const uploadFile = async () => {
console.log('进入了uploadFile函数');
if (!selectedFile.value) {
alert('请选择一个文件!');
return;
}
const totalChunks = Math.ceil(selectedFile.value.size / chunkSize);
let currentChunk = 0;
while (currentChunk < totalChunks) {
const start = currentChunk * chunkSize;
const end = Math.min(start + chunkSize, selectedFile.value.size);
const fileChunk = selectedFile.value.slice(start, end); // 获取当前分片
const formData = new FormData();
formData.append('file', fileChunk); // 将分片添加到 FormData 对象
formData.append('chunkNumber', currentChunk + 1); // 当前分片编号
formData.append('totalChunks', totalChunks); // 总分片数
try {
console.log(`上传第 ${currentChunk + 1} 个分片`);
const response = await api.post(`${BASE_URL}/upload`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
console.log(response, 'response');
if (response.data.success) {
currentChunk++; // 仅在当前分片成功上传后增加计数
} else {
throw new Error('上传失败');
}
} catch (error) {
responseMessage.value = '上传失败: ' + error.response?.data?.message || error.message;
return; // 在发生错误时停止上传
}
}
responseMessage.value = '上传成功!';
};
</script>
<template>
<div>
<h2>文件上传示例</h2>
<input type="file" @change="handleFileChange" accept="image/*" required />
<button @click="uploadFile">上传文件</button>
<div v-if="responseMessage">{{ responseMessage }}</div>
</div>
</template>
<style scoped lang='less'></style>
下面是我的后端node代码:
// 定义上传路由
app.post('/upload', upload.single('file'), (req, res) => {
console.log('接收到文件:', req.file);
console.log('分片编号:', req.body.chunkNumber);
console.log('总分片数:', req.body.totalChunks);
if (!req.file) {
return res.status(400).json({ message: '未选择文件' });
}
const totalChunks = parseInt(req.body.totalChunks);
const originalName = req.file.originalname;
if (parseInt(req.body.chunkNumber) === totalChunks) {
const mergedFilePath = path.join('uploads', originalName);
const writeStream = fs.createWriteStream(mergedFilePath);
for (let i = 1; i <= totalChunks; i++) {
const chunkFilePath = path.join('uploads', `${originalName}.part${i}`);
console.log(`检查分片文件: ${chunkFilePath}`);
// 检查分片文件是否存在
if (fs.existsSync(chunkFilePath)) {
const readStream = fs.createReadStream(chunkFilePath);
readStream.pipe(writeStream, { end: false });
readStream.on('end', () => {
fs.unlinkSync(chunkFilePath);
});
} else {
console.error(`分片文件不存在: ${chunkFilePath}`);
return res.status(400).json({ message: `分片文件不存在: ${chunkFilePath}` });
}
}
writeStream.on('finish', () => {
res.json({ message: '文件上传成功', filename: originalName });
});
writeStream.on('error', (err) => {
console.error(err);
res.status(500).json({ message: '合并分片失败' });
});
} else {
res.json({ message: '分片上传成功', chunkNumber: req.body.chunkNumber });
}
});
现在调用接口后参数:
file: (二进制)
chunkNumber: 1
totalChunks: 1
调用接口返回400:
{
"message": "分片文件不存在: uploads\\blob.part1"
}
控制台报错信息:
进入了uploadFile函数
UploadFIle.vue:35 上传第 1 个分片
axios.js:42
POST http://localhost:3001/upload 400 (Bad Request)
结合我的前后端代码分析哪里导致的
回答:
javascript
你的问题出在后端代码,具体来说是文件保存和合并的逻辑。
**问题分析:**
1. **分片文件命名冲突:** 你在上传每个分片时,直接使用 `originalName.part${i}` 作为文件名保存。当 `totalChunks` 为 1 时,也就是只有一个分片的情况下,这个文件名和最终合并后的文件名 `originalName` 是一样的。
2. **合并逻辑错误:** 在 `totalChunks` 为 1 的情况下,你的代码仍然会进入分片合并的逻辑,尝试读取 `originalName.part1` 文件,但此时这个文件已经被 `multer` 中间件保存成了 `originalName`,所以会报 \来保存,例如使用 UUID 或者时间戳。
2. **优化合并逻辑:** 在 `totalChunks` 为 1 的情况下,可以直接将上传的单个文件作为最终文件,不需要进行分片合并操作。
**修改后的后端代码:**
const fs = require('fs');
const path = require('path');
const multer = require('multer');
const upload = multer({ dest: 'uploads/' }); // 使用 uploads/ 目录作为临时存储目录
// ...
app.post('/upload', upload.single('file'), (req, res) => {
// ... (获取参数部分代码不变)
const totalChunks = parseInt(req.body.totalChunks);
const originalName = req.file.originalname;
// 使用临时文件名保存分片
const chunkFilePath = path.join('uploads', `${Date.now()}-${req.file.originalname}.part${req.body.chunkNumber}`);
fs.renameSync(req.file.path, chunkFilePath);
if (parseInt(req.body.chunkNumber) === totalChunks) {
// 当只有一个分片时,直接使用上传的文件
if (totalChunks === 1) {
const finalFilePath = path.join('uploads', originalName);
fs.renameSync(chunkFilePath, finalFilePath);
return res.json({ message: '文件上传成功', filename: originalName });
}
// ... (多个分片合并逻辑,注意修改分片文件路径)
} else {
res.json({ message: '分片上传成功', chunkNumber: req.body.chunkNumber });
}
});
**修改说明:**
1. **临时文件名:** 使用 `Date.now()}-${req.file.originalname}.part${req.body.chunkNumber}` 作为临时文件名,确保唯一性。
2. **分片合并逻辑:** 在 `totalChunks` 为 1 时,直接将上传的单个文件重命名为最终文件名,并跳过分片合并逻辑。
3. **分片文件路径:** 在分片合并逻辑中,需要将分片文件路径修改为新的临时文件名。
通过以上修改,你的代码就能正常处理单个分片上传的情况了
下面是完整代码:
// 定义上传路由
app.post('/upload', upload.single('file'), (req, res) => {
console.log('接收到文件:', req.file);
console.log('分片编号:', req.body.chunkNumber);
console.log('总分片数:', req.body.totalChunks);
if (!req.file) {
return res.status(400).json({ message: '未选择文件' });
}
const totalChunks = parseInt(req.body.totalChunks);
const originalName = req.file.originalname;
// 使用临时文件名保存分片
const chunkFilePath = path.join('uploads', `${Date.now()}-${req.file.originalname}.part${req.body.chunkNumber}`);
fs.renameSync(req.file.path, chunkFilePath);
if (parseInt(req.body.chunkNumber) === totalChunks) {
// 当只有一个分片时,直接使用上传的文件
if (totalChunks === 1) {
const finalFilePath = path.join('uploads', originalName);
fs.renameSync(chunkFilePath, finalFilePath);
return res.json({ message: '文件上传成功', filename: originalName });
}
const mergedFilePath = path.join('uploads', originalName);
const writeStream = fs.createWriteStream(mergedFilePath);
for (let i = 1; i <= totalChunks; i++) {
const partFilePath = path.join('uploads', `${Date.now()}-${originalName}.part${i}`);
console.log(`检查分片文件: ${partFilePath}`);
// 检查分片文件是否存在
if (fs.existsSync(partFilePath)) {
const readStream = fs.createReadStream(partFilePath);
readStream.pipe(writeStream, { end: false });
readStream.on('end', () => {
fs.unlinkSync(partFilePath);
});
} else {
console.error(`分片文件不存在: ${partFilePath}`);
return res.status(400).json({ message: `分片文件不存在: ${partFilePath}` });
}
}
writeStream.on('finish', () => {
res.json({ message: '文件上传成功', filename: originalName });
});
writeStream.on('error', (err) => {
console.error(err);
res.status(500).json({ message: '合并分片失败' });
});
} else {
res.json({ message: '分片上传成功', chunkNumber: req.body.chunkNumber });
}
});