feat: 更新应用版本和集成腾讯云 COS 上传功能
- 将应用版本更新至 1.0.2,修改相关配置文件 - 集成腾讯云 COS 上传功能,新增相关服务和钩子 - 更新 AI 体态评估页面,支持照片上传和评估结果展示 - 添加雷达图组件以展示评估结果 - 更新样式以适应新功能的展示和交互 - 修改登录页面背景效果,提升用户体验
This commit is contained in:
107
services/cos.ts
Normal file
107
services/cos.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { COS_BUCKET, COS_REGION } from '@/constants/Cos';
|
||||
import { api } from '@/services/api';
|
||||
|
||||
type CosCredential = {
|
||||
credentials: {
|
||||
tmpSecretId: string;
|
||||
tmpSecretKey: string;
|
||||
sessionToken: string;
|
||||
};
|
||||
startTime?: number;
|
||||
expiredTime?: number;
|
||||
};
|
||||
|
||||
type UploadOptions = {
|
||||
key: string;
|
||||
body: any;
|
||||
contentType?: string;
|
||||
onProgress?: (progress: { percent: number }) => void;
|
||||
signal?: AbortSignal;
|
||||
};
|
||||
|
||||
let CosSdk: any | null = null;
|
||||
|
||||
async function ensureCosSdk(): Promise<any> {
|
||||
if (CosSdk) return CosSdk;
|
||||
// 动态导入避免影响首屏
|
||||
const mod = await import('cos-js-sdk-v5');
|
||||
CosSdk = mod.default ?? mod;
|
||||
return CosSdk;
|
||||
}
|
||||
|
||||
async function fetchCredential(): Promise<CosCredential> {
|
||||
return await api.get<CosCredential>('/users/cos-token');
|
||||
}
|
||||
|
||||
export async function uploadToCos(options: UploadOptions): Promise<{ key: string; etag?: string; headers?: Record<string, string> }> {
|
||||
const { key, body, contentType, onProgress, signal } = options;
|
||||
if (!COS_BUCKET || !COS_REGION) {
|
||||
throw new Error('未配置 COS_BUCKET / COS_REGION');
|
||||
}
|
||||
const COS = await ensureCosSdk();
|
||||
const cred = await fetchCredential();
|
||||
|
||||
const controller = new AbortController();
|
||||
if (signal) {
|
||||
if (signal.aborted) controller.abort();
|
||||
signal.addEventListener('abort', () => controller.abort(), { once: true });
|
||||
}
|
||||
|
||||
return await new Promise((resolve, reject) => {
|
||||
const cos = new COS({
|
||||
getAuthorization: (_opts: any, cb: any) => {
|
||||
cb({
|
||||
TmpSecretId: cred.credentials.tmpSecretId,
|
||||
TmpSecretKey: cred.credentials.tmpSecretKey,
|
||||
SecurityToken: cred.credentials.sessionToken,
|
||||
StartTime: cred.startTime,
|
||||
ExpiredTime: cred.expiredTime,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const task = cos.putObject(
|
||||
{
|
||||
Bucket: COS_BUCKET,
|
||||
Region: COS_REGION,
|
||||
Key: key,
|
||||
Body: body,
|
||||
ContentType: contentType,
|
||||
onProgress: (progressData: any) => {
|
||||
if (onProgress) {
|
||||
const percent = progressData && progressData.percent ? progressData.percent : 0;
|
||||
onProgress({ percent });
|
||||
}
|
||||
},
|
||||
},
|
||||
(err: any, data: any) => {
|
||||
if (err) return reject(err);
|
||||
resolve({ key, etag: data && data.ETag, headers: data && data.headers });
|
||||
}
|
||||
);
|
||||
|
||||
controller.signal.addEventListener('abort', () => {
|
||||
try { task && task.cancel && task.cancel(); } catch { }
|
||||
reject(new DOMException('Aborted', 'AbortError'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function uploadWithRetry(options: UploadOptions & { maxRetries?: number; backoffMs?: number }): Promise<{ key: string; etag?: string }> {
|
||||
const { maxRetries = 2, backoffMs = 800, ...rest } = options;
|
||||
let attempt = 0;
|
||||
// 简单指数退避
|
||||
while (true) {
|
||||
try {
|
||||
return await uploadToCos(rest);
|
||||
} catch (e: any) {
|
||||
if (rest.signal?.aborted) throw e;
|
||||
if (attempt >= maxRetries) throw e;
|
||||
const wait = backoffMs * Math.pow(2, attempt);
|
||||
await new Promise(r => setTimeout(r, wait));
|
||||
attempt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user