将文件上传到oss,并实时监听上传进度,并将进度进行存储。实现这个功能的由来是有可能上传的文件较大,并不能在调用上传接口得到文件上传成功或者失败的回应
4.0.0 org.springframework.boot spring-boot-starter-parent 2.4.0 com.example demo 0.0.1-SNAPSHOT war demo Demo project for Spring Boot 8 UTF-8 1.7.30 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-tomcat provided org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-data-redis
io.springfox springfox-boot-starter 3.0.0 com.aliyun.oss aliyun-sdk-oss 3.15.0 org.slf4j slf4j-api ${slf4j.version} org.slf4j slf4j-log4j12 ${slf4j.version} org.apache.logging.log4j log4j-to-slf4j 2.14.0 org.projectlombok lombok org.apache.commons commons-lang3 3.7 org.apache.maven.plugins maven-compiler-plugin 3.1 ${java.version} ${java.version} ${java.encoding} org.apache.maven.plugins maven-surefire-plugin 2.6 org.apache.maven.plugins maven-release-plugin -Prelease org.apache.maven.plugins maven-source-plugin 2.1 true compile jar
.
├── 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
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
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() {}
}
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;}}
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;}}
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();}}
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);}}
启动项目无报错后访问:http://localhost:8080/swagger-ui/index.html#/
可以看到我们的接口在页面上有显示,可以点击对应的接口进行操作
文件下载时的进度也可以参考上述代码,进度存储也可以使用其他方式,如ConcurrentHashMap、Mysql等,当然前端也可以实现等。
Swagger UI页面可以让后端开发更变便捷的操作接口,个人感觉像个快捷版的Postman吧。
Oss官方文档地址: 点我调转
Swagger官方文档地址: 点我调转
Swagger2代3代配置相关疑问可参考文档:点我调转