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中创建业务专属目录,实现文件按业务线分类存储
- 权限隔离:不同业务文件可以设置不同的访问权限
- 检索过滤:便于后续按业务维度查询和管理文件
javascript
// 上传到路径: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的直传需要以下安全凭证:(接口返回)
javascript
{
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遵循特定格式:
4.为什么需要获取当前日期格式的目录?
getCurrentDateDir()
是一个用于 按日期自动生成文件存储目录 的工具函数。
自动组织文件存储结构
将文件按日期分目录存储,最终路径如:
kafile/业务名/2023/8/30/文件名.ext
4、代码实现
javascript
<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;
// 构建上传URL
const 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);// 访问密钥ID
formData.append('signature', signature);// 签名
formData.append('file', file);// 文件本身
// 5. 执行上传(这里是直传华为云OBS,注意不是后端接口)
await axios.post(url, formData);
// 6. 返回结果(这里是华为云OBS返回的参数)
return {
fileUrl: `${url}/${fileKey}`,// 完整文件URL
fileName,// 生成的文件名
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、组件使用
javascript
<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>