官方文档BrowserJS_SDK参考_对象存储服务 OBS-华为云
技术架构概览
- 核心依赖: 使用华为云官方SDK
esdk-obs-browserjs实现对象存储功能 - 异步配置获取:通过getOBSConfig函数并行请求AK、SK、bucketName和endpoint等必要配置项
- 动态配置更新:确保在上传前配置已正确加载
关键功能实现
1. 动态客户端初始化
- 通过 createObsClient 工厂函数动态创建OBS客户端实例
- 支持AK/SK认证信息验证,防止无效配置导致上传失败
- 超时时间设置为1秒(timeout: 1000)
2. 客户端实例管理
- 单例模式:通过全局变量obsClientInstance实现ObsClient单例管理
- 懒加载机制:采用createObsClient工厂函数延迟创建客户端实例
- 容错处理:在上传前检查并重新初始化客户端实例
3. 文件命名与校验策略
- 实现 getSimpleFileNameWithoutExt 函数提取文件名(不含扩展名)
- 文件名长度限制为20个字符,超过则抛出错误提示
- 采用
原始文件名_随机UUID.扩展名的命名规则,避免文件冲突
核心特性
1. 进度监控支持
- 提供可选的进度回调参数 progressCallback
- 内部实现 internalProgressCallback 处理上传进度数据
- 支持传输量、总大小和耗时统计
2. 统一响应格式
- 成功时返回
{ status: 200, data: { url: string } } - 失败时返回
{ status: 500, data: { message: string } } - 文件访问URL格式:
${obsEndpoint.value}/${obsBucket.value}/${fileName}
代码片段
//引入华为云sdk
import ObsClient from "esdk-obs-browserjs";
import { generateUUID } from "@/utils";
import { reactive } from "vue";
import { getConfigKey } from "@/api/login/login";
let obsClientInstance = null;
const obsConfig = reactive({
ak: null,
sk: null,
bucketName: null,
endpoint: null
});
// 同步获取OBS配置
export const getOBSConfig = async () => {
try {
// 并行请求所有配置项
const [skRes, endpointRes, bucketNameRes, akRes] = await Promise.all([
getConfigKey("sys.obs.secret.key"),
getConfigKey("sys.obs.endpoint"),
getConfigKey("sys.obs.bucket.name"),
getConfigKey("sys.obs.access.key")
]);
obsConfig.sk = skRes.data;
obsConfig.endpoint = endpointRes.data;
obsConfig.bucketName = bucketNameRes.data;
obsConfig.ak = akRes.data;
console.log('OBS配置已获取:', obsConfig);
} catch (error) {
console.error('获取OBS配置失败:', error);
throw new Error('获取OBS配置失败');
}
};
// 创建ObsClient的工厂函数
const createObsClient = () => {
if (!obsConfig.ak || !obsConfig.sk) {
console.log('OBS配置未完全加载,无法创建客户端');
return null;
}
let a = new ObsClient({
access_key_id: obsConfig.ak,
secret_access_key: obsConfig.sk,
server: obsConfig.endpoint,
timeout: 10000, // 增加超时时间
});
console.log('obsClientInstance',a)
return a
};
// 初始化或更新ObsClient
const initOrUpdateObsClient = () => {
obsClientInstance = createObsClient();
console.log('obsClientInstance',obsClientInstance)
};
export const obsUploadFile = async (option,progressCallback?: (transferredAmount: number, totalAmount: number, totalSeconds: number) => void) => {
// 检查配置是否已加载
if (!obsConfig.ak || !obsConfig.sk || !obsConfig.bucketName || !obsConfig.endpoint) {
console.log('OBS配置未加载,正在获取...');
await getOBSConfig();
// 确保配置已加载后再初始化客户端
initOrUpdateObsClient();
if (!obsClientInstance) {
uni.showToast({
title: '上传配置未加载,请重试',
icon: 'error',
});
throw new Error('OBS配置未初始化,请刷新重试或重新上传文件');
}
}
// 如果客户端实例仍然为空,尝试重新创建
if (!obsClientInstance) {
console.log('开始初始化配置')
initOrUpdateObsClient();
if (!obsClientInstance) {
uni.showToast({
title: '上传配置未加载,请重试',
icon: 'error',
});
throw new Error('OBS客户端初始化失败');
}
}
const file = option.file; //上传的文件
const extensionName = option.name.substr(option.name.indexOf(".")); // 文件扩展名
const fristName = getSimpleFileNameWithoutExt(option.name);
console.log('file',file)
if(fristName.length > 20){
throw new Error('文件名过长,请重新上传文件');
}
const fileName = getSimpleFileNameWithoutExt(option.name) + '_' + generateUUID() + extensionName; //自定义文件名
let blob;
// 根据不同的源类型进行转换
if (typeof file === 'string') {
if (file.startsWith('data:')) {
// 处理Base64字符串
blob = base64ToBlob(file);
} else if (file.startsWith('blob:')) {
// 处理Blob URL
blob = await blobUrlToBlob(file);
} else {
throw new Error('不支持的文件源格式');
}
} else if (file instanceof Blob || file instanceof File) {
// 已经是Blob或File对象,直接使用
blob = file;
} else {
throw new Error('文件源必须是Base64字符串、Blob URL、Blob或File对象');
}
if (!blob) {
throw new Error('转换为Blob对象失败');
}
const putFileObj = async () => {
try {
const internalProgressCallback = (transferredAmount: number, totalAmount: number, totalSeconds: number) => {
// 如果提供了外部回调函数,则执行它
if (progressCallback && typeof progressCallback === 'function') {
progressCallback(transferredAmount, totalAmount, totalSeconds);
}
};
const result = await obsClientInstance.putObject({
Bucket: obsConfig.bucketName, // 使用本地配置
Key: fileName, //文件名
SourceFile: blob, //流文件 其必须是File对象或者Blob对象
ProgressCallback: internalProgressCallback
});
if (result.CommonMsg.Status == 200) {
return {
data: {
url: `${obsConfig.endpoint}/${obsConfig.bucketName}/${fileName}`, // 使用本地配置
},
status: 200,
};
} else {
return {
status: 500,
data: {
message: '上传失败,请重试',
},
};
}
} catch (e) {
console.log('catch', e);
return {
status: 500,
data: {
message: '上传失败,请重试',
},
};
}
};
return putFileObj();
};
// 1. 将Base64转换为Blob对象
function base64ToBlob(base64String) {
// 分离Base64数据和类型信息
const arr = base64String.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}
// 2. 通过Blob URL获取Blob对象
async function blobUrlToBlob(blobUrl) {
try {
const response = await fetch(blobUrl);
const blob = await response.blob();
return blob;
} catch (error) {
console.error('获取Blob对象失败:', error);
return null;
}
}
function getSimpleFileNameWithoutExt(fileName) {
if (!fileName) return '';
const lastDotIndex = fileName.lastIndexOf('.');
return lastDotIndex > 0 ? fileName.substring(0, lastDotIndex) : fileName;
}
使用参考
el-upload覆盖默认上传
import { obsUploadFile } from "@/utils/uploadObsFile.js"
<el-upload ref="uploadRef"
class="upload-demo"
'http-request': uploadAction,
headers: item.uploadHeaders || {
Authorization: 'Bearer ' + getToken(),
},
:auto-upload="false" >
<template #trigger>
<el-button type="primary">select file</el-button>
</template>
<el-button class="ml-3" type="success" @click="submitUpload"> upload to server </el-button> <template #tip>
<div class="el-upload__tip"> jpg/png files with a size less than 500kb </div>
</template>
</el-upload>
const uploadAction = async (option) =>{
const result = await obsUploadFile(option,(transferredAmount, totalAmount, totalSeconds) => {
// 计算上传速率和进度
const rate = transferredAmount * 1.0 / totalSeconds / 1024 / 1024;
const percentage = transferredAmount * 100.0 / totalAmount;
loadingProgressText.value = `上传进度: ${percentage.toFixed(2)}% | 上传速率: ${rate.toFixed(2)} M/s`
// 在这里可以更新UI显示进度
console.log(`上传进度: ${percentage.toFixed(2)}%`);
console.log(`上传速率: ${rate.toFixed(2)} M/s`);
// 更新进度条
// updateProgressBar(percentage);
});
if(result.status != 200){
ElMessage.error('上传失败')
}
return result;
}