diff --git a/pom.xml b/pom.xml index 843ee6e8..96dad4c8 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,6 @@ 1.3.0 - 8.5.10 4.12.0 3.16.3 @@ -214,11 +213,11 @@ ${fastexcel.version} - + - io.minio - minio - ${minio.version} + software.amazon.awssdk + s3 + 2.54.2 diff --git a/src/main/java/com/youlai/boot/config/property/FileStorageProperties.java b/src/main/java/com/youlai/boot/config/property/FileStorageProperties.java index b732beba..fb02715a 100644 --- a/src/main/java/com/youlai/boot/config/property/FileStorageProperties.java +++ b/src/main/java/com/youlai/boot/config/property/FileStorageProperties.java @@ -12,7 +12,7 @@ import java.util.stream.Collectors; /** * 文件存储顶层配置:存储类型与上传限制。 *

- * minio / aliyun / local 子树由各自 {@code FileService} 实现类绑定, + * rustfs / aliyun / local 子树由各自 {@code FileService} 实现类绑定, * 此处仅承载 {@code type} 与 {@code upload}。 */ @Data @@ -20,7 +20,7 @@ import java.util.stream.Collectors; @ConfigurationProperties(prefix = "file-storage") public class FileStorageProperties { - /** 存储类型:minio | aliyun | local */ + /** 存储类型:rustfs | aliyun | local */ private String type; private Upload upload = new Upload(); diff --git a/src/main/java/com/youlai/boot/support/file/service/impl/AliyunFileServiceImpl.java b/src/main/java/com/youlai/boot/support/file/service/impl/AliyunFileServiceImpl.java index b50a32ec..3fb16ce7 100644 --- a/src/main/java/com/youlai/boot/support/file/service/impl/AliyunFileServiceImpl.java +++ b/src/main/java/com/youlai/boot/support/file/service/impl/AliyunFileServiceImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.model.ObjectMetadata; @@ -28,7 +29,8 @@ import java.time.LocalDateTime; * 阿里云 OSS 文件存储服务。 *

* 当 {@code file-storage.type=aliyun} 时生效。 - * 上传文件按日期分目录存储,URL 格式:{@code https://bucket.endpoint/yyyyMMdd/uuid.suffix}。 + * 上传文件按日期分目录存储,URL 格式:{@code 协议://bucket.endpoint/yyyyMMdd/uuid.suffix}, + * 协议取自 endpoint 配置(未指定时默认 https)。 */ @Component @Slf4j @@ -38,7 +40,7 @@ import java.time.LocalDateTime; @Data public class AliyunFileServiceImpl implements FileService { - /** OSS 地域节点,如 {@code oss-cn-hangzhou.aliyuncs.com} */ + /** OSS 地域节点,如 {@code oss-cn-hangzhou.aliyuncs.com},兼容带 {@code http(s)://} 前缀写法 */ private String endpoint; /** AccessKey ID */ private String accessKeyId; @@ -51,11 +53,11 @@ public class AliyunFileServiceImpl implements FileService { @PostConstruct public void init() { - aliyunOssClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); + aliyunOssClient = new OSSClientBuilder().build(endpointWithProtocol(), accessKeyId, accessKeySecret); } /** - * 上传文件到阿里云 OSS,按日期分目录,返回 {@code https://bucket.endpoint/path} 格式的 URL。 + * 上传文件到阿里云 OSS,按日期分目录,返回 {@code 协议://bucket.endpoint/path} 格式的 URL。 */ @Override @SneakyThrows @@ -73,7 +75,7 @@ public class AliyunFileServiceImpl implements FileService { log.error("文件上传失败", e); throw new BusinessException("文件上传失败", e); } - String fileUrl = "https://" + bucket + "." + endpoint + "/" + fileName; + String fileUrl = getFileHost() + "/" + fileName; FileInfo fileInfo = new FileInfo(); fileInfo.setName(originalFilename); fileInfo.setUrl(fileUrl); @@ -89,9 +91,33 @@ public class AliyunFileServiceImpl implements FileService { @Override public boolean deleteFile(String filePath) { Assert.notBlank(filePath, "删除文件路径不能为空"); - String fileHost = "https://" + bucket + "." + endpoint; - String fileName = filePath.substring(fileHost.length() + 1); + String fileName = filePath.substring(getFileHost().length() + 1); aliyunOssClient.deleteObject(bucket, fileName); return true; } + + /** + * 补全 endpoint 协议前缀:已带 {@code http://} 或 {@code https://} 的仅去除尾部 {@code /}, + * 纯域名写法默认补 {@code https://},避免 OSS 客户端对无协议 endpoint 默认走 http 明文传输。 + */ + private String endpointWithProtocol() { + String trimmed = StrUtil.trim(endpoint); + if (!StrUtil.startWithIgnoreCase(trimmed, "http://") && !StrUtil.startWithIgnoreCase(trimmed, "https://")) { + trimmed = "https://" + trimmed; + } + return StrUtil.removeSuffix(trimmed, "/"); + } + + /** + * 获取文件访问主机地址(含协议),如 {@code https://bucket.oss-cn-hangzhou.aliyuncs.com}。 + *

+ * 协议以 endpoint 配置为准,未指定时默认 https;上传拼接 URL 与删除解析 URL 共用,保证两处口径一致。 + */ + private String getFileHost() { + String endpointWithProtocol = endpointWithProtocol(); + int schemeEnd = endpointWithProtocol.indexOf("://"); + String protocol = endpointWithProtocol.substring(0, schemeEnd); + String host = endpointWithProtocol.substring(schemeEnd + "://".length()); + return protocol + "://" + bucket + "." + host; + } } diff --git a/src/main/java/com/youlai/boot/support/file/service/impl/MinioFileServiceImpl.java b/src/main/java/com/youlai/boot/support/file/service/impl/MinioFileServiceImpl.java deleted file mode 100644 index 072a1d81..00000000 --- a/src/main/java/com/youlai/boot/support/file/service/impl/MinioFileServiceImpl.java +++ /dev/null @@ -1,159 +0,0 @@ -package com.youlai.boot.support.file.service.impl; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import com.youlai.boot.common.exception.BusinessException; -import com.youlai.boot.common.result.ResultCode; -import com.youlai.boot.support.file.model.FileInfo; -import com.youlai.boot.support.file.service.FileService; -import io.minio.*; -import io.minio.http.Method; -import jakarta.annotation.PostConstruct; -import lombok.Data; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; -import org.springframework.web.multipart.MultipartFile; - -import java.io.InputStream; -import java.time.LocalDateTime; - -/** - * MinIO 文件存储服务。 - *

- * 当 {@code file-storage.type=minio} 时生效。 - * 上传文件按日期分目录存储,路径格式:{@code yyyyMMdd/uuid.suffix}。 - *

- */ -@Component -@ConditionalOnProperty(value = "file-storage.type", havingValue = "minio") -@ConfigurationProperties(prefix = "file-storage.minio") -@RequiredArgsConstructor -@Data -@Slf4j -public class MinioFileServiceImpl implements FileService { - - /** MinIO 服务端点,如 {@code http://localhost:9000} */ - private String endpoint; - /** 访问密钥 */ - private String accessKey; - /** 秘密密钥 */ - private String secretKey; - /** 存储桶名称 */ - private String bucket; - /** 自定义域名(可选),配置后不再使用预签名 URL */ - private String domain; - - private MinioClient minioClient; - - @PostConstruct - public void init() { - minioClient = MinioClient.builder() - .endpoint(endpoint) - .credentials(accessKey, secretKey) - .build(); - } - - /** - * 上传文件到 MinIO,按日期分目录,自动创建存储桶,返回文件访问 URL。 - */ - @Override - public FileInfo uploadFile(MultipartFile file) { - createBucketIfAbsent(bucket); - String originalFilename = file.getOriginalFilename(); - String suffix = FileUtil.getSuffix(originalFilename); - String dateFolder = DateUtil.format(LocalDateTime.now(), "yyyyMMdd"); - String fileName = IdUtil.simpleUUID() + "." + suffix; - - try (InputStream inputStream = file.getInputStream()) { - PutObjectArgs putObjectArgs = PutObjectArgs.builder() - .bucket(bucket) - .object(dateFolder + "/" + fileName) - .contentType(file.getContentType()) - .stream(inputStream, inputStream.available(), -1) - .build(); - minioClient.putObject(putObjectArgs); - - String fileUrl; - if (StrUtil.isBlank(domain)) { - GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder() - .bucket(bucket) - .object(dateFolder + "/" + fileName) - .method(Method.GET) - .build(); - fileUrl = minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs); - fileUrl = fileUrl.substring(0, fileUrl.indexOf("?")); - } else { - fileUrl = domain + "/" + bucket + "/" + dateFolder + "/" + fileName; - } - - FileInfo fileInfo = new FileInfo(); - fileInfo.setName(originalFilename); - fileInfo.setUrl(fileUrl); - return fileInfo; - } catch (Exception e) { - log.error("上传文件失败", e); - throw new BusinessException(ResultCode.UPLOAD_FILE_EXCEPTION, e.getMessage()); - } - } - - /** - * 删除文件。 - *

- * 从完整 URL 中提取文件名后调用 MinIO 删除。 - * - * @param filePath 文件完整 URL - * @return 删除成功返回 true - */ - @Override - public boolean deleteFile(String filePath) { - Assert.notBlank(filePath, "删除文件路径不能为空"); - try { - String fileName; - if (StrUtil.isNotBlank(domain)) { - fileName = filePath.substring(domain.length() + 1 + bucket.length() + 1); - } else { - fileName = filePath.substring(endpoint.length() + 1 + bucket.length() + 1); - } - minioClient.removeObject(RemoveObjectArgs.builder() - .bucket(bucket).object(fileName).build()); - return true; - } catch (Exception e) { - log.error("删除文件失败", e); - throw new BusinessException(ResultCode.DELETE_FILE_EXCEPTION, e.getMessage()); - } - } - - /** 生成公开读的存储桶策略 JSON */ - private static String publicBucketPolicy(String bucket) { - return "{\"Version\":\"2012-10-17\"," - + "\"Statement\":[{\"Effect\":\"Allow\"," - + "\"Principal\":{\"AWS\":[\"*\"]}," - + "\"Action\":[\"s3:ListBucketMultipartUploads\",\"s3:GetBucketLocation\",\"s3:ListBucket\"]," - + "\"Resource\":[\"arn:aws:s3:::" + bucket + "\"]}," - + "{\"Effect\":\"Allow\",\"" + "\"Principal\":{\"AWS\":[\"*\"]}," - + "\"Action\":[\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\"]," - + "\"Resource\":[\"arn:aws:s3:::" + bucket + "/*\"]}]}"; - } - - /** - * 存储桶不存在时自动创建,并设置为公开读策略。 - */ - @SneakyThrows - private void createBucketIfAbsent(String bucket) { - if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build())) { - minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build()); - minioClient.setBucketPolicy(SetBucketPolicyArgs.builder() - .bucket(bucket).config(publicBucketPolicy(bucket)).build()); - } - } -} diff --git a/src/main/java/com/youlai/boot/support/file/service/impl/RustFSFileServiceImpl.java b/src/main/java/com/youlai/boot/support/file/service/impl/RustFSFileServiceImpl.java new file mode 100644 index 00000000..290e3ed6 --- /dev/null +++ b/src/main/java/com/youlai/boot/support/file/service/impl/RustFSFileServiceImpl.java @@ -0,0 +1,209 @@ +package com.youlai.boot.support.file.service.impl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.youlai.boot.common.exception.BusinessException; +import com.youlai.boot.common.result.ResultCode; +import com.youlai.boot.support.file.model.FileInfo; +import com.youlai.boot.support.file.service.FileService; +import jakarta.annotation.PostConstruct; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3Configuration; +import software.amazon.awssdk.services.s3.model.CreateBucketRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.HeadBucketRequest; +import software.amazon.awssdk.services.s3.model.PutBucketPolicyRequest; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.S3Exception; +import software.amazon.awssdk.services.s3.presigner.S3Presigner; +import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; + +import java.net.URI; +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * RustFS 文件存储服务(S3 协议)。 + *

+ * 当 {@code file-storage.type=rustfs} 时生效。通过 AWS SDK 的 S3Client + * 连接 RustFS(S3 兼容对象存储),IP 端点必须走 path-style 寻址。 + * 上传文件按日期分目录存储,路径格式:{@code yyyyMMdd/uuid.suffix}。 + *

+ * + * @author youlai.tech + */ +@Component +@ConditionalOnProperty(value = "file-storage.type", havingValue = "rustfs") +@ConfigurationProperties(prefix = "file-storage.rustfs") +@RequiredArgsConstructor +@Data +@Slf4j +public class RustFSFileServiceImpl implements FileService { + + /** S3 对象存储服务端点,如 {@code http://localhost:9000} */ + private String endpoint; + /** 访问密钥 */ + private String accessKey; + /** 秘密密钥 */ + private String secretKey; + /** 存储桶名称 */ + private String bucket; + /** 自定义域名(可选),配置后不再使用预签名 URL */ + private String domain; + + private S3Client s3Client; + private S3Presigner presigner; + + @PostConstruct + public void init() { + // IP 端点必须走 path-style:桶名作为路径而不是子域名(public.IP 无法被 DNS 解析) + S3Configuration s3Config = S3Configuration.builder() + .pathStyleAccessEnabled(true) + .build(); + s3Client = S3Client.builder() + .endpointOverride(URI.create(endpoint)) + // 非 AWS 服务必填,值仅区分区域,任意即可 + .region(Region.US_EAST_1) + .serviceConfiguration(s3Config) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create(accessKey, secretKey))) + .build(); + presigner = S3Presigner.builder() + .endpointOverride(URI.create(endpoint)) + .region(Region.US_EAST_1) + .serviceConfiguration(s3Config) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create(accessKey, secretKey))) + .build(); + } + + /** + * 上传文件到 RustFS,按日期分目录,自动创建存储桶,返回文件访问 URL。 + */ + @Override + @SneakyThrows + public FileInfo uploadFile(MultipartFile file) { + createBucketIfAbsent(bucket); + String originalFilename = file.getOriginalFilename(); + String suffix = FileUtil.getSuffix(originalFilename); + String dateFolder = DateUtil.format(LocalDateTime.now(), "yyyyMMdd"); + String fileName = IdUtil.simpleUUID() + "." + suffix; + + try { + byte[] bytes = file.getBytes(); + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(bucket) + .key(dateFolder + "/" + fileName) + .contentType(file.getContentType()) + .build(); + // 用固定长度的字节数组上传,避免流式分块签名在某些 S3 兼容服务上报校验和不匹配 + s3Client.putObject(putObjectRequest, RequestBody.fromBytes(bytes)); + + String fileUrl; + if (StrUtil.isBlank(domain)) { + fileUrl = presignedUrl(dateFolder, fileName); + } else { + fileUrl = domain + "/" + bucket + "/" + dateFolder + "/" + fileName; + } + + FileInfo fileInfo = new FileInfo(); + fileInfo.setName(originalFilename); + fileInfo.setUrl(fileUrl); + return fileInfo; + } catch (Exception e) { + log.error("上传文件失败", e); + throw new BusinessException(ResultCode.UPLOAD_FILE_EXCEPTION, e.getMessage()); + } + } + + /** + * 生成预签名 GET URL 并去掉 query 参数,路径直达对象。 + * 依赖桶已开匿名只读(详见 createBucketIfAbsent 的策略),签名参数在 URL 中无实际作用。 + */ + private String presignedUrl(String dateFolder, String fileName) { + GetObjectRequest getObjectRequest = GetObjectRequest.builder() + .bucket(bucket) + .key(dateFolder + "/" + fileName) + .build(); + GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder() + .signatureDuration(Duration.ofMinutes(10)) + .getObjectRequest(getObjectRequest) + .build(); + String url = presigner.presignGetObject(presignRequest).url().toString(); + return url.substring(0, url.indexOf("?")); + } + + /** + * 删除文件。 + *

+ * 从完整 URL 中提取对象名后调用 S3 删除。 + * + * @param filePath 文件完整 URL + * @return 删除成功返回 true + */ + @Override + public boolean deleteFile(String filePath) { + Assert.notBlank(filePath, "删除文件路径不能为空"); + try { + String fileName; + if (StrUtil.isNotBlank(domain)) { + fileName = filePath.substring(domain.length() + 1 + bucket.length() + 1); + } else { + fileName = filePath.substring(endpoint.length() + 1 + bucket.length() + 1); + } + s3Client.deleteObject(DeleteObjectRequest.builder() + .bucket(bucket).key(fileName).build()); + return true; + } catch (Exception e) { + log.error("删除文件失败", e); + throw new BusinessException(ResultCode.DELETE_FILE_EXCEPTION, e.getMessage()); + } + } + + /** 公开只读策略:匿名可列桶与读对象,禁止任何匿名写入 */ + private static String publicBucketPolicy(String bucket) { + return "{\"Version\":\"2012-10-17\"," + + "\"Statement\":[{\"Effect\":\"Allow\"," + + "\"Principal\":{\"AWS\":[\"*\"]}," + + "\"Action\":[\"s3:GetObject\",\"s3:ListBucket\"]," + + "\"Resource\":[\"arn:aws:s3:::" + bucket + "\",\"arn:aws:s3:::" + bucket + "/*\"]}]}"; + } + + /** + * 存储桶不存在时自动创建,并设置为公开只读策略。 + */ + @SneakyThrows + private void createBucketIfAbsent(String bucket) { + try { + s3Client.headBucket(HeadBucketRequest.builder().bucket(bucket).build()); + } catch (S3Exception e) { + // 桶不存在(404)时才创建;其他错误(如权限不足)放行由调用方抛出 + if (e.statusCode() == 404) { + s3Client.createBucket(CreateBucketRequest.builder().bucket(bucket).build()); + s3Client.putBucketPolicy(PutBucketPolicyRequest.builder() + .bucket(bucket).policy(publicBucketPolicy(bucket)).build()); + } else { + throw e; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/youlai/boot/system/service/impl/RoleServiceImpl.java b/src/main/java/com/youlai/boot/system/service/impl/RoleServiceImpl.java index bf65a57d..b62faf43 100644 --- a/src/main/java/com/youlai/boot/system/service/impl/RoleServiceImpl.java +++ b/src/main/java/com/youlai/boot/system/service/impl/RoleServiceImpl.java @@ -55,7 +55,7 @@ public class RoleServiceImpl extends ServiceImpl implements Ro * 角色分页列表 * * @param queryParams 角色查询参数 - * @return {@link Page< RolePageVO >} – 角色分页列表 + * @return {@link Page} – 角色分页列表 */ @Override public Page getRolePage(RoleQuery queryParams) { diff --git a/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 3e7ba880..9ed766b1 100644 --- a/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -3,7 +3,7 @@ { "name": "file-storage.type", "type": "java.lang.String", - "description": "文件存储类型:minio | aliyun | local" + "description": "文件存储类型:rustfs | aliyun | local" }, { "name": "file-storage.upload.max-file-size", @@ -16,29 +16,29 @@ "description": "允许的文件扩展名白名单(置空表示不限制)" }, { - "name": "file-storage.minio.endpoint", + "name": "file-storage.rustfs.endpoint", "type": "java.lang.String", - "description": "MinIO 服务 Endpoint" + "description": "RustFS(S3) 服务 Endpoint" }, { - "name": "file-storage.minio.access-key", + "name": "file-storage.rustfs.access-key", "type": "java.lang.String", - "description": "MinIO 访问凭据" + "description": "RustFS 访问凭据" }, { - "name": "file-storage.minio.secret-key", + "name": "file-storage.rustfs.secret-key", "type": "java.lang.String", - "description": "MinIO 凭据密钥" + "description": "RustFS 凭据密钥" }, { - "name": "file-storage.minio.bucket", + "name": "file-storage.rustfs.bucket", "type": "java.lang.String", - "description": "MinIO 存储桶名称" + "description": "RustFS 存储桶名称" }, { - "name": "file-storage.minio.domain", + "name": "file-storage.rustfs.domain", "type": "java.lang.String", - "description": "MinIO 自定义域名(文件 URL 域名)" + "description": "RustFS 自定义域名(文件 URL 域名)" }, { "name": "file-storage.aliyun.endpoint", diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 10c9fd0e..50ccbdd4 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -108,8 +108,8 @@ security: # 文件存储 file-storage: - # 存储类型:minio | aliyun | local - type: minio + # 存储类型:rustfs | aliyun | local + type: rustfs # 上传限制 upload: # 单文件大小上限(同时作为 spring.servlet.multipart 的上限,单一来源) @@ -120,8 +120,8 @@ file-storage: - jpeg - png - gif - # MinIO 对象存储 - minio: + # RustFS(S3) 对象存储 + rustfs: endpoint: http://111.229.83.153:9000 access-key: bybaddp7zyARpgNbEGKf secret-key: p9rBdQZPBIJcMH23iyFkZkXmmawbmwPlk3JLlaaj diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index f7b4dab6..2ff6dccd 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -107,8 +107,8 @@ security: # 文件存储 file-storage: - # 存储类型:minio | aliyun | local - type: minio + # 存储类型:rustfs | aliyun | local + type: rustfs # 上传限制 upload: # 单文件大小上限(同时作为 spring.servlet.multipart 的上限,单一来源) @@ -119,11 +119,11 @@ file-storage: - jpeg - png - gif - # MinIO 对象存储 - minio: + # RustFS(S3) 对象存储 + rustfs: endpoint: http://localhost:9000 - access-key: minioadmin - secret-key: minioadmin + access-key: rustfs-admin + secret-key: rustfs-admin bucket: public # 自定义域名:配置后文件 URL 走域名,留空则用 endpoint(IP)格式 domain: