当前位置:首页 > 技术 > 正文内容

基于阿里云OSS与MinIO的文件存储集成方案

访客 技术 2026年7月24日 1

一、阿里云OSS对象存储接入流程

完成阿里云账号注册并开通OSS服务后,需创建存储桶(Bucket)并生成访问密钥(AccessKey)。配置信息应准确写入应用配置文件。

aliyun:
  oss:
    endpoint: https://oss-cn-hangzhou.aliyuncs.com
    access-key-id: LTAI5tS9qa9H2uftNY
    access-key-secret: VNEIKZfxBpa4HO9YLDx2i
    bucket-name: big-event993

配置类定义

使用Spring Boot的属性绑定机制,将配置项映射为可注入的实体类。

AliyunOSSConfig.java
package com.example.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Data
@Configuration
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliyunOSSConfig {
    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;
}

上传工具类实现

封装OSS客户端操作逻辑,支持按文件类型自动分类存储,并对路径进行安全编码处理。

AliOSSUtil.java
package com.example.util;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.PutObjectRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.InputStream;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

@Component
public class AliOSSUtil {

    @Autowired
    private AliyunOSSConfig config;

    public String uploadFile(String originalName, InputStream stream) throws Exception {
        String ext = getFileExtension(originalName);
        String dir = classifyByType(ext);
        String uniqueId = UUID.randomUUID().toString().substring(0, 4);
        String newName = originalName.substring(0, originalName.lastIndexOf('.')) + "-" + uniqueId + originalName.substring(originalName.lastIndexOf('.'));
        String objectName = dir + "/" + newName;

        OSS client = new OSSClientBuilder().build(config.getEndpoint(), config.getAccessKeyId(), config.getAccessKeySecret());
        String url = "";

        try {
            PutObjectRequest request = new PutObjectRequest(config.getBucketName(), objectName, stream);
            client.putObject(request);

            String encodedName = URLEncoder.encode(newName, StandardCharsets.UTF_8.toString()).replace("+", "%20");
            String encodedPath = objectName.substring(0, objectName.lastIndexOf('/') + 1) + encodedName;

            url = "https://" + config.getBucketName() + "." + config.getEndpoint().substring(config.getEndpoint().lastIndexOf("/") + 1) + "/" + encodedPath;
        } finally {
            client.shutdown();
        }

        return url;
    }

    private String getFileExtension(String name) {
        int idx = name.lastIndexOf('.');
        return idx != -1 ? name.substring(idx + 1).toLowerCase() : "";
    }

    private String classifyByType(String ext) {
        switch (ext) {
            case "mp3": case "wav": case "aac": case "ogg": case "flac":
                return "audio";
            case "mp4": case "avi": case "mov": case "mkv": case "webm":
                return "video";
            case "png": case "jpg": case "jpeg": case "gif": case "bmp":
                return "image";
            case "doc": case "pdf": case "txt": case "md":
                return "document";
            default:
                return "others";
        }
    }
}

控制器接口示例

提供标准HTTP接口用于文件上传与删除。

FileUploadController.java
package com.example.controller;

import com.example.result.ApiResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@RestController
@Tag(name = "文件管理")
public class FileUploadController {

    @Autowired
    private AliOSSUtil uploader;

    @PostMapping("/upload")
    @Operation(summary = "上传至OSS")
    public ApiResult<String> upload(@RequestParam("file") MultipartFile file) throws Exception {
        String url = uploader.uploadFile(file.getOriginalFilename(), file.getInputStream());
        return ApiResult.success(url);
    }

    @PostMapping("/delete")
    @Operation(summary = "删除OSS文件")
    public ApiResult<String> delete(@RequestParam("url") String fileUrl) {
        try {
            // 从URL解析出对象名并删除
            String bucket = config.getBucketName();
            String path = fileUrl.replace("https://" + bucket + ".", "").substring(1);
            uploader.deleteObject(bucket, path);
            return ApiResult.success("删除成功");
        } catch (Exception e) {
            return ApiResult.error("删除失败:" + e.getMessage());
        }
    }
}

二、MinIO本地化部署与集成

通过Docker快速搭建私有对象存储服务,适用于开发测试或内部系统。

Docker启动命令

docker run -d \
  --name minio \
  -p 9000:9000 \
  -p 9001:9001 \
  -v /data/minio:/data \
  -e MINIO_ROOT_USER=admin \
  -e MINIO_ROOT_PASSWORD=123456 \
  --restart always \
  minio/minio server /data --console-address ":9001"

配置文件设置

minio:
  url: http://localhost:9000
  access-key: admin
  secret-key: 123456
  bucket-name: my-bucket

MinIO配置类

MinIOConfig.java
package com.example.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinIOConfig {
    private String url;
    private String accessKey;
    private String secretKey;
    private String bucketName;
}

MinIO工具类实现

MinIOUtil.java
package com.example.util;

import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.errors.MinioException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.InputStream;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

@Component
public class MinIOUtil {

    @Autowired
    private MinIOConfig config;

    public String uploadFile(String fileName, InputStream stream) throws Exception {
        String ext = getFileExtension(fileName);
        String dir = classifyByType(ext);
        String uniqueId = UUID.randomUUID().toString().substring(0, 4);
        String newName = fileName.substring(0, fileName.lastIndexOf('.')) + "-" + uniqueId + fileName.substring(fileName.lastIndexOf('.'));
        String objectName = dir + "/" + newName;

        MinioClient client = MinioClient.builder()
                .endpoint(config.getUrl())
                .credentials(config.getAccessKey(), config.getSecretKey())
                .build();

        String url = "";
        try {
            PutObjectArgs args = PutObjectArgs.builder()
                    .bucket(config.getBucketName())
                    .object(objectName)
                    .stream(stream, stream.available(), -1)
                    .build();
            client.putObject(args);

            String encodedName = URLEncoder.encode(newName, StandardCharsets.UTF_8.toString()).replace("+", "%20");
            String encodedPath = objectName.substring(0, objectName.lastIndexOf('/') + 1) + encodedName;

            url = config.getUrl() + "/" + config.getBucketName() + "/" + encodedPath;
        } finally {
            if (stream != null) stream.close();
        }

        return url;
    }

    private String getFileExtension(String name) {
        int idx = name.lastIndexOf('.');
        return idx != -1 ? name.substring(idx + 1).toLowerCase() : "";
    }

    private String classifyByType(String ext) {
        switch (ext) {
            case "mp3": case "wav": case "aac": case "ogg": case "flac":
                return "audio";
            case "mp4": case "avi": case "mov": case "mkv": case "webm":
                return "video";
            case "png": case "jpg": case "jpeg": case "gif": case "bmp":
                return "image";
            default:
                return "others";
        }
    }

    public void deleteFileByUrl(String fileUrl) throws Exception {
        String decoded = URLDecoder.decode(fileUrl, StandardCharsets.UTF_8.toString());
        String bucket = config.getBucketName();
        String objectName = decoded.substring(decoded.indexOf(bucket) + bucket.length() + 1);

        MinioClient client = MinioClient.builder()
                .endpoint(config.getUrl())
                .credentials(config.getAccessKey(), config.getSecretKey())
                .build();

        RemoveObjectArgs args = RemoveObjectArgs.builder()
                .bucket(bucket)
                .object(objectName)
                .build();

        client.removeObject(args);
    }
}

三、配置文件与依赖管理

确保Spring Boot项目正确加载外部配置。

POM依赖声明

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

两种配置读取方式对比

  • 推荐方式:使用 @ConfigurationProperties + @Autowired 注入配置类,便于维护和类型检查。
  • 简化方式:对于少量字段,可直接用 @Value("${minio.url}") 获取值,但不建议在复杂场景中使用。
标签: AliyunOSSminio

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。