diff --git a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/InitApplication.java b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/InitApplication.java index d4d5410f4..91404845c 100644 --- a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/InitApplication.java +++ b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/InitApplication.java @@ -26,6 +26,7 @@ import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration; import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; @@ -35,6 +36,7 @@ import com.clougence.clouddm.console.web.global.handler.StaticResourceNoCacheFilter; import com.clougence.clouddm.console.web.global.i18n.DmI18nUtils; import com.clougence.clouddm.init.constant.I18nInitFieldKeys; +import com.clougence.clouddm.init.service.InitWebsitePluginLoader; import com.clougence.utils.ShutdownHook; import jakarta.annotation.PostConstruct; @@ -75,7 +77,8 @@ public static void main(String[] args) { + "com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration"); SpringApplication application = new SpringApplication(InitApplication.class); application.setRegisterShutdownHook(false); - application.run(args); + ConfigurableApplicationContext context = application.run(args); + context.getBean(InitWebsitePluginLoader.class).loadPlugin(InitApplication.class.getClassLoader()); log.info("[DmAloneLauncher] Alone All Context Inited."); ShutdownHook.joinShutdown(); diff --git a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/component/log/InitMysqlDriverProgressBus.java b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/component/log/InitMysqlDriverProgressBus.java deleted file mode 100644 index 36dcc19f8..000000000 --- a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/component/log/InitMysqlDriverProgressBus.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2026 杭州开云集致科技有限公司 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.clougence.clouddm.init.component.log; - -import java.util.concurrent.CopyOnWriteArraySet; -import java.util.function.Consumer; - -import com.clougence.clouddm.console.web.model.vo.datasource.DriverDownloadProgressVO; -import com.clougence.clouddm.init.service.InitMysqlDriverService; - -public final class InitMysqlDriverProgressBus { - - private static final CopyOnWriteArraySet> LISTENERS = new CopyOnWriteArraySet<>(); - - private static volatile DriverDownloadProgressVO currentProgress; - - private InitMysqlDriverProgressBus(){ - } - - public static void publish(DriverDownloadProgressVO progressVO) { - currentProgress = copyProgress(progressVO); - publish(new InstallUpgradeLogEvent(InitMysqlDriverService.WS_EVENT_TYPE, copyProgress(progressVO))); - } - - public static InstallUpgradeLogEvent snapshotEvent() { - DriverDownloadProgressVO snapshot = copyProgress(currentProgress); - return snapshot == null ? null : new InstallUpgradeLogEvent(InitMysqlDriverService.WS_EVENT_TYPE, snapshot); - } - - public static void addListener(Consumer listener) { - if (listener != null) { - LISTENERS.add(listener); - } - } - - public static void removeListener(Consumer listener) { - if (listener != null) { - LISTENERS.remove(listener); - } - } - - private static void publish(InstallUpgradeLogEvent event) { - if (event == null) { - return; - } - for (Consumer listener : LISTENERS) { - listener.accept(event); - } - } - - private static DriverDownloadProgressVO copyProgress(DriverDownloadProgressVO progressVO) { - if (progressVO == null) { - return null; - } - - DriverDownloadProgressVO copy = new DriverDownloadProgressVO(); - copy.setClusterId(progressVO.getClusterId()); - copy.setDriverFamily(progressVO.getDriverFamily()); - copy.setDriverVersion(progressVO.getDriverVersion()); - copy.setTotalFileCount(progressVO.getTotalFileCount()); - copy.setCompletedFileCount(progressVO.getCompletedFileCount()); - copy.setCurrentFilePercent(progressVO.getCurrentFilePercent()); - copy.setStatus(progressVO.getStatus()); - copy.setMessage(progressVO.getMessage()); - copy.setDetailMessage(progressVO.getDetailMessage()); - copy.setCurrentFileName(progressVO.getCurrentFileName()); - copy.setAvailable(progressVO.isAvailable()); - return copy; - } -} diff --git a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/component/scripts/V202605070002__init_data.java b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/component/scripts/V202605070002__init_data.java index 338a90d52..c5570a3dc 100644 --- a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/component/scripts/V202605070002__init_data.java +++ b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/component/scripts/V202605070002__init_data.java @@ -41,6 +41,7 @@ private static String buildInitAdminRoleSql() { } private static String buildInitPrimaryUserSql() { + String adminAccount = InitSeedConstants.escapeSqlLiteral(InitSeedConstants.resolveAdminAccount()); String adminEmail = InitSeedConstants.escapeSqlLiteral(InitSeedConstants.resolveAdminEmail()); String encodedPassword = CryptService.INSTANCE.encryptForOneWay(InitSeedConstants.resolveAdminPassword()).getEncryptPassword(); String encryptedSecretKey = CryptService.INSTANCE.encryptUseDefaultKeyAndSalt(InitSeedConstants.DEFAULT_PRIMARY_SECRET_KEY); @@ -51,7 +52,7 @@ private static String buildInitPrimaryUserSql() { `op_locked`, `account_type`, `user_domain`, `disable`, `parent_id`, `maintainer`, `aliyun_ak`, `aliyun_sk`, `last_date_update_aliyun_ak`, `bind_type`, `bind_account`, `phone_area_code`, `user_status`, `src`, `client_id`, `keyword`, `contact_me`, `country`) - VALUES (1,now(), now(), '%s', 'Trial', '%s', '%s', null, '', + VALUES (1,now(), now(), '%s', '%s', '%s', '%s', null, '', '%s', null, %s, '%s', '%s', @@ -59,6 +60,7 @@ private static String buildInitPrimaryUserSql() { 0, null, null, now(), 'INTERNAL', null, null, 'NORMAL', null, null, null, 0, null)\ """.formatted( // InitSeedConstants.ADMIN_UID, // + adminAccount, // adminEmail, // InitSeedConstants.DEFAULT_PRIMARY_PHONE, // encodedPassword, ADMIN_ROLE_ID, // diff --git a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/component/scripts/V202606050001__dm_auth_user_account.java b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/component/scripts/V202606050001__dm_auth_user_account.java index 6ff822299..3446567c4 100644 --- a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/component/scripts/V202606050001__dm_auth_user_account.java +++ b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/component/scripts/V202606050001__dm_auth_user_account.java @@ -24,7 +24,7 @@ public class V202606050001__dm_auth_user_account extends AbstractUpgradeJavaMigr @Override public List collectScript() { - String adminAccount = InitSeedConstants.escapeSqlLiteral(InitSeedConstants.DEFAULT_PRIMARY_ACCOUNT); + String adminAccount = InitSeedConstants.escapeSqlLiteral(InitSeedConstants.resolveAdminAccount()); return List.of(""" ALTER TABLE dm_auth_user MODIFY COLUMN username varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, diff --git a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/constant/I18nInitFieldKeys.java b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/constant/I18nInitFieldKeys.java index a5a7d72b8..c3aaa2fd6 100644 --- a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/constant/I18nInitFieldKeys.java +++ b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/constant/I18nInitFieldKeys.java @@ -28,6 +28,8 @@ public enum I18nInitFieldKeys { INIT_FIELD_DB_PASSWORD_DESC, INIT_FIELD_JWT_SECRET_LABEL, INIT_FIELD_JWT_SECRET_DESC, + INIT_FIELD_ADMIN_ACCOUNT_LABEL, + INIT_FIELD_ADMIN_ACCOUNT_DESC, INIT_FIELD_ADMIN_EMAIL_LABEL, INIT_FIELD_ADMIN_EMAIL_DESC, INIT_FIELD_ADMIN_PASSWORD_LABEL, @@ -41,15 +43,6 @@ public enum I18nInitFieldKeys { INIT_TEST_DB_SUCCESS, INIT_TEST_DB_CONNECTION_FAILED, INIT_TEST_DB_CHARSET_INVALID, - INIT_MYSQL_DRIVER_REQUIRED, - INIT_MYSQL_DRIVER_PREPARING, - INIT_MYSQL_DRIVER_READY, - INIT_MYSQL_DRIVER_UNAVAILABLE, - INIT_MYSQL_DRIVER_PREPARE_FAILED, - INIT_MYSQL_DRIVER_PREPARE_STARTED, - INIT_MYSQL_DRIVER_FILE_DOWNLOAD_COMPLETE, - INIT_MYSQL_DRIVER_FILE_DOWNLOADING, - INIT_MYSQL_DRIVER_FILE_DEFAULT_NAME, INIT_TEST_DB_REBUILD_PROMPT, INIT_TEST_DB_USE_EXISTING_WARNING, INIT_TEST_DB_REBUILD_WARNING, diff --git a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/constant/InitSeedConstants.java b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/constant/InitSeedConstants.java index f211fbadb..0d60e9b69 100644 --- a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/constant/InitSeedConstants.java +++ b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/constant/InitSeedConstants.java @@ -32,6 +32,7 @@ public final class InitSeedConstants { public static final String DEFAULT_WORKER_IP = "172.31.239.4"; public static final String DEFAULT_CONSOLE_IP = "172.31.239.3"; public static final String DEFAULT_EXTERNAL_IP = "183.134.161.226"; + public static final String RUNTIME_ADMIN_ACCOUNT_KEY = "clougence.init.admin.account"; public static final String RUNTIME_ADMIN_EMAIL_KEY = "clougence.init.admin.email"; public static final String RUNTIME_ADMIN_PASSWORD_KEY = "clougence.init.admin.password"; @@ -42,6 +43,10 @@ public static String resolveAdminEmail() { return defaultIfBlank(System.getProperty(RUNTIME_ADMIN_EMAIL_KEY), DEFAULT_PRIMARY_EMAIL); } + public static String resolveAdminAccount() { + return defaultIfBlank(System.getProperty(RUNTIME_ADMIN_ACCOUNT_KEY), DEFAULT_PRIMARY_ACCOUNT); + } + public static String resolveAdminPassword() { return defaultIfBlank(System.getProperty(RUNTIME_ADMIN_PASSWORD_KEY), DEFAULT_PRIMARY_PASSWORD); } diff --git a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/controller/InitController.java b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/controller/InitController.java index 7c3520f13..872baec2c 100644 --- a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/controller/InitController.java +++ b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/controller/InitController.java @@ -30,7 +30,6 @@ import com.clougence.clouddm.console.web.global.jwtsession.RequestAuth; import com.clougence.clouddm.init.model.InitFieldDef; import com.clougence.clouddm.init.model.TestDbResult; -import com.clougence.clouddm.init.service.InitMysqlDriverService; import com.clougence.clouddm.init.service.SysInitDefService; import com.clougence.clouddm.init.service.SysInitService; @@ -48,8 +47,6 @@ public class InitController { private SysInitService initService; @Resource private SysInitDefService defService; - @Resource - private InitMysqlDriverService initMysqlDriverService; /** * Returns the default configuration field definitions. @@ -77,19 +74,6 @@ public ResWebData testDb(@RequestBody Map params) { return ResWebDataUtils.buildSuccess(result); } - @RequestAuth(strategy = RequestAuth.AuthStrategy.Ignore) - @RequestMapping(value = "/checkDriverStatus", method = { RequestMethod.POST }) - public ResWebData checkDriverStatus() { - return ResWebDataUtils.buildSuccess(this.initMysqlDriverService.driverStatus()); - } - - @RequestAuth(strategy = RequestAuth.AuthStrategy.Ignore) - @RequestMapping(value = "/downloadDriver", method = { RequestMethod.POST }) - public ResWebData downloadDriver() { - this.initMysqlDriverService.downloadDriver(); - return ResWebDataUtils.buildSuccess(null); - } - @RequestAuth(strategy = RequestAuth.AuthStrategy.Ignore) @RequestMapping(value = "/previewScripts", method = { RequestMethod.POST }) public ResWebData previewScripts(@RequestBody Map params) { diff --git a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/controller/InitFaviconController.java b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/controller/InitFaviconController.java new file mode 100644 index 000000000..9575a00f6 --- /dev/null +++ b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/controller/InitFaviconController.java @@ -0,0 +1,84 @@ +/* + * Copyright 2026 杭州开云集致科技有限公司 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.clougence.clouddm.init.controller; + +import java.io.IOException; +import java.util.Locale; + +import org.springframework.context.annotation.Profile; +import org.springframework.http.CacheControl; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import com.clougence.clouddm.console.web.component.file.PluginResourceManager; +import com.clougence.clouddm.console.web.component.file.mode.PluginResourceData; +import com.clougence.clouddm.console.web.component.file.resource.PluginResourceModel; +import com.clougence.clouddm.console.web.global.jwtsession.RequestAuth; +import com.clougence.clouddm.sdk.resource.ResourceRequest; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Profile("init") +@RestController +public class InitFaviconController { + + private static final String FAVICON_RESOURCE = "webside/favicon"; + private static final String FAVICON_FORMAT = "ico"; + + @RequestAuth(strategy = RequestAuth.AuthStrategy.Ignore) + @RequestMapping(value = "/favicon.ico", method = { RequestMethod.GET }) + public ResponseEntity favicon(HttpServletRequest httpRequest) { + try { + PluginResourceModel resourceModel = PluginResourceManager.findResource(FAVICON_RESOURCE); + if (resourceModel == null) { + return ResponseEntity.notFound().build(); + } + + PluginResourceData resourceData = resourceModel.load(buildResourceRequest(httpRequest)); + if (resourceData == null || resourceData.inputStream() == null) { + return ResponseEntity.notFound().build(); + } + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.parseMediaType(resourceData.contentType())); + headers.setCacheControl(CacheControl.noCache()); + try (var inputStream = resourceData.inputStream()) { + return ResponseEntity.ok().headers(headers).body(inputStream.readAllBytes()); + } + } catch (IOException e) { + log.warn("Failed to load favicon resource '{}'.", FAVICON_RESOURCE, e); + return ResponseEntity.internalServerError().build(); + } + } + + private ResourceRequest buildResourceRequest(HttpServletRequest httpRequest) { + ResourceRequest request = new ResourceRequest(); + request.setLoggedIn(false); + request.setExpectedFormat(FAVICON_FORMAT); + + if (httpRequest != null) { + Locale locale = httpRequest.getLocale(); + request.setLanguage(locale == null ? null : locale.toLanguageTag()); + } + return request; + } +} diff --git a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/controller/InitHomeController.java b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/controller/InitHomeController.java index 7ad327365..ba17a0410 100644 --- a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/controller/InitHomeController.java +++ b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/controller/InitHomeController.java @@ -51,7 +51,7 @@ public class InitHomeController { private SysInitDefService defService; @RequestAuth(strategy = RequestAuth.AuthStrategy.Ignore) - @RequestMapping(value = "/dm_global_settings", method = { RequestMethod.POST }) + @RequestMapping(value = "/dmGlobalSettings", method = { RequestMethod.POST }) public ResWebData dmGlobalSettings() { GlobalSettingsVO vo = new GlobalSettingsVO(); vo.setVersion(GlobalConfUtils.getAppVersion()); diff --git a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/controller/InitMysqlDriverWebSocketConfig.java b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/controller/InitMysqlDriverWebSocketConfig.java deleted file mode 100644 index 64a0945c2..000000000 --- a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/controller/InitMysqlDriverWebSocketConfig.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2026 杭州开云集致科技有限公司 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.clougence.clouddm.init.controller; - -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.web.socket.config.annotation.EnableWebSocket; -import org.springframework.web.socket.config.annotation.WebSocketConfigurer; -import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; - -import com.clougence.clouddm.console.web.constants.DmControllerUrlPrefix; - -import jakarta.annotation.Resource; - -@Configuration -@EnableWebSocket -@Profile("init") -public class InitMysqlDriverWebSocketConfig implements WebSocketConfigurer { - - @Resource - private InitMysqlDriverWebSocketHandler initMysqlDriverWebSocketHandler; - - @Override - public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { - registry.addHandler(initMysqlDriverWebSocketHandler, DmControllerUrlPrefix.CONSOLE_PREFIX + "/init/ws/mysql-driver").setAllowedOrigins("*"); - } -} diff --git a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/controller/InitMysqlDriverWebSocketHandler.java b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/controller/InitMysqlDriverWebSocketHandler.java deleted file mode 100644 index 27671a83b..000000000 --- a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/controller/InitMysqlDriverWebSocketHandler.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2026 杭州开云集致科技有限公司 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.clougence.clouddm.init.controller; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; - -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Component; -import org.springframework.web.socket.CloseStatus; -import org.springframework.web.socket.TextMessage; -import org.springframework.web.socket.WebSocketSession; -import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator; -import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator.OverflowStrategy; -import org.springframework.web.socket.handler.TextWebSocketHandler; - -import com.clougence.clouddm.init.component.log.InitMysqlDriverProgressBus; -import com.clougence.clouddm.init.component.log.InstallUpgradeLogEvent; -import com.clougence.utils.JsonUtils; - -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Component -@Profile("init") -public class InitMysqlDriverWebSocketHandler extends TextWebSocketHandler { - - private final Map> listeners = new ConcurrentHashMap<>(); - - @Override - public void afterConnectionEstablished(WebSocketSession session) { - String sessionId = session.getId(); - WebSocketSession decorated = new ConcurrentWebSocketSessionDecorator(session, 10000, 4 * 1024 * 1024, OverflowStrategy.DROP); - Consumer listener = event -> writeEvent(decorated, event); - listeners.put(sessionId, listener); - InitMysqlDriverProgressBus.addListener(listener); - writeEvent(decorated, InitMysqlDriverProgressBus.snapshotEvent()); - log.info("[InitMysqlDriverWebSocketHandler] WebSocket connected, sessionId={}", sessionId); - } - - @Override - public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { - cleanup(session.getId()); - log.info("[InitMysqlDriverWebSocketHandler] WebSocket closed, sessionId={}, status={}", session.getId(), status); - } - - @Override - public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { - log.warn("[InitMysqlDriverWebSocketHandler] WebSocket transport error, sessionId={}, msg={}", session.getId(), exception.getMessage()); - cleanup(session.getId()); - if (session.isOpen()) { - session.close(CloseStatus.SERVER_ERROR); - } - } - - private void cleanup(String sessionId) { - Consumer listener = listeners.remove(sessionId); - if (listener != null) { - InitMysqlDriverProgressBus.removeListener(listener); - } - } - - private void writeEvent(WebSocketSession session, InstallUpgradeLogEvent event) { - if (event == null || session == null || !session.isOpen()) { - return; - } - - try { - session.sendMessage(new TextMessage(JsonUtils.toJson(event))); - } catch (Exception e) { - log.warn("[InitMysqlDriverWebSocketHandler] Failed to send driver event, sessionId={}, msg={}", session.getId(), e.getMessage()); - } - } -} diff --git a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/InitDBStatusDetector.java b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/InitDBStatusDetector.java index 3af74bc32..b527ab848 100644 --- a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/InitDBStatusDetector.java +++ b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/InitDBStatusDetector.java @@ -26,9 +26,6 @@ import com.clougence.clouddm.init.component.flyway.DmFlywayInit; import com.clougence.clouddm.init.model.SystemStatusResult; import com.clougence.clouddm.platform.dal.config.DmDalConfig; -import com.clougence.clouddm.platform.plugin.PluginManager; -import com.clougence.drivers.DriverBinding; -import com.clougence.drivers.DriverVersion; import com.clougence.utils.StringUtils; import lombok.extern.slf4j.Slf4j; @@ -56,24 +53,7 @@ public static SystemStatusResult detectDBStatus(Properties props) { } try { - DriverVersion ver = DmDalConfig.mainDsDriverVersion(); - if (!ver.isPrepared()) { - log.warn("[InitDBStatusDetector] Runtime driver is not prepared."); - result.setStatus(SystemStatus.Initial); - result.setInitReason(DRIVER_MISSING); - result.setDbError("Runtime MySQL driver is not ready."); - return result; - } - - DriverBinding binding = PluginManager.driverLoader().createBinding(// - DmDalConfig.class.getClassLoader(), DmDalConfig.MYSQL_DRIVER_RUNTIME_FAMILY, DmDalConfig.MYSQL_DRIVER_VERSION); - if (!DmDalConfig.isDriverClassAvailable(binding)) { - log.warn("[InitDBStatusDetector] Runtime driver class is not available."); - result.setStatus(SystemStatus.Initial); - result.setInitReason(DRIVER_MISSING); - result.setDbError("Runtime MySQL driver class is unavailable."); - return result; - } + DmDalConfig.ensureDriverAvailable(); } catch (RuntimeException e) { log.warn("[InitDBStatusDetector] Runtime driver is not ready: {}", e.getMessage()); result.setStatus(SystemStatus.Initial); diff --git a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/InitMysqlDriverService.java b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/InitMysqlDriverService.java deleted file mode 100644 index 553f9a04f..000000000 --- a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/InitMysqlDriverService.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright 2026 杭州开云集致科技有限公司 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.clougence.clouddm.init.service; - -import java.io.File; -import java.util.Locale; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicReference; - -import org.springframework.stereotype.Service; - -import com.clougence.clouddm.api.common.GlobalConfUtils; -import com.clougence.clouddm.console.web.global.i18n.DmI18nUtils; -import com.clougence.clouddm.console.web.model.vo.datasource.DriverDownloadProgressVO; -import com.clougence.clouddm.init.InitApplication; -import com.clougence.clouddm.init.component.log.InitMysqlDriverProgressBus; -import com.clougence.clouddm.init.constant.I18nInitFieldKeys; -import com.clougence.clouddm.platform.dal.config.DmDalConfig; -import com.clougence.clouddm.platform.plugin.PluginLoadHelper; -import com.clougence.clouddm.platform.plugin.PluginManager; -import com.clougence.drivers.DriverBinding; -import com.clougence.drivers.DriverPrepareProgress; -import com.clougence.drivers.DriverVersion; -import com.clougence.drivers.def.ResDef; -import com.clougence.utils.CollectionUtils; -import com.clougence.utils.ExceptionUtils; -import com.clougence.utils.StringUtils; -import com.clougence.utils.ThreadUtils; - -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Service -public class InitMysqlDriverService { - - public static final String WS_EVENT_TYPE = "INIT_MYSQL_DRIVER_PROGRESS"; - private final ExecutorService downloadExecutor; - private volatile boolean downloadRunning; - - public enum RuntimeDriverStatus { - UNAVAILABLE, - DOWNLOADING, - READY - } - - public InitMysqlDriverService(){ - ThreadFactory threadFactory = ThreadUtils.daemonThreadFactory(this.getClass().getClassLoader(), "init-mysql-driver-%s"); - this.downloadExecutor = Executors.newSingleThreadExecutor(threadFactory); - } - - public RuntimeDriverStatus driverStatus() { - if (this.downloadRunning) { - return RuntimeDriverStatus.DOWNLOADING; - } else { - return resolveDriverStatus(); - } - } - - private RuntimeDriverStatus resolveDriverStatus() { - try { - DriverVersion ver = DmDalConfig.mainDsDriverVersion(); - if (!ver.isPrepared()) { - return RuntimeDriverStatus.UNAVAILABLE; - } - - DriverBinding binding = PluginManager.driverLoader().createBinding(// - DmDalConfig.class.getClassLoader(), DmDalConfig.MYSQL_DRIVER_RUNTIME_FAMILY, DmDalConfig.MYSQL_DRIVER_VERSION); - if (DmDalConfig.isDriverClassAvailable(binding)) { - return RuntimeDriverStatus.READY; - } - } catch (Exception e) { - log.debug("[InitMysqlDriverService] Runtime MySQL driver is not ready: {}", e.getMessage()); - } - return RuntimeDriverStatus.UNAVAILABLE; - } - - // - // - // - - public synchronized void downloadDriver() { - RuntimeDriverStatus status = driverStatus(); - if (status == RuntimeDriverStatus.READY) { - publishCompletion(); - return; - } - if (status == RuntimeDriverStatus.DOWNLOADING) { - publishDownloadStarted(); - return; - } - - this.downloadRunning = true; - publishDownloadStarted(); - this.downloadExecutor.execute(() -> { - try { - File pluginPath1 = new File(GlobalConfUtils.getPluginDir("plugins")); - File pluginPath2 = new File(GlobalConfUtils.getAppDataHome(), "plugins"); - PluginLoadHelper.loadPlugins(InitApplication.class.getClassLoader(), pluginPath1, pluginPath2); - downloadDriverInternal(); - } catch (Exception e) { - log.error("[InitMysqlDriverService] Download mysql driver failed.", e); - String summary = i18n(I18nInitFieldKeys.INIT_MYSQL_DRIVER_PREPARE_FAILED); - publishProgress(0, 0, 0, "FAILED", false, null, summary, StringUtils.defaultIfBlank(e.getMessage(), summary)); - } finally { - this.downloadRunning = false; - } - }); - } - - private void downloadDriverInternal() { - if (resolveDriverStatus() == RuntimeDriverStatus.READY) { - this.publishCompletion(); - return; - } - - DriverVersion ver = DmDalConfig.mainDsDriverVersion(); - log.info("[InitMysqlDriverService] Start mysql runtime driver prepare. family={}, version={}, maven={}", // - DmDalConfig.MYSQL_DRIVER_RUNTIME_FAMILY, DmDalConfig.MYSQL_DRIVER_VERSION, DmDalConfig.MYSQL_DRIVER_MAVEN_COORDINATE); - this.prepareDriver(ver); - - if (resolveDriverStatus() != RuntimeDriverStatus.READY) { - throw new IllegalStateException("Runtime MySQL driver class is unavailable."); - } - - this.publishCompletion(); - } - - private void publishCompletion() { - boolean available = resolveDriverStatus() == RuntimeDriverStatus.READY; - String msg = available ? i18n(I18nInitFieldKeys.INIT_MYSQL_DRIVER_READY) : i18n(I18nInitFieldKeys.INIT_MYSQL_DRIVER_UNAVAILABLE); - publishProgress(1, available ? 1 : 0, 100, "COMPLETED", available, null, msg); - } - - private void publishDownloadStarted() { - publishProgress(0, 0, 0, "PREPARING", false, null, i18n(I18nInitFieldKeys.INIT_MYSQL_DRIVER_PREPARE_STARTED)); - } - - private void prepareDriver(DriverVersion ver) { - if (ver == null || CollectionUtils.isEmpty(ver.getResources())) { - throw new IllegalStateException("runtime maven driver resource is unavailable."); - } - - ResDef mavenResource = ver.getResources().get(0); - Set completedFiles = ConcurrentHashMap.newKeySet(); - - AtomicReference prepareError = new AtomicReference<>(); - PluginManager.driverLoader().prepareDriverVersion(ver, c -> c != mavenResource, new DriverPrepareProgress() { - - @Override - public void onStart(DriverVersion driverVersionValue, ResDef driverResource, int resourceIndex, int totalCount) { - String msg = i18n(I18nInitFieldKeys.INIT_MYSQL_DRIVER_PREPARE_STARTED); - publishProgress(resolveDriverFileCount(driverResource), completedFiles.size(), 0, "PREPARING", false, null, msg); - } - - @Override - public void onProgress(DriverVersion driverVersionValue, ResDef driverResource, String fileName, long current, long total) { - if (StringUtils.isNotBlank(fileName) && total > 0 && current >= total) { - completedFiles.add(fileName); - } - - String msg = buildDownloadMessage(fileName, current, total); - publishProgress(resolveDriverFileCount(driverResource), completedFiles.size(), calcPercent(current, total), "PREPARING", false, fileName, msg); - } - - @Override - public void onComplete(DriverVersion driverVersionValue, ResDef driverResource, int resourceIndex, int totalCount) { - String msg = i18n(I18nInitFieldKeys.INIT_MYSQL_DRIVER_FILE_DOWNLOAD_COMPLETE); - publishProgress(resolveDriverFileCount(driverResource), resolveDriverFileCount(driverResource), 100, "PREPARING", false, null, msg); - } - - @Override - public void onError(DriverVersion driverVersionValue, ResDef driverResource, Exception exception) { - String summary = i18n(I18nInitFieldKeys.INIT_MYSQL_DRIVER_PREPARE_FAILED); - String detailMessage = buildPrepareErrorMessage(exception); - prepareError.set(new RuntimeException(detailMessage, exception)); - publishProgress(resolveDriverFileCount(driverResource), completedFiles.size(), 0, "FAILED", false, null, summary, detailMessage); - } - }); - - if (prepareError.get() != null) { - throw prepareError.get(); - } - - int totalFileCount = resolveDriverFileCount(mavenResource); - String msg = i18n(I18nInitFieldKeys.INIT_MYSQL_DRIVER_FILE_DOWNLOAD_COMPLETE); - publishProgress(totalFileCount, totalFileCount, 100, "PREPARING", false, null, msg); - - if (CollectionUtils.isEmpty(mavenResource.getFileDefList())) { - throw new IllegalStateException("prepared mysql driver files not found."); - } - } - - private void publishProgress(int totalFileCount, int completedFileCount, int currentFilePercent, String status, boolean available, String currentFileName, String message) { - publishProgress(totalFileCount, completedFileCount, currentFilePercent, status, available, currentFileName, message, null); - } - - private void publishProgress(int totalFileCount, int completedFileCount, int currentFilePercent, String status, boolean available, String currentFileName, String message, - String detailMessage) { - DriverDownloadProgressVO progressVO = new DriverDownloadProgressVO(); - progressVO.setDriverFamily(DmDalConfig.MYSQL_DRIVER_RUNTIME_FAMILY); - progressVO.setDriverVersion(DmDalConfig.MYSQL_DRIVER_VERSION); - progressVO.setTotalFileCount(totalFileCount); - progressVO.setCompletedFileCount(completedFileCount); - progressVO.setCurrentFilePercent(currentFilePercent); - progressVO.setStatus(status); - progressVO.setAvailable(available); - progressVO.setCurrentFileName(currentFileName); - progressVO.setMessage(message); - progressVO.setDetailMessage(detailMessage); - InitMysqlDriverProgressBus.publish(progressVO); - } - - private int resolveDriverFileCount(ResDef resource) { - if (resource == null || CollectionUtils.isEmpty(resource.getFileDefList())) { - return 1; - } - return resource.getFileDefList().size(); - } - - private String buildDownloadMessage(String fileName, long current, long total) { - String displayName = StringUtils.defaultIfBlank(fileName, i18n(I18nInitFieldKeys.INIT_MYSQL_DRIVER_FILE_DEFAULT_NAME)); - int percent = calcPercent(current, total); - return i18n(I18nInitFieldKeys.INIT_MYSQL_DRIVER_FILE_DOWNLOADING, displayName, percent); - } - - private String i18n(I18nInitFieldKeys key, Object... args) { - return DmI18nUtils.getMessage(key.name(), args); - } - - static String buildPrepareErrorMessage(Throwable e) { - if (e == null) { - return "Prepare mysql driver failed."; - } - - Throwable[] eList = ExceptionUtils.getThrowables(e); - Throwable rootCause = ExceptionUtils.getRootCause(e); - rootCause = rootCause == null ? e : rootCause; - - StringBuilder message = new StringBuilder("Prepare mysql driver failed."); - String rootMessage = StringUtils.trimToNull(ExceptionUtils.getMessage(rootCause)); - if (rootMessage != null) { - message.append(" Root cause: ").append(rootMessage).append('.'); - } - - Throwable transferContext = findMavenTransferContext(eList, rootCause); - String transferMessage = transferContext == null ? null : StringUtils.trimToNull(ExceptionUtils.getMessage(transferContext)); - if (transferMessage != null && message.indexOf(transferMessage) < 0) { - message.append(" Maven transfer: ").append(transferMessage).append('.'); - } - return message.toString(); - } - - private static Throwable findMavenTransferContext(Throwable[] eList, Throwable rootCause) { - if (eList == null) { - return null; - } - - for (int i = eList.length - 1; i >= 0; i--) { - Throwable candidate = eList[i]; - if (candidate == null || candidate == rootCause) { - continue; - } - String text = StringUtils.defaultString(candidate.getMessage()).toLowerCase(Locale.ROOT); - if (text.contains("could not transfer artifact") || text.contains("could not be resolved") || text.contains("artifact descriptor")) { - return candidate; - } - } - return null; - } - - private int calcPercent(long current, long total) { - if (total <= 0L) { - return 0; - } - return (int) Math.clamp(Math.round((current * 100.0d) / total), 0L, 100L); - } -} diff --git a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/InitWebsitePluginLoader.java b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/InitWebsitePluginLoader.java new file mode 100644 index 000000000..9fa708845 --- /dev/null +++ b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/InitWebsitePluginLoader.java @@ -0,0 +1,45 @@ +/* + * Copyright 2026 杭州开云集致科技有限公司 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.clougence.clouddm.init.service; + +import java.io.File; + +import org.springframework.stereotype.Service; + +import com.clougence.clouddm.api.common.GlobalConfUtils; +import com.clougence.clouddm.console.web.component.file.PluginResourceManager; +import com.clougence.clouddm.platform.plugin.PluginLoadHelper; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class InitWebsitePluginLoader { + + private static final String INNER_WEBSITE_PLUGIN_FILE_NAME = "inner-website-lib.jar"; + + public void loadPlugin(ClassLoader parentClassLoader) { + File pluginPath1 = new File(GlobalConfUtils.getPluginDir("plugins")); + File pluginPath2 = new File(GlobalConfUtils.getAppDataHome(), "plugins"); + int loadedCount = PluginLoadHelper.loadPlugins(parentClassLoader, INNER_WEBSITE_PLUGIN_FILE_NAME, pluginPath1, pluginPath2); + if (loadedCount > 0) { + PluginResourceManager.refreshIndex(); + log.info("[InitWebsitePluginLoader] Loaded inner website plugin: {}", INNER_WEBSITE_PLUGIN_FILE_NAME); + return; + } + log.warn("[InitWebsitePluginLoader] Inner website plugin was not found; favicon.ico may be unavailable."); + } +} diff --git a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/SysInitDefService.java b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/SysInitDefService.java index b0ad9e0e8..620f60c15 100644 --- a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/SysInitDefService.java +++ b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/SysInitDefService.java @@ -16,20 +16,11 @@ package com.clougence.clouddm.init.service; import java.io.IOException; -import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; +import java.util.*; import org.springframework.stereotype.Service; -import com.clougence.clouddm.api.common.GlobalConfUtils; import com.clougence.clouddm.console.web.global.i18n.DmI18nUtils; import com.clougence.clouddm.init.constant.InitSeedConstants; import com.clougence.clouddm.init.model.InitFieldDef; @@ -146,6 +137,7 @@ private Properties buildInitDefaultProperties() { Properties props = new Properties(); props.setProperty("spring.datasource.username", ""); props.setProperty("spring.datasource.password", ""); + props.setProperty("clougence.init.admin.account", InitSeedConstants.DEFAULT_PRIMARY_ACCOUNT); props.setProperty("clougence.init.admin.email", InitSeedConstants.DEFAULT_PRIMARY_EMAIL); props.setProperty("server.port", "8222"); props.setProperty("clouddm.rsocket.dns", resolveDefaultHostIp()); @@ -165,28 +157,12 @@ private String resolveDefaultHostIp() { private void loadRuntimeProperties(Properties props, String defaultConfigName, String runtimeConfigName) throws IOException { loadClasspathProperties(props, defaultConfigName); - if (!loadAppHomeProperties(props, runtimeConfigName)) { - loadClasspathProperties(props, runtimeConfigName); - } + loadClasspathProperties(props, runtimeConfigName); } private void loadClasspathProperties(Properties props, String resourcePath) throws IOException { Map map = ResourcesUtils.getProperty(resourcePath); - if (map != null) { - props.putAll(map); - } - } - - private boolean loadAppHomeProperties(Properties props, String configName) throws IOException { - Path configPath = Paths.get(GlobalConfUtils.getAppHome(), "conf", configName); - if (!Files.exists(configPath)) { - return false; - } - - try (InputStream input = Files.newInputStream(configPath)) { - props.load(input); - } - return true; + props.putAll(map); } private void overlaySystemProperties(Properties props) { diff --git a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/SysInitService.java b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/SysInitService.java index bf763bfa1..15f10dd5a 100644 --- a/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/SysInitService.java +++ b/backend/clouddm-boot/boot-initialization/src/main/java/com/clougence/clouddm/init/service/SysInitService.java @@ -73,6 +73,7 @@ public class SysInitService { INIT_WORKFLOW_MODE_KEY, // INIT_DB_CREATE_IF_MISSING, // INIT_DB_REBUILD_IF_NOT_EMPTY, // + InitSeedConstants.RUNTIME_ADMIN_ACCOUNT_KEY, // InitSeedConstants.RUNTIME_ADMIN_EMAIL_KEY, // InitSeedConstants.RUNTIME_ADMIN_PASSWORD_KEY); @Resource @@ -204,9 +205,10 @@ public void applyInitConfig(Map userConfig) { String jdbcUrl = userConfig.get(JDBC_URL_CONFIG_KEY); InstallUpgradeLogBus.start("install", jdbcUrl); try { - log.info("[SysInitService] Applying initialization config, createIfMissing={}, rebuildIfNotEmpty={}, adminEmail={}", // + log.info("[SysInitService] Applying initialization config, createIfMissing={}, rebuildIfNotEmpty={}, adminAccount={}, adminEmail={}", // userConfig.getOrDefault(INIT_DB_CREATE_IF_MISSING, "false"), // userConfig.getOrDefault(INIT_DB_REBUILD_IF_NOT_EMPTY, "false"), // + userConfig.get(InitSeedConstants.RUNTIME_ADMIN_ACCOUNT_KEY), // userConfig.get(InitSeedConstants.RUNTIME_ADMIN_EMAIL_KEY)); InstallUpgradeLogBus.info("Applying initialization configuration."); @@ -216,6 +218,7 @@ public void applyInitConfig(Map userConfig) { jdbcUrl = userConfig.getOrDefault(JDBC_URL_CONFIG_KEY, props.getProperty(JDBC_URL_CONFIG_KEY)); String dbUser = userConfig.getOrDefault("spring.datasource.username", props.getProperty("spring.datasource.username")); String dbPass = userConfig.getOrDefault("spring.datasource.password", props.getProperty("spring.datasource.password")); + String adminAccount = userConfig.get(InitSeedConstants.RUNTIME_ADMIN_ACCOUNT_KEY); String adminEmail = userConfig.get(InitSeedConstants.RUNTIME_ADMIN_EMAIL_KEY); String adminPassword = userConfig.get(InitSeedConstants.RUNTIME_ADMIN_PASSWORD_KEY); boolean createIfMissing = Boolean.parseBoolean(userConfig.getOrDefault(INIT_DB_CREATE_IF_MISSING, "false")); @@ -226,7 +229,7 @@ public void applyInitConfig(Map userConfig) { if (StringUtils.isNotBlank(jdbcUrl) && StringUtils.isNotBlank(dbUser)) { DatabaseInspection inspection = inspectDatabase(jdbcUrl, dbUser, dbPass, false); bootstrapAdmin = !inspection.databaseExists || inspection.empty || rebuildIfNotEmpty; - log.info("[SysInitService] Initialization target inspection, bootstrapAdmin={}, databaseExists={}, empty={}, rebuildIfNotEmpty={}, adminEmail={}", bootstrapAdmin, inspection.databaseExists, inspection.empty, rebuildIfNotEmpty, adminEmail); + log.info("[SysInitService] Initialization target inspection, bootstrapAdmin={}, databaseExists={}, empty={}, rebuildIfNotEmpty={}, adminAccount={}, adminEmail={}", bootstrapAdmin, inspection.databaseExists, inspection.empty, rebuildIfNotEmpty, adminAccount, adminEmail); InstallUpgradeLogBus.info("Preparing database."); prepareDatabase(jdbcUrl, dbUser, dbPass, createIfMissing, rebuildIfNotEmpty); @@ -235,12 +238,13 @@ public void applyInitConfig(Map userConfig) { if (StringUtils.isNotBlank(jdbcUrl) && StringUtils.isNotBlank(dbUser)) { String databaseName = InitDBStatusDetector.getDatabaseName(jdbcUrl); pendingScripts = DmFlywayInit.listUpgradeRequiredScriptNames(jdbcUrl, dbUser, dbPass, databaseName); - runFlywayMigration(jdbcUrl, dbUser, dbPass, bootstrapAdmin ? adminEmail : null, bootstrapAdmin ? adminPassword : null); + runFlywayMigration(jdbcUrl, dbUser, dbPass, bootstrapAdmin ? adminAccount : null, bootstrapAdmin ? adminEmail : null, bootstrapAdmin ? adminPassword : null); } - if (StringUtils.isNotBlank(jdbcUrl) && StringUtils.isNotBlank(dbUser) && StringUtils.isNotBlank(adminEmail) && StringUtils.isNotBlank(adminPassword)) { + if (StringUtils.isNotBlank(jdbcUrl) && StringUtils.isNotBlank(dbUser) && StringUtils.isNotBlank(adminAccount) && StringUtils.isNotBlank(adminEmail) + && StringUtils.isNotBlank(adminPassword)) { InstallUpgradeLogBus.info("Updating administrator account."); - updateAdminUser(jdbcUrl, dbUser, dbPass, adminEmail, adminPassword); + updateAdminUser(jdbcUrl, dbUser, dbPass, adminAccount, adminEmail, adminPassword); } if (StringUtils.isNotBlank(jdbcUrl) && StringUtils.isNotBlank(dbUser) && (bootstrapAdmin || !pendingScripts.isEmpty())) { @@ -346,8 +350,9 @@ private void replaceConfigLines(Map userConfig) throws Exception } private void runFlywayMigration(String jdbcUrl, String dbUser, String dbPass,// - String adminEmail, String adminPassword) { + String adminAccount, String adminEmail, String adminPassword) { log.info("[SysInitService] Running Flyway migration with: {}", jdbcUrl); + String previousAdminAccount = System.getProperty(InitSeedConstants.RUNTIME_ADMIN_ACCOUNT_KEY); String previousAdminEmail = System.getProperty(InitSeedConstants.RUNTIME_ADMIN_EMAIL_KEY); String previousAdminPassword = System.getProperty(InitSeedConstants.RUNTIME_ADMIN_PASSWORD_KEY); try { @@ -358,6 +363,7 @@ private void runFlywayMigration(String jdbcUrl, String dbUser, String dbPass,// Properties props = buildTaskProperties(jdbcUrl, dbUser, dbPass); app.setDefaultProperties(props); + setRuntimeAdminProperty(InitSeedConstants.RUNTIME_ADMIN_ACCOUNT_KEY, adminAccount); setRuntimeAdminProperty(InitSeedConstants.RUNTIME_ADMIN_EMAIL_KEY, adminEmail); setRuntimeAdminProperty(InitSeedConstants.RUNTIME_ADMIN_PASSWORD_KEY, adminPassword); InstallUpgradeLogBus.notice("DB_INIT", "info"); @@ -371,6 +377,7 @@ private void runFlywayMigration(String jdbcUrl, String dbUser, String dbPass,// log.error("[SysInitService] Flyway migration failed", e); throw new RuntimeException("Flyway migration failed: " + e.getMessage(), e); } finally { + restoreRuntimeAdminProperty(InitSeedConstants.RUNTIME_ADMIN_ACCOUNT_KEY, previousAdminAccount); restoreRuntimeAdminProperty(InitSeedConstants.RUNTIME_ADMIN_EMAIL_KEY, previousAdminEmail); restoreRuntimeAdminProperty(InitSeedConstants.RUNTIME_ADMIN_PASSWORD_KEY, previousAdminPassword); } @@ -398,7 +405,7 @@ private void runUpgradeMigration(String jdbcUrl, String dbUser, String dbPass) { } private void updateAdminUser(String jdbcUrl, String dbUser, String dbPass,// - String adminEmail, String adminPassword) throws SQLException { + String adminAccount, String adminEmail, String adminPassword) throws SQLException { try (Connection conn = DmDalConfig.createDriverConnection(jdbcUrl, dbUser, dbPass, 1000L)) { // Encrypt the password using the same format as the Flyway seed scripts. PasswordInfo cryptResult = CryptService.INSTANCE.encryptForOneWay(adminPassword); @@ -426,8 +433,8 @@ private void updateAdminUser(String jdbcUrl, String dbUser, String dbPass,// .prepareStatement("UPDATE dm_auth_user SET email = ?, phone = NULL, password = ?, username = ?, account = ?, allow_local = 1 WHERE uid = ?")) { updateStmt.setString(1, adminEmail); updateStmt.setString(2, encodedPassword); - updateStmt.setString(3, InitSeedConstants.DEFAULT_PRIMARY_ACCOUNT); - updateStmt.setString(4, InitSeedConstants.DEFAULT_PRIMARY_ACCOUNT); + updateStmt.setString(3, adminAccount); + updateStmt.setString(4, adminAccount); updateStmt.setString(5, InitSeedConstants.ADMIN_UID); int affected = updateStmt.executeUpdate(); log.info("[SysInitService] Admin user updated, affected rows: {}", affected); @@ -440,8 +447,8 @@ private void updateAdminUser(String jdbcUrl, String dbUser, String dbPass,// insertStmt.setString(1, InitSeedConstants.ADMIN_UID); insertStmt.setString(2, adminEmail); insertStmt.setString(3, encodedPassword); - insertStmt.setString(4, InitSeedConstants.DEFAULT_PRIMARY_ACCOUNT); - insertStmt.setString(5, InitSeedConstants.DEFAULT_PRIMARY_ACCOUNT); + insertStmt.setString(4, adminAccount); + insertStmt.setString(5, adminAccount); insertStmt.setString(6, InitSeedConstants.DEFAULT_PRIMARY_ACCESS_KEY); insertStmt.setString(7, encryptedSecretKey); insertStmt.executeUpdate(); diff --git a/backend/clouddm-boot/boot-initialization/src/main/resources/config/init-fields.json b/backend/clouddm-boot/boot-initialization/src/main/resources/config/init-fields.json index f70a2e777..cc00c2887 100644 --- a/backend/clouddm-boot/boot-initialization/src/main/resources/config/init-fields.json +++ b/backend/clouddm-boot/boot-initialization/src/main/resources/config/init-fields.json @@ -32,6 +32,14 @@ "inputType": "text", "required": true }, + { + "category": "security", + "propertyKey": "clougence.init.admin.account", + "labelKey": "INIT_FIELD_ADMIN_ACCOUNT_LABEL", + "descriptionKey": "INIT_FIELD_ADMIN_ACCOUNT_DESC", + "inputType": "text", + "required": true + }, { "category": "security", "propertyKey": "clougence.init.admin.email", diff --git a/backend/clouddm-boot/boot-initialization/src/main/resources/i18n/init-fields.properties b/backend/clouddm-boot/boot-initialization/src/main/resources/i18n/init-fields.properties index 4e6d1a516..9eba7527d 100644 --- a/backend/clouddm-boot/boot-initialization/src/main/resources/i18n/init-fields.properties +++ b/backend/clouddm-boot/boot-initialization/src/main/resources/i18n/init-fields.properties @@ -6,6 +6,8 @@ INIT_FIELD_DB_PASSWORD_LABEL=Database Password INIT_FIELD_DB_PASSWORD_DESC=Password to connect to the database INIT_FIELD_JWT_SECRET_LABEL=JWT Secret INIT_FIELD_JWT_SECRET_DESC=JWT signing key for generating and verifying login tokens. Use a sufficiently long random string. +INIT_FIELD_ADMIN_ACCOUNT_LABEL=Admin Account +INIT_FIELD_ADMIN_ACCOUNT_DESC=Login account for the default admin after initialization INIT_FIELD_ADMIN_EMAIL_LABEL=Admin Email INIT_FIELD_ADMIN_EMAIL_DESC=Login email for the default admin after initialization INIT_FIELD_ADMIN_PASSWORD_LABEL=Admin Password @@ -19,15 +21,6 @@ INIT_FIELD_RSOCKET_PORT_DESC=RSocket Console service listening port INIT_TEST_DB_SUCCESS=Test succeeded INIT_TEST_DB_CONNECTION_FAILED=Connection failed: {0} INIT_TEST_DB_CHARSET_INVALID=Test failed: the database default charset must be utf8mb4, current value is {0} -INIT_MYSQL_DRIVER_REQUIRED=The MySQL 8.0+ driver is not ready. Download it on the initialization page first. -INIT_MYSQL_DRIVER_PREPARING=The MySQL 8.0+ driver is being prepared. Retry after the download completes. -INIT_MYSQL_DRIVER_READY=Driver is ready -INIT_MYSQL_DRIVER_UNAVAILABLE=Driver is not ready. Download it first. -INIT_MYSQL_DRIVER_PREPARE_FAILED=Driver preparation failed -INIT_MYSQL_DRIVER_PREPARE_STARTED=Preparing driver... -INIT_MYSQL_DRIVER_FILE_DOWNLOAD_COMPLETE=Driver files downloaded -INIT_MYSQL_DRIVER_FILE_DOWNLOADING=Downloading {0} {1}% -INIT_MYSQL_DRIVER_FILE_DEFAULT_NAME=driver file INIT_TEST_DB_REBUILD_PROMPT=Clear and rebuild the database? INIT_TEST_DB_USE_EXISTING_WARNING=Initialize system tables with the existing database. INIT_TEST_DB_REBUILD_WARNING=All tables and data will be erased, and the operation cannot be undone. diff --git a/backend/clouddm-boot/boot-initialization/src/main/resources/i18n/init-fields_zh_CN.properties b/backend/clouddm-boot/boot-initialization/src/main/resources/i18n/init-fields_zh_CN.properties index 44e22f318..4a3c90247 100644 --- a/backend/clouddm-boot/boot-initialization/src/main/resources/i18n/init-fields_zh_CN.properties +++ b/backend/clouddm-boot/boot-initialization/src/main/resources/i18n/init-fields_zh_CN.properties @@ -6,6 +6,8 @@ INIT_FIELD_DB_PASSWORD_LABEL=密码 INIT_FIELD_DB_PASSWORD_DESC=连接数据库的密码 INIT_FIELD_JWT_SECRET_LABEL=登录签名密钥 INIT_FIELD_JWT_SECRET_DESC=JWT 签名密钥,用于生成和验证登录令牌。建议使用足够长度的随机字符串 +INIT_FIELD_ADMIN_ACCOUNT_LABEL=管理员账号 +INIT_FIELD_ADMIN_ACCOUNT_DESC=系统初始化后默认管理员的登录账号 INIT_FIELD_ADMIN_EMAIL_LABEL=管理员邮箱 INIT_FIELD_ADMIN_EMAIL_DESC=系统初始化后默认管理员的登录邮箱 INIT_FIELD_ADMIN_PASSWORD_LABEL=管理员密码 @@ -19,15 +21,6 @@ INIT_FIELD_RSOCKET_PORT_DESC=RSocket Console 服务监听端口 INIT_TEST_DB_SUCCESS=测试成功 INIT_TEST_DB_CONNECTION_FAILED=连接失败:{0} INIT_TEST_DB_CHARSET_INVALID=测试失败:数据库默认编码必须为 utf8mb4,当前为 {0} -INIT_MYSQL_DRIVER_REQUIRED=MySQL 8.0+ 驱动未就绪,请先在初始化页面下载驱动。 -INIT_MYSQL_DRIVER_PREPARING=MySQL 8.0+ 驱动正在准备中,请等待下载完成后重试。 -INIT_MYSQL_DRIVER_READY=驱动已就绪 -INIT_MYSQL_DRIVER_UNAVAILABLE=驱动未就绪,请先下载 -INIT_MYSQL_DRIVER_PREPARE_FAILED=驱动准备错误 -INIT_MYSQL_DRIVER_PREPARE_STARTED=正在准备驱动... -INIT_MYSQL_DRIVER_FILE_DOWNLOAD_COMPLETE=驱动文件下载完成 -INIT_MYSQL_DRIVER_FILE_DOWNLOADING=正在下载 {0} {1}% -INIT_MYSQL_DRIVER_FILE_DEFAULT_NAME=驱动文件 INIT_TEST_DB_REBUILD_PROMPT=是否清除并重建数据库? INIT_TEST_DB_USE_EXISTING_WARNING=使用已有数据库初始化系统表。 INIT_TEST_DB_REBUILD_WARNING=将会抹除所有表和数据,并且操作不可恢复。 diff --git a/backend/clouddm-platform/cgdm-api/src/main/java/com/clougence/clouddm/api/common/GlobalConfUtils.java b/backend/clouddm-platform/cgdm-api/src/main/java/com/clougence/clouddm/api/common/GlobalConfUtils.java index 0fffa5cbf..8122892ed 100644 --- a/backend/clouddm-platform/cgdm-api/src/main/java/com/clougence/clouddm/api/common/GlobalConfUtils.java +++ b/backend/clouddm-platform/cgdm-api/src/main/java/com/clougence/clouddm/api/common/GlobalConfUtils.java @@ -73,6 +73,19 @@ public static String getAppDataHome() { public static String getTempDataHome() { return getAppDataHome() + File.separator + "temporary"; } + public static String getTempData(String moduleName) { + if (StringUtils.isBlank(moduleName)) { + throw new NullPointerException("moduleName is null."); + } + String tempDataHome = getTempDataHome(); + log.info("temporary[{}] path app.data is {}", moduleName, tempDataHome); + if (tempDataHome.endsWith(File.separator)) { + return tempDataHome + moduleName; + } else { + return tempDataHome + File.separator + moduleName; + } + } + public static String getLogHome() { String dataPath = System.getProperty("app.logPath"); if (StringUtils.isBlank(dataPath)) { diff --git a/backend/clouddm-platform/cgdm-console/src/main/java/com/clougence/clouddm/console/web/global/jwtsession/JwtManager.java b/backend/clouddm-platform/cgdm-console/src/main/java/com/clougence/clouddm/console/web/global/jwtsession/JwtManager.java index 44b24eeb7..cacf58c92 100644 --- a/backend/clouddm-platform/cgdm-console/src/main/java/com/clougence/clouddm/console/web/global/jwtsession/JwtManager.java +++ b/backend/clouddm-platform/cgdm-console/src/main/java/com/clougence/clouddm/console/web/global/jwtsession/JwtManager.java @@ -73,30 +73,13 @@ public void init() throws Exception { } protected void configUrl(Set includeVerifyStartWith, Set ignoreEndWithUrl) { - // RDP includeVerifyStartWith.add(RdpControllerUrlPrefix.CONSOLE_PREFIX); + includeVerifyStartWith.add(DmControllerUrlPrefix.CONSOLE_PREFIX); includeVerifyStartWith.add("/logout"); includeVerifyStartWith.add("/switchSaasMode"); - ignoreEndWithUrl.add(RdpControllerUrlPrefix.CONSOLE_PREFIX + "/user/resetpasswd"); ignoreEndWithUrl.add(RdpControllerUrlPrefix.CONSOLE_PREFIX + "/verify/sendcode"); - ignoreEndWithUrl.add(RdpControllerUrlPrefix.CONSOLE_PREFIX + "/constant/sso_type"); - ignoreEndWithUrl.add(RdpControllerUrlPrefix.CONSOLE_PREFIX + "/authcode/onlinecheckauthcode"); - ignoreEndWithUrl.add(RdpControllerUrlPrefix.CONSOLE_PREFIX + "/authcode/order/checkpayresult"); - ignoreEndWithUrl.add(RdpControllerUrlPrefix.CONSOLE_PREFIX + "/authcode/order/stripePayResult"); - ignoreEndWithUrl.add(RdpControllerUrlPrefix.CONSOLE_PREFIX + "/user/recordandreturndownloadurlbydm"); - ignoreEndWithUrl.add(RdpControllerUrlPrefix.CONSOLE_PREFIX + "/marketplace/notifications/handle"); - ignoreEndWithUrl.add(RdpControllerUrlPrefix.CONSOLE_PREFIX + "/authcode/order/alipayLicenseCallback"); - ignoreEndWithUrl.add(RdpControllerUrlPrefix.CONSOLE_PREFIX + "/authcode/order/alipayPrepayCallback"); - ignoreEndWithUrl.add(RdpControllerUrlPrefix.CONSOLE_PREFIX + "/authcode/order/stripeLicenseCallback"); - ignoreEndWithUrl.add(RdpControllerUrlPrefix.CONSOLE_PREFIX + "/authcode/order/stripePrepayCallback"); - ignoreEndWithUrl.add(RdpControllerUrlPrefix.CONSOLE_PREFIX + "/authcode/order/autoapplylicense"); - ignoreEndWithUrl.add(RdpControllerUrlPrefix.CONSOLE_PREFIX + "/saas/queryPriceMeta"); - - // DM - includeVerifyStartWith.add(DmControllerUrlPrefix.CONSOLE_PREFIX); - ignoreEndWithUrl.add(DmControllerUrlPrefix.CONSOLE_PREFIX + "/version"); - ignoreEndWithUrl.add(DmControllerUrlPrefix.CONSOLE_PREFIX + "/dm_global_settings"); + ignoreEndWithUrl.add(DmControllerUrlPrefix.CONSOLE_PREFIX + "/dmGlobalSettings"); ignoreEndWithUrl.add(DmControllerUrlPrefix.CONSOLE_PREFIX + "/resource/fetch"); } diff --git a/backend/clouddm-platform/cgdm-dao/build.gradle b/backend/clouddm-platform/cgdm-dao/build.gradle index 50906f9a6..44616ee9d 100644 --- a/backend/clouddm-platform/cgdm-dao/build.gradle +++ b/backend/clouddm-platform/cgdm-dao/build.gradle @@ -22,7 +22,6 @@ dependencies { api project(':cgdm-api') api project(':cg-drivers') api project(':cg-utils') - api project(':cgdm-plugin-loader') // spring api "org.springframework.boot:spring-boot-starter-jdbc:${SPRINGBOOT_VERSION}" diff --git a/backend/clouddm-platform/cgdm-dao/src/main/java/com/clougence/clouddm/platform/dal/config/DmDalConfig.java b/backend/clouddm-platform/cgdm-dao/src/main/java/com/clougence/clouddm/platform/dal/config/DmDalConfig.java index c26b06537..2524d5b9f 100644 --- a/backend/clouddm-platform/cgdm-dao/src/main/java/com/clougence/clouddm/platform/dal/config/DmDalConfig.java +++ b/backend/clouddm-platform/cgdm-dao/src/main/java/com/clougence/clouddm/platform/dal/config/DmDalConfig.java @@ -15,6 +15,11 @@ */ package com.clougence.clouddm.platform.dal.config; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; @@ -40,12 +45,13 @@ import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; -import com.clougence.clouddm.platform.plugin.PluginManager; -import com.clougence.drivers.*; -import com.clougence.drivers.def.ResDef; -import com.clougence.drivers.def.VerDef; +import com.clougence.clouddm.api.common.GlobalConfUtils; +import com.clougence.drivers.DataSourceBridge; +import com.clougence.drivers.DsConfigKeys; import com.clougence.utils.StringUtils; +import com.clougence.utils.io.IOUtils; import com.clougence.utils.loader.CgClassLoader; +import com.clougence.utils.loader.providers.JarResourceLoader; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; @@ -59,10 +65,10 @@ @EnableTransactionManagement @MapperScan(basePackages = "com.clougence.clouddm.platform.dal.mapper", sqlSessionFactoryRef = "sqlSessionFactory") public class DmDalConfig { - public static final String MYSQL_DRIVER_RUNTIME_FAMILY = "cgdm-runtime-mysql"; - public static final String MYSQL_DRIVER_VERSION = "default"; - public static final String MYSQL_DRIVER_CLASS_NAME = "com.mysql.cj.jdbc.Driver"; - public static final String MYSQL_DRIVER_MAVEN_COORDINATE = "com.mysql:mysql-connector-j:jar:8.0.33"; + public static final String MYSQL_DRIVER_CLASS_NAME = "com.mysql.cj.jdbc.Driver"; + private static final String DRIVER_JAR_RESOURCE = "driver-jar/mysql-connector-j-8.0.33.jar"; + private static final String DRIVER_JAR_FILE = "cgdm-mysq/mysql-connector-j-8.0.33.jar"; + private static CgClassLoader driverClassLoader; @Primary @Bean(name = "dataSource") @@ -97,21 +103,7 @@ public static Connection createDriverConnection(String jdbcUrl, String username, public static DataSource createDriverDataSource(String jdbcUrl, String username, String password, long connectionTimeout) { // dsFactory - DriverLoader loader = PluginManager.driverLoader(); - DriverVersion ver = DmDalConfig.mainDsDriverVersion(); - if (!ver.isPrepared()) { - throw new IllegalStateException("Runtime MySQL driver is not ready. family=" + MYSQL_DRIVER_RUNTIME_FAMILY + ", version=" + MYSQL_DRIVER_VERSION); - } - - DriverBinding binding = loader.createBinding(DmDalConfig.class.getClassLoader(), MYSQL_DRIVER_RUNTIME_FAMILY, MYSQL_DRIVER_VERSION); - if (binding == null) { - throw new IllegalStateException("MySQL driver binding is unavailable. family=" + MYSQL_DRIVER_RUNTIME_FAMILY + ", version=" + MYSQL_DRIVER_VERSION); - } - if (!isDriverClassAvailable(binding)) { - throw new IllegalStateException("Runtime MySQL driver class is unavailable."); - } - - CgClassLoader classLoader = binding.asClassLoader(); + CgClassLoader classLoader = ensureDriverAvailable(); DmDalConfigDsFactory dsFactory = new DmDalConfigDsFactory(classLoader); // Hikari @@ -124,39 +116,79 @@ public static DataSource createDriverDataSource(String jdbcUrl, String username, return new DataSourceBridge(properties, dsFactory); } - public static boolean isDriverClassAvailable(DriverBinding binding) { - if (binding == null) { - return false; + public static synchronized CgClassLoader ensureDriverAvailable() { + if (driverClassLoader != null) { + try { + driverClassLoader.loadClass(MYSQL_DRIVER_CLASS_NAME); + return driverClassLoader; + } catch (ClassNotFoundException e) { + log.debug("Cached runtime MySQL driver class is unavailable: {}", e.getMessage()); + IOUtils.closeQuietly(driverClassLoader); + driverClassLoader = null; + } } + CgClassLoader classLoader = createDriverClassLoader(false); try { - binding.asClassLoader().loadClass(MYSQL_DRIVER_CLASS_NAME); - return true; + classLoader.loadClass(MYSQL_DRIVER_CLASS_NAME); + driverClassLoader = classLoader; + return classLoader; } catch (ClassNotFoundException e) { - return false; + log.debug("Runtime MySQL driver class is unavailable after loading cache: {}", e.getMessage()); + IOUtils.closeQuietly(classLoader); } - } - public static DriverVersion mainDsDriverVersion() { - DriverLoader internalLoader = PluginManager.driverLoader(); - DriverFamily driverFamily = internalLoader.findDriver(MYSQL_DRIVER_RUNTIME_FAMILY); - if (driverFamily == null) { - driverFamily = internalLoader.addDriver(MYSQL_DRIVER_RUNTIME_FAMILY); + classLoader = createDriverClassLoader(true); + try { + classLoader.loadClass(MYSQL_DRIVER_CLASS_NAME); + driverClassLoader = classLoader; + return classLoader; + } catch (ClassNotFoundException e) { + IOUtils.closeQuietly(classLoader); + throw new IllegalStateException("Runtime MySQL driver class is unavailable after refreshing cache: " + MYSQL_DRIVER_CLASS_NAME, e); } + } - VerDef ver = (VerDef) driverFamily.findVersion(MYSQL_DRIVER_VERSION); - if (ver == null) { - ver = (VerDef) driverFamily.addVersion(MYSQL_DRIVER_VERSION); - ver.setLocalDir(internalLoader.getDriverHome()); - ver.setDsFactory(DmDalConfigDsFactory.class.getName()); - - ResDef resource = new ResDef(); - resource.setResourceType("maven"); - resource.setCoordinate(MYSQL_DRIVER_MAVEN_COORDINATE); - ver.addResource(resource); + private static CgClassLoader createDriverClassLoader(boolean refresh) { + File driverJarFile = new File(GlobalConfUtils.getTempData(DRIVER_JAR_FILE)); + if (refresh) { + try { + Files.deleteIfExists(driverJarFile.toPath()); + } catch (IOException e) { + throw new IllegalStateException("Delete runtime MySQL driver cache failed: " + driverJarFile.getAbsolutePath(), e); + } + } + if (!driverJarFile.isFile()) { + copyDriverJarResource(driverJarFile); + } + if (!driverJarFile.isFile()) { + throw new IllegalStateException("Runtime MySQL driver jar is unavailable: " + driverJarFile.getAbsolutePath()); } + try { + return new JarResourceLoader(driverJarFile).toClassLoader(DmDalConfig.class.getClassLoader()); + } catch (IOException e) { + throw new IllegalStateException("Load runtime MySQL driver jar failed: " + driverJarFile.getAbsolutePath(), e); + } + } - return ver; + private static void copyDriverJarResource(File driverJarFile) { + try (InputStream input = DmDalConfig.class.getClassLoader().getResourceAsStream(DRIVER_JAR_RESOURCE)) { + if (input == null) { + if (driverJarFile.isFile()) { + log.warn("Runtime MySQL driver resource {} not found in classpath, use existing file {}.", DRIVER_JAR_RESOURCE, driverJarFile.getAbsolutePath()); + return; + } + throw new IllegalStateException("Runtime MySQL driver resource is missing: " + DRIVER_JAR_RESOURCE); + } + + File parentFile = driverJarFile.getParentFile(); + if (parentFile != null) { + Files.createDirectories(parentFile.toPath()); + } + Files.copy(input, driverJarFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new IllegalStateException("Copy runtime MySQL driver resource failed: " + DRIVER_JAR_RESOURCE + " -> " + driverJarFile.getAbsolutePath(), e); + } } @Primary diff --git a/backend/clouddm-platform/cgdm-dao/src/main/java/com/clougence/clouddm/platform/dal/config/DmDalConfigDsFactory.java b/backend/clouddm-platform/cgdm-dao/src/main/java/com/clougence/clouddm/platform/dal/config/DmDalConfigDsFactory.java index 7ede20750..e1c4a0e50 100644 --- a/backend/clouddm-platform/cgdm-dao/src/main/java/com/clougence/clouddm/platform/dal/config/DmDalConfigDsFactory.java +++ b/backend/clouddm-platform/cgdm-dao/src/main/java/com/clougence/clouddm/platform/dal/config/DmDalConfigDsFactory.java @@ -13,7 +13,6 @@ import com.clougence.utils.StringUtils; public class DmDalConfigDsFactory implements DsFactory { - private static final String DEFAULT_CONNECT_TIMEOUT_MS = "3000"; private static final String DEFAULT_SOCKET_TIMEOUT_MS = "30000"; private static final String DEFAULT_CHARACTER_ENCODING = "utf8"; diff --git a/backend/clouddm-platform/cgdm-dao/src/main/resources/driver-jar/mysql-connector-j-8.0.33.jar b/backend/clouddm-platform/cgdm-dao/src/main/resources/driver-jar/mysql-connector-j-8.0.33.jar new file mode 100644 index 000000000..3f741f59f Binary files /dev/null and b/backend/clouddm-platform/cgdm-dao/src/main/resources/driver-jar/mysql-connector-j-8.0.33.jar differ diff --git a/backend/clouddm-platform/cgdm-plugin-loader/src/main/java/com/clougence/clouddm/platform/plugin/PluginLoadHelper.java b/backend/clouddm-platform/cgdm-plugin-loader/src/main/java/com/clougence/clouddm/platform/plugin/PluginLoadHelper.java index 5b135b049..3d7f3c574 100644 --- a/backend/clouddm-platform/cgdm-plugin-loader/src/main/java/com/clougence/clouddm/platform/plugin/PluginLoadHelper.java +++ b/backend/clouddm-platform/cgdm-plugin-loader/src/main/java/com/clougence/clouddm/platform/plugin/PluginLoadHelper.java @@ -59,12 +59,16 @@ public class PluginLoadHelper { // Scan // --------------------------------------------- - public static void loadPlugins(ClassLoader appClassLoader, File... pluginPaths) { + public static int loadPlugins(ClassLoader appClassLoader, File... pluginPaths) { + return loadPlugins(appClassLoader, null, pluginPaths); + } + + public static int loadPlugins(ClassLoader appClassLoader, String pluginFileName, File... pluginPaths) { PluginManager.markStarting(); try { List allPlugins = new ArrayList<>(); for (File path : pluginPaths) { - allPlugins.addAll(scanPluginDirectory(appClassLoader, path)); + allPlugins.addAll(scanPluginDirectory(appClassLoader, path, pluginFileName)); } allPlugins.sort(Comparator.comparingInt(BaseMeta::getOrder)); @@ -75,19 +79,20 @@ public static void loadPlugins(ClassLoader appClassLoader, File... pluginPaths) } SchemaFramework.install(new SchemaInitPlugin()); PluginManager.markReady(); + return allPlugins.size(); } catch (Throwable e) { PluginManager.markStarting(); throw e; } } - private static List scanPluginDirectory(ClassLoader appClassLoader, File pluginPath) { + private static List scanPluginDirectory(ClassLoader appClassLoader, File pluginPath, String pluginFileName) { List result = new ArrayList<>(); if (pluginPath == null || !pluginPath.exists() || !pluginPath.isDirectory()) { return result; } - File[] pluginEntries = pluginPath.listFiles(file -> file.exists() && !file.isHidden()); + File[] pluginEntries = pluginPath.listFiles(file -> file.exists() && !file.isHidden() && isTargetPlugin(file, pluginFileName)); if (pluginEntries == null || pluginEntries.length == 0) { return Collections.emptyList(); } @@ -102,6 +107,10 @@ private static List scanPluginDirectory(ClassLoader appClassLoader, Fi return result; } + private static boolean isTargetPlugin(File file, String pluginFileName) { + return StringUtils.isEmpty(pluginFileName) || pluginFileName.equals(file.getName()); + } + private static ResourceLoader scanPhysicalPlugin(File physicalPlugin) { if (physicalPlugin.isDirectory()) { return loadPluginsFromDir(physicalPlugin); @@ -116,11 +125,11 @@ private static ResourceLoader loadPluginsFromDir(File physicalPlugin) { MultiResourceLoader pluginLoader = new MultiResourceLoader(); pluginLoader.addLoader(new PathResourceLoader(physicalPlugin)); - File[] nestedJarFiles = physicalPlugin.listFiles(file -> file.exists() && file.isFile() && isJarFile(file)); - if (nestedJarFiles != null && nestedJarFiles.length > 0) { - Arrays.sort(nestedJarFiles, Comparator.comparing(File::getName, String.CASE_INSENSITIVE_ORDER)); - for (File nestedJarFile : nestedJarFiles) { - ResourceLoader nestedLoader = loadPluginsFromJar(nestedJarFile); + File[] classpathJarFiles = physicalPlugin.listFiles(file -> file.exists() && file.isFile() && isJarFile(file)); + if (classpathJarFiles != null && classpathJarFiles.length > 0) { + Arrays.sort(classpathJarFiles, Comparator.comparing(File::getName, String.CASE_INSENSITIVE_ORDER)); + for (File classpathJarFile : classpathJarFiles) { + ResourceLoader nestedLoader = loadPluginsFromJar(classpathJarFile); if (nestedLoader != null) { pluginLoader.addLoader(nestedLoader); } diff --git a/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/CgClassLoader.java b/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/CgClassLoader.java index 9160ca68a..92243dfd4 100644 --- a/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/CgClassLoader.java +++ b/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/CgClassLoader.java @@ -33,7 +33,7 @@ * @version : 2021-09-29 * @author 赵永春 (zyc@hasor.net) */ -public class CgClassLoader extends ClassLoader { +public class CgClassLoader extends ClassLoader implements Closeable { @Getter private final ResourceLoader resourceLoader; @@ -43,11 +43,11 @@ public class CgClassLoader extends ClassLoader { private final String tempDirectory = "cobbleLoader/" + System.currentTimeMillis(); private File tempDir; - public CgClassLoader(ClassLoader parent, ResourceLoader resourceLoader){ + public CgClassLoader(ClassLoader parent, ResourceLoader loader){ super(parent); this.includePackages = new HashSet<>(); this.excludePackages = new HashSet<>(); - this.resourceLoader = resourceLoader; + this.resourceLoader = loader; } public void addIncludePackages(String packageOrClass) { @@ -58,6 +58,11 @@ public void addExcludePackages(String packageOrClass) { this.excludePackages.add(packageOrClass); } + @Override + public void close() throws IOException { + this.resourceLoader.close(); + } + @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { if (this.loadedClass.containsKey(name)) { diff --git a/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/ResourceLoader.java b/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/ResourceLoader.java index 966d622ee..c47c80f24 100644 --- a/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/ResourceLoader.java +++ b/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/ResourceLoader.java @@ -15,6 +15,7 @@ */ package com.clougence.utils.loader; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.net.URI; @@ -27,7 +28,7 @@ * @version : 2021-09-29 * @author 赵永春 (zyc@hasor.net) */ -public interface ResourceLoader { +public interface ResourceLoader extends Closeable { default List scanResources(Scanner scanner) throws IOException { return scanResources(MatchType.None, scanner, new String[0]); @@ -63,6 +64,10 @@ default T scanOneResource(MatchType matchType, Scanner scanner) throws IO Manifest getManifest(String resource) throws IOException; + @Override + default void close() throws IOException { + } + interface InputStreamGet { InputStream getStream() throws IOException; diff --git a/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/providers/ExportResourceLoader.java b/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/providers/ExportResourceLoader.java index 04ccca920..1fa2801e4 100644 --- a/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/providers/ExportResourceLoader.java +++ b/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/providers/ExportResourceLoader.java @@ -180,4 +180,9 @@ public boolean exist(String resource) { public Manifest getManifest(String resource) throws IOException { return resourceLoader.getManifest(resource); } + + @Override + public void close() throws IOException { + this.resourceLoader.close(); + } } diff --git a/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/providers/ImportResourceLoader.java b/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/providers/ImportResourceLoader.java index 0b4b16c36..5237b0c27 100644 --- a/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/providers/ImportResourceLoader.java +++ b/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/providers/ImportResourceLoader.java @@ -274,4 +274,16 @@ public Manifest getManifest(String resource) throws IOException { ResourceLoader loader = findLoader(resource); return loader == null ? null : loader.getManifest(resource); } + + @Override + public void close() throws IOException { + Set closedLoaders = new HashSet<>(); + Iterator loaders = getAllLoader(); + while (loaders.hasNext()) { + ResourceLoader loader = loaders.next().loader; + if (closedLoaders.add(loader)) { + loader.close(); + } + } + } } diff --git a/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/providers/JarResourceLoader.java b/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/providers/JarResourceLoader.java index 32bc9f802..e77ebd723 100644 --- a/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/providers/JarResourceLoader.java +++ b/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/providers/JarResourceLoader.java @@ -274,4 +274,11 @@ public T scanOneResource(MatchType matchType, Scanner scanner, String[] s return result.isEmpty() ? null : result.get(0); } + @Override + public void close() throws IOException { + for (JarFile nestedJar : this.nestedJarFile) { + nestedJar.close(); + } + this.jarFile.close(); + } } diff --git a/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/providers/MultiResourceLoader.java b/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/providers/MultiResourceLoader.java index 29746c92a..e3053edc7 100644 --- a/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/providers/MultiResourceLoader.java +++ b/backend/clouddm-utils/cg-utils/src/main/java/com/clougence/utils/loader/providers/MultiResourceLoader.java @@ -130,4 +130,11 @@ public Manifest getManifest(String resource) throws IOException { ResourceLoader loader = findLoader(resource); return loader == null ? null : loader.getManifest(resource); } + + @Override + public void close() throws IOException { + for (ResourceLoader loader : this.loaders) { + loader.close(); + } + } } diff --git a/frontend/src/services/http/api/init.js b/frontend/src/services/http/api/init.js index 6a97c110d..2b6480b55 100644 --- a/frontend/src/services/http/api/init.js +++ b/frontend/src/services/http/api/init.js @@ -3,10 +3,6 @@ export const initApi = { dmInitDefaultConfig: '/clouddm/console/api/v1/init/defaultConfig', // Test database connection + empty library detection + installed testing dmInitTestDb: '/clouddm/console/api/v1/init/testDb', - // Check initializing program driver status - dmInitCheckDriverStatus: '/clouddm/console/api/v1/init/checkDriverStatus', - // Download Initialise Program Driver - dmInitDownloadDriver: '/clouddm/console/api/v1/init/downloadDriver', // Preview pending script dmInitPreviewScripts: '/clouddm/console/api/v1/init/previewScripts', // Save Initialisation Configuration (complete mode) diff --git a/frontend/src/views/initialization/InitMysqlDriverStatus.vue b/frontend/src/views/initialization/InitMysqlDriverStatus.vue deleted file mode 100644 index 37fcfa5d1..000000000 --- a/frontend/src/views/initialization/InitMysqlDriverStatus.vue +++ /dev/null @@ -1,498 +0,0 @@ - - - - - diff --git a/frontend/src/views/initialization/StepConfirm.vue b/frontend/src/views/initialization/StepConfirm.vue index e862c9a79..2d2a0d8db 100644 --- a/frontend/src/views/initialization/StepConfirm.vue +++ b/frontend/src/views/initialization/StepConfirm.vue @@ -37,7 +37,12 @@ import { EditOutlined } from '@ant-design/icons-vue'; const INIT_DB_REBUILD_IF_NOT_EMPTY = 'clougence.init.db.rebuildIfNotEmpty'; -const UPGRADE_HIDDEN_FIELD_KEYS = new Set(['jwt.secret', 'clougence.init.admin.email', 'clougence.init.admin.password']); +const UPGRADE_HIDDEN_FIELD_KEYS = new Set([ + 'jwt.secret', + 'clougence.init.admin.account', + 'clougence.init.admin.email', + 'clougence.init.admin.password' +]); export default { name: 'StepConfirm', @@ -147,11 +152,14 @@ export default { display: inline-flex; align-items: center; font-weight: 500; - width: 280px; + width: 160px; min-height: 30px; flex-shrink: 0; color: #595959; font-size: 13px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .summary-value { display: inline-flex; @@ -202,7 +210,7 @@ export default { } @media (max-width: 768px) { .summary-key { - width: 180px; + width: 104px; } } diff --git a/frontend/src/views/initialization/StepDb.vue b/frontend/src/views/initialization/StepDb.vue index 1cde01e94..d2dbc6b09 100644 --- a/frontend/src/views/initialization/StepDb.vue +++ b/frontend/src/views/initialization/StepDb.vue @@ -9,10 +9,6 @@ - - - -
@@ -125,8 +121,6 @@