用户反馈说看不到候选人上传了图片了,截图看了下页面,确实图片没法正常显示了。

本来怀疑是公司的OSS出了问题,返回了有问题的链接。
让后端捞了一下日志,得到图片的http地址。
点了一下获取到图片源文件。看了一下,好家伙,这图片不是正常的吗?

排查了半天没发现问题,其他图片都是好的,凭什么就这个图片不正常。于是怀疑到图片本身。
起了一下项目,本来想直接通过相对路径访问,看看是不是我代码写的有问题导致图片没法渲染出来。
结果刚把图片放入VS Code里,好家伙,直接显示加载图片出错。这下看来没跑了,就是图片有问题。

但图片又是有什么问题呢?说损坏了,但图片在系统里又能正常预览… 突然灵光一闪,会不会是文件的后缀名被改过呢?
我去问问万能的ChatGPT老师,怎么判断文件的后缀是否被改过,于是学习到了魔术数字这个概念。

魔术数字就是在文件头的开头会有一个固定的字符串来表示文件的类型。跟看到身份证号前两位是11就能快速知道这个人是京爷一样,识别一下这个魔术数字就能快速识别文件格式。

因为直接改后缀名并不会影响文件的内容,我们写个小Demo读取一下文件,看看他到底是什么类型的文件。只要他的开头是FF D8 FF,就能表示它确实是jpg文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const reader = new FileReader()
reader.onload = async function (e) {
const arrayBuffer = e.target.result

const uint8Array = new Uint8Array(arrayBuffer)

const hexString = Array.from(uint8Array)
.map((byte) => byte.toString(16).padStart(2, '0'))
.join(' ')

console.log(hexString) // 输出16进制字符串

}

reader.readAsArrayBuffer(file)

结果出来了。emm…虽然不知道是什么类型的文件,不过看着开头显然不是jpg

至于它原来到底是什么类型的文件,这里有个好用的工具:file-type

1
2
3
4
5
6
7
8
9
10
import { fileTypeFromBuffer } from 'file-type'

const reader = new FileReader()
reader.onload = async function (e) {
const arrayBuffer = e.target.result
const res = await fileTypeFromBuffer(arrayBuffer)
console.log(res);
}

reader.readAsArrayBuffer(file)

输出的结果是这个,heic是ios拍照存储的图片格式,看来用户是直接把他的后缀改了来绕过前端的校验。

1
{ext: 'heic', mime: 'image/heic'}

看来后续除了直接在代码里校验文件的后缀名外,还要校验文件是否确实为正确的图片格式。