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 { 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 { 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; 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 }; }