服务器代码
const express = require('express'), fs = require('fs'), bodyParser = require('body-parser'), multiparty = require('multiparty'), SparkMD5 = require('spark-md5');/*-CREATE SERVER-*/const app = express(), PORT = 8888, HOST = 'http://127.0.0.1', HOSTNAME = `${HOST}:${PORT}`;app.listen(PORT, () => { console.log(`THE WEB SERVICE IS CREATED SUCCESSFULLY AND IS LISTENING TO THE PORT:${PORT},YOU CAN VISIT:${HOSTNAME}`);});/*-中间件-*/app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", "*"); req.method === 'OPTIONS' ? res.send('CURRENT SERVICES SUPPORT CROSS DOMAIN REQUESTS!') : next();});app.use(bodyParser.urlencoded({ extended: false, limit: '1024mb'}));/*-API-*/// 延迟函数const delay = function delay(interval) { typeof interval !== "number" ? interval = 1000 : null; return new Promise(resolve => { setTimeout(() => { resolve(); }, interval); });};// 检测文件是否存在const exists = function exists(path) { return new Promise(resolve => { fs.access(path, fs.constants.F_OK, err => { if (err) { resolve(false); return; } resolve(true); }); });};// 创建文件并写入到指定的目录 & 返回客户端结果const writeFile = function writeFile(res, path, file, filename, stream) { return new Promise((resolve, reject) => { if (stream) { try { let readStream = fs.createReadStream(file.path), writeStream = fs.createWriteStream(path); readStream.pipe(writeStream); readStream.on('end', () => { resolve(); fs.unlinkSync(file.path); res.send({ code: 0, codeText: 'upload success', originalFilename: filename, servicePath: path.replace(__dirname, HOSTNAME) }); }); } catch (err) { reject(err); res.send({ code: 1, codeText: err }); } return; } fs.writeFile(path, file, err => { if (err) { reject(err); res.send({ code: 1, codeText: err }); return; } resolve(); res.send({ code: 0, codeText: 'upload success', originalFilename: filename, servicePath: path.replace(__dirname, HOSTNAME) }); }); });};// 基于multiparty插件实现文件上传处理 & form-data解析const uploadDir = `${__dirname}/upload`;const multiparty_upload = function multiparty_upload(req, auto) { typeof auto !== "boolean" ? auto = false : null; let config = { maxFieldsSize: 200 * 1024 * 1024, }; if (auto) config.uploadDir = uploadDir; return new Promise(async (resolve, reject) => { await delay(); new multiparty.Form(config) .parse(req, (err, fields, files) => { if (err) { reject(err); return; } resolve({ fields, files }); }); });};// 单文件上传处理「FORM-DATA」app.post('/upload_single', async (req, res) => { try { let { files } = await multiparty_upload(req, true); let file = (files.file && files.file[0]) || {}; res.send({ code: 0, codeText: 'upload success', originalFilename: file.originalFilename, servicePath: file.path.replace(__dirname, HOSTNAME) }); } catch (err) { res.send({ code: 1, codeText: err }); }});app.post('/upload_single_name', async (req, res) => { try { let { fields, files } = await multiparty_upload(req); let file = (files.file && files.file[0]) || {}, filename = (fields.filename && fields.filename[0]) || "", path = `${uploadDir}/${filename}`, isExists = false; // 检测是否存在 isExists = await exists(path); if (isExists) { res.send({ code: 0, codeText: 'file is exists', originalFilename: filename, servicePath: path.replace(__dirname, HOSTNAME) }); return; } writeFile(res, path, file, filename, true); } catch (err) { res.send({ code: 1, codeText: err }); }});// 单文件上传处理「BASE64」app.post('/upload_single_base64', async (req, res) => { let file = req.body.file, filename = req.body.filename, spark = new SparkMD5.ArrayBuffer(), suffix = /\.([0-9a-zA-Z]+)$/.exec(filename)[1], isExists = false, path; file = decodeURIComponent(file); file = file.replace(/^data:image\/\w+;base64,/, ""); file = Buffer.from(file, 'base64'); spark.append(file); path = `${uploadDir}/${spark.end()}.${suffix}`; await delay(); // 检测是否存在 isExists = await exists(path); if (isExists) { res.send({ code: 0, codeText: 'file is exists', originalFilename: filename, servicePath: path.replace(__dirname, HOSTNAME) }); return; } writeFile(res, path, file, filename, false);});// 大文件切片上传 & 合并切片const merge = function merge(HASH, count) { return new Promise(async (resolve, reject) => { let path = `${uploadDir}/${HASH}`, fileList = [], suffix, isExists; isExists = await exists(path); if (!isExists) { reject('HASH path is not found!'); return; } fileList = fs.readdirSync(path); if (fileList.length < count) { reject('the slice has not been uploaded!'); return; } fileList.sort((a, b) => { let reg = /_(\d+)/; return reg.exec(a)[1] - reg.exec(b)[1]; }).forEach(item => { !suffix ? suffix = /\.([0-9a-zA-Z]+)$/.exec(item)[1] : null; fs.appendFileSync(`${uploadDir}/${HASH}.${suffix}`, fs.readFileSync(`${path}/${item}`)); fs.unlinkSync(`${path}/${item}`); }); fs.rmdirSync(path); resolve({ path: `${uploadDir}/${HASH}.${suffix}`, filename: `${HASH}.${suffix}` }); });};app.post('/upload_chunk', async (req, res) => { try { let { fields, files } = await multiparty_upload(req); let file = (files.file && files.file[0]) || {}, filename = (fields.filename && fields.filename[0]) || "", path = '', isExists = false; // 创建存放切片的临时目录 let [, HASH] = /^([^_]+)_(\d+)/.exec(filename); path = `${uploadDir}/${HASH}`; !fs.existsSync(path) ? fs.mkdirSync(path) : null; // 把切片存储到临时目录中 path = `${uploadDir}/${HASH}/${filename}`; isExists = await exists(path); if (isExists) { res.send({ code: 0, codeText: 'file is exists', originalFilename: filename, servicePath: path.replace(__dirname, HOSTNAME) }); return; } writeFile(res, path, file, filename, true); } catch (err) { res.send({ code: 1, codeText: err }); }});app.post('/upload_merge', async (req, res) => { let { HASH, count } = req.body; try { let { filename, path } = await merge(HASH, count); res.send({ code: 0, codeText: 'merge success', originalFilename: filename, servicePath: path.replace(__dirname, HOSTNAME) }); } catch (err) { res.send({ code: 1, codeText: err }); }});app.get('/upload_already', async (req, res) => { let { HASH } = req.query; let path = `${uploadDir}/${HASH}`, fileList = []; try { fileList = fs.readdirSync(path); fileList = fileList.sort((a, b) => { let reg = /_(\d+)/; return reg.exec(a)[1] - reg.exec(b)[1]; }); res.send({ code: 0, codeText: '', fileList: fileList }); } catch (err) { res.send({ code: 0, codeText: '', fileList: fileList }); }});app.use(express.static('./'));app.use((req, res) => { res.status(404); res.send('NOT FOUND!');});
axios代码
let instance = axios.create();instance.defaults.baseURL = 'http://127.0.0.1:8888';instance.defaults.headers['Content-Type'] = 'multipart/form-data';instance.defaults.transformRequest = (data, headers) => { const contentType = headers['Content-Type']; if (contentType === "application/x-www-form-urlencoded") return Qs.stringify(data); return data;};instance.interceptors.response.use(response => { return response.data;});
基于FORM-DATA实现文件上传
let formData = new FormData();formData.append('file', _file);formData.append('filename', _file.name);instance.post('/upload_single', formData).then(data => { if (+data.code === 0) { alert(`文件已经上传成功~~,您可以基于 ${data.servicePath} 访问这个资源~~`); return; } return Promise.reject(data.codeText);}).catch(reason => { alert('文件上传失败,请您稍后再试~~');})//限制大小// 获取用户选中的文件对象// + name:文件名// + size:文件大小 B// + type:文件的MIME类型let file = upload_inp.files[0];if (!file) return;// 限制文件上传的格式「方案一」/* if (!/(PNG|JPG|JPEG)/i.test(file.type)) { alert('上传的文件只能是 PNG/JPG/JPEG 格式的~~'); return; } */// 限制文件上传的大小if (file.size > 2 * 1024 * 1024) { alert('上传的文件不能超过2MB~~'); return;}
基于BASE64实现文件上传
// 把选择的文件读取成为BASE64const changeBASE64 = file => { return new Promise(resolve => { let fileReader = new FileReader(); fileReader.readAsDataURL(file); fileReader.onload = ev => resolve(ev.target.result); });};//上传相关代码let file = upload_inp.files[0], BASE64, data;if (!file) return;if (file.size > 2 * 1024 * 1024) { alert('上传的文件不能超过2MB~~'); return;}BASE64 = await changeBASE64(file);try { data = await instance.post('/upload_single_base64', { file: encodeURIComponent(BASE64), filename: file.name }, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); if (+data.code === 0) { alert(`恭喜您,文件上传成功,您可以基于 ${data.servicePath} 地址去访问~~`); return; } throw data.codeText;} catch (err) { alert('很遗憾,文件上传失败,请您稍后再试~~');}
文件缩略图 & 自动生成名字
// 把选择的文件读取成为BASE64const changeBASE64 = file => { return new Promise(resolve => { let fileReader = new FileReader(); fileReader.readAsDataURL(file); fileReader.onload = ev => { resolve(ev.target.result); }; });};// 生成文件的HASH名字const changeBuffer = file => { return new Promise(resolve => { let fileReader = new FileReader(); fileReader.readAsArrayBuffer(file); fileReader.onload = ev => { let buffer = ev.target.result, spark = new SparkMD5.ArrayBuffer(), HASH, suffix; spark.append(buffer); HASH = spark.end(); suffix = /\.([a-zA-Z0-9]+)$/.exec(file.name)[1]; resolve({ buffer, HASH, suffix, filename: `${HASH}.${suffix}` }); }; });};//上传相关代码let { filename} = await changeBuffer(_file);let formData = new FormData();formData.append('file', _file);formData.append('filename', filename);instance.post('/upload_single_name', formData).then(data => { if (+data.code === 0) { alert(`文件已经上传成功~~,您可以基于 ${data.servicePath} 访问这个资源~~`); return; } return Promise.reject(data.codeText);}).catch(reason => { alert('文件上传失败,请您稍后再试~~');})// 文件预览,就是把文件对象转换为BASE64,赋值给图片的SRC属性即可let file = upload_inp.files[0], BASE64;BASE64 = await changeBASE64(file);upload_abbre_img.src = BASE64;
进度管控
let file = upload_inp.files[0], data;try { let formData = new FormData(); formData.append('file', file); formData.append('filename', file.name); data = await instance.post('/upload_single', formData, { // 文件上传中的回调函数 xhr.upload.onprogress onUploadProgress(ev) { let { loaded, total } = ev; upload_progress.style.display = 'block'; upload_progress_value.style.width = `${loaded / total * 100}%`; } }); if (+data.code === 0) { upload_progress_value.style.width = `100%`; await delay(300); alert(`恭喜您,文件上传成功,您可以基于 ${data.servicePath} 访问该文件~~`); return; } throw data.codeText;} catch (err) { alert('很遗憾,文件上传失败,请您稍后再试~~');}
多文件上传
// 循环发送请求let _files = [];let upload_list_arr = Array.from(upload_list.querySelectorAll('li'));_files = _files.map(item => { let fm = new FormData, curLi = upload_list_arr.find(liBox => liBox.getAttribute('key') === item.key), curSpan = curLi ? curLi.querySelector('span:nth-last-child(1)') : null; fm.append('file', item.file); fm.append('filename', item.filename); return instance.post('/upload_single', fm, { onUploadProgress(ev) { // 检测每一个的上传进度 if (curSpan) { curSpan.innerHTML = `${(ev.loaded / ev.total * 100).toFixed(2)}%`; } } }).then(data => { if (+data.code === 0) { if (curSpan) { curSpan.innerHTML = `100%`; } return; } return Promise.reject(); });});// 等待所有处理的结果Promise.all(_files).then(() => { alert('恭喜您,所有文件都上传成功~~');}).catch(() => { alert('很遗憾,上传过程中出现问题,请您稍后再试~~');}).finally(() => { changeDisable(false); _files = []; upload_list.innerHTML = ''; upload_list.style.display = 'none';});
拖拽上传
const uploadFile = async file => { if (isRun) return; isRun = true; upload_mark.style.display = 'block'; try { let fm = new FormData, data; fm.append('file', file); fm.append('filename', file.name); data = await instance.post('/upload_single', fm); if (+data.code === 0) { alert(`恭喜您,文件上传成功,您可以基于 ${data.servicePath} 访问该文件~~`); return; } throw data.codeText; } catch (err) { alert(`很遗憾,文件上传失败,请您稍后再试~~`); } finally { upload_mark.style.display = 'none'; isRun = false; }};upload.addEventListener('dragover', function (ev) { ev.preventDefault();});upload.addEventListener('drop', function (ev) { ev.preventDefault(); let file = ev.dataTransfer.files[0]; if (!file) return; uploadFile(file);});
大文件上传
//文件重命名const changeBuffer = file => { return new Promise(resolve => { let fileReader = new FileReader(); fileReader.readAsArrayBuffer(file); fileReader.onload = ev => { let buffer = ev.target.result, spark = new SparkMD5.ArrayBuffer(), HASH, suffix; spark.append(buffer); HASH = spark.end(); suffix = /\.([a-zA-Z0-9]+)$/.exec(file.name)[1]; resolve({ buffer, HASH, suffix, filename: `${HASH}.${suffix}` }); }; });};//文件上传let file = upload_inp.files[0];// 获取文件的HASHlet already = [], data = null, { HASH, suffix } = await changeBuffer(file);try { data = await instance.get('/upload_already', { params: { HASH } }); if (+data.code === 0) { console.log('文件信息-------',data,'---------end'); already = data.fileList; }} catch (err) { }// 实现文件切片处理 「固定数量 & 固定大小」let max = 1024 * 100, count = Math.ceil(file.size / max), index = 0, chunks = [];if (count > 100) { max = file.size / 100; count = 100;}while (index < count) { chunks.push({ file: file.slice(index * max, (index + 1) * max), filename: `${HASH}_${index + 1}.${suffix}` }); index++;}//上传后成功的回调函数const complate = async () => { // 管控进度条 index++; upload_progress_value.style.width = `${index / count * 100}%`; // 当所有切片都上传成功,我们合并切片 if (index < count) return; upload_progress_value.style.width = `100%`; try { data = await instance.post('/upload_merge', { HASH, count }, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); if (+data.code === 0) { alert(`恭喜您,文件上传成功,您可以基于 ${data.servicePath} 访问该文件~~`); clear(); return; } throw data.codeText; } catch (err) { alert('切片合并失败,请您稍后再试~~'); clear(); }};// 把每一个切片都上传到服务器上chunks.forEach(chunk => { // 已经上传的无需在上传 if (already.length > 0 && already.includes(chunk.filename)) { complate(); return; } let fm = new FormData; fm.append('file', chunk.file); fm.append('filename', chunk.filename); instance.post('/upload_chunk', fm).then(data => { if (+data.code === 0) { complate(); return; } return Promise.reject(data.codeText); }).catch(() => { alert('当前切片上传失败,请您稍后再试~~'); clear(); });});