From 816060444c06d9ff0c04cd1133a41d01fbf76c79 Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Tue, 3 Mar 2026 15:42:40 +0800 Subject: [PATCH] fix: correct cache key format to match .env file structure The root cause of config overwrites was key mismatch: - .env file uses: ERP_URL, DB_TYPE, DB_NAME (underscore uppercase) - Code was using: erp.url, database.dbType (dot notation) Fixed in three methods: - saveAllSettings() - now sets cache with correct keys - resetToDefaults() - now uses correct keys - save() - now reads cache with correct keys This ensures partial save preserves unmodified fields. --- src/main/services/config/config-manager.ts | 198 +++++++++++---------- 1 file changed, 100 insertions(+), 98 deletions(-) diff --git a/src/main/services/config/config-manager.ts b/src/main/services/config/config-manager.ts index 7432089..abcd33a 100644 --- a/src/main/services/config/config-manager.ts +++ b/src/main/services/config/config-manager.ts @@ -256,21 +256,21 @@ export class ConfigManager { lines.push('# ===========================') lines.push('# ERP 系统配置') lines.push('# ===========================') - lines.push(`ERP_URL=${this.configCache.get('erp.url') || DEFAULT_SETTINGS.erp.url}`) + lines.push(`ERP_URL=${this.configCache.get('ERP_URL') || DEFAULT_SETTINGS.erp.url}`) lines.push( - `ERP_USERNAME=${this.configCache.get('erp.username') || DEFAULT_SETTINGS.erp.username}` + `ERP_USERNAME=${this.configCache.get('ERP_USERNAME') || DEFAULT_SETTINGS.erp.username}` ) lines.push( - `ERP_PASSWORD=${this.configCache.get('erp.password') || DEFAULT_SETTINGS.erp.password}` + `ERP_PASSWORD=${this.configCache.get('ERP_PASSWORD') || DEFAULT_SETTINGS.erp.password}` ) lines.push( - `ERP_HEADLESS=${this.configCache.get('erp.headless') || DEFAULT_SETTINGS.erp.headless}` + `ERP_HEADLESS=${this.configCache.get('ERP_HEADLESS') || DEFAULT_SETTINGS.erp.headless}` ) lines.push( - `ERP_IGNORE_HTTPS_ERRORS=${this.configCache.get('erp.ignoreHttpsErrors') || DEFAULT_SETTINGS.erp.ignoreHttpsErrors}` + `ERP_IGNORE_HTTPS_ERRORS=${this.configCache.get('ERP_IGNORE_HTTPS_ERRORS') || DEFAULT_SETTINGS.erp.ignoreHttpsErrors}` ) lines.push( - `ERP_AUTO_CLOSE_BROWSER=${this.configCache.get('erp.autoCloseBrowser') || DEFAULT_SETTINGS.erp.autoCloseBrowser}` + `ERP_AUTO_CLOSE_BROWSER=${this.configCache.get('ERP_AUTO_CLOSE_BROWSER') || DEFAULT_SETTINGS.erp.autoCloseBrowser}` ) lines.push('') @@ -279,10 +279,10 @@ export class ConfigManager { lines.push('# 数据库配置 - SQL Server') lines.push('# ===========================') lines.push(`# DB_TYPE=sqlserver`) - lines.push(`# DB_SERVER=${this.configCache.get('database.server') || ''}`) - lines.push(`# DB_NAME=${this.configCache.get('database.database') || ''}`) - lines.push(`# DB_USERNAME=${this.configCache.get('database.username') || ''}`) - lines.push(`# DB_PASSWORD=${this.configCache.get('database.password') || ''}`) + lines.push(`# DB_SERVER=${this.configCache.get('DB_SERVER') || ''}`) + lines.push(`# DB_NAME=${this.configCache.get('DB_NAME') || ''}`) + lines.push(`# DB_USERNAME=${this.configCache.get('DB_USERNAME') || ''}`) + lines.push(`# DB_PASSWORD=${this.configCache.get('DB_PASSWORD') || ''}`) lines.push(`DB_SQLSERVER_DRIVER=ODBC Driver 18 for SQL Server`) lines.push(`DB_TRUST_SERVER_CERTIFICATE=yes`) lines.push('') @@ -292,22 +292,22 @@ export class ConfigManager { lines.push('# 数据库配置 - MySQL (切换时使用)') lines.push('# ===========================') lines.push( - `DB_TYPE=${this.configCache.get('database.dbType') || DEFAULT_SETTINGS.database.dbType}` + `DB_TYPE=${this.configCache.get('DB_TYPE') || DEFAULT_SETTINGS.database.dbType}` ) lines.push( - `DB_NAME=${this.configCache.get('database.database') || DEFAULT_SETTINGS.database.database}` + `DB_NAME=${this.configCache.get('DB_NAME') || DEFAULT_SETTINGS.database.database}` ) lines.push( - `DB_USERNAME=${this.configCache.get('database.username') || DEFAULT_SETTINGS.database.username}` + `DB_USERNAME=${this.configCache.get('DB_USERNAME') || DEFAULT_SETTINGS.database.username}` ) lines.push( - `DB_PASSWORD=${this.configCache.get('database.password') || DEFAULT_SETTINGS.database.password}` + `DB_PASSWORD=${this.configCache.get('DB_PASSWORD') || DEFAULT_SETTINGS.database.password}` ) lines.push( - `DB_MYSQL_HOST=${this.configCache.get('database.mysqlHost') || DEFAULT_SETTINGS.database.mysqlHost}` + `DB_MYSQL_HOST=${this.configCache.get('DB_MYSQL_HOST') || DEFAULT_SETTINGS.database.mysqlHost}` ) lines.push( - `DB_MYSQL_PORT=${this.configCache.get('database.mysqlPort') || DEFAULT_SETTINGS.database.mysqlPort}` + `DB_MYSQL_PORT=${this.configCache.get('DB_MYSQL_PORT') || DEFAULT_SETTINGS.database.mysqlPort}` ) lines.push(`DB_MYSQL_CHARSET=utf8mb4`) lines.push('') @@ -327,14 +327,14 @@ export class ConfigManager { lines.push('# 路径配置') lines.push('# ===========================') lines.push( - `PATH_DATA_DIR=${this.configCache.get('paths.dataDir') || DEFAULT_SETTINGS.paths.dataDir}` + `PATH_DATA_DIR=${this.configCache.get('PATH_DATA_DIR') || DEFAULT_SETTINGS.paths.dataDir}` ) lines.push(`PATH_PRODUCTION_ID_FILE=ProductionID.txt`) lines.push( - `PATH_DEFAULT_OUTPUT=${this.configCache.get('paths.defaultOutput') || DEFAULT_SETTINGS.paths.defaultOutput}` + `PATH_DEFAULT_OUTPUT=${this.configCache.get('PATH_DEFAULT_OUTPUT') || DEFAULT_SETTINGS.paths.defaultOutput}` ) lines.push( - `PATH_VALIDATION_OUTPUT=${this.configCache.get('paths.validationOutput') || DEFAULT_SETTINGS.paths.validationOutput}` + `PATH_VALIDATION_OUTPUT=${this.configCache.get('PATH_VALIDATION_OUTPUT') || DEFAULT_SETTINGS.paths.validationOutput}` ) lines.push('') @@ -343,19 +343,19 @@ export class ConfigManager { lines.push('# 数据提取配置') lines.push('# ===========================') lines.push( - `EXTRACTION_BATCH_SIZE=${this.configCache.get('extraction.batchSize') || DEFAULT_SETTINGS.extraction.batchSize}` + `EXTRACTION_BATCH_SIZE=${this.configCache.get('EXTRACTION_BATCH_SIZE') || DEFAULT_SETTINGS.extraction.batchSize}` ) lines.push( - `EXTRACTION_VERBOSE=${this.configCache.get('extraction.verbose') || DEFAULT_SETTINGS.extraction.verbose}` + `EXTRACTION_VERBOSE=${this.configCache.get('EXTRACTION_VERBOSE') || DEFAULT_SETTINGS.extraction.verbose}` ) lines.push( - `EXTRACTION_AUTO_CONVERT=${this.configCache.get('extraction.autoConvert') || DEFAULT_SETTINGS.extraction.autoConvert}` + `EXTRACTION_AUTO_CONVERT=${this.configCache.get('EXTRACTION_AUTO_CONVERT') || DEFAULT_SETTINGS.extraction.autoConvert}` ) lines.push( - `EXTRACTION_MERGE_BATCHES=${this.configCache.get('extraction.mergeBatches') || DEFAULT_SETTINGS.extraction.mergeBatches}` + `EXTRACTION_MERGE_BATCHES=${this.configCache.get('EXTRACTION_MERGE_BATCHES') || DEFAULT_SETTINGS.extraction.mergeBatches}` ) lines.push( - `EXTRACTION_ENABLE_DB_PERSISTENCE=${this.configCache.get('extraction.enableDbPersistence') || DEFAULT_SETTINGS.extraction.enableDbPersistence}` + `EXTRACTION_ENABLE_DB_PERSISTENCE=${this.configCache.get('EXTRACTION_ENABLE_DB_PERSISTENCE') || DEFAULT_SETTINGS.extraction.enableDbPersistence}` ) lines.push('') @@ -364,22 +364,22 @@ export class ConfigManager { lines.push('# 校验配置') lines.push('# ===========================') lines.push( - `VALIDATION_DATA_SOURCE=${this.configCache.get('validation.dataSource') || DEFAULT_SETTINGS.validation.dataSource}` + `VALIDATION_DATA_SOURCE=${this.configCache.get('VALIDATION_DATA_SOURCE') || DEFAULT_SETTINGS.validation.dataSource}` ) lines.push( - `VALIDATION_USE_DATABASE=${this.configCache.get('validation.useDatabase') || true}` + `VALIDATION_USE_DATABASE=${this.configCache.get('VALIDATION_USE_DATABASE') || true}` ) lines.push( - `VALIDATION_BATCH_SIZE=${this.configCache.get('validation.batchSize') || DEFAULT_SETTINGS.validation.batchSize}` + `VALIDATION_BATCH_SIZE=${this.configCache.get('VALIDATION_BATCH_SIZE') || DEFAULT_SETTINGS.validation.batchSize}` ) lines.push( - `VALIDATION_ENABLE_CRUD=${this.configCache.get('validation.enableCrud') || DEFAULT_SETTINGS.validation.enableCrud}` + `VALIDATION_ENABLE_CRUD=${this.configCache.get('VALIDATION_ENABLE_CRUD') || DEFAULT_SETTINGS.validation.enableCrud}` ) lines.push( - `VALIDATION_DEFAULT_MANAGER=${this.configCache.get('validation.defaultManager') || DEFAULT_SETTINGS.validation.defaultManager}` + `VALIDATION_DEFAULT_MANAGER=${this.configCache.get('VALIDATION_DEFAULT_MANAGER') || DEFAULT_SETTINGS.validation.defaultManager}` ) lines.push( - `VALIDATION_MATCH_MODE=${this.configCache.get('validation.matchMode') || DEFAULT_SETTINGS.validation.matchMode}` + `VALIDATION_MATCH_MODE=${this.configCache.get('VALIDATION_MATCH_MODE') || DEFAULT_SETTINGS.validation.matchMode}` ) lines.push('') @@ -388,13 +388,13 @@ export class ConfigManager { lines.push('# UI 配置') lines.push('# ===========================') lines.push( - `UI_FONT_FAMILY=${this.configCache.get('ui.fontFamily') || DEFAULT_SETTINGS.ui.fontFamily}` + `UI_FONT_FAMILY=${this.configCache.get('UI_FONT_FAMILY') || DEFAULT_SETTINGS.ui.fontFamily}` ) lines.push( - `UI_FONT_SIZE=${this.configCache.get('ui.fontSize') || DEFAULT_SETTINGS.ui.fontSize}` + `UI_FONT_SIZE=${this.configCache.get('UI_FONT_SIZE') || DEFAULT_SETTINGS.ui.fontSize}` ) lines.push( - `UI_PRODUCTION_ID_INPUT_WIDTH=${this.configCache.get('ui.productionIdInputWidth') || DEFAULT_SETTINGS.ui.productionIdInputWidth}` + `UI_PRODUCTION_ID_INPUT_WIDTH=${this.configCache.get('UI_PRODUCTION_ID_INPUT_WIDTH') || DEFAULT_SETTINGS.ui.productionIdInputWidth}` ) lines.push('') @@ -403,7 +403,7 @@ export class ConfigManager { lines.push('# 执行配置') lines.push('# ===========================') lines.push( - `EXECUTION_DRYRUN=${this.configCache.get('execution.dryRun') || DEFAULT_SETTINGS.execution.dryRun}` + `EXECUTION_DRYRUN=${this.configCache.get('EXECUTION_DRYRUN') || DEFAULT_SETTINGS.execution.dryRun}` ) const content = lines.join('\n') @@ -506,49 +506,49 @@ export class ConfigManager { * Save settings from SettingsData object */ public async saveAllSettings(settings: SettingsData): Promise { - // ERP settings - this.set('erp.url', settings.erp.url) - this.set('erp.username', settings.erp.username) - this.set('erp.password', settings.erp.password) - this.set('erp.headless', settings.erp.headless) - this.set('erp.ignoreHttpsErrors', settings.erp.ignoreHttpsErrors) - this.set('erp.autoCloseBrowser', settings.erp.autoCloseBrowser) + // ERP settings - use underscore uppercase keys to match .env file + this.set('ERP_URL', settings.erp.url) + this.set('ERP_USERNAME', settings.erp.username) + this.set('ERP_PASSWORD', settings.erp.password) + this.set('ERP_HEADLESS', settings.erp.headless) + this.set('ERP_IGNORE_HTTPS_ERRORS', settings.erp.ignoreHttpsErrors) + this.set('ERP_AUTO_CLOSE_BROWSER', settings.erp.autoCloseBrowser) // Database settings - this.set('database.dbType', settings.database.dbType) - this.set('database.server', settings.database.server) - this.set('database.mysqlHost', settings.database.mysqlHost) - this.set('database.mysqlPort', settings.database.mysqlPort) - this.set('database.database', settings.database.database) - this.set('database.username', settings.database.username) - this.set('database.password', settings.database.password) + this.set('DB_TYPE', settings.database.dbType) + this.set('DB_SERVER', settings.database.server) + this.set('DB_MYSQL_HOST', settings.database.mysqlHost) + this.set('DB_MYSQL_PORT', settings.database.mysqlPort) + this.set('DB_NAME', settings.database.database) + this.set('DB_USERNAME', settings.database.username) + this.set('DB_PASSWORD', settings.database.password) // Path settings - this.set('paths.dataDir', settings.paths.dataDir) - this.set('paths.defaultOutput', settings.paths.defaultOutput) - this.set('paths.validationOutput', settings.paths.validationOutput) + this.set('PATH_DATA_DIR', settings.paths.dataDir) + this.set('PATH_DEFAULT_OUTPUT', settings.paths.defaultOutput) + this.set('PATH_VALIDATION_OUTPUT', settings.paths.validationOutput) // Extraction settings - this.set('extraction.batchSize', settings.extraction.batchSize) - this.set('extraction.verbose', settings.extraction.verbose) - this.set('extraction.autoConvert', settings.extraction.autoConvert) - this.set('extraction.mergeBatches', settings.extraction.mergeBatches) - this.set('extraction.enableDbPersistence', settings.extraction.enableDbPersistence) + this.set('EXTRACTION_BATCH_SIZE', settings.extraction.batchSize) + this.set('EXTRACTION_VERBOSE', settings.extraction.verbose) + this.set('EXTRACTION_AUTO_CONVERT', settings.extraction.autoConvert) + this.set('EXTRACTION_MERGE_BATCHES', settings.extraction.mergeBatches) + this.set('EXTRACTION_ENABLE_DB_PERSISTENCE', settings.extraction.enableDbPersistence) // Validation settings - this.set('validation.dataSource', settings.validation.dataSource) - this.set('validation.batchSize', settings.validation.batchSize) - this.set('validation.matchMode', settings.validation.matchMode) - this.set('validation.enableCrud', settings.validation.enableCrud) - this.set('validation.defaultManager', settings.validation.defaultManager) + this.set('VALIDATION_DATA_SOURCE', settings.validation.dataSource) + this.set('VALIDATION_BATCH_SIZE', settings.validation.batchSize) + this.set('VALIDATION_MATCH_MODE', settings.validation.matchMode) + this.set('VALIDATION_ENABLE_CRUD', settings.validation.enableCrud) + this.set('VALIDATION_DEFAULT_MANAGER', settings.validation.defaultManager) // UI settings - this.set('ui.fontFamily', settings.ui.fontFamily) - this.set('ui.fontSize', settings.ui.fontSize) - this.set('ui.productionIdInputWidth', settings.ui.productionIdInputWidth) + this.set('UI_FONT_FAMILY', settings.ui.fontFamily) + this.set('UI_FONT_SIZE', settings.ui.fontSize) + this.set('UI_PRODUCTION_ID_INPUT_WIDTH', settings.ui.productionIdInputWidth) // Execution settings - this.set('execution.dryRun', settings.execution.dryRun) + this.set('EXECUTION_DRYRUN', settings.execution.dryRun) return this.save() } @@ -573,10 +573,12 @@ export class ConfigManager { } } - // Step 2: Read current settings + // Step 2: Read current settings from .env file directly + // This avoids the cache key mismatch issue (ERP_URL vs erp.url) + await this.loadEnvFile() const currentSettings = this.getAllSettings() - // Step 3: Deep merge + // Step 3: Deep merge - only update provided fields const mergedSettings = deepMerge(currentSettings, settings) // Step 4: Backup and save @@ -622,43 +624,43 @@ export class ConfigManager { // Clear cache and reload from defaults this.configCache.clear() - // Set all defaults - this.set('erp.url', DEFAULT_SETTINGS.erp.url) - this.set('erp.username', DEFAULT_SETTINGS.erp.username) - this.set('erp.password', DEFAULT_SETTINGS.erp.password) - this.set('erp.headless', DEFAULT_SETTINGS.erp.headless) - this.set('erp.ignoreHttpsErrors', DEFAULT_SETTINGS.erp.ignoreHttpsErrors) - this.set('erp.autoCloseBrowser', DEFAULT_SETTINGS.erp.autoCloseBrowser) + // Set all defaults using underscore uppercase keys + this.set('ERP_URL', DEFAULT_SETTINGS.erp.url) + this.set('ERP_USERNAME', DEFAULT_SETTINGS.erp.username) + this.set('ERP_PASSWORD', DEFAULT_SETTINGS.erp.password) + this.set('ERP_HEADLESS', DEFAULT_SETTINGS.erp.headless) + this.set('ERP_IGNORE_HTTPS_ERRORS', DEFAULT_SETTINGS.erp.ignoreHttpsErrors) + this.set('ERP_AUTO_CLOSE_BROWSER', DEFAULT_SETTINGS.erp.autoCloseBrowser) - this.set('database.dbType', DEFAULT_SETTINGS.database.dbType) - this.set('database.server', DEFAULT_SETTINGS.database.server) - this.set('database.mysqlHost', DEFAULT_SETTINGS.database.mysqlHost) - this.set('database.mysqlPort', DEFAULT_SETTINGS.database.mysqlPort) - this.set('database.database', DEFAULT_SETTINGS.database.database) - this.set('database.username', DEFAULT_SETTINGS.database.username) - this.set('database.password', DEFAULT_SETTINGS.database.password) + this.set('DB_TYPE', DEFAULT_SETTINGS.database.dbType) + this.set('DB_SERVER', DEFAULT_SETTINGS.database.server) + this.set('DB_MYSQL_HOST', DEFAULT_SETTINGS.database.mysqlHost) + this.set('DB_MYSQL_PORT', DEFAULT_SETTINGS.database.mysqlPort) + this.set('DB_NAME', DEFAULT_SETTINGS.database.database) + this.set('DB_USERNAME', DEFAULT_SETTINGS.database.username) + this.set('DB_PASSWORD', DEFAULT_SETTINGS.database.password) - this.set('paths.dataDir', DEFAULT_SETTINGS.paths.dataDir) - this.set('paths.defaultOutput', DEFAULT_SETTINGS.paths.defaultOutput) - this.set('paths.validationOutput', DEFAULT_SETTINGS.paths.validationOutput) + this.set('PATH_DATA_DIR', DEFAULT_SETTINGS.paths.dataDir) + this.set('PATH_DEFAULT_OUTPUT', DEFAULT_SETTINGS.paths.defaultOutput) + this.set('PATH_VALIDATION_OUTPUT', DEFAULT_SETTINGS.paths.validationOutput) - this.set('extraction.batchSize', DEFAULT_SETTINGS.extraction.batchSize) - this.set('extraction.verbose', DEFAULT_SETTINGS.extraction.verbose) - this.set('extraction.autoConvert', DEFAULT_SETTINGS.extraction.autoConvert) - this.set('extraction.mergeBatches', DEFAULT_SETTINGS.extraction.mergeBatches) - this.set('extraction.enableDbPersistence', DEFAULT_SETTINGS.extraction.enableDbPersistence) + this.set('EXTRACTION_BATCH_SIZE', DEFAULT_SETTINGS.extraction.batchSize) + this.set('EXTRACTION_VERBOSE', DEFAULT_SETTINGS.extraction.verbose) + this.set('EXTRACTION_AUTO_CONVERT', DEFAULT_SETTINGS.extraction.autoConvert) + this.set('EXTRACTION_MERGE_BATCHES', DEFAULT_SETTINGS.extraction.mergeBatches) + this.set('EXTRACTION_ENABLE_DB_PERSISTENCE', DEFAULT_SETTINGS.extraction.enableDbPersistence) - this.set('validation.dataSource', DEFAULT_SETTINGS.validation.dataSource) - this.set('validation.batchSize', DEFAULT_SETTINGS.validation.batchSize) - this.set('validation.matchMode', DEFAULT_SETTINGS.validation.matchMode) - this.set('validation.enableCrud', DEFAULT_SETTINGS.validation.enableCrud) - this.set('validation.defaultManager', DEFAULT_SETTINGS.validation.defaultManager) + this.set('VALIDATION_DATA_SOURCE', DEFAULT_SETTINGS.validation.dataSource) + this.set('VALIDATION_BATCH_SIZE', DEFAULT_SETTINGS.validation.batchSize) + this.set('VALIDATION_MATCH_MODE', DEFAULT_SETTINGS.validation.matchMode) + this.set('VALIDATION_ENABLE_CRUD', DEFAULT_SETTINGS.validation.enableCrud) + this.set('VALIDATION_DEFAULT_MANAGER', DEFAULT_SETTINGS.validation.defaultManager) - this.set('ui.fontFamily', DEFAULT_SETTINGS.ui.fontFamily) - this.set('ui.fontSize', DEFAULT_SETTINGS.ui.fontSize) - this.set('ui.productionIdInputWidth', DEFAULT_SETTINGS.ui.productionIdInputWidth) + this.set('UI_FONT_FAMILY', DEFAULT_SETTINGS.ui.fontFamily) + this.set('UI_FONT_SIZE', DEFAULT_SETTINGS.ui.fontSize) + this.set('UI_PRODUCTION_ID_INPUT_WIDTH', DEFAULT_SETTINGS.ui.productionIdInputWidth) - this.set('execution.dryRun', DEFAULT_SETTINGS.execution.dryRun) + this.set('EXECUTION_DRYRUN', DEFAULT_SETTINGS.execution.dryRun) return DEFAULT_SETTINGS }