文件上传oss,并查询上传进度(SpringBoot+Redis+Oss+Swagger3)
创始人
2024-05-13 05:51:12
0

文章目录

  • 诉求
  • 技术选型
  • pom配置
  • 项目结构
    • 文件树
  • 图示结构
  • 代码实现
    • 配置相关
      • 配置文件yaml
      • Swagger3配置
      • 跨域问题配置
      • oss相关
    • Service
    • Controller
    • Application
  • Swagger接口操作
    • 获取上传文件标识号
    • 获取文件上传进度
  • 小结

诉求

       将文件上传到oss,并实时监听上传进度,并将进度进行存储。实现这个功能的由来是有可能上传的文件较大,并不能在调用上传接口得到文件上传成功或者失败的回应

技术选型

  • SpringBoot 2.4.0:选用SpringBoot可以进行快速开发迭代,社区支持力度较大,搜索问题较为方便
  • Redis:使用Redis当作文件进度的缓存,并设置过期时间
  • Oss:选取Aliyun Oss作为文件存储管理器
  • Swagger3:使用Swagger3可以让后端开发更便捷的在页面上操作接口,方便了接口之间的操作

pom配置


4.0.0org.springframework.bootspring-boot-starter-parent2.4.0com.exampledemo0.0.1-SNAPSHOTwardemoDemo project for Spring Boot8UTF-81.7.30org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-tomcatprovidedorg.springframework.bootspring-boot-starter-testtestorg.springframework.bootspring-boot-starter-data-redis









io.springfoxspringfox-boot-starter3.0.0com.aliyun.ossaliyun-sdk-oss3.15.0org.slf4jslf4j-api${slf4j.version}org.slf4jslf4j-log4j12${slf4j.version}org.apache.logging.log4jlog4j-to-slf4j2.14.0org.projectlomboklombokorg.apache.commonscommons-lang33.7org.apache.maven.pluginsmaven-compiler-plugin3.1${java.version}${java.version}${java.encoding}org.apache.maven.pluginsmaven-surefire-plugin2.6org.apache.maven.pluginsmaven-release-plugin-Preleaseorg.apache.maven.pluginsmaven-source-plugin2.1truecompilejar

项目结构

文件树

.
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pom.xml
├── springboot-test.iml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── example
│   │   │           └── demo
│   │   │               ├── DemoApplication.java
│   │   │               ├── ProgressInfo.java
│   │   │               ├── ServletInitializer.java
│   │   │               ├── component
│   │   │               │   └── OssComponent.java
│   │   │               ├── config
│   │   │               │   ├── CorsFilter.java
│   │   │               │   └── SwaggerConfig.java
│   │   │               ├── controller
│   │   │               │   └── FileController.java
│   │   │               └── service
│   │   │                   └── FileService.java
│   │   └── resources
│   │       ├── application.properties
│   │       ├── application.yaml
│   │       ├── static
│   │       │   └── styles.css
│   │       └── templates
│   │           └── index.html
│   └── test
│       └── java
│           └── com
│               └── example
│                   └── demo
│                       └── DemoApplicationTests.java
└── target├── classes│   ├── application.properties│   ├── application.yaml│   ├── com│   │   └── example│   │       └── demo│   │           ├── DemoApplication.class│   │           ├── ProgressInfo.class│   │           ├── ServletInitializer.class│   │           ├── component│   │           │   ├── OssComponent$1.class│   │           │   ├── OssComponent$PutObjectProgressListener.class│   │           │   └── OssComponent.class│   │           ├── config│   │           │   ├── CorsFilter.class│   │           │   ├── SwaggerConfig$1.class│   │           │   └── SwaggerConfig.class│   │           ├── controller│   │           │   └── FileController.class│   │           └── service│   │               └── FileService.class│   ├── static│   │   └── styles.css│   └── templates│       └── index.html├── generated-sources│   └── annotations├── generated-test-sources│   └── test-annotations└── test-classes└── com└── example└── demo└── DemoApplicationTests.class37 directories, 34 files
fanlongfeideMacBook-Pro:springboot-test dasouche$ tree
.
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pom.xml
├── springboot-test.iml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── example
│   │   │           └── demo
│   │   │               ├── DemoApplication.java
│   │   │               ├── ServletInitializer.java
│   │   │               ├── component
│   │   │               │   └── OssComponent.java
│   │   │               ├── config
│   │   │               │   ├── CorsFilter.java
│   │   │               │   └── SwaggerConfig.java
│   │   │               ├── controller
│   │   │               │   └── FileController.java
│   │   │               └── service
│   │   │                   └── FileService.java
│   │   └── resources
│   │       ├── application.properties
│   │       ├── application.yaml
│   │       ├── static
│   │       └── templates
│   └── test
│       └── java
│           └── com
│               └── example
│                   └── demo
│                       └── DemoApplicationTests.java
└── target├── classes│   ├── application.properties│   ├── application.yaml│   ├── com│   │   └── example│   │       └── demo│   │           ├── DemoApplication.class│   │           ├── ServletInitializer.class│   │           ├── component│   │           │   ├── OssComponent$1.class│   │           │   ├── OssComponent$PutObjectProgressListener.class│   │           │   └── OssComponent.class│   │           ├── config│   │           │   ├── CorsFilter.class│   │           │   ├── SwaggerConfig$1.class│   │           │   └── SwaggerConfig.class│   │           ├── controller│   │           │   └── FileController.class│   │           └── service│   │               └── FileService.class│   ├── static│   └── templates├── generated-sources│   └── annotations├── generated-test-sources│   └── test-annotations└── test-classes└── com└── example└── demo└── DemoApplicationTests.class

图示结构

在这里插入图片描述

代码实现

配置相关

配置文件yaml

spring:web:resources:#设置静态文件访问路径,用于直接访问html文件static-locations: classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/templates/thymeleaf:prefix:  /templates/**suffix: .htmlcache: false#redis配置redis:host: xxxport: xxxpassword: xxxtimeout: 30000jedis:pool:max-active: 8max-idle: 8min-idle: 0max-wait: -1msmvc:pathmatch:matching-strategy: ANT_PATH_MATCHERserver:port: 8080aliyun:OSS_ENDPOINT: http://oss-cn-hangzhou.aliyuncs.comACCESS_ID: xxxACCESS_KEY: xxxbucket: xxx

Swagger3配置

package com.example.demo.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;import java.util.ArrayList;
import java.util.List;/*** @author * @date 2023年01月17日 16:00*/
@Configuration
@EnableOpenApi
public class SwaggerConfig {@Beanpublic Docket createRestApi() {return new Docket(DocumentationType.OAS_30) // v2 不同.select().apis(RequestHandlerSelectors.basePackage("com.example.demo")) // 设置扫描路径.build();}@Beanpublic WebMvcConfigurer webMvcConfigurer() {return new WebMvcConfigurer() {@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");}};}}

跨域问题配置

package com.example.demo.config;import org.springframework.stereotype.Component;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @author * @date 2023年01月17日 14:46*/
@Component
public class CorsFilter implements Filter {@Overridepublic void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {HttpServletResponse response = (HttpServletResponse) res;HttpServletRequest request = (HttpServletRequest) req;response.setHeader("Access-Control-Allow-Origin", "*");response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");response.setHeader("Access-Control-Max-Age", "3600");response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Content-Length, X-Requested-With");if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {response.setStatus(HttpServletResponse.SC_OK);} else {chain.doFilter(req, res);}}@Overridepublic void init(FilterConfig filterConfig) {}@Overridepublic void destroy() {}
}

oss相关

package com.example.demo.component;import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.event.ProgressEvent;
import com.aliyun.oss.event.ProgressEventType;
import com.aliyun.oss.event.ProgressListener;
import com.aliyun.oss.model.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.io.*;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;import static com.aliyun.oss.internal.OSSConstants.URL_ENCODING;/*** @author * @date 2023年01月17日 15:11*/
@Component
@Slf4j
public class OssComponent implements InitializingBean, DisposableBean {@Value("${aliyun.OSS_ENDPOINT}")private String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";@Value("${aliyun.ACCESS_ID}")private String accessKeyId = "xxx";@Value("${aliyun.ACCESS_KEY}")private String accessKeySecret = "xxx";@Value("${aliyun.bucket}")private String bucket = "xxx";@Resourceprivate RedisTemplate redisTemplate;private OSS ossClient;//设置缓存失效时间:1天private static final TimeUnit TIME_UNIT = TimeUnit.DAYS;private static final Integer EXPIRE = 1;public String upload(File file, String fileName) throws Exception {String requestId = null;String etag = null;try{//用于标识上传文件,用于获取进度时使用requestId = UUID.randomUUID().toString();PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, "process-test/" + fileName, file);//添加进度条Listener,用于进度条更新putObjectRequest.withProgressListener(new PutObjectProgressListener(requestId, redisTemplate));//文件PutObjectResult putObjectResult = ossClient.putObject(putObjectRequest);if(StringUtils.isBlank((etag = putObjectResult.getETag()))){throw new RuntimeException("上传失败!");}return requestId;}catch (Exception e){log.error("upload error ! requestId : {} etag : {} fileName : {}  " , requestId , etag , fileName , e);return null;}}public Integer batchDel(List fileNames) {String requestId = null;try{OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucket).withKeys(fileNames).withEncodingType(URL_ENCODING);DeleteObjectsResult deleteObjectsResult = ossClient.deleteObjects(deleteObjectsRequest);if(deleteObjectsResult == null){return 0;}requestId = deleteObjectsResult.getRequestId();List deletedObjects = deleteObjectsResult.getDeletedObjects();if(deletedObjects == null){return 0;}return deletedObjects.size();}catch (Exception e){log.error("upload error ! requestId : {} fileName : {}  " , requestId , fileNames , e);return null;}}@Overridepublic void afterPropertiesSet() throws Exception {ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);}@Overridepublic void destroy() throws Exception {ossClient.shutdown();}public static class PutObjectProgressListener implements ProgressListener {private String requestId;private long bytesWritten = 0;private long totalBytes = -1;private boolean succeed = false;private RedisTemplate redisTemplate;public PutObjectProgressListener(String requestId, RedisTemplate redisTemplate) {this.requestId = requestId;this.redisTemplate = redisTemplate;this.redisTemplate.opsForValue().set(requestId + "_total", totalBytes);this.redisTemplate.opsForValue().set(requestId + "_uploaded", bytesWritten);}public PutObjectProgressListener() {}@Overridepublic void progressChanged(ProgressEvent progressEvent) {long bytes = progressEvent.getBytes();ProgressEventType eventType = progressEvent.getEventType();switch (eventType) {case TRANSFER_STARTED_EVENT:System.out.println("Start to upload......");break;case REQUEST_CONTENT_LENGTH_EVENT:this.totalBytes = bytes;this.redisTemplate.opsForValue().set(requestId + "_total", totalBytes, EXPIRE, TIME_UNIT);
//                    this.totalBytes = bytes;
//                    System.out.println(this.totalBytes + " bytes in total will be uploaded to OSS");break;case REQUEST_BYTE_TRANSFER_EVENT:this.bytesWritten += bytes;redisTemplate.opsForValue().set(requestId + "_uploaded", bytesWritten, EXPIRE, TIME_UNIT);
//                    this.bytesWritten += bytes;
//                    if (this.totalBytes != -1) {
//                        int percent = (int)(this.bytesWritten * 100.0 / this.totalBytes);
//                        System.out.println(bytes + " bytes have been written at this time, upload progress: " + percent + "%(" + this.bytesWritten + "/" + this.totalBytes + ")");
//                    } else {
//                        System.out.println(bytes + " bytes have been written at this time, upload ratio: unknown" + "(" + this.bytesWritten + "/...)");
//                    }break;case TRANSFER_COMPLETED_EVENT:this.succeed = true;System.out.println("Succeed to upload, " + this.bytesWritten + " bytes have been transferred in total");break;case TRANSFER_FAILED_EVENT:System.out.println("Failed to upload, " + this.bytesWritten + " bytes have been transferred");break;default:break;}}}public static void main(String[] args) {String endpoint = "http://oss-cn-hangzhou.aliyuncs.com";String accessKeyId = "xxx";String accessKeySecret = "xxx";String bucketName = "xxx";
//String key = "process-test/object-get-progress-sample";OSS client = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);try {File fh = createSampleFile();PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, fh);putObjectRequest.withProgressListener(new PutObjectProgressListener());// 带进度条的上传PutObjectResult putObjectResult = client.putObject(putObjectRequest);String requestId = putObjectResult.getRequestId();System.out.println("requestId:" + requestId);// 带进度条的下载
//            client.getObject(new GetObjectRequest(bucketName, key).
//                    withProgressListener(new GetObjectProgressListener()), fh);} catch (Exception e) {e.printStackTrace();}}/*** Create a temp file with about 50MB.**/private static File createSampleFile() throws IOException {File file = File.createTempFile("oss-java-sdk-", ".txt");file.deleteOnExit();Writer writer = new OutputStreamWriter(new FileOutputStream(file));for (int i = 0; i < 10; i++) {writer.write("abcdefghijklmnopqrstuvwxyz\n");writer.write("0123456789011234567890\n");}writer.close();return file;}}

Service

package com.example.demo.service;import com.example.demo.component.OssComponent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.io.*;/*** @author * @date 2023年01月17日 14:57*/
@Service
@Slf4j
public class FileService {@Resourceprivate RedisTemplate redisTemplate;@Autowiredprivate OssComponent ossComponent;/*** 获取上传进度* @param requestId 文件标识id* @return*/public String getUploadFileProcess(String requestId){Long totalSize = redisTemplate.opsForValue().get(requestId + "_total");Long uploadedSize = redisTemplate.opsForValue().get(requestId + "_uploaded");if (null == totalSize || null == uploadedSize){return "0%";}return (int)(uploadedSize * 100.0 / totalSize) + "%";}/*** 模拟文件上传* @return*/public String simulateUploadedFile(){String requestId = "";try {File sampleFile = createSampleFile();requestId = ossComponent.upload(sampleFile, sampleFile.getName());} catch (Exception e) {log.error("upload file error!", e);}return requestId;}private File createSampleFile() throws IOException {File file = File.createTempFile("oss-java-sdk-", ".txt");file.deleteOnExit();Writer writer = new OutputStreamWriter(new FileOutputStream(file));for (int i = 0; i < 10; i++) {writer.write("abcdefghijklmnopqrstuvwxyz\n");writer.write("0123456789011234567890\n");}writer.close();return file;}}

Controller

package com.example.demo.controller;import com.example.demo.service.FileService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;
import java.util.Map;/*** @author * @date 2023年01月17日 14:34*/
@RestController()
@RequestMapping("/fileApi")
@Slf4j
@Api(value = "文件接口")
public class FileController {@Autowiredprivate FileService fileService;@ApiOperation("获取上传进度")@GetMapping("/uploadProgress")public String uploadProgress(String requestId) {return fileService.getUploadFileProcess(requestId);}@ApiOperation("模拟文件上传")@GetMapping("/simulateUploadedFile")public String simulateUploadedFile() {return fileService.simulateUploadedFile();}}

Application

package com.example.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.oas.annotations.EnableOpenApi;@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}}
package com.example.demo;import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;public class ServletInitializer extends SpringBootServletInitializer {@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder application) {return application.sources(DemoApplication.class);}}

Swagger接口操作

启动项目无报错后访问:http://localhost:8080/swagger-ui/index.html#/

在这里插入图片描述

可以看到我们的接口在页面上有显示,可以点击对应的接口进行操作

获取上传文件标识号

在这里插入图片描述
在这里插入图片描述

获取文件上传进度

在这里插入图片描述

小结

       文件下载时的进度也可以参考上述代码,进度存储也可以使用其他方式,如ConcurrentHashMap、Mysql等,当然前端也可以实现等。
       Swagger UI页面可以让后端开发更变便捷的操作接口,个人感觉像个快捷版的Postman吧。

Oss官方文档地址: 点我调转
Swagger官方文档地址: 点我调转
Swagger2代3代配置相关疑问可参考文档:点我调转

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...