前端直接上传文件到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