首页 > 其他分享 >文件的上传与下载

文件的上传与下载

时间:2023-08-05 21:45:00浏览次数:27  
标签:文件 const res filename return file path 上传 下载

单文件上传

客户端

前端通过 <input type="file"> 获取 File 对象,通过 FormData 二进制传输。

<template>
  <el-button type='success' :icon='Search' @click='selectFile'>选择文件</el-button>
  <el-button type='primary' :disabled='disabled' @click='upload'>
    上传
    <el-icon class='el-icon--right'>
      <Upload />
    </el-icon>
  </el-button>
  <br />
  <el-tag type='info' v-if='file'>{{ file.name }}</el-tag>
  <input type='file' class='hidden' ref='fileEl' @change='fileChange' />
</template>
const file = ref(null);
const disabled = computed(() => !file.value);
const fileEl = ref(null);
const selectFile = () => {
    fileEl.value.click();
};
const fileChange = () => {
    file.value = fileEl.value.files[0];
};

const upload = () => {
    if (!file.value) {
        return ElMessage.error('请选择文件');
    }
    singleUpload({ file: file.value }).then(res => {
        const data = res.data;
        ElMessage({
            message: `${data.filename} 上传成功!`,
            type: 'success'
        });
    });
};

服务端

借助 multer 中间件,会将文件暂缓存到指定目录中,再通过文件流写入指定路径。

const multer = require('multer');
const upload = multer({
    dest: 'uploads/'
});
// router
router.post('/single', upload.single('file'), async function(req, res, next) {
    const file = req.file;
    const filename = file.originalname;
    const path = `${uploadDir}/${filename}`;
    try {
        await fileUtils.writeStream(file.path, path);
        res.send(ResponseVo.success({ path, filename }));
    } catch (err) {
        res.send(ResponseVo.error(err));
    }
});

// fileUtils
function writeStream(sourcePath, targetPath) {
    return new Promise((resolve, reject) => {
        try {
            const readStream = fs.createReadStream(sourcePath), writeStream = fs.createWriteStream(targetPath);
            readStream.pipe(writeStream);
            readStream.on('end', () => {
                resolve();
                fs.unlinkSync(sourcePath);
            });
        } catch (err) {
            reject(err);
        }
    });
}

多文件上传

客户端中 <input type="file" multiple> 增加 multiple 属性能让浏览器一次性选取多个文件。

服务端中再对接收的 File 数组对象依次进行单文件输入。

Base64上传

客户端

File 对象转换为 Base64,以字符的方式传输。

const fileChange = async () => {
    const file = fileEl.value.files[0];
    base64Str.value = await covertFile2Base64(file);
    filename = file.name;
};

const upload = () => {
    if (!base64Str.value) {
        return ElMessage.error('请选择文件');
    }
    base64Upload({ file: base64Str.value, filename }).then(res => {
        const data = res.data;
        ElMessage({
            message: `${data.filename} 上传成功!`,
            type: 'success'
        });
    });
};

function covertFile2Base64(file): Promise<String> {
    return new Promise(resolve => {
        const fileReader = new FileReader();
        fileReader.readAsDataURL(file);
        fileReader.onload = (ev) => {
            resolve(ev.target.result);
        };
    });
}

服务端

router.post('/base64', upload.none(), async function(req, res, next) {
    const fileBuffer = bufferUtils.covertBase64ToFileBuffer(req.body.file), filename = req.body.filename,
        spark = new SparkMD5.ArrayBuffer(), suffix = shared.getFileExtendingName(filename);
    spark.append(fileBuffer);
    const path = `${uploadDir}/${spark.end()}.${suffix}`;
    const exists = fileUtils.fileExists(path);
    if (exists) {
        return res.send(ResponseVo.success({ path, filename }, 'exists'));
    }
    try {
        await fileUtils.writeFile(fileBuffer, path);
        res.send(ResponseVo.success({ path, filename }));
    } catch (err) {
        res.send(ResponseVo.error(err));
    }
});

// fileUtils
function writeFile(buffer, targetPath) {
    return new Promise((resolve, reject) => {
        fs.writeFile(targetPath, buffer, (err) => {
            if (err) {
                reject(err);
                return;
            }
            resolve();
        });
    });
}

大文件分片上传

客户端

将文件分割成若干个小文件,并予以特定的命名,待小文件全部上传后在服务端中进行文件合并。

const fileChange = async () => {
    file.value = fileEl.value.files[0];
    hash = `${file.value.name}_${Date.now()}`;
};

const upload = () => {
    if (!file.value) {
        return ElMessage.error('请选择文件');
    }
    const chunks = splitChunks(file.value);
    Promise.all(chunks.map(chunk => chunkUpload(chunk))).then(() => {
        mergeChunk({
            hash,
            filename: file.value.name
        }).then(res => {
            const data = res.data;
            ElMessage({
                message: `${data?.filename} 上传成功!`,
                type: 'success'
            });
        });
    });
};

interface IChunk {
    chunk: Blob,
        filename: String;
}

const splitChunks = (file): Array<IChunk> => {
    const chunkSize = 1024 * 1024 * 2,
        count = Math.ceil(file.size / chunkSize),
        chunks: Array<IChunk> = [];
    let index = 0;
    while (index < count) {
        chunks.push({
            chunk: file.slice(index * chunkSize, (index + 1) * chunkSize),
            filename: `${index}_${hash}`
        });
        index++;
    }
    return chunks;
};

服务端

// 上传文件切片
router.post('/chunk', upload.single('chunk'), async function(req, res, next) {
    const file = req.file, buffer = bufferUtils.covertBase64ToFileBuffer(file), filenameStr = req.body.filename,
        spark = new SparkMD5.ArrayBuffer();
    const index = filenameStr.indexOf('_');
    const idx = filenameStr.slice(0, index);
    const filename = filenameStr.slice(index + 1, filenameStr.length);
    spark.append(buffer);
    const hash = spark.end();

    const dirPath = `${uploadDir}/${filename}`;
    fileUtils.mkDir(dirPath);

    const path = `${dirPath}/${idx}_${hash}`;
    const exists = fileUtils.fileExists(path);
    if (exists) {
        return res.send(ResponseVo.success({ path, filename: hash }, 'exists'));
    }
    try {
        await fileUtils.writeStream(file.path, path);
        res.send(ResponseVo.success());
    } catch (err) {
        res.send(ResponseVo.error(err));
    }
});

// 合并切片
router.post('/merge', upload.none(), async function(req, res, next) {
    const hash = req.body.hash;
    const filename = req.body.filename;
    const dirPath = `${uploadDir}/${hash}`;
    try {
        const fileList = fileUtils.readDir(dirPath).sort((a, b) => {
            function getSort(str) {
                return parseInt(str.split('_')[0]);
            }

            return getSort(a) - getSort(b);
        }).map(file => `${dirPath}/${file}`);
        const path = `${uploadDir}/${filename}`;
        fileUtils.mergeFile(path, fileList);
        fileUtils.removeDir(dirPath);
        res.send(ResponseVo.success({
            path, filename
        }));
    } catch (err) {
        res.send(ResponseVo.error(err));
    }
});

// fileUtils
function readDir(path) {
    if (!fs.existsSync(path)) return [];
    return fs.readdirSync(path);
}

function mergeFile(targetPath, fileList) {
    fileList.forEach(filePath => {
        fs.appendFileSync(targetPath, fs.readFileSync(filePath));
    });
}

function removeDir(path) {
    const list = readDir(path);
    list.forEach(file => {
        const filePath = `${path}/${file}`;
        fs.unlinkSync(filePath);
    });
    fs.rmdirSync(path);
}

文件下载

使用 Express 提供的 res.download() 可以很方便的自动识别文件类型,返回对应的格式。

router.get('/', function(req, res, next) {
    const filename = req.query.file;
    const path = `${downloadDir}/${filename}`;
    const exists = fileUtils.fileExists(path);
    if (!exists) return res.send(ResponseVo.error('file is not exists'));
    try {
        res.download(path);
    } catch (err) {
        res.send(ResponseVo.error(err));
    }
});

二进制流下载

服务端

在响应头中设置 application/octet-stream 等特定的属性,即可让请求返回二进制流。

router.get('/stream', async function(req, res, next) {
    const filename = req.query.file;
    const path = `${downloadDir}/${filename}`;
    const exists = fileUtils.fileExists(path);
    if (!exists) return res.send(ResponseVo.error('file is not exists'));
    try {
        const data = await fileUtils.readFile(path);
        res.set('Content-Type', 'application/octet-stream');
        res.set('Content-Disposition', `attachment;filename=${filename}`);
        res.end(data);
    } catch (err) {
        res.send(ResponseVo.error(err));
    }
});

客户端

客户端接收数据时需预先约定数据接方式,再通过 a 标签的 download 属性触发下载。

// axios
export function streamDownload(filename) {
  return axios({
    url: `/download/stream?file=${filename}`, method: 'get', responseType: 'blob'
  });
}

// 数据拼装
const handleStreamDownload = () => {
    const filename = 'demo.xlsx';
    streamDownload(filename).then(res => {
        const objectUrl = URL.createObjectURL(res);
        const a = document.createElement('a');
        a.href = objectUrl;
        a.setAttribute('download', filename);
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
    });
};

标签:文件,const,res,filename,return,file,path,上传,下载
From: https://www.cnblogs.com/qingzhao/p/17608706.html

相关文章

  • SERP免费推广普惠版正式提供下载
    保留核心的客户关系管理功能+手机端。永久免费,无时间限制,无用户数限制!......
  • python文件操作
    Python文件操作目录Python文件操作1文件的编码1.1背景1.2总结1.2.1什么是编码?1.2.2为什么需要使用编码?2文件的读取2.1什么是文件2.2文件操作的内容2.3文件的操作步骤2.3.1open()打开函数2.3.2读操作相关方法最后通过close(),关闭文件对象,也就是关闭对文件的占用。文件......
  • Spring源码之XML文件中Bean标签的解析1
    读取XML文件,创建对象xml文件里包含Bean的信息,为了避免多次IO,需要一次性读取xml文件中所有bean信息,加入到Spring工厂。读取配置文件newClassPathResource("applicationContext.xml")ClassPathResource是Spring封装的一个类型;Resource接口:可以读取相关资源文件的内容获得......
  • 文件系统与磁盘分区
    磁盘分区里有MBR分区格式,GPT分区格式Windows常用的分区格式有三种,分别是FAT16,FAT32,NTFS,格式。Linux操作系统里有Ext2,Ext3,Linuxswap和VFAT四种格式。Linux版本CentOS7一般必须要有哪几个分区:根分区(/)主分区,交换分区,/boot分区,/home分区,扩展分区,逻辑分、硬盘格式化可以选择多......
  • 如何通过gRPC传输文件
    在gRPC中,可以通过将文件分割成多个小块,然后使用流式RPC将这些小块发送到服务器来传输文件。以下是一个简单的示例,展示了如何在gRPC中实现文件传输。首先,我们需要定义一个服务来处理文件传输。在.proto文件中,我们可以定义一个UploadFile服务,它接收一个流式的Chunk消息,并返回一个Up......
  • 4.文件和目录操作相关的命令
    一、文件操作命令1.显示文件命令1.1cat命令①cat的语法格式:cat[选项][文件]特点:①cat命令可以用来查看文件内容、创建文件、文件合并、追加文件内容等功能②cat会一次显示所有的内容,适合查看内容较少的文本文件②cat的使用常用的参数及解释见下:1、catfil......
  • 文件改名:文件改名如何批量插入时间
    在工作和学习里,我们都需要用到文件,也时常要对文件进行改名,以便更好的分类与管理。首先我们进入“文件批量改名高手”。进入操作页面后,点击“添加文件”,将需要修改时间文件名的文件选择“打开”、导入完毕后,我们在文件名中选择:“插入”,在“插入内容”里填写想要的日期。在上面的文件......
  • linux怎么查看文件夹多大
    方法:1、利用ls命令查看,该命令可将结果以KB、MB等为单位进行显示,语法为“ls-ll”或“ls-lh”;2、利用“du-h–max-depth=1*”命令,该命令可查看当前目录下的各个文件和文件夹的大小;3、利用“du-sh”命令,可查看当前文件夹的总大小;4、利用“du-h–max-depth=0*”命令,可查看直接......
  • ITK在C++文件里面,可以这样调用开旁路的函数
    问题:如果直接在c++文件引入开旁路函数POM_AM__set_application_bypass,是编译不通过的(PS:好像是因为开旁路函数是用C写的,和C++不兼容,具体也不是很懂的,有懂的大佬,可以帮忙评论解答下) 解决方法:在c++文件前面加上这行extern"C"intPOM_AM__set_application_bypass(logicalbypa......
  • wget设置UA下载文件
    背景许多的HTTP\HTTPS站点提供的文件下载服务并不允许wget访问,或者大文件之类的禁止wget访问,例如限制iso之类的格式,或者特定路径。或者有一些资源并不允许空ref访问,例如下载某些页面的图片。限制wget下载iso估计是总有无聊的人浪费带宽下载测速吧。方法以清华大学开源软件镜像站为......