java 上传文件注意事项
1、文件名有特殊字符的情况,所以最好是文件名前台url编码,后台再url解码,这点在下载的时候也一样
2、文件大小一定要设置,spring boot 有默认。
3、文件名校验:
3.1、文件后缀校验
3.2、content-type校验
3.3、通过文件流,校验文件头,真实校验
其中,单纯的文件后缀和content-type校验,容易被绕过,只有文件流才能真实的判断出这个文件是什么类型的。
一般的做法就是先通过后缀过滤,绝大部分请求还是正常人
然后再通过流截取文件头信息,判断文件的真实类型。
特别注意: txt文本文档时没有头的,所以不能通过判断头来过滤,以下是网友的一些建议,摘过来供参考:
bmp,jpg等图象文件,可以判断文件头,来确定它的文件类型
但txt没有文件头的,无法判断它是否是txt文件
所以,只能分析文件中所有数据,看看是不是都是可以显示的字符
如果都是可以显示的字符,说明它可以当作txt文件进行阅读,否则,会有乱码出现
这显然要先了解一下什么是文本类的文件,其实说白了,就是ACSII码文件。二进制文件与文本文件的显著差别在于:二进制文件中可能会出现数目众多的0,而文本文件中则不会有,因为文本文件中除了一些特殊的控制字符(比如\n,\r,\t)以外,都是可见字符。
补充1:
除了.txt文件以外,其它的各种文件似乎都会有二进制文件头,这些文件头都很特别,比如0xFEFE、0xFDFD等等。必须说明的一点是.doc文件严格的说,它不应该是一个文本文件,因为它内部有大量的不可见的控制字符,而且最关键的是,它内部允许包含0,并且它有二进制的头数据块。
如果你真想鉴别所有的,你所期望的文本类文件的话,比如.txt、.doc这些文件,你就必须了解这些文件的文件格式,当然.txt是没有固定格式的,但它有一个特征是,不会含有0。
补充2:
对于unicode的文本文件来说,它也是有文件头的,根据大小尾的不同,分别是FFFE和FEFF,严格的说,unicode的文件,不能说是文本文件。
回到你的补充问题,如果想确认一个文件是不是文本文件,加入说是ANSI的,你可以遍历整个文件,看是否存在0,如果没有那么就是了,如果具有FFFE或者FEFF文件头的unicode文件,那么你所关注的对象是,00,也就是两个连续的0。
个人认为,
要么就是把所有可能的文件后缀都放在那个判断的map中(下面代码中会初始化),然后再拿不到就认为是txt类型
要么就是遍历整个文件,看是否存在0,没有就是txt
下面是相关代码:
@Slf4j
public class FileCheckUtil {// 缓存文件头信息-文件头信息private static final HashMap<String, String> MFILETYPES = new HashMap<String, String>();static {//pdf,word,excel,pptMFILETYPES.put("25504446", "pdf");MFILETYPES.put("255044462D312E", "pdf");MFILETYPES.put("D0CF11E0", "xls");//ppt、doc、xls 2003版本文件MFILETYPES.put("504B0304", "xlsx");//pptx、docx、xlsx 2007以上版本文件// imagesMFILETYPES.put("FFD8FFE0", "jpg");MFILETYPES.put("FFD8FF", "jpg");MFILETYPES.put("89504E47", "png");MFILETYPES.put("47494638", "gif");MFILETYPES.put("49492A00", "tif");MFILETYPES.put("424D", "bmp");// CADMFILETYPES.put("41433130", "dwg");//音视频MFILETYPES.put("57415645", "wav");MFILETYPES.put("41564920", "avi");MFILETYPES.put("2E524D46", "rm");MFILETYPES.put("000001BA", "mpg");MFILETYPES.put("000001B3", "mpg");MFILETYPES.put("6D6F6F76", "mov");MFILETYPES.put("3026B2758E66CF11", "asf");//其他MFILETYPES.put("38425053", "psd");MFILETYPES.put("7B5C727466", "rtf"); // 日记本MFILETYPES.put("3C3F786D6C", "xml");MFILETYPES.put("68746D6C3E", "html");MFILETYPES.put("44656C69766572792D646174653A", "eml"); // 邮件MFILETYPES.put("5374616E64617264204A", "mdb");MFILETYPES.put("252150532D41646F6265", "ps");MFILETYPES.put("52617221", "rar");MFILETYPES.put("4D546864", "mid");MFILETYPES.put("1F8B08", "gz");}public static String getRealFileType(FileInputStream is) {return MFILETYPES.get(getFileHeader(is));}public static String getFileHeader(FileInputStream is) {String value = null;try {byte[] b = new byte[4];/** int read() 从此输入流中读取一个数据字节。int read(byte[] b) 从此输入流中将最多 b.length* 个字节的数据读入一个 byte 数组中。 int read(byte[] b, int off, int len)* 从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。*/int read = is.read(b, 0, b.length);log.debug("read:{}", read);value = bytesToHexString(b);} catch (Exception e) {} finally {if (null != is) {try {is.close();} catch (IOException e) {}}}return value;}private static String bytesToHexString(byte[] src) {StringBuilder builder = new StringBuilder();if (src == null || src.length <= 0) {return null;}String hv;for (int i = 0; i < src.length; i++) {// 以十六进制(基数 16)无符号整数形式返回一个整数参数的字符串表示形式,并转换为大写hv = Integer.toHexString(src[i] & 0xFF).toUpperCase();if (hv.length() < 2) {builder.append(0);}builder.append(hv);}log.debug("builder.toString()={}", builder.toString());return builder.toString();}public static void main(String[] args) throws Exception {// final String fileType = getRealFileType(new FileInputStream("D:\\ccc2007.doc"));
// System.out.println(fileType);}
}
正式的controller:
//提前定义支持的后缀类型private static final List<String> FILETYPELIST = Arrays.asList("txt", "doc", "docx", "xls", "xlsx", "pdf");@PostMapping(value = "/upload")public BaseResponseDTO uploadFile(@RequestParam("file") MultipartFile file) throws IOException {BaseResponseDTO data = checkFile(file);if (data != null) {return data;}// 获取文件名String originalFilename = file.getOriginalFilename();long now = System.currentTimeMillis();//文件名是以时间戳_真实文件名存储的originalFilename = now + "_" + originalFilename;// 设置文件存储路径String filePath = "/app/upload/";String path = filePath + originalFilename;File dest = new File(path);file.transferTo(dest);// 文件写入return new BaseResponseDTO<>(HTTPState.OK.getCode(), HTTPState.OK.getMsg(), originalFilename);}private String checkFile(MultipartFile file) throws IOException {//校验文件为空if (file.isEmpty()) {return "error";}//简单校验后缀String fileName = file.getOriginalFilename();if (fileName != null) {String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);log.debug("suffix:" + suffix);if (!FILETYPELIST.contains(suffix)) {return "error";}}//校验文件真实类型String fileType = FileCheckUtil.getRealFileType((FileInputStream) file.getInputStream());log.debug("fileType:" + fileType);if (fileType != null && !FILETYPELIST.contains(fileType)) {return "error";}return null;}