From fe9da5898ff5bf798aa47eae4a5823fa775bf119 Mon Sep 17 00:00:00 2001 From: liujiang Date: Mon, 16 Jun 2025 21:08:17 +0800 Subject: [PATCH 1/9] =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0S3=E5=8D=8F=E8=AE=AE=E7=9A=84OSS=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hsweb-system/hsweb-system-file/pom.xml | 7 ++ .../web/file/FileServiceConfiguration.java | 37 +++++++- .../web/file/S3StorageProperties.java | 85 ++++++++++++++++++ .../web/file/service/FileStorageService.java | 3 +- .../file/service/S3FileStorageService.java | 86 +++++++++++++++++++ .../web/file/web/ReactiveFileController.java | 11 ++- .../web/file/web/S3FileController.java | 66 ++++++++++++++ .../file/web/ReactiveFileControllerTest.java | 1 + .../web/file/web/S3FileControllerTest.java | 53 ++++++++++++ pom.xml | 2 + 10 files changed, 346 insertions(+), 5 deletions(-) create mode 100644 hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/S3StorageProperties.java create mode 100644 hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/service/S3FileStorageService.java create mode 100644 hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/S3FileController.java create mode 100644 hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/S3FileControllerTest.java diff --git a/hsweb-system/hsweb-system-file/pom.xml b/hsweb-system/hsweb-system-file/pom.xml index 5351fa264..4e75f347c 100644 --- a/hsweb-system/hsweb-system-file/pom.xml +++ b/hsweb-system/hsweb-system-file/pom.xml @@ -54,6 +54,13 @@ spring-test test + + + software.amazon.awssdk + s3 + ${aws.sdk.version} + + \ 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..1816b47d8 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 @@ -2,16 +2,25 @@ import org.hswebframework.web.file.service.FileStorageService; import org.hswebframework.web.file.service.LocalFileStorageService; +import org.hswebframework.web.file.service.S3FileStorageService; import org.hswebframework.web.file.web.ReactiveFileController; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 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; +import org.springframework.context.annotation.Primary; +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; @AutoConfiguration -@EnableConfigurationProperties(FileUploadProperties.class) +@EnableConfigurationProperties({FileUploadProperties.class,S3StorageProperties.class}) public class FileServiceConfiguration { @@ -19,18 +28,40 @@ public class FileServiceConfiguration { static class ReactiveConfiguration { @Bean - @ConditionalOnMissingBean(FileStorageService.class) + @ConditionalOnProperty(name = "file.storage", havingValue = "local", matchIfMissing = true) public FileStorageService fileStorageService(FileUploadProperties properties) { return new LocalFileStorageService(properties); } @Bean - @ConditionalOnMissingBean(name = "reactiveFileController") + @ConditionalOnProperty(name = "file.storage", havingValue = "s3") public ReactiveFileController reactiveFileController(FileUploadProperties properties, FileStorageService storageService) { return new ReactiveFileController(properties, storageService); } + + + + @Bean + @ConditionalOnMissingBean(FileStorageService.class) + public FileStorageService s3FileStorageService(S3StorageProperties properties, + S3Client s3Client) { + return new S3FileStorageService(properties, s3Client); + } + + @Bean + @ConditionalOnProperty(name = "file.storage", havingValue = "s3") + public S3Client s3Client(S3StorageProperties properties) { + return S3Client.builder() + .endpointOverride(URI.create(properties.getEndpoint())) + .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create( + properties.getAccessKey(), properties.getSecretKey() + ))) + .region(Region.of(properties.getRegion())) + .build(); + } + } } diff --git a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/S3StorageProperties.java b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/S3StorageProperties.java new file mode 100644 index 000000000..909cbdc81 --- /dev/null +++ b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/S3StorageProperties.java @@ -0,0 +1,85 @@ +package org.hswebframework.web.file; + +import lombok.Data; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.http.MediaType; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Locale; +import java.util.Set; + +@Data +@ConfigurationProperties(prefix = "oss.s3") +public class S3StorageProperties { + private String endpoint; + private String region; + private String accessKey; + private String secretKey; + private String bucket; + private String baseUrl; + + private Set allowFiles; + + private Set denyFiles; + + private Set allowMediaType; + + private Set denyMediaType; + + private Set permissions; + + + public void applyFilePermission(File file) { + + if (CollectionUtils.isEmpty(permissions)) { + return; + } + try { + Path path = Paths.get(file.toURI()); + PosixFileAttributeView view = Files.getFileAttributeView(path, PosixFileAttributeView.class); + view.setPermissions(permissions); + } catch (Throwable ignore) { + // 失败时忽略,兼容Windows等不支持Posix的系统 + } + } + + public boolean denied(String name, MediaType mediaType) { + String suffix = (name.contains(".") ? name.substring(name.lastIndexOf(".") + 1) : "").toLowerCase(Locale.ROOT); + boolean defaultDeny = false; + if (CollectionUtils.isNotEmpty(denyFiles)) { + if (denyFiles.contains(suffix)) { + return true; + } + defaultDeny = false; + } + + if (CollectionUtils.isNotEmpty(allowFiles)) { + if (allowFiles.contains(suffix)) { + return false; + } + defaultDeny = true; + } + + if (CollectionUtils.isNotEmpty(denyMediaType)) { + if (denyMediaType.contains(mediaType.toString())) { + return true; + } + defaultDeny = false; + } + + if (CollectionUtils.isNotEmpty(allowMediaType)) { + if (allowMediaType.contains(mediaType.toString())) { + return false; + } + defaultDeny = true; + } + + return defaultDeny; + } +} 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..3920cf0de 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; /** @@ -19,7 +20,7 @@ public interface FileStorageService { * @param filePart FilePart * @return 文件访问地址 */ - Mono saveFile(FilePart filePart); + Mono saveFile(FilePart filePart) throws IOException; /** * 使用文件流保存文件,并返回文件地址 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..b43991435 --- /dev/null +++ b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/service/S3FileStorageService.java @@ -0,0 +1,86 @@ +package org.hswebframework.web.file.service; + +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import org.hswebframework.web.file.S3StorageProperties; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.MediaType; +import org.springframework.http.codec.multipart.FilePart; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; +import software.amazon.awssdk.core.async.AsyncRequestBody; +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; +import java.util.concurrent.CompletableFuture; + +@AllArgsConstructor +public class S3FileStorageService implements FileStorageService { + + private final S3StorageProperties properties; + + private final S3Client s3Client; + + @Override + public Mono saveFile(FilePart filePart) { + String filename = buildFileName(filePart.filename()); + return DataBufferUtils.join(filePart.content()) + .publishOn(Schedulers.boundedElastic()) + .map(dataBuffer -> { + byte[] bytes = new byte[dataBuffer.readableByteCount()]; + dataBuffer.read(bytes); + DataBufferUtils.release(dataBuffer); + return new ByteArrayInputStream(bytes); + }) + .map(inputStream -> { + PutObjectRequest request = PutObjectRequest.builder() + .bucket(properties.getBucket()) + .key(filename) + .build(); + + s3Client.putObject(request, RequestBody.fromInputStream(inputStream, inputStream.available())); + return buildFileUrl(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 properties.getBaseUrl() + "/" + 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 properties.getBaseUrl() + "/" + key; + } + return "https://" + properties.getBucket() + "." + properties.getEndpoint().replace("https://", "").replace("http://", "") + "/" + key; + } +} 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..f5b97dc48 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 @@ -10,7 +10,9 @@ import org.hswebframework.web.authorization.annotation.ResourceAction; import org.hswebframework.web.authorization.exception.AccessDenyException; import org.hswebframework.web.file.FileUploadProperties; +import org.hswebframework.web.file.S3StorageProperties; import org.hswebframework.web.file.service.FileStorageService; +import org.hswebframework.web.file.service.S3FileStorageService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.codec.multipart.FilePart; import org.springframework.http.codec.multipart.Part; @@ -21,6 +23,7 @@ import reactor.core.publisher.Mono; import java.io.File; +import java.io.IOException; @RestController @Resource(id = "file", name = "文件上传") @@ -33,11 +36,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 = "静态文件") @@ -51,7 +56,11 @@ public Mono uploadStatic(@RequestPart("file") if (properties.denied(filePart.filename(), filePart.headers().getContentType())) { return Mono.error( new AccessDenyException()); } - return fileStorageService.saveFile(filePart); + try { + return fileStorageService.saveFile(filePart); + } catch (IOException e) { + throw new RuntimeException(e); + } } else { return Mono.error(() -> new IllegalArgumentException("[file] part is not a file")); } diff --git a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/S3FileController.java b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/S3FileController.java new file mode 100644 index 000000000..b8d8d6bd7 --- /dev/null +++ b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/S3FileController.java @@ -0,0 +1,66 @@ +package org.hswebframework.web.file.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterStyle; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.hswebframework.web.authorization.annotation.Resource; +import org.hswebframework.web.authorization.annotation.ResourceAction; +import org.hswebframework.web.authorization.exception.AccessDenyException; +import org.hswebframework.web.file.FileUploadProperties; +import org.hswebframework.web.file.S3StorageProperties; +import org.hswebframework.web.file.service.FileStorageService; +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 reactor.core.publisher.Mono; + +import java.io.IOException; + +@RestController +@Resource(id = "ossFile", name = "文件上传") +@Slf4j +@RequestMapping("/oss/file") +@Tag(name = "文件上传") +public class S3FileController { + private final S3StorageProperties properties; + + private final FileStorageService fileStorageService; + + + public S3FileController(S3StorageProperties properties, FileStorageService fileStorageService) { + this.properties = properties; + this.fileStorageService = fileStorageService; + } + + + @PostMapping("/static") + @SneakyThrows + @ResourceAction(id = "upload-static", name = "静态文件") + @Operation(summary = "上传静态文件") + public Mono uploadStatic(@RequestPart("file") + @Parameter(name = "file", description = "文件", style = ParameterStyle.FORM) Mono partMono) { + return partMono + .flatMap(part -> { + if (part instanceof FilePart) { + FilePart filePart = ((FilePart) part); + if (properties.denied(filePart.filename(), filePart.headers().getContentType())) { + return Mono.error( new AccessDenyException()); + } + try { + return fileStorageService.saveFile(filePart); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + return Mono.error(() -> new IllegalArgumentException("[file] part is not a 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..b76c5b436 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("file.storage", "local"); // System.setProperty("hsweb.file.upload.use-original-file-name","true"); } diff --git a/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/S3FileControllerTest.java b/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/S3FileControllerTest.java new file mode 100644 index 000000000..319accbce --- /dev/null +++ b/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/S3FileControllerTest.java @@ -0,0 +1,53 @@ +package org.hswebframework.web.file.web; + +import org.hswebframework.web.file.FileServiceConfiguration; +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.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.HttpEntity; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.BodyInserters; + +import static org.junit.Assert.*; + +@WebFluxTest(S3FileController.class) +@RunWith(SpringRunner.class) +@ImportAutoConfiguration(FileServiceConfiguration.class) +public class S3FileControllerTest { + + static { + System.setProperty("oss.s3.endpoint", "https://oss-cn-beijing.aliyuncs.com"); + System.setProperty("oss.s3.region", "us-east-1"); + System.setProperty("oss.s3.accessKey", ""); + System.setProperty("oss.s3.secretKey", ""); + System.setProperty("oss.s3.bucket", "maydaysansan"); + System.setProperty("file.storage", "s3"); + } + + @Autowired + WebTestClient client; + + @Test + public void test(){ + client.post() + .uri("/oss/file/static") + .contentType(MediaType.MULTIPART_FORM_DATA) + .body(BodyInserters.fromMultipartData("file",new HttpEntity<>(new ClassPathResource("test.json")))) + .exchange() + .expectStatus() + .isOk(); + + } +} \ No newline at end of file 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 From 28e1325b688801080cdb87e9e6ff356d774e779a Mon Sep 17 00:00:00 2001 From: liujiang Date: Tue, 17 Jun 2025 10:07:56 +0800 Subject: [PATCH 2/9] =?UTF-8?q?=E8=A7=A3=E8=80=A6=E4=B8=A4=E7=A7=8D?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0=E6=96=B9=E5=BC=8F=E7=9A=84?= =?UTF-8?q?=E7=9A=84Configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hsweb-system/hsweb-system-file/pom.xml | 1 + .../web/file/FileServiceConfiguration.java | 37 ++------------ .../web/file/S3FileStorageConfiguration.java | 50 +++++++++++++++++++ .../file/service/S3FileStorageService.java | 6 --- .../web/file/web/S3FileController.java | 6 +-- .../web/file/web/S3FileControllerTest.java | 15 ++---- 6 files changed, 61 insertions(+), 54 deletions(-) create mode 100644 hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/S3FileStorageConfiguration.java diff --git a/hsweb-system/hsweb-system-file/pom.xml b/hsweb-system/hsweb-system-file/pom.xml index 4e75f347c..bda09ae7e 100644 --- a/hsweb-system/hsweb-system-file/pom.xml +++ b/hsweb-system/hsweb-system-file/pom.xml @@ -59,6 +59,7 @@ software.amazon.awssdk s3 ${aws.sdk.version} + true 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 1816b47d8..dc44b1422 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 @@ -2,7 +2,6 @@ import org.hswebframework.web.file.service.FileStorageService; import org.hswebframework.web.file.service.LocalFileStorageService; -import org.hswebframework.web.file.service.S3FileStorageService; import org.hswebframework.web.file.web.ReactiveFileController; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -10,17 +9,11 @@ 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; -import org.springframework.context.annotation.Primary; -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; @AutoConfiguration -@EnableConfigurationProperties({FileUploadProperties.class,S3StorageProperties.class}) +@EnableConfigurationProperties(FileUploadProperties.class) +@ConditionalOnProperty(name = "file.storage", havingValue = "local", matchIfMissing = true) public class FileServiceConfiguration { @@ -28,40 +21,18 @@ public class FileServiceConfiguration { static class ReactiveConfiguration { @Bean - @ConditionalOnProperty(name = "file.storage", havingValue = "local", matchIfMissing = true) + @ConditionalOnMissingBean(FileStorageService.class) public FileStorageService fileStorageService(FileUploadProperties properties) { return new LocalFileStorageService(properties); } @Bean - @ConditionalOnProperty(name = "file.storage", havingValue = "s3") + @ConditionalOnMissingBean(name = "reactiveFileController") public ReactiveFileController reactiveFileController(FileUploadProperties properties, FileStorageService storageService) { return new ReactiveFileController(properties, storageService); } - - - - @Bean - @ConditionalOnMissingBean(FileStorageService.class) - public FileStorageService s3FileStorageService(S3StorageProperties properties, - S3Client s3Client) { - return new S3FileStorageService(properties, s3Client); - } - - @Bean - @ConditionalOnProperty(name = "file.storage", havingValue = "s3") - public S3Client s3Client(S3StorageProperties properties) { - return S3Client.builder() - .endpointOverride(URI.create(properties.getEndpoint())) - .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create( - properties.getAccessKey(), properties.getSecretKey() - ))) - .region(Region.of(properties.getRegion())) - .build(); - } - } } 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..6f896d7ca --- /dev/null +++ b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/S3FileStorageConfiguration.java @@ -0,0 +1,50 @@ +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.S3FileController; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +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 = "file.storage", havingValue = "s3", matchIfMissing = false) +@EnableConfigurationProperties(S3StorageProperties.class) +public class S3FileStorageConfiguration { + + @Bean + @ConditionalOnBean(S3StorageProperties.class) + @ConditionalOnMissingBean(name = "s3FileController") + public S3FileController s3FileController(S3StorageProperties properties, + FileStorageService storageService) { + return new S3FileController(properties, storageService); + } + + @Bean + @ConditionalOnMissingBean + public S3Client s3Client(S3StorageProperties 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 + @ConditionalOnMissingBean(FileStorageService.class) + public FileStorageService s3FileStorageService(S3StorageProperties properties, S3Client s3Client) { + return new S3FileStorageService(properties, s3Client); + } +} 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 index b43991435..b4519b9d7 100644 --- 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 @@ -3,24 +3,18 @@ import lombok.AllArgsConstructor; import lombok.SneakyThrows; import org.hswebframework.web.file.S3StorageProperties; -import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.http.MediaType; import org.springframework.http.codec.multipart.FilePart; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; -import software.amazon.awssdk.core.async.AsyncRequestBody; 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; -import java.util.concurrent.CompletableFuture; @AllArgsConstructor public class S3FileStorageService implements FileStorageService { diff --git a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/S3FileController.java b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/S3FileController.java index b8d8d6bd7..20f0aae6d 100644 --- a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/S3FileController.java +++ b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/S3FileController.java @@ -23,10 +23,10 @@ import java.io.IOException; @RestController -@Resource(id = "ossFile", name = "文件上传") +@Resource(id = "ossFile", name = "oss文件上传") @Slf4j -@RequestMapping("/oss/file") -@Tag(name = "文件上传") +@RequestMapping("/ossFile") +@Tag(name = "oss文件上传") public class S3FileController { private final S3StorageProperties properties; diff --git a/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/S3FileControllerTest.java b/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/S3FileControllerTest.java index 319accbce..3ac209850 100644 --- a/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/S3FileControllerTest.java +++ b/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/S3FileControllerTest.java @@ -1,30 +1,21 @@ package org.hswebframework.web.file.web; -import org.hswebframework.web.file.FileServiceConfiguration; +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.autoconfigure.SpringBootApplication; import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpEntity; import org.springframework.http.MediaType; -import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyInserters; -import static org.junit.Assert.*; - @WebFluxTest(S3FileController.class) @RunWith(SpringRunner.class) -@ImportAutoConfiguration(FileServiceConfiguration.class) +@ImportAutoConfiguration(S3FileStorageConfiguration.class) public class S3FileControllerTest { static { @@ -42,7 +33,7 @@ public class S3FileControllerTest { @Test public void test(){ client.post() - .uri("/oss/file/static") + .uri("/ossFile/static") .contentType(MediaType.MULTIPART_FORM_DATA) .body(BodyInserters.fromMultipartData("file",new HttpEntity<>(new ClassPathResource("test.json")))) .exchange() From d9fece3105b3aa17b04f92b7c6e4fe9901bf8092 Mon Sep 17 00:00:00 2001 From: liujiang Date: Tue, 17 Jun 2025 11:10:02 +0800 Subject: [PATCH 3/9] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E6=B5=81=E7=9A=84=E8=BF=94=E5=9B=9E=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hswebframework/web/file/service/S3FileStorageService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index b4519b9d7..99db91386 100644 --- 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 @@ -58,7 +58,7 @@ public Mono saveFile(InputStream inputStream, String fileType) { .build(); s3Client.putObject(request, RequestBody.fromInputStream(inputStream, inputStream.available())); - return properties.getBaseUrl() + "/" + key; + return buildFileUrl(key); }) .subscribeOn(Schedulers.boundedElastic()); } From 72c89ca35e209c26160c7a3a657442040b74190e Mon Sep 17 00:00:00 2001 From: liujiang Date: Tue, 17 Jun 2025 14:57:01 +0800 Subject: [PATCH 4/9] =?UTF-8?q?=E8=A1=A5=E9=BD=90=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E6=B5=81=E4=B8=8A=E4=BC=A0=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/file/web/S3FileController.java | 30 ++++++++++++++++--- .../web/file/web/S3FileControllerTest.java | 19 +++++++++++- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/S3FileController.java b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/S3FileController.java index 20f0aae6d..360241a94 100644 --- a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/S3FileController.java +++ b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/S3FileController.java @@ -12,14 +12,15 @@ import org.hswebframework.web.file.FileUploadProperties; import org.hswebframework.web.file.S3StorageProperties; import org.hswebframework.web.file.service.FileStorageService; +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.ByteArrayInputStream; import java.io.IOException; @RestController @@ -28,6 +29,7 @@ @RequestMapping("/ossFile") @Tag(name = "oss文件上传") public class S3FileController { + private final S3StorageProperties properties; private final FileStorageService fileStorageService; @@ -63,4 +65,24 @@ public Mono uploadStatic(@RequestPart("file") }); } + + @PostMapping(value = "/stream", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE) + @Operation(summary = "上传文件流") + public Mono uploadStream(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()) + .map(dataBuffer -> { + byte[] bytes = new byte[dataBuffer.readableByteCount()]; + dataBuffer.read(bytes); + DataBufferUtils.release(dataBuffer); + return new ByteArrayInputStream(bytes); + }) + .flatMap(inputStream -> fileStorageService.saveFile(inputStream, fileType)); + } + } diff --git a/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/S3FileControllerTest.java b/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/S3FileControllerTest.java index 3ac209850..c807c3d7b 100644 --- a/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/S3FileControllerTest.java +++ b/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/S3FileControllerTest.java @@ -11,6 +11,7 @@ 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(S3FileController.class) @@ -31,7 +32,7 @@ public class S3FileControllerTest { WebTestClient client; @Test - public void test(){ + public void testStatic(){ client.post() .uri("/ossFile/static") .contentType(MediaType.MULTIPART_FORM_DATA) @@ -41,4 +42,20 @@ public void test(){ .isOk(); } + + @Test + public void testStream() throws Exception { + byte[] fileBytes = StreamUtils.copyToByteArray(new ClassPathResource("test.json").getInputStream()); + + client.post() + .uri(uriBuilder -> + uriBuilder.path("/oss/file/stream") + .queryParam("fileType", "json") + .build()) + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .bodyValue(fileBytes) + .exchange() + .expectStatus().isOk(); + } + } \ No newline at end of file From a5c1e7d5b9cdd346b49376c6ebdb3f011a41ee0c Mon Sep 17 00:00:00 2001 From: liujiang Date: Tue, 17 Jun 2025 19:50:52 +0800 Subject: [PATCH 5/9] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/file/FileServiceConfiguration.java | 2 +- .../web/file/FileUploadProperties.java | 13 +++ .../web/file/S3FileStorageConfiguration.java | 31 ++++--- .../web/file/S3StorageProperties.java | 85 ------------------ .../file/service/S3FileStorageService.java | 14 +-- .../web/file/web/ReactiveFileController.java | 57 ++++++++++-- .../web/file/web/S3FileController.java | 88 ------------------- ...ControllerTest.java => OssUploadTest.java} | 20 ++--- .../file/web/ReactiveFileControllerTest.java | 2 +- 9 files changed, 96 insertions(+), 216 deletions(-) delete mode 100644 hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/S3StorageProperties.java delete mode 100644 hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/S3FileController.java rename hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/{S3FileControllerTest.java => OssUploadTest.java} (74%) 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 dc44b1422..f11369e74 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 @@ -13,7 +13,7 @@ @AutoConfiguration @EnableConfigurationProperties(FileUploadProperties.class) -@ConditionalOnProperty(name = "file.storage", havingValue = "local", matchIfMissing = true) +@ConditionalOnProperty(name = "hsweb.file.storage", havingValue = "local", matchIfMissing = true) public class FileServiceConfiguration { 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..24839434a 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; @@ -29,6 +30,8 @@ public class FileUploadProperties { private String staticLocation = "/static"; + private S3 s3; + //是否使用原始文件名进行存储 private boolean useOriginalFileName = false; @@ -126,4 +129,14 @@ public static class StaticFileInfo { private String relativeLocation; private String location; } + + @Data + public static class S3 { + private String endpoint; + private String accessKey; + private String secretKey; + private String region; + private String bucket; + private String baseUrl; + } } 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 index 6f896d7ca..be5352e14 100644 --- 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 @@ -2,8 +2,7 @@ import org.hswebframework.web.file.service.FileStorageService; import org.hswebframework.web.file.service.S3FileStorageService; -import org.hswebframework.web.file.web.S3FileController; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +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; @@ -19,32 +18,32 @@ @Configuration @ConditionalOnClass(S3Client.class) -@ConditionalOnProperty(name = "file.storage", havingValue = "s3", matchIfMissing = false) -@EnableConfigurationProperties(S3StorageProperties.class) +@ConditionalOnProperty(name = "hsweb.file.storage", havingValue = "s3", matchIfMissing = false) +@EnableConfigurationProperties(FileUploadProperties.class) public class S3FileStorageConfiguration { - @Bean - @ConditionalOnBean(S3StorageProperties.class) - @ConditionalOnMissingBean(name = "s3FileController") - public S3FileController s3FileController(S3StorageProperties properties, - FileStorageService storageService) { - return new S3FileController(properties, storageService); - } @Bean @ConditionalOnMissingBean - public S3Client s3Client(S3StorageProperties properties) { + public S3Client s3Client(FileUploadProperties properties) { return S3Client.builder() - .endpointOverride(URI.create(properties.getEndpoint())) + .endpointOverride(URI.create(properties.getS3().getEndpoint())) .credentialsProvider(StaticCredentialsProvider.create( - AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()))) - .region(Region.of(properties.getRegion())) + AwsBasicCredentials.create(properties.getS3().getAccessKey(), properties.getS3().getSecretKey()))) + .region(Region.of(properties.getS3().getRegion())) .build(); } @Bean @ConditionalOnMissingBean(FileStorageService.class) - public FileStorageService s3FileStorageService(S3StorageProperties properties, S3Client s3Client) { + public FileStorageService s3FileStorageService(FileUploadProperties properties, S3Client s3Client) { return new S3FileStorageService(properties, s3Client); } + + @Bean + @ConditionalOnMissingBean(name = "reactiveFileController") + public ReactiveFileController reactiveFileController(FileUploadProperties properties, + FileStorageService storageService) { + return new ReactiveFileController(properties, storageService); + } } diff --git a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/S3StorageProperties.java b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/S3StorageProperties.java deleted file mode 100644 index 909cbdc81..000000000 --- a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/S3StorageProperties.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.hswebframework.web.file; - -import lombok.Data; -import org.apache.commons.collections4.CollectionUtils; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.http.MediaType; - -import java.io.File; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.attribute.PosixFileAttributeView; -import java.nio.file.attribute.PosixFilePermission; -import java.util.Locale; -import java.util.Set; - -@Data -@ConfigurationProperties(prefix = "oss.s3") -public class S3StorageProperties { - private String endpoint; - private String region; - private String accessKey; - private String secretKey; - private String bucket; - private String baseUrl; - - private Set allowFiles; - - private Set denyFiles; - - private Set allowMediaType; - - private Set denyMediaType; - - private Set permissions; - - - public void applyFilePermission(File file) { - - if (CollectionUtils.isEmpty(permissions)) { - return; - } - try { - Path path = Paths.get(file.toURI()); - PosixFileAttributeView view = Files.getFileAttributeView(path, PosixFileAttributeView.class); - view.setPermissions(permissions); - } catch (Throwable ignore) { - // 失败时忽略,兼容Windows等不支持Posix的系统 - } - } - - public boolean denied(String name, MediaType mediaType) { - String suffix = (name.contains(".") ? name.substring(name.lastIndexOf(".") + 1) : "").toLowerCase(Locale.ROOT); - boolean defaultDeny = false; - if (CollectionUtils.isNotEmpty(denyFiles)) { - if (denyFiles.contains(suffix)) { - return true; - } - defaultDeny = false; - } - - if (CollectionUtils.isNotEmpty(allowFiles)) { - if (allowFiles.contains(suffix)) { - return false; - } - defaultDeny = true; - } - - if (CollectionUtils.isNotEmpty(denyMediaType)) { - if (denyMediaType.contains(mediaType.toString())) { - return true; - } - defaultDeny = false; - } - - if (CollectionUtils.isNotEmpty(allowMediaType)) { - if (allowMediaType.contains(mediaType.toString())) { - return false; - } - defaultDeny = true; - } - - return defaultDeny; - } -} 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 index 99db91386..17e7b2705 100644 --- 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 @@ -2,7 +2,7 @@ import lombok.AllArgsConstructor; import lombok.SneakyThrows; -import org.hswebframework.web.file.S3StorageProperties; +import org.hswebframework.web.file.FileUploadProperties; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.codec.multipart.FilePart; import reactor.core.publisher.Mono; @@ -19,7 +19,7 @@ @AllArgsConstructor public class S3FileStorageService implements FileStorageService { - private final S3StorageProperties properties; + private final FileUploadProperties properties; private final S3Client s3Client; @@ -36,7 +36,7 @@ public Mono saveFile(FilePart filePart) { }) .map(inputStream -> { PutObjectRequest request = PutObjectRequest.builder() - .bucket(properties.getBucket()) + .bucket(properties.getS3().getBucket()) .key(filename) .build(); @@ -53,7 +53,7 @@ public Mono saveFile(InputStream inputStream, String fileType) { String key = UUID.randomUUID().toString() + (fileType.startsWith(".") ? fileType : "." + fileType); PutObjectRequest request = PutObjectRequest.builder() - .bucket(properties.getBucket()) + .bucket(properties.getS3().getBucket()) .key(key) .build(); @@ -72,9 +72,9 @@ private String buildFileName(String originalName) { } private String buildFileUrl(String key) { - if (properties.getBaseUrl() != null && !properties.getBaseUrl().isEmpty()) { - return properties.getBaseUrl() + "/" + key; + if (properties.getS3().getBaseUrl() != null && !properties.getS3().getBaseUrl().isEmpty()) { + return properties.getS3().getBaseUrl() + "/" + key; } - return "https://" + properties.getBucket() + "." + properties.getEndpoint().replace("https://", "").replace("http://", "") + "/" + key; + return "https://" + properties.getS3().getBucket() + "." + properties.getS3().getEndpoint().replace("https://", "").replace("http://", "") + "/" + key; } } 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 f5b97dc48..eda383a15 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 @@ -10,19 +10,16 @@ import org.hswebframework.web.authorization.annotation.ResourceAction; import org.hswebframework.web.authorization.exception.AccessDenyException; import org.hswebframework.web.file.FileUploadProperties; -import org.hswebframework.web.file.S3StorageProperties; import org.hswebframework.web.file.service.FileStorageService; -import org.hswebframework.web.file.service.S3FileStorageService; -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; @RestController @@ -68,4 +65,48 @@ public Mono uploadStatic(@RequestPart("file") } + @PostMapping("/oss/static") + @SneakyThrows + @ResourceAction(id = "upload-static", name = "静态文件") + @Operation(summary = "上传静态文件") + public Mono uploadOssStatic(@RequestPart("file") + @Parameter(name = "file", description = "文件", style = ParameterStyle.FORM) Mono partMono) { + return partMono + .flatMap(part -> { + if (part instanceof FilePart) { + FilePart filePart = ((FilePart) part); + if (properties.denied(filePart.filename(), filePart.headers().getContentType())) { + return Mono.error( new AccessDenyException()); + } + try { + return fileStorageService.saveFile(filePart); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + return Mono.error(() -> new IllegalArgumentException("[file] part is not a file")); + } + }); + + } + + @PostMapping(value = "/oss/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()) + .map(dataBuffer -> { + byte[] bytes = new byte[dataBuffer.readableByteCount()]; + dataBuffer.read(bytes); + DataBufferUtils.release(dataBuffer); + return new ByteArrayInputStream(bytes); + }) + .flatMap(inputStream -> fileStorageService.saveFile(inputStream, fileType)); + } + } diff --git a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/S3FileController.java b/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/S3FileController.java deleted file mode 100644 index 360241a94..000000000 --- a/hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/S3FileController.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.hswebframework.web.file.web; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.enums.ParameterStyle; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.hswebframework.web.authorization.annotation.Resource; -import org.hswebframework.web.authorization.annotation.ResourceAction; -import org.hswebframework.web.authorization.exception.AccessDenyException; -import org.hswebframework.web.file.FileUploadProperties; -import org.hswebframework.web.file.S3StorageProperties; -import org.hswebframework.web.file.service.FileStorageService; -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.http.server.reactive.ServerHttpRequest; -import org.springframework.web.bind.annotation.*; -import reactor.core.publisher.Mono; - -import java.io.ByteArrayInputStream; -import java.io.IOException; - -@RestController -@Resource(id = "ossFile", name = "oss文件上传") -@Slf4j -@RequestMapping("/ossFile") -@Tag(name = "oss文件上传") -public class S3FileController { - - private final S3StorageProperties properties; - - private final FileStorageService fileStorageService; - - - public S3FileController(S3StorageProperties properties, FileStorageService fileStorageService) { - this.properties = properties; - this.fileStorageService = fileStorageService; - } - - - @PostMapping("/static") - @SneakyThrows - @ResourceAction(id = "upload-static", name = "静态文件") - @Operation(summary = "上传静态文件") - public Mono uploadStatic(@RequestPart("file") - @Parameter(name = "file", description = "文件", style = ParameterStyle.FORM) Mono partMono) { - return partMono - .flatMap(part -> { - if (part instanceof FilePart) { - FilePart filePart = ((FilePart) part); - if (properties.denied(filePart.filename(), filePart.headers().getContentType())) { - return Mono.error( new AccessDenyException()); - } - try { - return fileStorageService.saveFile(filePart); - } catch (IOException e) { - throw new RuntimeException(e); - } - } else { - return Mono.error(() -> new IllegalArgumentException("[file] part is not a file")); - } - }); - - } - - @PostMapping(value = "/stream", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE) - @Operation(summary = "上传文件流") - public Mono uploadStream(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()) - .map(dataBuffer -> { - byte[] bytes = new byte[dataBuffer.readableByteCount()]; - dataBuffer.read(bytes); - DataBufferUtils.release(dataBuffer); - return new ByteArrayInputStream(bytes); - }) - .flatMap(inputStream -> fileStorageService.saveFile(inputStream, fileType)); - } - -} diff --git a/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/S3FileControllerTest.java b/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/OssUploadTest.java similarity index 74% rename from hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/S3FileControllerTest.java rename to hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/OssUploadTest.java index c807c3d7b..098753a2e 100644 --- a/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/S3FileControllerTest.java +++ b/hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/web/OssUploadTest.java @@ -14,18 +14,18 @@ import org.springframework.util.StreamUtils; import org.springframework.web.reactive.function.BodyInserters; -@WebFluxTest(S3FileController.class) +@WebFluxTest(ReactiveFileController.class) @RunWith(SpringRunner.class) @ImportAutoConfiguration(S3FileStorageConfiguration.class) -public class S3FileControllerTest { +public class OssUploadTest { static { - System.setProperty("oss.s3.endpoint", "https://oss-cn-beijing.aliyuncs.com"); - System.setProperty("oss.s3.region", "us-east-1"); - System.setProperty("oss.s3.accessKey", ""); - System.setProperty("oss.s3.secretKey", ""); - System.setProperty("oss.s3.bucket", "maydaysansan"); - System.setProperty("file.storage", "s3"); + 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 @@ -34,7 +34,7 @@ public class S3FileControllerTest { @Test public void testStatic(){ client.post() - .uri("/ossFile/static") + .uri("/file/oss/static") .contentType(MediaType.MULTIPART_FORM_DATA) .body(BodyInserters.fromMultipartData("file",new HttpEntity<>(new ClassPathResource("test.json")))) .exchange() @@ -49,7 +49,7 @@ public void testStream() throws Exception { client.post() .uri(uriBuilder -> - uriBuilder.path("/oss/file/stream") + uriBuilder.path("/file/oss/stream") .queryParam("fileType", "json") .build()) .contentType(MediaType.APPLICATION_OCTET_STREAM) 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 b76c5b436..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,7 +29,7 @@ public class ReactiveFileControllerTest { static { System.setProperty("hsweb.file.upload.static-file-path","./target/upload"); - System.setProperty("file.storage", "local"); + System.setProperty("hsweb.file.storage","local"); // System.setProperty("hsweb.file.upload.use-original-file-name","true"); } From a4915530eb85ca499bd3d9ff58ab9e373257d6dc Mon Sep 17 00:00:00 2001 From: liujiang Date: Tue, 17 Jun 2025 20:45:49 +0800 Subject: [PATCH 6/9] =?UTF-8?q?=E6=96=B0=E5=A2=9ES3FileProperties=20?= =?UTF-8?q?=E4=BF=AE=E6=94=B9error=E5=BA=94=E5=BD=93=E7=94=B1Mono=E4=BC=A0?= =?UTF-8?q?=E9=80=92=EF=BC=8C=E8=80=8C=E4=B8=8D=E6=98=AF=E6=8A=9B=E5=87=BA?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=20=E7=A7=BB=E9=99=A4=E9=9D=9E=E5=BF=85?= =?UTF-8?q?=E8=A6=81Condition=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/file/FileServiceConfiguration.java | 2 -- .../web/file/S3FileProperties.java | 15 +++++++++++++++ .../web/file/S3FileStorageConfiguration.java | 16 +++++++++------- .../web/file/service/FileStorageService.java | 2 +- .../web/file/service/S3FileStorageService.java | 16 +++++++++------- .../web/file/web/ReactiveFileController.java | 13 ++----------- 6 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/S3FileProperties.java 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 f11369e74..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 @@ -5,7 +5,6 @@ import org.hswebframework.web.file.web.ReactiveFileController; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -13,7 +12,6 @@ @AutoConfiguration @EnableConfigurationProperties(FileUploadProperties.class) -@ConditionalOnProperty(name = "hsweb.file.storage", havingValue = "local", matchIfMissing = true) public class FileServiceConfiguration { 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 index be5352e14..643a0871b 100644 --- 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 @@ -19,25 +19,27 @@ @Configuration @ConditionalOnClass(S3Client.class) @ConditionalOnProperty(name = "hsweb.file.storage", havingValue = "s3", matchIfMissing = false) -@EnableConfigurationProperties(FileUploadProperties.class) +@EnableConfigurationProperties({S3FileProperties.class, FileUploadProperties.class}) public class S3FileStorageConfiguration { @Bean @ConditionalOnMissingBean - public S3Client s3Client(FileUploadProperties properties) { + public S3Client s3Client(S3FileProperties properties) { return S3Client.builder() - .endpointOverride(URI.create(properties.getS3().getEndpoint())) + .endpointOverride(URI.create(properties.getEndpoint())) .credentialsProvider(StaticCredentialsProvider.create( - AwsBasicCredentials.create(properties.getS3().getAccessKey(), properties.getS3().getSecretKey()))) - .region(Region.of(properties.getS3().getRegion())) + AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()))) + .region(Region.of(properties.getRegion())) .build(); } @Bean @ConditionalOnMissingBean(FileStorageService.class) - public FileStorageService s3FileStorageService(FileUploadProperties properties, S3Client s3Client) { - return new S3FileStorageService(properties, s3Client); + public FileStorageService s3FileStorageService(FileUploadProperties uploadProperties, + S3FileProperties s3Properties, + S3Client s3Client) { + return new S3FileStorageService(uploadProperties, s3Properties, s3Client); } @Bean 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 3920cf0de..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 @@ -20,7 +20,7 @@ public interface FileStorageService { * @param filePart FilePart * @return 文件访问地址 */ - Mono saveFile(FilePart filePart) throws IOException; + Mono saveFile(FilePart filePart); /** * 使用文件流保存文件,并返回文件地址 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 index 17e7b2705..88a4828b9 100644 --- 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 @@ -3,6 +3,7 @@ import lombok.AllArgsConstructor; import lombok.SneakyThrows; import org.hswebframework.web.file.FileUploadProperties; +import org.hswebframework.web.file.S3FileProperties; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.codec.multipart.FilePart; import reactor.core.publisher.Mono; @@ -19,9 +20,10 @@ @AllArgsConstructor public class S3FileStorageService implements FileStorageService { - private final FileUploadProperties properties; - + private final FileUploadProperties fileProperties; + private final S3FileProperties s3Properties; private final S3Client s3Client; + @Override public Mono saveFile(FilePart filePart) { @@ -36,7 +38,7 @@ public Mono saveFile(FilePart filePart) { }) .map(inputStream -> { PutObjectRequest request = PutObjectRequest.builder() - .bucket(properties.getS3().getBucket()) + .bucket(s3Properties.getBucket()) .key(filename) .build(); @@ -53,7 +55,7 @@ public Mono saveFile(InputStream inputStream, String fileType) { String key = UUID.randomUUID().toString() + (fileType.startsWith(".") ? fileType : "." + fileType); PutObjectRequest request = PutObjectRequest.builder() - .bucket(properties.getS3().getBucket()) + .bucket(s3Properties.getBucket()) .key(key) .build(); @@ -72,9 +74,9 @@ private String buildFileName(String originalName) { } private String buildFileUrl(String key) { - if (properties.getS3().getBaseUrl() != null && !properties.getS3().getBaseUrl().isEmpty()) { - return properties.getS3().getBaseUrl() + "/" + key; + if (s3Properties.getBaseUrl() != null && !s3Properties.getBaseUrl().isEmpty()) { + return s3Properties.getBaseUrl() + "/" + key; } - return "https://" + properties.getS3().getBucket() + "." + properties.getS3().getEndpoint().replace("https://", "").replace("http://", "") + "/" + key; + return "https://" + s3Properties.getBucket() + "." + s3Properties.getEndpoint().replace("https://", "").replace("http://", "") + "/" + key; } } 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 eda383a15..62256ac6c 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 @@ -53,11 +53,7 @@ public Mono uploadStatic(@RequestPart("file") if (properties.denied(filePart.filename(), filePart.headers().getContentType())) { return Mono.error( new AccessDenyException()); } - try { - return fileStorageService.saveFile(filePart); - } catch (IOException e) { - throw new RuntimeException(e); - } + return fileStorageService.saveFile(filePart); } else { return Mono.error(() -> new IllegalArgumentException("[file] part is not a file")); } @@ -66,7 +62,6 @@ public Mono uploadStatic(@RequestPart("file") } @PostMapping("/oss/static") - @SneakyThrows @ResourceAction(id = "upload-static", name = "静态文件") @Operation(summary = "上传静态文件") public Mono uploadOssStatic(@RequestPart("file") @@ -78,11 +73,7 @@ public Mono uploadOssStatic(@RequestPart("file") if (properties.denied(filePart.filename(), filePart.headers().getContentType())) { return Mono.error( new AccessDenyException()); } - try { - return fileStorageService.saveFile(filePart); - } catch (IOException e) { - throw new RuntimeException(e); - } + return fileStorageService.saveFile(filePart); } else { return Mono.error(() -> new IllegalArgumentException("[file] part is not a file")); } From ec8edfaf8dc342b403dbe7ee56320c7ce98fefc5 Mon Sep 17 00:00:00 2001 From: liujiang Date: Tue, 17 Jun 2025 21:11:33 +0800 Subject: [PATCH 7/9] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E8=A7=84=E8=8C=83=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/file/S3FileStorageConfiguration.java | 12 ++----- .../file/service/S3FileStorageService.java | 16 ++++----- .../web/file/web/ReactiveFileController.java | 34 ++++--------------- .../web/file/web/OssUploadTest.java | 4 +-- 4 files changed, 17 insertions(+), 49 deletions(-) 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 index 643a0871b..83b9cee1a 100644 --- 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 @@ -35,17 +35,9 @@ public S3Client s3Client(S3FileProperties properties) { } @Bean - @ConditionalOnMissingBean(FileStorageService.class) - public FileStorageService s3FileStorageService(FileUploadProperties uploadProperties, + public FileStorageService s3FileStorageService( S3FileProperties s3Properties, S3Client s3Client) { - return new S3FileStorageService(uploadProperties, s3Properties, s3Client); - } - - @Bean - @ConditionalOnMissingBean(name = "reactiveFileController") - public ReactiveFileController reactiveFileController(FileUploadProperties properties, - FileStorageService storageService) { - return new ReactiveFileController(properties, storageService); + return new S3FileStorageService(s3Properties, s3Client); } } 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 index 88a4828b9..b93ca9430 100644 --- 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 @@ -2,7 +2,6 @@ import lombok.AllArgsConstructor; import lombok.SneakyThrows; -import org.hswebframework.web.file.FileUploadProperties; import org.hswebframework.web.file.S3FileProperties; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.codec.multipart.FilePart; @@ -19,9 +18,8 @@ @AllArgsConstructor public class S3FileStorageService implements FileStorageService { - - private final FileUploadProperties fileProperties; - private final S3FileProperties s3Properties; + + private final S3FileProperties properties; private final S3Client s3Client; @@ -38,7 +36,7 @@ public Mono saveFile(FilePart filePart) { }) .map(inputStream -> { PutObjectRequest request = PutObjectRequest.builder() - .bucket(s3Properties.getBucket()) + .bucket(properties.getBucket()) .key(filename) .build(); @@ -55,7 +53,7 @@ public Mono saveFile(InputStream inputStream, String fileType) { String key = UUID.randomUUID().toString() + (fileType.startsWith(".") ? fileType : "." + fileType); PutObjectRequest request = PutObjectRequest.builder() - .bucket(s3Properties.getBucket()) + .bucket(properties.getBucket()) .key(key) .build(); @@ -74,9 +72,9 @@ private String buildFileName(String originalName) { } private String buildFileUrl(String key) { - if (s3Properties.getBaseUrl() != null && !s3Properties.getBaseUrl().isEmpty()) { - return s3Properties.getBaseUrl() + "/" + key; + if (properties.getBaseUrl() != null && !properties.getBaseUrl().isEmpty()) { + return properties.getBaseUrl() + "/" + key; } - return "https://" + s3Properties.getBucket() + "." + s3Properties.getEndpoint().replace("https://", "").replace("http://", "") + "/" + key; + return "https://" + properties.getBucket() + "." + properties.getEndpoint().replace("https://", "").replace("http://", "") + "/" + key; } } 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 62256ac6c..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 @@ -21,6 +21,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; @RestController @Resource(id = "file", name = "文件上传") @@ -61,27 +62,7 @@ public Mono uploadStatic(@RequestPart("file") } - @PostMapping("/oss/static") - @ResourceAction(id = "upload-static", name = "静态文件") - @Operation(summary = "上传静态文件") - public Mono uploadOssStatic(@RequestPart("file") - @Parameter(name = "file", description = "文件", style = ParameterStyle.FORM) Mono partMono) { - return partMono - .flatMap(part -> { - if (part instanceof FilePart) { - FilePart filePart = ((FilePart) part); - if (properties.denied(filePart.filename(), filePart.headers().getContentType())) { - return Mono.error( new AccessDenyException()); - } - return fileStorageService.saveFile(filePart); - } else { - return Mono.error(() -> new IllegalArgumentException("[file] part is not a file")); - } - }); - - } - - @PostMapping(value = "/oss/stream", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE) + @PostMapping(value = "/static/stream", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE) @Operation(summary = "上传文件流") public Mono uploadOssStream(ServerHttpRequest request, @RequestParam("fileType") String fileType) { @@ -91,13 +72,10 @@ public Mono uploadOssStream(ServerHttpRequest request, } return DataBufferUtils.join(request.getBody()) - .map(dataBuffer -> { - byte[] bytes = new byte[dataBuffer.readableByteCount()]; - dataBuffer.read(bytes); - DataBufferUtils.release(dataBuffer); - return new ByteArrayInputStream(bytes); - }) - .flatMap(inputStream -> fileStorageService.saveFile(inputStream, fileType)); + .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 index 098753a2e..f047c2325 100644 --- 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 @@ -34,7 +34,7 @@ public class OssUploadTest { @Test public void testStatic(){ client.post() - .uri("/file/oss/static") + .uri("/file/static") .contentType(MediaType.MULTIPART_FORM_DATA) .body(BodyInserters.fromMultipartData("file",new HttpEntity<>(new ClassPathResource("test.json")))) .exchange() @@ -49,7 +49,7 @@ public void testStream() throws Exception { client.post() .uri(uriBuilder -> - uriBuilder.path("/file/oss/stream") + uriBuilder.path("/file/static/stream") .queryParam("fileType", "json") .build()) .contentType(MediaType.APPLICATION_OCTET_STREAM) From 947c6954d4f285b2f781f6a48f0a9bccbc651b90 Mon Sep 17 00:00:00 2001 From: liujiang Date: Thu, 19 Jun 2025 10:01:26 +0800 Subject: [PATCH 8/9] =?UTF-8?q?=E6=8F=90=E4=BA=A4UriComponentsBuilder?= =?UTF-8?q?=E6=9E=84=E5=BB=BAurl=20=E9=87=87=E7=94=A8buffer.asInputStream?= =?UTF-8?q?=E8=AE=BF=E9=97=AE=E6=96=87=E4=BB=B6=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E5=86=97=E4=BD=99=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/file/FileUploadProperties.java | 12 ----- .../file/service/S3FileStorageService.java | 46 ++++++++++++------- 2 files changed, 29 insertions(+), 29 deletions(-) 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 24839434a..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 @@ -30,8 +30,6 @@ public class FileUploadProperties { private String staticLocation = "/static"; - private S3 s3; - //是否使用原始文件名进行存储 private boolean useOriginalFileName = false; @@ -129,14 +127,4 @@ public static class StaticFileInfo { private String relativeLocation; private String location; } - - @Data - public static class S3 { - private String endpoint; - private String accessKey; - private String secretKey; - private String region; - private String bucket; - private String baseUrl; - } } 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 index b93ca9430..97483d6fc 100644 --- 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 @@ -5,6 +5,7 @@ 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; @@ -12,6 +13,7 @@ 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; @@ -26,23 +28,22 @@ public class S3FileStorageService implements FileStorageService { @Override public Mono saveFile(FilePart filePart) { String filename = buildFileName(filePart.filename()); + return DataBufferUtils.join(filePart.content()) - .publishOn(Schedulers.boundedElastic()) - .map(dataBuffer -> { - byte[] bytes = new byte[dataBuffer.readableByteCount()]; - dataBuffer.read(bytes); - DataBufferUtils.release(dataBuffer); - return new ByteArrayInputStream(bytes); - }) - .map(inputStream -> { - PutObjectRequest request = PutObjectRequest.builder() - .bucket(properties.getBucket()) - .key(filename) - .build(); + .flatMap(dataBuffer -> { + try (InputStream inputStream = dataBuffer.asInputStream(true)) { + PutObjectRequest request = PutObjectRequest.builder() + .bucket(properties.getBucket()) + .key(filename) + .build(); - s3Client.putObject(request, RequestBody.fromInputStream(inputStream, inputStream.available())); - return buildFileUrl(filename); - }); + s3Client.putObject(request, RequestBody.fromInputStream(inputStream, dataBuffer.readableByteCount())); + return Mono.just(buildFileUrl(filename)); + } catch (IOException e) { + return Mono.error(e); + } + }) + .subscribeOn(Schedulers.boundedElastic()); } @@ -73,8 +74,19 @@ private String buildFileName(String originalName) { private String buildFileUrl(String key) { if (properties.getBaseUrl() != null && !properties.getBaseUrl().isEmpty()) { - return properties.getBaseUrl() + "/" + key; + return UriComponentsBuilder + .fromUriString(properties.getBaseUrl()) + .pathSegment(key) + .build() + .toUriString(); } - return "https://" + properties.getBucket() + "." + properties.getEndpoint().replace("https://", "").replace("http://", "") + "/" + key; + String host = properties.getBucket() + "." + properties.getEndpoint().replaceFirst("^https?://", ""); + return UriComponentsBuilder + .newInstance() + .scheme("https") + .host(host) + .pathSegment(key) + .build() + .toUriString(); } } From 6e7dd9ee9fc7f48edfdc79a28e9199870300f8c4 Mon Sep 17 00:00:00 2001 From: liujiang Date: Thu, 19 Jun 2025 10:36:59 +0800 Subject: [PATCH 9/9] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=8A=E4=BC=A0static?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/service/S3FileStorageService.java | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) 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 index 97483d6fc..89339a48e 100644 --- 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 @@ -1,5 +1,6 @@ package org.hswebframework.web.file.service; +import com.google.common.io.Files; import lombok.AllArgsConstructor; import lombok.SneakyThrows; import org.hswebframework.web.file.S3FileProperties; @@ -28,22 +29,12 @@ public class S3FileStorageService implements FileStorageService { @Override public Mono saveFile(FilePart filePart) { String filename = buildFileName(filePart.filename()); - + return DataBufferUtils.join(filePart.content()) .flatMap(dataBuffer -> { - try (InputStream inputStream = dataBuffer.asInputStream(true)) { - PutObjectRequest request = PutObjectRequest.builder() - .bucket(properties.getBucket()) - .key(filename) - .build(); - - s3Client.putObject(request, RequestBody.fromInputStream(inputStream, dataBuffer.readableByteCount())); - return Mono.just(buildFileUrl(filename)); - } catch (IOException e) { - return Mono.error(e); - } - }) - .subscribeOn(Schedulers.boundedElastic()); + InputStream inputStream = dataBuffer.asInputStream(true); + return saveFile(inputStream, Files.getFileExtension(filename)); + }); }