import axios from "axios"; import { buildOssDirPath } from "./env"; // ==================== 类型定义 ==================== /** * OSS 直传 Token 响应 */ export interface OssToken { /** OSS 访问 ID */ accessid: string; /** Base64 编码的 Policy */ policy: string; /** 签名 */ signature: string; /** 上传目录前缀 */ dir: string; /** OSS 上传地址(https://bucketname.endpoint) */ host: string; /** 完整文件路径(dir + 日期路径 + UUID文件名) */ key: string; /** 过期时间(秒) */ expire: number; } /** * 上传结果 */ export interface UploadResult { success: boolean; filePath: string; fileName: string; fileSize: number; mimeType: string; } // ==================== 配置 ==================== /** * 后端 API 基础地址 * 根据你的项目修改此值 */ const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || "/api"; // ==================== 核心方法 ==================== /** * 获取阿里云 OSS 直传 Token * * @param fileName 文件名(如:图片.jpg) * @param dir 业务目录(如:avatar, course/cover),会自动添加环境前缀 * @returns OSS 直传 Token */ export async function getOssToken( fileName: string, dir?: string, ): Promise { // 自动添加环境前缀(dev/test/prod) const fullDir = buildOssDirPath(dir); const response = await axios.get<{ code: number; data: OssToken }>( `${API_BASE_URL}/v1/files/oss/token`, { params: { fileName, dir: fullDir }, }, ); return response.data.data; } /** * 直接上传文件到阿里云 OSS * * 使用 FormData + POST 方式直传,无需经过后端中转。 * 支持:进度回调、取消上传、超时设置。 * * @param file 要上传的文件 * @param token OSS Token(通过 getOssToken 获取) * @param options 可选配置 * @returns 上传后的文件 URL */ export async function uploadToOss( file: File, token: OssToken, options?: { /** 上传进度回调(0-100) */ onProgress?: (percent: number) => void; /** 取消信号 */ signal?: AbortSignal; /** 超时时间(毫秒),默认 5 分钟 */ timeout?: number; }, ): Promise<{ url: string }> { const opts = options ?? {}; const formData = new FormData(); // 按照阿里云 OSS PostObject 要求构造表单 // 注意:file 必须为最后一个表单域 formData.append("success_action_status", "200"); // 成功时返回 200 formData.append("OSSAccessKeyId", token.accessid); formData.append("policy", token.policy); formData.append("signature", token.signature); formData.append("key", token.key); formData.append("file", file); // file 必须为最后一个表单域 await axios.post(token.host, formData, { headers: { "Content-Type": "multipart/form-data", }, timeout: opts.timeout ?? 1000 * 60 * 5, // 默认 5 分钟 signal: opts.signal, onUploadProgress: (progressEvent) => { if (opts.onProgress) { const percent = progressEvent.progress != null ? progressEvent.progress * 100 : progressEvent.total ? (progressEvent.loaded * 100) / progressEvent.total : 0; opts.onProgress(Math.round(percent)); } }, }); return { url: `${token.host}/${token.key}`, }; } /** * 一站式上传文件(获取 Token + 直传 OSS) * * @param file 要上传的文件 * @param dir 业务目录(如:avatar, course/cover) * @param options 可选配置 * @returns 上传结果 */ export async function uploadFile( file: File, dir?: string, options?: { onProgress?: (percent: number) => void; signal?: AbortSignal; }, ): Promise { // 1. 获取 OSS 直传 Token const token = await getOssToken(file.name, dir); // 2. 直传到 OSS await uploadToOss(file, token, { onProgress: options?.onProgress, signal: options?.signal, }); // 3. 返回结果 return { success: true, filePath: `${token.host}/${token.key}`, fileName: file.name, fileSize: file.size, mimeType: file.type, }; }