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:
parent
d72e85f71a
commit
f1bb1447bb
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user