Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions hsweb-system/hsweb-system-file/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>${aws.sdk.version}</version>
<optional>true</optional>
</dependency>

</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.hswebframework.web.file;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.collections4.CollectionUtils;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.springframework.http.codec.multipart.FilePart;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.io.InputStream;

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.hswebframework.web.file.service;

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<String> 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());
}


@Override
@SneakyThrows
public Mono<String> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "文件上传")
Expand All @@ -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 = "静态文件")
Expand All @@ -59,4 +62,20 @@ public Mono<String> uploadStatic(@RequestPart("file")

}

@PostMapping(value = "/static/stream", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
@Operation(summary = "上传文件流")
public Mono<String> 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);
});
}

}
Original file line number Diff line number Diff line change
@@ -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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

Expand Down
2 changes: 2 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@
<swagger.version>2.7.0</swagger.version>
<netty.version>4.1.111.Final</netty.version>
<r2dbc.version>Borca-SR2</r2dbc.version>
<r2dbc.version>Borca-SR2</r2dbc.version>
<aws.sdk.version>2.25.5</aws.sdk.version>
</properties>

<profiles>
Expand Down