抽离后台平台配置上下文解析辅助逻辑

This commit is contained in:
yml
2026-05-04 14:35:46 +08:00
parent 5c44184de0
commit 244aa0ed1d
3 changed files with 260 additions and 69 deletions
@@ -0,0 +1,68 @@
// @ts-check
export function pickFirstNonEmpty(values) {
for (const value of values) {
const normalized = String(value || '').trim()
if (normalized) {
return normalized
}
}
return ''
}
export function isPlainObject(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
}
export function resolveKuaishouEticketAdminContext(
payload = {},
options = {},
) {
const savedSource = /** @type {Record<string, unknown>} */ (options.savedSource || {})
const findShopConfig = /** @type {(shopId: string, source: Record<string, unknown>) => unknown} */ (
options.findShopConfig || (() => null)
)
const getFirstAvailableShop = /** @type {(source: Record<string, unknown>) => unknown} */ (
options.getFirstAvailableShop || (() => null)
)
const defaultBaseUrl = String(options.defaultBaseUrl || 'https://s.kwaixiaodian.com').trim()
const requestedShopId = String(payload.shopId || '').trim()
const configuredShop = /** @type {Record<string, unknown> | null} */ (
requestedShopId
? findShopConfig(requestedShopId, savedSource)
: getFirstAvailableShop(savedSource)
)
return {
baseUrl: pickFirstNonEmpty([payload.baseUrl, savedSource.baseUrl, defaultBaseUrl]),
cookie: pickFirstNonEmpty([payload.cookie, configuredShop?.cookie]),
shopId: pickFirstNonEmpty([requestedShopId, configuredShop?.shopId]),
shop: configuredShop,
}
}
export function resolveCloudtentaclesAdminContext(
payload = {},
options = {},
) {
const savedSource = /** @type {Record<string, unknown>} */ (options.savedSource || {})
const persistedSession = /** @type {Record<string, unknown>} */ (options.persistedSession || {})
const defaultBaseUrl = String(options.defaultBaseUrl || 'https://123.207.217.176').trim()
return {
baseUrl: pickFirstNonEmpty([payload.baseUrl, savedSource.baseUrl, defaultBaseUrl]),
token: pickFirstNonEmpty([payload.token, persistedSession.token]),
deviceId: pickFirstNonEmpty([payload.deviceId, savedSource.deviceId, persistedSession.deviceId, '-']),
deviceType: payload.deviceType ?? savedSource.deviceType ?? persistedSession.deviceType,
}
}
export function hasCloudtentaclesCredentialContextChanged(current = {}, next = {}) {
return (
String(current.baseUrl || '').trim() !== String(next.baseUrl || '').trim()
|| String(current.username || '').trim() !== String(next.username || '').trim()
|| String(current.phone || '').trim() !== String(next.phone || '').trim()
|| String(current.deviceId || '').trim() !== String(next.deviceId || '').trim()
|| Number(current.deviceType || 0) !== Number(next.deviceType || 0)
)
}
@@ -0,0 +1,114 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import {
hasCloudtentaclesCredentialContextChanged,
isPlainObject,
pickFirstNonEmpty,
resolveCloudtentaclesAdminContext,
resolveKuaishouEticketAdminContext,
} from './admin-platform-config-context.js'
test('pickFirstNonEmpty returns first trimmed non-empty string value', () => {
assert.equal(pickFirstNonEmpty(['', ' ', ' value ', 'fallback']), 'value')
assert.equal(pickFirstNonEmpty([null, undefined, 0, '']), '')
})
test('isPlainObject only accepts non-array objects', () => {
assert.equal(isPlainObject({ a: 1 }), true)
assert.equal(isPlainObject([]), false)
assert.equal(isPlainObject(null), false)
assert.equal(isPlainObject('x'), false)
})
test('resolveKuaishouEticketAdminContext prefers explicit payload values', () => {
const context = resolveKuaishouEticketAdminContext(
{
baseUrl: ' https://override.example.com ',
shopId: ' shop-2 ',
cookie: ' cookie-2 ',
},
{
savedSource: { baseUrl: 'https://saved.example.com' },
findShopConfig(requestedShopId) {
assert.equal(requestedShopId, 'shop-2')
return { shopId: 'shop-2', cookie: 'saved-cookie-2' }
},
getFirstAvailableShop() {
throw new Error('should not use first available shop')
},
},
)
assert.deepEqual(context, {
baseUrl: 'https://override.example.com',
cookie: 'cookie-2',
shopId: 'shop-2',
shop: { shopId: 'shop-2', cookie: 'saved-cookie-2' },
})
})
test('resolveKuaishouEticketAdminContext falls back to first available shop', () => {
const context = resolveKuaishouEticketAdminContext(
{},
{
savedSource: { baseUrl: ' https://saved.example.com ' },
getFirstAvailableShop() {
return { shopId: 'shop-1', cookie: 'cookie-1' }
},
},
)
assert.deepEqual(context, {
baseUrl: 'https://saved.example.com',
cookie: 'cookie-1',
shopId: 'shop-1',
shop: { shopId: 'shop-1', cookie: 'cookie-1' },
})
})
test('resolveCloudtentaclesAdminContext merges payload source and persisted session', () => {
const context = resolveCloudtentaclesAdminContext(
{
token: ' token-1 ',
deviceType: 3,
},
{
savedSource: {
baseUrl: ' https://saved.example.com ',
deviceId: ' device-saved ',
deviceType: 1,
},
persistedSession: {
token: 'token-persisted',
deviceId: 'device-persisted',
deviceType: 2,
},
},
)
assert.deepEqual(context, {
baseUrl: 'https://saved.example.com',
token: 'token-1',
deviceId: 'device-saved',
deviceType: 3,
})
})
test('hasCloudtentaclesCredentialContextChanged detects normalized credential changes', () => {
assert.equal(
hasCloudtentaclesCredentialContextChanged(
{ baseUrl: ' https://a ', username: 'u1', phone: '13812345678', deviceId: 'd1', deviceType: 1 },
{ baseUrl: 'https://a', username: 'u1', phone: '13812345678', deviceId: 'd1', deviceType: 1 },
),
false,
)
assert.equal(
hasCloudtentaclesCredentialContextChanged(
{ baseUrl: 'https://a', username: 'u1', phone: '13812345678', deviceId: 'd1', deviceType: 1 },
{ baseUrl: 'https://b', username: 'u1', phone: '13812345678', deviceId: 'd1', deviceType: 1 },
),
true,
)
})
@@ -80,6 +80,13 @@ import { formatFenToAmount } from '../../utils/money.js'
import { syncConfiguredFulfillmentBindings } from '../bootstrap/fulfillment-bootstrap-service.js'
import { normalizeProductName } from '../order/product-match-service.js'
import { resolveDisplayShopName } from './admin-read-shared-helpers.js'
import {
hasCloudtentaclesCredentialContextChanged,
isPlainObject,
pickFirstNonEmpty,
resolveCloudtentaclesAdminContext,
resolveKuaishouEticketAdminContext,
} from './admin-platform-config-context.js'
import {
mapAdminCloudtentaclesSession,
mapAdminCloudtentaclesSourceConfig,
@@ -396,7 +403,11 @@ export async function syncAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrderQ
/** @param {AdminKuaishouEticketDetailQueryInput} [payload] */
export async function queryAdminKuaishouEticketDetail(payload = /** @type {AdminKuaishouEticketDetailQueryInput} */ ({})) {
const context = resolveKuaishouEticketAdminContext(payload)
const context = resolveKuaishouEticketAdminContext(payload, {
savedSource: getKuaishouEticketSourceConfig(),
findShopConfig: findKuaishouEticketShopConfig,
getFirstAvailableShop: getFirstAvailableKuaishouEticketShop,
})
return queryKuaishouEticketConsumeDetail({
baseUrl: context.baseUrl,
@@ -407,7 +418,11 @@ export async function queryAdminKuaishouEticketDetail(payload = /** @type {Admin
/** @param {AdminKuaishouEticketShopInfoInput} [payload] */
export async function queryAdminKuaishouEticketShopInfo(payload = /** @type {AdminKuaishouEticketShopInfoInput} */ ({})) {
const context = resolveKuaishouEticketAdminContext(payload)
const context = resolveKuaishouEticketAdminContext(payload, {
savedSource: getKuaishouEticketSourceConfig(),
findShopConfig: findKuaishouEticketShopConfig,
getFirstAvailableShop: getFirstAvailableKuaishouEticketShop,
})
return queryKuaishouEticketStableInfo({
baseUrl: context.baseUrl,
cookie: context.cookie,
@@ -416,7 +431,11 @@ export async function queryAdminKuaishouEticketShopInfo(payload = /** @type {Adm
/** @param {AdminKuaishouEticketConsumeInput} [payload] */
export async function consumeAdminKuaishouEticket(payload = /** @type {AdminKuaishouEticketConsumeInput} */ ({})) {
const context = resolveKuaishouEticketAdminContext(payload)
const context = resolveKuaishouEticketAdminContext(payload, {
savedSource: getKuaishouEticketSourceConfig(),
findShopConfig: findKuaishouEticketShopConfig,
getFirstAvailableShop: getFirstAvailableKuaishouEticketShop,
})
return consumeKuaishouEticket({
baseUrl: context.baseUrl,
@@ -557,25 +576,37 @@ export async function validateAdminCloudtentaclesSession(payload = /** @type {Ad
/** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */
export async function getAdminCloudtentaclesAsset(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) {
const context = resolveCloudtentaclesAdminContext(payload)
const context = resolveCloudtentaclesAdminContext(payload, {
savedSource: getCloudtentaclesSourceConfig(),
persistedSession: getCloudtentaclesSessionState(),
})
return getCloudtentaclesAsset(context)
}
/** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */
export async function getAdminCloudtentaclesCategories(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) {
const context = resolveCloudtentaclesAdminContext(payload)
const context = resolveCloudtentaclesAdminContext(payload, {
savedSource: getCloudtentaclesSourceConfig(),
persistedSession: getCloudtentaclesSessionState(),
})
return getCloudtentaclesCategories(context)
}
/** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */
export async function getAdminCloudtentaclesSkuList(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) {
const context = resolveCloudtentaclesAdminContext(payload)
const context = resolveCloudtentaclesAdminContext(payload, {
savedSource: getCloudtentaclesSourceConfig(),
persistedSession: getCloudtentaclesSessionState(),
})
return listCloudtentaclesSku(context)
}
/** @param {AdminCloudtentaclesSkuBuyInput} [payload] */
export async function buyAdminCloudtentaclesSku(payload = /** @type {AdminCloudtentaclesSkuBuyInput} */ ({})) {
const context = resolveCloudtentaclesAdminContext(payload)
const context = resolveCloudtentaclesAdminContext(payload, {
savedSource: getCloudtentaclesSourceConfig(),
persistedSession: getCloudtentaclesSessionState(),
})
return buyCloudtentaclesSku({
...context,
id: payload.id,
@@ -585,7 +616,10 @@ export async function buyAdminCloudtentaclesSku(payload = /** @type {AdminCloudt
/** @param {AdminCloudtentaclesSkuUseInput} [payload] */
export async function useAdminCloudtentaclesSku(payload = /** @type {AdminCloudtentaclesSkuUseInput} */ ({})) {
const context = resolveCloudtentaclesAdminContext(payload)
const context = resolveCloudtentaclesAdminContext(payload, {
savedSource: getCloudtentaclesSourceConfig(),
persistedSession: getCloudtentaclesSessionState(),
})
return useCloudtentaclesSku({
...context,
id: payload.id,
@@ -596,13 +630,19 @@ export async function useAdminCloudtentaclesSku(payload = /** @type {AdminCloudt
/** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */
export async function getAdminCloudtentaclesKnapsack(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) {
const context = resolveCloudtentaclesAdminContext(payload)
const context = resolveCloudtentaclesAdminContext(payload, {
savedSource: getCloudtentaclesSourceConfig(),
persistedSession: getCloudtentaclesSessionState(),
})
return getCloudtentaclesKnapsack(context)
}
/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */
export async function listAdminCloudtentaclesVirtualNumbers(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) {
const context = resolveCloudtentaclesAdminContext(payload)
const context = resolveCloudtentaclesAdminContext(payload, {
savedSource: getCloudtentaclesSourceConfig(),
persistedSession: getCloudtentaclesSessionState(),
})
return listCloudtentaclesVirtualNumbers({
...context,
key: payload.key,
@@ -611,7 +651,10 @@ export async function listAdminCloudtentaclesVirtualNumbers(payload = /** @type
/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */
export async function appointAdminCloudtentaclesVirtualNumber(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) {
const context = resolveCloudtentaclesAdminContext(payload)
const context = resolveCloudtentaclesAdminContext(payload, {
savedSource: getCloudtentaclesSourceConfig(),
persistedSession: getCloudtentaclesSessionState(),
})
return appointCloudtentaclesVirtualNumber({
...context,
key: payload.key,
@@ -620,7 +663,10 @@ export async function appointAdminCloudtentaclesVirtualNumber(payload = /** @typ
/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */
export async function generateAdminCloudtentaclesLoginCode(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) {
const context = resolveCloudtentaclesAdminContext(payload)
const context = resolveCloudtentaclesAdminContext(payload, {
savedSource: getCloudtentaclesSourceConfig(),
persistedSession: getCloudtentaclesSessionState(),
})
return generateCloudtentaclesLoginCode({
...context,
key: payload.key,
@@ -630,7 +676,10 @@ export async function generateAdminCloudtentaclesLoginCode(payload = /** @type {
/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */
export async function fetchAdminCloudtentaclesVirtualNumberCode(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) {
const context = resolveCloudtentaclesAdminContext(payload)
const context = resolveCloudtentaclesAdminContext(payload, {
savedSource: getCloudtentaclesSourceConfig(),
persistedSession: getCloudtentaclesSessionState(),
})
return fetchCloudtentaclesVirtualNumberCode({
...context,
key: payload.key,
@@ -640,7 +689,10 @@ export async function fetchAdminCloudtentaclesVirtualNumberCode(payload = /** @t
/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */
export async function verifyAdminCloudtentaclesLoginCode(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) {
const context = resolveCloudtentaclesAdminContext(payload)
const context = resolveCloudtentaclesAdminContext(payload, {
savedSource: getCloudtentaclesSourceConfig(),
persistedSession: getCloudtentaclesSessionState(),
})
return verifyCloudtentaclesLoginCode({
...context,
key: payload.key,
@@ -651,7 +703,10 @@ export async function verifyAdminCloudtentaclesLoginCode(payload = /** @type {Ad
/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */
export async function getAdminCloudtentaclesBindUrl(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) {
const context = resolveCloudtentaclesAdminContext(payload)
const context = resolveCloudtentaclesAdminContext(payload, {
savedSource: getCloudtentaclesSourceConfig(),
persistedSession: getCloudtentaclesSessionState(),
})
return getCloudtentaclesBindUrl({
...context,
key: payload.key,
@@ -661,7 +716,10 @@ export async function getAdminCloudtentaclesBindUrl(payload = /** @type {AdminCl
/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */
export async function backAdminCloudtentaclesVirtualNumber(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) {
const context = resolveCloudtentaclesAdminContext(payload)
const context = resolveCloudtentaclesAdminContext(payload, {
savedSource: getCloudtentaclesSourceConfig(),
persistedSession: getCloudtentaclesSessionState(),
})
return backCloudtentaclesVirtualNumber({
...context,
key: payload.key,
@@ -671,7 +729,10 @@ export async function backAdminCloudtentaclesVirtualNumber(payload = /** @type {
/** @param {AdminCloudtentaclesFullFlowInput} [payload] */
export async function runAdminCloudtentaclesFullFlow(payload = /** @type {AdminCloudtentaclesFullFlowInput} */ ({})) {
const context = resolveCloudtentaclesAdminContext(payload)
const context = resolveCloudtentaclesAdminContext(payload, {
savedSource: getCloudtentaclesSourceConfig(),
persistedSession: getCloudtentaclesSessionState(),
})
return runCloudtentaclesFullDebugFlow({
...context,
skuId: payload.skuId,
@@ -1125,55 +1186,3 @@ function mapAdminObservedProductItem(item, bindings = []) {
function findMatchingObservedBinding(bindings, observed) {
return (Array.isArray(bindings) ? bindings : []).find((binding) => matchesObservedProduct(binding, observed)) || null
}
function resolveKuaishouEticketAdminContext(payload = {}) {
const savedSource = getKuaishouEticketSourceConfig()
const requestedShopId = String(payload.shopId || '').trim()
const configuredShop = requestedShopId
? findKuaishouEticketShopConfig(requestedShopId, savedSource)
: getFirstAvailableKuaishouEticketShop(savedSource)
return {
baseUrl: pickFirstNonEmpty([payload.baseUrl, savedSource.baseUrl, 'https://s.kwaixiaodian.com']),
cookie: pickFirstNonEmpty([payload.cookie, configuredShop?.cookie]),
shopId: pickFirstNonEmpty([requestedShopId, configuredShop?.shopId]),
shop: configuredShop,
}
}
function resolveCloudtentaclesAdminContext(payload = {}) {
const savedSource = getCloudtentaclesSourceConfig()
const persistedSession = getCloudtentaclesSessionState()
return {
baseUrl: pickFirstNonEmpty([payload.baseUrl, savedSource.baseUrl, 'https://123.207.217.176']),
token: pickFirstNonEmpty([payload.token, persistedSession.token]),
deviceId: pickFirstNonEmpty([payload.deviceId, savedSource.deviceId, persistedSession.deviceId, '-']),
deviceType: payload.deviceType ?? savedSource.deviceType ?? persistedSession.deviceType,
}
}
function hasCloudtentaclesCredentialContextChanged(current, next) {
return (
String(current.baseUrl || '').trim() !== String(next.baseUrl || '').trim()
|| String(current.username || '').trim() !== String(next.username || '').trim()
|| String(current.phone || '').trim() !== String(next.phone || '').trim()
|| String(current.deviceId || '').trim() !== String(next.deviceId || '').trim()
|| Number(current.deviceType || 0) !== Number(next.deviceType || 0)
)
}
function isPlainObject(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
}
function pickFirstNonEmpty(values) {
for (const value of values) {
const normalized = String(value || '').trim()
if (normalized) {
return normalized
}
}
return ''
}