import { Feature } from 'oak-frontend-base/es/types/Feature'; import { Upload } from 'oak-frontend-base/es/utils/upload'; import { bytesToSize, getFileURL } from '../utils/extraFile'; import { assert } from 'oak-domain/lib/utils/assert'; import { getCos, registerCos } from '../utils/cos'; import { unset } from 'oak-domain/lib/utils/lodash'; import { generateNewId, generateNewIdAsync } from 'oak-domain/lib/utils/uuid'; import { extraFileProjection } from '../types/Projection'; export class ExtraFile extends Feature { cache; application; files; constructor(cache, application) { super(); this.cache = cache; this.application = application; this.files = {}; } registerCos(clazzes) { clazzes.forEach((clazz) => registerCos(clazz)); } addLocalFile(id, file) { assert(!this.files[id]); this.files[id] = { file, state: 'local', }; this.publish(); } removeLocalFiles(ids) { ids.forEach((id) => unset(this.files, id)); this.publish(); } async upload(id, entity) { /** * 这个函数假设了前台知道后台会产生modi的行为和数据结构,不是很好的设计 */ const { toModi } = this.cache.getSchema()[entity]; let modiEntityId = ''; const getExtraFileData = () => { if (toModi) { const [modi] = this.cache.get('modi', { data: { id: 1, data: 1, entity: 1, entityId: 1, }, filter: { entity: entity, targetEntity: 'extraFile', action: 'create', filter: { id, }, }, }); modiEntityId = modi.entityId; return modi.data; } else { const [extraFile] = this.cache.get('extraFile', { data: extraFileProjection, filter: { id, }, }); return extraFile; } }; const extraFile = getExtraFileData(); assert(extraFile && extraFile.uploadState === 'uploading'); const item = this.files[id]; assert(item); const { file, state } = item; assert(['local', 'failed'].includes(state)); item.state = 'uploading'; item.percentage = 0; const up = new Upload(); const cos = getCos(extraFile.origin); try { await cos.upload(extraFile, up.uploadFile, file, this.uploadToAspect.bind(this)); } catch (err) { item.state = 'failed'; item.percentage = undefined; this.publish(); throw err; } if (!cos.autoInform()) { const informServer = async () => { const operation = { id: await generateNewIdAsync(), action: 'update', data: { uploadState: 'success', }, filter: { id, }, }; if (toModi) { await this.cache.exec('operate', { entity: 'modi', operation: { id: await generateNewIdAsync(), action: 'create', data: { id: await generateNewIdAsync(), entity: entity, entityId: modiEntityId, data: operation.data, action: 'update', filter: operation.filter, targetEntity: 'extraFile', }, }, }); } else { await this.cache.exec('operate', { entity: 'extraFile', operation, }); } }; await informServer(); } item.state = 'uploaded'; item.percentage = undefined; this.publish(); } 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 getFileURL(file); } assert(false, 'the incoming file is not supported'); } const { origin, extra1 } = extraFile; if (origin === 'unknown') { return extra1 || ''; } const cos = getCos(origin); return cos.composeFileUrl(extraFile, this.application.getApplication()?.system.config, style); } 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 bytesToSize(size); } async autoUpload(extraFile, file, style) { const extraFileId = extraFile.id || generateNewId(); const applicationId = extraFile.applicationId || this.application.getApplicationId(); await this.cache.operate('extraFile', { action: 'create', data: Object.assign(extraFile, { id: extraFileId, applicationId, }), id: await generateNewIdAsync(), }); const [newExtraFile] = this.cache.get('extraFile', { data: extraFileProjection, filter: { id: extraFileId, }, }); const up = new Upload(); try { const cos = getCos(newExtraFile.origin); await cos.upload(newExtraFile, up.uploadFile, file, this.uploadToAspect.bind(this)); if (!cos.autoInform()) { await this.cache.exec('operate', { entity: 'extraFile', operation: { id: await 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 generateNewIdAsync(), }); this.publish(); throw err; } } // 私有 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; } }