问:(目前代码暂不可用)
在大文件上传过程中遇到了问题:
下面是我的前端代码:
<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 chunkconst 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)
结合我的前后端代码分析哪里导致的
回答:
你的问题出在后端代码,具体来说是文件保存和合并的逻辑。**问题分析:**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 });}
});