基于Node的React图片上传组件实现实例代码

网友投稿 259 2023-05-16


基于Node的React图片上传组件实现实例代码

写在前面

红旗不倒,誓把javascript进行到底!今天介绍我的开源项目 Royal 里的图片上传组件的前后端实现原理(React + Node),花了一些时间,希望对你有所帮助。

前端实现

遵循React 组件化的思想,我把图片上传做成了一个独立的组件(没有其他依赖),直接import即可。

import React, { Component } from 'react'

import Upload from '../../components/FormControls/Upload/'

//......

render() {

return (

)

}

uri 参数是必须传的,是图片上传的后端接口地址,接口怎么写下面会讲到。

渲染页面

组件render部分需要体现三个功能:

图片选取(dialog窗口)

可拖拽功能(拖拽容器)

可预览(预览列表)

上传按钮 (button)

上传完成图片地址和链接 (信息列表)

主render函数

render() {

return (

onChange={(v)=>this.handleChange(v)}

type="file"

size={this.state.size}

name="fileSelect"

accept="image/*"

multiple={this.state.multiple} />

onDragOver={(e)=>this.handleDragHover(e)}

onDragLeave={(e)=>this.handleDragHover(e)}

onDrop={(e)=>this.handleDrop(e)}

className="upload-drag-area">

或者将图片拖到此处

onDragOver={(e)=>this.handleDragHover(e)}

onDragLeave={(e)=>this.handleDragHover(e)}

onDrop={(e)=>this.handleDrop(e)}

className="upload-drag-area">

或者将图片拖到此处

"upload-preview":"upload-preview ry-hidden"}>

{ this._renderPreview(); // 渲染图片预览列表 }

"upload-submit":"upload-submit ry-hidden"}>

onClick={()=>this.handleUpload()}

class="upload-submit-btn">

确认上传图片

{ this._renderUploadInfos(); // 渲染图片上传信息 }

)

}

渲染图片预览列表

_renderPreview() {

if (this.state.files) {

return this.state.files.map((item, idx) => {

return (

{item.name}

className="upload-delete"

title="删除" index={idx}>

className="upload-delete"

title="删除" index={idx}>


"upload-progress":

"upload-progress ry-hidden"}>

{this.state.progress[idx]}

"upload-progress":

"upload-progress ry-hidden"}>

{this.state.progress[idx]}

)

})

} else {

return null

}

}

渲染图片上传信息列表

_renderUploadInfos() {

if (this.state.uploadHistory) {

return this.state.uploadHistory.map((item, idx) => {

return (

上传成功,图片地址是:

查看

);

})

} else {

return null;

}

}

文件上传

前端要实现图片上传的原理就是通过构建FormData对象,把文件对象append()到该对象,然后挂载在XMLHttpRequest对象上 send() 到服务端。

获取文件对象

获取文件对象需要借助 input 输入框的 change 事件来获取 句柄参数 e

onChange={(e)=>this.handleChange(e)}

然后做以下处理:

e.preventDefault()

let target = event.target

let files = target.files

let count = this.state.multiple ? files.length : 1

for (let i = 0; i < count; i++) {

files[i].thumb = URL.createObjectURL(files[i])

}

// 转换为真正的数组

files = Array.prototype.slice.call(files, 0)

// 过滤非图片类型的文件

files = files.filter(function (file) {

return /image/i.test(file.type)

})

这时 files 就是 我们需要的文件对象组成的数组,把它 concat 到原有的 files里。

this.setState({files: this.state.files.concat(files)})

如此,接下来的操作 就可以 通过 this.state.files 取到当前已选中的 图片文件。

利用Promise处理异步上传

文件上传对于浏览器来说是异步的,为了处理 接下来的多图上传,这里引入了 Promise来处理异步操作:

upload(file, idx) {

return new Promise((resolve, reject) => {

let xhr = new XMLHttpRequest()

if (xhr.upload) {

// 上传中

xhr.upload.addEventListener("progress", (e) => {

// 处理上传进度

this.handleProgress(file, e.loaded, e.total, idx);

}, false)

// 文件上传成功或是失败

xhr.onreadystatechange = (e) => {

if (xhr.readyState === 4) {

if (xhr.status === 200) {

// 上传成功操作

this.handleSuccess(file, xhr.responseText)

// 把该文件从上传队列中删除

this.handleDeleteFile(file)

resolve(xhr.responseText);

} else {

// 上传出错处理

this.handleFailure(file, xhr.responseText)

reject(xhr.responseText);

}

}

}

// 开始上传

xhr.open("POST", this.state.uri, true)

let form = new FormData()

form.append("filedata", file)

xhr.send(form)

})

}

上传进度计算

利用XMLHttpRequest对象发异步请求的好处是可以 计算请求处理的进度,这是fetch所不具备的。

我们可以为 xhr.upload 对象的 progress 事件添加事件监听:

xhr.upload.addEventListener("progress", (e) => {

// 处理上传进度

this.handleProgress(file, e.loaded, e.total, i);

}, false)

说明:idx参数是纪录多图上传队列的索引

handleProgress(file, loaded, total, idx) {

let percent = (loaded / total * 100).toFixed(2) + '%';

let _progress = this.state.progress;

_progress[idx] = percent;

this.setState({ progress: _progress }) // 反馈到DOM里显示

}

拖拽上传

拖拽文件对于HTML5来说其实非常简单,因为它自带的几个事件监听机制可以直接做这类处理。主要用到的是下面三个:

onDragOver={(e)=>this.handleDragHover(e)}

onDragLeave={(e)=>this.handleDragHover(e)}

onDrop={(e)=>this.handleDrop(e)}

取消拖拽时的浏览器行为:

handleDragHover(e) {

e.stopPropagation()

e.preventDefault()

}

处理拖拽进来的文件:

handleDrop(e) {

this.setState({progress:[]})

this.handleDragHover(e)

// 获取文件列表对象

let files = e.target.files || e.dataTransfer.files

let count = this.state.multiple ? files.length : 1

for (let i = 0; i < count; i++) {

files[i].thumb = URL.createObjectURL(files[i])

}

// 转换为真正的数组

files = Array.prototype.slice.call(files, 0)

// 过滤非图片类型的文件

files = files.filter(function (file) {

return /image/i.test(file.type)

})

this.setState({files: this.state.files.concat(files)})

}

多图同时上传

支持多图上传我们需要在组件调用处设置属性:

multiple = { true } // 开启多图上传

size = { 50 } // 一次最大上传数量(虽没有上限,为保证服务端正常,建议50以下)

然后我们可以使用 Promise.all() 处理异步操作队列:

handleUpload() {

let _promises = this.state.files.map((file, idx) => this.upload(file, idx))

Promise.all(_promises).then( (res) => {

// 全部上传完成

this.handleComplete()

}).catch( (err) => { console.log(err) })

}

好了,前端工作已经完成,接下来就是Node的工作了。

后端实现

为了方便,后端采用的是express框架来快速搭建Http服务和路由。具体项目见我的github node-image-upload。逻辑虽然简单,但还是有几个可圈可点的地方:

跨域调用

本项目后端采用的是express,我们可以通过 res.header() 设置 请求的 “允许源” 来允许跨域调用:

res.header('Access-Control-Allow-Origin', '*');

设置为 * 说明允许任何 访问源,不太安全。建议设置成 你需要的 二级域名,如 jafeney.com。

除了 “允许源” ,其他还有 “允许头” 、”允许域”、 “允许方法”、”文本类型” 等。常用的设置如下:

function allowCross(res) {

res.header('Access-Control-Allow-Origin', '*');

res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');

res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');

res.header("X-Powered-By",' 3.2.1')

res.header("Content-Type", "application/json;charset=utf-8");

}

ES6下的Ajax请求

ES6风格下的Ajax请求和ES5不太一样,在正式的请求发出之前都会先发一个 类型为 OPTIONS的请求 作为试探,只有当该请求通过以后,正式的请求才能发向服务端。

所以服务端路由 我们还需要 处理这样一个 请求:

router.options('*', function (req, res, next) {

res.header('Access-Control-Allow-Origin', '*');

res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');

res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');

res.header("X-Powered-By",' 3.2.1')

res.header("Content-Type", "application/json;charset=utf-8");

next();

});

注意:该请求同样需要设置跨域。

处理上传

处理上传的图片引人了multiparty模块,用法很简单:

/*使用multiparty处理上传的图片*/

router.post('/upload', function(req, res, next) {

// 生成multiparty对象,并配置上传目标路径

var form = new multiparty.Form({uploadDir: './public/file/'});

// 上传完成后处理

form.parse(req, function(err, fields, files) {

var filesTmp = JSON.stringify(files, null, 2);

var relPath = '';

if (err) {

// 保存失败

console.log('Parse error: ' + err);

} else {

// 图片保存成功!

console.log('Parse Files: ' + filesTmp);

// 图片处理

processImg(files);

}

});

});

图片处理

Node处理图片需要引入 gm 模块,它需要用 npm 来安装:

npm install gm --save

BUG说明

注意:node的图形操作gm模块前使用必须 先安装 imagemagick 和 graphicsmagick,linux (ubuntu)上使用apt-get 安装:

sudo apt-get install imagemagick

sudo apt-get install graphicsmagick --with-webp // 支持webp格式的图片

MacOS上可以用 Homebrew 直接安装:

brew install imagemagick

brew install graphicsmagick --with-webp // 支持webp格式的图片

预设尺寸

有些时候除了原图,我们可能需要把原图等比例缩小作为预览图或者缩略图。这个异步操作还是用Promise来实现:

function reSizeImage(paths, dstPath, size) {

return new Promise(function(resolve, reject) {

gm(dstPath)

.noProfile()

.resizeExact(size)

.write('.' + paths[1] + '@' + size + '00.' + paths[2], function (err) {

if (!err) {

console.log('resize as ' + size + ' ok!')

resolve()

}

else {

reject(err)

};

});

});

}

重命名图片

为了方便排序和管理图片,我们按照 “年月日 + 时间戳 + 尺寸” 来命名图片:

var _dateSymbol = new Date().toLocaleDateString().split('-').join('');

var _timeSymbol = new Date().getTime().toString();

至于图片尺寸 使用 gm的 size() 方法来获取,处理方式如下:

gm(uploadedPath).size(function(err, size) {

var dstPath = './public/file/' + _dateSymbol + _timeSymbol

+ '_' + size.width + 'x' + size.height + '.'

+ _img.originalFilename.split('.')[1];

var _port = process.env.PORT || '9999';

relPath = 'http://' + req.hostname + ( _port!==80 ? ':' + _port : '' )

+ '/file/' + _dateSymbol + _timeSymbol + '_' + size.width + 'x'

+ size.height + '.' + _img.originalFilename.split('.')[1];

// 重命名

fs.rename(uploadedPath, dstPath, function(err) {

if (err) {

reject(err)

} else {

console.log('rename ok!');

}

});

});

总结

对于大前端的工作,理解图片上传的前后端原理仅仅是浅层的。我们的口号是 “把JavaScript进行到底!”,现在无论是 ReactNative的移动端开发,还是NodeJS的后端开发,前端工程师可以做的工作早已不仅仅是局限于web页面,它已经渗透到了互联网应用层面的方方面面,或许,叫 全栈工程师 更为贴切吧。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:详解Vue用axios发送post请求自动set cookie
下一篇:Java IO流体系继承结构图_动力节点Java学院整理
相关文章

 发表评论

暂时没有评论,来抢沙发吧~