# 创高家具官网 - 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 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> 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 产品图 ``` ```vue ``` ### 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 ```