vue3+antd实现华为云OBS文件拖拽上传详解
1、文件上传核心流程
- 选择文件:用户通过拖拽或点击选择文件
- 手动触发上传:点击"确定"按钮后开始上传(阻止自动上传)
- 获取上传凭证:从后端获取华为云OBS的上传配置
- 构建表单数据:按照华为云要求组织表单数据
- 执行上传:发送POST请求到华为云OBS
- 返回结果:处理上传结果并返回文件信息
2、关键参数说明
参数 | 说明 | 来源 |
---|---|---|
file | 要上传的文件对象 | 用户选择 |
businessName | 业务分类名称 | 组件props传入 |
privateMode | 是否私有模式 | 默认为true |
sourceType | 数据源类型('ka'/'oin') | 组件props传入 |
bucket | OBS桶名称 | 后端接口返回 |
endpoint | OBS服务端点 | 后端接口返回 |
policy | 上传策略 | 后端接口返回 |
signature | 签名 | 后端接口返回 |
accessId | 访问密钥ID | 后端接口返回 |
3、部分字段解释
1、业务名称 (businessName)
作用:
- 文件分类存储:用于在华为云OBS中创建业务专属目录,实现文件按业务线分类存储
- 权限隔离:不同业务文件可以设置不同的访问权限
- 检索过滤:便于后续按业务维度查询和管理文件
// 上传到路径:kafile/订单业务/2023/08/30/时间戳.jpg
pathParts = ['kafile', '订单业务', '2023/08/30', '1693388800000.jpg']// 如果没有业务名称则上传到:kafile/2023/08/30/时间戳.jpg
pathParts = ['kafile', '2023/08/30', '1693388800000.jpg']
2、数据源类型 (sourceType)
作用:
- 多账户切换:对接不同华为云账号或服务端点
- 差异化处理:不同来源的文件可能需要不同的上传策略
- 扩展性:预留的枚举字段方便后续接入新存储系统
3、为什么需要这些参数?
1.主要用来区分调不同服务的接口来获取华为云OBS的上传凭证
- 同一系统可能服务多个业务线(如大客户、oin等)
- 不同业务对文件存储的要求不同(如订单需要长期保存,临时文件只需保留7天)
2.为什么需要调用后端接口获取华为云OBS的上传凭证?
华为云OBS的直传需要以下安全凭证:(接口返回)
{bucket: "your-bucket-name", // 存储桶名称endpoint: "obs.cn-south-1.myhuaweicloud.com", // 区域端点policy: "eyJleHBpcmF0aW9uIjo...", // 经过Base64编码的上传策略signature: "Dq1YpZxlQODhQwM...", // 对policy的签名accessId: "AKIDEXAMPLE" // 临时访问密钥ID
}
安全原因:直接在前端存储华为云AK/SK是极度危险的,必须通过后端中转。
3.为什么还需要拼接上传url?
在华为云OBS(对象存储服务)中,拼接上传URL (https://${bucket}.${endpoint}
) 是华为云OBS API的强制要求,这种设计涉及到底层的访问机制和安全策略。
华为云OBS的访问URL遵循特定格式:
https://[bucket名称].[区域端点]
4.为什么需要获取当前日期格式的目录?
getCurrentDateDir()
是一个用于 按日期自动生成文件存储目录 的工具函数。
自动组织文件存储结构
将文件按日期分目录存储,最终路径如:
kafile/业务名/2023/8/30/文件名.ext
4、代码实现
<template><a-upload-dragger:max-count="maxCount"//最大上传文件数:before-upload="beforeUpload"//上传前的处理函数v-model:fileList="fileList"//双向绑定文件列表数据 ><p>点击或拖拽文件上传</p></a-upload-dragger><a-button type="link" @click="handleOk">确定</a-button>
</template><script setup>
import { ref } from 'vue';
import { message } from 'ant-design-vue';
import dayjs from 'dayjs';const props = defineProps({maxCount: Number,// 最大上传文件数量businessName: String,// 业务名称sourceType: { type: String, default: 'ka' }// 数据源类型,默认'ka'
});const emits = defineEmits(['handleUpload']);const fileList = ref([]);// 阻止自动上传的函数,返回false表示阻止默认上传行为
const beforeUpload = () => false;// 处理确定按钮点击
const handleOk = async () => {if (!fileList.value.length) {// 检查是否有文件被选中message.warning('请先选择文件');return;}try {// 获取第一个文件的原始文件对象const file = fileList.value[0].originFileObj;// 调用上传函数并等待结果const result = await uploadToHuaweiOBS({file,// 文件对象businessName: props.businessName,// 业务名称sourceType: props.sourceType// 数据源类型});emits('handleUpload', {bucketName: result.bucketName,// 存储桶名称oriFileName: result.originalName,// 原始文件名fileName: result.fileNameWithoutDir,// 不含目录的文件名file,// 文件对象dir: 'kafile'// 存储目录});message.success('文件上传成功');} catch (error) {message.error(`上传失败: ${error.message}`);}
};// 华为云OBS上传函数
const uploadToHuaweiOBS = async ({ file, businessName = '', sourceType = 'ka' }) => {// 1. 调用后端接口获取华为云OBS的上传凭证const authData = await getUploadAuth(sourceType);// 2. 准备上传参数const { bucket, endpoint, policy, signature, accessId } = authData;// 构建上传URLconst url = `https://${bucket}.${endpoint}`;// 3. 构建文件路径const fileDir = getCurrentDateDir(); // 获取当前日期格式的目录const fileName = generateFileName(file.name); // 生成新文件名const folder = 'kafile'; // 设置存储目录// 构建完整路径数组const pathParts = [folder, fileDir, fileName];// 如果有业务名称,插入到路径中if (businessName) pathParts.splice(1, 0, businessName);// 拼接完整文件路径const fileKey = pathParts.join('/');// 4. 构建表单数据用来做文件上传const formData = new FormData();// 按照华为云要求的顺序添加字段formData.append('key', fileKey);// 文件路径formData.append('policy', policy);// 上传策略formData.append('AccessKeyId', accessId);// 访问密钥IDformData.append('signature', signature);// 签名formData.append('file', file);// 文件本身// 5. 执行上传(这里是直传华为云OBS,注意不是后端接口)await axios.post(url, formData);// 6. 返回结果(这里是华为云OBS返回的参数)return {fileUrl: `${url}/${fileKey}`,// 完整文件URLfileName,// 生成的文件名bucketName: bucket,// 存储桶名称originalName: file.name,// 原始文件名fileNameWithoutDir: pathParts.slice(1).join('/'),// 不含根目录的路径relativePath: fileKey,// 相对路径businessName// 业务名称};
};// 辅助函数
// 如需补零格式(推荐):使用 'YYYY/MM/DD'
const getCurrentDateDir= () => {return dayjs().format('YYYY/MM/DD'); // 输出示例: "2023/08/30"
};// 生成带时间戳的新文件名
const generateFileName = (originalName) => {const ext = originalName.split('.').pop();// 获取文件扩展名return `${Date.now()}.${ext}`;// 时间戳+扩展名
};
</script>
5、组件使用
<template><HuaweiUpload:max-count="3"business-name="customer-service"source-type="oin"@handle-upload="handleUpload"/>
</template><script setup>
import { ref } from 'vue';
import { message } from 'ant-design-vue';const uploading = ref(false);const handleUpload = async (fileInfo) => {uploading.value = true;const formData = new FormData()//创建表单数据对象for (const key in data) {//遍历数据对象并添加到表单formData.append(key, data[key])}try {//调用自己的业务逻辑const { data } = await weeklyImport(formData)if (data.importStatus === '1') {useMessage().success('导入成功')} else if (data.importStatus === '2') {useMessage().error('导入失败,请查看历史记录')}} catch (error) {useMessage().warning('导入失败,请查看历史记录')} finally {//隐藏加载状态uploading.value = false}
};
</script>