用 Base64 上传存储图片

Table of Contents

从互联网上东抄一点西抄一点 + 本地测试得来的缝合之作

获取二进制文件格式

在 Postman 中上传图片,我选择在 body 中加入 binary 数据;这样,Headers 中会自动加入包括Content-Type之类的键值对。

不过因为Content-Type不能用@RequestParam直接获取,自动生成的Content-Type也不会匹配multipart/form-data,所以就需要自己做一些文件格式的识别。恰好这里只需要上传图片,所以非常好做。

Controller的声明是:public Result upload(@RequestBody byte[] binaryData),其中Result用来 wrap 返回数据。

也就是说,用字节数组的方式接收二进制文件。

首先是对上传的字节数组做校验:

if (binaryData == null || binaryData.length <= 0) {
    return Result.error(405, "文件错误");
}

接下来获取文件的十六进制形式的字符串,用来获取文件头。用文件头获取文件格式,比后缀名更准确。

这部分代码参考的是 https://blog.csdn.net/dongyuxu342719/article/details/96447444

StringBuilder builder = new StringBuilder();
String hv;
for (int i = 0; i < 4; ++i) {
    // 以十六进制(基数 16)无符号整数形式返回一个整数参数的字符串表示形式,并转换为大写
    hv = Integer.toHexString(binaryData[i] & 0xFF).toUpperCase();
    if (hv.length() < 2) {
        builder.append(0);
    }
    builder.append(hv);
}
String header = builder.toString();

不过更改了循环次数,因为只需要关心文件头,对后面的内容并不关心。

这样以后,就能提取到文件头了,通过文件头可以确定文件格式:

String HTTP_Content_Type = null;
if(header.startsWith("FFD8FF"))HTTP_Content_Type = "image/jpeg";
else if(header.startsWith("89504E47"))HTTP_Content_Type = "image/png";
else if(header.startsWith("47494638"))HTTP_Content_Type = "image/gif";
else HTTP_Content_Type = "application/octet-stream";

如果不在三种图片格式之内,一律认为是二进制流数据(application/octet-stream,如常见的文件下载)就行了。

生成 MultipartFile 类型的图片文件

这一步是不必要的。

这一步是不必要的。

这一步是不必要的。

留个优化的口子 其实是因为一开始没有考虑到上传 MultipartFile 如此麻烦于是转用字节数组导致的

用字节数组构造一个输入流:InputStream inputStream = new ByteArrayInputStream(binaryData);

开始构建:

MultipartFile multipartFile;
String str = null;
try {
    multipartFile = new MockMultipartFile(HTTP_Content_Type, inputStream);
    str = picService.img2base(multipartFile, HTTP_Content_Type).getData().toString();
    // System.out.println(str);
} catch(Exception e) {
    e.printStackTrace();
}

构造 MultipartFile 居然还要再引入MockMultipartFile,吐了(

Base64 和 MultipartFile 互转

图片转 Base64 字符串

public Result img2base(MultipartFile multipartFile, String HTTP_Content_Type) {
    // 需要知道 HTTP_Content_Type 以确定图片格式
    // 至于确定这个字符串的值,在 Controller 中完成即可
    try {
        Base64.Encoder encoder = Base64.getEncoder();
        String imgData = encoder.encodeToString(multipartFile.getBytes());
        imgData = "data:" + HTTP_Content_Type + ";base64," + imgData;
        return Result.success(imgData);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return Result.error(405, "编码图像失败");
}

Base64 的编码器 / 解码器都在自带的库里,import 下即可。

生成的 Base64 字符串前要加入形如"data:image/png;base64,"的字符串。

Base64 字符串转图片

public Result base2img(String imgData) {
    // 拿出文件头
    // 第一个 Magic number,也就是 5,是因为字符串以"data:"开始,文件头总是从第五位开始
    // 第二个 Magic number,也就是 8,是因为";base64"长度为 8,之后全为 base64 编码的数据
    int pos = imgData.indexOf(";base64");
    String HTTP_Content_Type = imgData.substring(5, pos);
    imgData = imgData.substring(pos + 8);
    try {
        Base64.Decoder decoder = Base64.getDecoder();
        // 把 Base64 字符串变为 byte 数组
        byte[] binaryFile = decoder.decode(imgData);
        // 修正异常数据
        for (int i = 0; i < binaryFile.length; ++i) {
            if(binaryFile[i] < 0) binaryFile[i] += 256;
        }
        // 生成 MultipartFile 类型的图片
        InputStream inputStream = new ByteArrayInputStream(binaryFile);
        MultipartFile multipartFile = new MockMultipartFile(HTTP_Content_Type, inputStream);
        return Result.success(multipartFile);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return Result.error(405, "读取图像失败");
}

简单来说,就是拿出形如"data:image/png;base64,"的头,再把后面正文内容用 Base64 转回byte[]

测试时,可以用 MultipartFile 的transferTo方法,把文件输出出来看看;也可以把 Base64 字符串和网上的一些在线转换得到的结果做对比,例如站长工具提供的。

Share