从互联网上东抄一点西抄一点 + 本地测试得来的缝合之作
获取二进制文件格式
在 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 字符串和网上的一些在线转换得到的结果做对比,例如站长工具提供的。
1 Response
[…] 具体的阶段性内容见 用 Base64 上传存储图片。 […]