Appearance
OSS 上传工具
OSS 上传工具是一个用于处理阿里云对象存储服务(OSS)文件上传的实用工具类。它提供了文件上传、URL 签名和凭证管理等功能。
功能特点
- ✅ 文件上传进度跟踪
- ✅ 自动凭证管理和刷新
- ✅ 签名 URL 生成
- ✅ 自定义 OSS 错误处理
- ✅ 多配置 Client 缓存(支持不同 endpoint/region/bucket)
- ✅ 分片上传(支持大文件上传)
- ✅ 智能缓存管理(15分钟自动过期)
安装
OSS 上传工具已包含在 utils 包中,无需额外安装。
使用方法
基础文件上传
typescript
import { uploadFile } from '@/utils/oss-uploader'
import { apiClient } from '@/api'
import { ossUrls } from '@/api/urls'
const handleUpload = async (file: File) => {
try {
const result = await uploadFile({
file,
getCredentials: async () => {
const { res, error } = await apiClient(ossUrls.getSts, {
file_type: 'document',
filename: file.name,
auto_create_file: true,
knowledge_base_id: 'kb_123',
})
if (error) {
throw new OssError(error.msg)
}
// 构建回调变量
const callbackVars = {
'x:callback_id': res.callback_id,
'x:file_type': res.file_type
}
// 构建 headers(处理 callback 可能为空的情况)
const headers: Record<string, string> = {}
if (res.callback) {
headers['x-oss-callback'] = res.callback
headers['x-oss-callback-var'] = btoa(JSON.stringify(callbackVars))
}
return {
headers,
path: res.object_key || undefined,
bucket: res.bucket || undefined,
endpoint: res.endpoint,
region: res.region,
accessKeyId: res.access_key_id,
accessKeySecret: res.access_key_secret,
securityToken: res.security_token,
}
},
onProgress: (progress) => {
console.log(`上传进度: ${progress.percent}%`)
}
})
console.log('上传成功:', result)
} catch (error) {
console.error('上传失败:', error)
}
}使用自定义 Endpoint(CDN 加速)
typescript
import { uploadFile } from '@/utils/oss-uploader'
const handleUploadWithCDN = async (file: File) => {
try {
const result = await uploadFile({
file,
getCredentials: async () => {
const response = await fetch('/api/oss/sts')
const data = await response.json()
return {
accessKeyId: data.access_key_id,
accessKeySecret: data.access_key_secret,
securityToken: data.security_token,
endpoint: 'https://cdn.example.com', // 自定义 CDN 域名
bucket: 'my-bucket',
cname: true, // 告诉 OSS SDK 这是 CNAME 域名
path: 'images',
}
},
onProgress: ({ percent }) => {
console.log(`上传进度: ${percent}%`)
}
})
console.log('上传成功:', result)
} catch (error) {
console.error('上传失败:', error)
}
}覆盖后端返回的配置
typescript
import { uploadFile } from '@/utils/oss-uploader'
const handleUpload = async (file: File) => {
const result = await uploadFile({
file,
// 这里的 path 和 headers 会覆盖 getCredentials 返回的配置
path: 'custom/path',
headers: {
'x-oss-callback': customCallback,
},
getCredentials: async () => {
const data = await fetchOssConfig()
return {
accessKeyId: data.access_key_id,
accessKeySecret: data.access_key_secret,
securityToken: data.security_token,
// 这些配置会被上面的参数覆盖
path: data.object_key,
headers: data.headers,
}
}
})
}生成签名 URL
typescript
import { generateSignedUrl } from '@/utils/oss-uploader'
const getFileUrl = async (objectKey: string) => {
try {
const url = await generateSignedUrl({
objectKey,
expires: 3600, // 可选,默认 3600 秒
getCredentials: async () => {
const response = await fetch('/api/oss/sts')
const data = await response.json()
return {
accessKeyId: data.access_key_id,
accessKeySecret: data.access_key_secret,
securityToken: data.security_token,
endpoint: data.endpoint, // 可选
bucket: data.bucket,
region: data.region,
}
}
})
return url
} catch (error) {
console.error('生成签名 URL 失败:', error)
throw error
}
}缓存管理
typescript
import { OssUploader } from '@/utils/oss-uploader'
// 清理过期的 client 缓存(可选,系统会自动管理)
OssUploader.clearExpiredClients()
// 清空所有缓存(例如用户登出时)
OssUploader.clearAllClients()API 参考
uploadFile(options: UploadFileOptions)
使用指定配置上传文件到 OSS。
参数
options: UploadFileOptionsfile: File- 要上传的文件(必填)getCredentials: () => Promise<OssCredentialsConfig | null>- 获取 OSS 凭证和配置的函数(必填)- 返回值包含:
accessKeyId: string- 访问密钥 ID(必填)accessKeySecret: string- 访问密钥(必填)securityToken: string- 安全令牌(必填)bucket?: string- OSS bucket 名称(可选)region?: string- OSS 区域(可选)endpoint?: string- 自定义 OSS endpoint,支持 CDN 域名(可选)cname?: boolean- 是否为 CNAME 域名(可选)secure?: boolean- 是否使用 HTTPS(可选)timeout?: number- 请求超时时间(可选)path?: string- 上传路径/对象键(可选)headers?: Record<string, string>- 自定义 headers(可选)
- 返回值包含:
path?: string- 自定义上传路径,会覆盖 getCredentials 返回的 path(可选)headers?: Record<string, string>- 自定义 headers,会覆盖 getCredentials 返回的 headers(可选)onProgress?: (progress: { percent: number }) => void- 进度回调函数(可选)
返回值
Promise<OSS.MultipartUploadResult>- OSS 上传结果
示例
typescript
// 基础上传(所有配置由后端返回)
await uploadFile({
file: myFile,
getCredentials: async () => {
const res = await fetch('/api/oss/sts')
const data = await res.json()
return {
accessKeyId: data.access_key_id,
accessKeySecret: data.access_key_secret,
securityToken: data.security_token,
bucket: data.bucket,
region: data.region,
endpoint: data.endpoint,
cname: true,
path: data.object_key,
headers: data.headers,
}
}
})
// 覆盖后端配置
await uploadFile({
file: myFile,
path: 'custom/path', // 覆盖后端返回的 path
getCredentials: async () => ({ /* ... */ })
})generateSignedUrl(options: GenerateSignedUrlOptions)
生成用于访问 OSS 对象的签名 URL。
参数
options: GenerateSignedUrlOptionsobjectKey: string- OSS 中的对象键(必填)getCredentials: () => Promise<OssCredentialsConfig | null>- 获取 OSS 凭证和配置的函数(必填)expires?: number- URL 过期时间(秒),默认 3600(可选)
返回值
Promise<string>- 签名 URL
示例
typescript
// 基础用法
const url = await generateSignedUrl({
objectKey: 'path/to/file.jpg',
getCredentials: async () => {
const res = await fetch('/api/oss/sts')
const data = await res.json()
return {
accessKeyId: data.access_key_id,
accessKeySecret: data.access_key_secret,
securityToken: data.security_token,
bucket: data.bucket,
region: data.region,
endpoint: data.endpoint,
}
}
})
// 自定义过期时间
const url = await generateSignedUrl({
objectKey: 'path/to/file.jpg',
expires: 7200, // 2 小时
getCredentials: async () => ({ /* ... */ })
})OssUploader.clearExpiredClients()
清理过期的 client 缓存(过期时间为 15 分钟)。
返回值
void
示例
typescript
import { OssUploader } from '@/utils/oss-uploader'
// 定期清理过期缓存
setInterval(() => {
OssUploader.clearExpiredClients()
}, 5 * 60 * 1000) // 每 5 分钟清理一次OssUploader.clearAllClients()
清空所有 client 缓存。
返回值
void
示例
typescript
import { OssUploader } from '@/utils/oss-uploader'
// 用户登出时清空缓存
const handleLogout = () => {
OssUploader.clearAllClients()
// 其他登出逻辑...
}错误处理
该工具包含自定义的 OssError 类用于错误处理。所有操作都会将错误包装在此类中以实现一致的错误处理:
typescript
try {
await uploadFile(/* ... */)
} catch (error) {
if (error instanceof OssError) {
// 处理 OSS 特定错误
console.error('OSS 操作失败:', error.message)
} else {
// 处理其他错误
console.error('意外错误:', error)
}
}配置说明
默认配置包括:
- 地域:oss-cn-hangzhou
- Bucket:yao-file-daily
- 安全传输:启用
- 超时时间:60000ms
- 已启用 CORS 并配置相应的请求头
安全注意事项
- 永远不要在应用程序中硬编码凭证
- 确保实现适当的凭证轮换机制
- 使用最小必要的 OSS 权限
- Token 默认过期时间为 15 分钟
高级用法
多配置场景
工具会自动为不同的配置组合创建独立的 client 实例:
typescript
// 这三个上传会使用不同的 client 实例
await uploadFile({
file: file1,
endpoint: 'https://cdn1.example.com',
bucket: 'bucket-1',
getCredentials: getCredentials1
})
await uploadFile({
file: file2,
endpoint: 'https://cdn2.example.com',
bucket: 'bucket-2',
getCredentials: getCredentials2
})
await uploadFile({
file: file3,
region: 'oss-cn-beijing',
bucket: 'bucket-3',
getCredentials: getCredentials3
})与 STS 服务集成
推荐的后端 STS 接口实现:
typescript
import { apiClient } from '@/api'
import { ossUrls } from '@/api/urls'
import { OssError } from '@/utils/oss-uploader'
// 前端调用
const getCredentials = async () => {
const { res, error } = await apiClient(ossUrls.getSts, {
file_type: 'image',
filename: file.name,
auto_create_file: true,
knowledge_base_id: 'kb_123',
})
if (error) {
throw new OssError(error.msg)
}
// 构建回调变量
const callbackVars = {
'x:callback_id': res.callback_id,
'x:file_type': res.file_type
}
// 构建 headers(处理 callback 可能为空的情况)
const headers: Record<string, string> = {}
if (res.callback) {
headers['x-oss-callback'] = res.callback
headers['x-oss-callback-var'] = btoa(JSON.stringify(callbackVars))
}
return {
headers,
path: res.object_key || undefined,
bucket: res.bucket || undefined,
endpoint: res.endpoint,
region: res.region,
accessKeyId: res.access_key_id,
accessKeySecret: res.access_key_secret,
securityToken: res.security_token,
}
}
// 使用
await uploadFile({
file,
getCredentials
})大文件上传优化
工具使用分片上传(multipartUpload),自动处理大文件:
typescript
// 上传大文件(如视频)
await uploadFile({
file: largeVideoFile, // 例如 500MB
path: 'videos',
timeout: 300000, // 增加超时时间
getCredentials: async () => ({ /* ... */ }),
onProgress: ({ percent }) => {
// 实时显示进度
updateProgressBar(percent)
}
})常见问题
Q: 为什么我的 endpoint 配置没有生效?
A: 在 v2.0 之前,工具使用单例模式缓存 client,导致后续的配置被忽略。现在已修复,每个不同的配置组合都会创建独立的 client 实例。
Q: 如何判断使用哪个 client?
A: 工具根据 endpoint-region-bucket 组合生成缓存 key:
- 如果指定了
endpoint,使用 endpoint - 否则使用
region - 不同的
bucket也会创建不同的 client
Q: client 缓存会占用太多内存吗?
A: 不会。缓存有以下机制:
- 每个 client 15 分钟后自动过期
- 可以手动调用
clearExpiredClients()清理 - 用户登出时调用
clearAllClients()清空
Q: 支持断点续传吗?
A: 支持。工具使用 multipartUpload 方法,在网络中断后可以继续上传未完成的分片。
Q: 如何处理上传失败?
A: 建议实现重试机制:
typescript
async function uploadWithRetry(file: File, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await uploadFile({
file,
getCredentials: async () => ({ /* ... */ })
})
} catch (error) {
if (i === maxRetries - 1) throw error
console.log(`上传失败,重试 ${i + 1}/${maxRetries}`)
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)))
}
}
}Q: 可以同时上传多个文件吗?
A: 可以。每个上传都是独立的:
typescript
const files = [file1, file2, file3]
// 并行上传
const results = await Promise.all(
files.map(file => uploadFile({
file,
getCredentials: async () => ({ /* ... */ })
}))
)
// 或串行上传
for (const file of files) {
await uploadFile({
file,
getCredentials: async () => ({ /* ... */ })
})
}最佳实践
- 使用 STS 临时凭证:永远不要在前端硬编码 AccessKey
- 处理上传进度:提供
onProgress回调以改善用户体验 - 实现错误处理:捕获并处理
OssError,必要时实现重试机制 - 合理设置路径:使用有意义的路径前缀便于文件管理
- CDN 加速:生产环境建议使用 CDN 域名作为 endpoint
- 清理缓存:用户登出时调用
clearAllClients()清空敏感信息 - 监控凭证过期:确保
getCredentials函数能正确获取新凭证 - 大文件优化:上传大文件时适当增加
timeout值
高级特性
自定义 Headers(OSS 回调支持)
工具支持在 getCredentials 中返回自定义 headers,用于 OSS 回调等场景:
typescript
import { apiClient } from '@/api'
import { ossUrls } from '@/api/urls'
import { OssError } from '@/utils/oss-uploader'
// 推荐方式:在 getCredentials 中返回 headers
await uploadFile({
file: myFile,
getCredentials: async () => {
const { res, error } = await apiClient(ossUrls.getSts, {
file_type: 'document',
filename: myFile.name,
auto_create_file: true,
})
if (error) {
throw new OssError(error.msg)
}
// 构建回调变量
const callbackVars = {
'x:callback_id': res.callback_id,
'x:file_type': res.file_type
}
// 构建 headers(处理 callback 可能为空的情况)
const headers: Record<string, string> = {}
if (res.callback) {
headers['x-oss-callback'] = res.callback
headers['x-oss-callback-var'] = btoa(JSON.stringify(callbackVars))
}
return {
headers,
path: res.object_key,
bucket: res.bucket,
endpoint: res.endpoint,
region: res.region,
accessKeyId: res.access_key_id,
accessKeySecret: res.access_key_secret,
securityToken: res.security_token,
}
}
})智能路径判断
工具会根据以下优先级确定上传路径:
带后缀的
path参数(最高优先级)- 如果
path参数包含文件后缀(如.pdf、.jpg),直接使用 - 例如:
path: 'documents/report-2024.pdf'
- 如果
目录 + 文件名组合(默认方式)
- 如果
path是目录路径,会自动拼接文件名 - 例如:
path: 'documents'→documents/filename.pdf
- 如果
typescript
// 场景1:path 是完整路径(带后缀)
await uploadFile({
file: myFile,
path: 'documents/report-2024.pdf', // ✅ 直接使用这个完整路径
getCredentials: async () => ({ ...credentials })
})
// 场景2:path 是目录(传统方式)
await uploadFile({
file: myFile, // 假设文件名是 invoice.pdf
path: 'documents', // ✅ 会拼接成 documents/invoice.pdf
getCredentials: async () => ({ ...credentials })
})接口说明
typescript
// OSS 凭证和配置接口(由 getCredentials 返回)
interface OssCredentialsConfig {
// 必需的凭证
accessKeyId: string // 必填:访问密钥 ID
accessKeySecret: string // 必填:访问密钥
securityToken: string // 必填:安全令牌(STS)
// OSS 配置
bucket?: string // 可选:OSS bucket 名称
region?: string // 可选:OSS 区域
endpoint?: string // 可选:自定义 OSS endpoint(支持 CDN 域名)
cname?: boolean // 可选:是否为 CNAME 域名
secure?: boolean // 可选:是否使用 HTTPS
timeout?: number // 可选:请求超时时间(毫秒)
// 上传相关配置
path?: string // 可选:上传路径/对象键
headers?: Record<string, string> // 可选:自定义 headers(如 OSS 回调)
}
// uploadFile 的参数接口
interface UploadFileOptions {
file: File // 必填:要上传的文件
getCredentials: () => Promise<OssCredentialsConfig | null> // 必填:获取凭证和配置的函数
onProgress?: (p: { percent: number }) => void // 可选:进度回调
// 以下参数可以覆盖 getCredentials 返回的配置
path?: string // 可选:上传路径(会覆盖 getCredentials 返回的 path)
headers?: Record<string, string> // 可选:自定义 headers(会覆盖 getCredentials 返回的 headers)
}
// generateSignedUrl 的参数接口
interface GenerateSignedUrlOptions {
objectKey: string // 必填:OSS 中的对象键
getCredentials: () => Promise<OssCredentialsConfig | null> // 必填:获取凭证和配置的函数
expires?: number // 可选:URL 过期时间(秒),默认 3600
}更新日志
v3.0.0 (2025-12-16)
- 🔥 重大变更:重新设计 API,
getCredentials现在返回完整的 OSS 配置 - ✨ 新增
OssCredentialsConfig接口,支持在getCredentials中返回所有 OSS 配置 - ✨ 新增参数优先级机制:
uploadFile的直接参数可覆盖getCredentials返回的配置 - ✨ 优化
generateSignedUrlAPI,改为对象参数形式 - 📝 完善文档和使用示例,更符合实际使用场景
- 🎯 更好地支持后端统一返回 OSS 配置的场景
v2.1.0 (2025-12-16)
- ✨ 新增 OSS 回调 headers 支持
- ✨ 新增智能路径判断(支持后端返回 object_key)
- ✨ 支持 path 参数带文件后缀时直接使用
- 📝 完善 OssCredentials 接口文档
v2.0.0 (2025-12-16)
- ✨ 新增多配置 client 缓存机制
- 🐛 修复自定义 endpoint 不生效的问题
- ✨ 新增
clearExpiredClients()和clearAllClients()方法 - 📝 完善文档和使用示例
- ⚡️ 优化缓存管理策略
v1.0.0
- 🎉 初始版本发布
- ✨ 支持文件上传和进度跟踪
- ✨ 支持签名 URL 生成
- ✨ 自动凭证管理和刷新