micro-fileos 前端使用指南
1. FileosUploader 组件设计
1.1 Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
mode | 'simple' | 'presign' | 'multipart' | 'presign' | 上传模式 |
accept | string | — | 允许的文件类型 |
multiple | boolean | false | 是否允许多文件 |
maxSize | number | 50 (MB) | 单文件最大大小 |
category | string | — | 业务分类 |
bucketName | string | — | 指定 Bucket |
isPublic | boolean | false | 是否公开读 |
autoUpload | boolean | true | 选择后自动上传 |
chunkSize | number | 5 (MB) | 分片大小 |
concurrency | number | 3 | 并发上传数 |
imageProcess | object | — | 图片处理参数 |
1.2 Events
| 事件 | 参数 | 说明 |
|---|---|---|
upload-start | { file, fileId } | 上传开始 |
upload-progress | { fileId, loaded, total, percent } | 上传进度 |
upload-success | { fileId, record } | 上传成功 |
upload-error | { fileId, error } | 上传失败 |
upload-complete | { results } | 全部上传完成 |
1.3 返回数据结构
javascript
interface FileosRecord {
id: number
fileId: string
fileName: string
fileType: string
fileSize: number
fileHash: string
contentType: string
category: string
dirPath: string
isPublic: number
ossSp: string
bucketName: string
uploadType: string
uploadId: string
uploadStatus: string
imageProcess: string
previewUrl: string
}1.4 ImageProcess 类型说明
图片处理参数,用于上传组件的 imageProcess 属性,支持缩放、裁剪、水印等 OSS 图片处理能力。
javascript
interface ImageProcess {
resize?: {
width?: number
height?: number
mode?: 'fit' | 'fill' | 'exact'
}
crop?: {
x?: number
y?: number
width?: number
height?: number
}
watermark?: {
text?: string
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center'
opacity?: number
fontSize?: number
}
}2. 上传方式选型
| 方式 | 接口 | 文件流向 | 适用场景 |
|---|---|---|---|
| 简单上传 | POST /micro-fileos/upload/simple | 前端 → 后端 → OSS | 小文件(≤50MB),后端需校验或加工文件 |
| 预签名直传 | /presign/upload + /presign/complete | 前端 → OSS | 推荐,不占用后端带宽 |
| 分片上传 | /presign/multipart/* | 前端 → OSS(分片) | 大文件(>50MB),支持并行与断点续传 |
3. 简单上传(服务端中转)
文件由前端上传到后端,由后端转存 OSS。Content-Type: multipart/form-data。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
file | File | 是 | 文件二进制 |
category | string | 否 | 业务分类 |
bucketName | string | 否 | 指定 Bucket,缺省使用默认 Bucket |
fileName | string | 否 | 自定义文件名,缺省使用原始文件名 |
isPublic | boolean | 否 | 是否公开读 |
imageProcess | string | 否 | 图片处理参数 JSON(见 1.4) |
公开读文件可改用 POST /micro-fileos/upload/simple/public,等价于强制 isPublic=true。
javascript
async function uploadSimple(file) {
const form = new FormData()
form.append('file', file)
form.append('category', 'avatar')
// form.append('bucketName', 'my-bucket')
// form.append('fileName', 'custom-name.jpg')
// form.append('isPublic', 'false')
// form.append('imageProcess', JSON.stringify({ resize: { width: 200, height: 200, mode: 'fit' } }))
const resp = await fetch('/micro-fileos/upload/simple', {
method: 'POST',
body: form
})
const { data: record } = await resp.json()
// record.fileId — 文件唯一标识,业务表中保存此值
// record.previewUrl — 预览签名 URL(10 分钟有效)
return record
}私有文件后续访问时,通过签名接口换取临时 URL:
javascript
const resp = await fetch(`/micro-fileos/sign/url?fileId=${encodeURIComponent(record.fileId)}&expireMinutes=30`)
const { data: url } = await resp.json()4. 预签名直传流程(推荐)
推荐使用预签名模式,文件由前端直传 OSS,不经过后端服务器。
┌────────┐ ①请求预签名URL ┌────────┐
│ 前端 │ ──────────────────→ │ 后端 │
│ │ ←────────────────── │ │
│ │ presignUrl+fileId │ │
│ │ └────────┘
│ │
│ │ ② PUT文件到OSS ┌────────┐
│ │ ──────────────────→ │ OSS │
│ │ ←────────────────── │ │
│ │ 200 OK └────────┘
│ │
│ │ ③确认上传完成 ┌────────┐
│ │ ──────────────────→ │ 后端 │
│ │ ←────────────────── │ │
│ │ record └────────┘
└────────┘javascript
// ① 请求预签名 URL
const presignResp = await fetch('/micro-fileos/presign/upload', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileName: file.name,
fileSize: file.size,
contentType: file.type,
category: 'avatar',
bucketName: 'my-bucket',
isPublic: false,
expireMinutes: 30
})
})
const { data } = await presignResp.json()
const { fileId, presignUrl, ossSp, bucketName } = data
// ② PUT 文件到 OSS
const uploadResp = await fetch(presignUrl, {
method: 'PUT',
headers: { 'Content-Type': file.type },
body: file
})
// ③ 确认上传完成
const completeResp = await fetch('/micro-fileos/presign/complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileId,
ossSp,
bucketName,
fileName: file.name,
fileSize: file.size,
category: 'avatar',
isPublic: false
})
})
const record = (await completeResp.json()).data5. 分片上传流程
适用于大文件(>50MB),将文件拆分为多个分片并行上传。
┌────────┐ ①初始化分片上传 ┌────────┐
│ 前端 │ ──────────────→ │ 后端 │
│ │ ←────────────── │ │
│ │ uploadId+parts └────────┘
│ │
│ │ ②逐片PUT到OSS ┌────────┐
│ │ ──────────────→ │ OSS │
│ │ ←────────────── │ │
│ │ eTag └────────┘
│ │ (重复②直到所有分片完成)
│ │
│ │ ③完成分片上传 ┌────────┐
│ │ ──────────────→ │ 后端 │
│ │ ←────────────── │ │
│ │ record └────────┘
└────────┘javascript
const CHUNK_SIZE = 5 * 1024 * 1024
// ① 初始化分片上传
const file = document.getElementById('fileInput').files[0]
const partCount = Math.ceil(file.size / CHUNK_SIZE)
const initResp = await fetch('/micro-fileos/presign/multipart/init', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileName: file.name,
fileSize: file.size,
contentType: file.type,
category: 'video',
bucketName: 'media-bucket',
partCount,
expireMinutes: 60
})
})
const { uploadId, fileId, ossSp, bucketName, parts } = (await initResp.json()).data
// ② 逐片上传
const completedParts = []
for (const part of parts) {
const start = (part.partNumber - 1) * CHUNK_SIZE
const end = Math.min(start + CHUNK_SIZE, file.size)
const chunk = file.slice(start, end)
const partResp = await fetch(part.presignUrl, {
method: 'PUT',
headers: { 'Content-Type': file.type },
body: chunk
})
const eTag = partResp.headers.get('ETag')
completedParts.push({ partNumber: part.partNumber, eTag })
}
// ③ 完成分片上传
const completeResp = await fetch('/micro-fileos/presign/multipart/complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
uploadId,
fileId,
bucketName,
ossSp,
fileName: file.name,
fileSize: file.size,
category: 'video',
parts: completedParts
})
})
const record = (await completeResp.json()).data6. 断点续传流程
分片上传天然支持断点续传,核心思路是记录已完成的分片信息,恢复时跳过已上传的分片。
javascript
// 上传进度持久化(localStorage / IndexedDB)
function saveProgress(fileId, completedParts) {
localStorage.setItem(`fileos_upload_${fileId}`, JSON.stringify({
fileId,
uploadId,
completedParts,
timestamp: Date.now()
}))
}
function loadProgress(fileId) {
const data = localStorage.getItem(`fileos_upload_${fileId}`)
return data ? JSON.parse(data) : null
}
// 恢复上传
async function resumeUpload(file) {
const progress = loadProgress(file.id)
if (!progress) {
return startNewUpload(file)
}
const { uploadId, fileId, completedParts } = progress
// 重新获取分片预签名 URL(原 URL 可能已过期)
const initResp = await fetch('/micro-fileos/presign/multipart/init', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileName: file.name,
fileSize: file.size,
contentType: file.type,
partCount: Math.ceil(file.size / CHUNK_SIZE),
expireMinutes: 60
})
})
const { parts } = (await initResp.json()).data
// 过滤已完成的分片
const completedNumbers = new Set(completedParts.map(p => p.partNumber))
const pendingParts = parts.filter(p => !completedNumbers.has(p.partNumber))
// 仅上传未完成的分片
for (const part of pendingParts) {
const start = (part.partNumber - 1) * CHUNK_SIZE
const end = Math.min(start + CHUNK_SIZE, file.size)
const chunk = file.slice(start, end)
const partResp = await fetch(part.presignUrl, {
method: 'PUT',
body: chunk
})
const eTag = partResp.headers.get('ETag')
completedParts.push({ partNumber: part.partNumber, eTag })
saveProgress(fileId, completedParts)
}
// 完成上传
await completeMultipartUpload(uploadId, fileId, completedParts)
}7. 其他组件说明
7.1 FileosDirectoryBrowser — 目录浏览组件
用于浏览和管理文件目录结构,支持文件选择、预览和删除操作。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
action | string | — | API 基础路径,如 /micro-fileos |
bucket | string | — | 指定 Bucket |
selectable | boolean | false | 是否可选择文件 |
multiple | boolean | false | 是否多选 |
accept | string | — | 可选择的文件类型过滤 |
showPreview | boolean | true | 是否显示文件预览 |
onDelete | boolean | false | 是否允许删除文件 |
7.2 FileosImageUploader — 图片上传组件
专门用于图片上传,带预览、裁剪、压缩功能。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
action | string | — | API 基础路径,如 /micro-fileos |
bucket | string | — | 指定 Bucket |
category | string | — | 业务分类 |
accept | string | 'image/*' | 允许的文件类型 |
maxSize | number | 10 (MB) | 单文件最大大小 |
maxCount | number | 9 | 最大上传数量 |
multiple | boolean | true | 是否允许多文件 |
compress | boolean | true | 是否启用压缩 |
compressQuality | number | 0.8 | 压缩质量(0-1) |
crop | boolean | false | 是否启用裁剪 |
cropRatio | number | — | 裁剪比例,如 1/1、4/3、16/9 |
imageProcess | ImageProcess | — | 图片处理参数(缩放、水印) |
listType | 'text' | 'picture' | 'picture-card' | 'picture-card' | 文件列表展示样式 |
disabled | boolean | false | 是否禁用 |
7.3 FileosDragUploader — 拖拽上传组件
支持拖拽文件到区域上传,适用于批量文件上传场景。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
action | string | — | API 基础路径,如 /micro-fileos |
bucket | string | — | 指定 Bucket |
category | string | — | 业务分类 |
accept | string | — | 允许的文件类型 |
maxSize | number | 50 (MB) | 单文件最大大小 |
maxCount | number | 5 | 最大上传数量 |
multiple | boolean | true | 是否允许多文件 |
isPublic | boolean | false | 是否公开读 |
imageProcess | ImageProcess | — | 图片处理参数(缩放、水印) |
disabled | boolean | false | 是否禁用 |
dragAreaText | string | '将文件拖到此处,或点击上传' | 拖拽区域提示文字 |
dragAreaIcon | string | — | 拖拽区域图标 |
7.4 FileosChunkUploader — 大文件分片上传组件
专门用于大文件上传,支持分片、断点续传。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
action | string | — | API 基础路径,如 /micro-fileos |
bucket | string | — | 指定 Bucket |
category | string | — | 业务分类 |
accept | string | — | 允许的文件类型 |
maxSize | number | 500 (MB) | 单文件最大大小 |
chunkSize | number | 5 (MB) | 分片大小 |
chunkThreshold | number | 10 (MB) | 触发分片上传的阈值 |
concurrent | number | 3 | 并发上传数 |
retryCount | number | 3 | 失败重试次数 |
isPublic | boolean | false | 是否公开读 |
disabled | boolean | false | 是否禁用 |
showProgress | boolean | true | 是否显示上传进度 |
showSpeed | boolean | true | 是否显示上传速度 |
8. 前端完整使用示例
8.1 基础使用 — 预签名直传
javascript
async function uploadSimpleFile(file) {
const presignResp = await fetch('/micro-fileos/presign/upload', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileName: file.name,
fileSize: file.size,
contentType: file.type,
category: 'document',
isPublic: false
})
})
const { data: { fileId, presignUrl, ossSp, bucketName } } = await presignResp.json()
const uploadResp = await fetch(presignUrl, {
method: 'PUT',
headers: { 'Content-Type': file.type },
body: file
})
const completeResp = await fetch('/micro-fileos/presign/complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileId,
ossSp,
bucketName,
fileName: file.name,
fileSize: file.size,
category: 'document',
isPublic: false
})
})
const { data: record } = await completeResp.json()
console.log('上传成功:', record)
return record
}8.2 进阶使用 — 带进度显示的分片上传
javascript
const CHUNK_SIZE = 5 * 1024 * 1024
async function uploadWithProgress(file, onProgress) {
const partCount = Math.ceil(file.size / CHUNK_SIZE)
const initResp = await fetch('/micro-fileos/presign/multipart/init', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileName: file.name,
fileSize: file.size,
contentType: file.type,
category: 'video',
partCount,
expireMinutes: 60
})
})
const { data: { uploadId, fileId, ossSp, bucketName, parts } } = await initResp.json()
const completedParts = []
let uploadedSize = 0
for (const part of parts) {
const start = (part.partNumber - 1) * CHUNK_SIZE
const end = Math.min(start + CHUNK_SIZE, file.size)
const chunk = file.slice(start, end)
const partResp = await fetch(part.presignUrl, {
method: 'PUT',
body: chunk
})
const eTag = partResp.headers.get('ETag')
completedParts.push({ partNumber: part.partNumber, eTag })
uploadedSize += chunk.size
onProgress({
loaded: uploadedSize,
total: file.size,
percent: Math.round((uploadedSize / file.size) * 100)
})
}
const completeResp = await fetch('/micro-fileos/presign/multipart/complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
uploadId,
fileId,
bucketName,
ossSp,
fileName: file.name,
fileSize: file.size,
category: 'video',
parts: completedParts
})
})
const { data: record } = await completeResp.json()
return record
}
// 使用示例
const fileInput = document.getElementById('fileInput')
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0]
if (!file) return
await uploadWithProgress(file, (progress) => {
console.log(`上传进度: ${progress.percent}%`)
})
})8.3 目录浏览示例
javascript
// 加载目录树
async function loadDirectoryTree(bucketName = 'my-bucket') {
const resp = await fetch(`/micro-fileos/directory/tree?bucketName=${bucketName}`)
const { data: tree } = await resp.json()
return tree
}
// 加载子目录列表
async function loadSubDirectories(parentPath, bucketName = 'my-bucket') {
const resp = await fetch(`/micro-fileos/directory/list?parentPath=${encodeURIComponent(parentPath)}&bucketName=${bucketName}`)
const { data: directories } = await resp.json()
return directories
}
// 获取文件访问签名 URL
async function getFileUrl(fileId, expireMinutes = 30) {
const resp = await fetch(`/micro-fileos/sign/url?fileId=${fileId}&expireMinutes=${expireMinutes}`)
const { data: url } = await resp.json()
return url
}8.4 图片上传示例(带压缩和裁剪)
javascript
async function uploadAvatar(file) {
const presignResp = await fetch('/micro-fileos/presign/upload', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileName: file.name,
fileSize: file.size,
contentType: 'image/jpeg',
category: 'avatar',
isPublic: true,
imageProcess: JSON.stringify({
resize: {
width: 200,
height: 200,
mode: 'fit'
}
})
})
})
const { data: { fileId, presignUrl, ossSp, bucketName } } = await presignResp.json()
await fetch(presignUrl, {
method: 'PUT',
headers: { 'Content-Type': 'image/jpeg' },
body: file
})
const completeResp = await fetch('/micro-fileos/presign/complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileId,
ossSp,
bucketName,
fileName: file.name,
fileSize: file.size,
category: 'avatar',
isPublic: true
})
})
const { data: record } = await completeResp.json()
return record
}