内容简介:因为公司内部平台非常多,很多开发的站点地址没有一个统一的入口,所以作者基于 egg + mongodb + redies + umi +antd 搭建了一个简单的入口平台。 由于各个平台各有特点如果能输入名字的话还是不太好区分,logo上传必然是一个必须的功能。 一起来看一下整个前后端功能实现的过程。Egg声明路由首先 egg-multipart 已经帮我们处理了二进制对象,在前端发起请求姿势没有问题的情况下,后端部分只要调用ctx.getFileStream 就可以得到一个流
因为公司内部平台非常多,很多开发的站点地址没有一个统一的入口,所以作者基于 egg + mongodb + redies + umi +antd 搭建了一个简单的入口平台。 由于各个平台各有特点如果能输入名字的话还是不太好区分,logo上传必然是一个必须的功能。 一起来看一下整个前后端功能实现的过程。
node部分
依赖安装
yarn add await-stream-ready stream-wormhole 复制代码
- await-stream-ready 异步二进制 写入流
- stream-wormhole 管道读入一个虫洞。
路由声明
module.exports = app => { const { router, controller } = app; router.get(‘/api/file/upload’, controller.file.upload) }; 复制代码
Egg声明路由
controller
'use strict'; //node.js 文件操作对象 const fs = require('fs'); //node.js 路径操作对象 const path = require('path'); //故名思意 异步二进制 写入流 const awaitWriteStream = require('await-stream-ready').write; //管道读入一个虫洞。 const sendToWormhole = require('stream-wormhole'); //当然你也可以不使用这个 哈哈 个人比较赖 //还有我们这里使用了egg-multipart const Controller = require('egg').Controller; class FileController extends Controller { async upload() { const ctx = this.ctx; //egg-multipart 已经帮我们处理文件二进制对象 // node.js 和 php 的上传唯一的不同就是 ,php 是转移一个 临时文件 // node.js 和 其他语言(java c#) 一样操作文件流 const stream = await ctx.getFileStream(); //新建一个文件名 const filename = stream.filename // const filename = md5(stream.filename) + path // .extname(stream.filename) // .toLocaleLowerCase(); //文件生成绝对路径 //当然这里这样市不行的,因为你还要判断一下是否存在文件路径 const target = path.join(this.config.baseDir, 'app/public/uploads', filename); //生成一个文件写入 文件流 const writeStream = fs.createWriteStream(target); try { //异步把文件流 写入 await awaitWriteStream(stream.pipe(writeStream)); } catch (err) { //如果出现错误,关闭管道 await sendToWormhole(stream); throw err; } const url = `/public/uploads/${filename}` //文件响应 ctx.body = { url }; } } module.exports = FileController; 复制代码
首先 egg-multipart 已经帮我们处理了二进制对象,在前端发起请求姿势没有问题的情况下,后端部分只要调用ctx.getFileStream 就可以得到一个流
const stream = await ctx.getFileStream(); 复制代码
然后指定写入的文件夹,注意这边如果没有找到文件夹会直接报错!
const filename = stream.filename //当然这里这样市不行的,因为你还要判断一下是否存在文件路径 const target = path.join(this.config.baseDir, 'app/public/uploads', filename); 复制代码
然后创建一个文件写入流
const writeStream = fs.createWriteStream(target); 复制代码
接下来我们引用的两个库就派上用场了,一个是用来异步完成写入流,另外一个是用来报错的时候关闭管道,这部分不了解的请移步node。
try { //异步把文件流 写入 await awaitWriteStream(stream.pipe(writeStream)); } catch (err) { //如果出现错误,关闭管道 await sendToWormhole(stream); throw err; } 复制代码
等待文件写入流结束之后,文件就在目标文件夹下了,就可以把文件的地址返回给前端
const url = `/public/uploads/${filename}` //文件响应 ctx.body = { url }; 复制代码
总结
Egg部分已经帮我们把处理二进制对象处理完了,我们需要做的事情其实很简单,拿到文件流,指定写入的文件夹,创建文件写入流,等待写入流结束之后返回文件写入的地址给前端。
前端部分
依赖安装
本示例涉及到图片裁剪,如果没有这个需求的请略过
yarn add react-image-crop 复制代码
PlatformModal.jsx
upload(上传模块)
这里直接使用的是antd upload的组件,如果你后端部分写好了,直接贴入代码,updateUrl 为你上传文件的api接口。这边接口响应之后的格式根据你的情况定义,拿到的url可以直接写在
<img src={this.state.iconUrl}> 复制代码
既可。 到了这边一个图片上传的示例就结束了,后面我们将裁减模块。
renderUpdate = () => { const uploadProps = { name: 'icon', action: updateUrl, onChange: (info) => { if (info.file.status !== 'uploading') { console.log(info.file, info.fileList); } if (info.file.status === 'done') { message.success(`${info.file.name} LOGO 上传成功!`); this.setState({ iconUrl: info.file.response.data.url, crop: {} }) } else if (info.file.status === 'error') { message.error(`${info.file.name} LOGO 上传失败!`); } } } return <Upload {...uploadProps}> <Button><Icon type="upload" />选择图片</Button> </Upload> } 复制代码
renderReactCrop(裁减模块)
图片裁减部分我们引用了 react-image-crop 这个react组件,这部分功能的一个思路是这样的。
- 图片地址设置
- 裁减图片动作触发并带有裁减范围的参数
- 通过canvas将图片根据裁减范围转化为base64
- 将 base64 数据暂时存起来
- 在提交的时候,将base64转化为文件
- 通过 formData 创建文件对象提交到后端接口
- 后端返回新的url地址
- 更改平台的图片地址
import ReactCrop from 'react-image-crop' import 'react-image-crop/dist/ReactCrop.css' function getBlobBydataURI(dataURI, type) { var binary = atob(dataURI.split(',')[1]); var array = []; for (var i = 0; i < binary.length; i++) { array.push(binary.charCodeAt(i)); } return new Blob([new Uint8Array(array)], { type: type }); } 复制代码
renderReactCrop = () => { const { iconUrl, crop } = this.state const loadImage = imgSrc => new Promise((resolve, reject) => { const img = new Image() img.setAttribute('crossOrigin', 'anonymous') img.src = imgSrc img.onload = e => { resolve(img) } }) const cropImage = async (imgSrc, crop) => { const img = await loadImage(imgSrc) let canvas, cropX, cropY, cropWidth, cropHeight // return this.loadImage(imgSrc, cropAfterLoad.bind(this)) const imageWidth = img.naturalWidth const imageHeight = img.naturalHeight cropX = (crop.x / 100) * imageWidth cropY = (crop.y / 100) * imageHeight cropWidth = (crop.width / 100) * imageWidth cropHeight = (crop.height / 100) * imageHeight canvas = document.createElement('canvas') canvas.width = cropWidth canvas.height = cropHeight const _2d = canvas.getContext('2d') _2d.drawImage(img, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight) return canvas.toDataURL('image/jpeg') } const handleCropComplete = (crop, pixelCrop) => { cropImage(iconUrl, crop) .then(result => { message.success('裁剪成功!') this.setState({ iconBase64: result, crop, }) }) .catch(err => { message.error(err.message) }) } const handleCropChange = (crop) => { this.setState({ crop }) } return <ReactCrop src={iconUrl} onComplete={handleCropComplete.bind(this)} onChange={handleCropChange} crop={crop} /> } 复制代码
然后是提交的时候的一个处理方式,将base64 转化为一个blob对象,然
- 将base64 转化为一个blob对象
- 创建一个 formData 用于提交
- 向fornData里面添加文件,注意这边图片名称记得带上时间戳
- 用fetch向文件上传接口发起请求
- 拿到url
const blob = getBlobBydataURI(iconBase64, 'image/png') let formData = new FormData(); formData.append('files', blob, `${name}_${Date.parse(new Date())}_icon.png`) fetch(updateUrl, { method: 'POST', body: formData }) 复制代码
结束
部分细节代码参考了网上的,一些细节没有深入研究,整理了以下整个流程。希望对别人有帮助。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。