This commit is contained in:
104
src/api/modules/storage.ts
Normal file
104
src/api/modules/storage.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 对象存储相关 API
|
||||
*/
|
||||
|
||||
import { BaseService } from '../BaseService'
|
||||
import type { BaseResponse } from '@/types/api'
|
||||
|
||||
/**
|
||||
* 文件用途枚举
|
||||
*/
|
||||
export type FilePurpose = 'iot_import' | 'export' | 'attachment'
|
||||
|
||||
/**
|
||||
* 获取上传 URL 请求参数
|
||||
*/
|
||||
export interface GetUploadUrlRequest {
|
||||
/** 文件名(如:cards.csv) */
|
||||
file_name: string
|
||||
/** 文件 MIME 类型(如:text/csv),留空则自动推断 */
|
||||
content_type?: string
|
||||
/** 文件用途 (iot_import:ICCID导入, export:数据导出, attachment:附件) */
|
||||
purpose: FilePurpose
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上传 URL 响应
|
||||
*/
|
||||
export interface GetUploadUrlResponse {
|
||||
/** 预签名上传 URL,使用 PUT 方法上传文件 */
|
||||
upload_url: string
|
||||
/** 文件路径标识,上传成功后用于调用业务接口 */
|
||||
file_key: string
|
||||
/** URL 有效期(秒) */
|
||||
expires_in: number
|
||||
}
|
||||
|
||||
export class StorageService extends BaseService {
|
||||
/**
|
||||
* 获取文件上传预签名 URL
|
||||
*
|
||||
* ## 完整上传流程
|
||||
* 1. 调用本接口获取预签名 URL 和 file_key
|
||||
* 2. 使用预签名 URL 上传文件(发起 PUT 请求直接上传到对象存储)
|
||||
* 3. 使用 file_key 调用相关业务接口
|
||||
*
|
||||
* @param data 请求参数
|
||||
*/
|
||||
static getUploadUrl(data: GetUploadUrlRequest): Promise<BaseResponse<GetUploadUrlResponse>> {
|
||||
return this.post<BaseResponse<GetUploadUrlResponse>>('/api/admin/storage/upload-url', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用预签名 URL 上传文件到对象存储
|
||||
*
|
||||
* 注意事项:
|
||||
* - 预签名 URL 有效期 15 分钟,请及时使用
|
||||
* - 上传时 Content-Type 需与请求时一致
|
||||
* - file_key 在上传成功后永久有效
|
||||
*
|
||||
* @param uploadUrl 预签名 URL
|
||||
* @param file 文件
|
||||
* @param contentType 文件类型(需与 getUploadUrl 请求时保持一致)
|
||||
*/
|
||||
static async uploadFile(uploadUrl: string, file: File, contentType?: string): Promise<void> {
|
||||
try {
|
||||
// 在开发环境下,使用代理路径避免 CORS 问题
|
||||
let finalUrl = uploadUrl
|
||||
if (import.meta.env.DEV) {
|
||||
// 将对象存储的域名替换为代理路径
|
||||
// 例如:http://obs-helf.cucloud.cn/cmp/... -> /obs-proxy/cmp/...
|
||||
finalUrl = uploadUrl.replace(/^https?:\/\/obs-helf\.cucloud\.cn/, '/obs-proxy')
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {}
|
||||
|
||||
// 只有在明确指定 contentType 时才设置,否则让浏览器自动处理
|
||||
if (contentType) {
|
||||
headers['Content-Type'] = contentType
|
||||
}
|
||||
|
||||
const response = await fetch(finalUrl, {
|
||||
method: 'PUT',
|
||||
body: file,
|
||||
headers,
|
||||
mode: 'cors' // 明确指定 CORS 模式
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text().catch(() => response.statusText)
|
||||
throw new Error(`上传文件失败 (${response.status}): ${errorText}`)
|
||||
}
|
||||
} catch (error: any) {
|
||||
// 增强错误信息
|
||||
if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) {
|
||||
throw new Error(
|
||||
'CORS 错误: 无法上传文件到对象存储。' +
|
||||
'这通常是因为对象存储服务器未正确配置 CORS 策略。' +
|
||||
'请联系后端开发人员检查对象存储的 CORS 配置。'
|
||||
)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user