From b9ca3339588920cdb088e5e21e0b1616ecdbff85 Mon Sep 17 00:00:00 2001 From: suyiiyii Date: Tue, 3 Sep 2024 16:13:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E5=8A=9F=E8=83=BD=E5=8F=8AS3=E9=9B=86=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 @@ spring-restdocs-mockmvc test + org.springdoc springdoc-openapi-starter-webmvc-ui - 2.3.0 + 2.6.0 + org.xerial sqlite-jdbc test + + com.amazonaws + aws-java-sdk-s3 + 1.12.706 + + + commons-io + commons-io + 2.11.0 + 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