download
属性标识文件需要下载且下载名称为test
如果有 Content-Disposition
响应头,则不需要设置download
属性就能下载,文件名在响应头里面由后端控制
此方法有同源和请求headers
鉴权的问题
window.open('xxx.zip'); location.href = 'xxx.zip';
需要注意 url 长度和编码问题
不能直接下载浏览器默认预览的文件,如txt
、图片
function downloadFile(res, Filename) { // res为接口返回数据,在请求接口的时候可进行鉴权 if (!res) return; // IE及IE内核浏览器 if ("msSaveOrOpenBlob" in navigator) { navigator.msSaveOrOpenBlob(res, name); return; } const url = URL.createObjectURL(new Blob([res])); // const fileReader = new FileReader(); 使用 Base64 编码生成 // fileReader.readAsDataURL(res); // fileReader.Onload= function() { ...此处逻辑和下面创建a标签并释放代码一致,可从fileReader.result获取href值...} const a = document.createElement("a"); a.style.display = "none"; a.href = url; a.download = Filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); // 释放blob对象 }
注意 请求发送的时候注明 respOnseType= "blob"
,如无设置则需要 new Blob的时候传入第二个参数,如
new Blob([res], { type: xhr.getResponseHeader("Content-Type") });
此方法可以解决请求headers
鉴权和下载浏览器默认直接预览的文件,并得知下载进度
type
属性file
:用户选择文件accept
属性:规定选择文件的类型上传进度:0
const app = express(); // 上传成功后返回URL地址 const resourceUrl = `http://127.0.0.1:${port}/`; // 存储文件目录 const uploadDIr = path.join(__dirname, "/upload"); // destination 设置资源保存路径,filename 设置资源名称 const storage = multer.diskStorage({ destination: async function (_req, _file, cb) { cb(null, uploadDIr); }, filename: function (_req, file, cb) { // 设置文件名 cb(null, `${file.originalname}`); }, }); const multerUpload = multer({ storage }); //设置静态访问目录 app.use(express.static(uploadDIr)); app.post("/upload", multerUpload.any(), function (req, res, _next) { // req.file 是 `avatar` 文件的信息 let urls = []; //获取所有已上传的文件 const files = req.files; if (files && files.length > 0) { //遍历生成url 集合返回给客户端 urls = files.map((item, _key) => { return resourceUrl + item.originalname; }); } return res.json({ REV: true, DATA: { url: urls, }, MSG: "成功", }); });
multiple
是否允许多个值(相关类型email
、file
)上传进度:0
上传进度:0
fileUtils
// 文件分片 function handleFileChunk(file, chunkSize) { const fileChunkList = []; // 索引值 let curIndex = 0; while (curIndex
//设置默认切片大小为5M const DefaultChunkSize = 5 * 1024 * 1024; const start = async function (bigFile) { // 生成多个切片 const fileList = handleFileChunk(bigFile, DefaultChunkSize); // 获取整个大文件的内容hash,方便实现秒传 // const cOntainerHash= await getFileHash(fileList); const cOntainerHash= await getFileHash2(bigFile); // 给每个切片添加辅助内容信息 const chunksInfo = fileList.map(({ file }, index) => ({ // 整个文件hash fileHash: containerHash, // 当前切片的hash hash: containerHash + "-" + index, // 当前是第几个切片 index, // 文件个数 fileCount: fileList.length, // 切片内容 chunk: file, // 文件总体大小 totalSize: bigFile.size, // 单个文件大小 size: file.size, })); //上传所有文件 uploadChunks(chunksInfo, bigFile.name); }; /** * * 获取全部文件内容hash * @param {any} fileList */ async function getFileHash(fileList) { console.time("filehash"); const spark = new SparkMD5.ArrayBuffer(); // 获取全部内容 const result = fileList.map((item, key) => { return getFileContent(item.file); }); try { const cOntentList= await Promise.all(result); for (let i = 0; i{ const fileReader = new FileReader(); // 读取文件内容 fileReader.readAsArrayBuffer(file); fileReader.Onload= (e) => { // 返回读取到的文件内容 resolve(e.target.result); }; fileReader.Onerror= (e) => { reject(fileReader.error); fileReader.abort(); }; }); } /** * * 上传所有的分片 * @param {any} chunks * @param {any} fileName */ async function uploadChunks(chunks, fileName) { const requestList = chunks .map(({ chunk, hash, fileHash, index, fileCount, size, totalSize }) => { //生成每个切片上传的信息 const formData = new FormData(); formData.append("hash", hash); formData.append("index", index); formData.append("fileCount", fileCount); formData.append("size", size); formData.append("splitSize", DefaultChunkSize); formData.append("fileName", fileName); formData.append("fileHash", fileHash); formData.append("chunk", chunk); formData.append("totalSize", totalSize); return { formData, index }; }) .map(async ({ formData, index }) => singleRequest({ url: "http://127.0.0.1:3000/uploadBigFile", data: formData, }) ); //全部上传 await Promise.all(requestList); } /** * 单个文件上传 */ function singleRequest({ url, method = "post", data, headers = {} }) { return new Promise((resolve) => { const xhr = new XMLHttpRequest(); xhr.open(method, url); Object.keys(headers).forEach((key) => xhr.setRequestHeader(key, headers[key])); xhr.send(data); xhr.Onload= (e) => { resolve({ data: e.target.response, }); }; }); } window.upload = { start: start, };
... import { checkFileIsMerge, chunkMerge } from "./upload"; const multiparty = require("multiparty"); const fse = require("fs-extra"); // 上传成功后返回URL地址 const resourceUrl = `http://127.0.0.1:${port}/`; // 存储文件目录 const uploadDIr = path.join(__dirname, "/upload"); //设置静态访问目录 app.use(express.static(uploadDIr)); const extractExt = (filename) => filename.slice(filename.lastIndexOf("."), filename.length); // 提取后缀名 app.post("/uploadBigFile", function (req, res, _next) { const multipart = new multiparty.Form(); multipart.parse(req, async (err, fields, files) => { if (err) { console.error(err); return res.json({ code: 5000, data: null, msg: "上传文件失败", }); } //取出文件内容 const [chunk] = files.chunk; //当前chunk 文件hash const [hash] = fields.hash; //大文件的hash const [fileHash] = fields.fileHash; //大文件的名称 const [fileName] = fields.fileName; //切片索引 const [index] = fields.index; //总共切片个数 const [fileCount] = fields.fileCount; //当前chunk 的大小 // const [size] = fields.size; const [splitSize] = fields.splitSize; //整个文件大小 const [totalSize] = fields.totalSize; const saveFileName = `${fileHash}${extractExt(fileName)}`; //获取整个文件存储路径 const filePath = path.resolve(uploadDIr, saveFileName); const chunkDir = path.resolve(uploadDIr, fileHash); // 大文件存在直接返回,根据内容hash存储,可以实现后续秒传 if (fse.existsSync(filePath)) { return res.json({ code: 1000, data: { url: `${resourceUrl}${saveFileName}` }, msg: "上传文件已存在", }); } // 切片目录不存在,创建切片目录 if (!fse.existsSync(chunkDir)) { await fse.mkdirs(chunkDir); } const chunkFile = path.resolve(chunkDir, hash); if (!fse.existsSync(chunkFile)) { await fse.move(chunk.path, path.resolve(chunkDir, hash)); } const isMerge = checkFileIsMerge(chunkDir, Number(fileCount), fileHash); if (isMerge) { //合并 await chunkMerge({ filePath: filePath, fileHash: fileHash, chunkDir: chunkDir, splitSize: Number(splitSize), fileCount: Number(fileCount), totalSize: Number(totalSize), }); return res.json({ code: 1000, data: { url: `${resourceUrl}${saveFileName}` }, msg: "文件上传成功", }); } else { return res.json({ code: 200, data: { url: `${resourceUrl}${filePath}` }, msg: "文件上传成功", }); } }); });
upload.ts
const fse = require("fs-extra"); const path = require("path"); /** * 读流,写流 * @param path * @param writeStream * @returns */ const pipeStream = (path, writeStream) => new Promise((resolve) => { const readStream = fse.createReadStream(path); readStream.on("end", () => { // fse.unlinkSync(path); resolve(null); }); readStream.pipe(writeStream); }); /** * * 合并所有切片 * @export * @param {any} { * filePath:文件路径包含后缀名 * fileHash:文件hash * chunkDir:切片存放的临时目录 * splitSize:每个切片的大小 * fileCount:文件总个数 * totalSize:文件总大小 * } * @returns */ export async function chunkMerge({ filePath, fileHash, chunkDir, splitSize, fileCount, totalSize, }) { const chunkPaths = await fse.readdir(chunkDir); //帅选合适的切片 const filterPath = chunkPaths.filter((item) => { return item.includes(fileHash); }); //数量不对,抛出错误 if (filterPath.length !== fileCount) { console.log("合并错误"); return; } // 根据切片下标进行排序,方便合并 filterPath.sort((a, b) => a.split("-")[1] - b.split("-")[1]); await Promise.all( chunkPaths.map((chunkPath, index) => { //并发写入,需要知道开始和结束位置 let end = (index + 1) * splitSize; if (index === fileCount - 1) { end = totalSize + 1; } return pipeStream( path.resolve(chunkDir, chunkPath), // 指定位置创建可写流 fse.createWriteStream(filePath, { start: index * splitSize, end: end, }) ); }) ); //删除所有切片 // fse.rmdirSync(chunkDir); // 合并后删除保存切片的目录 return filePath; } /** * * 检查切片是否可以合并 * @export * @param {any} pathName 切片存储目录 * @param {any} totalCount 大文件包含切片个数 * @param {any} hash 大文件hash * @returns */ export function checkFileIsMerge(pathName, totalCount, hash) { var dirs = []; //同步读取切片存储目录 const readDir = fse.readdirSync(pathName); //判断目录下切片数量 小于 总切片数,不能合并 if (readDir && readDir.length
这里的大文件上传有几处问题,我没有解决,留给各位思考啦
以上就是Javascript进阶之前端文件上传和下载示例详解的详细内容,更多关于Javascript前端文件上传下载的资料请关注其它相关文章!