Note 18(下)分布式存储架构:MinIO 本地部署与SpringBoot接入实战

18.4. 分布式存储架构:MinIO 本地部署与全栈实战

本节目标:我们将跨越“能用”到“生产级高可用”的鸿沟。在代码层,我们将深入 Java SDK 的 HTTP 连接池(OkHttp)进行调优,并掌握 预签名策略 技术,构建一个无需应用服务器中转、直连存储桶的高性能架构。

18.4.1. 基础设施:去中心化存储的底层奥义

在上一节,我们将文件存在了本地磁盘。这在单体架构下尚可,但在微服务或集群架构下,本地磁盘就是“数据孤岛”。MinIO 是目前全球增长最快的对象存储服务器,它完全兼容 AWS S3 协议,且性能极其强悍。

在开始部署前,必须理解 MinIO 与传统存储(如 NFS、RAID)的本质区别。

  1. 纠删码:RAID 的终结者

在传统的企业存储中,我们习惯使用硬件 RAID 卡(如 RAID 5 或 RAID 6)来防止硬盘损坏。但在大容量磁盘(10TB+)时代,RAID 重建数据极其缓慢,且重建过程中极易引发第二块盘损坏,导致数据彻底丢失。

MinIO 摒弃了硬件 RAID,采用软件定义的 纠删码 技术。

  • 原理:MinIO 将一个对象切分成 N​ 个数据块和 M 个奇偶校验块。
  • 优势:只要 N​ 个块存在,数据就能恢复。例如配置了 4 块盘的 MinIO 集群(2 数据+2 校验),你可以任意拔掉 2 块硬盘,数据依然可读可写,且无需漫长的 RAID 重建过程。
  • JBOD 模式:MinIO 官方极度推荐 JBOD (Just a Bunch Of Disks) 模式,即让操作系统直接识别每一块裸盘,不要做任何 RAID。
  1. 位衰减 (Bit Rot) 防御

硬盘是会撒谎的。由于磁性介质的物理衰减,磁盘上的 0 可能会莫名其妙变成 1,这种现象称为“位衰减”或“静默数据损坏”。传统的 OS 文件系统无法发现这种错误。

MinIO 使用 HighwayHash 算法计算数据校验和。当你读取一张图片时,MinIO 会在 CPU 寄存器级别高速计算哈希值,一旦发现磁盘数据与哈希不符,它会利用纠删码自动从其他盘修复数据,而应用层对此完全无感。

功。


18.4.2. 生产级部署实战:Windows 原生服务化

很多初学者在 Windows 上测试 MinIO 时,习惯直接双击 minio.exe 或者在 CMD 里跑一行命令。这样做有一个致命缺点:那个黑色的 CMD 窗口绝对不能关,一关服务就挂了,而且电脑重启后它不会自己启动。

为了像专业的服务器一样在后台静默运行,我们将使用 NSSM (Non-Sucking Service Manager) 把它注册为 Windows 服务。

关于 NSSM 的安装教程可以参考:Windows-NSSM 通俗易懂介绍,安装与深度实战 | Prorise - 博客小栈

1. 目录规划与环境准备 (小白必看)

为了方便管理,我们不要把文件乱放。这里推荐参考我的目录结构(基于 E 盘),你可以根据自己的实际情况修改盘符,但 强烈建议保持文件夹结构一致,这样下面的命令你可以直接复制使用。

  • MinIO 程序目录: E:\minio (存放 minio.exe 和脚本)
  • MinIO 数据目录: E:\minio-data (存放上传的图片/文件)
  • NSSM 工具目录: E:\NSSM (存放 nssm.exe)

动手操作:

  1. 下载 MinIO: 去官网下载 Windows 版 minio.exe,放入 E:\minio 文件夹。
  2. 下载 NSSM: 下载 NSSM 2.24+ 版本,解压后将其中的 win64/nssm.exe 放入 E:\NSSM 文件夹。
  3. 新建额外目录: 在 E:\minio 里手动右键新建两个文件夹,分别命名为 logs (存日志) 和 config (存配置),避免文件散乱。

2. 编写启动脚本 (run.bat)

我们需要一个脚本来帮我们设置账号密码并启动 MinIO。这样做的好处是 不需要配置复杂的系统环境变量,且以后修改密码只需改这个文件。

E:\minio 文件夹下,新建一个文本文件,重命名为 run.bat (注意后缀名要是 .bat),右键选择“编辑”,填入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@echo off
setlocal

rem 1. 切换到脚本所在目录 (E:\minio)
cd /d %~dp0

rem 2. 设置管理员账号和密码
rem 注意:如果是生产环境,建议修改密码
set MINIO_ROOT_USER=admin
set MINIO_ROOT_PASSWORD=admin123

rem 3. 检查并创建数据存储目录
if not exist "E:\minio-data" (
mkdir "E:\minio-data"
)

rem 4. 启动 MinIO Server
minio.exe server E:\minio-data --address ":9000" --console-address ":9001"

endlocal

注意:如果你的文件不在 E 盘,请务必修改脚本中的路径资源

3. 使用 NSSM 注册服务 (核心步骤)

这一步我们将把上面的 run.bat 变成 Windows 服务。

请按下 Win + S 搜索 “PowerShell”,右键选择“以管理员身份运行”(必须是管理员,否则无法安装服务)。

3.1 进入 NSSM 目录
在 PowerShell 中输入以下命令并回车,进入我们下载好的目录:

1
cd E:\NSSM

3.2 安装并配置服务
依次执行(复制/粘贴)以下命令。每行执行完应该不会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1. 安装服务:告诉 NSSM 我们的启动脚本在哪里
.\nssm.exe install MinioService "E:\minio\run.bat"

# 2. 添加描述:以后在服务列表里能一眼认出它是干嘛的
.\nssm.exe set MinioService Description "MinIO Object Storage - Image Server"

# 3. 配置日志路径 (非常重要!)
# 如果服务启动失败,我们需要去 logs 文件夹看日志才能知道原因
.\nssm.exe set MinioService AppStdout "E:\minio\logs\service.log"
.\nssm.exe set MinioService AppStderr "E:\minio\logs\error.log"

# 4. 配置日志自动切割 (防止日志文件无限变大把磁盘撑爆)
# 策略:文件达到 10MB 就切割,最多保留 10 个备份文件
.\nssm.exe set MinioService AppRotateFiles 1
.\nssm.exe set MinioService AppRotateOnline 1
.\nssm.exe set MinioService AppRotateBytes 10485760

# 5. 启动服务!
.\nssm.exe start MinioService

4. 验证与防火墙设置

验证是否成功:

  1. 打开任务管理器 -> “服务”选项卡,找一下有没有 MinioService,状态应该是 “正在运行”
  2. 打开浏览器访问 http://localhost: 9001,应该能看到 MinIO 的登录界面

image-20251222175826844

防火墙小贴士(如果要在局域网访问):
如果你希望同一局域网下的同事也能访问你的 MinIO,你需要去“Windows Defender 防火墙” -> “高级设置” -> “入站规则”中,新建规则放行 90009001 端口。

5. 初始化配置 (Console 操作)

服务跑起来后,最后做一次初始化:

  1. 访问 http://localhost:9001,使用脚本里设置的 admin / admin123 登录。
  2. 点击左侧 Buckets -> Create Bucket,输入名字 mall-files
  3. 修改访问权限 (关键)
  • 点击刚创建的 Bucket。
  • 找到 Access Policy (通常默认是 Private)。
  • 为了开发方便,将其改为 Public (或添加一条规则设为 readonly)。
  • 这一步不做的话,前端代码是无法直接通过 URL (http://ip:9000/mall-files/xxx.jpg) 显示图片的!

image-20251222201128392


18.4.3. 客户端架构:SDK 的“手术级”调优与配置

MinIO 官方提供的 Java SDK 底层是基于 OkHttp 实现的。如果你偷懒,只是简单地 new MinioClient(),它会使用默认的 HTTP 设置。这在本地跑跑 demo 没问题,但一旦上了生产环境,你就会遇到两个大坑:

  1. 连接池爆炸:高并发下,因为没有复用 TCP 连接,会导致服务器端口被耗尽。
  2. 大文件上传失败:默认的超时时间很短,稍微传个 100MB 的视频可能就因为网络波动超时报错了。

所以,我们需要对底层的 OkHttpClient 进行 深度定制

1. 引入依赖 (pom.xml)

我们需要引入 MinIO 的 SDK。注意,这里建议显式引入 okhttp,防止其他依赖(如 Spring Cloud)引入了旧版本的 OkHttp 导致冲突。

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.7</version> </dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>

2. 编写配置文件 (application.yml)

我们需要将 MinIO 的连接信息抽离到配置文件中,而不是硬编码在 Java 类里。

1
2
3
4
5
6
7
8
9
10
11
# MinIO 配置
minio:
# MinIO 服务的 API 地址 (注意:是 API 端口 9000,不是控制台端口 9001)
# 如果是本地,就是 http://127.0.0.1:9000
endpoint: http://127.0.0.1:9000
# 之前在 run.bat 里设置的账号
access-key: admin
# 之前在 run.bat 里设置的密码
secret-key: admin123
# 默认上传到的桶名称
bucket-name: mall-files

3. 编写配置类:MinioConfig.java (核心)

这个类的作用是将 MinioClient 注入到 Spring 容器中。
重点在于下面的 customHttpClient 配置,请务必仔细阅读注释,理解为什么要设置这三个超时时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.example.demo.config;

import io.minio.MinioClient;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
public class MinioConfig {

@Value("${minio.endpoint}")
private String endpoint;

@Value("${minio.access-key}")
private String accessKey;

@Value("${minio.secret-key}")
private String secretKey;

@Bean
public MinioClient minioClient() {
// 1. 深度定制 OkHttpClient (这是核心调优部分)
OkHttpClient customHttpClient = new OkHttpClient.Builder()
// [连接池优化]
// 10: 最大空闲连接数。保持 10 个 TCP 连接不断开,随取随用,避免每次上传都三次握手。
// 5, MINUTES: 空闲连接存活 5 分钟。
.connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES))

// [超时时间分层配置]
// 1. 连接超时 (Connect Timeout): TCP 握手时间。
// 设为 10 秒足够了,如果 10 秒连不上,说明网络不通,没必要干等。
.connectTimeout(10, TimeUnit.SECONDS)

// 2. 写入超时 (Write Timeout): 极其重要!
// 这是发送数据(上传文件)的超时时间。
// 默认值很短,如果你传一个 500MB 的文件,还没传完就报错了。
// 这里设为 60 秒(甚至可以更长),给大文件上传留足时间。
.writeTimeout(60, TimeUnit.SECONDS)

// 3. 读取超时 (Read Timeout): 获取服务器响应的时间。
// 下载文件或获取文件元数据时生效。
.readTimeout(30, TimeUnit.SECONDS)

// 失败重试:遇到网络微弱抖动(如闪断)时自动重试,增加鲁棒性
.retryOnConnectionFailure(true)
.build();

// 2. 构建 MinioClient
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.httpClient(customHttpClient) // 把我们要来的 "定制客户端" 塞进去
.build();
}
}


18.4.4. 极速落地:后端 Controller + 前端 Vue3 联调

我们现在的目标是:前端选一张图 -> 传给后台 -> 后台存入 MinIO -> 返回一个临时链接 -> 前端展示出来

1. 编写后端接口 (MinioController.java)

在企业级开发中,我们通常会把这些逻辑下沉到 Service 层(如上一节演示的那样)。但为了让你在这个 Demo 中一眼看清全流程,我们这里特意简化架构,直接在 Controller 里“一把梭”。

请直接在 controller 包下新建 MinioController.java。注意看代码里的 @CrossOrigin,这是为了允许你的 Vue 项目(端口 5173)访问后台(端口 8080), 如果在上一节配置了全局跨域处理器则不需要设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.example.demo.controller;

import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.http.Method;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/minio")
@CrossOrigin(origins = "*") // 允许 Vue 前端跨域访问
public class MinioController {

@Autowired
private MinioClient minioClient;

@Value("${minio.bucket-name}")
private String bucketName;

/**
* 上传接口:接收文件 -> 存 MinIO -> 返回预览链接
*/
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) {
try {
// 1. 改名:避免文件名冲突 (UUID + 原后缀)
String original = file.getOriginalFilename();
String suffix = original.substring(original.lastIndexOf("."));
String fileName = UUID.randomUUID() + suffix;

// 2. 上传:必须设置 Content-Type,否则浏览器无法直接预览
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build()
);

// 3. 签名:生成一个 2 小时有效期的临时预览链接
// 这样前端拿到这个 URL 就能直接放在 <img src="..."> 里显示了
String url = minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.bucket(bucketName)
.object(fileName)
.method(Method.GET)
.expiry(2, TimeUnit.HOURS)
.build()
);

System.out.println("上传成功,链接:" + url);
return url; // 直接把链接扔给前端

} catch (Exception e) {
e.printStackTrace();
return "上传失败: " + e.getMessage();
}
}
}


2. 编写前端页面 (App.vue)

打开我们之前在 18 上章之前创建的 Vue 3 项目,找到 src/App.vue,把里面的内容全部删掉,替换成下面的代码。

这里我们利用 Axios 发送文件,并把后端返回的 URL 直接显示在图片框里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<template>
<div class="upload-container">
<h2>MinIO 图片上传测试</h2>

<div class="input-group">
<input type="file" @change="handleFileChange" accept="image/*" />
<button @click="uploadFile" :disabled="!selectedFile">点击上传</button>
</div>

<p v-if="uploadStatus">{{ uploadStatus }}</p>

<div v-if="previewUrl" class="preview-box">
<h3>上传成功!预览图:</h3>
<img :src="previewUrl" alt="预览图" />
</div>
</div>
</template>

<script setup>
import { ref } from 'vue';
import axios from 'axios';

// 定义响应式变量
const selectedFile = ref(null);
const uploadStatus = ref('');
const previewUrl = ref('');

// 1. 监听文件选择
const handleFileChange = (event) => {
selectedFile.value = event.target.files[0];
};

// 2. 执行上传
const uploadFile = async () => {
if (!selectedFile.value) return;

uploadStatus.value = '正在上传...';

// 构建 FormData 对象 (上传文件的标准方式)
const formData = new FormData();
formData.append('file', selectedFile.value);

try {
// 发送请求给后端 (注意端口号要和你的 SpringBoot 端口一致)
const response = await axios.post('http://localhost:8080/minio/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});

// 后端直接返回了 URL 字符串
previewUrl.value = response.data;
uploadStatus.value = '上传成功!链接有效期 2 小时。';

} catch (error) {
console.error(error);
uploadStatus.value = '上传失败,请检查控制台';
}
};
</script>

<style scoped>
.upload-container {
max-width: 600px;
margin: 50px auto;
text-align: center;
font-family: Arial, sans-serif;
}
.preview-box img {
max-width: 100%;
border: 2px solid #ddd;
border-radius: 8px;
margin-top: 20px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
button {
margin-left: 10px;
padding: 5px 15px;
cursor: pointer;
}
</style>

image-20251222203524516

如果你能看到图片,恭喜你!你已经打通了从「Vue 前端 -> Spring Boot 后端 -> MinIO 文件服务器」的全链路。

这一节的内容非常核心,涉及架构安全和生产部署。原笔记中提到的“直传”和“Nginx”如果只给配置文件,初学者(尤其是 Windows 用户)很难落地。

我不仅保留了你的核心逻辑,还为你补充了 Vue3 的直传代码实现Windows 下 Nginx 的完整配置与 Hosts 修改教程


18.4.5. 架构进阶:直传架构与 Policy 安全策略

在上一节的“服务端中转上传”模式中,流量走向是:Browser (前端) -> Tomcat (你的后端) -> MinIO。这就好比你要给朋友寄快递,非要先寄给自己,再由你寄给朋友。

  • 弊端:Tomcat 是流量瓶颈。如果 1000 个人同时上传视频,你的 Java 后端瞬间就被堵死了,连正常的 API 请求都处理不了。

直传架构 (Direct Upload) 允许浏览器拿到“门票”后,直接把文件扔进 MinIO:Browser -> MinIO。你的后端只负责“发门票”,不负责“搬运货物”。

1. 为什么 Presigned PUT 很危险?

我们在上面的教程中使用了 getPresignedObjectUrl(PUT)。但其实这是一个 巨大的安全隐患

  • 漏洞场景:后端生成了一个 avatar.jpg 的上传链接给用户。
  • 攻击方式:黑客拦截这个链接,但他不传图片,而是上传了一个 5TB 的垃圾文件。因为 PUT 方式的签名 无法限制文件大小!你的硬盘瞬间就会被填满。

2. 安全方案:Presigned POST Policy

我们要使用 POST 方式,并配合 Policy (策略)。这相当于给门票加了极其严格的限制条件(必须是图片、必须小于 10MB、名字必须叫 xxx)。

后端实现:在 MinioController 中增加获取“门票”的接口

请在 MinioController.java 中添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* 获取直传“门票” (Policy)
* 前端拿到这个参数后,不再请求后端,而是直接向 MinIO 发起 POST 请求
*/
@GetMapping("/policy")
public Map<String, String> getPolicy(@RequestParam("fileName") String fileName) {
try {
// 1. 也是为了安全,我们帮前端生成一个随机文件名
String suffix = fileName.substring(fileName.lastIndexOf("."));
String finalFileName = UUID.randomUUID() + suffix;

// 2. 构建策略对象 (10 分钟内有效)
PostPolicy policy = new PostPolicy(bucketName, LocalDateTime.now().plusMinutes(10));

// 3. 添加安全限制 (核心部分!)
// 限制一:文件名必须是我们生成的这个 (防止前端乱改文件名覆盖他人文件)
policy.addEqualsCondition("key", finalFileName);

// 限制二:文件类型必须以 image/ 开头 (防止上传 .exe 病毒)
policy.addStartsWithCondition("Content-Type", "image/");

// 限制三:文件大小必须在 1KB 到 10MB 之间 (防止 DDoS 存储攻击)
policy.addContentLengthRangeCondition(1024, 10 * 1024 * 1024);

// 4. 生成签名数据
// 这里返回的是一个 Map,里面包含了 accessKey, policy, signature 等所有 MinIO 需要的验证字段
Map<String, String> formData = minioClient.getPresignedPostFormData(policy);

// 我们需要手动把 finalFileName 也塞进去,前端需要用
formData.put("key", finalFileName);
// 告诉前端应该往哪里传 (MinIO 的地址 + Bucket 名)
formData.put("host", "http://localhost:9000/" + bucketName);

return formData;

} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("生成直传策略失败");
}
}

3. 前端落地:Vue3 实现直传

这是原笔记中缺失的部分。前端拿到 Map 后,怎么把它变成一个请求发出去?
原理:我们需要构建一个 FormData,把后端给的所有字段(Key, Policy, Signature…)都塞进去,最后再塞文件。

修改你的 App.vue 中的 uploadFile 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 新的直传逻辑
const uploadFile = async () => {
if (!selectedFile.value) return;
uploadStatus.value = '正在申请上传门票...';

try {
// 1. 第一步:找后端要“门票” (Policy)
const policyRes = await axios.get('http://localhost:8080/minio/policy', {
params: { fileName: selectedFile.value.name }
});

const policyData = policyRes.data;
const minioHost = policyData.host; // 上传的目标地址

// 2. 第二步:构建直传表单
const formData = new FormData();
// ⚠️ 极其重要:必须把后端返回的字段,一个不差地塞进 FormData
// key, policy, x-amz-algorithm, x-amz-signature...
for (const key in policyData) {
if (key !== 'host') { // host 字段是用来发请求的 URL,不用塞进表单
formData.append(key, policyData[key]);
}
}
// ⚠️ 文件必须是最后一个 append 的字段!否则 MinIO 会报错!
formData.append('Content-Type', selectedFile.value.type);
formData.append('file', selectedFile.value);

uploadStatus.value = '正在直传 MinIO...';

// 3. 第三步:直接发给 MinIO (不经过后端)
await axios.post(minioHost, formData);

// 4. 拼接预览图地址 (因为是直传,后端没返回 URL,我们需要自己拼)
// 格式:http://localhost: 9000/bucket-name/filename
previewUrl.value = `${minioHost}/${policyData.key}`;
uploadStatus.value = '直传成功!后端服务器毫无压力。';

} catch (error) {
console.error(error);
uploadStatus.value = '上传失败,请看控制台';
}
};


18.4.6. 生产级运维:Nginx 反向代理与域名映射

在生产环境,我们不能让用户直接访问 9000 端口。原因:

  1. 丑陋http://192.168.1.5:9000 看起来不专业。
  2. 跨域:前后端分离时,CORS 问题很烦人。
  3. 安全:我们需要隐藏真实的 MinIO 端口。

我们将使用 Nginx 把它包装成 http://oss.mycompany.com

1. Windows 安装 Nginx (小白指引)

  1. 下载 Nginx Windows 版(推荐 1.24+ 稳定版)。
  2. 解压到 E:\nginx (不要放在桌面或有中文的路径)。
  3. 进入 E:\nginx\conf 文件夹,用记事本打开 nginx.conf

2. 配置 nginx.conf

请清空原文件 server {...} 部分,替换为以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
server {
# 监听 80 端口,这样访问时就不用输端口号了
listen 80;
# 你的自定义域名 (稍后我们需要去修改 hosts 文件骗过电脑)
server_name oss.mycompany.com;

# 【重要】允许大文件上传 (Nginx 默认只允许 1M,传视频必报错)
client_max_body_size 1000M;

# 开启分块传输支持
chunked_transfer_encoding on;

location / {
# 把流量转发给本机的 MinIO
proxy_pass http://localhost:9000;

# 【极其重要】MinIO 的签名校验强依赖 Host 头
# 如果不加这一行,MinIO 会以为你是通过 localhost 访问的,导致签名算错
# 报错信息通常是:SignatureDoesNotMatch
proxy_set_header Host $http_host;

# 传递真实 IP,方便以后查日志
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# WebSocket 支持 (MinIO 的管理后台有些功能需要 WS)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

3. 修改 Hosts 文件 (关键步骤)

因为 oss.mycompany.com 是个假域名,我们需要告诉你的电脑:“访问这个域名时,直接找本机”。

  1. 进入路径:C:\Windows\System32\drivers\etc
  2. 找到 hosts 文件,复制一份到桌面(防止没权限修改)。
  3. 用记事本打开桌面的 hosts 文件,在最后加一行:
1
2
127.0.0.1 oss.mycompany.com

  1. 保存,然后把桌面的文件拖回去覆盖原文件。

4. 启动与验证

  1. 双击 E:\nginx\nginx.exe(你会看到一个黑框一闪而过,这是正常的)。
  2. 打开浏览器,访问 http://oss.mycompany.com
  3. 如果能看到 MinIO 的登录界面,说明配置成功!

18.4.7. 总结

通过本章的深度实战,你已经超越了 90% 只会写 Hello World 的开发者,掌握了一套 生产级 的对象存储架构:

  1. 部署层:不再用脆弱的 CMD 启动,而是用 NSSM 实现了 Windows 原生服务化与开机自启。
  2. SDK 层:通过 OkHttpClient 连接池调优,解决了高并发下连接数耗尽的隐患。
  3. 架构层:实现了 Presigned Post Policy 直传,让前端直接把 1GB 的视频传给 MinIO,彻底释放了后端服务器的内存和带宽。
  4. 运维层:通过 Nginx 反向代理,解决了复杂的跨域问题,并规范了访问入口。