springboot整合阿里云OSS实现多线程下文件上传(aop限制文件大小和类型) 您所在的位置:网站首页 gitee上传文件大小限制 springboot整合阿里云OSS实现多线程下文件上传(aop限制文件大小和类型)

springboot整合阿里云OSS实现多线程下文件上传(aop限制文件大小和类型)

#springboot整合阿里云OSS实现多线程下文件上传(aop限制文件大小和类型)| 来源: 网络整理| 查看: 265

内容涉及:

springboot整合阿里云oss

自定义注解及aop的使用:对上传文件格式(视频格式、图片格式)、不同类型文件进行大小限制(视频和图片各自自定义大小)

线程池使用:阿里云OSS多线程上传文件

阿里云OSS分片上传大文件

 

业务需求

需求一:

前端传递单个或多个小文件(这里以图片为例)到后端;

后端对图片进行处理,并上传至阿里云oss;

上传完毕之后,返回图片链接给前端,如果格式支持,可以在线预览。

 

需求二:

前端上传大文件(这里以视频为例)到后端;

后端对视频进行分片处理,上传到oss;

上传完毕后,返回视频连接给前端。

 

过滤文件

主要使用了aop切面+自定义注解来切入请求中,过滤文件的类型,将不符合要求的文件剔除;

这里主要实现注解的方式来过滤文件类型和自定义文件大小

 

添加自定义注解类 package org.aliyunoss.aop; import org.aliyunoss.utils.FileLimitUnit; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; /** * @Description :FileLimit 注解,内置参数value,max,以及文件单位 */ @Documented @Target(ElementType.METHOD) // 作用与方法上 @Retention(RetentionPolicy.RUNTIME) // RUNTIME: 在运行时有效(即运行时保留) public @interface FileLimit { @AliasFor("max") // @AliasFor 表示其可与max互换别名:当注解指定value时,为max赋值 int value() default 5; // 定义单个文件最大限制 @AliasFor("value") // @AliasFor 表示其可与value互换别名:当注解指定max是,为value赋值 int max() default 5; // 文件单位,默认定义为MB //定义单次上传文件的总大小,默认50MB int maxRequestSize() default 50; //上传文件格式,默认是图片 String fileFormat() default "images"; FileLimitUnit unit() default FileLimitUnit.MB; }

可以根据实际需求,这里设置了注解的几个参数,分别是

max()/value(),表示文件最大限制,默认为5, maxRequestSize(),单次请求的总文件大小,即一次请求上传的多个文件的总大小,默认为50 fileFormat(),表示文件格式,默认是图片"images",如果上传视频的话需要改成"videos",文件类型可以根据需求自定义,修改getFileFormatLimit()方法即 FileLimitUnit类型的unit()参数表示文件大小的类型,默认是MB,可以设置成KB或GB

 

 

添加aop切面类

@Before:前置通知, 在目标方法(切入点)执行之前执行。

因为我们需要在请求过来时就对文件过滤,所以这里使用前置通知@Before

springboot中使用aop功能需要引入aop的依赖

org.springframework.boot spring-boot-starter-aop 2.5.0

 

添加aop切面类

package org.aliyunoss.aop; import cn.hutool.core.io.FileTypeUtil; import org.aliyunoss.utils.MyFileUtils; import org.aliyunoss.vo.ErrorCode; import org.aliyunoss.vo.MyAppException; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import javax.annotation.processing.FilerException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Aspect @Component public class FileLimitAop { // 定义默认的单个文件最大限制 5MB 。5Mb = 5 * 1024 * 1024 byte private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; private static final long MAX_REQUEST_SIZE = 50 * 1024 * 1024; // 注意,这里要指定注解的全限定类名。不然无法进入AOP拦截自定义注解FileLimit @Pointcut("@annotation(org.aliyunoss.aop.FileLimit)") public void pointcut() { } /** * 方法体执行之前执行 */ @Before("pointcut()") public void beforeLog(JoinPoint joinPoint) throws FilerException { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); FileLimit annotation = AnnotationUtils.getAnnotation(signature.getMethod(), FileLimit.class); if (null == annotation) { return; } // 执行文件检查 fileSizeLimit(joinPoint, annotation); } // 判定文件大小是否合格,如果不合格,直接跑出自定义异常FileLimitException。进而阻塞方法正常进行。 private void fileSizeLimit(JoinPoint joinPoint, FileLimit annotation) throws FilerException { // 获取AOP签名 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 获取注解的指定最大文件大小 Map annotationMaxFileSize = getAnnotationMaxFileSize(annotation); long maxFileSize = (long) annotationMaxFileSize.get("maxFileSize"); long maxRequestSize = (long) annotationMaxFileSize.get("maxRequestSize"); // 通过AOP签名 获取接口参数,调用方法获取文件 //= List multipartFileList= new ArrayList(); Object[] args = joinPoint.getArgs(); for (Object arg : args) { multipartFileList = (List) arg; } //保存总文件大小 long maxSize = 0; for (MultipartFile multipartFile : multipartFileList) { if (null != multipartFile) { long size = multipartFile.getSize(); if (0 == size) { //自定义异常 throw new MyAppException(ErrorCode.FILE_DATA_EXCEPTION.getCode(), "文件数据(大小为0)异常"); } maxSize += size; if (multipartFile.getSize() > maxFileSize) { String msg = "文件大小不得超过 " + annotation.max() + annotation.unit().toString(); //System.out.println(msg); throw new MyAppException(ErrorCode.FILE_DATA_EXCEPTION.getCode(), msg); } } else { throw new MyAppException(ErrorCode.FILE_DATA_EXCEPTION.getCode(), "文件为null"); } } if (maxSize > maxRequestSize) { throw new MyAppException(ErrorCode.FILE_DATA_EXCEPTION.getCode(), "单次上传总文件大小不能超过" + annotation.maxRequestSize() + annotation.unit()); } List fileFormatLimit = getFileFormatLimit(annotation); //判断文件格式 根据文件头信息判断 for (MultipartFile multipartFile : multipartFileList) { String type = FileTypeUtil.getType(MyFileUtils.multipartFileToFile(multipartFile)); //判断类型是String类型,因此可以直接用contains方法 boolean contains = fileFormatLimit.contains(type); if (!contains){ throw new MyAppException(ErrorCode.FILE_DATA_EXCEPTION.getCode(), "文件格式不正确"); } } } // 获取使用注解指定最大文件大小。如果没有指定文件大小,就用默认值 public Map getAnnotationMaxFileSize(FileLimit fileLimit) { Map map = new HashMap(); if (null == fileLimit) { map.put("maxFileSize", MAX_FILE_SIZE); map.put("maxRequestSize", MAX_REQUEST_SIZE); return map; } switch (fileLimit.unit()) { case MB: map.put("maxFileSize", (long) fileLimit.max() 0)) { int dot = filename.lastIndexOf('.'); if ((dot > -1) && (dot < (filename.length() - 1))) { return filename.substring(dot); } } return filename; } /** * Description: 判断OSS服务文件上传时文件的contentType * @param filenameExtension 文件后缀 * @return String */ public static String getContentType(String filenameExtension) { String contentType = ""; switch(filenameExtension.toUpperCase()) { //image contentType case "BMP": contentType = "image/bmp";break; case "GIF": contentType = "image/gif";break; case "JPEG": case "JPG":contentType = "image/jpg";break; case "ICO": contentType="image/x-icon";break; case "TIF": case "TIFF": contentType="image/tiff";break; case "PNG": contentType = "image/png";break; case "WBMP": contentType = "image/vnd.wap.wbmp";break; case "WEBP": contentType = "image/webp";break; case "JFIF": contentType = "image/jpeg";break; //video contentType case "AVI": contentType = "video/avi";break; case "FLV": contentType = "video/x-flv";break; case "MP4": contentType = "video/mpeg4";break; case "MPEG": contentType = "video/mpg";break; case "WMV": contentType = "video/x-ms-wmv";break; case "WMA": contentType = "video/wma";break; case "W4A": contentType = "video/mp4";break; case "W4V": contentType = "video/mp4";break; case "WOV": contentType = "video/quicktime";break; case "3GP": contentType = "video/3gpp";break; case "WEBM": contentType = "video/webm";break; case "VOB": contentType = "video/vob";break; case "MKV": contentType = "video/x-matroska";break; case "HTML": contentType = "text/html";break; case "TXT": contentType = "text/plain";break; case "VSD": contentType = "application/vnd.visio";break; case "PPTX": case "PPT": contentType = "application/vnd.ms-powerpoint";break; case "DOCX": contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";break; case "DOC": contentType = "application/msword";break; case "XLSX": contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";break; case "XLS": contentType = "application/vnd.ms-excel";break; case "XML": contentType = "text/xml";break; case "PDF": contentType = "application/pdf";break; default:contentType="file"; } return contentType; } }

 

hutool工具类中判断文件类型的是 FileTypeUtil工具类,通过调用FileTypeUtil.getType即可判断。原理:通过读取文件流中前几位byte值来判断文件类型,如果判断不出来的话那么就会根据文件后缀来判断。

 

FileTypeUtil局限性:对于文本、zip判断不准确,对于视频、图片类型判断准确

 

通过查看源码中的FILE_TYPE_MAP,可以看到,FileTypeUtile可以识别出以下文件类型:

FILE_TYPE_MAP.put("ffd8ff", "jpg"); // JPEG (jpg) FILE_TYPE_MAP.put("52494646", "webp"); FILE_TYPE_MAP.put("89504e47", "png"); // PNG (png) FILE_TYPE_MAP.put("4749463837", "gif"); // GIF (gif) FILE_TYPE_MAP.put("4749463839", "gif"); // GIF (gif) FILE_TYPE_MAP.put("49492a00227105008037", "tif"); // TIFF (tif) // https://github.com/sindresorhus/file-type/blob/main/core.js#L90 FILE_TYPE_MAP.put("424d", "bmp"); // 位图(bmp) FILE_TYPE_MAP.put("41433130313500000000", "dwg"); // CAD (dwg) FILE_TYPE_MAP.put("7b5c727466315c616e73", "rtf"); // Rich Text Format (rtf) FILE_TYPE_MAP.put("38425053000100000000", "psd"); // Photoshop (psd) FILE_TYPE_MAP.put("46726f6d3a203d3f6762", "eml"); // Email [Outlook Express 6] (eml) FILE_TYPE_MAP.put("5374616E64617264204A", "mdb"); // MS Access (mdb) FILE_TYPE_MAP.put("252150532D41646F6265", "ps"); FILE_TYPE_MAP.put("255044462d312e", "pdf"); // Adobe Acrobat (pdf) FILE_TYPE_MAP.put("2e524d46000000120001", "rmvb"); // rmvb/rm相同 FILE_TYPE_MAP.put("464c5601050000000900", "flv"); // flv与f4v相同 FILE_TYPE_MAP.put("0000001C66747970", "mp4"); FILE_TYPE_MAP.put("00000020667479706", "mp4"); FILE_TYPE_MAP.put("00000018667479706D70", "mp4"); FILE_TYPE_MAP.put("49443303000000002176", "mp3"); FILE_TYPE_MAP.put("000001ba210001000180", "mpg"); // FILE_TYPE_MAP.put("3026b2758e66cf11a6d9", "wmv"); // wmv与asf相同 FILE_TYPE_MAP.put("52494646e27807005741", "wav"); // Wave (wav) FILE_TYPE_MAP.put("52494646d07d60074156", "avi"); FILE_TYPE_MAP.put("4d546864000000060001", "mid"); // MIDI (mid) FILE_TYPE_MAP.put("526172211a0700cf9073", "rar"); // WinRAR FILE_TYPE_MAP.put("235468697320636f6e66", "ini"); FILE_TYPE_MAP.put("504B03040a0000000000", "jar"); FILE_TYPE_MAP.put("504B0304140008000800", "jar"); // MS Excel 注意:word、msi 和 excel的文件头一样 FILE_TYPE_MAP.put("d0cf11e0a1b11ae10", "xls"); FILE_TYPE_MAP.put("504B0304", "zip"); FILE_TYPE_MAP.put("4d5a9000030000000400", "exe"); // 可执行文件 FILE_TYPE_MAP.put("3c25402070616765206c", "jsp"); // jsp文件 FILE_TYPE_MAP.put("4d616e69666573742d56", "mf"); // MF文件 FILE_TYPE_MAP.put("7061636b616765207765", "java"); // java文件 FILE_TYPE_MAP.put("406563686f206f66660d", "bat"); // bat文件 FILE_TYPE_MAP.put("1f8b0800000000000000", "gz"); // gz文件 FILE_TYPE_MAP.put("cafebabe0000002e0041", "class"); // class文件 FILE_TYPE_MAP.put("49545346030000006000", "chm"); // chm文件 FILE_TYPE_MAP.put("04000000010000001300", "mxp"); // mxp文件 FILE_TYPE_MAP.put("6431303a637265617465", "torrent"); FILE_TYPE_MAP.put("6D6F6F76", "mov"); // Quicktime (mov) FILE_TYPE_MAP.put("FF575043", "wpd"); // WordPerfect (wpd) FILE_TYPE_MAP.put("CFAD12FEC5FD746F", "dbx"); // Outlook Express (dbx) FILE_TYPE_MAP.put("2142444E", "pst"); // Outlook (pst) FILE_TYPE_MAP.put("AC9EBD8F", "qdf"); // Quicken (qdf) FILE_TYPE_MAP.put("E3828596", "pwl"); // Windows Password (pwl) FILE_TYPE_MAP.put("2E7261FD", "ram"); // Real Audio (ram) // https://stackoverflow.com/questions/45321665/magic-number-for-google-image-format

 

注意,该工具类对 xlsx、docx等Office2007的格式,全部识别为zip,因为新版采用了OpenXML格式,这些格式本质上是XML文件打包成zip

 

解决方案 需求一:上传多个小文件

通常的解决方案是前端传入multipartFiles集合到后端接口,通过自定义注解+aop切面来对文件进行过滤,筛选出不符合要求的文件(如文件过大,文件类型不匹配),然后通过单线程循环调用阿里云OSS提供的putObject方法,将获取到的文件集合按照顺序逐一上传到OSS之中

缺点:网络不良时,容易造成文件丢失,需要重新上传,进而提高等待时间

 

 

 

上传文件核心代码:

@Override public List uploadImages(List multipartFile) { this.ossClient = AliyunOssConfig.createOss(aliyunOssConfig); //保存上传后返回的云端文件URLs List responseUrls = new ArrayList(); //设置url过期时间 上传时间后五年后失效 Date expiration = new Date(System.currentTimeMillis() + 5 * 365 * 24 * 3600 * 1000); String dir = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); try { for (MultipartFile file : multipartFile) { String originalFilename = file.getOriginalFilename(); String cloudFileName = new StringBuilder() .append(UUID.randomUUID().toString()) .append(MyFileUtils.getExtensionName(originalFilename)) .toString(); //阿里云OSS bucket下存储位置 String cloudPath = dir + "/" + cloudFileName; //设置ContentType,使得返回的url可以在网页中预览(仅有少部分格式支持在线预览) 默认不设置或不支持在线预览的,返回的url是下载附件,而不是预览(可以从前端传个参数来判断是在线预览还是下载) ObjectMetadata objectMetadata = new ObjectMetadata(); //判断文件类型(获取扩展名方式) //objectMetadata.setContentType(MyFileUtils.getContentType(StringUtils.substringAfterLast(originalFilename, "."))); //判断文件类型,通过hutool工具类,本质是根据件流头部16进制字符串进行判断 objectMetadata.setContentType(MyFileUtils.getContentType(FileTypeUtil.getType(MyFileUtils.multipartFileToFile(file)))); // 设置URL过期时间为1小时。 InputStream multipartFileInputStream = file.getInputStream(); //PutObjectRequest putObjectRequest = new PutObjectRequest(aliyunOssConfig.getBucket(), cloudPath, multipartFileInputStream); ossClient.putObject(aliyunOssConfig.getBucket(), cloudPath, multipartFileInputStream, objectMetadata); //ossClient.generatePresignedUrl() String url = ossClient.generatePresignedUrl(aliyunOssConfig.getBucket(), cloudPath, expiration).toString(); //去掉url尾部的Expires信息、OSSAccessKeyId信息以及Signature信息 url = url.substring(0, url.indexOf("?")); responseUrls.add(url); } } catch (IOException e) { e.printStackTrace(); } finally { // 关闭流 ossClient.shutdown(); } return responseUrls; }

 

这里可以使用线程池来优化上传:

public List uploadImages(List multipartFile) { //保存上传后返回的云端文件URLs List responseUrls = Collections.synchronizedList(new ArrayList()); //设置url过期时间 上传时间后五年后失效 Date expiration = new Date(System.currentTimeMillis() + 5 * 365 * 24 * 3600 * 1000); // 用户上传文件时指定的前缀,即存放在以时间命名的文件夹内 String dir = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); //定义List类型的submit,用来接收上传文件的url Future submit = null; int coreThreads = Runtime.getRuntime().availableProcessors(); logger.info("当前计算机核心线程数:" + coreThreads); //创建线程池 核心线程数:当前当前计算机核心线程数 同时容纳最大线程:5*当前当前计算机核心线程数 非核心空闲线程存活时间:30 存活时间单位:毫秒(1/1000s) 任务队列:当前当前计算机核心线程数*10 拒绝策略:默认 //ThreadPoolExecutor threadPoolExecutor = new org.apache.tomcat.util.threads.ThreadPoolExecutor // (coreThreads, coreThreads * 5, 30L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque(coreThreads * 10)); for (MultipartFile file : multipartFile) { // 多线程上传图片 使用submit方法 为了返回上传文件的url submit= executor.submit((Callable)()->{ ossClient = AliyunOssConfig.createOss(aliyunOssConfig); //获得原始文件名称 String originalFilename = file.getOriginalFilename(); // 设置上传到云存储的文件名,规则为"当前时间-UUID.源文件后缀名" String cloudFileName = new StringBuilder() .append(UUID.randomUUID().toString()) .append(MyFileUtils.getExtensionName(originalFilename)) .toString(); //阿里云OSS bucket下存储位置 String cloudPath = dir + "/" + cloudFileName; //设置ContentType,使得返回的url可以在网页中预览(仅有少部分格式支持在线预览) 默认不设置或不支持在线预览的,返回的url是下载附件,而不是预览(可以从前端传个参数来判断是在线预览还是下载) ObjectMetadata objectMetadata = new ObjectMetadata(); //判断文件类型(获取扩展名方式) //objectMetadata.setContentType(MyFileUtils.getContentType(StringUtils.substringAfterLast(originalFilename, "."))); //判断文件类型,通过hutool工具类,本质是根据件流头部16进制字符串进行判断 objectMetadata.setContentType(MyFileUtils.getContentType(FileTypeUtil.getType(MyFileUtils.multipartFileToFile(file)))); InputStream multipartFileInputStream = file.getInputStream(); try { ossClient.putObject(aliyunOssConfig.getBucket(), cloudPath, multipartFileInputStream, objectMetadata); } catch (Exception e){ e.printStackTrace(); } String url = ossClient.generatePresignedUrl(aliyunOssConfig.getBucket(), cloudPath, expiration).toString(); //去掉url尾部的Expires信息、OSSAccessKeyId信息以及Signature信息 url = url.substring(0, url.indexOf("?")); responseUrls.add(url); return responseUrls; }); } //关闭oss资源 //ossClient.shutdown(); //executor.shutdown(); try { //将线程池返回的结果返回 return submit.get(); } catch (Exception e) { throw new RuntimeException(e); } }

 

上传大文件,分片上传

 

@Override public Result fileUploadZone(MultipartFile file) { this.ossClient = AliyunOssConfig.createOss(aliyunOssConfig); try { /* 耗时记录输出 */ Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String beginTime = sdf.format(date); long l1 = System.currentTimeMillis(); long test = l1; //String beginTime = date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds(); logger.info("fileUploadZone-开始上传时间:" + beginTime + "时间戳:" + l1); //设置url过期时间 上传时间后一周内失效 Date expiration = new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000); //获取文件的原始名字 String originalfileName = file.getOriginalFilename(); //文件后缀 String suffix = originalfileName.substring(originalfileName.lastIndexOf(".") + 1); //重新命名文件,文件夹要是改动,app记录删除的地方一并改动 String pack = "file/"; String fileName = "file_" + System.currentTimeMillis() + "." + suffix; String cloudPath = pack + fileName; // 创建InitiateMultipartUploadRequest对象。 InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(aliyunOssConfig.getBucket(), cloudPath); // 如果需要在初始化分片时设置文件存储类型,请参考以下示例代码。 ObjectMetadata objectMetadata = new ObjectMetadata(); objectMetadata.setContentType(MyFileUtils.getContentType(FileTypeUtil.getType(MyFileUtils.multipartFileToFile(file)))); // ObjectMetadata metadata = new ObjectMetadata(); //objectMetadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString()); //String files = URLEncoder.encode(cloudPath, "UTF-8"); //objectMetadata.setHeader("Content-Disposition", "filename*=utf-8''" + files); request.setObjectMetadata(objectMetadata); // 初始化分片。 InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request); // 返回uploadId,它是分片上传事件的唯一标识,可以根据这个uploadId发起相关的操作,如取消分片上传、查询分片上传等。 String uploadId = upresult.getUploadId(); // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。 List partETags = new ArrayList(); // 计算文件有多少个分片。 // 2MB final long partSize = 2 * 1024 * 1024L; long fileLength = file.getSize(); int partCount = (int) (fileLength / partSize); if (fileLength % partSize != 0) { partCount++; } // 遍历分片上传。 for (int i = 0; i < partCount; i++) { long startPos = i * partSize; long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize; // 跳过已经上传的分片。 InputStream instream = file.getInputStream(); instream.skip(startPos); UploadPartRequest uploadPartRequest = new UploadPartRequest(); uploadPartRequest.setBucketName(aliyunOssConfig.getBucket()); uploadPartRequest.setKey(cloudPath); uploadPartRequest.setUploadId(uploadId); uploadPartRequest.setInputStream(instream); // 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。 uploadPartRequest.setPartSize(curPartSize); // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出这个范围,OSS将返回InvalidArgument的错误码。 uploadPartRequest.setPartNumber(i + 1); // 每个分片不需要按顺序上传,甚至可以在不同客户端上传,OSS会按照分片号排序组成完整的文件。 UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest); // 每次上传分片之后,OSS的返回结果包含PartETag。PartETag将被保存在partETags中。 partETags.add(uploadPartResult.getPartETag()); /* 耗时记录输出 */ Date dateBlock = new Date(); SimpleDateFormat sdfBlock = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String blockBeginTime = sdf.format(date); long block = System.currentTimeMillis(); logger.info("第" + i + "块block上传时间:" + blockBeginTime + "当前时间戳:" + block + ",耗时:" + (block - test)); test = block; } /** * 创建CompleteMultipartUploadRequest对象。 * 在执行完成分片上传操作时,需要提供所有有效的partETags。OSS收到提交的partETags后,会逐一验证每个分片的有效性。 * 当所有的数据分片验证通过后,OSS将把这些分片组合成一个完整的文件。 */ //设置ContentType,使得返回的url可以在网页中预览 CompleteMultipartUploadRequest uploadRequest = new CompleteMultipartUploadRequest(aliyunOssConfig.getBucket(), cloudPath, uploadId, partETags); // 在完成文件上传的同时设置文件访问权限。 uploadRequest.setObjectACL(CannedAccessControlList.PublicRead); // 完成上传。 ossClient.completeMultipartUpload(uploadRequest); String url = ossClient.generatePresignedUrl(aliyunOssConfig.getBucket(), cloudPath, expiration).toString(); //去掉url尾部的Expires信息、OSSAccessKeyId信息以及Signature信息 url = url.substring(0, url.indexOf("?")); // 关闭OSSClient。 ossClient.shutdown(); Date date2 = new Date(); SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-ddHH:mm:ss"); String endTime = sdf2.format(date2); long l2 = System.currentTimeMillis(); logger.info("fileUploadZone-结束上传时间:" + endTime + " 总耗时:" + (l2 - l1) + "ms"); Map map = new HashMap(); map.put("url", url); map.put("name", fileName); return Result.success(map); } catch (Exception e) { e.printStackTrace(); ossClient.shutdown(); //logger.error(e.getMessage()); return Result.fail(111111, "操作失败!"); } }

 

 

最后附上Controller层代码,注解生效。这里指贴了一个controller,测试用例写了好几种,其实本质都差不多

@PostMapping("/uploadByThreads") @FileLimit(max = 30,maxRequestSize = 1000,fileFormat = "images",unit = FileLimitUnit.MB) public Result uploadImages(@RequestParam("images") List multipartFile) { // 文件上传,获取上传得到的图片地址返回 List responseUrls = ossService.uploadImages(multipartFile); return Result.success(responseUrls); }

 

ApiPost测试接口:可以看到已经成功了

参考

[1]【OSS】SpringBoot搭配线程池整合阿里云OSS实现图片异步上传--陈宝子

[2]springboot整合阿里云oss上传文件(图片或视频)--热河不是河

[3]阿里云oss分片上传,大文件上传--潜水的章鱼

[4]Multipart自定义资源限制文件大小限制设计-aop切面切入Multipart的文件大小拦截

 

 完整代码放在gitee了。【测试完整代码 在此】



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有