前端直接上传文件到oss

import CryptoJS from 'crypto-js';
import { getOssConfig } from '@/api/system/ossConfig';
import { saveOssUploadUrl } from '@/api/system/oss';

// 上传参数类型
export interface UploadParams {
  file: File;
}

// 上传返回结果类型
export interface UploadResult {
  url: string;
  ossId: string;
}

// OSS直传工具类
class OssUploadHelper {
  private accessKeyId: string = '';
  private accessKeySecret: string = '';
  private endpoint: string = '';
  private bucketName: string = '';
  private configKey: string = '';
  private maxSize: number = 500; // 最大500MB
  private timeout: number = 1; // 1小时过期
  private credentialLoaded: boolean = false;

  // 获取上传URL
  private getUploadUrl(): string {
    return `https://${this.bucketName}.${this.endpoint}`;
  }

  // 从接口获取OSS配置
  async loadCredential(): Promise<void> {
    if (this.credentialLoaded) return;

    try {
      // 获取第3个OSS配置
      const res = await getOssConfig(3);
      if (res.code === 200 && res.data) {
        this.accessKeyId = res.data.accessKey || '';
        this.accessKeySecret = res.data.secretKey || '';
        this.endpoint = res.data.endpoint || '';
        this.bucketName = res.data.bucketName || '';
        this.configKey = res.data.configKey || '';
        this.credentialLoaded = true;
      } else {
        console.warn('获取OSS配置失败,使用默认配置');
      }
    } catch (error) {
      console.error('加载OSS凭证失败:', error);
      throw new Error('加载OSS凭证失败');
    }
  }

  // 生成文件名
  private generateFileName(file: File): string {
    const timestamp = Date.now();
    const random = Math.random().toString(36).substring(2, 15);
    const date = new Date().toLocaleDateString('zh-CN').replace(/\//g, '-');
    const ext = file.name.split('.').pop() || 'file';
    return `${date}/${timestamp}_${random}.${ext}`;
  }

  // 生成Policy
  private generatePolicy(): string {
    const expiration = new Date();
    expiration.setHours(expiration.getHours() + this.timeout);

    const policyText = {
      expiration: expiration.toISOString(),
      conditions: [['content-length-range', 0, this.maxSize * 1024 * 1024]]
    };

    const policyString = JSON.stringify(policyText);
    return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(policyString));
  }

  // 生成签名 (HMAC-SHA1)
  private generateSignature(policy: string): string {
    const signature = CryptoJS.HmacSHA1(policy, this.accessKeySecret);
    return CryptoJS.enc.Base64.stringify(signature);
  }

  // 上传文件到OSS
  async upload(params: UploadParams): Promise<UploadResult> {
    const { file } = params;

    // 加载凭证
    await this.loadCredential();

    const key = this.generateFileName(file);
    const policy = this.generatePolicy();
    const signature = this.generateSignature(policy);

    // 构建FormData
    const formData = new FormData();
    formData.append('key', key);
    formData.append('OSSAccessKeyId', this.accessKeyId);
    formData.append('policy', policy);
    formData.append('signature', signature);
    formData.append('success_action_status', '200');
    // 关键:添加file字段,传文件
    formData.append('file', file);

    try {
      const response = await fetch(this.getUploadUrl(), {
        method: 'POST',
        body: formData
      });

      if (response.ok || response.status === 204) {
        const fileUrl = `${this.getUploadUrl()}/${key}`;

        // 提取文件信息
        const fileName = key.split('/').pop() || key;
        const originalName = file.name;
        const fileSuffix = fileName.split('.').pop() || '';

        // 保存上传信息到后端,看具体需求
        // let res = await this.saveUploadInfo(key, fileName, originalName, fileSuffix, fileUrl);
        let res = await await saveOssUploadUrl({
          key,
          fileName,
          originalName,
          fileSuffix,
          url: fileUrl,
          service: this.configKey
        });

        return {
          url: fileUrl,
          ossId: res.data.ossId || ''
        };
      } else {
        const errorText = await response.text();

        // 解析XML错误响应
        const parser = new DOMParser();
        const xmlDoc = parser.parseFromString(errorText, 'text/xml');
        const code = xmlDoc.getElementsByTagName('Code')[0]?.textContent;
        const message = xmlDoc.getElementsByTagName('Message')[0]?.textContent;

        // 针对不同错误给出建议
        if (code === 'AccessDenied') {
          console.error('⚠️ 请检查OSS Bucket的CORS配置:');
          console.error('1. 进入阿里云OSS控制台 -> Bucket -> 权限管理 -> 跨域设置');
          console.error('2. 添加CORS规则:');
          console.error('   - 来源: * (或您的域名)');
          console.error('   - 允许Methods: POST, GET');
          console.error('   - 允许Headers: *');
          console.error('   - 暴露Headers: ETag, x-oss-request-id');
        } else if (code === 'SignatureDoesNotMatch') {
          console.error('⚠️ 签名不匹配,请检查AccessKey Secret是否正确');
        } else if (code === 'InvalidAccessKeyId') {
          console.error('⚠️ AccessKeyId无效,请检查配置');
        }

        throw new Error(`上传失败: ${code || response.status} - ${message || '未知错误'}`);
      }
    } catch (error: any) {
      if (error.message.includes('上传失败')) {
        throw error;
      }
      throw new Error(error.message || '上传文件失败');
    }
  }
}

export default new OssUploadHelper();

使用时:
<template>
    <el-upload
      ref="fileUploadRef"
      multiple
      :http-request="handleHttpRequest"
      :before-upload="handleBeforeUpload"
      :file-list="fileList"
      :limit="limit"
      :accept="fileAccept"
      :on-error="handleUploadError"
      :on-exceed="handleExceed"
      :show-file-list="false"
      drag
      class="upload-file-uploader"
      v-if="!disabled && fileList.length < limit"
    >
      <!-- 其他代码隐藏 -->
</template>

<script setup lang="ts">
import oss from '@/utils/oss';
   <!-- 其他代码隐藏 -->// 自定义上传请求,直接上传到OSS
const handleHttpRequest = async (options: any) => {
  const { file, onSuccess, onError } = options;

  try {
    // 调用OSS上传,直接传入File对象
    const uploadResult = await oss.upload({
      file: file
    });

    // 构建上传结果
    const result = {
      name: file.name,
      url: uploadResult.url,
      ossId: uploadResult.ossId
    };

    fileList.value.push(result);
    onSuccess(result);
    uploadedSuccessfully();
  } catch (error: any) {
    number.value--;
    proxy?.$modal.closeLoading();
    proxy?.$modal.msgError(error.message || '上传文件失败');
    onError(error);
    uploadedSuccessfully();
  }
};

</script>


前端直接上传文件到oss
http://localhost:8090/archives/qian-duan-zhi-jie-shang-chuan-wen-jian-dao-oss
作者
Administrator
发布于
2025年07月19日
许可协议