Java 下载url图片 二进制图片转MultipartFile文件上传图片

HttpUrlConnection 基础使用
IOUtils快速进行内容复制与常用方法

附一个方便查看图片base64的工具网址:图片base64互转工具

下载图片

在项目中有需求是将当前上传Amazon S3上的图片在发送一份至其他系统中保存,对方提供的接口是传图片base64和图片名称,上传成功后返回一个图片id。而库中取到的是上传s3后拿到的url。通过url先获取图片二进制,再base64发送给对方。
通过url下载图片,获得图片二进制数据代码如下:

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;

public class PictureUtil {
	//获取图片二进制
    private byte[] downloadPicture(String url){
        URL urlConnection = null;
        HttpURLConnection httpURLConnection = null;
        try {
            urlConnection = new URL(url);
            httpURLConnection = (HttpURLConnection) urlConnection.openConnection();
            InputStream in = httpURLConnection.getInputStream();
            byte[] buffer = new byte[1024];
            int len = 0;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            while ((len = in.read(buffer)) != -1){
                out.write(buffer,0,len);
            }
            in.close();
            out.close();
            return out.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            httpURLConnection.disconnect();
        }
        return null;
    }

    public String getPictureBase64(String url){
    	//base64编码
        return Base64.getEncoder().encodeToString(downloadPicture(url));
    }
}

通过url获取图片,实际上就是发送一个http的get请求,因此使用HttpURLConnection的默认参数就可达到目的。如果发送其他请求,可以对其设置请求头或响应头等。

一个支持HTTP特定功能的URLConnection。

使用这个类遵循以下模式:
  1.通过调用URL.openConnection()来获得一个新的HttpURLConnection对象,并且将其结果强制转换为HttpURLConnection.
  2.准备请求。一个请求主要的参数是它的URI。请求头可能也包含元数据,例如证书,首选数据类型和会话cookies.
  3.可以选择性的上传一个请求体。HttpURLConnection实例必须设置setDoOutput(true),如果它包含一个请求体。通过将数据写入一个由getOutStream()返回的输出流来传输数据。
  4.读取响应。响应头通常包含元数据例如响应体的内容类型和长度,修改日期和会话cookies。响应体可以被由getInputStream返回的输入流读取。如果响应没有响应体,则该方法会返回一个空的流。
  5.关闭连接。一旦一个响应体已经被阅读后,HttpURLConnection 对象应该通过调用disconnect()关闭。断开连接会释放被一个connection占有的资源,这样它们就能被关闭或再次使用。

注意:之前有使用InputStream.available()方法获取可读字节的长度,以new相同长度的byte数组读近byte直接返回,代码如下:

    private byte[] downloadPicture(String url){
        URL urlConnection = null;
        HttpURLConnection httpURLConnection = null;
        try {
            urlConnection = new URL(url);
            httpURLConnection = (HttpURLConnection) urlConnection.openConnection();
            InputStream in = httpURLConnection.getInputStream();
            //使用available()方法获取数据长度
            byte[] data = new byte[in.available()];
            in.read(data);
            in.close();
            return data;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            httpURLConnection.disconnect();
        }
        return null;
    }

在本地测试时,试过两三次,都可以拿到图片完整的流数据,但是到部署到uat环境上时,通过打印日志发现有时候能拿到图片数据,有时候拿不到数据。
原因正是available使用的不对,InputStream.available()读取文件流长度不不完整,该方法读取本地文件时一般不会出现问题,但是通过网路传输就会出现图片传输不完整的情况,因为网络通讯是间断性的一串字节往往分几批进行发送。
通过查看InputStream.available()源码的注释,也发现注释也说明了该问题:

 * 返回可以从此输入流读取(或跳过)的字节数的估计值,而不会因下一次调用此输入流的方法而阻塞。下一次调用可能是同一个线程或另一个线程。单次读取或跳过这么多字节不会阻塞,但可能读取或跳过更少的字节。
 
 * 请注意,虽然 {@code InputStream} 的某些实现会返回流中的总字节数,但很多不会。使用此方法的返回值来分配用于保存此流中所有数据的缓冲区永远是不正确的。

 * 如果此输入流已通过调用 {@link #close()} 方法关闭,则此方法的子类实现可能会选择抛出 {@link IOException}。

 * {@code InputStream} 类的 {@code available} 方法总是返回 {@code 0}。

 * 这个方法应该被子类覆盖。

因此不应使用该方法企图获取正确的文件的数据长度。

上传图片

当获取该图片时,通过对方返回的图片id,调用他们的获取图片接口,将返回图片base64的数据。将图片还原成MultipartFile上传至Amazon S3。将MultipartFile上传至S3是项目中已有的方法和功能,此处只需拿到图片二进制数据,转换为MultipartFile文件,调用已有方法即可。
通过搜索资料得知MockMultipartFile可以转换,代码如下:

import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;
import java.util.Base64;

public class PictureUtil {
    public MultipartFile getFileByPictureBase64(String name, String pic) {
        byte[] buffer = Base64.getDecoder().decode(pic);
        return getMultipartFile(name, buffer);
    }

    //二进制->MultipartFile
    private MultipartFile getMultipartFile(String name, byte[] bytes) {
        MockMultipartFile mockMultipartFile = null;
        try {
            mockMultipartFile = new MockMultipartFile("name", bytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return mockMultipartFile;
    }
}

但是MockMultipartFile是在spring-test包下,要加依赖。此外该包是单元测试时使用的,不适合在业务中使用。因此改用其他方法。
通过FileItem对象,使用CommonsMultipartFile类获取MultipartFile,MultipartFile 是接口, CommonsMultipartFile 是其实现类。代码如下:

import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;

public class PictureUtil {

    //二进制文件转换MultipartFile
    public MultipartFile getMultipartFile(String name, byte[] bytes) {
        MultipartFile mfile = null;
        ByteArrayInputStream in = null;
        try {
            in = new ByteArrayInputStream(bytes);
            FileItemFactory factory = new DiskFileItemFactory(16, null);
            FileItem fileItem = factory.createItem("mainFile", "text/plain", false, name);
            IOUtils.copy(new ByteArrayInputStream(bytes), fileItem.getOutputStream());
            mfile = new CommonsMultipartFile(fileItem);
            in.close();
        }catch (Exception e){
            e.printStackTrace();
        }
        return mfile;
    }
}

通过ByteArrayInputStream字节流读入bytes数组,使用IOUtils类的方法直接拷贝到新建的FileItem对象的输出流中,使用FileItem对象构造CommonsMultipartFile,从而得到MultipartFile。

总结

本文讲述了项目实际过程中遇到的两个问题的解决方法:

  1. 通过url获取图片二进制:使用URL和HttpURLConnection发送http请求获取图片数据流,使用循环读取数据方式读取,不建议使用available()判断数据长度;
  2. 通过二进制获取MultipartFile:使用FileItemFactory创建一个FileItem来构造CommonsMultipartFile,不建议使用spring-test包下MockMultipartFile。
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值