import fs from 'node:fs' import path from 'node:path' import { APP_CONFIG_KEYS } from '../../config/app-config-keys.js' import { PROJECT_ROOT } from '../../config/runtime.js' import { upsertAppConfigEntry } from '../../repositories/app-config-repo.js' import { logInfo, logWarn } from '../../utils/logger.js' import { normalizeFulfillmentRoutingConfig } from '../fulfillment/routing-config-service.js' import { normalizeNotificationConfig } from '../notification/config-service.js' import { normalizeCloudtentaclesOverrideRuleConfig } from '../order/cloudtentacles-override-rule-service.js' import { normalizeCloudtentaclesSourcesConfig } from '../platforms/cloudtentacles/source-config-service.js' import { normalizeSessionStatesFile } from '../platforms/cloudtentacles/session-state-service.js' import { normalizeKuaishouFeifeiSourceConfig } from '../platforms/kuaishou-feifei/source-config-service.js' import { normalizeKuaishouIndustrySourceConfig } from '../platforms/kuaishou-industry/source-config-service.js' import { normalizeScheduledJobsConfig } from '../scheduler/config-service.js' import { loadAppConfigCacheFromDatabase } from './app-config-store.js' type JsonConfigMigrationItem = { configKey: string fileName: string normalize: (value: unknown) => unknown } const DATA_DIR = path.join(PROJECT_ROOT, 'data') const JSON_CONFIG_MIGRATION_ITEMS: JsonConfigMigrationItem[] = [ { configKey: APP_CONFIG_KEYS.notification, fileName: 'notification-config.json', normalize: normalizeNotificationConfig, }, { configKey: APP_CONFIG_KEYS.scheduledJobs, fileName: 'scheduled-jobs.json', normalize: normalizeScheduledJobsConfig, }, { configKey: APP_CONFIG_KEYS.fulfillmentRouting, fileName: 'fulfillment-routing-config.json', normalize: normalizeFulfillmentRoutingConfig, }, { configKey: APP_CONFIG_KEYS.cloudtentaclesSources, fileName: 'cloudtentacles-sources.json', normalize: normalizeCloudtentaclesSourcesConfig, }, { configKey: APP_CONFIG_KEYS.cloudtentaclesSession, fileName: 'cloudtentacles-session.json', normalize: normalizeSessionStatesFile, }, { configKey: APP_CONFIG_KEYS.cloudtentaclesOverrideRules, fileName: 'cloudtentacles-override-rules.json', normalize: normalizeCloudtentaclesOverrideRuleConfig, }, { configKey: APP_CONFIG_KEYS.kuaishouFeifei, fileName: 'kuaishou-feifei-config.json', normalize: normalizeKuaishouFeifeiSourceConfig, }, { configKey: APP_CONFIG_KEYS.kuaishouIndustrySource, fileName: 'kuaishou-industry-source.json', normalize: normalizeKuaishouIndustrySourceConfig, }, ] export async function migrateJsonConfigFilesToDatabase() { const migrated: Array<{ configKey: string; filePath: string }> = [] const skipped: Array<{ configKey: string; filePath: string; reason: string }> = [] for (const item of JSON_CONFIG_MIGRATION_ITEMS) { const filePath = path.join(DATA_DIR, item.fileName) if (!fs.existsSync(filePath)) { continue } let normalized: unknown try { const rawText = fs.readFileSync(filePath, 'utf8') normalized = item.normalize(JSON.parse(rawText)) } catch (error) { skipped.push({ configKey: item.configKey, filePath, reason: error instanceof Error ? error.message : String(error), }) logWarn('[config/migration]', 'JSON 配置解析失败,已保留原文件', { configKey: item.configKey, filePath, error, }) continue } await upsertAppConfigEntry({ configKey: item.configKey, configJson: normalized, sourceFilePath: filePath, migratedFromFile: true, timestamp: new Date().toISOString(), }) try { fs.unlinkSync(filePath) migrated.push({ configKey: item.configKey, filePath, }) } catch (error) { skipped.push({ configKey: item.configKey, filePath, reason: error instanceof Error ? error.message : String(error), }) logWarn('[config/migration]', 'JSON 配置已入库,但删除原文件失败', { configKey: item.configKey, filePath, error, }) } } await loadAppConfigCacheFromDatabase() if (migrated.length > 0 || skipped.length > 0) { logInfo('[config/migration]', 'JSON 配置文件迁移完成', { migratedCount: migrated.length, skippedCount: skipped.length, migrated, skipped, }) } return { migrated, skipped, } }