vue3+antd实现华为云OBS文件拖拽上传详解

1、文件上传核心流程

  1. 选择文件​:用户通过拖拽或点击选择文件
  2. 手动触发上传​:点击"确定"按钮后开始上传(阻止自动上传)
  3. ​获取上传凭证​:从后端获取华为云OBS的上传配置
  4. 构建表单数据​:按照华为云要求组织表单数据
  5. 执行上传​:发送POST请求到华为云OBS
  6. 返回结果​:处理上传结果并返回文件信息

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遵循特定格式:

https://[bucket名称].[区域端点]

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>
相关推荐
Pu_Nine_95 小时前
Axios 实例配置指南
前端·笔记·typescript·axios
THMAIL5 小时前
攻克 Java 分布式难题:并发模型优化与分布式事务处理实战指南
java·开发语言·分布式
红尘客栈25 小时前
Shell 编程入门指南:从基础到实战2
前端·chrome
完美世界的一天5 小时前
Golang 面试题「中级」
开发语言·后端·面试·golang
前端大卫6 小时前
Vue 和 React 受控组件的区别!
前端
Hy行者勇哥7 小时前
前端代码结构详解
前端
Morpheon7 小时前
Intro to R Programming - Lesson 4 (Graphs)
开发语言·r语言
代码AI弗森7 小时前
使用 JavaScript 构建 RAG(检索增强生成)库:原理与实现
开发语言·javascript·ecmascript
Lhy@@7 小时前
Axios 整理常用形式及涉及的参数
javascript