556 lines
14 KiB
Markdown
556 lines
14 KiB
Markdown
# 创高家具官网 - 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
|
||
|
||
**方式1:Web控制台**
|
||
1. 访问 `http://localhost:9001`
|
||
2. 登录后点击 "Create Bucket"
|
||
3. 输入名称:`chuanggao-images`
|
||
4. 确认创建
|
||
|
||
**方式2:mc命令行**
|
||
|
||
```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 设置公开读权限
|
||
|
||
**方式1:Web控制台**
|
||
1. 进入 Bucket → `chuanggao-images`
|
||
2. 点击 "Access Policy"
|
||
3. 选择 "Public"
|
||
4. 保存
|
||
|
||
**方式2:mc命令行**
|
||
|
||
```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
|
||
```
|