又遇到一个文件上传MINE TYPE的坑,这次是压缩文件-rar、zip
一、背景:产品在一次迭代中新增了上传文件类型,支持zip和rar文件
OS:这还不简单,网上搜了一下这两个文件对应的MIME TYPE,搜索结果如下:
// .rar
application/vnd.rar
//.zip
application/zip
于是在原来旧的MIMT TYPE对象加键值对:
// Mine Type 映射
export const MINE_TYPE = {
COMMON: 'application/octet-stream',
PDF: 'application/pdf',
DOC: 'application/msword',
DOCX: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
XLS: 'application/vnd.ms-excel',
XLSX: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
PNG: 'image/png',
JPG: 'image/jpeg', // jpg和jpeg都是这个
GIF: 'image/gif',
BMP: 'image/bmp',
APK: 'application/vnd.android.package-archive',
TXT: 'text/plain',
HTML: 'text/html',
CSV: 'text/csv',
MP3: 'audio/mpeg',
RAR: 'application/vnd.rar', // 新加
ZIP: 'application/zip', // 新加
};
import { invert } from 'lodash';
// 获取文件名后缀
const getFileSuffix = (filename: string) => {
const index = filename.lastIndexOf('.');
if (index !== -1) {
return filename.substr(index + 1);
}
return false;
};
// 检查上传文件类型
export const checkTypes = (file: RcFile, typeList: string[], forceSuffix: boolean = false) => {
const type = typeList;
if (type && file && file.type && !forceSuffix) {
return type.includes(file.type);
}
const suffix = getFileSuffix(file.name);
if (suffix && file && file.name) {
let pass = false;
const mine = invert(MINE_TYPE);
typeList.forEach((item) => {
if (!pass) {
pass = suffix.toLocaleUpperCase() === mine[item];
}
});
return pass;
}
return false;
};
// 轉換文件為相應的單位
export const transSize = (limit: number, base: string = 'bytes', isBit: boolean = true) => {
const baseByte = isBit ? 1024 : 1000;
let bytes;
switch (base.toLowerCase()) {
case 'mb':
bytes = limit * baseByte * baseByte;
break;
default:
bytes = limit;
break;
}
if (bytes === 0) return '0 B';
const k = baseByte;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${(bytes / k ** i).toPrecision(3)} ${sizes[i]}`;
};
// 获取限制文件大小
export const checkMaxSize = (size: number, maxSize: number) => {
return size / 1024 / 1024 < maxSize;
};
export const asyncBeforeUpload = async (file: RcFile, $_props: UploadPlusProps) => {
let mathType = true;
let ltSize = true;
if ($_props.mineType) {
mathType = checkTypes(file, $_props.mineType);
if (!mathType) {
notifiy.error('文件類型不符合, 請檢查後重試');
}
}
if ($_props.maxSize) {
ltSize = checkMaxSize(file.size, $_props.maxSize);
if (!ltSize) {
notifiy.error('文件大小必須小於'+transSize($_props.maxSize || 5, 'mb'))
}
}
return ltSize && mathType;
};
二、发现异常
1、上传rar文件type不符预期
上传时rar文件,在asyncBeforeUpload方法中,我的电脑拿到file的type是type: ''
,根本没办法用includes这样的方式去判断文件类型,所以进行改造:
export const asyncBeforeUpload = async (file: RcFile, $_props: UploadPlusProps) => {
let mathType = true;
let ltSize = true;
if ($_props.mineType) {
// 處理type為空的文件類型
const fileSuffix = file.name.replace(/.+\./, '');
// const fileSuffix = getFileSuffix(file.name);
const special_file_list: Array<keyof typeof MIME_TYPE> = ['RAR'];
const isSpecialFile = (special_file_list as string[]).includes(fileSuffix.toLocaleUpperCase());
// 特殊文件用文件名后缀判断
mathType = checkTypes(file, $_props.mineType, isSpecialFile);
if (!mathType) {
notifiy.error('文件類型不符合, 請檢查後重試');
}
}
if ($_props.maxSize) {
ltSize = checkMaxSize(file.size, $_props.maxSize);
if (!ltSize) {
notifiy.error(notifiy.error('文件大小必須小於'+transSize($_props.maxSize || 5, 'mb')));
}
}
return ltSize && mathType;
};
后面让同事本地调试了一下下,发现他的电脑可以拿到type的值!!!所以又在MIME_TYPE加了一下注释,以警示后人
// Mine Type 映射
export const MINE_TYPE = {
COMMON: 'application/octet-stream',
PDF: 'application/pdf',
DOC: 'application/msword',
DOCX: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
XLS: 'application/vnd.ms-excel',
XLSX: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
PNG: 'image/png',
JPG: 'image/jpeg', // jpg和jpeg都是这个
GIF: 'image/gif',
BMP: 'image/bmp',
APK: 'application/vnd.android.package-archive',
TXT: 'text/plain',
HTML: 'text/html',
CSV: 'text/csv',
MP3: 'audio/mpeg',
// RAR类型不准确,仅做标识使用,例如我的电脑会直接返回空字符串
// RAR类型还有两个值是application/x-rar-compressed、application/x-compressed
RAR: 'application/vnd.rar',
ZIP: 'application/zip',
ZIP2: 'application/x-zip-compressed',
};
2、上传zip文件type我电脑符合预期,但是测试的电脑不符合预期
我的电脑拿到的type是application/zip
,但是测试的电脑拿到的是application/x-zip-compressed
,由于在我电脑没发现这个问题,导致上测试出现bug,所以要对MIME_TYPE做以下调整:
// Mine Type 映射
export const MINE_TYPE = {
COMMON: 'application/octet-stream',
PDF: 'application/pdf',
DOC: 'application/msword',
DOCX: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
XLS: 'application/vnd.ms-excel',
XLSX: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
PNG: 'image/png',
JPG: 'image/jpeg', // jpg和jpeg都是这个
GIF: 'image/gif',
BMP: 'image/bmp',
APK: 'application/vnd.android.package-archive',
TXT: 'text/plain',
HTML: 'text/html',
CSV: 'text/csv',
MP3: 'audio/mpeg',
RAR: 'application/vnd.rar',
// ZIP文件类型要一起用,不用系统、不同内核浏览器类型不一样
ZIP: 'application/zip',
ZIP2: 'application/x-zip-compressed',
};
使用时:
// 传入支持的类型:
mineType={[
// ...
MINE_TYPE.RAR,
MINE_TYPE.ZIP,
MINE_TYPE.ZIP2,
]}
三、总结
不同电脑、不同浏览器获取的file.type可能不一样!!!
rar文件:
以下是通义千问的回答:
rar文件上传type可能的值:
// 1.空
''
// 2
application/x-rar-compressed
// 3
application/vnd.rar
zip文件:
以下是通义千问的回答:
zip文件上传type可能的值:
// 1
application/x-zip
// 2
application/x-zip-compressed