From 7a315302a86f5a725b7fcad3e14ed7188a429866 Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Wed, 23 Apr 2025 22:17:13 +0800 Subject: [PATCH] refactor: add chunked execution mechanism for post batch operations (#7378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /area ui /kind improvement /milestone 2.20.x #### What this PR does / why we need it: Use `Promise.all` to execute part of the batch operation logic of the post in chunks to optimize the execution performance. #### Which issue(s) this PR fixes: Fixes # #### Does this PR introduce a user-facing change? ```release-note 优化文章部分批量操作的执行性能 ``` --- .../contents/posts/DeletedPostList.vue | 65 ++++++----- .../modules/contents/posts/PostList.vue | 42 ++++--- .../components/PostBatchSettingModal.vue | 106 +++++++++--------- 3 files changed, 123 insertions(+), 90 deletions(-) diff --git a/ui/console-src/modules/contents/posts/DeletedPostList.vue b/ui/console-src/modules/contents/posts/DeletedPostList.vue index 85090a37b..6ea365d71 100644 --- a/ui/console-src/modules/contents/posts/DeletedPostList.vue +++ b/ui/console-src/modules/contents/posts/DeletedPostList.vue @@ -23,6 +23,7 @@ import { VStatusDot, } from "@halo-dev/components"; import { useQuery } from "@tanstack/vue-query"; +import { chunk } from "lodash-es"; import { ref, watch } from "vue"; import { useI18n } from "vue-i18n"; import PostTag from "./tags/components/PostTag.vue"; @@ -109,13 +110,18 @@ const handleDeletePermanentlyInBatch = async () => { confirmText: t("core.common.buttons.confirm"), cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { - await Promise.all( - selectedPostNames.value.map((name) => { - return coreApiClient.content.post.deletePost({ - name, - }); - }) - ); + const chunks = chunk(selectedPostNames.value, 5); + + for (const chunk of chunks) { + await Promise.all( + chunk.map((name) => { + return coreApiClient.content.post.deletePost({ + name, + }); + }) + ); + } + await refetch(); selectedPostNames.value = []; @@ -158,28 +164,33 @@ const handleRecoveryInBatch = async () => { confirmText: t("core.common.buttons.confirm"), cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { - await Promise.all( - selectedPostNames.value.map((name) => { - const isPostExist = posts.value?.some( - (item) => item.post.metadata.name === name - ); + const chunks = chunk(selectedPostNames.value, 5); - if (!isPostExist) { - return Promise.resolve(); - } + for (const chunk of chunks) { + await Promise.all( + chunk.map((name) => { + const isPostExist = posts.value?.some( + (item) => item.post.metadata.name === name + ); + + if (!isPostExist) { + return Promise.resolve(); + } + + return coreApiClient.content.post.patchPost({ + name: name, + jsonPatchInner: [ + { + op: "add", + path: "/spec/deleted", + value: false, + }, + ], + }); + }) + ); + } - return coreApiClient.content.post.patchPost({ - name: name, - jsonPatchInner: [ - { - op: "add", - path: "/spec/deleted", - value: false, - }, - ], - }); - }) - ); await refetch(); selectedPostNames.value = []; diff --git a/ui/console-src/modules/contents/posts/PostList.vue b/ui/console-src/modules/contents/posts/PostList.vue index 13a80bf1c..b8e36b1e3 100644 --- a/ui/console-src/modules/contents/posts/PostList.vue +++ b/ui/console-src/modules/contents/posts/PostList.vue @@ -23,6 +23,7 @@ import { } from "@halo-dev/components"; import { useQuery } from "@tanstack/vue-query"; import { useRouteQuery } from "@vueuse/router"; +import { chunk } from "lodash-es"; import type { Ref } from "vue"; import { computed, provide, ref, watch } from "vue"; import { useI18n } from "vue-i18n"; @@ -266,13 +267,18 @@ const handleDeleteInBatch = async () => { confirmText: t("core.common.buttons.confirm"), cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { - await Promise.all( - selectedPostNames.value.map((name) => { - return consoleApiClient.content.post.recyclePost({ - name, - }); - }) - ); + const chunks = chunk(selectedPostNames.value, 5); + + for (const chunk of chunks) { + await Promise.all( + chunk.map((name) => { + return consoleApiClient.content.post.recyclePost({ + name, + }); + }) + ); + } + await refetch(); selectedPostNames.value = []; @@ -288,9 +294,14 @@ const handlePublishInBatch = async () => { confirmText: t("core.common.buttons.confirm"), cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { - for (const i in selectedPostNames.value) { - const name = selectedPostNames.value[i]; - await consoleApiClient.content.post.publishPost({ name }); + const chunks = chunk(selectedPostNames.value, 5); + + for (const chunk of chunks) { + await Promise.all( + chunk.map((name) => { + return consoleApiClient.content.post.publishPost({ name }); + }) + ); } await refetch(); @@ -308,9 +319,14 @@ const handleCancelPublishInBatch = async () => { confirmText: t("core.common.buttons.confirm"), cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { - for (const i in selectedPostNames.value) { - const name = selectedPostNames.value[i]; - await consoleApiClient.content.post.unpublishPost({ name }); + const chunks = chunk(selectedPostNames.value, 5); + + for (const chunk of chunks) { + await Promise.all( + chunk.map((name) => { + return consoleApiClient.content.post.unpublishPost({ name }); + }) + ); } await refetch(); diff --git a/ui/console-src/modules/contents/posts/components/PostBatchSettingModal.vue b/ui/console-src/modules/contents/posts/components/PostBatchSettingModal.vue index 1b68e249c..a852a5b40 100644 --- a/ui/console-src/modules/contents/posts/components/PostBatchSettingModal.vue +++ b/ui/console-src/modules/contents/posts/components/PostBatchSettingModal.vue @@ -6,6 +6,7 @@ import { } from "@halo-dev/api-client"; import { Toast, VButton, VModal, VSpace } from "@halo-dev/components"; import { useMutation, useQueryClient } from "@tanstack/vue-query"; +import { chunk } from "lodash-es"; import { ref } from "vue"; import { useI18n } from "vue-i18n"; @@ -50,61 +51,66 @@ const modal = ref | null>(null); const { mutate, isLoading } = useMutation({ mutationKey: ["batch-update-posts"], mutationFn: async ({ data }: { data: FormData }) => { - for (const key in props.posts) { - const post = props.posts[key]; - const jsonPatchInner: JsonPatchInner[] = []; - if (data.category.enabled) { - jsonPatchInner.push({ - op: "add", - path: "/spec/categories", - value: computeArrayPatchValue( - data.category.op, - post.post.spec.categories || [], - data.category.names || [] - ), - }); - } + const postChunks = chunk(props.posts, 5); - if (data.tag.enabled) { - jsonPatchInner.push({ - op: "add", - path: "/spec/tags", - value: computeArrayPatchValue( - data.tag.op, - post.post.spec.tags || [], - data.tag.names || [] - ), - }); - } + for (const postChunk of postChunks) { + await Promise.all( + postChunk.map((post) => { + const jsonPatchInner: JsonPatchInner[] = []; + if (data.category.enabled) { + jsonPatchInner.push({ + op: "add", + path: "/spec/categories", + value: computeArrayPatchValue( + data.category.op, + post.post.spec.categories || [], + data.category.names || [] + ), + }); + } - if (data.owner.enabled) { - jsonPatchInner.push({ - op: "add", - path: "/spec/owner", - value: data.owner.value, - }); - } + if (data.tag.enabled) { + jsonPatchInner.push({ + op: "add", + path: "/spec/tags", + value: computeArrayPatchValue( + data.tag.op, + post.post.spec.tags || [], + data.tag.names || [] + ), + }); + } - if (data.visible.enabled) { - jsonPatchInner.push({ - op: "add", - path: "/spec/visible", - value: data.visible.value, - }); - } + if (data.owner.enabled) { + jsonPatchInner.push({ + op: "add", + path: "/spec/owner", + value: data.owner.value, + }); + } - if (data.allowComment.enabled) { - jsonPatchInner.push({ - op: "add", - path: "/spec/allowComment", - value: data.allowComment.value, - }); - } + if (data.visible.enabled) { + jsonPatchInner.push({ + op: "add", + path: "/spec/visible", + value: data.visible.value, + }); + } - await coreApiClient.content.post.patchPost({ - name: post.post.metadata.name, - jsonPatchInner, - }); + if (data.allowComment.enabled) { + jsonPatchInner.push({ + op: "add", + path: "/spec/allowComment", + value: data.allowComment.value, + }); + } + + return coreApiClient.content.post.patchPost({ + name: post.post.metadata.name, + jsonPatchInner, + }); + }) + ); } Toast.success(t("core.common.toast.save_success"));