perf(platform): 创作页加载 - 合并用户数据与余额请求并按路由延迟加载
This commit is contained in:
@@ -12,6 +12,14 @@ export type RemoteUserDataSummary = {
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type RemoteUserDataSnapshot = {
|
||||
assets: Asset[];
|
||||
projects: CanvasProject[];
|
||||
};
|
||||
|
||||
export function getRemoteUserDataSnapshot() {
|
||||
return request<RemoteUserDataSnapshot>(api.get("/user-data/snapshot"));
|
||||
}
|
||||
|
||||
export function listRemoteAssets() {
|
||||
return request<{ assets: RemoteUserDataSummary[] }>(api.get("/assets"));
|
||||
|
||||
@@ -2,7 +2,7 @@ import localforage from "localforage";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import { getActiveUserScope } from "@/lib/user-scope";
|
||||
import { resourceFileUrl, resourceIdFromStorageKey, resourceStorageKey, resolveResourceUrl, uploadResourceFile } from "@/services/api/resources";
|
||||
import { resourceFileUrl, resourceIdFromStorageKey, resourceStorageKey, uploadResourceFile } from "@/services/api/resources";
|
||||
import { getCachedResourceBlob, getCachedResourceObjectUrl, primeResourceBlobCache } from "@/services/resource-blob-cache";
|
||||
|
||||
export type UploadedFile = { url: string; storageKey: string; bytes: number; mimeType: string; width?: number; height?: number; durationMs?: number };
|
||||
@@ -32,9 +32,10 @@ export async function uploadMediaFile(input: string | Blob, prefix = "file"): Pr
|
||||
|
||||
export async function resolveMediaUrl(storageKey?: string, fallback = "") {
|
||||
if (!storageKey) return fallback;
|
||||
if (resourceIdFromStorageKey(storageKey)) {
|
||||
const resourceId = resourceIdFromStorageKey(storageKey);
|
||||
if (resourceId) {
|
||||
const cached = await getCachedResourceObjectUrl(storageKey).catch(() => "");
|
||||
return cached || resolveResourceUrl(storageKey, fallback);
|
||||
return cached || resourceFileUrl(resourceId);
|
||||
}
|
||||
const cached = objectUrls.get(storageKey);
|
||||
if (cached) return cached;
|
||||
|
||||
@@ -3,7 +3,7 @@ import localforage from "localforage";
|
||||
import { nanoid } from "nanoid";
|
||||
import { readImageMeta } from "@/lib/image-utils";
|
||||
import { getActiveUserScope } from "@/lib/user-scope";
|
||||
import { importResourceFromUrl, isResourceUrl, resourceFileUrl, resourceIdFromStorageKey, resourceStorageKey, resolveResourceUrl, uploadResourceFile } from "@/services/api/resources";
|
||||
import { importResourceFromUrl, isResourceUrl, resourceFileUrl, resourceIdFromStorageKey, resourceStorageKey, uploadResourceFile } from "@/services/api/resources";
|
||||
import { cacheResourceObjectUrl, getCachedResourceBlob, getCachedResourceObjectUrl, primeResourceBlobCache } from "@/services/resource-blob-cache";
|
||||
|
||||
export type UploadedImage = {
|
||||
@@ -65,14 +65,15 @@ function shouldImportRemoteImage(input: string) {
|
||||
|
||||
export async function resolveImageUrl(storageKey?: string, fallback = "", options?: { cacheMiss?: boolean }) {
|
||||
if (!storageKey) return fallback;
|
||||
if (resourceIdFromStorageKey(storageKey)) {
|
||||
const resourceId = resourceIdFromStorageKey(storageKey);
|
||||
if (resourceId) {
|
||||
const cached = await getCachedResourceObjectUrl(storageKey).catch(() => "");
|
||||
if (cached) return cached;
|
||||
if (options?.cacheMiss) {
|
||||
const populated = await cacheResourceObjectUrl(storageKey).catch(() => "");
|
||||
if (populated) return populated;
|
||||
}
|
||||
return resolveResourceUrl(storageKey, fallback);
|
||||
return resourceFileUrl(resourceId);
|
||||
}
|
||||
const cached = objectUrls.get(storageKey);
|
||||
if (cached) return cached;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getMediaBlob } from "@/services/file-storage";
|
||||
import { getImageBlob, resolveImageUrl } from "@/services/image-storage";
|
||||
import { deleteRemoteAsset, deleteRemoteCanvasProject, getRemoteAsset, getRemoteCanvasProject, listRemoteAssets, listRemoteCanvasProjects, upsertRemoteAsset, upsertRemoteCanvasProject, type RemoteUserDataSummary } from "@/services/api/user-data";
|
||||
import { deleteRemoteAsset, deleteRemoteCanvasProject, getRemoteUserDataSnapshot, upsertRemoteAsset, upsertRemoteCanvasProject } from "@/services/api/user-data";
|
||||
import { resourceFileUrl, resourceIdFromStorageKey, resourceStorageKey, uploadResourceFile } from "@/services/api/resources";
|
||||
import type { Asset } from "@/stores/use-asset-store";
|
||||
import { useAssetStore } from "@/stores/use-asset-store";
|
||||
@@ -23,17 +23,13 @@ export async function syncRemoteUserData(userId?: string | null) {
|
||||
if (!activeRemoteUserId) return;
|
||||
applyingRemoteState = true;
|
||||
try {
|
||||
const [remoteCanvas, remoteAssets] = await Promise.all([listRemoteCanvasProjects(), listRemoteAssets()]);
|
||||
remoteProjectVersions = versionMap(remoteCanvas.projects);
|
||||
remoteAssetVersions = versionMap(remoteAssets.assets);
|
||||
const remoteSnapshot = await getRemoteUserDataSnapshot();
|
||||
remoteProjectVersions = versionMap(remoteSnapshot.projects);
|
||||
remoteAssetVersions = versionMap(remoteSnapshot.assets);
|
||||
const localProjects = useCanvasStore.getState().projects;
|
||||
const localAssets = useAssetStore.getState().assets;
|
||||
const [changedProjects, changedAssets] = await Promise.all([
|
||||
fetchNewerRemoteItems(localProjects, remoteCanvas.projects, async (id) => (await getRemoteCanvasProject(id)).project),
|
||||
fetchNewerRemoteItems(localAssets, remoteAssets.assets, async (id) => (await getRemoteAsset(id)).asset),
|
||||
]);
|
||||
const mergedProjects = mergeById(localProjects, changedProjects);
|
||||
const mergedAssets = mergeById(localAssets, await hydrateAssets(changedAssets));
|
||||
const mergedProjects = mergeById(localProjects, remoteSnapshot.projects);
|
||||
const mergedAssets = mergeById(localAssets, await hydrateAssets(remoteSnapshot.assets));
|
||||
useCanvasStore.getState().replaceProjects(mergedProjects);
|
||||
useAssetStore.getState().replaceAssets(mergedAssets);
|
||||
} finally {
|
||||
@@ -128,8 +124,8 @@ async function saveRemoteUserDataBatch() {
|
||||
try {
|
||||
const currentProjects = useCanvasStore.getState().projects;
|
||||
const currentAssets = useAssetStore.getState().assets;
|
||||
const dirtyProjects = currentProjects.filter((item) => remoteProjectVersions.get(item.id) !== item.updatedAt);
|
||||
const dirtyAssets = currentAssets.filter((item) => remoteAssetVersions.get(item.id) !== item.updatedAt);
|
||||
const dirtyProjects = currentProjects.filter((item) => !sameVersion(remoteProjectVersions.get(item.id), item.updatedAt));
|
||||
const dirtyAssets = currentAssets.filter((item) => !sameVersion(remoteAssetVersions.get(item.id), item.updatedAt));
|
||||
const deletedProjectIds = missingIds(remoteProjectVersions, currentProjects);
|
||||
const deletedAssetIds = missingIds(remoteAssetVersions, currentAssets);
|
||||
if (!dirtyProjects.length && !dirtyAssets.length && !deletedProjectIds.length && !deletedAssetIds.length) return;
|
||||
@@ -278,17 +274,8 @@ function mergeById<T extends { id?: string; updatedAt?: string }>(local: T[], re
|
||||
return Array.from(items.values()).sort((a, b) => timeValue(b.updatedAt) - timeValue(a.updatedAt));
|
||||
}
|
||||
|
||||
async function fetchNewerRemoteItems<T extends { id: string; updatedAt?: string }>(local: T[], remote: RemoteUserDataSummary[], fetchItem: (id: string) => Promise<T>) {
|
||||
const localById = new Map(local.map((item) => [item.id, item]));
|
||||
const pending = remote.filter((item) => {
|
||||
const current = localById.get(item.id);
|
||||
return !current || timeValue(item.updatedAt) > timeValue(current.updatedAt);
|
||||
});
|
||||
return Promise.all(pending.map((item) => fetchItem(item.id)));
|
||||
}
|
||||
|
||||
function versionMap(items: RemoteUserDataSummary[]) {
|
||||
return new Map(items.map((item) => [item.id, item.updatedAt]));
|
||||
function versionMap(items: Array<{ id: string; updatedAt?: string }>) {
|
||||
return new Map<string, string>(items.map((item) => [item.id, item.updatedAt || ""]));
|
||||
}
|
||||
|
||||
function missingIds<T extends { id: string }>(remote: Map<string, string>, local: T[]) {
|
||||
@@ -306,6 +293,10 @@ function timeValue(value?: string) {
|
||||
return Number.isFinite(time) ? time : 0;
|
||||
}
|
||||
|
||||
function sameVersion(remote?: string, local?: string) {
|
||||
return Boolean(remote && local) && timeValue(remote) === timeValue(local);
|
||||
}
|
||||
|
||||
function isLocalStorageKey(value: string) {
|
||||
return LOCAL_STORAGE_KEY_PATTERN.test(value) && !resourceIdFromStorageKey(value);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user