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.
This commit is contained in:
Misaka_Company
2026-03-03 15:42:40 +08:00
parent 73a49f9de3
commit 816060444c

View File

@@ -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<boolean> {
// 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
}