学习原生js文件上传笔记2之单一文件上传「BASE64」,只适合小图片

107 阅读3分钟

前言:图片转换成base64格式的优缺点

  1. 优点
  • base64格式的图片是文本格式,占用内存小,转换后的大小比例大概为1/3,降低了资源服务器的消耗;
  • 网页中使用base64格式的图片时,不用再请求服务器调用图片资源,减少了服务器访问次数。
  1. 缺点
  • base64格式的文本内容较多,存储在数据库中增大了数据库服务器的压力;
  • 网页加载图片虽然不用访问服务器了,但因为base64格式的内容太多,所以加载网页的速度会降低,可能会影响用户的体验。
  • base64无法缓存,要缓存只能缓存包含base64的文件,比如js或者css,这比直接缓存图片要差很多,而且一般HTML改动比较频繁,所以等同于得不到缓存效益。
  1. 总结

    因为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代码了

屏幕截图 2023-04-21 152755.png

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参数是一个整数,有以下常量值:

  1. fs.constants.F_OK path对调用进程是可见的,既存在
  2. fs.constants.R_OK path是可读的
  3. fs.constants.W_OK path是可写的
  4. fs.constants.X_OK path是可执行的

详细请看:blog.csdn.net/qq_44352182…

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);
});

效果

  1. 用户选择文件上传后

屏幕截图 2023-04-21 163053.png 2. 后端显示

屏幕截图 2023-04-21 170332.png 3. 如果上传的是同一个文件,后端不会进行缓存,直接把图片资源地址发给前端

屏幕截图 2023-04-21 163924.png

小白文章,欢迎指正错误。