fix(server): avoid get object content when syncing (#9402)

This commit is contained in:
liuyi
2024-12-28 00:25:15 +08:00
committed by GitHub
parent 6ebefbbf2b
commit 378db1054b
5 changed files with 75 additions and 37 deletions
@@ -24,6 +24,10 @@ export interface WorkspaceEvents {
workspaceId: Workspace['id']; workspaceId: Workspace['id'];
key: string; key: string;
}>; }>;
sync: Payload<{
workspaceId: Workspace['id'];
key: string;
}>;
}; };
} }
@@ -61,6 +61,15 @@ export class FsStorageProvider implements StorageProvider {
this.logger.verbose(`Object \`${key}\` put`); this.logger.verbose(`Object \`${key}\` put`);
} }
async head(key: string) {
const metadata = this.readMetadata(key);
if (!metadata) {
this.logger.verbose(`Object \`${key}\` not found`);
return undefined;
}
return metadata;
}
async get(key: string): Promise<{ async get(key: string): Promise<{
body?: Readable; body?: Readable;
metadata?: GetObjectMetadata; metadata?: GetObjectMetadata;
@@ -34,6 +34,7 @@ export interface StorageProvider {
body: BlobInputType, body: BlobInputType,
metadata?: PutObjectMetadata metadata?: PutObjectMetadata
): Promise<void>; ): Promise<void>;
head(key: string): Promise<GetObjectMetadata | undefined>;
get( get(
key: string key: string
): Promise<{ body?: BlobOutputType; metadata?: GetObjectMetadata }>; ): Promise<{ body?: BlobOutputType; metadata?: GetObjectMetadata }>;
@@ -32,7 +32,7 @@ export class WorkspaceBlobStorage {
const meta: PutObjectMetadata = autoMetadata(blob); const meta: PutObjectMetadata = autoMetadata(blob);
await this.provider.put(`${workspaceId}/${key}`, blob, meta); await this.provider.put(`${workspaceId}/${key}`, blob, meta);
this.trySyncBlobMeta(workspaceId, key, { await this.upsert(workspaceId, key, {
contentType: meta.contentType ?? 'application/octet-stream', contentType: meta.contentType ?? 'application/octet-stream',
contentLength: blob.length, contentLength: blob.length,
lastModified: new Date(), lastModified: new Date(),
@@ -119,53 +119,48 @@ export class WorkspaceBlobStorage {
private trySyncBlobsMeta(workspaceId: string, blobs: ListObjectsMetadata[]) { private trySyncBlobsMeta(workspaceId: string, blobs: ListObjectsMetadata[]) {
for (const blob of blobs) { for (const blob of blobs) {
this.trySyncBlobMeta(workspaceId, blob.key); this.event.emit('workspace.blob.sync', {
workspaceId,
key: blob.key,
});
} }
} }
private trySyncBlobMeta( private async upsert(
workspaceId: string, workspaceId: string,
key: string, key: string,
meta?: GetObjectMetadata meta: GetObjectMetadata
) { ) {
setImmediate(() => { await this.db.blob.upsert({
this.syncBlobMeta(workspaceId, key, meta).catch(() => { where: {
/* never throw */ workspaceId_key: {
}); workspaceId,
key,
},
},
update: {
mime: meta.contentType,
size: meta.contentLength,
},
create: {
workspaceId,
key,
mime: meta.contentType,
size: meta.contentLength,
},
}); });
} }
private async syncBlobMeta( @OnEvent('workspace.blob.sync')
workspaceId: string, async syncBlobMeta({
key: string, workspaceId,
meta?: GetObjectMetadata key,
) { }: EventPayload<'workspace.blob.sync'>) {
try { try {
if (!meta) { const meta = await this.provider.head(`${workspaceId}/${key}`);
const blob = await this.get(workspaceId, key);
meta = blob.metadata;
blob.body?.destroy();
}
if (meta) { if (meta) {
await this.db.blob.upsert({ await this.upsert(workspaceId, key, meta);
where: {
workspaceId_key: {
workspaceId,
key,
},
},
update: {
mime: meta.contentType,
size: meta.contentLength,
},
create: {
workspaceId,
key,
mime: meta.contentType,
size: meta.contentLength,
},
});
} else { } else {
await this.db.blob.deleteMany({ await this.db.blob.deleteMany({
where: { where: {
@@ -1,9 +1,10 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* oxlint-disable @typescript-eslint/no-non-null-assertion */
import { Readable } from 'node:stream'; import { Readable } from 'node:stream';
import { import {
DeleteObjectCommand, DeleteObjectCommand,
GetObjectCommand, GetObjectCommand,
HeadObjectCommand,
ListObjectsV2Command, ListObjectsV2Command,
NoSuchKey, NoSuchKey,
PutObjectCommand, PutObjectCommand,
@@ -75,6 +76,34 @@ export class S3StorageProvider implements StorageProvider {
} }
} }
async head(key: string) {
try {
const obj = await this.client.send(
new HeadObjectCommand({
Bucket: this.bucket,
Key: key,
})
);
return {
contentType: obj.ContentType!,
contentLength: obj.ContentLength!,
lastModified: obj.LastModified!,
checksumCRC32: obj.ChecksumCRC32,
};
} catch (e) {
// 404
if (e instanceof NoSuchKey) {
this.logger.verbose(`Object \`${key}\` not found`);
return undefined;
} else {
throw new Error(`Failed to head object \`${key}\``, {
cause: e,
});
}
}
}
async get(key: string): Promise<{ async get(key: string): Promise<{
body?: Readable; body?: Readable;
metadata?: GetObjectMetadata; metadata?: GetObjectMetadata;