feat: add reward code
This commit is contained in:
202
src/lib/uploadService.ts
Normal file
202
src/lib/uploadService.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
import imageCompression from 'browser-image-compression';
|
||||
import SparkMD5 from 'spark-md5';
|
||||
import http from './api';
|
||||
import { OssSignatureData, OssSignatureRequest, RecognitionTaskData, RecognitionTaskRequest, RecognitionResultData, TaskListData } from '../types/api';
|
||||
|
||||
/**
|
||||
* 文件上传服务
|
||||
*/
|
||||
export const uploadService = {
|
||||
/**
|
||||
* 处理并上传文件
|
||||
*/
|
||||
async uploadFile(file: File): Promise<OssSignatureData> {
|
||||
// ... (保留之前的 uploadFile 逻辑)
|
||||
// 1. 验证文件
|
||||
if (!this.validateFile(file)) {
|
||||
throw new Error('不支持的文件类型或文件大小超过限制');
|
||||
}
|
||||
|
||||
// 2. 计算文件 Hash (使用原始文件,确保去重逻辑基于用户源文件)
|
||||
// 压缩后的文件 Hash 可能会因为压缩过程的微小差异(如时间戳元数据)而不同
|
||||
const fileHash = await this.calculateMD5(file);
|
||||
|
||||
// 3. 压缩图片 (如果是图片且超过一定大小)
|
||||
let processedFile = file;
|
||||
if (file.type.startsWith('image/')) {
|
||||
try {
|
||||
processedFile = await this.compressImage(file);
|
||||
} catch (error) {
|
||||
console.warn('图片压缩失败,尝试使用原文件上传', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 获取上传签名
|
||||
const signatureData = await this.getOssSignature({
|
||||
file_hash: fileHash,
|
||||
file_name: processedFile.name, // 使用处理后的文件名(通常相同)
|
||||
});
|
||||
|
||||
// 5. 如果需要上传 (repeat = false),则上传到 OSS (上传处理后的文件)
|
||||
if (!signatureData.data?.repeat && signatureData.data?.sign_url) {
|
||||
await this.uploadToOss(signatureData.data.sign_url, processedFile);
|
||||
}
|
||||
|
||||
if (!signatureData.data) {
|
||||
throw new Error('获取上传签名失败');
|
||||
}
|
||||
|
||||
return signatureData.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建公式识别任务
|
||||
*/
|
||||
async createRecognitionTask(path: string, fileHash: string, fileName: string): Promise<RecognitionTaskData> {
|
||||
const data: RecognitionTaskRequest = {
|
||||
file_url: path,
|
||||
file_hash: fileHash,
|
||||
file_name: fileName,
|
||||
task_type: 'FORMULA'
|
||||
};
|
||||
return http.post<RecognitionTaskData>('/formula/recognition', data).then(res => {
|
||||
if (!res.data) throw new Error('创建任务失败: 无返回数据');
|
||||
return res.data;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取任务结果
|
||||
*/
|
||||
async getTaskResult(taskNo: string): Promise<RecognitionResultData> {
|
||||
return http.get<RecognitionResultData>(`/formula/recognition/${taskNo}`).then(res => {
|
||||
if (!res.data) throw new Error('获取结果失败: 无返回数据');
|
||||
return res.data;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取任务历史记录列表
|
||||
*/
|
||||
async getTaskList(taskType: 'FORMULA' = 'FORMULA', page: number = 1, pageSize: number = 5): Promise<TaskListData> {
|
||||
return http.get<TaskListData>(`/task/list?task_type=${taskType}&page=${page}&page_size=${pageSize}`).then(res => {
|
||||
if (!res.data) throw new Error('获取历史记录失败: 无返回数据');
|
||||
return res.data;
|
||||
});
|
||||
},
|
||||
|
||||
// ... (保留其他 helper 方法: validateFile, compressImage, calculateMD5, getOssSignature, uploadToOss)
|
||||
/**
|
||||
* 验证文件
|
||||
*/
|
||||
validateFile(file: File): boolean {
|
||||
const validTypes = ['image/jpeg', 'image/png', 'image/jpg'];
|
||||
// 检查类型 (虽然 input accept 做了限制,但这里再检查一次)
|
||||
// 注意:有些 jpg 文件的 type 可能是 image/jpeg
|
||||
if (!file.type.startsWith('image/')) {
|
||||
// 用户提到支持 jpg, png。
|
||||
// 暂时只允许图片,虽然 UI 代码里有 application/pdf,但用户需求只提到了图片。
|
||||
// 根据用户需求:支持 jpg, png 图片上传
|
||||
if (validTypes.indexOf(file.type) === -1 && !file.name.match(/\.(jpg|jpeg|png)$/i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 5MB 限制 (前端压缩前的检查,压缩后应该更小)
|
||||
// 但如果用户传了个很大的文件,压缩可能也花很久。
|
||||
// 用户说 "文件大小显示5M, 前端进行压缩处理",可能指压缩目标是5M,或者限制原文件5M?
|
||||
// 通常是限制上传大小。如果原文件很大,压缩后小于5M也可以。
|
||||
// 这里暂时不做严格的源文件大小限制,交给压缩处理,或者设置一个合理的上限比如 20MB。
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* 压缩图片
|
||||
*/
|
||||
async compressImage(file: File): Promise<File> {
|
||||
const options = {
|
||||
maxSizeMB: 5, // 限制最大 5MB
|
||||
maxWidthOrHeight: 1920, // 限制最大宽/高,防止过大图片
|
||||
useWebWorker: true,
|
||||
initialQuality: 0.8,
|
||||
};
|
||||
|
||||
try {
|
||||
return await imageCompression(file, options);
|
||||
} catch (error) {
|
||||
console.error('Image compression error:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算文件 MD5
|
||||
*/
|
||||
calculateMD5(file: File): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const blobSlice = File.prototype.slice || (File.prototype as any).mozSlice || (File.prototype as any).webkitSlice;
|
||||
const chunkSize = 2097152; // 2MB
|
||||
const chunks = Math.ceil(file.size / chunkSize);
|
||||
let currentChunk = 0;
|
||||
const spark = new SparkMD5.ArrayBuffer();
|
||||
const fileReader = new FileReader();
|
||||
|
||||
fileReader.onload = function (e) {
|
||||
if (e.target?.result) {
|
||||
spark.append(e.target.result as ArrayBuffer);
|
||||
}
|
||||
currentChunk++;
|
||||
|
||||
if (currentChunk < chunks) {
|
||||
loadNext();
|
||||
} else {
|
||||
resolve(spark.end());
|
||||
}
|
||||
};
|
||||
|
||||
fileReader.onerror = function () {
|
||||
reject('File read failed');
|
||||
};
|
||||
|
||||
function loadNext() {
|
||||
const start = currentChunk * chunkSize;
|
||||
const end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
|
||||
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
|
||||
}
|
||||
|
||||
loadNext();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取 OSS 签名
|
||||
*/
|
||||
async getOssSignature(data: OssSignatureRequest) {
|
||||
return http.post<OssSignatureData>('/oss/signature_url', data);
|
||||
},
|
||||
|
||||
/**
|
||||
* 上传文件到 OSS
|
||||
*/
|
||||
async uploadToOss(url: string, file: File): Promise<void> {
|
||||
try {
|
||||
// OSS 直接 PUT 上传,不需要额外的 headers (除非签名里有要求,通常 Content-Type 需要匹配)
|
||||
// 注意:Content-Type 需要根据文件实际类型设置,或者 application/octet-stream
|
||||
// 这里使用文件类型
|
||||
await fetch(url, {
|
||||
method: 'PUT',
|
||||
body: file,
|
||||
headers: {
|
||||
// 有些 OSS 签名会绑定 Content-Type,如果不一致会报错。
|
||||
// 这里尽量使用 file.type,如果为空则设为 application/octet-stream
|
||||
// 如果签名不限制 Content-Type,则无所谓。
|
||||
// 按照常规 OSS PutObject,建议带上 type
|
||||
'Content-Type': file.type || 'application/octet-stream',
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Upload to OSS failed:', error);
|
||||
throw new Error('文件上传失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user