feat: 前端 uploadFile 接口改为 OSS 直传

- 修改 uploadFile 方法使用 OSS 直传方式上传
- 新增 getOssToken 和 uploadToOss 方法
- 返回格式保持兼容,filePath 返回 OSS 完整 URL
- 自动添加环境前缀 (dev/test/prod)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
En 2026-03-16 18:11:07 +08:00
parent d72e85f71a
commit f1bb1447bb

View File

@ -1,4 +1,5 @@
import axios from 'axios';
import { buildOssDirPath } from '@/utils/env';
const API_BASE = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api/v1';
@ -16,50 +17,114 @@ export interface DeleteResult {
message: string;
}
/**
* OSS Token
*/
export interface OssToken {
accessid: string;
policy: string;
signature: string;
dir: string;
host: string;
key: string;
expire: number;
}
/**
* API
*/
export const fileApi = {
/**
*
* OSS Token
* dev/test/prod
*
* @param fileName
* @param dir avatar, course/cover
* @returns OSS Token
*/
getOssToken: async (
fileName: string,
dir?: string,
): Promise<OssToken> => {
// 自动添加环境前缀
const fullDir = buildOssDirPath(dir);
const response = await axios.get<{ data: OssToken }>(`${API_BASE}/api/v1/files/oss/token`, {
params: { fileName, dir: fullDir },
});
return response.data.data;
},
/**
* OSS
*/
uploadToOss: async (
file: File,
token: OssToken,
onProgress?: (percent: number) => void,
): Promise<{ url: string }> => {
const formData = new FormData();
// 按照阿里云 OSS PostObject 要求构造表单
formData.append('OSSAccessKeyId', token.accessid);
formData.append('policy', token.policy);
formData.append('signature', token.signature);
formData.append('key', token.key);
formData.append('x-oss-credential', token.accessid);
formData.append('file', file);
await axios.post(token.host, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: (progressEvent) => {
if (progressEvent.total && onProgress) {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
onProgress(percentCompleted);
}
},
});
// 上传成功后返回文件访问 URL
return {
url: `${token.host}/${token.key}`,
};
},
/**
* 使 OSS
*
* @param file
* @param type OSS
* @param _courseId ID -
* @returns OSS URL
*/
uploadFile: async (
file: File,
type: 'cover' | 'ebook' | 'audio' | 'video' | 'ppt' | 'poster' | 'document' | 'other',
courseId?: number,
_courseId?: number,
): Promise<UploadResult> => {
const formData = new FormData();
formData.append('file', file);
formData.append('type', type);
if (courseId) {
formData.append('courseId', courseId.toString());
}
// 1. 获取 OSS 直传 Token自动添加环境前缀
const token = await getOssToken(file.name, type);
const response = await axios.post<UploadResult>(
`${API_BASE}/files/upload`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
// 添加上传进度回调
onUploadProgress: (progressEvent) => {
if (progressEvent.total) {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
console.log(`Upload progress: ${percentCompleted}%`);
}
},
},
);
// 2. 上传到 OSS
await uploadToOss(file, token);
return response.data;
// 3. 返回兼容格式的结果
return {
success: true,
filePath: `${token.host}/${token.key}`,
fileName: file.name,
originalName: file.name,
fileSize: file.size,
mimeType: file.type,
};
},
/**
*
*/
deleteFile: async (filePath: string): Promise<DeleteResult> => {
const response = await axios.delete<DeleteResult>(`${API_BASE}/files/delete`, {
const response = await axios.delete<DeleteResult>(`${API_BASE}/api/v1/files/delete`, {
data: { filePath },
});
return response.data;
@ -132,3 +197,5 @@ export const validateFileType = (
export const uploadFile = fileApi.uploadFile;
export const deleteFile = fileApi.deleteFile;
export const getFileUrl = fileApi.getFileUrl;
export const getOssToken = fileApi.getOssToken;
export const uploadToOss = fileApi.uploadToOss;