feat: 增加多账号备选退避机制支持 cloudSourceKeyFallbacks
- kuaishou-cloud-fulfillment-config-service: 新增 cloudSourceKeyFallbacks 字段和 normalizeStringArray 辅助函数 - kuaishou-cloud-task-service: 新增 resolvePersistedCloudtentaclesContextWithFallback 函数实现备选账号自动切换 - 改造 5 个调用点(prepare/refresh/roleRefresh/dispatch/return)使用 fallback 机制 - binding 中新增 resolvedSourceKey 字段持久化首次选定的 sourceKey - admin-write-inputs: 类型定义增加 cloudSourceKeyFallbacks 可选字段 - 向后兼容:旧数据无 fallbacks 字段时等同于旧行为
This commit is contained in:
@@ -1,174 +1,227 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import { PROJECT_ROOT } from '../../config/runtime.js'
|
||||
import { resolveKuaishouEticketShopConfig } from '../platforms/kuaishou-eticket/source-config-service.js'
|
||||
import { PROJECT_ROOT } from "../../config/runtime.js";
|
||||
import { resolveKuaishouEticketShopConfig } from "../platforms/kuaishou-eticket/source-config-service.js";
|
||||
|
||||
const KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'kuaishou-cloud-fulfillment.json')
|
||||
const KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH = path.join(
|
||||
PROJECT_ROOT,
|
||||
"data",
|
||||
"kuaishou-cloud-fulfillment.json"
|
||||
);
|
||||
|
||||
export function getKuaishouCloudFulfillmentFilePath() {
|
||||
return KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH
|
||||
return KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH;
|
||||
}
|
||||
|
||||
export function getKuaishouCloudFulfillmentConfig() {
|
||||
return loadKuaishouCloudFulfillmentConfigFromFile()
|
||||
return loadKuaishouCloudFulfillmentConfigFromFile();
|
||||
}
|
||||
|
||||
export function saveKuaishouCloudFulfillmentConfig(rawValue) {
|
||||
const normalized = normalizeKuaishouCloudFulfillmentConfig(rawValue)
|
||||
fs.mkdirSync(path.dirname(KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH), { recursive: true })
|
||||
const normalized = normalizeKuaishouCloudFulfillmentConfig(rawValue);
|
||||
fs.mkdirSync(path.dirname(KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH), {
|
||||
recursive: true,
|
||||
});
|
||||
fs.writeFileSync(
|
||||
KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH,
|
||||
`${JSON.stringify(normalized, null, 2)}\n`,
|
||||
'utf8',
|
||||
)
|
||||
return normalized
|
||||
"utf8"
|
||||
);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function mapKuaishouCloudFulfillmentItemsToBindings(config = {}) {
|
||||
const items = Array.isArray(config.items) ? config.items : []
|
||||
const items = Array.isArray(config.items) ? config.items : [];
|
||||
|
||||
return items
|
||||
.filter((item) => item && item.enabled !== false)
|
||||
.map((item) => ({
|
||||
provider: String(item.provider || '91kaquan').trim() || '91kaquan',
|
||||
platform: String(item.platform || 'kuaishou').trim() || 'kuaishou',
|
||||
shopId: String(item.shopId || '').trim(),
|
||||
skuCode: String(item.internalSkuCode || '').trim(),
|
||||
skuName: String(item.internalSkuName || '').trim(),
|
||||
profileKey: 'kuaishou_ct_assisted',
|
||||
provider: String(item.provider || "91kaquan").trim() || "91kaquan",
|
||||
platform: String(item.platform || "kuaishou").trim() || "kuaishou",
|
||||
shopId: String(item.shopId || "").trim(),
|
||||
skuCode: String(item.internalSkuCode || "").trim(),
|
||||
skuName: String(item.internalSkuName || "").trim(),
|
||||
profileKey: "kuaishou_ct_assisted",
|
||||
enabled: item.enabled !== false,
|
||||
priority: normalizePriority(item.priority),
|
||||
config: {
|
||||
flowType: 'kuaishou_cloud_fulfillment',
|
||||
configId: String(item.id || '').trim(),
|
||||
flowType: "kuaishou_cloud_fulfillment",
|
||||
configId: String(item.id || "").trim(),
|
||||
cloudtentacles: {
|
||||
cloudSourceKey: String(item.cloudSourceKey || 'default').trim() || 'default',
|
||||
cloudSourceKey:
|
||||
String(item.cloudSourceKey || "default").trim() || "default",
|
||||
cloudSourceKeyFallbacks: Array.isArray(item.cloudSourceKeyFallbacks)
|
||||
? item.cloudSourceKeyFallbacks
|
||||
: [],
|
||||
skuId: normalizePositiveInteger(item.cloudSkuId),
|
||||
skuName: String(item.cloudSkuName || '').trim(),
|
||||
vnKey: '1',
|
||||
skuName: String(item.cloudSkuName || "").trim(),
|
||||
vnKey: "1",
|
||||
autoBuyEnabled: item.autoBuyEnabled !== false,
|
||||
minAssetReserve: normalizeNonNegativeInteger(item.minAssetReserve, 0),
|
||||
autoReturnNumberAfterDispatch: item.autoReturnNumberAfterDispatch === true,
|
||||
autoReturnNumberAfterDispatch:
|
||||
item.autoReturnNumberAfterDispatch === true,
|
||||
},
|
||||
kuaishouConsume: {
|
||||
shopId: String(item.kuaishouConsumeShopId || '').trim(),
|
||||
shopName: String(item.kuaishouConsumeShopName || '').trim(),
|
||||
shopId: String(item.kuaishouConsumeShopId || "").trim(),
|
||||
shopName: String(item.kuaishouConsumeShopName || "").trim(),
|
||||
autoConsumeAfterDispatch: item.autoConsumeAfterDispatch === true,
|
||||
},
|
||||
notes: String(item.notes || '').trim(),
|
||||
notes: String(item.notes || "").trim(),
|
||||
},
|
||||
match: {
|
||||
externalSkuCode: String(item.externalSkuCode || '').trim(),
|
||||
externalItemId: String(item.externalItemId || '').trim(),
|
||||
externalSkuName: String(item.externalSkuName || '').trim(),
|
||||
externalSkuCode: String(item.externalSkuCode || "").trim(),
|
||||
externalItemId: String(item.externalItemId || "").trim(),
|
||||
externalSkuName: String(item.externalSkuName || "").trim(),
|
||||
config: {
|
||||
resolvedSkuName: String(item.resolvedSkuName || item.internalSkuName || '').trim(),
|
||||
resolvedSkuName: String(
|
||||
item.resolvedSkuName || item.internalSkuName || ""
|
||||
).trim(),
|
||||
},
|
||||
},
|
||||
}))
|
||||
.filter((item) => item.skuCode && (item.match.externalSkuCode || item.match.externalItemId || item.match.externalSkuName))
|
||||
.filter(
|
||||
(item) =>
|
||||
item.skuCode &&
|
||||
(item.match.externalSkuCode ||
|
||||
item.match.externalItemId ||
|
||||
item.match.externalSkuName)
|
||||
);
|
||||
}
|
||||
|
||||
function loadKuaishouCloudFulfillmentConfigFromFile() {
|
||||
if (!fs.existsSync(KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH)) {
|
||||
return normalizeKuaishouCloudFulfillmentConfig({})
|
||||
return normalizeKuaishouCloudFulfillmentConfig({});
|
||||
}
|
||||
|
||||
try {
|
||||
const rawText = fs.readFileSync(KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH, 'utf8')
|
||||
const parsed = JSON.parse(rawText)
|
||||
return normalizeKuaishouCloudFulfillmentConfig(parsed)
|
||||
const rawText = fs.readFileSync(
|
||||
KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH,
|
||||
"utf8"
|
||||
);
|
||||
const parsed = JSON.parse(rawText);
|
||||
return normalizeKuaishouCloudFulfillmentConfig(parsed);
|
||||
} catch {
|
||||
return normalizeKuaishouCloudFulfillmentConfig({})
|
||||
return normalizeKuaishouCloudFulfillmentConfig({});
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeKuaishouCloudFulfillmentConfig(rawValue) {
|
||||
const source = isPlainObject(rawValue) ? rawValue : {}
|
||||
const source = isPlainObject(rawValue) ? rawValue : {};
|
||||
return {
|
||||
enabled: source.enabled !== false,
|
||||
items: Array.isArray(source.items)
|
||||
? source.items.map((item) => normalizeKuaishouCloudFulfillmentItem(item)).filter(Boolean)
|
||||
? source.items
|
||||
.map((item) => normalizeKuaishouCloudFulfillmentItem(item))
|
||||
.filter(Boolean)
|
||||
: [],
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeKuaishouCloudFulfillmentItem(rawValue) {
|
||||
if (!isPlainObject(rawValue)) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
const internalSkuCode = String(rawValue.internalSkuCode || '').trim()
|
||||
const cloudSkuId = normalizePositiveInteger(rawValue.cloudSkuId)
|
||||
const externalSkuCode = String(rawValue.externalSkuCode || '').trim()
|
||||
const externalItemId = String(rawValue.externalItemId || '').trim()
|
||||
const externalSkuName = String(rawValue.externalSkuName || '').trim()
|
||||
const kuaishouConsumeShopId = String(rawValue.kuaishouConsumeShopId || '').trim()
|
||||
const kuaishouConsumeShopName = String(rawValue.kuaishouConsumeShopName || '').trim()
|
||||
const internalSkuCode = String(rawValue.internalSkuCode || "").trim();
|
||||
const cloudSkuId = normalizePositiveInteger(rawValue.cloudSkuId);
|
||||
const externalSkuCode = String(rawValue.externalSkuCode || "").trim();
|
||||
const externalItemId = String(rawValue.externalItemId || "").trim();
|
||||
const externalSkuName = String(rawValue.externalSkuName || "").trim();
|
||||
const kuaishouConsumeShopId = String(
|
||||
rawValue.kuaishouConsumeShopId || ""
|
||||
).trim();
|
||||
const kuaishouConsumeShopName = String(
|
||||
rawValue.kuaishouConsumeShopName || ""
|
||||
).trim();
|
||||
const kuaishouShopConfig = resolveKuaishouEticketShopConfig({
|
||||
shopId: kuaishouConsumeShopId,
|
||||
shopName: kuaishouConsumeShopName,
|
||||
})
|
||||
});
|
||||
|
||||
if (!internalSkuCode) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!cloudSkuId) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!externalSkuCode && !externalItemId && !externalSkuName) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: String(rawValue.id || internalSkuCode).trim() || internalSkuCode,
|
||||
enabled: rawValue.enabled !== false,
|
||||
priority: normalizePriority(rawValue.priority),
|
||||
provider: String(rawValue.provider || '91kaquan').trim() || '91kaquan',
|
||||
platform: String(rawValue.platform || 'kuaishou').trim() || 'kuaishou',
|
||||
shopId: String(rawValue.shopId || '').trim(),
|
||||
provider: String(rawValue.provider || "91kaquan").trim() || "91kaquan",
|
||||
platform: String(rawValue.platform || "kuaishou").trim() || "kuaishou",
|
||||
shopId: String(rawValue.shopId || "").trim(),
|
||||
internalSkuCode,
|
||||
internalSkuName: String(rawValue.internalSkuName || '').trim() || internalSkuCode,
|
||||
internalSkuName:
|
||||
String(rawValue.internalSkuName || "").trim() || internalSkuCode,
|
||||
externalSkuCode,
|
||||
externalItemId,
|
||||
externalSkuName,
|
||||
resolvedSkuName: String(rawValue.resolvedSkuName || rawValue.internalSkuName || '').trim(),
|
||||
cloudSourceKey: String(rawValue.cloudSourceKey || 'default').trim() || 'default',
|
||||
resolvedSkuName: String(
|
||||
rawValue.resolvedSkuName || rawValue.internalSkuName || ""
|
||||
).trim(),
|
||||
cloudSourceKey:
|
||||
String(rawValue.cloudSourceKey || "default").trim() || "default",
|
||||
cloudSourceKeyFallbacks: normalizeStringArray(
|
||||
rawValue.cloudSourceKeyFallbacks
|
||||
),
|
||||
cloudSkuId,
|
||||
cloudSkuName: String(rawValue.cloudSkuName || '').trim(),
|
||||
vnKey: '1',
|
||||
cloudSkuName: String(rawValue.cloudSkuName || "").trim(),
|
||||
vnKey: "1",
|
||||
autoBuyEnabled: rawValue.autoBuyEnabled !== false,
|
||||
minAssetReserve: normalizeNonNegativeInteger(rawValue.minAssetReserve, 0),
|
||||
autoReturnNumberAfterDispatch: rawValue.autoReturnNumberAfterDispatch === true,
|
||||
autoReturnNumberAfterDispatch:
|
||||
rawValue.autoReturnNumberAfterDispatch === true,
|
||||
autoConsumeAfterDispatch: rawValue.autoConsumeAfterDispatch === true,
|
||||
kuaishouConsumeShopId: String(kuaishouShopConfig?.shopId || kuaishouConsumeShopId).trim(),
|
||||
kuaishouConsumeShopName: String(kuaishouShopConfig?.kshopName || kuaishouConsumeShopName).trim(),
|
||||
notes: String(rawValue.notes || '').trim(),
|
||||
}
|
||||
kuaishouConsumeShopId: String(
|
||||
kuaishouShopConfig?.shopId || kuaishouConsumeShopId
|
||||
).trim(),
|
||||
kuaishouConsumeShopName: String(
|
||||
kuaishouShopConfig?.kshopName || kuaishouConsumeShopName
|
||||
).trim(),
|
||||
notes: String(rawValue.notes || "").trim(),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizePositiveInteger(value) {
|
||||
const parsed = Number(value)
|
||||
return Number.isInteger(parsed) && parsed > 0 ? parsed : 0
|
||||
const parsed = Number(value);
|
||||
return Number.isInteger(parsed) && parsed > 0 ? parsed : 0;
|
||||
}
|
||||
|
||||
function normalizeNonNegativeInteger(value, fallback) {
|
||||
const parsed = Number(value)
|
||||
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback
|
||||
const parsed = Number(value);
|
||||
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
||||
}
|
||||
|
||||
function normalizePriority(value) {
|
||||
const parsed = Number(value)
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed)) {
|
||||
return 100
|
||||
return 100;
|
||||
}
|
||||
|
||||
return Math.max(1, Math.round(parsed))
|
||||
return Math.max(1, Math.round(parsed));
|
||||
}
|
||||
function isPlainObject(value) {
|
||||
return Object.prototype.toString.call(value) === "[object Object]";
|
||||
}
|
||||
|
||||
function isPlainObject(value) {
|
||||
return Object.prototype.toString.call(value) === '[object Object]'
|
||||
function normalizeStringArray(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((v) => String(v || "").trim()).filter(Boolean);
|
||||
}
|
||||
// 兼容旧格式:字符串用逗号分隔
|
||||
if (typeof value === "string") {
|
||||
return value
|
||||
.split(",")
|
||||
.map((v) => v.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user