MST

星途 面试题库

面试题:Vue 表单绑定文件上传功能的深度优化与跨域处理

假设要在一个大型 Vue 项目中实现高并发的文件上传功能,同时存在跨域问题。请详细说明你会采取哪些优化策略,比如如何优化文件切片上传、断点续传等,以及如何有效处理跨域问题,包括但不限于 CORS、代理服务器等方式的选择与配置。请给出完整的实现方案和关键代码结构。
25.8万 热度难度
前端开发Vue

知识考点

AI 面试

面试题答案

一键面试

优化策略

  1. 文件切片上传
    • 原理:将大文件分割成多个小的切片,分别上传这些切片,减少单个请求的数据量,提高上传成功率和并发处理能力。
    • 实现:在前端利用 Blob.slice() 方法对文件进行切片。例如:
function sliceFile(file, sliceSize) {
    const slices = [];
    let start = 0;
    while (start < file.size) {
        const end = Math.min(start + sliceSize, file.size);
        slices.push(file.slice(start, end));
        start = end;
    }
    return slices;
}
  1. 断点续传
    • 原理:记录已经上传成功的切片,当上传中断后,只需要上传未成功的切片。
    • 实现
      • 在前端,每次切片上传成功后,记录切片的相关信息(如切片序号、偏移量等)。可以使用 localStorage 或者在 Vuex 中存储。例如:
// 在 Vuex 的 mutation 中记录已上传切片
const mutations = {
    SET_UPLOADED_SLICE(state, { sliceIndex, offset }) {
        state.uploadedSlices[sliceIndex] = offset;
    }
};
 - 在后端,需要记录每个文件已成功上传的切片,当下次请求时,判断哪些切片还未上传。例如在 Node.js 中使用 `fs` 模块记录切片上传状态:
const fs = require('fs');
const path = require('path');

function saveUploadedSliceInfo(fileId, sliceIndex, offset) {
    const infoFilePath = path.join(__dirname, 'uploadInfo', `${fileId}.json`);
    let info = {};
    try {
        info = JSON.parse(fs.readFileSync(infoFilePath, 'utf8'));
    } catch (e) {}
    info[sliceIndex] = offset;
    fs.writeFileSync(infoFilePath, JSON.stringify(info));
}
  1. 处理跨域问题
    • CORS(跨域资源共享)
      • 原理:服务端通过设置响应头来允许跨域请求。
      • 配置:在 Node.js 中使用 cors 中间件。首先安装 corsnpm install cors,然后在 Express 应用中配置:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
  • 代理服务器
    • 原理:在开发环境下,通过配置代理服务器,将跨域请求转发到目标服务器,这样在浏览器看来,请求是同源的。
    • 配置:在 Vue 项目中,可以通过 vue - clivue.config.js 配置代理。例如:
module.exports = {
    devServer: {
        proxy: {
            '/api': {
                target: 'http://target - server - url',
                changeOrigin: true,
                pathRewrite: {
                    '^/api': ''
                }
            }
        }
    }
};

完整实现方案

  1. 前端
    • Vue 组件结构
<template>
    <div>
        <input type="file" @change="handleFileChange">
        <button @click="startUpload">开始上传</button>
    </div>
</template>

<script>
export default {
    data() {
        return {
            file: null,
            sliceSize: 1024 * 1024, // 1MB 切片大小
            uploadedSlices: {}
        };
    },
    methods: {
        handleFileChange(e) {
            this.file = e.target.files[0];
        },
        async startUpload() {
            const slices = sliceFile(this.file, this.sliceSize);
            for (let i = 0; i < slices.length; i++) {
                if (this.uploadedSlices[i]) {
                    continue;
                }
                const formData = new FormData();
                formData.append('file', slices[i]);
                formData.append('sliceIndex', i);
                try {
                    const response = await this.$axios.post('/api/upload', formData);
                    this.$store.commit('SET_UPLOADED_SLICE', { sliceIndex: i, offset: response.data.offset });
                } catch (error) {
                    console.error('上传切片失败', error);
                }
            }
        }
    }
};
function sliceFile(file, sliceSize) {
    const slices = [];
    let start = 0;
    while (start < file.size) {
        const end = Math.min(start + sliceSize, file.size);
        slices.push(file.slice(start, end));
        start = end;
    }
    return slices;
}
</script>
  1. 后端(以 Node.js + Express 为例)
const express = require('express');
const cors = require('cors');
const app = express();
const multer = require('multer');
const path = require('path');
const fs = require('fs');

app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

const storage = multer.memoryStorage();
const upload = multer({ storage: storage });

app.post('/upload', upload.single('file'), (req, res) => {
    const { sliceIndex } = req.body;
    const fileId = 'your - unique - file - id';
    const uploadDir = path.join(__dirname, 'uploads');
    if (!fs.existsSync(uploadDir)) {
        fs.mkdirSync(uploadDir);
    }
    const filePath = path.join(uploadDir, `${fileId}_${sliceIndex}`);
    fs.writeFileSync(filePath, req.file.buffer);
    saveUploadedSliceInfo(fileId, sliceIndex, req.file.buffer.length);
    res.json({ offset: req.file.buffer.length });
});

function saveUploadedSliceInfo(fileId, sliceIndex, offset) {
    const infoFilePath = path.join(__dirname, 'uploadInfo', `${fileId}.json`);
    let info = {};
    try {
        info = JSON.parse(fs.readFileSync(infoFilePath, 'utf8'));
    } catch (e) {}
    info[sliceIndex] = offset;
    fs.writeFileSync(infoFilePath, JSON.stringify(info));
}

const port = 3000;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

以上代码实现了文件切片上传、断点续传以及跨域问题的处理,在实际应用中,还需要考虑更多的细节,如文件合并、错误处理等。