Minio+Vue+Springboot:网页文件上传-对象文件存储实战

一、场景分析

技术架构

  1. 前端:Vue + Axios
  2. 后端:Spring Boot
  3. 存储:MinIO对象存储

实现步骤

1. Spring Boot配置

在application.yml中添加MinIO配置:

yaml 复制代码
minio:
  url: http://localhost:9000
  access-key: your-access-key
  secret-key: your-secret-key
  bucket-name: mybucket
  secure: false

2. 文件上传实现

前端核心代码:

html 复制代码
<template>
  <div>
    <input type="file" @change="handleFileUpload">
    <button @click="uploadFile">上传</button>
    <div v-if="progress > 0">进度:{{progress}}%</div>
  </div>
</template>

3. 后端处理逻辑

使用MinIO Java Client实现文件存储:

  • 自动创建存储桶
  • 支持多种文件类型
  • 返回可访问URL

最佳实践

  1. 文件命名使用UUID避免重复
  2. 限制上传文件大小
  3. 前端添加文件类型校验
  4. 后端配置CORS策略

二、前端代码(Vue实现)

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>文件上传</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
    <div id="app">
        <div>
            <input type="file" ref="fileInput" @change="onFileSelect">
            <button @click="upload">上传文件</button>
            <div v-if="uploadProgress > 0">
                上传进度:{{ uploadProgress }}%
            </div>
            <div v-if="fileUrl">
                文件地址:<a :href="fileUrl" target="_blank">{{ fileUrl }}</a>
            </div>
        </div>
    </div>

    <script>
        const { createApp } = Vue;
        
        createApp({
            data() {
                return {
                    selectedFile: null,
                    uploadProgress: 0,
                    fileUrl: ''
                };
            },
            methods: {
                onFileSelect(event) {
                    this.selectedFile = event.target.files[0];
                },
                async upload() {
                    if (!this.selectedFile) return;
                    
                    const formData = new FormData();
                    formData.append('file', this.selectedFile);
                    
                    try {
                        const response = await axios.post('/api/upload', formData, {
                            headers: { 'Content-Type': 'multipart/form-data' },
                            onUploadProgress: (progressEvent) => {
                                this.uploadProgress = Math.round(
                                    (progressEvent.loaded * 100) / progressEvent.total
                                );
                            }
                        });
                        
                        this.fileUrl = response.data.url;
                        alert('上传成功!');
                    } catch (error) {
                        console.error('上传失败:', error);
                        alert('上传失败!');
                    }
                }
            }
        }).mount('#app');
    </script>
</body>
</html>

三、Spring Boot配置

application.yml配置示例:

yaml 复制代码
server:
  port: 8080

spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 10MB

minio:
  url: http://localhost:9000
  access-key: minioadmin
  secret-key: minioadmin
  bucket-name: mybucket
  secure: false

四、后端Java代码

FileStorageController.java:

java 复制代码
@RestController
@RequestMapping("/api")
public class FileStorageController {

    @Autowired
    private FileStorageService fileStorageService;

    @PostMapping("/upload")
    public ResponseEntity<Map<String, String>> uploadFile(@RequestParam("file") MultipartFile file) {
        try {
            String url = fileStorageService.uploadFile(file);
            return ResponseEntity.ok().body(Collections.singletonMap("url", url));
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(Collections.singletonMap("error", e.getMessage()));
        }
    }
}

FileStorageService.java:

java 复制代码
@Service
public class FileStorageService {

    @Value("${minio.url}")
    private String minioUrl;
    
    @Value("${minio.access-key}")
    private String accessKey;
    
    @Value("${minio.secret-key}")
    private String secretKey;
    
    @Value("${minio.bucket-name}")
    private String bucketName;

    private MinioClient minioClient;

    @PostConstruct
    public void init() throws Exception {
        minioClient = MinioClient.builder()
                .endpoint(minioUrl)
                .credentials(accessKey, secretKey)
                .build();
                
        createBucketIfNotExists();
    }

    private void createBucketIfNotExists() throws Exception {
        boolean found = minioClient.bucketExists(BucketExistsArgs.builder()
                .bucket(bucketName)
                .build());
        if (!found) {
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        }
    }

    public String uploadFile(MultipartFile file) throws Exception {
        String fileName = UUID.randomUUID() + "-" + file.getOriginalFilename();
        
        minioClient.putObject(PutObjectArgs.builder()
                .bucket(bucketName)
                .object(fileName)
                .stream(file.getInputStream(), file.getSize(), -1)
                .contentType(file.getContentType())
                .build());
                
        return minioUrl + "/" + bucketName + "/" + fileName;
    }
}

五、系统要求

  1. 添加MinIO Java客户端依赖(pom.xml):
xml 复制代码
<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.5.6</version>
</dependency>
  1. 启动MinIO服务:
bash 复制代码
minio server /data --console-address ":9001"

该方案实现了:

  1. 前端文件上传进度显示
  2. 自动生成唯一文件名
  3. 支持多种文件类型
  4. 返回可直接访问的URL
  5. 完善的异常处理机制

建议根据实际需求添加:

  1. 文件类型白名单校验
  2. 文件大小限制
  3. 用户身份验证
  4. 文件管理功能(删除、列表等)
相关推荐
葫芦和十三1 小时前
图解 MongoDB 30|查询路由与广播查询:带片键定向,不带就广播
后端·mongodb·agent
葫芦和十三1 小时前
图解 MongoDB 29|分片键陷阱:jumbo chunk、孤儿文档和写入热点
后端·mongodb·agent
进击的程序猿~2 小时前
Go Zero源码阅读1
后端·golang
2601_961593423 小时前
Rust 开发环境配置繁琐?RustRover 开箱即用搞定编码调试
开发语言·后端·macos·rust
犀利豆5 小时前
AI in Harness(三)
人工智能·后端
IT_陈寒6 小时前
Java的Stream.parallel()把我CPU跑爆了,这种优化要谨慎
前端·人工智能·后端
子兮曰6 小时前
Bun 重写为 Rust:11天、64个Claude、16.5万美元,一次改变行业认知的激进实验
前端·后端·bun
子兮曰6 小时前
Node.js v26.5.0 深度解读:import Text、流式 ReadableStreamTee、以及隐藏的安全加固浪潮
前端·后端·node.js
子兮曰6 小时前
TypeScript 7.0 RC 深度解读:Go 重写完成,10 倍提速,编译器的「奇点时刻」
前端·后端·typescript
kosan7 小时前
.NET/C# 实战:我如何把登录验证码从“裸奔”改到“铜墙铁壁”
后端