122 lines
3.3 KiB
TypeScript
122 lines
3.3 KiB
TypeScript
import type { OssSignData } from '@/api/upload';
|
||
import { fetchOssSign } from '@/api/upload';
|
||
|
||
/**
|
||
* OSS 上传类型
|
||
* 1=证件照 2=赛事封面 3=头像 4=富文本 5=Banner
|
||
*/
|
||
export const OssUploadType = {
|
||
IdCard: '1',
|
||
EventCover: '2',
|
||
Avatar: '3',
|
||
RichText: '4',
|
||
Banner: '5',
|
||
} as const;
|
||
|
||
export type OssUploadTypeValue = (typeof OssUploadType)[keyof typeof OssUploadType];
|
||
|
||
/**
|
||
* 生成唯一文件名:16 位大小写字母+数字随机串.ext
|
||
*/
|
||
export function genFileName(filePath: string, fileName?: string): string {
|
||
let suffix = '';
|
||
if (fileName) {
|
||
const dot = fileName.lastIndexOf('.');
|
||
suffix = dot >= 0 ? fileName.slice(dot).toLowerCase() : '';
|
||
}
|
||
if (!suffix && filePath) {
|
||
const dot = filePath.lastIndexOf('.');
|
||
suffix = dot >= 0 ? filePath.slice(dot).toLowerCase() : '';
|
||
}
|
||
if (!suffix) suffix = '.jpg';
|
||
|
||
const random = Array.from({ length: 16 }, () => {
|
||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||
return chars.charAt(Math.floor(Math.random() * chars.length));
|
||
}).join('');
|
||
|
||
return `${random}${suffix}`;
|
||
}
|
||
|
||
/** 根据签名和文件名构建 OSS 上传 FormData 字段 */
|
||
export function buildOssFormData(sign: OssSignData, fileName: string): Record<string, string> {
|
||
return {
|
||
key: `${sign.dir}${fileName}`,
|
||
policy: sign.policy,
|
||
'x-oss-signature': sign.signature,
|
||
'x-oss-signature-version': 'OSS4-HMAC-SHA256',
|
||
'x-oss-credential': sign.x_oss_credential,
|
||
'x-oss-date': sign.x_oss_date,
|
||
'x-oss-security-token': sign.security_token,
|
||
success_action_status: '200',
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 使用已有签名上传单个文件到 OSS(Web 版,fetch + FormData)
|
||
*/
|
||
export async function uploadFileWithSign(file: File, sign: OssSignData): Promise<string> {
|
||
const fileName = genFileName(file.name, file.name);
|
||
const key = `${sign.dir}${fileName}`;
|
||
|
||
const formData = new FormData();
|
||
Object.entries(buildOssFormData(sign, fileName)).forEach(([k, v]) => {
|
||
formData.append(k, v);
|
||
});
|
||
formData.append('file', file);
|
||
|
||
const res = await fetch(sign.host, {
|
||
method: 'POST',
|
||
body: formData,
|
||
});
|
||
|
||
if (!res.ok) {
|
||
throw new Error(`OSS 上传失败:${res.status} ${res.statusText}`);
|
||
}
|
||
|
||
return `${sign.host}/${key}`;
|
||
}
|
||
|
||
/**
|
||
* 上传单个文件到 OSS
|
||
* @returns 文件访问 URL
|
||
*/
|
||
export async function uploadFile(file: File, type: OssUploadTypeValue): Promise<{ url: string }> {
|
||
const sign = await fetchOssSign(type);
|
||
const url = await uploadFileWithSign(file, sign);
|
||
return { url };
|
||
}
|
||
|
||
/**
|
||
* 逐张上传文件到 OSS(同一批复用同一签名,单张失败不影响后续)
|
||
*/
|
||
export async function uploadFilesOneByOne(
|
||
files: File[],
|
||
type: OssUploadTypeValue,
|
||
options?: {
|
||
onSuccess?: (url: string, index: number) => void | Promise<void>;
|
||
onError?: (error: unknown, index: number) => void;
|
||
},
|
||
): Promise<{ successCount: number; failCount: number }> {
|
||
if (files.length === 0) {
|
||
return { successCount: 0, failCount: 0 };
|
||
}
|
||
|
||
const sign = await fetchOssSign(type);
|
||
let successCount = 0;
|
||
let failCount = 0;
|
||
|
||
for (let i = 0; i < files.length; i++) {
|
||
try {
|
||
const url = await uploadFileWithSign(files[i], sign);
|
||
successCount++;
|
||
await options?.onSuccess?.(url, i);
|
||
} catch (err) {
|
||
failCount++;
|
||
options?.onError?.(err, i);
|
||
}
|
||
}
|
||
|
||
return { successCount, failCount };
|
||
}
|