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 axios from 'axios';
|
||||||
|
import { buildOssDirPath } from '@/utils/env';
|
||||||
|
|
||||||
const API_BASE = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api/v1';
|
const API_BASE = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api/v1';
|
||||||
|
|
||||||
@ -16,50 +17,114 @@ export interface DeleteResult {
|
|||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OSS 直传 Token 响应
|
||||||
|
*/
|
||||||
|
export interface OssToken {
|
||||||
|
accessid: string;
|
||||||
|
policy: string;
|
||||||
|
signature: string;
|
||||||
|
dir: string;
|
||||||
|
host: string;
|
||||||
|
key: string;
|
||||||
|
expire: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件上传 API
|
* 文件上传 API
|
||||||
*/
|
*/
|
||||||
export const fileApi = {
|
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 (
|
uploadFile: async (
|
||||||
file: File,
|
file: File,
|
||||||
type: 'cover' | 'ebook' | 'audio' | 'video' | 'ppt' | 'poster' | 'document' | 'other',
|
type: 'cover' | 'ebook' | 'audio' | 'video' | 'ppt' | 'poster' | 'document' | 'other',
|
||||||
courseId?: number,
|
_courseId?: number,
|
||||||
): Promise<UploadResult> => {
|
): Promise<UploadResult> => {
|
||||||
const formData = new FormData();
|
// 1. 获取 OSS 直传 Token(自动添加环境前缀)
|
||||||
formData.append('file', file);
|
const token = await getOssToken(file.name, type);
|
||||||
formData.append('type', type);
|
|
||||||
if (courseId) {
|
|
||||||
formData.append('courseId', courseId.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await axios.post<UploadResult>(
|
// 2. 上传到 OSS
|
||||||
`${API_BASE}/files/upload`,
|
await uploadToOss(file, token);
|
||||||
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}%`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
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> => {
|
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 },
|
data: { filePath },
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
@ -132,3 +197,5 @@ export const validateFileType = (
|
|||||||
export const uploadFile = fileApi.uploadFile;
|
export const uploadFile = fileApi.uploadFile;
|
||||||
export const deleteFile = fileApi.deleteFile;
|
export const deleteFile = fileApi.deleteFile;
|
||||||
export const getFileUrl = fileApi.getFileUrl;
|
export const getFileUrl = fileApi.getFileUrl;
|
||||||
|
export const getOssToken = fileApi.getOssToken;
|
||||||
|
export const uploadToOss = fileApi.uploadToOss;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user