图片裁剪上传示例(node + react)

栏目: 服务器 · 发布时间: 6年前

内容简介:因为公司内部平台非常多,很多开发的站点地址没有一个统一的入口,所以作者基于 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
        })
复制代码

结束

部分细节代码参考了网上的,一些细节没有深入研究,整理了以下整个流程。希望对别人有帮助。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Java多线程编程实战指南(设计模式篇)

Java多线程编程实战指南(设计模式篇)

黄文海 / 电子工业出版社 / 2015-10 / 59.00

随着CPU 多核时代的到来,多线程编程在充分利用计算资源、提高软件服务质量方面扮演了越来越重要的角色。而 解决多线程编程中频繁出现的普遍问题可以借鉴设计模式所提供的现成解决方案。然而,多线程编程相关的设计模式书籍多采用C++作为描述语言,且书中所举的例子多与应用开发人员的实际工作相去甚远。《Java多线程编程实战指南(设计模式篇)》采用Java(JDK1.6)语言和UML 为描述语言,并结合作者多......一起来看看 《Java多线程编程实战指南(设计模式篇)》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具