场景

为保护用户隐私,图片上传至服务器后服务器做了按位异或处理,达到无法直接通过图片链接或图片文件查看用户所上传图片内容的目的。
如此就要求前端展示时对图片二进制流做相反的位操作才能展示图片内容。

关键代码与注释

此处用原生方式书写,实际应用时根据所使用 ajax 库的语法修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const request = new XMLHttpRequest();
//构造get方法用于异步请求图片链接
request.open('get', "/img/encripted.jpg", true);
//以arraybuffer的类型解析服务器返回数据
request.responseType = 'arraybuffer';
//得到服务器数据后的回调函数
request.onload = function(){
const data = request.response;
//使用DataView遍历与操作二进制流
let bufferReader = new DataView(data);
let bufferStr = '';
//以每8位为一组,每组按照无符号整数解析成字符串
for(let i=0;i<bufferReader.byteLength;i++)
{
//假设服务器做的是按位与100做异或处理,则此处再做一次异或处理即可将数据恢复
bufferStr += String.fromCharCode(bufferReader.getUint8(i) ^ 100);
}
//将字符串转为Base64编码格式,使用Base64图片格式展示图片数据内容
$('#the-img').attr('src', `data:image/png;base64, ${window.btoa(bufferStr)}`);
};
request.send();

关键技术点解析

指定 ajax 请求的返回数据类型

◎ ajax 请求的 responseType 参数指定将以哪种数据类型的数据结构解析服务器返回数据,进而使用数据类型提供的对应函数对数据进行读取或写入操作,
可取值及含义可参考MDN web doc 之 XMLHttpRequest.responseType

处理二进制数据

◎ 处理二进制数据需要先将原始二进制数据存放在一段连续的内存区域中,ArrayBuffer 对象就是用来映射一段通用的、固定长度的内存缓冲区的,通过 ArrayBuffer 的 byteLength 属性可得到缓冲区的字节数,但它未提供直接按字节操作缓冲区的函数,按字节操作需要通过类型数组对象DataView 对象进行。

使用“类型数组对象”

◎ 类型数组对象包含以下几种类型

以 Int8Array 为例,基础用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
//arraybuffer缓冲区
let buffer = new ArrayBuffer(8);
//以int8Array类型解析缓冲区,即以每8位作为一个单位,每个单位用8位二进制有符号整数解析
let int8Array1 = new Int8Array(buffer);
//输出字节长度8,这个字节长度不会随着解析类型不同而变化
console.log(int8Array2.byteLength);
//以30的有符号整数的二进制(即二进制序列:00011110)填充缓冲区的前8位
int8Array1[0] = 30;
//int8Array2解析同样的缓冲区
let int8Array2 = new Int8Array(buffer);
// 输出30 说明缓冲区已经被int8Array1修改
console.log(int8Array2[0]);
使用“DataView 对象”

◎ DataView 通过一系列 get 和 set 方法(set 和 get 后接类型数组对象的类型名,如 setUint8)读取或写入缓冲区。
基础用法如下:

1
2
3
4
5
6
7
8
9
10
//arraybuffer缓冲区
let buffer = new ArrayBuffer(8);
//以DataView解析缓冲区
let bufferReader = new DataView(buffer);
//输出字节长度8,这个字节长度不会随着解析类型不同而变化
console.log(bufferReader.byteLength);
//以30的有符号整数的二进制(即二进制序列:00011110)填充缓冲区的前8位
bufferReader.setInt8(0, 30);
// 输出30 缓冲区已经被bufferReader修改
console.log(bufferReader.getInt8(0));

对二进制数据的字符编码

◎ String 的静态方法”fromCharCode”接受一个 Unicode 值参数,返回这个 Unicode 值对应的字符。
◎ Base64 是一种基于 64 个可打印字符表示二进制数据的方法,转换过程中每 6 位为一组,详情整理在这篇
◎ WindowOrWorkerGlobalScope.btoa()用于从 String 对象创建一个 Base64 编码的 ASCII 字符串,转换过程中字符串里的每个字符都被视为一个二进制数据字节。