前端文件上传

文件上传。

  • 前端中与后台的交互无外乎表单的交互,如今的前端和后台交互主要采用的是Ajax发送JSON的形式来实现的,一些组件也封装了一些获取表单内容的方式。但是对于文件上传我之前一直使用的是 组件封装的Upload。所以之前对于文件上传就没有太关心,直到。。。。,先说用他人组件上传的方式把。

  • 之前自己做的一个小项目需要上传头像,就使用到了文件上传,当时直接使用了antd 的Upload组件,当时时间比较紧,做完以后没有进行后续的学习,所以对于文件上传一直很模糊,先看下当时使用的文件上传。(这里是独立出来的文件上传)

  • 对于antd也是简简单单的使用,当时也没有深入了解他的原理,所以就导致了使用仅仅是使用,一旦遇到了变通的需求就懵了。下面是基于antd的上传代码:

const UploadExample: React.FC = () => {
    const uploadParam = {
        name: 'testFile',
        action: 'http://127.0.0.1:7001/uploadFile',
        onChange: fileChange,
        showUploadList:false
    }
    
    return (
        <>
            <h3>文件上传实例</h3>
            <Upload
                {...uploadParam}
            >
                <Button>上传文件</Button>
            </Upload>
        </>
    )
}
  • 实现的功能也比较简单,就是上传一个文件到指定的服务器,这里后续的上传回调可给定onChange事件中处理。这里没有给出代码。后台接受也比较简单,基于Egg官方文档里也给出了详细的实现。有兴趣可以去文档中查看。文档地址
  • 基于文档中的实现方式,我本地也做了个小deme来接受前端传递的文件,具体的实现代码如下:
   @POST('/uploadFile')
    public async testUpload() {
        const {ctx} = this;
        const fileList:string[] = [];
        for(const file of ctx.request.files) {
            const filename = (new Date()).getTime() + Math.random().toString(36).substr(2) + path.basename(file.filename);
            const fileValue = fs.readFileSync(file.filepath); 
            fs.writeFileSync(path.resolve(__dirname, `../public/img/${filename}`), fileValue);
            await fs.unlink(file.filepath,() =>{});
            fileList.push(filename);
        }
        this.returnSuccess({fileList: fileList.join(',')})
    }
  • 这里可以自己对文件进行处理,我自己是把他保存到了本地,返回文件名字。也可以上传到OSS,这里不再深入研究(主要咱也没有OSS)
  • 然后本以为文件上传实现了,谁知事隔一年文件上传它又来了,这次来让我措手不及。
  • 具体的需求是有一个表单,其中有表单内容和上传的文件需要一起传给后端,一听好像有点懵,之前使用组件的上传方式好像不行了,况且这次是jquery, 所以决定自己来实现表单的上传。具体的表单内容如下所示,不仅有表单数据,还有多个文件的上传。
  • 经过查找,发现古老的form表单上传。。。记得之前写jsp的时候使用过给form设置action 然后点击按钮就直接上传了。就像下面的方式一样:
        <form action="http://127.0.0.1:7001/formUpload" method = "post">
			<div>
				<input type="text" name="input" id="input">
			</div>
			<h4>select选择内容</h4>
			<div>
				<select name="select" id="select">
					<option value="first">第一个</option>
					<option value="second">第二个</option>
					<option value="three">第三个</option>
				</select>
			</div>
			<h4>文件区域</h4>
			<div>
				<input type="file" name="file1" id="file1">
				<input type="file" name=file2" id="file2">
			</div>
			<input type="submit" value = "提交">
		</form>
  • 上面是比较传统的方式,只要点击提交浏览器会自动与action指定的服务器建立联系,这里使用的是post的方式,所以建立联系以后浏览器,浏览器就会按分段传输的方法将数据发送给服务器看起来是可行的,但是提交会自动跳转页面,这对于如今前后端分离的方式来说是十分不友好的,那么可以对其进行一些改进,自己对上传进行控制,通过Ajax的方式来发送数据。
  • 通过ajax发送的话需要把form表单的默认提交事件给阻止掉,然后自己使用Ajax的方式上传,参数使用formData的方式,直接获取到表单中的数据,然后使用Ajax的方式发送。这样就自己实现了表单的默认提交方式。同时还可以自己实现对表单提交的校验。下面是实现的代码:
    $("#formEle").submit(function(e) {
			e.preventDefault();	
			const param = new FormData(this);
			$.ajax({
                url: 'http://127.0.0.1:7001/shuuy',
                type: 'POST',
                data: param,
                dataType: 'JSON',
                cache: false,
                processData: false,
                contentType: false,
                success: function (data) {
                    console.log(data);
                }
            });
		})
  • 通过查看请求头可以查看到表单中的数据已经可以全部以multipart/form-data的编码发送了。

然后后台的处理就跟上面的方式一样了,只需要分开接受string类型的参数和文件参数。代码如下:

    @POST('/shuuy')
    public async buSHuuy() {
        const { ctx } = this;
        const fileList:string[] = [];
        for (const file of ctx.request.files) {
            const filename = (new Date()).getTime() + Math.random().toString(36).substr(2) + path.basename(file.filename);
            let fileValue = fs.readFileSync(file.filepath) 
            fs.writeFileSync(path.resolve(__dirname, `../public/img/${filename}`), fileValue)
            await fs.unlink(file.filepath,() =>{});
            fileList.push(filename);
        }
        this.returnSuccess({ ...this.ctx.request.body, fileName: fileList.join(',') })
    }
  • 可以查看响应体可以看到响应的所有信息:

  • 到这里基本上就实现了,然后到那边一看,好像跟自己想的不太一样,这里是使用form提交的方式,可是那边给出的页面直接是div 布局法,代码如下:
        <div>
			<input type="text" name="input" id="input">
		</div>
		<h4>select选择内容</h4>
		<div>
			<select name="select" id="select">
				<option value="first">第一个</option>
				<option value="second">第二个</option>
				<option value="three">第三个</option>
			</select>
		</div>
		<h4>文件区域</h4>
		<div>
			<input type="file" name="file1" id="file1">
			<input type="file" name=file2" id="file2">
	    </div>
	    <button>表单提交</button>
  • 同样需要通过ajax 把所有的内容发送到后台,基于这种情况就可以针对上边表单提交的方式进行改进:使用FormData的方式注入参数,然后通过Ajax发送请求。FormData对象可以添加键值对,和form键值对是一样的。同时也可以自己对其进行校验。
const formSubmit = () => {
            const formData = new FormData();
            const validator = [
                { dom: 'input', required: true, message: 'input是必须的' },
                { dom: 'select', required: true, message: 'select是必须的' },
				{ dom: 'file1', required: true, message: 'file1是必须的', type: "file" },
				{ dom: 'file2', required: true, message: 'file2是必须的',type: 'file'}
            ]
            let flag = false;
            validator.forEach(item => {
                if (item.hasOwnProperty('dom') && item.hasOwnProperty('required')) {
                    if (item.required && !$(`#${item.dom}`).val()) {
                        !flag && item.hasOwnProperty('message') && alert(item.message);
                        flag = true;
                    }
                }
            })
            if (flag) {
                return;
            }
            validator.forEach(item => {
                item.hasOwnProperty('type') && item.type === 'file' ? formData.append(item.dom, $(`#${item.dom}`)[0].files[0]) : formData.append(item.dom, $(`#${item.dom}`).val())
            })
            $.ajax({
                url: 'http://127.0.0.1:7001/shuuy',
                type: 'POST',
                data: formData,
                dataType: 'JSON',
                cache: false,
                processData: false,
                contentType: false,
                success: function (data) {
                    console.log(data);
                }
            });
		}
  • 至此需求大概算是实现了,然而问题又来了,因为后端使用某平台开发,所以导致了使用 multipart/form-data提交的请求直接返回403(这里平台的上传是需要一个登录以后获得到的id的,因为没有所以就被拦截了),此路不通,好像没有办法可以走了,于是就回家了,第二天继续突然想到了能不能使用base64的方式使用JSON的方式提交。于是只能试一试了。我需要把文件转换为base64然后拼成键值对的方式提交。于是先要把文件转换为base64,之前没有接触过,找了个方法
        const blodTo64 = (blod) => {
			const reader = new FileReader();
			reader.onload = function (e) {
				console.log(e.target.result);
			}
			reader.readAsDataURL(blob);
		}
  • 由于该方法是异步的,又需要对多个文件转换,那么就封装个promise把
const blobToBase = (blob, key) => new Promise(resolve => {
			const reader = new FileReader();
			reader.readAsDataURL(blob);
			reader.onload = function (e) {
				resolve({ [key]: e.target.result })
			}
		})
		const formSubmit = () => {
			const input = $("#input").val();
			const select = $("#select").val();
			Promise.all([blobToBase($("#file1")[0].files[0], $("#file1")[0].files[0].name), blobToBase($("#file2")[0].files[0], $("#file2")[0].files[0].name)]).then(res => {
				const value = res.reduce((prev, curr) => Object.assign(prev, curr), {});
				const param = Object.assign({input,select},value);
				$.ajax({
					url: 'http://127.0.0.1:7001/formBaseUplaod',
					type: 'POST',
					data: JSON.stringify(param),
					contentType:'application/json;charset=utf-8',
					dataType: 'JSON',	
					success: function (data) {
						console.log(data);
					}
				});
			})
		}
  • 需求已经完成,交给了后台,但是这里也有自己的思考如果自己接收到这样一个这么大的base64数据,后台怎么处理呢? 自己后台写个小demo试试。
  • 首先是跟传统的接口请求一样使用JSON的方式接收到数据,然后把base64转换为文件存储起来,最后返回前台路径。这里我只是简单的保存了一下,所以直接返回存储的文件名字。
  • 后台代码,这里通过判断传递的内容是否是data:开头,如果是的话就可以直接拿到key当文件名字,然后写入文件。代码如下:
@POST('/formBaseUplaod')
    public async formBaseUpload() {
        const { ctx } = this;
        const reqBody = ctx.request.body;
        const fileList:string[] = [];
        const formValue =<{index: string}> {}
        for (const key in reqBody) {
            if (typeof reqBody[key] === 'string') {
                //    如果是 base64文件上传,就进行处理
                if (reqBody[key].startsWith('data:')) {
                    const base64Data = reqBody[key].replace(/^data:image\/\w+;base64,/, "");
                    const dataBuffer = new Buffer(base64Data, 'base64');
                    const filename = (new Date()).getTime() + Math.random().toString(36).substr(2) + '.' + key;
                    fileList.push(filename);
                    await fs.writeFile(path.resolve(__dirname, `../public/img/${filename}`), dataBuffer,() => {});
                }else {
                    formValue[key] = reqBody[key];
                }
            }
        }
        this.returnSuccess({...formValue,fileList: fileList.join(',')});
    }
  • 发送的请求头:
  • 响应体:

未完待续:[上传预览、多项上传]

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值