学习原生js文件上传笔记1之单文件(FORM-DATA格式)上传

161 阅读3分钟

最近学习了文件上传,想做个笔记。写的简陋,勿打!

客户端

1.先看html结构

<div class="container">
    <div class="item">
        <h3>单一文件上传「FORM-DATA」</h3>
        <section class="upload_box" id="upload1">
            <!-- accept=".png" 限定上传文件的格式 已display:none -->
            <input type="file" class="upload_inp" accept=".png,.jpg,.jpeg">
            <div class="upload_button_box">
                <button class="upload_button select">选择文件</button>
                <button class="upload_button upload">上传到服务器</button>
            </div>
            <div class="upload_tip">只能上传 PNG/JPG/JPEG 格式图片,且大小不能超过2MB</div>
            <ul class="upload_list">
                <!-- <li>
                    <span>文件:...</span>
                    <span><em>移除</em></span>
                </li> -->
            </ul>
        </section>
    </div>
</div>

html效果

屏幕截图 2023-04-16 195623.png

2.配置axios实例

let instance = axios.create(); //创建单独的实例
instance.defaults.baseURL = 'http://127.0.0.1:8888';
instance.defaults.headers['Content-Type'] = 'multipart/form-data'; //默认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;
};
//transformRequest处理post参数,data为要传的文件,Qs.stringify将对象转为query参数

//截拦响应结果,直接返回主体信息。
instance.interceptors.response.use(response => {
    return response.data;
});

3.发起请求操作

/* 基于FORM-DATA实现文件上传 */
(function () {
    let upload = document.querySelector('#upload1'), //上传的大框
        upload_inp = upload.querySelector('.upload_inp'), //input框,被display:none
        upload_button_select = upload.querySelector('.upload_button.select'), //选择文件按钮
        upload_button_upload = upload.querySelector('.upload_button.upload'), //上传文件按钮 不加空格是两个类名都具备
        upload_tip = upload.querySelector('.upload_tip'), //提示语
        upload_list = upload.querySelector('.upload_list'); //文件列表
    let _file = null; //初始要上传的file
    
        // 点击选择文件按钮,触发上传文件INPUT框选择文件的行为
    upload_button_select.addEventListener('click', function () {
        if (upload_button_select.classList.contains('disable') || upload_button_select.classList.contains('loading')) return;
        upload_inp.click();//触发文件选择
    });

    // 上传文件到服务器时的按钮状态
    const changeDisable = flag => {
    //flag为true,代表正在上传文件将按钮禁用,显示loading图片
        if (flag) {
            upload_button_select.classList.add('disable');
            upload_button_upload.classList.add('loading');
            return;
        }
        upload_button_select.classList.remove('disable');
        upload_button_upload.classList.remove('loading');
    };
    
    //文件判空
    upload_button_upload.addEventListener('click', function () {
        if (upload_button_upload.classList.contains('disable') || upload_button_upload.classList.contains('loading')) return;
        if (!_file) {
            alert('请您先选择要上传的文件~~');
            return;
    };
    
    // 监听用户选择文件的操作
    upload_inp.addEventListener('change', function () {
        // 获取用户选中的文件对象
        //   + name:文件名
        //   + size:文件大小 B
        //   + type:文件的MIME类型
        // console.log(this.files);
       
        let file = upload_inp.files[0]; //因为是单文件上传,所以获取第一个文件
        if (!file) return;

        // 限制文件上传的大小
        if (file.size > 2 * 1024 * 1024) {
            alert('上传的文件不能超过2MB~~');
            return;
        }

        _file = file; //_file才是我们要上传的文件

        // 显示上传的文件列表
        upload_tip.style.display = 'none';
        upload_list.style.display = 'block';
        upload_list.innerHTML = `<li>
            <span>文件:${file.name}</span>
            <span><em>移除</em></span>
        </li>`;
        });
        
        //将按钮禁用,显示loading图片的函数
        changeDisable(true);
        
        // 把文件传递给服务器:FormData 
        let formData = new FormData();  //创建form-data格式
        formData.append('file', _file); //文件对象
        formData.append('filename', _file.name); //文件名
        
        //请求接口/upload_single,开始上传文件
        instance.post('/upload_single', formData).then(data => {
            if (+data.code === 0) {
                alert(`文件已经上传成功~~,您可以基于 ${data.servicePath} 访问这个资源~~`);
                return;
            }
            return Promise.reject(data.codeText);
        }).catch(reason => {
            alert('文件上传失败,请您稍后再试~~');
        }).finally(() => {
            clearHandle(); //文件列表复原操作
            changeDisable(false); //按钮复原操作
        });
    });

    // 移除按钮的点击处理
    const clearHandle = () => {
        _file = null;
        upload_tip.style.display = 'block';
        upload_list.style.display = 'none';
        upload_list.innerHTML = ``;
    };
    upload_list.addEventListener('click', function (ev) {
        let target = ev.target;
        if (target.tagName === "EM") {
            // 点击的是移除按钮
            clearHandle();
        }
    });

})();

选择文件时的状态展示

屏幕截图 2023-04-16 204714.png

服务端node-express

1.导入必要的模块

const express = require('express'),
    fs = require('fs'), 
    multiparty = require('multiparty'); //解析form-data格式的插件

2.创建一个express服务器

const app = express(),
    PORT = 8888,
    HOST = 'http://127.0.0.1',
    HOSTNAME = `${HOST}:${PORT}`;
    app.listen(PORT, () => {
    console.log(`服务器开启:http://127.0.0.1:8888`);
});

3.解决跨域问题

app.use((req, res, next) => {
    res.header("Access-Control-Allow-Origin", "*");
    req.method === 'OPTIONS' ? res.send('当前服务支持跨域请求!') : next();
});

4.为了更加真实,这里添加了延迟函数

// 延迟函数
const delay = function delay(interval) {
    typeof interval !== "number" ? interval = 1000 : null;
    return new Promise(resolve => {
        setTimeout(() => {
            resolve();
        }, interval);
    });
};

5.使用multiparty处理上传的文件

// 使用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,
    };
    //auto为true就传到/upload目录下
    if (auto) config.uploadDir = uploadDir;
    return new Promise(async (resolve, reject) => {
        await delay();  //延迟函数,模拟上传时间
        //解析数据
        //fields---->{ filename: [ 'IMG_20210315_110600_polarr.jpg' ] }
        //files----->
        //  {
        //    file: [
        //      {
        //        fieldName: 'file',
        //       originalFilename: 'IMG_20210315_110600_polarr.jpg',
        //        path: 'E:\\****\\upload\\IBzQNI15x1kZd5f8GWW3SF_V.jpg',
        //        headers: [Object],
        //        size: 1063973
        //      }
        //    ]
        //  }
        new multiparty.Form(config)
            .parse(req, (err, fields, files) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve({
                    fields,
                    files
                });
            });
    });
};

multiparty会在field事件中,将数据信息的字段名和值返回。在file事件中,将文件的字段名和信息返回。 上传成功后,会在指定的文件夹创建一个上传的文件,并会将文件重命名(如:IBzQNI15x1kZd5f8GWW3SF_V.jpg),以防止重名。 若上传出现失败,已保存的文件会自动删除。

6.请求的接口

// 单文件上传处理「FORM-DATA」
app.post('/upload_single', async (req, res) => {
    try {
        let {
            fields,
            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
        });
    }
});

最终效果

屏幕截图 2023-04-16 210448.png

如有错误的地方,欢迎指出!