diff --git a/hsweb-system/hsweb-system-file/pom.xml b/hsweb-system/hsweb-system-file/pom.xml
index 5351fa264..bda09ae7e 100644
--- a/hsweb-system/hsweb-system-file/pom.xml
+++ b/hsweb-system/hsweb-system-file/pom.xml
@@ -54,6 +54,14 @@
spring-test
test
+
+
+ software.amazon.awssdk
+ s3
+ ${aws.sdk.version}
+ true
+
+
\ No newline at end of file
diff --git a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/FileServiceConfiguration.java b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/FileServiceConfiguration.java
index 082c8cf86..b4d67db33 100644
--- a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/FileServiceConfiguration.java
+++ b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/FileServiceConfiguration.java
@@ -8,7 +8,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
+
@AutoConfiguration
@EnableConfigurationProperties(FileUploadProperties.class)
diff --git a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/FileUploadProperties.java b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/FileUploadProperties.java
index 4cd67adfa..d9f599b26 100644
--- a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/FileUploadProperties.java
+++ b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/FileUploadProperties.java
@@ -1,5 +1,6 @@
package org.hswebframework.web.file;
+import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.collections4.CollectionUtils;
diff --git a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/S3FileProperties.java b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/S3FileProperties.java
new file mode 100644
index 000000000..40d943388
--- /dev/null
+++ b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/S3FileProperties.java
@@ -0,0 +1,15 @@
+package org.hswebframework.web.file;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties(prefix = "hsweb.file.upload.s3")
+@Data
+public class S3FileProperties {
+ private String endpoint;
+ private String accessKey;
+ private String secretKey;
+ private String bucket;
+ private String region;
+ private String baseUrl;
+}
\ No newline at end of file
diff --git a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/S3FileStorageConfiguration.java b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/S3FileStorageConfiguration.java
new file mode 100644
index 000000000..83b9cee1a
--- /dev/null
+++ b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/S3FileStorageConfiguration.java
@@ -0,0 +1,43 @@
+package org.hswebframework.web.file;
+
+import org.hswebframework.web.file.service.FileStorageService;
+import org.hswebframework.web.file.service.S3FileStorageService;
+import org.hswebframework.web.file.web.ReactiveFileController;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3Client;
+
+import java.net.URI;
+
+@Configuration
+@ConditionalOnClass(S3Client.class)
+@ConditionalOnProperty(name = "hsweb.file.storage", havingValue = "s3", matchIfMissing = false)
+@EnableConfigurationProperties({S3FileProperties.class, FileUploadProperties.class})
+public class S3FileStorageConfiguration {
+
+
+ @Bean
+ @ConditionalOnMissingBean
+ public S3Client s3Client(S3FileProperties properties) {
+ return S3Client.builder()
+ .endpointOverride(URI.create(properties.getEndpoint()))
+ .credentialsProvider(StaticCredentialsProvider.create(
+ AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey())))
+ .region(Region.of(properties.getRegion()))
+ .build();
+ }
+
+ @Bean
+ public FileStorageService s3FileStorageService(
+ S3FileProperties s3Properties,
+ S3Client s3Client) {
+ return new S3FileStorageService(s3Properties, s3Client);
+ }
+}
diff --git a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/service/FileStorageService.java b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/service/FileStorageService.java
index 8a2a93276..8888d54ae 100644
--- a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/service/FileStorageService.java
+++ b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/service/FileStorageService.java
@@ -3,6 +3,7 @@
import org.springframework.http.codec.multipart.FilePart;
import reactor.core.publisher.Mono;
+import java.io.IOException;
import java.io.InputStream;
/**
diff --git a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/service/S3FileStorageService.java b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/service/S3FileStorageService.java
new file mode 100644
index 000000000..89339a48e
--- /dev/null
+++ b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/service/S3FileStorageService.java
@@ -0,0 +1,83 @@
+package org.hswebframework.web.file.service;
+
+import com.google.common.io.Files;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.hswebframework.web.file.S3FileProperties;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.http.codec.multipart.FilePart;
+import org.springframework.web.util.UriComponentsBuilder;
+import reactor.core.publisher.Mono;
+import reactor.core.scheduler.Schedulers;
+import software.amazon.awssdk.core.sync.RequestBody;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.model.PutObjectRequest;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+import java.util.UUID;
+
+@AllArgsConstructor
+public class S3FileStorageService implements FileStorageService {
+
+ private final S3FileProperties properties;
+ private final S3Client s3Client;
+
+
+ @Override
+ public Mono saveFile(FilePart filePart) {
+ String filename = buildFileName(filePart.filename());
+
+ return DataBufferUtils.join(filePart.content())
+ .flatMap(dataBuffer -> {
+ InputStream inputStream = dataBuffer.asInputStream(true);
+ return saveFile(inputStream, Files.getFileExtension(filename));
+ });
+ }
+
+
+ @Override
+ @SneakyThrows
+ public Mono saveFile(InputStream inputStream, String fileType) {
+ return Mono.fromCallable(() -> {
+ String key = UUID.randomUUID().toString() + (fileType.startsWith(".") ? fileType : "." + fileType);
+
+ PutObjectRequest request = PutObjectRequest.builder()
+ .bucket(properties.getBucket())
+ .key(key)
+ .build();
+
+ s3Client.putObject(request, RequestBody.fromInputStream(inputStream, inputStream.available()));
+ return buildFileUrl(key);
+ })
+ .subscribeOn(Schedulers.boundedElastic());
+ }
+
+ private String buildFileName(String originalName) {
+ String suffix = "";
+ if (originalName != null && originalName.contains(".")) {
+ suffix = originalName.substring(originalName.lastIndexOf("."));
+ }
+ return UUID.randomUUID().toString().replace("-", "") + suffix.toLowerCase(Locale.ROOT);
+ }
+
+ private String buildFileUrl(String key) {
+ if (properties.getBaseUrl() != null && !properties.getBaseUrl().isEmpty()) {
+ return UriComponentsBuilder
+ .fromUriString(properties.getBaseUrl())
+ .pathSegment(key)
+ .build()
+ .toUriString();
+ }
+ String host = properties.getBucket() + "." + properties.getEndpoint().replaceFirst("^https?://", "");
+ return UriComponentsBuilder
+ .newInstance()
+ .scheme("https")
+ .host(host)
+ .pathSegment(key)
+ .build()
+ .toUriString();
+ }
+}
diff --git a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/ReactiveFileController.java b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/ReactiveFileController.java
index c55a3ee2f..876dccf28 100644
--- a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/ReactiveFileController.java
+++ b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/ReactiveFileController.java
@@ -11,16 +11,17 @@
import org.hswebframework.web.authorization.exception.AccessDenyException;
import org.hswebframework.web.file.FileUploadProperties;
import org.hswebframework.web.file.service.FileStorageService;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.codec.multipart.Part;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestPart;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
-import java.io.File;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
@RestController
@Resource(id = "file", name = "文件上传")
@@ -33,11 +34,13 @@ public class ReactiveFileController {
private final FileStorageService fileStorageService;
+
public ReactiveFileController(FileUploadProperties properties, FileStorageService fileStorageService) {
this.properties = properties;
this.fileStorageService = fileStorageService;
}
+
@PostMapping("/static")
@SneakyThrows
@ResourceAction(id = "upload-static", name = "静态文件")
@@ -59,4 +62,20 @@ public Mono uploadStatic(@RequestPart("file")
}
+ @PostMapping(value = "/static/stream", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
+ @Operation(summary = "上传文件流")
+ public Mono uploadOssStream(ServerHttpRequest request,
+ @RequestParam("fileType") String fileType) {
+
+ if (properties.denied("upload." + fileType, MediaType.APPLICATION_OCTET_STREAM)) {
+ return Mono.error(new AccessDenyException());
+ }
+
+ return DataBufferUtils.join(request.getBody())
+ .flatMap(dataBuffer -> {
+ InputStream inputStream = dataBuffer.asInputStream(true);
+ return fileStorageService.saveFile(inputStream, fileType);
+ });
+ }
+
}
diff --git a/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/OssUploadTest.java b/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/OssUploadTest.java
new file mode 100644
index 000000000..f047c2325
--- /dev/null
+++ b/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/OssUploadTest.java
@@ -0,0 +1,61 @@
+package org.hswebframework.web.file.web;
+
+import org.hswebframework.web.file.S3FileStorageConfiguration;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import org.springframework.util.StreamUtils;
+import org.springframework.web.reactive.function.BodyInserters;
+
+@WebFluxTest(ReactiveFileController.class)
+@RunWith(SpringRunner.class)
+@ImportAutoConfiguration(S3FileStorageConfiguration.class)
+public class OssUploadTest {
+
+ static {
+ System.setProperty("hsweb.file.upload.s3.endpoint", "https://oss-cn-beijing.aliyuncs.com");
+ System.setProperty("hsweb.file.upload.s3.region", "us-east-1");
+ System.setProperty("hsweb.file.upload.s3.accessKey", "");
+ System.setProperty("hsweb.file.upload.s3.secretKey", "");
+ System.setProperty("hsweb.file.upload.s3.bucket", "maydaysansan");
+ System.setProperty("hsweb.file.storage", "s3");
+ }
+
+ @Autowired
+ WebTestClient client;
+
+ @Test
+ public void testStatic(){
+ client.post()
+ .uri("/file/static")
+ .contentType(MediaType.MULTIPART_FORM_DATA)
+ .body(BodyInserters.fromMultipartData("file",new HttpEntity<>(new ClassPathResource("test.json"))))
+ .exchange()
+ .expectStatus()
+ .isOk();
+
+ }
+
+ @Test
+ public void testStream() throws Exception {
+ byte[] fileBytes = StreamUtils.copyToByteArray(new ClassPathResource("test.json").getInputStream());
+
+ client.post()
+ .uri(uriBuilder ->
+ uriBuilder.path("/file/static/stream")
+ .queryParam("fileType", "json")
+ .build())
+ .contentType(MediaType.APPLICATION_OCTET_STREAM)
+ .bodyValue(fileBytes)
+ .exchange()
+ .expectStatus().isOk();
+ }
+
+}
\ No newline at end of file
diff --git a/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/ReactiveFileControllerTest.java b/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/ReactiveFileControllerTest.java
index 67ff2654a..d1cf5b6e0 100644
--- a/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/ReactiveFileControllerTest.java
+++ b/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/ReactiveFileControllerTest.java
@@ -29,6 +29,7 @@ public class ReactiveFileControllerTest {
static {
System.setProperty("hsweb.file.upload.static-file-path","./target/upload");
+ System.setProperty("hsweb.file.storage","local");
// System.setProperty("hsweb.file.upload.use-original-file-name","true");
}
diff --git a/pom.xml b/pom.xml
index 13a59626f..774c71a07 100644
--- a/pom.xml
+++ b/pom.xml
@@ -95,6 +95,8 @@
2.7.0
4.1.111.Final
Borca-SR2
+ Borca-SR2
+ 2.25.5