抽离后台平台配置写入归一化逻辑

This commit is contained in:
yml
2026-05-04 15:39:35 +08:00
parent 8c9d487050
commit dad646b678
3 changed files with 285 additions and 90 deletions
@@ -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()
@@ -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<string, unknown>} */ ({ ...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<string, unknown>} */ (options.currentDefaults || {})
const currentMap = /** @type {Record<string, Record<string, unknown>>} */ (options.currentMap || {})
const nextDefaults = /** @type {Record<string, unknown>} */ ({ ...currentDefaults })
const nextMap = /** @type {Record<string, Record<string, unknown>>} */ ({})
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),
}
}
@@ -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,
},
)
})