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. 上传方式选型 ​

方式接口文件流向适用场景
简单上传POST /micro-fileos/upload/simple前端 → 后端 → OSS小文件(≤50MB),后端需校验或加工文件
预签名直传/presign/upload + /presign/complete前端 → OSS推荐,不占用后端带宽
分片上传/presign/multipart/*前端 → OSS(分片)大文件(>50MB),支持并行与断点续传

3. 简单上传(服务端中转) ​

文件由前端上传到后端,由后端转存 OSS。Content-Type: multipart/form-data。

参数类型必填说明
fileFile是文件二进制
categorystring否业务分类
bucketNamestring否指定 Bucket,缺省使用默认 Bucket
fileNamestring否自定义文件名,缺省使用原始文件名
isPublicboolean否是否公开读
imageProcessstring否图片处理参数 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()).data

5. 分片上传流程 ​

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

6. 断点续传流程 ​

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

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 — 目录浏览组件 ​

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

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

7.2 FileosImageUploader — 图片上传组件 ​

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

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

7.3 FileosDragUploader — 拖拽上传组件 ​

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

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

7.4 FileosChunkUploader — 大文件分片上传组件 ​

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

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

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
}

Released under the MIT License.