前端框架: react 16.8.6、umi 3.2.20、antd-mobile 2.3.1
后端框架: jdk1.8、spring boot 2.3.4
浏览器:chrome 85.0.4183.121
业务场景:
原有页面是展示一堆图片,这些图片从java后台获取的,后台直接往outputStream输出图片的二进制数据,前端通过img标签的src属性来显示这些图片
现在由于增加了鉴权功能,使用的jwt,往http header里面放jwt来鉴权的,然后就不能使用img的src来做了,现在前端请求使用的umi-request(axios也可以的),配置了拦截器自动添加header里的jwt,所以想通过前端fetch(当然ajax也是可以的)去请求图片,然后通过blob去展示图片
Binary Large Object的缩写,代表二进制类型的大对象。Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。
Blob常用于文件上传下载、图片显示、资源分段上传、读取本地文件
Blob URL/Object URL
Blob URL/Object URL 是一种伪协议,允许 Blob 和 File 对象用作图像,下载二进制数据链接等的 URL 源。在浏览器中,我们使用 URL.createObjectURL 方法来创建 Blob URL,该方法接收一个 Blob 对象,并为其创建一个唯一的 URL,对应的示例如下:
blob:https://example.org/40a5fb5a-d56d-4a33-b4e2-0acf6a8e5f641
浏览器内部为每个通过 URL.createObjectURL 生成的 URL 存储了一个 URL → Blob 映射。因此,此类 URL 较短,但可以访问 Blob。生成的 URL 仅在当前文档打开的状态下才有效。它允许引用 、
中的 Blob,但如果你访问的 Blob URL 不再存在,则会从浏览器中收到 404 错误。
URL.revokeObjectURL(url)
上述的 Blob URL 看似很不错,但实际上它也有副作用。虽然存储了 URL → Blob 的映射,但 Blob 本身仍驻留在内存中,浏览器无法释放它。映射在文档卸载时自动清除,因此 Blob 对象随后被释放。但是,如果应用程序寿命很长,那不会很快发生。因此,如果我们创建一个 Blob URL,即使不再需要该 Blob,它也会存在内存中。
针对这个问题,我们可以调用 URL.revokeObjectURL(url) 方法,从内部映射中删除引用,从而允许删除 Blob(如果没有其他引用),并释放内存。接下来,我们来看一下 Blob 文件下载的具体示例。
首先java端代码,主要就是从sftp里面获取到图片二进制数据,然后输出
@RequestMapping("/getSendImage/{indexId}")
@ResponseBody
@SuppressWarnings("unchecked")
public void getStaticImage(@PathVariable("indexId") Integer indexId,
HttpServletRequest request, HttpServletResponse response) throws IOException {
Map<String, String> payload = (Map<String, String>) request.getAttribute(JwtParams.JWT_PAYLOAD.getValue());
List<Map<String, Object>> contents = objectMapper.readValue(payload.get(JwtParams.CONTENTS.getValue()), List.class);
Long contId = Long.parseLong(String.valueOf(contents.get(indexId).get(JwtParams.CONT_ID.getValue())));
response.setHeader("Content-type", "application/octet-stream;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
ServletOutputStream outputStream = response.getOutputStream();
MMSSendContent sendContent = mmsSendContentService.get(contId);
SFTPTool.downLoadFileForStream("MMS_SEND_RECORD" + File.separator + sendContent.getContPath(), sendContent.getContSrc(), outputStream);
outputStream.flush();
outputStream.close();
}
后台有个返回图片的接口,直接将数据推到OutputStream,原来使用的img src就可以直接接受这种类型的图片,现在由于增加了jwt鉴权,后端没有接收到jwt会在后台拦截器返回401。需要前端在header里面设置jwt,然后通过react的umi-request去发送请求,如果jwt验签不通过,也会在前端的拦截器自动跳转到401页面
接下来就是js的请求
export async function getStaticImage(indexId : number) {
return request(`/aimms/app/mms/getSendImage/${indexId}`, {
method: 'POST',
requestType: 'form',
responseType: 'blob'
});
};
最关键的是这个responseType: ‘blob’,这样配置后,请求后返回的是一个blob,如果用的是axios去请求,那么也可以请求的时候在option里带上responseType: ‘blob’,需要设置这个参数,blob才能正常保存二进制数据
上面是responseType可以设置的值
private fetchStaticImage(index: number) {
return new Promise((resolve, reject) => {
this.props.dispatch({
type: `${namespace}/getStaticImage`,
payload: index,
callback: (data: any) => {
let mmsData = this.state.mmsData;
let imageUrl = URL.createObjectURL(data);
mmsData[index] = {src: imageUrl, type: 'img'};
this.setState({
mmsData,
});
resolve();
},
});
})
}
然后我们拿到这个blob,就可以create URL了,这里我将生成后的URL放进了react的state保存,方便下面读取
这一步遇到的问题:
很奇怪的是原来返回的是二进制,没有加responseType: ‘blob’,然后通过const blob = new Blob([content]);的形式再去创建url,url就无法访问,img上看图片就裂开了,可能是我使用方法不对吧。
后面考虑过后台直接返回图片的base64编码,前端直接使用base64编码来展示图片,但是由于业务的原因,这里图片可能会很大(超过1M),这样转base64后会造成html文本膨胀,影响性能。
所以关键还是要加responseType: ‘blob‘
{
this.state.mmsData.map((e, index) => {
if(e.type === 'img') {
return <div className="text-center" key={index}>
<span className={styles.content}>第{index + 1}页:</span>
<img src={this.state.mmsData[index].src}
style={{width: '100%'}}/>
</div>
})
}
然后在页面上遍历这个集合,渲染出来即可,img的动态src指向生成的blob URL即可
运行起来就可以看到post请求了三张图片,生成url后,再去通过blob的url访问了三张图片
这是post接口返回的数据,可以发现是二进制数据
然后blob返回的,虽然Content-Type: application/octet-stream,但是可以看到图片了
Web API 接口参考Blob
聊聊JS的二进制家族:Blob、ArrayBuffer和Buffer
你不知道的 Blob