服务器代码
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实现文件上传
// 把选择的文件读取成为BASE64
const 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('很遗憾,文件上传失败,请您稍后再试~~');
}
文件缩略图 & 自动生成名字
// 把选择的文件读取成为BASE64
const 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];
// 获取文件的HASH
let 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();
});
});