library-picturebook-activity/oss-direct-upload-demo/frontend/file.ts

163 lines
4.1 KiB
TypeScript
Raw Normal View History

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<OssToken> {
// 自动添加环境前缀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<UploadResult> {
// 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,
};
}