14 KiB
14 KiB
创高家具官网 - MinIO配置说明
一、MinIO基础信息
| 配置项 | 值 | 说明 |
|---|---|---|
| 服务地址 | http://localhost:9000 |
API端口 |
| 控制台 | http://localhost:9001 |
Web管理界面 |
| Bucket名称 | chuanggao-images |
统一图片存储桶 |
| 访问策略 | 公开读(Public Read) | 图片可直接访问 |
二、MinIO部署步骤
2.1 Docker部署(推荐)
# docker-compose.yml
version: '3.8'
services:
minio:
image: minio/minio:latest
container_name: minio-cg
ports:
- "9000:9000" # API端口
- "9001:9001" # 控制台端口
environment:
MINIO_ROOT_USER: chuanggao-admin
MINIO_ROOT_PASSWORD: YourStrongPassword123!
MINIO_SERVER_URL: http://localhost:9000
MINIO_BROWSER_REDIRECT_URL: http://localhost:9001
volumes:
- ./minio-data:/data
command: server /data --console-address ":9001"
restart: unless-stopped
# 创建并启动
# docker-compose up -d
2.2 验证部署
# 查看容器状态
docker ps | grep minio
# 查看日志
docker logs minio-cg
# 访问控制台
open http://localhost:9001
# 用户名: chuanggao-admin
# 密码: YourStrongPassword123!
三、Bucket创建与配置
3.1 创建Bucket
方式1:Web控制台
- 访问
http://localhost:9001 - 登录后点击 "Create Bucket"
- 输入名称:
chuanggao-images - 确认创建
方式2:mc命令行
# 安装mc客户端
wget https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
sudo mv mc /usr/local/bin/
# 配置MinIO连接
mc alias set chuanggao http://localhost:9000 chuanggao-admin YourStrongPassword123!
# 创建bucket
mc mb chuanggao/chuanggao-images
# 验证
mc ls chuanggao
3.2 设置公开读权限
方式1:Web控制台
- 进入 Bucket →
chuanggao-images - 点击 "Access Policy"
- 选择 "Public"
- 保存
方式2:mc命令行
# 设置bucket为公开读
mc anonymous set download chuanggao/chuanggao-images
# 验证策略
mc anonymous get chuanggao/chuanggao-images
3.3 设置CORS(跨域)
# 创建cors配置文件
cat > cors-config.json << 'EOF'
{
"CORSRules": [
{
"AllowedOrigins": ["*"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]
}
EOF
# 应用CORS配置
mc admin bucket cors set chuanggao/chuanggao-images cors-config.json
四、Spring Boot集成配置
4.1 application.yml
# 文件存储配置
upload:
storage: minio # local 或 minio
path: ./uploads # local模式使用
# MinIO配置
minio:
endpoint: http://localhost:9000
access-key: ${MINIO_ACCESS_KEY:chuanggao-admin}
secret-key: ${MINIO_SECRET_KEY:YourStrongPassword123!}
bucket: chuanggao-images
# 公开访问URL(用于生成图片直链)
public-url: http://localhost:9000/chuanggao-images
4.2 MinioConfig.java
package com.chuanggao.config;
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {
private String endpoint;
private String accessKey;
private String secretKey;
private String bucket;
private String publicUrl;
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}
4.3 MinioService.java
package com.chuanggao.service;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.errors.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
@Slf4j
@Service
@RequiredArgsConstructor
public class MinioService {
private final MinioClient minioClient;
private final MinioConfig minioConfig;
/**
* 上传文件
*/
public String uploadFile(MultipartFile file, String directory)
throws IOException, InvalidKeyException, NoSuchAlgorithmException,
InsufficientDataException, InternalException, InvalidResponseException,
NoSuchBucketException, XmlParserException, ErrorResponseException,
ServerException {
// 生成唯一文件名
String originalFilename = file.getOriginalFilename();
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
String newFilename = UUID.randomUUID().toString().replace("-", "") + extension;
// 构建存储路径: uploads/目录/文件名
String objectKey = "uploads/" + directory + "/" + newFilename;
// 上传
minioClient.putObject(
PutObjectArgs.builder()
.bucket(minioConfig.getBucket())
.object(objectKey)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build()
);
// 返回访问路径
return "/" + objectKey;
}
/**
* 删除文件
*/
public void deleteFile(String objectKey)
throws IOException, InvalidKeyException, NoSuchAlgorithmException,
InsufficientDataException, InternalException, InvalidResponseException,
NoSuchBucketException, XmlParserException, ErrorResponseException,
ServerException {
// 移除开头的 /
if (objectKey.startsWith("/")) {
objectKey = objectKey.substring(1);
}
minioClient.removeObject(
RemoveObjectArgs.builder()
.bucket(minioConfig.getBucket())
.object(objectKey)
.build()
);
}
/**
* 获取完整访问URL
*/
public String getFullUrl(String objectKey) {
if (objectKey == null || objectKey.isEmpty()) {
return null;
}
// 移除开头的 /
if (objectKey.startsWith("/")) {
objectKey = objectKey.substring(1);
}
return minioConfig.getPublicUrl() + "/" + objectKey;
}
}
五、上传Controller
package com.chuanggao.controller.admin;
import com.chuanggao.service.MinioService;
import com.chuanggao.service.MediaLibraryService;
import com.chuanggao.common.Result;
import com.chuanggao.vo.MediaVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/api/admin/upload")
@RequiredArgsConstructor
@SaCheckLogin
public class AdminUploadController {
private final MinioService minioService;
private final MediaLibraryService mediaLibraryService;
/**
* 单文件上传
*/
@PostMapping("/single")
public Result<MediaVO> uploadSingle(
@RequestParam("file") MultipartFile file,
@RequestParam(defaultValue = "other") String usedIn) {
try {
// 1. 上传到MinIO
String directory = getDirectoryByUsedIn(usedIn);
String filePath = minioService.uploadFile(file, directory);
// 2. 获取完整URL
String fullUrl = minioService.getFullUrl(filePath);
// 3. 保存到媒体库
MediaVO media = mediaLibraryService.saveMedia(file, filePath, fullUrl, usedIn);
log.info("文件上传成功: {}, 路径: {}", file.getOriginalFilename(), filePath);
return Result.success(media);
} catch (Exception e) {
log.error("文件上传失败", e);
return Result.error("上传失败: " + e.getMessage());
}
}
/**
* 多文件上传
*/
@PostMapping("/multiple")
public Result<List<MediaVO>> uploadMultiple(
@RequestParam("files") MultipartFile[] files,
@RequestParam(defaultValue = "other") String usedIn) {
// 批量上传逻辑...
return Result.success(mediaList);
}
/**
* 根据用途确定存储目录
*/
private String getDirectoryByUsedIn(String usedIn) {
return switch (usedIn) {
case "carousel" -> "carousel";
case "product" -> "products/" + DateUtil.format(new Date(), "yyyy/MM");
case "case" -> "cases/" + DateUtil.format(new Date(), "yyyy/MM");
case "news" -> "news/" + DateUtil.format(new Date(), "yyyy/MM");
case "page" -> "pages";
case "company" -> "company";
case "honor" -> "honor";
default -> "others";
};
}
}
六、存储目录结构
chuanggao-images (bucket)
├── uploads/
│ ├── carousel/ # 轮播图
│ │ ├── home-banner-01.jpg
│ │ └── about-banner-01.jpg
│ │
│ ├── products/ # 产品图片
│ │ ├── 2024/
│ │ │ ├── 01/
│ │ │ │ ├── abc123.jpg
│ │ │ │ └── def456.jpg
│ │ │ └── 02/
│ │ └── thumbs/ # 缩略图(自动生成)
│ │
│ ├── cases/ # 案例图片
│ │ └── 2024/
│ │ └── 01/
│ │
│ ├── news/ # 新闻封面
│ │ └── 2024/
│ │ └── 01/
│ │
│ ├── pages/ # 页面区块图片
│ │ ├── home/
│ │ └── about/
│ │
│ ├── company/ # 公司相关
│ │ ├── logo.png
│ │ └── wechat-qr.jpg
│ │
│ ├── honor/ # 荣誉证书
│ └── others/ # 其他
│
└── temp/ # 临时文件(定期清理)
七、前端访问方式
7.1 直接访问(公开读)
由于bucket设置为public read,图片可直接访问:
<!-- HTML -->
<img src="http://localhost:9000/chuanggao-images/uploads/products/2024/01/abc123.jpg" alt="产品图">
<!-- Vue -->
<template>
<img :src="imageUrl" alt="产品图">
</template>
<script setup>
const imageUrl = 'http://localhost:9000/chuanggao-images/uploads/products/2024/01/abc123.jpg'
</script>
7.2 通过API获取(推荐)
// 从后端获取完整URL
const getImageUrl = async (mediaId) => {
const res = await fetch(`/api/admin/media/url/${mediaId}`)
const data = await res.json()
return data.data.url // 返回MinIO直链
}
八、Nginx反代配置(生产环境)
server {
listen 80;
server_name www.chuanggao.com;
# 前端
location / {
root /var/www/chuanggao-website/client/dist;
try_files $uri $uri/ /index.html;
}
# 后端API
location /api {
proxy_pass http://localhost:8080/api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# MinIO访问(可选,如需统一域名)
location /images {
proxy_pass http://localhost:9000/chuanggao-images;
proxy_set_header Host $host;
expires 1y;
add_header Cache-Control "public, immutable";
}
}
九、安全建议
9.1 生产环境配置
# docker-compose.prod.yml
version: '3.8'
services:
minio:
image: minio/minio:latest
container_name: minio-cg
ports:
- "127.0.0.1:9000:9000" # 仅本地访问,不暴露公网
- "127.0.0.1:9001:9001"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD} # 从环境变量读取
volumes:
- /data/minio:/data
command: server /data --console-address ":9001"
restart: always
9.2 环境变量管理
# .env 文件(不提交到git)
MINIO_ROOT_USER=chuanggao-admin
MINIO_ROOT_PASSWORD=YourSuperSecretPassword!!!
MINIO_ACCESS_KEY=chuanggao-app
MINIO_SECRET_KEY=AnotherSecretKey123
9.3 备份策略
# 定时备份脚本 backup-minio.sh
#!/bin/bash
DATE=$(date +%Y%m%d)
mc mirror chuanggao/chuanggao-images /backup/minio/chuanggao-images-$DATE
find /backup/minio -name "chuanggao-images-*" -mtime +30 -delete
十、常见问题
Q1: 上传失败,提示连接拒绝
检查MinIO服务是否启动,端口9000是否被占用
Q2: 图片无法访问,返回403
检查bucket权限是否为public,或CORS配置是否正确
Q3: 中文文件名乱码
上传前对文件名进行URL编码,或改用UUID重命名
Q4: 大文件上传超时
调整spring.servlet.multipart.max-file-size和max-request-size
十一、测试命令
# 1. 测试MinIO连接
mc alias set local http://localhost:9000 chuanggao-admin YourStrongPassword123!
# 2. 上传测试文件
mc cp test.jpg local/chuanggao-images/uploads/test/
# 3. 查看文件
mc ls local/chuanggao-images/uploads/test/
# 4. 获取公开访问URL
echo "http://localhost:9000/chuanggao-images/uploads/test/test.jpg"
# 5. 浏览器访问测试
curl -I http://localhost:9000/chuanggao-images/uploads/test/test.jpg
# 应返回 200 OK