Files
chuanggao-website/design/MinIO配置说明.md
2026-05-12 16:53:18 +08:00

556 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 创高家具官网 - MinIO配置说明
## 一、MinIO基础信息
| 配置项 | 值 | 说明 |
|--------|-----|------|
| 服务地址 | `http://localhost:9000` | API端口 |
| 控制台 | `http://localhost:9001` | Web管理界面 |
| Bucket名称 | `chuanggao-images` | 统一图片存储桶 |
| 访问策略 | 公开读Public Read | 图片可直接访问 |
---
## 二、MinIO部署步骤
### 2.1 Docker部署推荐
```yaml
# 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 验证部署
```bash
# 查看容器状态
docker ps | grep minio
# 查看日志
docker logs minio-cg
# 访问控制台
open http://localhost:9001
# 用户名: chuanggao-admin
# 密码: YourStrongPassword123!
```
---
## 三、Bucket创建与配置
### 3.1 创建Bucket
**方式1Web控制台**
1. 访问 `http://localhost:9001`
2. 登录后点击 "Create Bucket"
3. 输入名称:`chuanggao-images`
4. 确认创建
**方式2mc命令行**
```bash
# 安装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 设置公开读权限
**方式1Web控制台**
1. 进入 Bucket → `chuanggao-images`
2. 点击 "Access Policy"
3. 选择 "Public"
4. 保存
**方式2mc命令行**
```bash
# 设置bucket为公开读
mc anonymous set download chuanggao/chuanggao-images
# 验证策略
mc anonymous get chuanggao/chuanggao-images
```
### 3.3 设置CORS跨域
```bash
# 创建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
```yaml
# 文件存储配置
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
```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
```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
```java
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
<!-- HTML -->
<img src="http://localhost:9000/chuanggao-images/uploads/products/2024/01/abc123.jpg" alt="产品图">
```
```vue
<!-- 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获取推荐
```javascript
// 从后端获取完整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反代配置生产环境
```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 生产环境配置
```yaml
# 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 环境变量管理
```bash
# .env 文件不提交到git
MINIO_ROOT_USER=chuanggao-admin
MINIO_ROOT_PASSWORD=YourSuperSecretPassword!!!
MINIO_ACCESS_KEY=chuanggao-app
MINIO_SECRET_KEY=AnotherSecretKey123
```
### 9.3 备份策略
```bash
# 定时备份脚本 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
---
## 十一、测试命令
```bash
# 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
```