Skip to content

micro-fileos 前端使用指南

1. FileosUploader 组件设计

1.1 Props

属性类型默认值说明
mode'simple' | 'presign' | 'multipart''presign'上传模式
acceptstring允许的文件类型
multiplebooleanfalse是否允许多文件
maxSizenumber50 (MB)单文件最大大小
categorystring业务分类
bucketNamestring指定 Bucket
isPublicbooleanfalse是否公开读
autoUploadbooleantrue选择后自动上传
chunkSizenumber5 (MB)分片大小
concurrencynumber3并发上传数
imageProcessobject图片处理参数

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. 简单上传流程(预签名模式)

推荐使用预签名模式,文件由前端直传 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()).data

3. 分片上传流程

适用于大文件(>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()).data

4. 断点续传流程

分片上传天然支持断点续传,核心思路是记录已完成的分片信息,恢复时跳过已上传的分片。

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)
}

5. 其他组件说明

5.1 FileosDirectoryBrowser — 目录浏览组件

用于浏览和管理文件目录结构,支持文件选择、预览和删除操作。

属性类型默认值说明
actionstringAPI 基础路径,如 /micro-fileos
bucketstring指定 Bucket
selectablebooleanfalse是否可选择文件
multiplebooleanfalse是否多选
acceptstring可选择的文件类型过滤
showPreviewbooleantrue是否显示文件预览
onDeletebooleanfalse是否允许删除文件

5.2 FileosImageUploader — 图片上传组件

专门用于图片上传,带预览、裁剪、压缩功能。

属性类型默认值说明
actionstringAPI 基础路径,如 /micro-fileos
bucketstring指定 Bucket
categorystring业务分类
acceptstring'image/*'允许的文件类型
maxSizenumber10 (MB)单文件最大大小
maxCountnumber9最大上传数量
multiplebooleantrue是否允许多文件
compressbooleantrue是否启用压缩
compressQualitynumber0.8压缩质量(0-1)
cropbooleanfalse是否启用裁剪
cropRationumber裁剪比例,如 1/14/316/9
imageProcessImageProcess图片处理参数(缩放、水印)
listType'text' | 'picture' | 'picture-card''picture-card'文件列表展示样式
disabledbooleanfalse是否禁用

5.3 FileosDragUploader — 拖拽上传组件

支持拖拽文件到区域上传,适用于批量文件上传场景。

属性类型默认值说明
actionstringAPI 基础路径,如 /micro-fileos
bucketstring指定 Bucket
categorystring业务分类
acceptstring允许的文件类型
maxSizenumber50 (MB)单文件最大大小
maxCountnumber5最大上传数量
multiplebooleantrue是否允许多文件
isPublicbooleanfalse是否公开读
imageProcessImageProcess图片处理参数(缩放、水印)
disabledbooleanfalse是否禁用
dragAreaTextstring'将文件拖到此处,或点击上传'拖拽区域提示文字
dragAreaIconstring拖拽区域图标

5.4 FileosChunkUploader — 大文件分片上传组件

专门用于大文件上传,支持分片、断点续传。

属性类型默认值说明
actionstringAPI 基础路径,如 /micro-fileos
bucketstring指定 Bucket
categorystring业务分类
acceptstring允许的文件类型
maxSizenumber500 (MB)单文件最大大小
chunkSizenumber5 (MB)分片大小
chunkThresholdnumber10 (MB)触发分片上传的阈值
concurrentnumber3并发上传数
retryCountnumber3失败重试次数
isPublicbooleanfalse是否公开读
disabledbooleanfalse是否禁用
showProgressbooleantrue是否显示上传进度
showSpeedbooleantrue是否显示上传速度

6. 前端完整使用示例

6.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
}

6.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}%`)
  })
})

6.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
}

6.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
}

Released under the MIT License.