目的:前端直接上传文件到Minio,不通过服务器中转文件。密钥不能在前端明文传输。
一、架构设计
```mermaid
sequenceDiagram
前端->>后端: 1.请求上传凭证
后端->>MinIO: 2.生成预签名URL
后端-->>前端: 3.返回预签名URL
前端->>MinIO: 4.使用URL直传文件
MinIO-->>前端: 5.返回上传结果
```
二、服务端实现
1. 环境配置
```bash
安装依赖
npm install minio express dotenv cors
```
2. 安全凭证管理
```javascript
// .env文件
MINIO_ENDPOINT=your-minio.example.com
MINIO_PORT=9000
MINIO_USE_SSL=true
MINIO_ACCESS_KEY=your_access_key
MINIO_SECRET_KEY=your_secret_key
```
3. 预签名URL生成接口
```javascript
const Minio = require('minio')
const express = require('express')
const app = express()
require('dotenv').config()
const minioClient = new Minio.Client({
endPoint: process.env.MINIO_ENDPOINT,
port: parseInt(process.env.MINIO_PORT),
useSSL: process.env.MINIO_USE_SSL === 'true',
accessKey: process.env.MINIO_ACCESS_KEY,
secretKey: process.env.MINIO_SECRET_KEY
})
// 生成预签名上传URL
app.get('/api/upload-token', (req, res) => {
const fileId = `{Date.now()}-{Math.random().toString(36).substr(2, 9)}`
const objectName = `uploads/{fileId}/{req.query.fileName}`
const expiry = 15 * 60 // 15分钟有效期
minioClient.presignedPutObject(
process.env.MINIO_BUCKET,
objectName,
expiry,
(err, presignedUrl) => {
if (err) return res.status(500).json({ error: err.message })
res.json({
presignedUrl,
objectUrl: `https://${process.env.MINIO_ENDPOINT}/{process.env.MINIO_BUCKET}/{objectName}`
})
}
)
})
app.listen(3000, () => console.log('Server running on port 3000'))
```
三、前端实现
1. 获取上传凭证
```javascript
async function getUploadToken(fileName) {
const response = await fetch(`/api/upload-token?fileName=${encodeURIComponent(fileName)}`)
return response.json()
}
```
2. 文件上传组件
```html
<input type="file" id="fileInput" />
<button οnclick="uploadFile()">上传</button>
<script>
async function uploadFile() {
const fileInput = document.getElementById('fileInput')
const file = fileInput.files[0]
// 获取上传凭证
const { presignedUrl, objectUrl } = await getUploadToken(file.name)
// 直传MinIO
const result = await fetch(presignedUrl, {
method: 'PUT',
body: file,
headers: {
'Content-Type': file.type
}
})
if (result.ok) {
console.log('文件地址:', objectUrl)
} else {
console.error('上传失败')
}
}
</script>
```
四、安全增强措施
1. MinIO存储桶策略
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::your-bucket/*",
"Condition": {
"NumericGreaterThan": {"s3:signatureAge": "900"}
}
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::your-bucket/uploads/*"
}
]
}
```
2. 服务端安全中间件
```javascript
// 添加CORS限制
const cors = require('cors')
app.use(cors({
origin: ['https://your-domain.com'],
methods: ['GET', 'POST'],
maxAge: 300
}))
// 请求频率限制
const rateLimit = require('express-rate-limit')
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
})
app.use('/api/upload-token', limiter)
```
五、大文件分片上传
1. 前端分片处理
```javascript
async function uploadLargeFile(file) {
const CHUNK_SIZE = 5 * 1024 * 1024 // 5MB
const chunks = Math.ceil(file.size / CHUNK_SIZE)
// 初始化分片上传
const { uploadId } = await initMultipartUpload(file.name)
// 并发上传分片
const uploadPromises = []
for (let i = 0; i < chunks; i++) {
const chunk = file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE)
uploadPromises.push(uploadChunk(uploadId, i + 1, chunk))
}
await Promise.all(uploadPromises)
// 完成上传
return completeUpload(uploadId)
}
```
2. 分片上传服务端支持
```javascript
// 初始化分片上传
app.post('/api/multipart-init', (req, res) => {
const objectName = `uploads/{uuidv4()}/{req.query.fileName}`
minioClient.initiateNewMultipartUpload(
process.env.MINIO_BUCKET,
objectName,
(err, uploadId) => {
if (err) return res.status(500).send(err)
res.json({ uploadId, objectName })
}
)
})
// 获取分片上传URL
app.get('/api/multipart-url', (req, res) => {
const presignedUrl = minioClient.presignedPutObject(
process.env.MINIO_BUCKET,
req.query.objectName,
15 * 60,
{ partNumber: req.query.partNumber, uploadId: req.query.uploadId }
)
res.json({ presignedUrl })
})
```
六、监控与日志
1. MinIO操作日志
```bash
启用访问日志
mc admin config set myminio audit_webhook endpoint=http://log-server:3000/logs
```
2. 服务端监控指标
```javascript
const prometheus = require('prom-client')
const uploadCounter = new prometheus.Counter({
name: 'file_uploads_total',
help: 'Total number of file uploads',
labelNames: ['status']
})
app.post('/api/upload', (req, res) => {
uploadCounter.inc({ status: 'started' })
// ...上传逻辑
})
```
七、方案优势
-
**零密钥暴露**:前端仅使用临时预签名URL
-
**高安全性**:15分钟有效期+IP限制+HTTPS
-
**高性能**:支持5GB+大文件分片上传
-
**可扩展**:轻松集成CDN和自动转码
-
**合规性**:满足GDPR和等保要求
八、部署注意事项
-
启用MinIO的HTTPS访问
-
定期轮换MinIO根密钥
-
设置存储桶生命周期策略
-
监控存储桶使用情况
-
启用防病毒扫描
-
配置自动告警规则
```bash
MinIO客户端配置示例
mc alias set myminio https://minio.example.com your_access_key your_secret_key
mc mb myminio/secure-uploads
mc lifecycle set myminio/secure-uploads lifecycle.json
```
通过该方案,前端可直接安全上传文件到MinIO,无需在后端保存中转文件,同时确保敏感凭证不会暴露在前端代码中。实际部署时建议结合具体业务需求调整安全策略和性能参数。