修复取号账号与退号账号混用:操作已有号码严格锁定取号账号,禁止静默 fallback 到其他账号
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
import { listKuaishouCloudSourceLoadStats } from '../../../repositories/kuaishou-cloud-task-state-repo.js'
|
import { listKuaishouCloudSourceLoadStats } from '../../../repositories/kuaishou-cloud-task-state-repo.js'
|
||||||
import { normalizeKuaishouCloudFlow, normalizeStringArray, type JsonObject } from './domain.js'
|
import { normalizeKuaishouCloudFlow, normalizeStringArray, type JsonObject } from './domain.js'
|
||||||
import { resolvePersistedCloudtentaclesContextBySourceKeys } from './cloudtentacles-context.js'
|
import { resolvePersistedCloudtentaclesContextWithFallback } from './cloudtentacles-context.js'
|
||||||
|
|
||||||
type CloudtentaclesContext = ReturnType<typeof resolvePersistedCloudtentaclesContextBySourceKeys>
|
type CloudtentaclesContext = ReturnType<typeof resolvePersistedCloudtentaclesContextWithFallback>
|
||||||
|
|
||||||
type CloudtentaclesSourceSelectionDeps = {
|
type CloudtentaclesSourceSelectionDeps = {
|
||||||
resolveContextBySourceKeys?: typeof resolvePersistedCloudtentaclesContextBySourceKeys
|
resolveContextBySourceKeys?: typeof resolvePersistedCloudtentaclesContextWithFallback
|
||||||
listSourceLoadStats?: typeof listKuaishouCloudSourceLoadStats
|
listSourceLoadStats?: typeof listKuaishouCloudSourceLoadStats
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ export async function selectCloudtentaclesSourceForFulfillment(
|
|||||||
): Promise<CloudtentaclesSourceSelection> {
|
): Promise<CloudtentaclesSourceSelection> {
|
||||||
const flow = normalizeKuaishouCloudFlow(flowLike)
|
const flow = normalizeKuaishouCloudFlow(flowLike)
|
||||||
const resolveContext =
|
const resolveContext =
|
||||||
deps.resolveContextBySourceKeys || resolvePersistedCloudtentaclesContextBySourceKeys
|
deps.resolveContextBySourceKeys || resolvePersistedCloudtentaclesContextWithFallback
|
||||||
const listSourceLoadStats = deps.listSourceLoadStats || listKuaishouCloudSourceLoadStats
|
const listSourceLoadStats = deps.listSourceLoadStats || listKuaishouCloudSourceLoadStats
|
||||||
const fixedSourceKey = String(flow.binding.resolvedSourceKey || '').trim()
|
const fixedSourceKey = String(flow.binding.resolvedSourceKey || '').trim()
|
||||||
const candidates = normalizeCloudtentaclesSourceCandidates(
|
const candidates = normalizeCloudtentaclesSourceCandidates(
|
||||||
|
|||||||
@@ -8,48 +8,89 @@ import { getCloudtentaclesSessionStateByKey } from "../../platforms/cloudtentacl
|
|||||||
import { normalizeStringArray } from "./domain.js";
|
import { normalizeStringArray } from "./domain.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按优先级依次尝试 cloudSourceKeys 列表中的账号,
|
* 严格模式:只解析列表中第一个账号(调用方均把任务实际取号账号 resolvedSourceKey 放首位),
|
||||||
* 找到第一个有可用 token 的账号返回其 context。
|
* 该账号不可用时直接报错,绝不静默 fallback 到其他账号——
|
||||||
* 将实际使用的 resolvedSourceKey 也返回,确保后续操作(退号、发货等)
|
* 否则会用「非取号账号」去退号/取链,上游会因号码不归属当前会话而报权限不足。
|
||||||
* 使用同一个 sourceKey,防止跨账号操作导致数据不一致。
|
* 退号、取链、发货等「操作任务已有号码」的场景必须使用此函数。
|
||||||
|
* 将实际使用的 resolvedSourceKey 也返回,确保后续操作使用同一个 sourceKey。
|
||||||
*/
|
*/
|
||||||
export function resolvePersistedCloudtentaclesContextBySourceKeys(
|
export function resolvePersistedCloudtentaclesContextBySourceKeys(
|
||||||
sourceKeys: unknown[] = []
|
sourceKeys: unknown[] = []
|
||||||
) {
|
) {
|
||||||
const candidates = normalizeStringArray(sourceKeys);
|
const candidates = [...new Set(normalizeStringArray(sourceKeys))];
|
||||||
|
|
||||||
const uniqueCandidates = [...new Set(candidates)];
|
if (candidates.length === 0) {
|
||||||
|
throw createHttpError(
|
||||||
|
"cloudtentacles 没有可用账号,请先到平台配置完成账号配置",
|
||||||
|
{ statusCode: 409, errorCode: "kuaishou_cloud_no_source_keys" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let lastError = null;
|
const sourceKey = String(candidates[0] || "").trim();
|
||||||
|
|
||||||
for (const sourceKey of uniqueCandidates) {
|
return resolveSingleCloudtentaclesAccountContext(sourceKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 宽松模式:按顺序尝试候选账号,找到第一个有可用 token 的账号。
|
||||||
|
* 仅用于「取新号」场景(新号码归属被选中的账号,不会产生跨账号孤儿),
|
||||||
|
* 以及 account-selector 单候选解析。严禁用于退号/取链/发货等操作已有号码的场景。
|
||||||
|
*/
|
||||||
|
export function resolvePersistedCloudtentaclesContextWithFallback(
|
||||||
|
sourceKeys: unknown[] = []
|
||||||
|
) {
|
||||||
|
const candidates = [...new Set(normalizeStringArray(sourceKeys))];
|
||||||
|
|
||||||
|
let lastError: unknown = null;
|
||||||
|
|
||||||
|
for (const sourceKey of candidates) {
|
||||||
|
try {
|
||||||
|
return resolveSingleCloudtentaclesAccountContext(sourceKey);
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw (
|
||||||
|
lastError ||
|
||||||
|
createHttpError(
|
||||||
|
"所有 cloudtentacles 账号均不可用,请先到平台配置完成登录校验",
|
||||||
|
{ statusCode: 409, errorCode: "kuaishou_cloud_all_source_keys_exhausted" }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveSingleCloudtentaclesAccountContext(sourceKey: string) {
|
||||||
const source = getCloudtentaclesSourceByKey(sourceKey);
|
const source = getCloudtentaclesSourceByKey(sourceKey);
|
||||||
const session = getCloudtentaclesSessionStateByKey(sourceKey);
|
const session = getCloudtentaclesSessionStateByKey(sourceKey);
|
||||||
|
|
||||||
if (!source) continue;
|
if (!source) {
|
||||||
|
throw createHttpError(
|
||||||
|
`cloudtentacles 账号 ${sourceKey} 不存在(取号账号已变更或被删除,无法继续操作其虚拟号)`,
|
||||||
|
{ statusCode: 409, errorCode: "kuaishou_cloud_source_missing" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (source.enabled === false) {
|
if (source.enabled === false) {
|
||||||
lastError = createHttpError(
|
throw createHttpError(
|
||||||
`cloudtentacles 账号 ${sourceKey} 已停用`,
|
`cloudtentacles 账号 ${sourceKey} 已停用`,
|
||||||
{ statusCode: 409, errorCode: "kuaishou_cloud_source_disabled" }
|
{ statusCode: 409, errorCode: "kuaishou_cloud_source_disabled" }
|
||||||
);
|
);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
lastError = createHttpError(
|
throw createHttpError(
|
||||||
`cloudtentacles 账号 ${sourceKey} 没有可用 token`,
|
`cloudtentacles 账号 ${sourceKey} 没有可用 token`,
|
||||||
{ statusCode: 409, errorCode: "kuaishou_cloud_missing_cloud_token" }
|
{ statusCode: 409, errorCode: "kuaishou_cloud_missing_cloud_token" }
|
||||||
);
|
);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = String(session.token || "").trim();
|
const token = String(session.token || "").trim();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
lastError = createHttpError(
|
throw createHttpError(
|
||||||
`cloudtentacles 账号 ${sourceKey} 没有可用 token`,
|
`cloudtentacles 账号 ${sourceKey} 没有可用 token`,
|
||||||
{ statusCode: 409, errorCode: "kuaishou_cloud_missing_cloud_token" }
|
{ statusCode: 409, errorCode: "kuaishou_cloud_missing_cloud_token" }
|
||||||
);
|
);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -69,12 +110,3 @@ export function resolvePersistedCloudtentaclesContextBySourceKeys(
|
|||||||
accountLabel: String(source.label || sourceKey).trim() || sourceKey,
|
accountLabel: String(source.label || sourceKey).trim() || sourceKey,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw (
|
|
||||||
lastError ||
|
|
||||||
createHttpError(
|
|
||||||
"所有 cloudtentacles 账号均不可用,请先到平台配置完成登录校验",
|
|
||||||
{ statusCode: 409, errorCode: "kuaishou_cloud_all_source_keys_exhausted" }
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user