diff --git a/client/packages/lowcoder-sdk/package.json b/client/packages/lowcoder-sdk/package.json index f5df08715..360c79e3b 100644 --- a/client/packages/lowcoder-sdk/package.json +++ b/client/packages/lowcoder-sdk/package.json @@ -1,6 +1,6 @@ { "name": "lowcoder-sdk", - "version": "2.6.3", + "version": "2.6.4", "type": "module", "files": [ "src", @@ -25,6 +25,9 @@ }, "./dist/style.css": { "import": "./dist/style.css" + }, + "./dist/chunks/": { + "import": "./dist/chunks/" } }, "scripts": { diff --git a/client/packages/lowcoder-sdk/vite.config.mts b/client/packages/lowcoder-sdk/vite.config.mts index 903b336f6..3cd7b5c45 100644 --- a/client/packages/lowcoder-sdk/vite.config.mts +++ b/client/packages/lowcoder-sdk/vite.config.mts @@ -36,17 +36,7 @@ export const viteConfig: UserConfig = { }, base: ensureLastSlash(process.env.PUBLIC_URL), build: { - minify: "terser", - terserOptions: { - compress: { - drop_console: true, - drop_debugger: true, - pure_funcs: ["console.info", "console.debug", "console.log"], - }, - format: { - comments: false, - }, - }, + minify: "terser", chunkSizeWarningLimit: 500, lib: { formats: ["es"], @@ -56,7 +46,7 @@ export const viteConfig: UserConfig = { }, rollupOptions: { treeshake: { - moduleSideEffects: false, + moduleSideEffects: true, propertyReadSideEffects: false, tryCatchDeoptimization: false, unknownGlobalSideEffects: false, @@ -64,38 +54,23 @@ export const viteConfig: UserConfig = { external: ["react", "react-dom"], output: { chunkFileNames: "chunks/[name]-[hash].js", - entryFileNames: "entry/[name]-[hash].js", - assetFileNames: "assets/[name]-[hash].[ext]", + entryFileNames: "lowcoder-sdk.js", + assetFileNames: "style.css", manualChunks: (id) => { if (id.includes("node_modules")) { - // CORE FRAMEWORK CHUNKS - if (id.includes("react")) return "react"; - if (id.includes("react-dom")) return "react-dom"; - if (id.includes("react-router")) return "react-router"; - if (id.includes("react-redux")) return "react-redux"; - if (id.includes("redux")) return "redux"; - if (id.includes("redux-saga")) return "redux-saga"; - // UI LIBRARIES if (id.includes("@ant-design/icons")) return "ant-design-icons"; - if (id.includes("antd")) return "antd"; + if (id.includes("node_modules/antd")) return "antd"; if (id.includes("styled-components")) return "styled-components"; // 🔹 BARCODE & QR CODE PROCESSING - if (id.includes("zxing") || id.includes("Barcode") || id.includes("QRCode") || id.includes("PDF417")) return "barcode"; - - // CHARTING & DATA VISUALIZATION - if (id.includes("echarts")) return "echarts"; - if (id.includes("echarts-wordcloud")) return "echarts-wordcloud"; - if (id.includes("d3")) return "d3"; + if (id.includes("react-qr-barcode-scanner")) return "barcode"; // TEXT EDITORS & PARSERS if (id.includes("codemirror")) return "codemirror"; if (id.includes("quill")) return "quill"; if (id.includes("react-json-view")) return "react-json-view"; - if (id.includes("react-markdown")) return "react-markdown"; if (id.includes("react-quill")) return "react-quill"; - if (id.includes("remark") || id.includes("rehype") || id.includes("markdown")) return "markdown-parsers"; if (id.includes("remark-gfm")) return "remark-gfm"; if (id.includes("rehype-raw")) return "rehype-raw"; if (id.includes("rehype-sanitize")) return "rehype-sanitize"; @@ -133,7 +108,6 @@ export const viteConfig: UserConfig = { if (id.includes("xlsx")) return "xlsx"; if (id.includes("alasql")) return "alasql"; if (id.includes("sql-formatter")) return "sql-formatter"; - if (id.includes("tern")) return "tern"; // NETWORK & HTTP if (id.includes("axios")) return "axios"; @@ -158,41 +132,38 @@ export const viteConfig: UserConfig = { if (id.includes("cnchar")) return "cnchar"; if (id.includes("hotkeys-js")) return "hotkeys-js"; if (id.includes("loglevel")) return "loglevel"; - if (id.includes("qrcode-react")) return "qrcode-react"; + if (id.includes("qrcode.react")) return "qrcode-react"; if (id.includes("react-joyride")) return "react-joyride"; if (id.includes("rc-trigger")) return "rc-trigger"; if (id.includes("really-relaxed-json")) return "really-relaxed-json"; if (id.includes("simplebar-react")) return "simplebar-react"; - return "vendor"; + if (id.includes("react-documents")) return "react-documents"; + if (id.includes("react-colorful")) return "react-colorful"; + if (id.includes("react-best-gradient-color-picker")) return "react-best-gradient-color-picker"; + if (id.includes("@supabase/supabase-js")) return "supabase"; + return null; } - if (id.includes("src/api")) return "api"; - if (id.includes("src/appView")) return "appView"; - if (id.includes("src/base")) return "base"; - if (id.includes("src/constants")) return "constants"; - if (id.includes("src/i18n")) return "i18n"; - if (id.includes("src/ide")) return "ide"; - if (id.includes("src/layout")) return "layout"; - if (id.includes("src/pages")) return "pages"; - if (id.includes("src/redux")) return "app_redux"; - if (id.includes("src/comps")) return "comps"; - if (id.includes("comps/comps")) return "comps2"; - if (id.includes("comps/controls")) return "controls"; - if (id.includes("comps/queries")) return "queries"; - if (id.includes("comps/utils")) return "utils"; - if (id.includes("src/hooks")) return "hooks"; - if (id.includes("src/util")) return "util"; - return "common"; // 📦 Internal app shared code - }, + return null; + } }, experimental: { minChunkSize: 300000, // 📏 Force smaller chunks (~300KB) }, plugins: [ - terser(), + terser({ + compress: { + drop_console: true, + drop_debugger: true, + pure_funcs: ["console.info", "console.debug", "console.log"], + }, + format: { + comments: /(@vite-ignore|webpackIgnore)/ + }, + }) as PluginOption, strip({ functions: ["console.log", "debugger"], // ✅ Remove logs sourceMap: true, - }), + }) as PluginOption, ], onwarn: (warning, warn) => { if (warning.code === 'MODULE_LEVEL_DIRECTIVE') { diff --git a/server/api-service/lowcoder-dependencies/pom.xml b/server/api-service/lowcoder-dependencies/pom.xml index 3f8b6b703..0787bcfcf 100644 --- a/server/api-service/lowcoder-dependencies/pom.xml +++ b/server/api-service/lowcoder-dependencies/pom.xml @@ -26,7 +26,7 @@ org.lowcoder.plugin lowcoder-plugin-api - 2.3.0 + 2.3.1 diff --git a/server/api-service/lowcoder-infra/src/main/java/org/lowcoder/infra/event/AbstractEvent.java b/server/api-service/lowcoder-infra/src/main/java/org/lowcoder/infra/event/AbstractEvent.java index 8514624a2..7f96689f5 100644 --- a/server/api-service/lowcoder-infra/src/main/java/org/lowcoder/infra/event/AbstractEvent.java +++ b/server/api-service/lowcoder-infra/src/main/java/org/lowcoder/infra/event/AbstractEvent.java @@ -4,6 +4,8 @@ import lombok.Setter; import lombok.experimental.SuperBuilder; import org.lowcoder.plugin.api.event.LowcoderEvent; +import org.lowcoder.sdk.constants.GlobalContext; +import reactor.util.context.ContextView; import java.lang.reflect.Field; import java.util.HashMap; @@ -19,6 +21,7 @@ public abstract class AbstractEvent implements LowcoderEvent protected final Boolean isAnonymous; private final String ipAddress; protected Map details; + protected Map eventHeaders; @Setter private static String environmentID; @@ -42,7 +45,10 @@ public B detail(String name, String value) } } - public void populateDetails() { + public void populateDetails(ContextView contextView) { + //populate eventHeaders field + eventHeaders = contextView.get(GlobalContext.HEADERS); + if (details == null) { details = new HashMap<>(); } @@ -57,5 +63,8 @@ public void populateDetails() { } details.put("environmentId", environmentID); + if(!details.containsKey("headers")) { + details.put("headers", eventHeaders); + } } } diff --git a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/constants/GlobalContext.java b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/constants/GlobalContext.java index e52624cec..735ec57ff 100644 --- a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/constants/GlobalContext.java +++ b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/constants/GlobalContext.java @@ -16,4 +16,5 @@ public class GlobalContext { public static final String CURRENT_ORG_MEMBER = "currentOrgMember"; public static final String DOMAIN = "domain"; + public static final String HEADERS = "headers"; } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/filter/ApiEventFilter.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/filter/ApiEventFilter.java index e8b75f8c3..b593469b8 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/filter/ApiEventFilter.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/filter/ApiEventFilter.java @@ -17,6 +17,7 @@ import org.springframework.web.server.WebFilterChain; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; +import reactor.util.context.ContextView; import java.nio.charset.StandardCharsets; import java.util.Optional; @@ -40,7 +41,7 @@ public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { String token = contextView.get(VISITOR_TOKEN); ((Mono) contextView.get(CURRENT_ORG_MEMBER)) .flatMap(orgMember -> { - emitEvent(exchange.getRequest(), token, orgMember); + emitEvent(exchange.getRequest(), token, orgMember, contextView); return Mono.empty(); }) .subscribeOn(Schedulers.boundedElastic()) @@ -51,7 +52,7 @@ public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { ); } - private void emitEvent(ServerHttpRequest request, String token, OrgMember orgMember) { + private void emitEvent(ServerHttpRequest request, String token, OrgMember orgMember, ContextView contextView) { MultiValueMap headers = writableHttpHeaders(request.getHeaders()); headers.remove("Cookie"); Optional ipAddressOptional = headers.remove("X-Real-IP").stream().findFirst(); @@ -69,7 +70,7 @@ private void emitEvent(ServerHttpRequest request, String token, OrgMember orgMem .queryParams(request.getQueryParams()) .ipAddress(ipAddress) .build(); - event.populateDetails(); + event.populateDetails(contextView); log.debug("API call event emitted for '{}' from org '{}' on URI: {}", orgMember.getUserId(), orgMember.getUserId(), request.getURI().getPath()); applicationEventPublisher.publishEvent(event); diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/filter/GlobalContextFilter.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/filter/GlobalContextFilter.java index f2ab85ca7..fe00a0065 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/filter/GlobalContextFilter.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/filter/GlobalContextFilter.java @@ -16,6 +16,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; +import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; @@ -32,6 +33,7 @@ import static org.lowcoder.sdk.constants.Authentication.isAnonymousUser; import static org.lowcoder.sdk.constants.GlobalContext.*; import static org.lowcoder.sdk.util.IDUtils.generate; +import static org.springframework.http.HttpHeaders.writableHttpHeaders; @Component @RequiredArgsConstructor @@ -107,6 +109,11 @@ private Map buildContextMap(ServerWebExchange serverWebExchange, contextMap.put(CURRENT_ORG_MEMBER, orgMemberService.getCurrentOrgMember(visitorId).cache()); contextMap.put(VISITOR_TOKEN, cookieHelper.getCookieToken(serverWebExchange)); contextMap.put(DOMAIN, UriUtils.getRefererDomainFromRequest(serverWebExchange)); + + //Analytics related fields + MultiValueMap headers = writableHttpHeaders(request.getHeaders()); + headers.remove("Cookie"); + contextMap.put(HEADERS, headers); return contextMap; } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/ApiCallEventPublisher.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/ApiCallEventPublisher.java index 590c8c7a8..5e6619000 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/ApiCallEventPublisher.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/ApiCallEventPublisher.java @@ -54,13 +54,13 @@ public Object handleAPICallEvent(ProceedingJoinPoint joinPoint) throws Throwable return sessionUserService.getVisitorToken() .zipWith(sessionUserService.getVisitorOrgMemberCacheSilent().defaultIfEmpty(OrgMember.NOT_EXIST)) .zipWith(ReactiveRequestContextHolder.getRequest()) - .doOnNext( + .delayUntil( tuple -> { String token = tuple.getT1().getT1(); OrgMember orgMember = tuple.getT1().getT2(); ServerHttpRequest request = tuple.getT2(); if (orgMember == OrgMember.NOT_EXIST) { - return; + return Mono.empty(); } MultiValueMap headers = writableHttpHeaders(request.getHeaders()); headers.remove("Cookie"); @@ -77,8 +77,11 @@ public Object handleAPICallEvent(ProceedingJoinPoint joinPoint) throws Throwable .queryParams(request.getQueryParams()) .ipAddress(ipAddress) .build(); - event.populateDetails(); - applicationEventPublisher.publishEvent(event); + return Mono.deferContextual(contextView -> { + event.populateDetails(contextView); + applicationEventPublisher.publishEvent(event); + return Mono.empty(); + }); }) .onErrorResume(throwable -> { log.error("handleAPICallEvent error {} for: {} ", joinPoint.getSignature().getName(), EventType.API_CALL_EVENT, throwable); diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/BusinessEventPublisher.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/BusinessEventPublisher.java index fe3119d32..85932a56f 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/BusinessEventPublisher.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/BusinessEventPublisher.java @@ -74,7 +74,7 @@ public Mono publishFolderCommonEvent(String folderId, String folderName, E return sessionUserService.getVisitorToken() .zipWith(sessionUserService.getVisitorOrgMemberCache()) - .doOnNext( + .delayUntil( tuple -> { String token = tuple.getT1(); OrgMember orgMember = tuple.getT2(); @@ -87,8 +87,11 @@ public Mono publishFolderCommonEvent(String folderId, String folderName, E .isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId())) .sessionHash(Hashing.sha512().hashString(token, StandardCharsets.UTF_8).toString()) .build(); - event.populateDetails(); - applicationEventPublisher.publishEvent(event); + return Mono.deferContextual(contextView -> { + event.populateDetails(contextView); + applicationEventPublisher.publishEvent(event); + return Mono.empty(); + }); }) .then() .onErrorResume(throwable -> { @@ -151,7 +154,7 @@ public Mono publishApplicationCommonEvent(ApplicationView applicationView, return Pair.of(category, description); }); }), TupleUtils::merge) - .doOnNext(tuple -> { + .delayUntil(tuple -> { OrgMember orgMember = tuple.getT1().getT1(); Optional optional = tuple.getT1().getT2(); Optional optionalFrom = tuple.getT1().getT3(); @@ -175,8 +178,11 @@ public Mono publishApplicationCommonEvent(ApplicationView applicationView, .isAnonymous(anonymous) .sessionHash(Hashing.sha512().hashString(token, StandardCharsets.UTF_8).toString()) .build(); - event.populateDetails(); - applicationEventPublisher.publishEvent(event); + return Mono.deferContextual(contextView -> { + event.populateDetails(contextView); + applicationEventPublisher.publishEvent(event); + return Mono.empty(); + }); }) .then() .onErrorResume(throwable -> { @@ -188,7 +194,7 @@ public Mono publishApplicationCommonEvent(ApplicationView applicationView, public Mono publishUserLoginEvent(String source) { return sessionUserService.getVisitorOrgMember().zipWith(sessionUserService.getVisitorToken()) - .doOnNext(tuple -> { + .delayUntil(tuple -> { OrgMember orgMember = tuple.getT1(); String token = tuple.getT2(); UserLoginEvent event = UserLoginEvent.builder() @@ -198,8 +204,11 @@ public Mono publishUserLoginEvent(String source) { .isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId())) .sessionHash(Hashing.sha512().hashString(token, StandardCharsets.UTF_8).toString()) .build(); - event.populateDetails(); - applicationEventPublisher.publishEvent(event); + return Mono.deferContextual(contextView -> { + event.populateDetails(contextView); + applicationEventPublisher.publishEvent(event); + return Mono.empty(); + }); }) .then() .onErrorResume(throwable -> { @@ -211,7 +220,7 @@ public Mono publishUserLoginEvent(String source) { public Mono publishUserLogoutEvent() { return sessionUserService.getVisitorOrgMemberCache() .zipWith(sessionUserService.getVisitorToken()) - .doOnNext(tuple -> { + .delayUntil(tuple -> { OrgMember orgMember = tuple.getT1(); String token = tuple.getT2(); UserLogoutEvent event = UserLogoutEvent.builder() @@ -220,8 +229,11 @@ public Mono publishUserLogoutEvent() { .isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId())) .sessionHash(Hashing.sha512().hashString(token, StandardCharsets.UTF_8).toString()) .build(); - event.populateDetails(); - applicationEventPublisher.publishEvent(event); + return Mono.deferContextual(contextView -> { + event.populateDetails(contextView); + applicationEventPublisher.publishEvent(event); + return Mono.empty(); + }); }) .then() .onErrorResume(throwable -> { @@ -244,7 +256,7 @@ public Mono publishGroupCreateEvent(Group group) { .isAnonymous(Authentication.isAnonymousUser(tuple.getT1().getUserId())) .sessionHash(Hashing.sha512().hashString(tuple.getT2(), StandardCharsets.UTF_8).toString()) .build(); - event.populateDetails(); + event.populateDetails(contextView); applicationEventPublisher.publishEvent(event); return Mono.empty(); })) @@ -272,7 +284,7 @@ public Mono publishGroupUpdateEvent(boolean publish, Group previousGroup, .isAnonymous(Authentication.isAnonymousUser(tuple.getT1().getUserId())) .sessionHash(Hashing.sha512().hashString(tuple.getT2(), StandardCharsets.UTF_8).toString()) .build(); - event.populateDetails(); + event.populateDetails(contextView); applicationEventPublisher.publishEvent(event); return Mono.empty(); })) @@ -300,7 +312,7 @@ public Mono publishGroupDeleteEvent(boolean publish, Group previousGroup) .isAnonymous(Authentication.isAnonymousUser(tuple.getT1().getUserId())) .sessionHash(Hashing.sha512().hashString(tuple.getT2(), StandardCharsets.UTF_8).toString()) .build(); - event.populateDetails(); + event.populateDetails(contextView); applicationEventPublisher.publishEvent(event); return Mono.empty(); })) @@ -337,7 +349,7 @@ public Mono publishGroupMemberAddEvent(boolean publish, String groupId, Ad .isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId())) .sessionHash(Hashing.sha512().hashString(token, StandardCharsets.UTF_8).toString()) .build(); - event.populateDetails(); + event.populateDetails(contextView); applicationEventPublisher.publishEvent(event); return Mono.empty(); })) @@ -374,7 +386,7 @@ public Mono publishGroupMemberRoleUpdateEvent(boolean publish, String grou .isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId())) .sessionHash(Hashing.sha512().hashString(tuple.getT4(), StandardCharsets.UTF_8).toString()) .build(); - event.populateDetails(); + event.populateDetails(contextView); applicationEventPublisher.publishEvent(event); return Mono.empty(); })) @@ -410,7 +422,7 @@ public Mono publishGroupMemberLeaveEvent(boolean publish, GroupMember grou .isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId())) .sessionHash(Hashing.sha512().hashString(tuple.getT4(), StandardCharsets.UTF_8).toString()) .build(); - event.populateDetails(); + event.populateDetails(contextView); applicationEventPublisher.publishEvent(event); return Mono.empty(); })) @@ -446,7 +458,7 @@ public Mono publishGroupMemberRemoveEvent(boolean publish, GroupMember pre .isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId())) .sessionHash(Hashing.sha512().hashString(tuple.getT4(), StandardCharsets.UTF_8).toString()) .build(); - event.populateDetails(); + event.populateDetails(contextView); applicationEventPublisher.publishEvent(event); return Mono.empty(); })) @@ -484,9 +496,11 @@ public Mono publishDatasourceEvent(Datasource datasource, EventType eventT .isAnonymous(Authentication.isAnonymousUser(tuple.getT1().getUserId())) .sessionHash(Hashing.sha512().hashString(tuple.getT2(), StandardCharsets.UTF_8).toString()) .build(); - event.populateDetails(); - applicationEventPublisher.publishEvent(event); - return Mono. empty(); + return Mono.deferContextual(contextView -> { + event.populateDetails(contextView); + applicationEventPublisher.publishEvent(event); + return Mono.empty(); + }); }) .onErrorResume(throwable -> { log.error("publishDatasourceEvent error.", throwable); @@ -535,9 +549,11 @@ public Mono publishDatasourcePermissionEvent(String datasourceId, .isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId())) .sessionHash(Hashing.sha512().hashString(tuple.getT3(), StandardCharsets.UTF_8).toString()) .build(); - datasourcePermissionEvent.populateDetails(); - applicationEventPublisher.publishEvent(datasourcePermissionEvent); - return Mono. empty(); + return Mono.deferContextual(contextView -> { + datasourcePermissionEvent.populateDetails(contextView); + applicationEventPublisher.publishEvent(datasourcePermissionEvent); + return Mono.empty(); + }); }) .onErrorResume(throwable -> { log.error("publishDatasourcePermissionEvent error.", throwable); @@ -552,7 +568,7 @@ public Mono publishLibraryQuery(LibraryQuery libraryQuery, EventType event public Mono publishLibraryQueryEvent(String id, String name, EventType eventType) { return sessionUserService.getVisitorOrgMemberCache() .zipWith(sessionUserService.getVisitorToken()) - .map(tuple -> { + .flatMap(tuple -> { LibraryQueryEvent event = LibraryQueryEvent.builder() .userId(tuple.getT1().getUserId()) .orgId(tuple.getT1().getOrgId()) @@ -562,10 +578,12 @@ public Mono publishLibraryQueryEvent(String id, String name, EventType eve .isAnonymous(Authentication.isAnonymousUser(tuple.getT1().getUserId())) .sessionHash(Hashing.sha512().hashString(tuple.getT2(), StandardCharsets.UTF_8).toString()) .build(); - event.populateDetails(); - return event; + return Mono.deferContextual(contextView -> { + event.populateDetails(contextView); + applicationEventPublisher.publishEvent(event); + return Mono.empty(); + }); }) - .doOnNext(applicationEventPublisher::publishEvent) .then() .onErrorResume(throwable -> { log.error("publishLibraryQueryEvent error.", throwable);