前言:图片转换成base64格式的优缺点
- 优点
- base64格式的图片是文本格式,占用内存小,转换后的大小比例大概为1/3,降低了资源服务器的消耗;
- 网页中使用base64格式的图片时,不用再请求服务器调用图片资源,减少了服务器访问次数。
- 缺点
- base64格式的文本内容较多,存储在数据库中增大了数据库服务器的压力;
- 网页加载图片虽然不用访问服务器了,但因为base64格式的内容太多,所以加载网页的速度会降低,可能会影响用户的体验。
- base64无法缓存,要缓存只能缓存包含base64的文件,比如js或者css,这比直接缓存图片要差很多,而且一般HTML改动比较频繁,所以等同于得不到缓存效益。
总结
因为base64的使用缺点,所以一般图片小于10kb的时候,我们才会选择使用base64图片,比如一些表情图片,太大的图片转换成base64得不偿失
客服端
1.先看html结构
<div class="item">
<h3>单一文件上传「BASE64」,只适合图片</h3>
<section class="upload_box" id="upload2" style="display: none;">
<input type="file" class="upload_inp" accept=".jpg,.jpeg,.png"> //accept限定文件为图片
<div class="upload_button_box">
<button class="upload_button select">上传图片</button>
</div>
<div class="upload_tip">只能上传jpg/png格式图片,且大小不能超过2mb</div>
</section>
</div>
因为原生input框太丑,所以我们重写样式,这里就不放css代码了
2.上传操作
1.获取dom
upload_inp = upload.querySelector('.upload_inp'), //获取选择文件的input
upload_button_select = upload.querySelector('.upload_button.select'); //获取点击上传的按钮
2.封装一个函数将file转为base64格式
// 把选择的文件读取成为BASE64
const changeBASE64 = (file) => {
return new Promise(resolve => {
//FileReader把文件对象变成想要的格式
let fileReader = new FileReader();
//将文件对象变为base64格式,异步
//readAsDataURL方法:开始读取指定的Blob中的内容。一旦完成,`result`属性中将包含一个`data:` URL 格式的 Base64 字符串以表示所读取文件的内容
fileReader.readAsDataURL(file);
//获取转化的结果
fileReader.onload = ev => {
// console.log(ev.target.result);
resolve(ev.target.result);
};
});
};
FileReader对象允许 Web 应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,更多内容见(FileReader - Web API 接口参考 | MDN (mozilla.org))
3. 点击按钮开始上传
upload_button_select.addEventListener('click', function () {
if (checkIsDisable(this)) return;
upload_inp.click(); //触发input框
});
// 验证是否处于可操作性状态
const checkIsDisable = element => {
let classList = element.classList;
return classList.contains('disable') || classList.contains('loading');
};
upload_inp.addEventListener('change', async function () {
let file = upload_inp.files[0],//用户选择的图片文件
BASE64, //base64字符串
data; //服务器结果
if (!file) return;
if (file.size > 2 * 1024 * 1024) {
alert('上传的文件不能超过2MB~~');
return;
}
//获取base64文件数据
BASE64 = await changeBASE64(file);
try {
//发起请求,注:封装的instance请看我的笔记1
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('很遗憾,文件上传失败,请您稍后再试~~');
} finally {
upload_button_select.classList.remove('loading');
}
});
服务器
注意,这里后端我用的是node-express,创建一个基本的服务器就ok,详细请看我的笔记1。
1. 引入spark-md5插件
const SparkMD5 = require('spark-md5');
SparkMD5 是 MD5 算法的快速 md5 实现。 该脚本基于 JKM md5 库,这是最快的算法。这最适合浏览器使用,正因为每个文件的md5是一样的,那么,我们在做文件上传的时候,就只要在前端先获取要上传的文件md5,并把文件md5传到服务器,对比之前文件的md5,如果存在相同的md5,我们只要把文件的名字传到服务器关联之前的文件即可,并不需要再次去上传相同的文件,再去耗费存储资源、上传的时间、网络带宽。
两个方法需要注意 SparkMD5.ArrayBuffer#append()、SparkMD5.ArrayBuffer#end(),更多详细信息请看(火花-MD5 - npm (npmjs.com))
spark = new SparkMD5.ArrayBuffer();//创建一个ArrayBuffer对象
spark.append(file) //将文件(base64)追加数组缓冲区,此时开始生成hash命名的文件
spark.end() //拿到hash文件名,完成 md5 的计算,返回十六进制结果。 如果为 true,则将以二进制字符串的形式返回结果
2.写一个文件判重函数
let fs=require('fs')
// 检测文件是否存在
const exists = function exists(path) {
return new Promise(resolve => {
//fs.access判断文件和目录是否存在
fs.access(path, fs.constants.F_OK, err => {
if (err) {
resolve(false);
return;
}
resolve(true);
});
});
};
使用fs.access(path[, mode], callback)方法检查权限,mode参数是一个整数,有以下常量值:
- fs.constants.F_OK path对调用进程是可见的,既存在
- fs.constants.R_OK path是可读的
- fs.constants.W_OK path是可写的
- fs.constants.X_OK path是可执行的
3.创建一个写入文件函数
// 创建文件并写入到指定的目录 & 返回客户端结果
const writeFile = function writeFile(res, path, file, filename) {
return new Promise((resolve, reject) => {
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)
});
});
});
};
4.接口
// 单文件上传处理「BASE64」
app.post('/upload_single_base64', async (req, res) => {
let file = req.body.file, //获取前端传递的文件对象
filename = req.body.filename, //获取前端传递的文件名
//SparkMD5.ArrayBuffer插件根据文件内容生成hash文件名,内容相同会重名,服务端不会保存
spark = new SparkMD5.ArrayBuffer(), //创建一个ArrayBuffer对象
suffix = /\.([0-9a-zA-Z]+)$/.exec(filename)[1], //获取后缀名
isExists = false, //是否已存在
path; //上传路径
file = decodeURIComponent(file); //解码
file = file.replace(/^data:image\/\w+;base64,/, ""); //去掉base64声明前缀
file = Buffer.from(file, 'base64'); //将base64转buffer数据
spark.append(file); //生成hash文件名
path = `${uploadDir}/${spark.end()}.${suffix}`; //拿到hash文件名(spark.end())
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);
});
效果
- 用户选择文件上传后
2. 后端显示
3. 如果上传的是同一个文件,后端不会进行缓存,直接把图片资源地址发给前端
小白文章,欢迎指正错误。