From b9ca3339588920cdb088e5e21e0b1616ecdbff85 Mon Sep 17 00:00:00 2001 From: suyiiyii <suyiiyii@gmail.com> Date: Tue, 3 Sep 2024 16:13:00 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=8A=9F=E8=83=BD=E5=8F=8AS3=E9=9B=86?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新版本中添加了文件上传功能,并集成了S3进行文件存储。这包括控制器、服务和工具类的更新,以及配置S3客户端的bean配置。 --- pom.xml | 14 +- .../top/suyiiyii/sims/common/S3Config.java | 17 +++ .../sims/controller/FileController.java | 40 ++++++ .../suyiiyii/sims/service/FileService.java | 18 +++ .../top/suyiiyii/sims/utils/S3Client.java | 123 ++++++++++++++++++ src/main/resources/application.yaml | 15 ++- 6 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 src/main/java/top/suyiiyii/sims/common/S3Config.java create mode 100644 src/main/java/top/suyiiyii/sims/controller/FileController.java create mode 100644 src/main/java/top/suyiiyii/sims/service/FileService.java create mode 100644 src/main/java/top/suyiiyii/sims/utils/S3Client.java diff --git a/pom.xml b/pom.xml index 4374986..7611098 100644 --- a/pom.xml +++ b/pom.xml @@ -95,16 +95,28 @@ <artifactId>spring-restdocs-mockmvc</artifactId> <scope>test</scope> </dependency> + <!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> - <version>2.3.0</version> + <version>2.6.0</version> </dependency> + <dependency> <groupId>org.xerial</groupId> <artifactId>sqlite-jdbc</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-java-sdk-s3</artifactId> + <version>1.12.706</version> + </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + <version>2.11.0</version> + </dependency> </dependencies> <build> diff --git a/src/main/java/top/suyiiyii/sims/common/S3Config.java b/src/main/java/top/suyiiyii/sims/common/S3Config.java new file mode 100644 index 0000000..900618a --- /dev/null +++ b/src/main/java/top/suyiiyii/sims/common/S3Config.java @@ -0,0 +1,17 @@ +package top.suyiiyii.sims.common; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import top.suyiiyii.sims.utils.S3Client; + +@Configuration +public class S3Config { + @Bean + public static S3Client Config(@Value("${S3.ENDPOINT}") String endpoint, + @Value("${S3.ACCESS_KEY}") String accessKey, + @Value("${S3.SECRET_KEY}") String secretKey, + @Value("${S3.BUCKET}") String bucket) { + return new S3Client(endpoint, accessKey, secretKey, bucket); + } +} diff --git a/src/main/java/top/suyiiyii/sims/controller/FileController.java b/src/main/java/top/suyiiyii/sims/controller/FileController.java new file mode 100644 index 0000000..d66f458 --- /dev/null +++ b/src/main/java/top/suyiiyii/sims/controller/FileController.java @@ -0,0 +1,40 @@ +package top.suyiiyii.sims.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import top.suyiiyii.sims.common.AuthAccess; +import top.suyiiyii.sims.service.FileService; + +import java.io.IOException; +import java.io.InputStream; + +@Slf4j +@RestController +public class FileController { + @Autowired + FileService fileService; + + @AuthAccess(allowRoles = {"user"}) + @Operation(summary = "上传文件", description = "使用form-data格式\nfile:文件\nfilename: 文件名\n返回可访问的路径") + @PostMapping("/upload") + public String uploadFile( + @Parameter String filename, + @RequestBody(content = @Content(mediaType = "multipart/form-data", + schema = @Schema(type = "string", format = "binary"))) MultipartFile file) { + try (InputStream in = file.getInputStream()) { + log.info("文件上传,文件名:{},描述:{}", file.getOriginalFilename(), filename); + return fileService.uploadFile(in, filename); + } catch (IOException e) { + log.warn("文件上传失败", e); + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/top/suyiiyii/sims/service/FileService.java b/src/main/java/top/suyiiyii/sims/service/FileService.java new file mode 100644 index 0000000..f632a56 --- /dev/null +++ b/src/main/java/top/suyiiyii/sims/service/FileService.java @@ -0,0 +1,18 @@ +package top.suyiiyii.sims.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import top.suyiiyii.sims.utils.S3Client; + +import java.io.InputStream; + +@Service +public class FileService { + @Autowired + private S3Client s3Client; + + public String uploadFile(InputStream input,String fileName) { + String extension = fileName.substring(fileName.lastIndexOf(".")); + return s3Client.uploadFile(input, extension); + } +} diff --git a/src/main/java/top/suyiiyii/sims/utils/S3Client.java b/src/main/java/top/suyiiyii/sims/utils/S3Client.java new file mode 100644 index 0000000..837f121 --- /dev/null +++ b/src/main/java/top/suyiiyii/sims/utils/S3Client.java @@ -0,0 +1,123 @@ +package top.suyiiyii.sims.utils; + + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.Protocol; +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.S3ClientOptions; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.S3Object; +import org.apache.commons.io.IOUtils; + +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.UUID; + + +public class S3Client { + private final String endpoint; + private final String bucket; + private final AmazonS3 s3client; + + public S3Client(String endpoint, String accessKey, String secretKey, String bucket) { + this.endpoint = endpoint; + this.bucket = bucket; + URL endpointUrl; + try { + endpointUrl = new URL(endpoint); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + String protocol = endpointUrl.getProtocol(); + int port = endpointUrl.getPort() == -1 ? endpointUrl.getDefaultPort() : endpointUrl.getPort(); + ClientConfiguration clientConfig = new ClientConfiguration(); + clientConfig.setSignerOverride("S3SignerType"); + clientConfig.setProtocol(Protocol.valueOf(protocol.toUpperCase())); + // 禁用证书检查,避免https自签证书校验失败 + System.setProperty("com.amazonaws.sdk.disableCertChecking", "true"); + // 屏蔽 AWS 的 MD5 校验,避免校验导致的下载抛出异常问题 + System.setProperty("com.amazonaws.services.s3.disableGetObjectMD5Validation", "true"); + AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + // 创建 S3Client 实例 + AmazonS3 s3client = new AmazonS3Client(awsCredentials, clientConfig); + s3client.setEndpoint(endpointUrl.getHost() + ":" + port); + s3client.setS3ClientOptions(S3ClientOptions.builder().setPathStyleAccess(true).build()); + this.s3client = s3client; + } + + + public boolean bucketExists(String bucket) { + try { + return s3client.doesBucketExist(bucket); + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + public boolean existObject(String bucket, String objectId) { + try { + return s3client.doesObjectExist(bucket, objectId); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + public InputStream download(String bucket, String objectId) { + try { + S3Object o = s3client.getObject(bucket, objectId); + return o.getObjectContent(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public void download(String bucket, String objectId, OutputStream out) { + S3Object o = s3client.getObject(bucket, objectId); + try (InputStream in = o.getObjectContent()) { + IOUtils.copyLarge(in, out); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void upload(String bucket, String objectId, InputStream input) { + try { + // 创建文件上传的元数据 + ObjectMetadata meta = new ObjectMetadata(); + // 设置文件上传长度 + meta.setContentLength(input.available()); + // 上传 + s3client.putObject(bucket, objectId, input, meta); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public String uploadFile(InputStream input) { + String objectID = UUID.randomUUID().toString(); + upload(bucket, objectID, input); + return endpoint + "/" + bucket + "/" + objectID; + } + + /** + * 接收文件流,自动使用随机uuid命名并保留扩展名 + * 返回公网上可以直接访问的URL + * + * @param input 文件流 + * @param extensionName 扩展名 + * @return 文件的URL + */ + public String uploadFile(InputStream input, String extensionName) { + String objectID = UUID.randomUUID() + extensionName; + upload(bucket, objectID, input); + return endpoint + "/" + bucket + "/" + objectID; + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 4b7599a..17c0cca 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -2,6 +2,10 @@ spring: profiles: active: prod + servlet: + multipart: + max-file-size: 100MB + max-request-size: 100MB datasource: url: ${DATASOURCE_URL} username: ${DATASOURCE_USERNAME} @@ -14,4 +18,13 @@ auto-table: model-package: top.suyiiyii.sims.entity jwt: - secret: ${JWT_SECRET} \ No newline at end of file + secret: ${JWT_SECRET} + +S3: + ENDPOINT: ${S3_ENDPOINT} + BUCKET: ${S3_BUCKET} + ACCESS_KEY: ${S3_ACCESS_KEY} + SECRET_KEY: ${S3_SECRET_KEY} + +springdoc: + default-support-form-data: true \ No newline at end of file From d353304f3a8e7681d6a07fdb991d5d3bb7e1cce1 Mon Sep 17 00:00:00 2001 From: suyiiyii <suyiiyii@gmail.com> Date: Tue, 3 Sep 2024 16:34:35 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=9C=A8=E6=B5=8B=E8=AF=95=E4=B8=ADmock=20?= =?UTF-8?q?S3Client=EF=BC=8C=E9=81=BF=E5=85=8D=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 6 ++++++ src/main/resources/application.yaml | 3 --- .../java/top/suyiiyii/sims/service/RbacServiceTest.java | 8 ++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 7611098..df911c3 100644 --- a/pom.xml +++ b/pom.xml @@ -117,6 +117,12 @@ <artifactId>commons-io</artifactId> <version>2.11.0</version> </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <version>4.0.0</version> + <scope>test</scope> + </dependency> </dependencies> <build> diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 17c0cca..d54a8ca 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -25,6 +25,3 @@ S3: BUCKET: ${S3_BUCKET} ACCESS_KEY: ${S3_ACCESS_KEY} SECRET_KEY: ${S3_SECRET_KEY} - -springdoc: - default-support-form-data: true \ No newline at end of file diff --git a/src/test/java/top/suyiiyii/sims/service/RbacServiceTest.java b/src/test/java/top/suyiiyii/sims/service/RbacServiceTest.java index b06cec6..f46b2ae 100644 --- a/src/test/java/top/suyiiyii/sims/service/RbacServiceTest.java +++ b/src/test/java/top/suyiiyii/sims/service/RbacServiceTest.java @@ -1,10 +1,14 @@ package top.suyiiyii.sims.service; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.ActiveProfiles; import top.suyiiyii.sims.entity.Role; +import top.suyiiyii.sims.utils.S3Client; import java.util.List; @@ -12,11 +16,15 @@ import static org.junit.jupiter.api.Assertions.*; @SpringBootTest @ActiveProfiles("test") +@ExtendWith(MockitoExtension.class) class RbacServiceTest { @Autowired private RbacService rbacService; + @MockBean + private S3Client s3Client; + @Test void addRoleWithUserId() { int userId = 1; // mock userId