由于公司内部平台很是多,不少开发的站点地址没有一个统一的入口,因此做者基于 egg + mongodb + redies + umi +antd 搭建了一个简单的入口平台。 因为各个平台各有特色若是能输入名字的话仍是不太好区分,logo上传必然是一个必须的功能。 一块儿来看一下整个先后端功能实现的过程。php
yarn add await-stream-ready stream-wormhole
复制代码
module.exports = app => {
const { router, controller } = app;
router.get(‘/api/file/upload’, controller.file.upload)
};
复制代码
Egg声明路由css
'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();
复制代码
而后指定写入的文件夹,注意这边若是没有找到文件夹会直接报错!java
const filename = stream.filename
//固然这里这样市不行的,由于你还要判断一下是否存在文件路径
const target = path.join(this.config.baseDir, 'app/public/uploads', filename);
复制代码
而后建立一个文件写入流node
const writeStream = fs.createWriteStream(target);
复制代码
接下来咱们引用的两个库就派上用场了,一个是用来异步完成写入流,另一个是用来报错的时候关闭管道,这部分不了解的请移步node。react
try {
//异步把文件流 写入
await awaitWriteStream(stream.pipe(writeStream));
} catch (err) {
//若是出现错误,关闭管道
await sendToWormhole(stream);
throw err;
}
复制代码
等待文件写入流结束以后,文件就在目标文件夹下了,就能够把文件的地址返回给前端mongodb
const url = `/public/uploads/${filename}`
//文件响应
ctx.body = { url };
复制代码
Egg部分已经帮咱们把处理二进制对象处理完了,咱们须要作的事情其实很简单,拿到文件流,指定写入的文件夹,建立文件写入流,等待写入流结束以后返回文件写入的地址给前端。canvas
本示例涉及到图片裁剪,若是没有这个需求的请略过后端
yarn add react-image-crop
复制代码
这里直接使用的是antd upload的组件,若是你后端部分写好了,直接贴入代码,updateUrl 为你上传文件的api接口。这边接口响应以后的格式根据你的状况定义,拿到的url能够直接写在api
<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>
}
复制代码
图片裁减部分咱们引用了 react-image-crop 这个react组件,这部分功能的一个思路是这样的。
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对象,然
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
})
复制代码
部分细节代码参考了网上的,一些细节没有深刻研究,整理了如下整个流程。但愿对别人有帮助。