"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExtraFile = void 0; const Feature_1 = require("oak-frontend-base/es/types/Feature"); const upload_1 = require("oak-frontend-base/es/utils/upload"); const extraFile_1 = require("../utils/extraFile"); const assert_1 = require("oak-domain/lib/utils/assert"); const index_frontend_1 = require("../utils/cos/index.frontend"); const lodash_1 = require("oak-domain/lib/utils/lodash"); const uuid_1 = require("oak-domain/lib/utils/uuid"); const Projection_1 = require("../types/Projection"); class ExtraFile extends Feature_1.Feature { cache; application; files; fileUpLoad; uploadIdToTaskId = {}; constructor(cache, application) { super(); this.cache = cache; this.application = application; this.files = {}; const up = new upload_1.Upload(); this.fileUpLoad = up; } registerCos(clazzes) { clazzes.forEach((clazz) => (0, index_frontend_1.registerCos)(clazz)); } addLocalFile(id, file) { (0, assert_1.assert)(!this.files[id]); this.files[id] = { file, state: 'local', }; this.publish(); } removeLocalFiles(ids) { ids.forEach((id) => (0, lodash_1.unset)(this.files, id)); this.publish(); } async upload(id) { /** * 这个函数假设了前台知道后台会产生modi的行为和数据结构,不是很好的设计 */ let modiEntityId = ''; const getExtraFileData = () => { const [extraFile] = this.cache.get('extraFile', { data: { ...Projection_1.extraFileProjection, enableChunkedUpload: 1, chunkInfo: 1, }, filter: { id, }, }); if (extraFile) { return extraFile; } // 否则再去modi中查看 const [modi] = this.cache.get('modi', { data: { id: 1, data: 1, entity: 1, entityId: 1, }, filter: { targetEntity: 'extraFile', action: 'create', filter: { id, }, }, }); modiEntityId = modi.entityId; return modi.data; }; const extraFile = getExtraFileData(); (0, assert_1.assert)(extraFile && extraFile.uploadState === 'uploading'); const item = this.files[id]; (0, assert_1.assert)(item); const { file, state } = item; (0, assert_1.assert)(['local', 'failed'].includes(state)); item.state = 'uploading'; item.percentage = 0; const cos = (0, index_frontend_1.getCos)(extraFile.origin); try { await this.doUpload({ extraFile: extraFile, file: file, cos, }); } catch (err) { item.state = 'failed'; item.percentage = undefined; this.publish(); throw err; } if (!cos.autoInform()) { const informServer = async () => { const operation = { id: await (0, uuid_1.generateNewIdAsync)(), action: 'update', data: { uploadState: 'success', }, filter: { id, }, }; await this.cache.exec('operate', { entity: 'extraFile', operation, }); }; await informServer(); } item.state = 'uploaded'; item.percentage = undefined; this.publish(); } // style 多媒体样式 getUrl(extraFile, style) { if (!extraFile) { return ''; } if (extraFile?.isBridge && extraFile?.extra1) { return this.cache.makeBridgeUrl(extraFile?.extra1); } const { id } = extraFile; if (this.files[id]) { const { file } = this.files[id]; if (typeof file === 'string') { return file; } if (file instanceof File) { return (0, extraFile_1.getFileURL)(file); } (0, assert_1.assert)(false, 'the incoming file is not supported'); } const { origin, extra1 } = extraFile; // if (origin === 'unknown') { // return extra1 || ''; // } if (!origin) { return "unknown"; } const cos = (0, index_frontend_1.getCos)(origin); return cos.composeFileUrl({ application: this.application.getApplication(), extraFile: extraFile, style: style, cache: this.cache, }); } getFileState(id) { if (this.files[id]) { return this.files[id]; } } getFileName(extraFile) { const name = extraFile.filename + (extraFile.extension ? `.${extraFile.extension}` : ''); return name; } formatBytes(size) { return (0, extraFile_1.bytesToSize)(size); } async autoUpload(options) { const { extraFile, file, style, getPercent, chunkOptions } = options; const extraFileId = extraFile.id || (0, uuid_1.generateNewId)(); const applicationId = extraFile.applicationId || this.application.getApplicationId(); const fileSize = extraFile.size; const enabledChunkUpload = chunkOptions && fileSize && chunkOptions.chunkSize && fileSize > chunkOptions.chunkSize; await this.cache.operate('extraFile', { action: 'create', data: Object.assign(extraFile, { id: extraFileId, applicationId, enableChunkedUpload: enabledChunkUpload ? true : false, chunkInfo: enabledChunkUpload ? { chunkSize: chunkOptions.chunkSize, partCount: Math.ceil((extraFile.size || 0) / chunkOptions.chunkSize), } : undefined, }), id: await (0, uuid_1.generateNewIdAsync)(), }); const [newExtraFile] = this.cache.get('extraFile', { data: { ...Projection_1.extraFileProjection, enableChunkedUpload: 1, chunkInfo: 1, }, filter: { id: extraFileId, }, }); const cos = (0, index_frontend_1.getCos)(newExtraFile.origin); try { await this.doUpload({ extraFile: newExtraFile, file: file, cos, getPercent: getPercent, parallelism: chunkOptions?.parallelism, retryTimes: chunkOptions?.retryTimes, retryDelay: chunkOptions?.retryDelay, }); if (!cos.autoInform()) { await this.cache.exec('operate', { entity: 'extraFile', operation: { id: await (0, uuid_1.generateNewIdAsync)(), action: 'update', data: { uploadState: 'success', }, filter: { id: extraFileId, }, }, }); } this.publish(); return this.getUrl(newExtraFile, style); } catch (err) { await this.cache.operate('extraFile', { action: 'remove', data: {}, filter: { id: extraFileId, }, id: await (0, uuid_1.generateNewIdAsync)(), }); this.publish(); throw err; } } /** * 中止上传 * @param extraFileId ExtraFile的ID */ abortUpload(extraFileId) { // this.fileUpLoad?.abortUpload(extraFileId); const taskIds = this.uploadIdToTaskId[extraFileId]; console.log('abortUpload', extraFileId, taskIds); if (taskIds) { taskIds.forEach((taskId) => { this.fileUpLoad?.abortUpload(taskId); }); } } // 私有 async doUpload(options) { const { extraFile, file, cos, getPercent, parallelism, retryTimes, retryDelay } = options; const taskIds = this.generateUploadId(extraFile); const extraFileId = extraFile.id; const enabledChunkUpload = extraFile.enableChunkedUpload; this.uploadIdToTaskId[extraFile.id] = taskIds; await cos.upload({ extraFile: extraFile, uploadFn: this.fileUpLoad.uploadFile, presignMultiPartUpload: async (from, to) => { const res = await this.cache.exec('presignMultiPartUpload', { extraFileId, from, to, }); return res.result; }, file: file, uploadToAspect: this.uploadToAspect.bind(this), getPercent: getPercent, parallelism: parallelism, retryTimes: retryTimes, retryDelay: retryDelay, onChunkSuccess: async (chunkInfo) => { if (enabledChunkUpload) { await this.cache.operate('extraFile', [{ id: await (0, uuid_1.generateNewIdAsync)(), action: 'update', data: { chunkInfo, }, filter: { id: extraFileId, }, }]); } }, }); // 上传成功后删除uploadId映射 delete this.uploadIdToTaskId[extraFile.id]; if (enabledChunkUpload) { await this.cache.exec('mergeChunkedUpload', { extraFileId, }); } } async uploadToAspect(file, name, // 文件的part name aspectName, // 上传的aspect名 formData, // 上传的其它part参数 autoInform // 上传成功是否会自动通知server(若不会则需要前台显式通知) ) { const formData2 = new FormData(); for (const key of Object.keys(formData)) { formData2.append(key, formData[key]); } formData2.append(name || 'file', file); const { result } = await this.cache.exec(aspectName, formData2); return result; } /** * 生成上传ID列表,请确保在cos实现时,该方法生成的ID与upload方法中调用的上传接口的任务ID一致 */ generateUploadId(extraFile) { const chunkInfo = extraFile.chunkInfo; const uploadIds = []; if (extraFile.enableChunkedUpload) { for (let partNumber = 1; partNumber <= chunkInfo.partCount; partNumber++) { if (!chunkInfo.parts[partNumber - 1]) { uploadIds.push(`${extraFile.id}:${partNumber}`); } } } else { uploadIds.push(`${extraFile.id}`); } return uploadIds; } } exports.ExtraFile = ExtraFile;