通过form表单上传图片时,有时候web容器对文件大小的限制会影响我们上传。这时,前端页面可以考虑将图片转换成base64串来实现上传。可参见:vue利用canvas将图片上传到服务器
■ 图片与Base64的互转,其实就是利用了文件流与Base64的互转
> 文件转换成Base64字符串:读取文件的输入流,因为文件流是字节流,所以要放到byte数组(字节数组,byte取值范围-128~127)里,然后对byte数组做Base64编码,返回字符串。
> Base64串转换成文件:对Base64编码的字符串进行Base64解码,得到byte数组,利用文件输出流将byte数据写入到文件。
介绍一下byte:
byte,即字节,就是我们常说的B(b)。byte由8位二进制组成,在java中,byte类型的数据是8位带符号的二进制数。
在计算机中,8位带符号二进制数的取值范围是[10000000,01111111],其中高位(第一位)代表符号,0代表正数,1代表负数,10000000对应十进制-128,01111111对应十进制127,所以,byte的取值范围是[-128,127]。
现在的文件比如手机拍摄的照片都比较大,几乎不会谈到B了,也很少谈到K(kb)了,常见的是M(mb)。byte数组就是字节数组,byte[] buffer=new byte[1024]代表数组容量为1k。换算单位:1Mb = 1024kb, 1Kb=1024b。
Talk is cheap, show me the code。直接上代码:
import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; import java.io.*; public class ImageBase64Converter { /** * 本地文件(图片、excel等)转换成Base64字符串 * * @param imgPath */ public static String convertFileToBase64(String imgPath) { byte[] data = null; // 读取图片字节数组 try { InputStream in = new FileInputStream(imgPath); System.out.println("文件大小(字节)="+in.available()); data = new byte[in.available()]; in.read(data); in.close(); } catch (IOException e) { e.printStackTrace(); } // 对字节数组进行Base64编码,得到Base64编码的字符串 BASE64Encoder encoder = new BASE64Encoder(); String base64Str = encoder.encode(data); return base64Str; } /** * 将base64字符串,生成文件 */ public static File convertBase64ToFile(String fileBase64String, String filePath, String fileName) { BufferedOutputStream bos = null; FileOutputStream fos = null; File file = null; try { File dir = new File(filePath); if (!dir.exists() && dir.isDirectory()) {//判断文件目录是否存在 dir.mkdirs(); } BASE64Decoder decoder = new BASE64Decoder(); byte[] bfile = decoder.decodeBuffer(fileBase64String); file = new File(filePath + File.separator + fileName); fos = new FileOutputStream(file); bos = new BufferedOutputStream(fos); bos.write(bfile); return file; } catch (Exception e) { e.printStackTrace(); return null; } finally { if (bos != null) { try { bos.close(); } catch (IOException e1) { e1.printStackTrace(); } } if (fos != null) { try { fos.close(); } catch (IOException e1) { e1.printStackTrace(); } } } } }
testcase:
public static void main(String[] args) { long start = System.currentTimeMillis(); String imgBase64Str= ImageBase64Converter.convertFileToBase64("D:\\Pictures\\科技\\liziqi-李子柒爆红.jpg"); // System.out.println("本地图片转换Base64:" + imgBase64Str); System.out.println("Base64字符串length="+imgBase64Str.length()); ImageBase64Converter.convertBase64ToFile(imgBase64Str,"D:\\Pictures\\科技","test.jpg"); System.out.println("duration:"+(System.currentTimeMillis()-start)); start=System.currentTimeMillis(); String fileBase64Str= ImageBase64Converter.convertFileToBase64("D:\\Pictures\\科技\\PayOrderList200109075516581.xlsx"); // System.out.println("本地excel转换Base64:" + fileBase64Str); System.out.println("Base64字符串length="+fileBase64Str.length()); ImageBase64Converter.convertBase64ToFile(fileBase64Str,"D:\\Pictures\\科技","test.xlsx"); System.out.println("duration:"+(System.currentTimeMillis()-start)); }
执行结果:
文件大小(字节)=2820811 Base64字符串length=3860058 duration:244 文件大小(字节)=25506 Base64字符串length=34902 duration:10
提醒一下:获取文件的大小是用FileInputStream实例的available()方法哦,用File实例的length()返回的是0。
如下图,测试方法里图片文件“liziqi-李子柒爆红.jpg”的大小正是2820811字节 ÷1024=2755KB ÷1024=2.68M
■ Fastdfs工具类中的图片上传
FastdfsClientUtil中关于图片上传,有3个重载方法。
- public String uploadImageAndCrtThumbImage(MultipartFile myfile) throws Exception
- public String uploadImageAndCrtThumbImageByStream(InputStream inputStream,int size,String fileExtName) throws Exception
- public String uploadImageAndCrtThumbImageByBase64(String base64ImgData) throws Exception
在这里,我说一下uploadImageAndCrtThumbImageByBase64这个方法。它的入参是一个base64串。注意这个这个参数的值,在base64串的前头有一个前缀,诸如 "data:image/gif;base64,"、"data:image/jpeg;base64,"。其中gif、jpeg是目标图片文件的扩展名。这正适合于前端页面通过base64串来上传图片的场景。
下面贴出来这个方法的实现,一看便知。
import com.github.tobato.fastdfs.domain.fdfs.StorePath; import com.github.tobato.fastdfs.exception.FdfsUnsupportStorePathException; import com.github.tobato.fastdfs.service.FastFileStorageClient; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import sun.misc.BASE64Decoder; import java.io.ByteArrayInputStream; import java.io.InputStream; @Component public class FastdfsClientUtil { private final Logger logger = LoggerFactory.getLogger(FastdfsClientUtil.class); @Autowired private FastFileStorageClient storageClient; //上传文件并生成缩略图 public String uploadImageAndCrtThumbImageByBase64(String base64ImgData) throws Exception { BASE64Decoder decoder = new BASE64Decoder(); String[] base64ImageSplit = base64ImgData.split(","); byte[] result = decoder.decodeBuffer(base64ImageSplit[1]);//解码 String fileExtName = base64ImageSplit[0].substring(base64ImageSplit[0].indexOf("/")+1,base64ImageSplit[0].indexOf(";")); for (int i = 0; i < result.length; ++i) { if (result[i] < 0) {// 调整异常数据 result[i] += 256; } } InputStream inputStream = new ByteArrayInputStream(result); return uploadImageAndCrtThumbImageByStream(inputStream, result.length,fileExtName ); } }
测试用例:
@Test public void uploadPng() throws Exception { String imgBase64="/9j/4AAQSkZJRgABAQEAYABgAAD/7QjCUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA\n" + "AgBIAAAAAQACOEJJTQQNAAAAAAAEAAAAHjhCSU0EGQAAAAAABAAAAB44QklNA/MAAAAAAAkAAAAA\n" + "AAAAAAEAOEJJTQQKAAAAAAABAAA4QklNJxAAAAAAAAoAAQAAAAAAAAACOEJJTQP1AAAAAABIAC9m\n" + "ZgABAGxmZgAGAAAAAAABAC9mZgABAKGZmgAGAAAAAAABADIAAAABAFoAAAAGAAAAAAABADUAAAAB\n" + "AC0AAAAGAAAAAAABOEJJTQP4AAAAAABwAAD/////////////////////////////A+gAAAAA////\n" + "/////////////////////////wPoAAAAAP////////////////////////////8D6AAAAAD/////\n" + ...... "0HoaKACg80GjvQISijrmjFMD/9k="; String fileName = fastdfsClientUtil.uploadImageAndCrtThumbImageByBase64("data:image/png;base64,"+imgBase64); System.out.println("------------" + fileName); //fileName:上传后的文件(含路径):group1/M00/00/20/wKgoVF8hHdSAaRrkAAAlClwx2Hg883.png //访问:http://192.168.40.84:8888/group1/M00/00/20/wKgoVF8hHdSAaRrkAAAlClwx2Hg883.png }