js以字符串的形式将图片上传到服务器

前言

       首先说一下所使用的环境:前端用的是vue.js+vue-cli+typescript,后端用的是spring boot,没有其他多余的依赖。

最后使用效果说明:

       上传图片的时候并不是通过提交form表单的形式将文件提交,而是从input节点中获取可以解析为二进制流的字符串,ajax提交这个字符串然后在java后台负责解析这个字符串,生成图片。这种方法现在看起来可能有点多余,但是以下两种情况发生时,这种方法就显得特别好用了,首先是混合式app开发的时候,节点inputtype=file是无效的,无法让用户去选择文件,只能通过调用插件让用户选择文件,调用返回一个字符串,提交这个字符串,然后在后台解析就可以实现混合式app上传文件的功能;还有一个是,当两个系统需要对接的时候,直接把文件提交过去可能有点困难,这时候如果保存了这个字符串,就可以单单把这个字符串提交,让另一个系统单独解析就行了。

先从最接近的地方开始,看vue中的ts代码(不会ts的同学,基础比较好的话,可以把ts代码转换成js,这个不难,只是很简单的逻辑):

<template>
  <div class="s-dev-img-upload">
    <s-card class="card">
      <div class="card-head" slot="head">
        <Icon type="heart"></Icon>
        <span>测试 图片选择以及预览 组件</span>
      </div>
      <div class="card-body" slot="body">
        <span>预览:</span>
        <div class="card-body-content area-center">
          <input type="file" ref="fileInput" class="display-none"/>
          <Button type="primary" @click="pickImg">选择文件</Button>
        </div>
      </div>
      <div class="card-footer" slot="foot">
        <!--<mu-switch label="文件多选?" v-model="isMultiple"/>-->
      </div>
    </s-card>
  </div>
</template>

<script>
  import SCard from 'src/base/components/s-card.vue'
  import {get, post, HttpOption} from 'src/base/js/public/http.js'

  export default {
    name: "s-dev-img-upload",
    components: {
      SCard
    },
    methods: {
      pickImg() {
        this.$fileInput = $(this.$refs.fileInput);
        this.$fileInput.click();
      }
    },
    mounted() {
      this.$fileInput = $(this.$refs.fileInput);
      this.$fileInput.change(() => {
        let files = this.$fileInput.prop('files');
        const file = files[0];

        if (window.FileReader) {
          let fr = new FileReader();
          fr.onloadend = (e) => {
            this.$fileInput.val(null);
            post({
              url: 'http://localhost:9321/image/uploadImg',
              param: {
                dataUrl: e.target.result,
                name: file.name
              },
              success: (data) => {
                console.log(data);
                fr = null;
              },
              error: (data) => {
                console.log(data);
                fr = null;
              }
            });
          };
          fr.readAsDataURL(file);
        }
      });
    }
  }
</script>

<style scoped lang="scss">
  @import "src/base/css/public/variable.scss";

  .s-dev-img-upload {
    height: 100%;
    width: 100%;
    padding: $padding-space;
    display: flex;
    justify-content: space-around;
    flex-wrap: wrap;
    align-items: center;
    .card {
      width: 264px;
      .card-head {
        height: 44px;
        @include vertical-center
      }
      .card-body {
        min-height: 132px;
        display: flex;
        flex-direction: column;
        position: relative;
        .card-body-content {
          word-wrap: break-word;
          word-break: break-all;
          flex: 1;
        }
      }
      .card-footer {
        height: 44px;
        @include vertical-center;
      }
    }
  }

</style>


script中mounted方法中的代码是重点代码,其他是与本次内容无关的代码,简单解释为,当触发了input的change事件之后,获取所有的files,我这里只是单选,所以只有一个files[0];通过判断FileReader判断浏览器是否支持,然后发送一个ajax,post请求,把字符串以及文件名提交;

java后台代码一览:


controller

package com.martsforever.core.img;

import com.martsforever.core.global.RequestManage;
import com.sun.imageio.plugins.common.ImageUtil;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("image")
public class ImageController {

    /**
     * 测试图片文件读写功能读取classes目录下的me.jpg文件,并创建一个新的文件me1.jpg,最后还把图片输出到response供用户下载或者查看
     *
     * @param req
     * @param resp
     * @return
     */
    @GetMapping("readWriteImg")
    public Map<String, Object> getImg(HttpServletRequest req, HttpServletResponse resp) {
        Map<String, Object> result = new HashMap<>();
        try {
            String resourceName = "me.jpg";
            /*获取classes目录绝对路径*/
            String path = this.getClass().getClassLoader().getResource(resourceName).getPath();
            System.out.println(path);
            /*读取文件*/
            FileInputStream fs = new FileInputStream(path);
            BufferedImage bi = ImageIO.read(fs);
            File file = new File("/D:/6_workspace/ideaspace_3/core/target/classes/me1.jpg");
            /*写入文件*/
            ImageIO.write(bi, "jpg", file);
            BufferedInputStream bis = new BufferedInputStream(fs);
            byte[] buffer = new byte[bis.available()];
            bis.read(buffer);
            /*输出文件响应请求*/
            resp.getOutputStream().write(buffer);
        } catch (Exception e) {
            e.printStackTrace();
        }
        result.put("success", true);
        return result;
    }

    /**
     * 测试图片上传,跟一般的图片上传不一样的是,这里只接受dataUrl,然后将dataUrl解析为二进制流,最后保存为图片文件
     *
     * @param ii
     * @return
     */
    @PostMapping("uploadImg")
    public Map<String, Object> uploadImg(@RequestBody ImageInfo ii) {
        System.out.println(ii.getDataUrl());
        System.out.println(ii.getName());
        try {
            ImgUtils.saveDataUrl(ii);
            return RequestManage.success(ii);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return RequestManage.error(null, "系统异常!");
    }

}


图片工具类:

package com.martsforever.core.img;

import sun.misc.BASE64Decoder;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;

public class ImgUtils {

    /*用来将二进制字符串解析成byte数组*/
    private static BASE64Decoder base64Decoder = new BASE64Decoder();
    /*项目根目录*/
    private static String rootPath = new File("").getAbsolutePath() + "\\";

    private static final int MID_WIDTH = 300;
    private static final int SMALL_WIDTH = 50;

    /**
     * 传入一个ImageInfo对象;
     * ii.dataUrl:为用来解析为二进制流的字符串,在浏览器端通过FileReadergetAsDataUrl方法获得;
     * ii.name:最后文件保存的名称
     * ii.path:文件保存的路径,咩有则使用项目根目录
     *
     * @param ii
     * @return ii,最后更新的信息为图片文件的大小以及图片保存的路径
     * @throws IOException
     */
    public static ImageInfo saveDataUrl(ImageInfo ii) throws Exception {
        ii.setDataUrl(ii.getDataUrl().substring(ii.getDataUrl().lastIndexOf(",") + 1));
        if (ii.getPath() == null || ii.getPath().equals("")) {
            ii.setPath(rootPath);
        }
        byte[] bytes = base64Decoder.decodeBuffer(ii.getDataUrl());
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        BufferedImage bi = ImageIO.read(bais);
        ImageInfo sbi = saveBigImage(bi, rootPath, ii.getName());
        ImageInfo smi = saveMidImage(bi, rootPath, ii.getName());
        ImageInfo ssi = saveSmallImage(bi, rootPath, ii.getName());
        ii.setPath(sbi.getPath());
        ii.setSize(sbi.getSize());
        ii.setMidPath(smi.getPath());
        ii.setMidSize(smi.getSize());
        ii.setSmallPath(ssi.getPath());
        ii.setSmallSize(ssi.getSize());
        ii.setType(ii.getName().substring(ii.getName().lastIndexOf(".") + 1));
        return ii;
    }

    /**
     * 缩放图片
     *
     * @param oldBi     图片源
     * @param fixWidth  固定宽度缩小
     * @param fixHeight 固定高度缩小
     * @return
     */
    private static BufferedImage scaleImage(BufferedImage oldBi, Integer fixWidth, Integer fixHeight) throws Exception {
        int width = oldBi.getWidth();
        int height = oldBi.getHeight();
        /*如果输入的固定宽度为空,则按照固定高度缩小,反之亦然*/
        if (fixWidth == null) {
            fixWidth = (width * fixHeight / height);
        } else if (fixHeight == null) {
            fixHeight = (height * fixWidth / width);
        } else {
            throw new Exception("固定宽度或者固定宽度不能同时为空!");
        }
        BufferedImage newBi = new BufferedImage(fixWidth, fixHeight, BufferedImage.TYPE_INT_ARGB);
        Graphics g = newBi.getGraphics();
        g.drawImage(oldBi.getScaledInstance(fixWidth, fixHeight, java.awt.Image.SCALE_SMOOTH), 0, 0, null);
        g.dispose();
        return newBi;
    }

    /**
     * 保存图片为文件
     *
     * @param bi
     * @param fullPath
     * @return
     * @throws IOException
     */
    private static ImageInfo saveImage(BufferedImage bi, String fullPath) throws IOException {
        ImageInfo imageInfo = new ImageInfo();

        File newFile = new File(fullPath);
        if (newFile.exists()) {
            System.out.println("文件已经存在:" + fullPath);
        } else {
            ImageIO.write(bi, "png", newFile);
            imageInfo.setPath(fullPath);
            imageInfo.setSize(newFile.length() + "");
        }
        return imageInfo;
    }

    /**
     * 保存大图片,就是原图
     *
     * @param bi
     * @param path
     * @param fileName
     * @return
     * @throws Exception
     */
    private static ImageInfo saveBigImage(BufferedImage bi, String path, String fileName) throws Exception {
        return saveImage(bi, path + "big." + fileName);
    }

    /**
     * 保存中图片
     *
     * @param bi
     * @param path
     * @param fileName
     * @return
     * @throws Exception
     */
    private static ImageInfo saveMidImage(BufferedImage bi, String path, String fileName) throws Exception {
        bi = scaleImage(bi, null, MID_WIDTH);
        return saveImage(bi, path + "mid." + fileName);
    }

    /**
     * 保存小图片
     *
     * @param bi
     * @param path
     * @param fileName
     * @return
     * @throws Exception
     */
    private static ImageInfo saveSmallImage(BufferedImage bi, String path, String fileName) throws Exception {
        bi = scaleImage(bi, null, SMALL_WIDTH);
        return saveImage(bi, path + "small." + fileName);
    }

}


图片实体:

package com.martsforever.core.img;

public class ImageInfo {
    private String name;
    private String size;
    private String midSize;
    private String smallSize;
    private String type;
    private String dataUrl;
    private String path;
    private String midPath;
    private String smallPath;

    public String getMidSize() {
        return midSize;
    }

    public void setMidSize(String midSize) {
        this.midSize = midSize;
    }

    public String getSmallSize() {
        return smallSize;
    }

    public void setSmallSize(String smallSize) {
        this.smallSize = smallSize;
    }

    public String getMidPath() {
        return midPath;
    }

    public void setMidPath(String midPath) {
        this.midPath = midPath;
    }

    public String getSmallPath() {
        return smallPath;
    }

    public void setSmallPath(String smallPath) {
        this.smallPath = smallPath;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSize() {
        return size;
    }

    public void setSize(String size) {
        this.size = size;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getDataUrl() {
        return dataUrl;
    }

    public void setDataUrl(String dataUrl) {
        this.dataUrl = dataUrl;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }
}


maven中没有其他额外的依赖。在controller中,由于spring boot已经将请求参数体注入到ImageInfo中,所以controller中没有其他逻辑,只有一个数据输入输出;在ImgUtils中,saveDataUrl方法是重点,现在只暴露一个方法,

我们拿到的dataUrl理论上应该是这样的:


很长很长,在解析的时候需要先将开头逗号之前的字符去掉,包括逗号,然后我是顺便将图片保存为大图(原图),中图和小图,如果传入路径参数为空的话,就默认保存在项目根目录下面,最后将大图中图小图的保存路径以及大小放到图片信息实体ImageInfo中,返回给客户端。

在一次文件上传的过程中,java日志只有这么一点:


前端日志:


如果咩有设置特别的保存路径,在项目根目录下会生成三个文件:


猜你喜欢

转载自blog.csdn.net/mate_ge/article/details/78848269