Nacos 配置中心 + OSS / Minio 文件上传实战

环境

组件 版本
Spring Boot 3.5.16
Spring Cloud 2025.0.0
Spring Cloud Alibaba 2025.0.0.0
Nacos Server 3.0.3
Java 17

Nacos 控制台怎么创建配置文件

登录 Nacos 控制台 http://127.0.0.1:8848/nacos,左侧菜单 配置管理 → 配置列表,点右上角 + 号。

Data ID:   idle-store-oss-dev.yaml
Group:     DEFAULT_GROUP
配置格式:   YAML
命名空间:   idlestore
配置内容:   见下面

Data ID 的命名规则是 {spring.application.name}-{profile}.{file-extension}。项目里 application-dev.yml 写的 optional:nacos:idle-store-oss-dev.yaml,所以 Data ID 就是 idle-store-oss-dev.yaml

注意:spring.config.import 方式不会自动读取 spring.application.name 来拼 Data ID,你得在 import URL 里写完整。这和 bootstrap 方式不一样,老方式只需要配 prefixfile-extension,框架自动拼。


OSS + Minio 完整 Nacos 配置

项目里用一个 storage.type 来控制走阿里云 OSS 还是 Minio,FileStrategyFactory 根据这个值返回对应的策略 Bean。两套 SDK 的 client 也都是从配置读参数创建的。

Nacos 里 idle-store-oss-dev.yaml 的完整内容:

storage:
  type: aliyun
  minio:
    endpoint: http://127.0.0.1:9000
    accessKey: admin
    secretKey: 12345678
  aliyun-oss:
    accessKey: xxx
    secretKey: xxx
    endpoint: oss-cn-beijing.aliyuncs.com

每个字段的含义:

字段 含义 示例值
storage.type 切换存储类型 aliyun / minio
storage.minio.endpoint Minio 服务地址 http://127.0.0.1:9000
storage.minio.accessKey Minio 用户名 admin
storage.minio.secretKey Minio 密码 12345678
storage.aliyun-oss.accessKey 阿里云 RAM AccessKey LTAI5...
storage.aliyun-oss.secretKey 阿里云 RAM SecretKey wxsck...
storage.aliyun-oss.bucket OSS Bucket 名称 typora-picture-0
storage.aliyun-oss.endpoint OSS 地域 Endpoint oss-cn-beijing.aliyuncs.com

本地 application-dev.yml 只要保留 Nacos 连接信息,不要写这些业务配置:

spring:
  application:
    name: idle-store-oss
  config:
    import:
      - optional:nacos:idle-store-oss-dev.yaml?group=DEFAULT_GROUP&refreshEnabled=true
  cloud:
    nacos:
      config:
        group: DEFAULT_GROUP
        server-addr: 127.0.0.1:8848
        namespace: idlestore
      discovery:
        enabled: true
        group: DEFAULT_GROUP
        namespace: idlestore
        server-addr: 127.0.0.1:8848

代码怎么绑这些配置

完整的工程结构:

idle-store-oss/idle-store-oss-biz/src/main/java/com/lh/idlestore/oss/biz/
├── config/
│   ├── StorageProperties.java       ← storage.type
│   ├── MinioProperties.java         ← storage.minio.*
│   ├── AliyunOSSProperties.java     ← storage.aliyun-oss.*
│   ├── AliyunOSSConfig.java         ← 创建 OSS Client
│   └── MinioConfig.java             ← 创建 Minio Client
├── strategy/
│   ├── FileStrategy.java            ← 上传接口
│   └── impl/
│       ├── AliyunOSSFileStrategy.java
│       └── MinioFileStrategy.java
├── factory/
│   └── FileStrategyFactory.java     ← 根据 storage.type 选策略
├── controller/
│   └── FileController.java          ← 上传接口
└── service/
    ├── FileService.java
    └── impl/
        └── FileServiceImpl.java

Properties 类——三层配置对应三个类

// storage.type
@ConfigurationProperties("storage")
@Component
@RefreshScope
@Data
public class StorageProperties {
    private String type;
}
// storage.minio
@ConfigurationProperties("storage.minio")
@Component
@RefreshScope
@Data
public class MinioProperties {
    private String endpoint;
    private String accessKey;
    private String secretKey;
}
// storage.aliyun-oss
@ConfigurationProperties("storage.aliyun-oss")
@Component
@RefreshScope
@Data
public class AliyunOSSProperties {
    private String endpoint;
    private String accessKey;
    private String secretKey;
}

prefix. 分割,Spring Boot 自动把 yaml 的嵌套结构映射到对应的 Properties 类。

Client 创建——从 Properties 读参数

// 阿里云 OSS Client
@Configuration
public class AliyunOSSConfig {

    @Resource
    private AliyunOSSProperties aliyunOSSProperties;

    @Bean
    public OSS aliyunOSSClient() {
        DefaultCredentialProvider credentialsProvider = CredentialsProviderFactory
                .newDefaultCredentialProvider(
                        aliyunOSSProperties.getAccessKey(),
                        aliyunOSSProperties.getSecretKey());
        return new OSSClientBuilder()
                .build(aliyunOSSProperties.getEndpoint(), credentialsProvider);
    }
}
// Minio Client
@Configuration
public class MinioConfig {

    @Resource
    private MinioProperties minioProperties;

    @Bean
    public MinioClient minioClient() {
        return MinioClient.builder()
                .endpoint(minioProperties.getEndpoint())
                .credentials(minioProperties.getAccessKey(),
                             minioProperties.getSecretKey())
                .build();
    }
}

策略工厂——用 storage.type 决定走哪个

@Configuration
@Slf4j
public class FileStrategyFactory {

    @Autowired
    private StorageProperties storageProperties;

    @Bean
    public FileStrategy getFileStrategy() {
        log.info("配置变为了: {}", storageProperties.getType());
        if (StringUtils.equals(storageProperties.getType(), "minio")) {
            return new MinioFileStrategy();
        } else if (StringUtils.equals(storageProperties.getType(), "aliyun")) {
            return new AliyunOSSFileStrategy();
        }
        throw new IllegalArgumentException("不可用的存储类型");
    }
}

注意:FileStrategyFactory 上没有 @RefreshScopeStorageProperties 上有。因为 StorageProperties 加了 @RefreshScope,它的代理每次 getType() 都会从 Environment 拿最新值,所以工厂不需要自己加 @RefreshScope

文件上传——策略模式,调用方不用管底层

// 接口
public interface FileStrategy {
    String uploadFile(MultipartFile file, String bucketName);
}
// 阿里云 OSS 实现
@Slf4j
@Component("aliyun")
public class AliyunOSSFileStrategy implements FileStrategy {

    @Resource
    private AliyunOSSProperties aliyunOSSProperties;

    @Resource
    private OSS ossClient;

    @Override
    @SneakyThrows
    public String uploadFile(MultipartFile file, String bucketName) {
        String originalFileName = file.getOriginalFilename();
        String key = UUID.randomUUID().toString().replace("-", "");
        String suffix = originalFileName.substring(originalFileName.lastIndexOf("."));
        String objectName = String.format("%s%s", key, suffix);

        ossClient.putObject(bucketName, objectName,
                new ByteArrayInputStream(file.getInputStream().readAllBytes()));

        // OSS 访问 URL 格式:https://{bucket}.{endpoint}/{objectName}
        return String.format("https://%s.%s/%s",
                bucketName, aliyunOSSProperties.getEndpoint(), objectName);
    }
}
// Minio 实现
@Slf4j
@Component("minio")
public class MinioFileStrategy implements FileStrategy {

    @Resource
    private MinioProperties minioProperties;

    @Resource
    private MinioClient minioClient;

    @Override
    @SneakyThrows
    public String uploadFile(MultipartFile file, String bucketName) {
        String originalFileName = file.getOriginalFilename();
        String contentType = file.getContentType();
        String key = UUID.randomUUID().toString().replace("-", "");
        String suffix = originalFileName.substring(originalFileName.lastIndexOf("."));
        String objectName = String.format("%s%s", key, suffix);

        minioClient.putObject(PutObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .stream(file.getInputStream(), file.getSize(), -1L)
                .contentType(contentType)
                .build());

        // Minio 访问 URL 格式:http://{endpoint}/{bucket}/{objectName}
        return String.format("%s/%s/%s",
                minioProperties.getEndpoint(), bucketName, objectName);
    }
}

阿里云 OSS 和 Minio 上传的区别:

阿里云 OSS Minio
URL 格式 https://{bucket}.{endpoint}/{objectName} http://{endpoint}/{bucket}/{objectName}
putObject 方式 ossClient.putObject(bucket, key, inputStream) minioClient.putObject(PutObjectArgs.builder()...)
ContentType 不需要手动设 PutObjectArgs.builder().contentType(...)
访问协议 HTTPS HTTP(本地 Minio)

调用入口

@RestController
@RequestMapping("/file")
public class FileController {

    @Resource
    private FileService fileService;

    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Response<?> uploadFile(@RequestPart(value = "file") MultipartFile file) {
        return fileService.uploadFile(file);
    }
}
@Service
public class FileServiceImpl implements FileService {

    @Resource
    private FileStrategyFactory fileStrategyFactory;

    private static final String BUCKET_NAME = "idlestore";

    @Override
    public Response<?> uploadFile(MultipartFile file) {
        String url = fileStrategyFactory.getFileStrategy()
                .uploadFile(file, BUCKET_NAME);
        return Response.success(url);
    }
}

切换存储类型的完整链路

在 Nacos 里把 storage.typealiyun 改成 minio,不用重启:

推送变更

@RefreshScope 检测到变化

getType() 返回新值

返回 MinioFileStrategy

Nacos 控制台
storage.type: aliyun → minio

Environment 更新

StorageProperties
type 字段变为 'minio'

FileStrategyFactory
getFileStrategy()

FileServiceImpl
调用 uploadFile()

MinioClient.putObject()


pom.xml 和 yml 模板

依赖

<dependencies>
    <!-- Nacos 配置中心 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>

    <!-- Nacos 服务发现 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <!-- ❌ 不要引入 spring-cloud-starter-bootstrap -->

    <!-- 阿里云 OSS SDK -->
    <dependency>
        <groupId>com.aliyun.oss</groupId>
        <artifactId>aliyun-sdk-oss</artifactId>
    </dependency>

    <!-- Minio SDK -->
    <dependency>
        <groupId>io.minio</groupId>
        <artifactId>minio</artifactId>
    </dependency>

    <!-- Spring Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

application-dev.yml

spring:
  application:
    name: idle-store-oss
  config:
    import:
      - optional:nacos:idle-store-oss-dev.yaml?group=DEFAULT_GROUP&refreshEnabled=true
  cloud:
    nacos:
      config:
        group: DEFAULT_GROUP
        server-addr: 127.0.0.1:8848
        namespace: idlestore
      discovery:
        enabled: true
        group: DEFAULT_GROUP
        namespace: idlestore
        server-addr: 127.0.0.1:8848

# 业务配置全部放 Nacos,这里不留

Nacos 里的 idle-store-oss-dev.yaml

storage:
  type: aliyun
  minio:
    endpoint: http://127.0.0.1:9000
    accessKey: admin
    secretKey: 12345678
  aliyun-oss:
    accessKey: your-aliyun-ak
    secretKey: your-aliyun-sk
    endpoint: oss-cn-beijing.aliyuncs.com
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐