From dad646b6780159a52f27732c232a84f54e8dc72c Mon Sep 17 00:00:00 2001 From: yml Date: Mon, 4 May 2026 15:39:35 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=BD=E7=A6=BB=E5=90=8E=E5=8F=B0=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E9=85=8D=E7=BD=AE=E5=86=99=E5=85=A5=E5=BD=92=E4=B8=80?= =?UTF-8?q?=E5=8C=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../services/admin/platform-config/service.js | 101 ++--------- .../services/admin/platform-config/writes.js | 117 +++++++++++++ .../admin/platform-config/writes.test.js | 157 ++++++++++++++++++ 3 files changed, 285 insertions(+), 90 deletions(-) create mode 100644 apps/backend/src/services/admin/platform-config/writes.js create mode 100644 apps/backend/src/services/admin/platform-config/writes.test.js diff --git a/apps/backend/src/services/admin/platform-config/service.js b/apps/backend/src/services/admin/platform-config/service.js index da992f9e..d22bdd48 100644 --- a/apps/backend/src/services/admin/platform-config/service.js +++ b/apps/backend/src/services/admin/platform-config/service.js @@ -99,7 +99,11 @@ import { validateAdminFulfillmentBindingConfigs, } from './validation.js' import { - applyOptionalStringField, + buildAdminAgisoMessagingSavePayload, + normalizeAdminCloudtentaclesSourceConfigPayload, + normalizeAdminKhhaoSourceConfigPayload, +} from './writes.js' +import { mapAdminAgisoMessagingDefaults, mapAdminAgisoShopConfigItem, mapAdminFulfillmentBindingConfigItem, @@ -173,73 +177,10 @@ export async function getAdminAgisoShopConfigs() { /** @param {AdminAgisoShopConfigSaveInput} [payload] */ export function updateAdminAgisoShopConfigs(payload = /** @type {AdminAgisoShopConfigSaveInput} */ ({})) { - const rawItems = Array.isArray(payload.shops) ? payload.shops : [] - const currentDefaults = getAgisoMessagingDefaults() - const currentMap = getAgisoShopConfigMap() - const nextDefaults = { ...currentDefaults } - const nextMap = {} - - applyOptionalStringField(nextDefaults, 'messageTemplate', payload.defaults) - applyOptionalStringField(nextDefaults, 'autoDeliveryMessageTemplate', payload.defaults) - - for (const item of rawItems) { - const shopId = String(item?.shopId || '').trim() - if (!shopId) { - continue - } - - const current = currentMap[shopId] || {} - const next = { ...current } - const shopName = String(item?.shopName || '').trim() - const accessToken = String(item?.accessToken || '').trim() - const messageTemplate = String(item?.messageTemplate || '').trim() - const autoDeliveryMessageTemplate = String(item?.autoDeliveryMessageTemplate || '').trim() - const appSecret = String(item?.appSecret || '').trim() - const apiVersion = String(item?.apiVersion || '').trim() - const sendMessageEndpoint = String(item?.sendMessageEndpoint || '').trim() - - if (shopName) { - next.shopName = shopName - } else { - delete next.shopName - } - if (accessToken) { - next.accessToken = accessToken - } - if (messageTemplate) { - next.messageTemplate = messageTemplate - } else if (typeof item?.messageTemplate === 'string') { - delete next.messageTemplate - } - if (autoDeliveryMessageTemplate) { - next.autoDeliveryMessageTemplate = autoDeliveryMessageTemplate - } else if (typeof item?.autoDeliveryMessageTemplate === 'string') { - delete next.autoDeliveryMessageTemplate - } - if (appSecret) { - next.appSecret = appSecret - } - if (apiVersion) { - next.apiVersion = apiVersion - } - if (sendMessageEndpoint) { - next.sendMessageEndpoint = sendMessageEndpoint - } - if (typeof item?.enabled === 'boolean') { - next.enabled = item.enabled - } - - if (!next.accessToken) { - continue - } - - nextMap[shopId] = next - } - - const saved = saveAgisoMessagingConfig({ - defaults: nextDefaults, - shops: nextMap, - }) + const saved = saveAgisoMessagingConfig(buildAdminAgisoMessagingSavePayload(payload, { + currentDefaults: getAgisoMessagingDefaults(), + currentMap: getAgisoShopConfigMap(), + })) return { filePath: getAgisoShopsFilePath(), @@ -287,19 +228,7 @@ export function updateAdminKuaishouEticketSourceConfig(payload = /** @type {Admi /** @param {AdminKhhaoSourceConfigInput} [payload] */ export function updateAdminKhhaoSourceConfig(payload = /** @type {AdminKhhaoSourceConfigInput} */ ({})) { - const saved = saveKhhaoSourceConfig({ - enabled: payload.enabled !== false, - baseUrl: String(payload.baseUrl || '').trim() || 'https://admin.khhao.com', - username: String(payload.username || '').trim(), - password: String(payload.password || '').trim(), - maxCaptchaAttempts: Number(payload.maxCaptchaAttempts || 3) || 3, - autoSync: { - enabled: payload.autoSync?.enabled === true, - intervalMinutes: Number(payload.autoSync?.intervalMinutes || 3) || 3, - pages: Number(payload.autoSync?.pages || 2) || 2, - pageSize: Number(payload.autoSync?.pageSize || 20) || 20, - }, - }) + const saved = saveKhhaoSourceConfig(normalizeAdminKhhaoSourceConfigPayload(payload)) const syncState = saved.autoSync?.enabled ? ensureKhhaoSyncBaseline() : getKhhaoSyncState() return { @@ -444,15 +373,7 @@ export function getAdminCloudtentaclesSourceConfig() { /** @param {AdminCloudtentaclesSourceConfigInput} [payload] */ export function updateAdminCloudtentaclesSourceConfig(payload = /** @type {AdminCloudtentaclesSourceConfigInput} */ ({})) { const current = getCloudtentaclesSourceConfig() - const saved = saveCloudtentaclesSourceConfig({ - enabled: payload.enabled !== false, - baseUrl: String(payload.baseUrl || '').trim() || 'https://123.207.217.176', - username: String(payload.username || '').trim(), - password: String(payload.password || '').trim(), - phone: String(payload.phone || '').trim(), - deviceId: String(payload.deviceId || '-').trim() || '-', - deviceType: Number(payload.deviceType || 0), - }) + const saved = saveCloudtentaclesSourceConfig(normalizeAdminCloudtentaclesSourceConfigPayload(payload)) const shouldClearSession = hasCloudtentaclesCredentialContextChanged(current, saved) const session = shouldClearSession ? clearCloudtentaclesSessionState() : getCloudtentaclesSessionState() diff --git a/apps/backend/src/services/admin/platform-config/writes.js b/apps/backend/src/services/admin/platform-config/writes.js new file mode 100644 index 00000000..5cfe0f75 --- /dev/null +++ b/apps/backend/src/services/admin/platform-config/writes.js @@ -0,0 +1,117 @@ +// @ts-check + +import { applyOptionalStringField } from './domain.js' + +export function buildAdminAgisoShopConfigUpdate(item, current = {}) { + const shopId = String(item?.shopId || '').trim() + if (!shopId) { + return null + } + + const next = /** @type {Record} */ ({ ...current }) + const shopName = String(item?.shopName || '').trim() + const accessToken = String(item?.accessToken || '').trim() + const messageTemplate = String(item?.messageTemplate || '').trim() + const autoDeliveryMessageTemplate = String(item?.autoDeliveryMessageTemplate || '').trim() + const appSecret = String(item?.appSecret || '').trim() + const apiVersion = String(item?.apiVersion || '').trim() + const sendMessageEndpoint = String(item?.sendMessageEndpoint || '').trim() + + if (shopName) { + next.shopName = shopName + } else { + delete next.shopName + } + if (accessToken) { + next.accessToken = accessToken + } + if (messageTemplate) { + next.messageTemplate = messageTemplate + } else if (typeof item?.messageTemplate === 'string') { + delete next.messageTemplate + } + if (autoDeliveryMessageTemplate) { + next.autoDeliveryMessageTemplate = autoDeliveryMessageTemplate + } else if (typeof item?.autoDeliveryMessageTemplate === 'string') { + delete next.autoDeliveryMessageTemplate + } + if (appSecret) { + next.appSecret = appSecret + } + if (apiVersion) { + next.apiVersion = apiVersion + } + if (sendMessageEndpoint) { + next.sendMessageEndpoint = sendMessageEndpoint + } + if (typeof item?.enabled === 'boolean') { + next.enabled = item.enabled + } + + if (!next.accessToken) { + return null + } + + return { shopId, config: next } +} + +export function buildAdminAgisoMessagingSavePayload( + payload = {}, + options = {}, +) { + const rawItems = Array.isArray(payload.shops) ? payload.shops : [] + const currentDefaults = /** @type {Record} */ (options.currentDefaults || {}) + const currentMap = /** @type {Record>} */ (options.currentMap || {}) + const nextDefaults = /** @type {Record} */ ({ ...currentDefaults }) + const nextMap = /** @type {Record>} */ ({}) + + applyOptionalStringField(nextDefaults, 'messageTemplate', payload.defaults) + applyOptionalStringField(nextDefaults, 'autoDeliveryMessageTemplate', payload.defaults) + + for (const item of rawItems) { + const shopId = String(item?.shopId || '').trim() + if (!shopId) { + continue + } + + const updated = buildAdminAgisoShopConfigUpdate(item, currentMap[shopId] || {}) + if (!updated) { + continue + } + + nextMap[updated.shopId] = updated.config + } + + return { + defaults: nextDefaults, + shops: nextMap, + } +} + +export function normalizeAdminKhhaoSourceConfigPayload(payload = {}) { + return { + enabled: payload.enabled !== false, + baseUrl: String(payload.baseUrl || '').trim() || 'https://admin.khhao.com', + username: String(payload.username || '').trim(), + password: String(payload.password || '').trim(), + maxCaptchaAttempts: Number(payload.maxCaptchaAttempts || 3) || 3, + autoSync: { + enabled: payload.autoSync?.enabled === true, + intervalMinutes: Number(payload.autoSync?.intervalMinutes || 3) || 3, + pages: Number(payload.autoSync?.pages || 2) || 2, + pageSize: Number(payload.autoSync?.pageSize || 20) || 20, + }, + } +} + +export function normalizeAdminCloudtentaclesSourceConfigPayload(payload = {}) { + return { + enabled: payload.enabled !== false, + baseUrl: String(payload.baseUrl || '').trim() || 'https://123.207.217.176', + username: String(payload.username || '').trim(), + password: String(payload.password || '').trim(), + phone: String(payload.phone || '').trim(), + deviceId: String(payload.deviceId || '-').trim() || '-', + deviceType: Number(payload.deviceType || 0), + } +} diff --git a/apps/backend/src/services/admin/platform-config/writes.test.js b/apps/backend/src/services/admin/platform-config/writes.test.js new file mode 100644 index 00000000..056fb0bf --- /dev/null +++ b/apps/backend/src/services/admin/platform-config/writes.test.js @@ -0,0 +1,157 @@ +import test from 'node:test' +import assert from 'node:assert/strict' + +import { + buildAdminAgisoMessagingSavePayload, + buildAdminAgisoShopConfigUpdate, + normalizeAdminCloudtentaclesSourceConfigPayload, + normalizeAdminKhhaoSourceConfigPayload, +} from './writes.js' + +test('buildAdminAgisoShopConfigUpdate merges item fields and drops explicit empty templates', () => { + assert.deepEqual( + buildAdminAgisoShopConfigUpdate( + { + shopId: ' shop-1 ', + shopName: ' ', + accessToken: ' new-token ', + messageTemplate: ' ', + autoDeliveryMessageTemplate: ' done ', + enabled: false, + }, + { + shopName: '旧店铺', + accessToken: 'old-token', + messageTemplate: 'old-template', + autoDeliveryMessageTemplate: 'old-auto', + appSecret: 'keep-secret', + }, + ), + { + shopId: 'shop-1', + config: { + accessToken: 'new-token', + autoDeliveryMessageTemplate: 'done', + appSecret: 'keep-secret', + enabled: false, + }, + }, + ) +}) + +test('buildAdminAgisoMessagingSavePayload updates defaults and keeps only shops with resulting access token', () => { + assert.deepEqual( + buildAdminAgisoMessagingSavePayload( + { + defaults: { + messageTemplate: ' next\\nline ', + autoDeliveryMessageTemplate: ' ', + }, + shops: [ + { + shopId: ' shop-1 ', + shopName: '店铺一', + accessToken: '', + enabled: true, + }, + { + shopId: 'shop-2', + shopName: ' 店铺二 ', + accessToken: ' token-2 ', + messageTemplate: ' hi ', + apiVersion: ' v2 ', + }, + { + shopId: '', + accessToken: 'skip', + }, + ], + }, + { + currentDefaults: { + messageTemplate: 'old', + autoDeliveryMessageTemplate: 'old-auto', + }, + currentMap: { + 'shop-1': { + accessToken: 'token-1', + autoDeliveryMessageTemplate: 'keep-auto', + }, + }, + }, + ), + { + defaults: { + messageTemplate: 'next\nline', + }, + shops: { + 'shop-1': { + accessToken: 'token-1', + autoDeliveryMessageTemplate: 'keep-auto', + enabled: true, + shopName: '店铺一', + }, + 'shop-2': { + accessToken: 'token-2', + apiVersion: 'v2', + messageTemplate: 'hi', + shopName: '店铺二', + }, + }, + }, + ) +}) + +test('normalizeAdminKhhaoSourceConfigPayload applies defaults and numeric fallbacks', () => { + assert.deepEqual( + normalizeAdminKhhaoSourceConfigPayload({ + enabled: true, + baseUrl: ' https://admin.khhao.com ', + username: ' user ', + password: ' pass ', + maxCaptchaAttempts: 0, + autoSync: { + enabled: true, + intervalMinutes: '', + pages: '0', + pageSize: '50', + }, + }), + { + enabled: true, + baseUrl: 'https://admin.khhao.com', + username: 'user', + password: 'pass', + maxCaptchaAttempts: 3, + autoSync: { + enabled: true, + intervalMinutes: 3, + pages: 2, + pageSize: 50, + }, + }, + ) +}) + +test('normalizeAdminCloudtentaclesSourceConfigPayload applies defaults', () => { + assert.deepEqual( + normalizeAdminCloudtentaclesSourceConfigPayload({ + enabled: false, + baseUrl: ' ', + username: ' user ', + password: ' pass ', + phone: ' 13800138000 ', + deviceId: ' ', + deviceType: '2', + }), + { + enabled: false, + baseUrl: 'https://123.207.217.176', + username: 'user', + password: 'pass', + phone: '13800138000', + deviceId: '-', + deviceType: 2, + }, + ) +})