fix: 改为由后端判断分片上传情况
This commit is contained in:
parent
147dc5eb12
commit
56b07dc4cb
|
|
@ -72,9 +72,17 @@ export async function mergeChunkedUpload(params, context) {
|
|||
assert(extrafile.chunkInfo, `extraFile ${extraFileId} 的chunkInfo信息缺失`);
|
||||
assert(!extrafile.chunkInfo.merged, `extraFile ${extraFileId} 已经合并过分片,无需重复合并`);
|
||||
// 必须保证所有分片都有上传完成
|
||||
const allPartsDone = extrafile.chunkInfo.parts.every(part => part.etag);
|
||||
assert(allPartsDone, `extraFile ${extraFileId} 存在未上传完成的分片,无法合并`);
|
||||
const cos = getCosBackend(extrafile.origin);
|
||||
const { parts } = await cos.listMultipartUploads(extrafile.application, extrafile, context);
|
||||
const allPartsDone = parts.every(part => part.etag && part.size > 0);
|
||||
assert(allPartsDone, `extraFile ${extraFileId} 存在未上传完成的分片,无法合并`);
|
||||
// 赋值,顺带删除一些无用信息,减小体积(出现过mysql排序超出限制的问题)
|
||||
extrafile.chunkInfo.parts = parts.map((part, index) => ({
|
||||
...extrafile.chunkInfo.parts[index],
|
||||
partNumber: part.partNumber,
|
||||
etag: part.etag,
|
||||
uploadUrl: '', // 不需要保存上传链接
|
||||
}));
|
||||
await cos.mergeChunkedUpload(extrafile.application, extrafile, context);
|
||||
// 更新chunkInfo状态
|
||||
const closeRootMode = context.openRootMode();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { EntityDict } from "../../oak-app-domain";
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, "applicationPassport", true, {
|
||||
import { ReactComponentProps } from "oak-frontend-base";
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
|
||||
declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
|
||||
systemId: string;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUser", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthApplication", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthProvider", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUserAuthorization", import("../context/BackendRuntimeContext").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "mobile", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
|
||||
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthApplication", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthProvider", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUser", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUserAuthorization", import("../context/BackendRuntimeContext").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "mobile", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export declare function createToDo<ED extends EntityDict & BaseEntityDict, T ext
|
|||
redirectTo: EntityDict['toDo']['OpSchema']['redirectTo'];
|
||||
entity: any;
|
||||
entityId: string;
|
||||
}, userIds?: string[]): Promise<1 | 0>;
|
||||
}, userIds?: string[]): Promise<0 | 1>;
|
||||
/**
|
||||
* 完成todo例程,当在entity对象上进行action操作时(操作条件是filter),将对应的todo完成
|
||||
* 必须在entity的action的后trigger中调用
|
||||
|
|
|
|||
|
|
@ -103,4 +103,15 @@ export interface CosBackend<ED extends EntityDict> {
|
|||
* 完成分片上传后的合并操作
|
||||
*/
|
||||
mergeChunkedUpload: (application: ED['application']['Schema'], extraFile: ED['extraFile']['OpSchema'], context: BRC<ED>) => Promise<void>;
|
||||
/**
|
||||
* 列出分片信息
|
||||
*/
|
||||
listMultipartUploads: (application: ED['application']['Schema'], extraFile: ED['extraFile']['OpSchema'], context: BRC<ED>) => Promise<{
|
||||
parts: Array<{
|
||||
partNumber: number;
|
||||
etag: string;
|
||||
size: number;
|
||||
lastModified: Date;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,4 +29,12 @@ export default class ALiYunBackend extends ALiYun implements CosBackend<EntityDi
|
|||
*/
|
||||
mergeChunkedUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
abortMultipartUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
listMultipartUploads(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<{
|
||||
parts: Array<{
|
||||
partNumber: number;
|
||||
etag: string;
|
||||
size: number;
|
||||
lastModified: Date;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,4 +144,11 @@ export default class ALiYunBackend extends ALiYun {
|
|||
assert(b, `extraFile中的bucket名称在阿里云配置中找不到「${extraFile.bucket}」`);
|
||||
await instance.abortMultipartUpload(extraFile.bucket, b.zone, key, extraFile.chunkInfo.uploadId);
|
||||
}
|
||||
async listMultipartUploads(application, extraFile, context) {
|
||||
const key = this.formKey(extraFile);
|
||||
const { instance, config: aliyunCosConfig } = this.getConfigAndInstance(application);
|
||||
const b = aliyunCosConfig.buckets.find((ele) => ele.name === extraFile.bucket);
|
||||
assert(b, `extraFile中的bucket名称在阿里云配置中找不到「${extraFile.bucket}」`);
|
||||
return await instance.listParts(extraFile.bucket, b.zone, key, extraFile.chunkInfo.uploadId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export async function chunkUpload(options) {
|
|||
// 验证上传是否成功
|
||||
let isSuccess = false;
|
||||
if (process.env.OAK_PLATFORM === 'wechatMp') {
|
||||
if (response.errMsg === 'uploadFile:ok') {
|
||||
if (response.errMsg === 'request:ok') {
|
||||
const data = JSON.parse(response.data);
|
||||
isSuccess = !!(data.status === 204 || data.status === 200);
|
||||
}
|
||||
|
|
@ -118,10 +118,10 @@ export async function chunkUpload(options) {
|
|||
}
|
||||
// 等待所有任务完成
|
||||
await Promise.all(executing);
|
||||
// 调用分片成功回调(所有分片完成后)
|
||||
if (onChunkSuccess) {
|
||||
await onChunkSuccess(chunkInfo);
|
||||
}
|
||||
// // 调用分片成功回调(所有分片完成后)
|
||||
// if (onChunkSuccess) {
|
||||
// await onChunkSuccess(chunkInfo);
|
||||
// }
|
||||
// 清理小程序环境下的临时文件
|
||||
if (process.env.OAK_PLATFORM === 'wechatMp' && typeof file === 'string') {
|
||||
await cleanTempFiles(chunks);
|
||||
|
|
|
|||
|
|
@ -26,4 +26,7 @@ export default class CTYunBackend extends CTYun implements CosBackend<EntityDict
|
|||
*/
|
||||
mergeChunkedUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
abortMultipartUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
listMultipartUploads(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<{
|
||||
parts: never[];
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,4 +95,9 @@ export default class CTYunBackend extends CTYun {
|
|||
const key = this.formKey(extraFile);
|
||||
const { instance, config: aliyunCosConfig } = this.getConfigAndInstance(application);
|
||||
}
|
||||
async listMultipartUploads(application, extraFile, context) {
|
||||
return {
|
||||
parts: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,4 +26,7 @@ export default class LocalBackend extends Local implements CosBackend<EntityDict
|
|||
*/
|
||||
mergeChunkedUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
abortMultipartUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
listMultipartUploads(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<{
|
||||
parts: never[];
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,5 +73,10 @@ export default class LocalBackend extends Local {
|
|||
const key = this.formKey(extraFile);
|
||||
const { instance, config: aliyunCosConfig } = this.getConfigAndInstance(application);
|
||||
}
|
||||
async listMultipartUploads(application, extraFile, context) {
|
||||
return {
|
||||
parts: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
;
|
||||
|
|
|
|||
|
|
@ -26,4 +26,7 @@ export default class QiniuBackend extends Qiniu implements CosBackend<EntityDict
|
|||
*/
|
||||
mergeChunkedUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
abortMultipartUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
listMultipartUploads(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<{
|
||||
parts: never[];
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,5 +114,10 @@ export default class QiniuBackend extends Qiniu {
|
|||
const key = this.formKey(extraFile);
|
||||
const { instance, config: aliyunCosConfig } = this.getConfigAndInstance(application);
|
||||
}
|
||||
async listMultipartUploads(application, extraFile, context) {
|
||||
return {
|
||||
parts: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
;
|
||||
|
|
|
|||
|
|
@ -29,4 +29,12 @@ export default class S3Backend extends S3 implements CosBackend<EntityDict> {
|
|||
*/
|
||||
mergeChunkedUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
abortMultipartUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
listMultipartUploads(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<{
|
||||
parts: Array<{
|
||||
partNumber: number;
|
||||
etag: string;
|
||||
size: number;
|
||||
lastModified: Date;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,4 +129,18 @@ export default class S3Backend extends S3 {
|
|||
const { instance, config: s3CosConfig } = this.getConfigAndInstance(application, extraFile.bucket);
|
||||
await instance.abortMultipartUpload(extraFile.bucket, key, extraFile.chunkInfo.uploadId, s3CosConfig.endpoint);
|
||||
}
|
||||
async listMultipartUploads(application, extraFile, context) {
|
||||
const key = this.formKey(extraFile);
|
||||
const { instance, config: s3CosConfig } = this.getConfigAndInstance(application, extraFile.bucket);
|
||||
const result = await instance.listParts(extraFile.bucket, key, extraFile.chunkInfo.uploadId, s3CosConfig.endpoint, 101);
|
||||
assert(result.isTruncated === false, `分片数量超过101,无法列出所有分片信息,不应当出现这个情况,触发器中已经限制了最大分片数量为100`);
|
||||
return {
|
||||
parts: result.parts.map((part) => ({
|
||||
partNumber: part.partNumber,
|
||||
etag: part.eTag,
|
||||
size: part.size,
|
||||
lastModified: part.lastModified,
|
||||
}))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,4 +26,7 @@ export default class TencentYunBackend extends TencentYun implements CosBackend<
|
|||
*/
|
||||
mergeChunkedUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
abortMultipartUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
listMultipartUploads(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<{
|
||||
parts: never[];
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,4 +93,9 @@ export default class TencentYunBackend extends TencentYun {
|
|||
}
|
||||
async abortMultipartUpload(application, extraFile, context) {
|
||||
}
|
||||
async listMultipartUploads(application, extraFile, context) {
|
||||
return {
|
||||
parts: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,4 +26,7 @@ export default class UnknownBackend extends Unknown implements CosBackend<Entity
|
|||
*/
|
||||
mergeChunkedUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
abortMultipartUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
listMultipartUploads(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<{
|
||||
parts: never[];
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,5 +33,10 @@ export default class UnknownBackend extends Unknown {
|
|||
}
|
||||
async abortMultipartUpload(application, extraFile, context) {
|
||||
}
|
||||
async listMultipartUploads(application, extraFile, context) {
|
||||
return {
|
||||
parts: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
;
|
||||
|
|
|
|||
|
|
@ -26,4 +26,7 @@ export default class WechatBackend extends Wechat implements CosBackend<EntityDi
|
|||
*/
|
||||
mergeChunkedUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
abortMultipartUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
listMultipartUploads(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<{
|
||||
parts: never[];
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,5 +54,10 @@ export default class WechatBackend extends Wechat {
|
|||
}
|
||||
async abortMultipartUpload(application, extraFile, context) {
|
||||
}
|
||||
async listMultipartUploads(application, extraFile, context) {
|
||||
return {
|
||||
parts: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
;
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ export async function sliceFileForMp(filePath, chunkSize, partCount) {
|
|||
chunks.push(tempFilePath);
|
||||
}
|
||||
catch (err) {
|
||||
console.error('分片读取失败:', err);
|
||||
// 清理已创建的临时文件
|
||||
await cleanTempFiles(chunks);
|
||||
throw new OakUploadException(`分片 ${i + 1} 读取失败: ${err}`);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { assert } from 'oak-domain/lib/utils/assert';
|
|||
import { getCosBackend } from '../utils/cos/index.backend';
|
||||
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
||||
import { groupBy } from 'oak-domain/lib/utils/lodash';
|
||||
import { extraFileProjection } from '../types/Projection';
|
||||
import applicationPassport from '../components/applicationPassport';
|
||||
async function checkWhetherSuccess(context, applicationId, rows) {
|
||||
const successIds = [];
|
||||
const failedIds = [];
|
||||
|
|
@ -59,6 +61,9 @@ const watchers = [
|
|||
$lt: deadline,
|
||||
},
|
||||
uploadState: 'uploading',
|
||||
enableChunkedUpload: {
|
||||
$exists: false,
|
||||
}
|
||||
};
|
||||
},
|
||||
projection: {
|
||||
|
|
@ -82,5 +87,38 @@ const watchers = [
|
|||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '确定uploading的文件状态',
|
||||
entity: 'extraFile',
|
||||
filter: async () => {
|
||||
const now = Date.now();
|
||||
const deadline = process.env.NODE_ENV === 'production' ? now - 3 * 24 * 60 * 60 * 1000 : now - 60 * 1000;
|
||||
return {
|
||||
$$updateAt$$: {
|
||||
$lt: deadline,
|
||||
},
|
||||
uploadState: 'uploading',
|
||||
enableChunkedUpload: true
|
||||
};
|
||||
},
|
||||
projection: {
|
||||
...extraFileProjection,
|
||||
application: {
|
||||
...applicationPassport,
|
||||
}
|
||||
},
|
||||
fn: async (context, data) => {
|
||||
const eg = groupBy(data, 'applicationId');
|
||||
for (const appId in eg) {
|
||||
// 这里检查需要分片上传的文件信息,但是考虑到可能还在上传之类的,所以先不管,后面再处理
|
||||
// 1. 要去查询分片信息,看看是不是都有etag了,如果都有etag了,就说明上传完成了,否则就标记为失败
|
||||
}
|
||||
return {
|
||||
extraFile: {
|
||||
update: data.length,
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
];
|
||||
export default watchers;
|
||||
|
|
|
|||
|
|
@ -78,9 +78,17 @@ async function mergeChunkedUpload(params, context) {
|
|||
(0, assert_1.assert)(extrafile.chunkInfo, `extraFile ${extraFileId} 的chunkInfo信息缺失`);
|
||||
(0, assert_1.assert)(!extrafile.chunkInfo.merged, `extraFile ${extraFileId} 已经合并过分片,无需重复合并`);
|
||||
// 必须保证所有分片都有上传完成
|
||||
const allPartsDone = extrafile.chunkInfo.parts.every(part => part.etag);
|
||||
(0, assert_1.assert)(allPartsDone, `extraFile ${extraFileId} 存在未上传完成的分片,无法合并`);
|
||||
const cos = (0, index_backend_1.getCosBackend)(extrafile.origin);
|
||||
const { parts } = await cos.listMultipartUploads(extrafile.application, extrafile, context);
|
||||
const allPartsDone = parts.every(part => part.etag && part.size > 0);
|
||||
(0, assert_1.assert)(allPartsDone, `extraFile ${extraFileId} 存在未上传完成的分片,无法合并`);
|
||||
// 赋值,顺带删除一些无用信息,减小体积(出现过mysql排序超出限制的问题)
|
||||
extrafile.chunkInfo.parts = parts.map((part, index) => ({
|
||||
...extrafile.chunkInfo.parts[index],
|
||||
partNumber: part.partNumber,
|
||||
etag: part.etag,
|
||||
uploadUrl: '', // 不需要保存上传链接
|
||||
}));
|
||||
await cos.mergeChunkedUpload(extrafile.application, extrafile, context);
|
||||
// 更新chunkInfo状态
|
||||
const closeRootMode = context.openRootMode();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
import { EntityDict } from "../../oak-app-domain";
|
||||
import { ReactComponentProps } from "oak-frontend-base";
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
|
||||
declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
|
||||
systemId: string;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
@ -0,0 +1,356 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const lodash_1 = require("oak-domain/lib/utils/lodash");
|
||||
const uuid_1 = require("oak-domain/lib/utils/uuid");
|
||||
exports.default = OakComponent({
|
||||
entity: 'applicationPassport',
|
||||
isList: true,
|
||||
projection: {
|
||||
id: 1,
|
||||
applicationId: 1,
|
||||
application: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
type: 1,
|
||||
systemId: 1,
|
||||
},
|
||||
passportId: 1,
|
||||
passport: {
|
||||
id: 1,
|
||||
type: 1,
|
||||
systemId: 1,
|
||||
enabled: 1,
|
||||
},
|
||||
isDefault: 1,
|
||||
},
|
||||
properties: {
|
||||
systemId: '',
|
||||
},
|
||||
filters: [
|
||||
{
|
||||
filter() {
|
||||
const { systemId } = this.props;
|
||||
return {
|
||||
application: {
|
||||
systemId,
|
||||
},
|
||||
passport: {
|
||||
systemId,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
],
|
||||
formData({ data }) {
|
||||
const aps = data.filter((ele) => ele.$$deleteAt$$ !== 1);
|
||||
return {
|
||||
aps,
|
||||
};
|
||||
},
|
||||
listeners: {
|
||||
async 'aps,applications,passports'(prev, next) {
|
||||
if (!this.arraysAreEqual(prev.aps, next.aps) || !this.arraysAreEqual(prev.applications, next.applications) || !this.arraysAreEqual(prev.passports, next.passports)) {
|
||||
let apArray = [];
|
||||
const records = (0, lodash_1.groupBy)(next.passports, 'type');
|
||||
if (next.applications && next.applications.length > 0 && next.passports && next.passports.length > 0) {
|
||||
for (const a of next.applications) {
|
||||
let item = {
|
||||
aId: a.id,
|
||||
aName: a.name,
|
||||
typeRecords: {},
|
||||
defaultOptions: [],
|
||||
defaultValue: '',
|
||||
};
|
||||
let typeRecords = {};
|
||||
for (const key of Object.keys(records)) {
|
||||
const r = records[key];
|
||||
const render = this.getRender(key, r, a.type);
|
||||
if (render === 'select') {
|
||||
const passportOptions = r.map((ele) => {
|
||||
const { disabled, disabledTip } = this.checkDisabled(a, ele);
|
||||
return {
|
||||
label: ele.type === 'email' ? ele.config.account : ele.config.appId,
|
||||
value: ele.id,
|
||||
apId: (0, uuid_1.generateNewId)(),
|
||||
disabled,
|
||||
disabledTip,
|
||||
};
|
||||
});
|
||||
const d = !passportOptions.find((ele) => !ele.disabled);
|
||||
let disabledTip = '';
|
||||
if (d) {
|
||||
disabledTip = '暂不支持该登录方式';
|
||||
}
|
||||
Object.assign(typeRecords, { [key]: { render, passportOptions, chekedValue: undefined, disabled: d, disabledTip } });
|
||||
}
|
||||
else {
|
||||
const { disabled, disabledTip } = this.checkDisabled(a, r[0]);
|
||||
const apId = await (0, uuid_1.generateNewIdAsync)();
|
||||
Object.assign(typeRecords, { [key]: { render, pId: r[0].id, checked: false, disabled, disabledTip, apId, } });
|
||||
}
|
||||
}
|
||||
Object.assign(item, { typeRecords });
|
||||
apArray.push(item);
|
||||
}
|
||||
if (next.aps && next.aps.length > 0) {
|
||||
for (const ap of next.aps) {
|
||||
const aIdx = apArray.findIndex((ele) => ele.aId === ap.applicationId);
|
||||
if (aIdx !== -1) {
|
||||
const p = ap.passport;
|
||||
const t = p.type;
|
||||
if (apArray[aIdx].typeRecords[t].render === 'select') {
|
||||
apArray[aIdx].typeRecords[t].checkedValue = p.id;
|
||||
apArray[aIdx].typeRecords[t].apId = ap.id;
|
||||
const option = apArray[aIdx].typeRecords[t].passportOptions?.find((ele) => ele.value === p.id);
|
||||
option && Object.assign(option, { apId: ap.id });
|
||||
}
|
||||
else {
|
||||
if (apArray[aIdx].typeRecords[t].pId === p.id) {
|
||||
apArray[aIdx].typeRecords[t].checked = true;
|
||||
apArray[aIdx].typeRecords[t].apId = ap.id;
|
||||
}
|
||||
}
|
||||
apArray[aIdx].defaultOptions.push({
|
||||
label: this.t(`passport:v.type.${p.type}`),
|
||||
value: ap.id,
|
||||
});
|
||||
if (ap.isDefault) {
|
||||
apArray[aIdx].defaultValue = ap.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
apArray,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
data: {
|
||||
applications: [],
|
||||
passports: [],
|
||||
apArray: [],
|
||||
types: [],
|
||||
},
|
||||
lifetimes: {
|
||||
async ready() {
|
||||
const { systemId } = this.props;
|
||||
const { data: applicationDatas } = await this.features.cache.refresh('application', {
|
||||
data: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
type: 1,
|
||||
config: 1,
|
||||
systemId: 1,
|
||||
},
|
||||
filter: {
|
||||
systemId,
|
||||
}
|
||||
});
|
||||
const { data: passportDatas } = await this.features.cache.refresh('passport', {
|
||||
data: {
|
||||
id: 1,
|
||||
type: 1,
|
||||
config: 1,
|
||||
enabled: 1,
|
||||
systemId: 1,
|
||||
},
|
||||
filter: {
|
||||
systemId,
|
||||
enabled: true,
|
||||
},
|
||||
sorter: [{
|
||||
$attr: {
|
||||
$$updateAt$$: 1,
|
||||
},
|
||||
$direction: 'desc'
|
||||
}]
|
||||
});
|
||||
const applications = applicationDatas;
|
||||
const passports = passportDatas;
|
||||
const types = (0, lodash_1.uniq)(passports.map((ele) => ele.type));
|
||||
this.setState({
|
||||
applications,
|
||||
passports,
|
||||
types,
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
arraysAreEqual(first, second) {
|
||||
if (first?.length !== second?.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < first?.length; ++i) {
|
||||
if (!(0, lodash_1.isEqual)(first[i], second[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
checkDisabled(application, passport) {
|
||||
const { type: aType, config: aConfig } = application;
|
||||
const { type: pType, config: pConfig } = passport;
|
||||
switch (pType) {
|
||||
case 'sms':
|
||||
if (!pConfig.mockSend) {
|
||||
if (!pConfig.templateName || pConfig.templateName === '') {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '短信登录未配置验证码模板名称',
|
||||
};
|
||||
}
|
||||
if (!pConfig.defaultOrigin) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '短信登录未配置默认渠道',
|
||||
};
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'email':
|
||||
if (!pConfig.mockSend) {
|
||||
if (!pConfig.account || pConfig.account === '') {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '邮箱登录未配置账号',
|
||||
};
|
||||
}
|
||||
else if (!pConfig.subject || pConfig.subject === '') {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '邮箱登录未配置邮件主题',
|
||||
};
|
||||
}
|
||||
else if ((!pConfig.text || pConfig.text === '' || !pConfig.text?.includes('${code}')) &&
|
||||
(!pConfig.html || pConfig.html === '' || !pConfig.html?.includes('${code}'))) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '邮箱登录未配置邮件内容模板',
|
||||
};
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'wechatPublicForWeb':
|
||||
if (!pConfig.appId || pConfig.appId === '') {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '公众号授权登录未配置appId',
|
||||
};
|
||||
}
|
||||
break;
|
||||
case 'wechatMpForWeb':
|
||||
if (!pConfig.appId || pConfig.appId === '') {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '小程序授权登录未配置appId',
|
||||
};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (aType) {
|
||||
case 'web':
|
||||
if (pType === 'wechatWeb') {
|
||||
//微信网站登录 application需配置微信网站appId
|
||||
const { appId } = aConfig.wechat || {};
|
||||
if (!appId || appId === '') {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '当前application未配置微信网站appId',
|
||||
};
|
||||
}
|
||||
}
|
||||
else if (pType === 'wechatMp' || pType === 'wechatPublic') {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '当前application不支持该登录方式',
|
||||
};
|
||||
}
|
||||
break;
|
||||
case 'wechatMp':
|
||||
if (['wechatPublic', 'wechatWeb', 'wechatMpForWeb', 'wechatPublicForWeb'].includes(pType)) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '当前application不支持该登录方式',
|
||||
};
|
||||
}
|
||||
break;
|
||||
case 'wechatPublic':
|
||||
if (['wechatMp', 'wechatWeb', 'wechatMpForWeb', 'wechatPublicForWeb'].includes(pType)) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '当前application不支持该登录方式',
|
||||
};
|
||||
}
|
||||
break;
|
||||
case 'native':
|
||||
if (['wechatMp', 'wechatPublic', 'wechatWeb', 'wechatMpForWeb', 'wechatPublicForWeb'].includes(pType)) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '当前application不支持该登录方式',
|
||||
};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return {
|
||||
disabled: false,
|
||||
disabledTip: undefined,
|
||||
};
|
||||
},
|
||||
async onCheckedChange(aId, pId, checked, apId) {
|
||||
if (checked) {
|
||||
//create applicationPassport
|
||||
this.addItem({
|
||||
applicationId: aId,
|
||||
passportId: pId,
|
||||
isDefault: true,
|
||||
});
|
||||
}
|
||||
else {
|
||||
//remove id为apId的applicationPassport
|
||||
apId && this.removeItem(apId);
|
||||
}
|
||||
},
|
||||
checkLastOne(aId, pId) {
|
||||
const { apArray } = this.state;
|
||||
const idx = apArray.findIndex((ele) => ele.aId === aId);
|
||||
if (idx !== -1) {
|
||||
const records = apArray[idx].typeRecords;
|
||||
for (const key of Object.keys(records)) {
|
||||
const r = records[key];
|
||||
if ((r.checkedValue && r.checkedValue !== pId && !!r.checkedValue) || (r.pId && r.pId !== pId && !!r.checked)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async onSelectChange(aId, addPId, removeApId) {
|
||||
if (removeApId) {
|
||||
removeApId && this.removeItem(removeApId);
|
||||
}
|
||||
if (addPId) {
|
||||
this.addItem({
|
||||
applicationId: aId,
|
||||
passportId: addPId,
|
||||
isDefault: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
getRender(pType, passports, aType) {
|
||||
let render = 'switch';
|
||||
if (passports.length > 1) {
|
||||
if (aType === 'web' || (['wechatMp', 'wechatPublic'].includes(aType) && ['email', 'sms'].includes(pType))) {
|
||||
render = 'select';
|
||||
}
|
||||
}
|
||||
return render;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.initialize = exports.create = void 0;
|
||||
exports.create = create;
|
||||
exports.initialize = initialize;
|
||||
const tslib_1 = require("tslib");
|
||||
const token_1 = require("./token");
|
||||
const extraFile_1 = require("./extraFile");
|
||||
|
|
@ -37,7 +38,6 @@ function create(basicFeatures) {
|
|||
userWechatPublicTag,
|
||||
};
|
||||
}
|
||||
exports.create = create;
|
||||
const selectionRewriter_1 = require("../utils/selectionRewriter");
|
||||
async function initialize(features, access, config, clazzes) {
|
||||
features.cache.registerSelectionRewriter(selectionRewriter_1.rewriteSelection);
|
||||
|
|
@ -56,4 +56,3 @@ async function initialize(features, access, config, clazzes) {
|
|||
}
|
||||
}
|
||||
}
|
||||
exports.initialize = initialize;
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUser", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthApplication", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthProvider", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUserAuthorization", import("../context/BackendRuntimeContext").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "mobile", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
|
||||
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthApplication", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthProvider", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUser", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUserAuthorization", import("../context/BackendRuntimeContext").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "mobile", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -103,4 +103,15 @@ export interface CosBackend<ED extends EntityDict> {
|
|||
* 完成分片上传后的合并操作
|
||||
*/
|
||||
mergeChunkedUpload: (application: ED['application']['Schema'], extraFile: ED['extraFile']['OpSchema'], context: BRC<ED>) => Promise<void>;
|
||||
/**
|
||||
* 列出分片信息
|
||||
*/
|
||||
listMultipartUploads: (application: ED['application']['Schema'], extraFile: ED['extraFile']['OpSchema'], context: BRC<ED>) => Promise<{
|
||||
parts: Array<{
|
||||
partNumber: number;
|
||||
etag: string;
|
||||
size: number;
|
||||
lastModified: Date;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,4 +29,12 @@ export default class ALiYunBackend extends ALiYun implements CosBackend<EntityDi
|
|||
*/
|
||||
mergeChunkedUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
abortMultipartUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
listMultipartUploads(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<{
|
||||
parts: Array<{
|
||||
partNumber: number;
|
||||
etag: string;
|
||||
size: number;
|
||||
lastModified: Date;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,5 +147,12 @@ class ALiYunBackend extends aliyun_1.default {
|
|||
(0, assert_1.assert)(b, `extraFile中的bucket名称在阿里云配置中找不到「${extraFile.bucket}」`);
|
||||
await instance.abortMultipartUpload(extraFile.bucket, b.zone, key, extraFile.chunkInfo.uploadId);
|
||||
}
|
||||
async listMultipartUploads(application, extraFile, context) {
|
||||
const key = this.formKey(extraFile);
|
||||
const { instance, config: aliyunCosConfig } = this.getConfigAndInstance(application);
|
||||
const b = aliyunCosConfig.buckets.find((ele) => ele.name === extraFile.bucket);
|
||||
(0, assert_1.assert)(b, `extraFile中的bucket名称在阿里云配置中找不到「${extraFile.bucket}」`);
|
||||
return await instance.listParts(extraFile.bucket, b.zone, key, extraFile.chunkInfo.uploadId);
|
||||
}
|
||||
}
|
||||
exports.default = ALiYunBackend;
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ async function chunkUpload(options) {
|
|||
// 验证上传是否成功
|
||||
let isSuccess = false;
|
||||
if (process.env.OAK_PLATFORM === 'wechatMp') {
|
||||
if (response.errMsg === 'uploadFile:ok') {
|
||||
if (response.errMsg === 'request:ok') {
|
||||
const data = JSON.parse(response.data);
|
||||
isSuccess = !!(data.status === 204 || data.status === 200);
|
||||
}
|
||||
|
|
@ -123,10 +123,10 @@ async function chunkUpload(options) {
|
|||
}
|
||||
// 等待所有任务完成
|
||||
await Promise.all(executing);
|
||||
// 调用分片成功回调(所有分片完成后)
|
||||
if (onChunkSuccess) {
|
||||
await onChunkSuccess(chunkInfo);
|
||||
}
|
||||
// // 调用分片成功回调(所有分片完成后)
|
||||
// if (onChunkSuccess) {
|
||||
// await onChunkSuccess(chunkInfo);
|
||||
// }
|
||||
// 清理小程序环境下的临时文件
|
||||
if (process.env.OAK_PLATFORM === 'wechatMp' && typeof file === 'string') {
|
||||
await (0, slice_1.cleanTempFiles)(chunks);
|
||||
|
|
|
|||
|
|
@ -26,4 +26,7 @@ export default class CTYunBackend extends CTYun implements CosBackend<EntityDict
|
|||
*/
|
||||
mergeChunkedUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
abortMultipartUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
listMultipartUploads(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<{
|
||||
parts: never[];
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,5 +98,10 @@ class CTYunBackend extends ctyun_1.default {
|
|||
const key = this.formKey(extraFile);
|
||||
const { instance, config: aliyunCosConfig } = this.getConfigAndInstance(application);
|
||||
}
|
||||
async listMultipartUploads(application, extraFile, context) {
|
||||
return {
|
||||
parts: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.default = CTYunBackend;
|
||||
|
|
|
|||
|
|
@ -26,4 +26,7 @@ export default class LocalBackend extends Local implements CosBackend<EntityDict
|
|||
*/
|
||||
mergeChunkedUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
abortMultipartUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
listMultipartUploads(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<{
|
||||
parts: never[];
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,6 +76,11 @@ class LocalBackend extends local_1.default {
|
|||
const key = this.formKey(extraFile);
|
||||
const { instance, config: aliyunCosConfig } = this.getConfigAndInstance(application);
|
||||
}
|
||||
async listMultipartUploads(application, extraFile, context) {
|
||||
return {
|
||||
parts: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.default = LocalBackend;
|
||||
;
|
||||
|
|
|
|||
|
|
@ -26,4 +26,7 @@ export default class QiniuBackend extends Qiniu implements CosBackend<EntityDict
|
|||
*/
|
||||
mergeChunkedUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
abortMultipartUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
listMultipartUploads(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<{
|
||||
parts: never[];
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,11 @@ class QiniuBackend extends qiniu_1.default {
|
|||
const key = this.formKey(extraFile);
|
||||
const { instance, config: aliyunCosConfig } = this.getConfigAndInstance(application);
|
||||
}
|
||||
async listMultipartUploads(application, extraFile, context) {
|
||||
return {
|
||||
parts: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.default = QiniuBackend;
|
||||
;
|
||||
|
|
|
|||
|
|
@ -29,4 +29,12 @@ export default class S3Backend extends S3 implements CosBackend<EntityDict> {
|
|||
*/
|
||||
mergeChunkedUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
abortMultipartUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
listMultipartUploads(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<{
|
||||
parts: Array<{
|
||||
partNumber: number;
|
||||
etag: string;
|
||||
size: number;
|
||||
lastModified: Date;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,5 +132,19 @@ class S3Backend extends s3_1.default {
|
|||
const { instance, config: s3CosConfig } = this.getConfigAndInstance(application, extraFile.bucket);
|
||||
await instance.abortMultipartUpload(extraFile.bucket, key, extraFile.chunkInfo.uploadId, s3CosConfig.endpoint);
|
||||
}
|
||||
async listMultipartUploads(application, extraFile, context) {
|
||||
const key = this.formKey(extraFile);
|
||||
const { instance, config: s3CosConfig } = this.getConfigAndInstance(application, extraFile.bucket);
|
||||
const result = await instance.listParts(extraFile.bucket, key, extraFile.chunkInfo.uploadId, s3CosConfig.endpoint, 101);
|
||||
(0, assert_1.assert)(result.isTruncated === false, `分片数量超过101,无法列出所有分片信息,不应当出现这个情况,触发器中已经限制了最大分片数量为100`);
|
||||
return {
|
||||
parts: result.parts.map((part) => ({
|
||||
partNumber: part.partNumber,
|
||||
etag: part.eTag,
|
||||
size: part.size,
|
||||
lastModified: part.lastModified,
|
||||
}))
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.default = S3Backend;
|
||||
|
|
|
|||
|
|
@ -26,4 +26,7 @@ export default class TencentYunBackend extends TencentYun implements CosBackend<
|
|||
*/
|
||||
mergeChunkedUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
abortMultipartUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
listMultipartUploads(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<{
|
||||
parts: never[];
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,5 +96,10 @@ class TencentYunBackend extends tencent_1.default {
|
|||
}
|
||||
async abortMultipartUpload(application, extraFile, context) {
|
||||
}
|
||||
async listMultipartUploads(application, extraFile, context) {
|
||||
return {
|
||||
parts: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.default = TencentYunBackend;
|
||||
|
|
|
|||
|
|
@ -26,4 +26,7 @@ export default class UnknownBackend extends Unknown implements CosBackend<Entity
|
|||
*/
|
||||
mergeChunkedUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
abortMultipartUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
listMultipartUploads(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<{
|
||||
parts: never[];
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ class UnknownBackend extends unknown_1.default {
|
|||
}
|
||||
async abortMultipartUpload(application, extraFile, context) {
|
||||
}
|
||||
async listMultipartUploads(application, extraFile, context) {
|
||||
return {
|
||||
parts: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.default = UnknownBackend;
|
||||
;
|
||||
|
|
|
|||
|
|
@ -26,4 +26,7 @@ export default class WechatBackend extends Wechat implements CosBackend<EntityDi
|
|||
*/
|
||||
mergeChunkedUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
abortMultipartUpload(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<void>;
|
||||
listMultipartUploads(application: EntityDict['application']['Schema'], extraFile: OpSchema, context: BRC<EntityDict>): Promise<{
|
||||
parts: never[];
|
||||
}>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,11 @@ class WechatBackend extends wechat_1.default {
|
|||
}
|
||||
async abortMultipartUpload(application, extraFile, context) {
|
||||
}
|
||||
async listMultipartUploads(application, extraFile, context) {
|
||||
return {
|
||||
parts: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.default = WechatBackend;
|
||||
;
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ async function sliceFileForMp(filePath, chunkSize, partCount) {
|
|||
chunks.push(tempFilePath);
|
||||
}
|
||||
catch (err) {
|
||||
console.error('分片读取失败:', err);
|
||||
// 清理已创建的临时文件
|
||||
await cleanTempFiles(chunks);
|
||||
throw new Exception_1.OakUploadException(`分片 ${i + 1} 读取失败: ${err}`);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const tslib_1 = require("tslib");
|
||||
const assert_1 = require("oak-domain/lib/utils/assert");
|
||||
const index_backend_1 = require("../utils/cos/index.backend");
|
||||
const uuid_1 = require("oak-domain/lib/utils/uuid");
|
||||
const lodash_1 = require("oak-domain/lib/utils/lodash");
|
||||
const Projection_1 = require("../types/Projection");
|
||||
const applicationPassport_1 = tslib_1.__importDefault(require("../components/applicationPassport"));
|
||||
async function checkWhetherSuccess(context, applicationId, rows) {
|
||||
const successIds = [];
|
||||
const failedIds = [];
|
||||
|
|
@ -61,6 +64,9 @@ const watchers = [
|
|||
$lt: deadline,
|
||||
},
|
||||
uploadState: 'uploading',
|
||||
enableChunkedUpload: {
|
||||
$exists: false,
|
||||
}
|
||||
};
|
||||
},
|
||||
projection: {
|
||||
|
|
@ -84,5 +90,38 @@ const watchers = [
|
|||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '确定uploading的文件状态',
|
||||
entity: 'extraFile',
|
||||
filter: async () => {
|
||||
const now = Date.now();
|
||||
const deadline = process.env.NODE_ENV === 'production' ? now - 3 * 24 * 60 * 60 * 1000 : now - 60 * 1000;
|
||||
return {
|
||||
$$updateAt$$: {
|
||||
$lt: deadline,
|
||||
},
|
||||
uploadState: 'uploading',
|
||||
enableChunkedUpload: true
|
||||
};
|
||||
},
|
||||
projection: {
|
||||
...Projection_1.extraFileProjection,
|
||||
application: {
|
||||
...applicationPassport_1.default,
|
||||
}
|
||||
},
|
||||
fn: async (context, data) => {
|
||||
const eg = (0, lodash_1.groupBy)(data, 'applicationId');
|
||||
for (const appId in eg) {
|
||||
// 这里检查需要分片上传的文件信息,但是考虑到可能还在上传之类的,所以先不管,后面再处理
|
||||
// 1. 要去查询分片信息,看看是不是都有etag了,如果都有etag了,就说明上传完成了,否则就标记为失败
|
||||
}
|
||||
return {
|
||||
extraFile: {
|
||||
update: data.length,
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
];
|
||||
exports.default = watchers;
|
||||
|
|
|
|||
|
|
@ -112,10 +112,24 @@ export async function mergeChunkedUpload<ED extends EntityDict>(
|
|||
assert(!extrafile.chunkInfo.merged, `extraFile ${extraFileId} 已经合并过分片,无需重复合并`);
|
||||
|
||||
// 必须保证所有分片都有上传完成
|
||||
const allPartsDone = extrafile.chunkInfo!.parts.every(part => part.etag);
|
||||
const cos = getCosBackend(extrafile.origin!);
|
||||
|
||||
const { parts } = await cos.listMultipartUploads(
|
||||
extrafile.application!,
|
||||
extrafile as any,
|
||||
context as any
|
||||
);
|
||||
|
||||
const allPartsDone = parts.every(part => part.etag && part.size > 0);
|
||||
assert(allPartsDone, `extraFile ${extraFileId} 存在未上传完成的分片,无法合并`);
|
||||
|
||||
const cos = getCosBackend(extrafile.origin!);
|
||||
// 赋值,顺带删除一些无用信息,减小体积(出现过mysql排序超出限制的问题)
|
||||
extrafile.chunkInfo!.parts = parts.map((part, index) => ({
|
||||
...extrafile.chunkInfo!.parts[index],
|
||||
partNumber: part.partNumber,
|
||||
etag: part.etag!,
|
||||
uploadUrl: '', // 不需要保存上传链接
|
||||
}));
|
||||
|
||||
await cos.mergeChunkedUpload(
|
||||
extrafile.application!,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import { EmailConfig, PfwConfig, SmsConfig, MfwConfig } from "../../entities/Pas
|
|||
import { WebConfig } from "../../entities/Application";
|
||||
import { assert } from "oak-domain/lib/utils/assert";
|
||||
import { generateNewId, generateNewIdAsync } from "oak-domain/lib/utils/uuid";
|
||||
import { ReactComponentProps } from "oak-frontend-base";
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
|
||||
|
||||
type RowItem = {
|
||||
aId: string;
|
||||
|
|
@ -391,4 +393,13 @@ export default OakComponent({
|
|||
return render;
|
||||
}
|
||||
}
|
||||
});
|
||||
}) as <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(
|
||||
props: ReactComponentProps<
|
||||
ED2,
|
||||
T2,
|
||||
true,
|
||||
{
|
||||
systemId: string;
|
||||
}
|
||||
>
|
||||
) => React.ReactElement;
|
||||
|
|
@ -158,4 +158,20 @@ export interface CosBackend<ED extends EntityDict> {
|
|||
extraFile: ED['extraFile']['OpSchema'],
|
||||
context: BRC<ED>,
|
||||
) => Promise<void>;
|
||||
|
||||
/**
|
||||
* 列出分片信息
|
||||
*/
|
||||
listMultipartUploads: (
|
||||
application: ED['application']['Schema'],
|
||||
extraFile: ED['extraFile']['OpSchema'],
|
||||
context: BRC<ED>,
|
||||
) => Promise<{
|
||||
parts: Array<{
|
||||
partNumber: number,
|
||||
etag: string,
|
||||
size: number,
|
||||
lastModified: Date,
|
||||
}>
|
||||
}>;
|
||||
}
|
||||
|
|
@ -260,4 +260,34 @@ export default class ALiYunBackend
|
|||
extraFile.chunkInfo!.uploadId!
|
||||
);
|
||||
}
|
||||
|
||||
async listMultipartUploads(
|
||||
application: EntityDict['application']['Schema'],
|
||||
extraFile: OpSchema,
|
||||
context: BRC<EntityDict>,
|
||||
): Promise<{
|
||||
parts: Array<{
|
||||
partNumber: number,
|
||||
etag: string,
|
||||
size: number,
|
||||
lastModified: Date,
|
||||
}>
|
||||
}> {
|
||||
const key = this.formKey(extraFile);
|
||||
const { instance, config: aliyunCosConfig } =
|
||||
this.getConfigAndInstance(application);
|
||||
const b = (aliyunCosConfig as ALiYunCosConfig).buckets.find(
|
||||
(ele) => ele.name === extraFile.bucket
|
||||
);
|
||||
assert(
|
||||
b,
|
||||
`extraFile中的bucket名称在阿里云配置中找不到「${extraFile.bucket}」`
|
||||
);
|
||||
return await instance.listParts(
|
||||
extraFile.bucket!,
|
||||
b.zone,
|
||||
key,
|
||||
extraFile.chunkInfo!.uploadId!
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export async function chunkUpload(
|
|||
// 验证上传是否成功
|
||||
let isSuccess = false;
|
||||
if (process.env.OAK_PLATFORM === 'wechatMp') {
|
||||
if (response.errMsg === 'uploadFile:ok') {
|
||||
if (response.errMsg === 'request:ok') {
|
||||
const data = JSON.parse(response.data);
|
||||
isSuccess = !!(data.status === 204 || data.status === 200);
|
||||
}
|
||||
|
|
@ -162,10 +162,10 @@ export async function chunkUpload(
|
|||
// 等待所有任务完成
|
||||
await Promise.all(executing);
|
||||
|
||||
// 调用分片成功回调(所有分片完成后)
|
||||
if (onChunkSuccess) {
|
||||
await onChunkSuccess(chunkInfo);
|
||||
}
|
||||
// // 调用分片成功回调(所有分片完成后)
|
||||
// if (onChunkSuccess) {
|
||||
// await onChunkSuccess(chunkInfo);
|
||||
// }
|
||||
|
||||
// 清理小程序环境下的临时文件
|
||||
if (process.env.OAK_PLATFORM === 'wechatMp' && typeof file === 'string') {
|
||||
|
|
|
|||
|
|
@ -177,4 +177,15 @@ export default class CTYunBackend
|
|||
const { instance, config: aliyunCosConfig } =
|
||||
this.getConfigAndInstance(application);
|
||||
}
|
||||
|
||||
|
||||
async listMultipartUploads(
|
||||
application: EntityDict['application']['Schema'],
|
||||
extraFile: OpSchema,
|
||||
context: BRC<EntityDict>,
|
||||
){
|
||||
return {
|
||||
parts: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,4 +132,15 @@ export default class LocalBackend extends Local implements CosBackend<EntityDict
|
|||
const { instance, config: aliyunCosConfig } =
|
||||
this.getConfigAndInstance(application);
|
||||
}
|
||||
|
||||
|
||||
async listMultipartUploads(
|
||||
application: EntityDict['application']['Schema'],
|
||||
extraFile: OpSchema,
|
||||
context: BRC<EntityDict>,
|
||||
){
|
||||
return {
|
||||
parts: [],
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -189,4 +189,14 @@ export default class QiniuBackend extends Qiniu implements CosBackend<EntityDict
|
|||
const { instance, config: aliyunCosConfig } =
|
||||
this.getConfigAndInstance(application);
|
||||
}
|
||||
|
||||
async listMultipartUploads(
|
||||
application: EntityDict['application']['Schema'],
|
||||
extraFile: OpSchema,
|
||||
context: BRC<EntityDict>,
|
||||
){
|
||||
return {
|
||||
parts: [],
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -246,4 +246,43 @@ export default class S3Backend extends S3 implements CosBackend<EntityDict> {
|
|||
s3CosConfig.endpoint!
|
||||
);
|
||||
}
|
||||
|
||||
async listMultipartUploads(
|
||||
application: EntityDict['application']['Schema'],
|
||||
extraFile: OpSchema,
|
||||
context: BRC<EntityDict>,
|
||||
): Promise<{
|
||||
parts: Array<{
|
||||
partNumber: number,
|
||||
etag: string,
|
||||
size: number,
|
||||
lastModified: Date,
|
||||
}>
|
||||
}> {
|
||||
const key = this.formKey(extraFile);
|
||||
const { instance, config: s3CosConfig } =
|
||||
this.getConfigAndInstance(application, extraFile.bucket!);
|
||||
|
||||
const result = await instance.listParts(
|
||||
extraFile.bucket!,
|
||||
key,
|
||||
extraFile.chunkInfo!.uploadId!,
|
||||
s3CosConfig.endpoint!,
|
||||
101,
|
||||
);
|
||||
|
||||
assert(
|
||||
result.isTruncated === false,
|
||||
`分片数量超过101,无法列出所有分片信息,不应当出现这个情况,触发器中已经限制了最大分片数量为100`
|
||||
);
|
||||
|
||||
return {
|
||||
parts: result.parts.map((part) => ({
|
||||
partNumber: part.partNumber,
|
||||
etag: part.eTag,
|
||||
size: part.size,
|
||||
lastModified: part.lastModified!,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -174,4 +174,15 @@ export default class TencentYunBackend extends TencentYun implements CosBackend<
|
|||
): Promise<void> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
async listMultipartUploads(
|
||||
application: EntityDict['application']['Schema'],
|
||||
extraFile: OpSchema,
|
||||
context: BRC<EntityDict>,
|
||||
){
|
||||
return {
|
||||
parts: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,4 +81,15 @@ export default class UnknownBackend extends Unknown implements CosBackend<Entity
|
|||
): Promise<void> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
async listMultipartUploads(
|
||||
application: EntityDict['application']['Schema'],
|
||||
extraFile: OpSchema,
|
||||
context: BRC<EntityDict>,
|
||||
){
|
||||
return {
|
||||
parts: [],
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -111,4 +111,15 @@ export default class WechatBackend extends Wechat implements CosBackend<EntityDi
|
|||
): Promise<void> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
async listMultipartUploads(
|
||||
application: EntityDict['application']['Schema'],
|
||||
extraFile: OpSchema,
|
||||
context: BRC<EntityDict>,
|
||||
){
|
||||
return {
|
||||
parts: [],
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ export async function sliceFileForMp(filePath: string, chunkSize: number, partCo
|
|||
|
||||
chunks.push(tempFilePath);
|
||||
} catch (err) {
|
||||
console.error('分片读取失败:', err);
|
||||
// 清理已创建的临时文件
|
||||
await cleanTempFiles(chunks);
|
||||
throw new OakUploadException(`分片 ${i + 1} 读取失败: ${err}`);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { Watcher, BBWatcher } from 'oak-domain/lib/types/Watcher';
|
|||
import { getCosBackend } from '../utils/cos/index.backend';
|
||||
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
||||
import { groupBy } from 'oak-domain/lib/utils/lodash';
|
||||
import { extraFileProjection } from '../types/Projection';
|
||||
import applicationPassport from '../components/applicationPassport';
|
||||
|
||||
async function checkWhetherSuccess(context: BackendRuntimeContext<EntityDict>, applicationId: string, rows: EntityDict['extraFile']['OpSchema'][]) {
|
||||
const successIds: string[] = [];
|
||||
|
|
@ -74,6 +76,9 @@ const watchers: Watcher<
|
|||
$lt: deadline,
|
||||
},
|
||||
uploadState: 'uploading',
|
||||
enableChunkedUpload: {
|
||||
$exists: false,
|
||||
}
|
||||
};
|
||||
},
|
||||
projection: {
|
||||
|
|
@ -92,6 +97,41 @@ const watchers: Watcher<
|
|||
await checkWhetherSuccess(context, appId, eg[appId] as EntityDict['extraFile']['OpSchema'][]);
|
||||
}
|
||||
|
||||
return {
|
||||
extraFile: {
|
||||
update: data.length,
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '确定uploading的文件状态',
|
||||
entity: 'extraFile',
|
||||
filter: async () => {
|
||||
const now = Date.now();
|
||||
const deadline = process.env.NODE_ENV === 'production' ? now - 3 * 24 * 60 * 60 * 1000 : now - 60 * 1000;
|
||||
return {
|
||||
$$updateAt$$: {
|
||||
$lt: deadline,
|
||||
},
|
||||
uploadState: 'uploading',
|
||||
enableChunkedUpload: true
|
||||
};
|
||||
},
|
||||
projection: {
|
||||
...extraFileProjection,
|
||||
application: {
|
||||
...applicationPassport,
|
||||
}
|
||||
},
|
||||
fn: async (context, data) => {
|
||||
const eg = groupBy(data, 'applicationId');
|
||||
|
||||
for (const appId in eg) {
|
||||
// 这里检查需要分片上传的文件信息,但是考虑到可能还在上传之类的,所以先不管,后面再处理
|
||||
// 1. 要去查询分片信息,看看是不是都有etag了,如果都有etag了,就说明上传完成了,否则就标记为失败
|
||||
}
|
||||
|
||||
return {
|
||||
extraFile: {
|
||||
update: data.length,
|
||||
|
|
|
|||
Loading…
Reference in New Issue