SpringBoot文件上传与校验
文章目录
- 一、简介
- 1、概述
- 2、环境与技术介绍
- 3、简单的文件上传
- 二、文件校验与上传实战
- 1、 前提准备
- 2、 文件枚举类
- 3、 自定义文件校验注解
- 4、 文件校验切面
- 5、 文件上传工具类
- 6、 控制类
- 7、 配置文件
- 8、 文件的前端显示
- 三、阿里云OSS文件上传
- 1、 阿里云oss配置
- 2、 Java整合oss
- 3、 注意事项
一、简介
1、概述
文件上传是Web项目的一个基本功能,一般是通过上传文件的后缀名进行格式校验,但是由于文件的后缀是可以手动更改的,黑客可以通过修改后缀名入侵文件服务器,因此后缀名校验不是一种严格有效的文件校验方式。如果想要对上传文件进行严格的格式校验,则需要通过文件头进行校验,即魔数,文件头是位于文件开头的一段承担一定任务的数据,一般都在开头的部分,其作用就是为了描述一个文件的一些重要的属性,其可以作为是一类特定文件的标识。
2、环境与技术介绍
SpringBoot2.5.6,AOP思想
使用切面编程,在文件上传之前,通过自定义注解首先进行自定义文件类型判断,若判断不通过,则通过全全局自定义异常返回,通过所有检查后才进行文件的上传,同时通过ConditionalOnProperty
注解可以在application.yml
中进行注解文件的打开或关闭,即校验文件功能的开启与关闭。
3、简单的文件上传
@Value("${file.staticPath}")private String staticPath;@Value("${file.uploadFolder}")private String uploadFolder;/*** 上传文件,比较通用的方法,这里我写在这里可以进行参考修改* 其他方法*/public String uploadFile(MultipartFile multipartFile, String dir) {try {//上传的文件:aaa.jpgString realFileName = multipartFile.getOriginalFilename();//2:藏图文件名的后级String imgSuffix = realFileName.substring(realFileName.lastIndexOf("."));//3:生成的唯一的文件名:能不能用中文名:不能因为统一用英文命名。String newFileName = UUID.randomUUID().toString() + imgSuffix;//4:日期目录SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");String datePath = dateFormat.format(new Date());//5:服务路径String serverName = uploadFolder;//6:指定文件上传以后的目录File targetPath = new File(serverName + dir, datePath);if (!targetPath.exists()) {targetPath.mkdirs();}//6:指定文件上传以后的服务器的完整的文件名File targetFileName = new File(targetPath, newFileName);//7:文件上传到指定的目录multipartFile.transferTo(targetFileName);// 返回的自由选择,可以选择Map进行返回String fileName = dir + File.separator + datePath + File.separator + newFileName;return staticPath + File.separator + fileName;} catch (IOException e) {e.printStackTrace();return "fail";}}
yml中进行配置
file:staticPatternPath: /upload/**uploadFolder: /www/upload/staticPath: http://www.shawn22.xyz:8080
二、文件校验与上传实战
1、 前提准备
SpringBoot Log4j2日志
SpringBoot自定义全局异常
2、 文件枚举类
包含了每种文件的后缀名与头部魔数
/*** 文件类型* 文件魔数* @author Shawn* @date 2021/11/23*/
@Getter
public enum FileType {/*** JPEG (jpg)*/JPEG("JPEG", "FFD8FF"),JPG("JPG", "FFD8FF"),/*** PNG*/PNG("PNG", "89504E47"),/*** GIF*/GIF("GIF", "47494638"),/*** TIFF (tif)*/TIFF("TIF", "49492A00"),/*** Windows bitmap (bmp)*/BMP("BMP", "424D"),/*** 16色位图(bmp)*/BMP_16("BMP", "424D228C010000000000"),/*** 24位位图(bmp)*/BMP_24("BMP", "424D8240090000000000"),/*** 256色位图(bmp)*/BMP_256("BMP", "424D8E1B030000000000"),/*** CAD (dwg)*/DWG("DWG", "41433130"),/*** Adobe photoshop (psd)*/PSD("PSD", "38425053"),/*** Rich Text Format (rtf)*/RTF("RTF", "7B5C727466"),/*** XML*/XML("XML", "3C3F786D6C"),/*** HTML (html)*/HTML("HTML", "68746D6C3E"),/*** Email [thorough only] (eml)*/EML("EML", "44656C69766572792D646174653A"),/*** Outlook Express (dbx)*/DBX("DBX", "CFAD12FEC5FD746F "),/*** Outlook (pst)*/PST("", "2142444E"),/*** doc;xls;dot;ppt;xla;ppa;pps;pot;msi;sdw;db*/OLE2("OLE2", "0xD0CF11E0A1B11AE1"),/*** Microsoft Word/Excel 注意:word 和 excel的文件头一样*/XLS("XLS", "D0CF11E0"),/*** Microsoft Word/Excel 注意:word 和 excel的文件头一样*/DOC("DOC", "D0CF11E0"),/*** Microsoft Word/Excel 2007以上版本文件 注意:word 和 excel的文件头一样*/DOCX("DOCX", "504B0304"),/*** Microsoft Word/Excel 2007以上版本文件 注意:word 和 excel的文件头一样 504B030414000600080000002100*/XLSX("XLSX", "504B0304"),/*** Microsoft Access (mdb)*/MDB("MDB", "5374616E64617264204A"),/*** Adobe Acrobat (pdf) 255044462D312E*/PDF("PDF", "25504446"),/*** Windows Password (pwl)*/PWL("PWL", "E3828596"),/*** WAVE (wav)*/WAV("WAV", "57415645"),/*** AVI*/AVI("AVI", "41564920"),/*** Real Audio (ram)*/RAM("RAM", "2E7261FD"),/*** Real Media (rm) rmvb/rm相同*/RM("RM", "2E524D46"),/*** Real Media (rm) rmvb/rm相同*/RMVB("RMVB", "2E524D46000000120001"),/*** MPEG (mpg)*/MPG("MPG", "000001BA"),/*** Quicktime (mov)*/MOV("MOV", "6D6F6F76"),/*** MIDI (mid)*/MID("MID", "4D546864"),/*** MP4*/MP4("MP4", "00000020667479706D70"),/*** MP3*/MP3("MP3", "49443303000000002176"),/*** FLV*/FLV("FLV", "464C5601050000000900"),/*** torrent*/TORRENT("TORRENT", "6431303A637265617465"),/*** JSP Archive*/JSP("JSP", "3C2540207061676520"),/*** JAVA Archive*/JAVA("JAVA", "7061636B61676520"),/*** CLASS Archive*/CLASS("CLASS", "CAFEBABE0000002E00"),/*** JAR Archive*/JAR("JAR", "504B03040A000000"),/*** MF Archive*/MF("MF", "4D616E69666573742D56"),/*** EXE Archive*/EXE("EXE", "4D5A9000030000000400"),/*** ELF Executable*/ELF("ELF", "7F454C4601010100"),/*** Lotus 123 v1*/WK1("WK1", "2000604060"),/*** Lotus 123 v3*/WK3("WK3", "00001A0000100400"),/*** Lotus 123 v5*/WK4("WK4", "00001A0002100400"),/*** Lotus WordPro v9*/LWP("LWP", "576F726450726F"),/*** Sage(sly.or.srt.or.slt;sly;srt;slt)*/SLY("SLY", "53520100");/*** 后缀 大写字母*/private final String suffix;/*** 魔数*/private final String magicNumber;FileType(String suffix, String magicNumber) {this.suffix = suffix;this.magicNumber = magicNumber;}@NonNullpublic static FileType getBySuffix(String suffix) throws FileUploadException {for (FileType fileType : FileType.values()) {if (fileType.getSuffix().equals(suffix.toUpperCase())) {return fileType;}}throw new FileUploadException("不支持的文件后缀 : " + suffix);}
}
3、 自定义文件校验注解
/*** 文件检查** @author Shawn* @date 2021/11/23*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public @interface FileCheck {/*** 校验不通过提示信息** @return*/String message() default "不支持的文件格式";/*** 校验方式*/CheckType type() default CheckType.SUFFIX;/*** 支持的文件后缀** @return*/String[] supportedSuffixes() default {};/*** 支持的文件类型** @return*/FileType[] supportedFileTypes() default {};enum CheckType {/*** 仅校验后缀*/SUFFIX,/*** 校验文件头(魔数)*/MAGIC_NUMBER,/*** 同时校验后缀和文件头*/SUFFIX_MAGIC_NUMBER}
}
4、 文件校验切面
/*** @author Shawn* @date 2021年11月23日9:32* prefix为配置文件中的前缀,* name为配置的名字* havingValue是与配置的值对比值,当两个值相同返回true,配置类生效* 需要在yml中进行配置:前缀+名字,值为true,表示该配置文件生效**/
@Aspect
@Slf4j
@Component
@ConditionalOnProperty(prefix = "file-check", name = "enabled", havingValue = "true")
public class FileCheckAspect {/*** 目标方法:被@FileCheck注解的方法即为目标方法* 其中@annotation中的值,需要和target方法中参数名称相同(必须相同,但是名称任意)** @param joinPoint 连接点* @param annotation 文件检查*/@Before("@annotation(annotation)")public void before(JoinPoint joinPoint, FileCheck annotation) throws FileUploadException {final String[] suffixes = annotation.supportedSuffixes();final FileCheck.CheckType type = annotation.type();final FileType[] fileTypes = annotation.supportedFileTypes();final String message = annotation.message();// 支持的文件后缀和文件类型有一个为空则返回if (ArrayUtils.isEmpty(suffixes) && ArrayUtils.isEmpty(fileTypes)) {return;}Object[] args = joinPoint.getArgs();//文件后缀转成set集合Set<String> suffixSet = new HashSet<>(Arrays.asList(suffixes));for (FileType fileType : fileTypes) {suffixSet.add(fileType.getSuffix());}//文件类型转成set集合Set<FileType> fileTypeSet = new HashSet<>(Arrays.asList(fileTypes));for (String suffix : suffixes) {fileTypeSet.add(FileType.getBySuffix(suffix));}//对参数是文件的进行校验for (Object arg : args) {if (arg instanceof MultipartFile) {doCheck((MultipartFile) arg, type, suffixSet, fileTypeSet, message);} else if (arg instanceof MultipartFile[]) {for (MultipartFile file : (MultipartFile[]) arg) {doCheck(file, type, suffixSet, fileTypeSet, message);}}}}/*** 根据指定的检查类型对文件进行校验*/private void doCheck(MultipartFile file, FileCheck.CheckType type, Set<String> suffixSet, Set<FileType> fileTypeSet, String message) throws FileUploadException {if (type == FileCheck.CheckType.SUFFIX) {doCheckSuffix(file, suffixSet, message);} else if (type == FileCheck.CheckType.MAGIC_NUMBER) {doCheckMagicNumber(file, fileTypeSet, message);} else {doCheckSuffix(file, suffixSet, message);doCheckMagicNumber(file, fileTypeSet, message);}}/*** 验证文件魔数*/private void doCheckMagicNumber(MultipartFile file, Set<FileType> fileTypeSet, String message) throws FileUploadException {String magicNumber = readMagicNumber(file);String fileName = file.getOriginalFilename();String fileSuffix = fileName.substring(fileName.lastIndexOf(".") + 1).toUpperCase();for (FileType fileType : fileTypeSet) {if (magicNumber.startsWith(fileType.getMagicNumber()) && fileType.getSuffix().toUpperCase().equalsIgnoreCase(fileSuffix)) {return;}}log.error("文件头格式错误:{}", magicNumber);throw new FileUploadException(message);}/*** 验证文件后缀*/private void doCheckSuffix(MultipartFile file, Set<String> suffixSet, String message) throws FileUploadException {String fileName = file.getOriginalFilename();String fileSuffix = fileName.substring(fileName.lastIndexOf(".") + 1).toUpperCase();for (String suffix : suffixSet) {if (suffix.toUpperCase().equalsIgnoreCase(fileSuffix)) {return;}}log.error("文件后缀格式错误:{}", message);throw new FileUploadException(message);}/*** 读取文件,获取文件头*/private String readMagicNumber(MultipartFile file) throws FileUploadException {try (InputStream is = file.getInputStream()) {byte[] fileHeader = new byte[4];is.read(fileHeader, 0, 4);return byteArray2Hex(fileHeader);} catch (IOException e) {log.error("文件读取错误:{0}", e);throw new FileUploadException("读取文件失败!");}}/*** 字节数组转十六进制*/private String byteArray2Hex(byte[] data) {StringBuilder stringBuilder = new StringBuilder();if (ArrayUtils.isEmpty(data)) {return null;}for (byte datum : data) {int v = datum & 0xFF;String hv = Integer.toHexString(v).toUpperCase();if (hv.length() < 2) {stringBuilder.append(0);}stringBuilder.append(hv);}return stringBuilder.toString();}}
5、 文件上传工具类
/*** 文件上传工具类* @author Shawn* @date 2021年11月22日19:45**/
public class FileUtils {private static final Logger logger = LoggerFactory.getLogger(FileUtils.class);/*** 文件上传** @param file 文件* @return {@link String}* @throws Exception 异常*/public static String fileUpload(Integer type, Integer userId,MultipartFile file) throws FileUploadException {// 获取文件名,带后缀String originalFilename = file.getOriginalFilename();// 获取文件的后缀格式String fileSuffix = originalFilename.substring(originalFilename.lastIndexOf(".") + 1).toLowerCase();String filePrefix = String.valueOf(System.currentTimeMillis()).concat(String.valueOf(type)).concat(String.valueOf(userId));String newFileName = filePrefix.concat(".").concat(fileSuffix);String dirPath;// 判断上传类型if(type == 0 ){dirPath = FileLocationEnum.LocalVideoLocation.getLocation();}else{dirPath = FileLocationEnum.LocalPicLocation.getLocation();}String path = dirPath + newFileName;File destFile = new File(dirPath + newFileName);if (!destFile.getParentFile().exists()) {destFile.getParentFile().mkdirs();}try {file.transferTo(destFile);logger.info("单次上传文件成功");// 将相对路径返回给前端return path;} catch (IOException e) {logger.error("upload pic error");throw new FileUploadException("上传文件错误");}}/*** 文件上传的图片** @param type 类型,图片为1,视频为0* @param userId 用户id* @param files 文件* @return {@link List<String>}*/public static List<String> fileUploadWithPics(int type, Integer userId, MultipartFile[] files) throws FileUploadException {List<String> picList = new ArrayList<>();for (MultipartFile file:files) {picList.add(fileUpload(type,userId,file));}logger.info("多图片文件上传成功");return picList;}}
6、 控制类
这里提供了一个视频上传接口和多图片上传接口
/*** @author Shawn* @date 2021年11月22日21:09**/
@RestController
@RequestMapping("/file")
public class FileUploadController {/*** 文件上传的图片* 同时校验后缀和文件头* @param userId 用户id* @param file 文件* @return {@link ResultVO<?>}* @throws Exception 异常*/@PostMapping("/fileuploadwithpics")@FileCheck(message = "不支持的图片格式",supportedSuffixes = {"png", "jpg", "jpeg"},type = FileCheck.CheckType.SUFFIX_MAGIC_NUMBER,supportedFileTypes = {FileType.PNG, FileType.JPG, FileType.JPEG})public ResultVO<?> fileUploadWithPics(Integer userId, @RequestParam("pics") MultipartFile[] MultipartFile) throws Exception {if(userId==null){return new ResultVO<>(400,"缺少userId参数");}// 1表示图片,0 表示视频List<String> result = FileUtils.fileUploadWithPics(1, userId, MultipartFile);Map<String, List<String>> map = new HashMap<>(4);map.put("picUrl",result);return new ResultVO<>(map);}/*** 文件上传视频* 仅校验后缀* @param userId 用户id* @param file 文件* @return {@link ResultVO<?>}* @throws Exception 异常*/@PostMapping("/fileuploadwithvideo")@FileCheck(message = "不支持的视频格式",type = FileCheck.CheckType.SUFFIX,supportedSuffixes = {"mp4","gif"})public ResultVO<?> fileUploadWithVideo(Integer userId, @RequestParam("video") MultipartFile file) throws Exception {if(userId==null){return new ResultVO<>(400,"缺少userId参数");}String s = FileUtils.fileUpload(0, userId, file);Map<String, String> map = new HashMap<>(4);map.put("videoUrl",s);return new ResultVO<>(map);}}
7、 配置文件
在application.yml
进行配置
spring:servlet:multipart:enabled: true# 单个文件大小,m默认1Mmax-file-size: 10MB# 总上传文件大小,默认10Mmax-request-size: 30MB# 文件多少时写入磁盘,默认为0,有文件就写入# file-size-threshold: 10MB
8、 文件的前端显示
一种是Nginx进行映射,这种方式比较常见;另一种是SpringBoot自带的映射穿透,需要在application配置好映射关系,或者在java里配置好映射关系。
若视频放在D:\social\
文件夹下,最终资源访问路径http://ip:port/social/xxxx
Yml配置文件方式
spring:mvc:static-path-pattern: /social/**web:resources:static-locations: file:D:\social\
javaBean配置方式
/**#application.yml中的配置file:staticPatternPath: /social/**uploadFolder: file:D:\social\
*///这个注解必须加,将该bean交给Spring管理,否则无法解析@Value
@Component
public class WebMvcConfig implements WebMvcConfigurer {@Value("${file.staticPatternPath}")private String staticPatternPath;@Value("${file.uploadFolder}")private String uploadFolder;// 这个方法是springboot中springMvc让程序开发者去配置文件上传的额外的静态资源服务的配置@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {// staticPatternPath 是访问路径,后面的是上传的资源路径// uploadFolder 是文件存储位置,而文件保存在uploadFolder 目录下registry.addResourceHandler(staticPatternPath).addResourceLocations(uploadFolder);}
}
三、阿里云OSS文件上传
1、 阿里云oss配置
首先开通阿里云oss,选择公共读,这样别人才可以读到我们的文件,但这样可能会导致上行流量剧增
创建玩Bucket后,需要配置一下ssl证书和已备案自定义域名,否则浏览器只能下载,不能读
最后获取AccessKey和SecretKey。进入 AccessKey管理 ,进入之后选择开始使用子用户AccessKey(推荐,这样安全),创建子用户,选择openAPI访问,创建完成后,添加AliyunOSSFullAccess权限
2、 Java整合oss
官方教程:https://help.aliyun.com/document_detail/84778.html
下面简单说一下配置,首先配置maven
<dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.10.2</version>
</dependency>
创建上传方法
public static String uploadFile(MultipartFile multipartFile) {// yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。String endpoint = "oss-cn-hangzhou.aliyuncs.com";// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。String accessKeyId = "";String accessKeySecret = "";// 你的桶名字String bucketName = "";// 你的自定义域名,需要备案和配好ssl证书String domainName = "";// 桶里面你的根目录String rootPath = "lamp";OSS ossClient = null;try {// 创建OSSClient实例。ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);// 获取文件上传的流InputStream inputStream = multipartFile.getInputStream();// 构建指定目录,按日期分类SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");String datePath = dateFormat.format(new Date());// 获取文件名String originName = multipartFile.getOriginalFilename();String filename = UUID.randomUUID().toString();String suffix = originName.substring(originName.lastIndexOf("."));String newName = filename + suffix;String fileUrl = rootPath + "/" + datePath + "/" + newName;// 上传文件ossClient.putObject(bucketName, fileUrl, inputStream);return "https://" + domainName + "/" + fileUrl;} catch (IOException e) {e.printStackTrace();return "fail";} finally {// 关闭OSSClient。ossClient.shutdown();}}
3、 注意事项
使用 OSS 默认域名访问 html、图片资源,会有以附件形式下载的情况。若需要浏览器直接访问,需使用自定义域名进行访问,同时保证已经配置好ssl证书;同时oss桶还可以用来做图床
其他请参考官方文档
参考文献:
https://www.jianshu.com/p/be3f4c26c39a
https://www.cnblogs.com/zys2019/p/15394599.html
https://www.bilibili.com/video/BV1C3411b7wt?p=15&spm_id_from=pageDriver
SpringBoot文件上传与校验相关推荐
- 补习系列(11)-springboot 文件上传原理
一.文件上传原理 一个文件上传的过程如下图所示: 浏览器发起HTTP POST请求,指定请求头: Content-Type: multipart/form-data 服务端解析请求内容,执行文件保存处 ...
- SpringBoot文件上传源码解析
一.SpringMVC文件上传源码分析前言(这部分我觉得原作者写的很好) 该如何研究SpringMVC的文件上传的源码呢? 研究源码并不是仅仅知道程序是怎样运行的,而应该从宏观的角度.不同的立场去看待 ...
- 解决Springboot文件上传报错,java.io.FileNotFoundException: D:\System\Temp\tomcat.819...00.tmp (系统找不到指定的文件。)
Springboot文件上传,csdn上的方法无非是下面这两个: imgFile.transferTo(imageFolder); // 方法一/*** 方法二* FileUtils.copyInpu ...
- springboot文件上传下载实战 ——文件上传、下载、在线打开、删除
springboot文件上传下载实战 文件上传 文件上传核心 UserFileController 文件上传测试 文件下载与在线打开 文件下载.在线打开核心 UserFileController 文件 ...
- springboot文件上传下载实战 —— 登录功能、展示所有文件
springboot文件上传下载实战 创建项目 pom.xml 数据库建表与环境准备 建表SQL 配置文件 application.properties 整体架构 前端页面 登录页面 login.ht ...
- SpringBoot文件上传异常之提示The temporary upload location xxx is not valid
SpringBoot文件上传异常之提示The temporary upload location xxx is not valid 参考文章: (1)SpringBoot文件上传异常之提示The te ...
- SpringBoot 文件上传 通过Content-Type和文件头判断文件类型
SpringBoot 文件上传 通过Content-Type和文件头判断文件类型 一.关于MIME MIME的全称是Multipurpose Internet Mail Extensions,即多用途 ...
- springboot文件上传、下载使用ftp工具将文件上传至服务器
springboot文件上传.下载使用ftp工具 首先在服务器搭建ftp服务 配置文件(在application.properties中) # Single file max size multipa ...
- springboot文件上传,单文件上传和多文件上传,以及数据遍历和回显
springboot文件上传,单文件上传和多文件上传 项目结构及pom.xml 创建文件表单页面 编写javabean 编写controller映射 MultipartFile类 @RequestPa ...
最新文章
- 美国国家科学委员会发布学术研发报告
- MySQL之帮助的使用
- 如何加入IETF 如何发表自己的RFC
- 吴继业:LinkedIn商业分析部如何运用大数据实现商业价值
- matlab如何使用cu文件,Matlab编译cuda的.cu文件
- 注释嵌套注释_DIY注释
- Arbitrage——判断正环Bellman-Ford/SPFA
- Effective Java 电子书 apk版本下载
- 4001.基于双向链表的双向冒泡排序法
- 趣文:有趣的 Linux 命令
- 组策略参考文档1-共享打印机
- php图像处理缩略图,17.ThinkPHP 扩展库:图像处理--生成缩略图
- 功能强大的全新虚拟商品自动发货商城源码
- c语言程序后退_单片机控制小车循迹(前进、后退、左右转)
- Android 微信聊天页面
- win7双屏幕,双任务栏
- 详细了解SQLITE 优缺点 性能测试
- html 条纹背景,CSS3 一组条纹背景图案
- 沙漠 草原 湖泊 羊群 骆驼(2)
- 复盘二: 了解自我和管理自我,诚惶诚恐,保持敬畏-- 宁向东的清华管理学课总结