修复取号账号与退号账号混用:操作已有号码严格锁定取号账号,禁止静默 fallback 到其他账号

This commit is contained in:
yml2213
2026-08-08 14:23:11 +08:00
parent e1a0a74477
commit 6d0534296c
2 changed files with 87 additions and 55 deletions
@@ -1,11 +1,11 @@
import { listKuaishouCloudSourceLoadStats } from '../../../repositories/kuaishou-cloud-task-state-repo.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 = {
resolveContextBySourceKeys?: typeof resolvePersistedCloudtentaclesContextBySourceKeys
resolveContextBySourceKeys?: typeof resolvePersistedCloudtentaclesContextWithFallback
listSourceLoadStats?: typeof listKuaishouCloudSourceLoadStats
}
@@ -30,7 +30,7 @@ export async function selectCloudtentaclesSourceForFulfillment(
): Promise<CloudtentaclesSourceSelection> {
const flow = normalizeKuaishouCloudFlow(flowLike)
const resolveContext =
deps.resolveContextBySourceKeys || resolvePersistedCloudtentaclesContextBySourceKeys
deps.resolveContextBySourceKeys || resolvePersistedCloudtentaclesContextWithFallback
const listSourceLoadStats = deps.listSourceLoadStats || listKuaishouCloudSourceLoadStats
const fixedSourceKey = String(flow.binding.resolvedSourceKey || '').trim()
const candidates = normalizeCloudtentaclesSourceCandidates(
@@ -8,48 +8,89 @@ import { getCloudtentaclesSessionStateByKey } from "../../platforms/cloudtentacl
import { normalizeStringArray } from "./domain.js";
/**
* 按优先级依次尝试 cloudSourceKeys 列表中的账号
* 找到第一个有可用 token 的账号返回其 context。
* 将实际使用的 resolvedSourceKey 也返回,确保后续操作(退号、发货等)
* 使用同一个 sourceKey,防止跨账号操作导致数据不一致
* 严格模式:只解析列表中第一个账号(调用方均把任务实际取号账号 resolvedSourceKey 放首位)
* 该账号不可用时直接报错,绝不静默 fallback 到其他账号——
* 否则会用「非取号账号」去退号/取链,上游会因号码不归属当前会话而报权限不足。
* 退号、取链、发货等「操作任务已有号码」的场景必须使用此函数
* 将实际使用的 resolvedSourceKey 也返回,确保后续操作使用同一个 sourceKey。
*/
export function resolvePersistedCloudtentaclesContextBySourceKeys(
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 session = getCloudtentaclesSessionStateByKey(sourceKey);
if (!source) continue;
if (!source) {
throw createHttpError(
`cloudtentacles 账号 ${sourceKey} 不存在(取号账号已变更或被删除,无法继续操作其虚拟号)`,
{ statusCode: 409, errorCode: "kuaishou_cloud_source_missing" }
);
}
if (source.enabled === false) {
lastError = createHttpError(
throw createHttpError(
`cloudtentacles 账号 ${sourceKey} 已停用`,
{ statusCode: 409, errorCode: "kuaishou_cloud_source_disabled" }
);
continue;
}
if (!session) {
lastError = createHttpError(
throw createHttpError(
`cloudtentacles 账号 ${sourceKey} 没有可用 token`,
{ statusCode: 409, errorCode: "kuaishou_cloud_missing_cloud_token" }
);
continue;
}
const token = String(session.token || "").trim();
if (!token) {
lastError = createHttpError(
throw createHttpError(
`cloudtentacles 账号 ${sourceKey} 没有可用 token`,
{ statusCode: 409, errorCode: "kuaishou_cloud_missing_cloud_token" }
);
continue;
}
return {
@@ -68,13 +109,4 @@ export function resolvePersistedCloudtentaclesContextBySourceKeys(
resolvedSourceKey: sourceKey,
accountLabel: String(source.label || sourceKey).trim() || sourceKey,
};
}
throw (
lastError ||
createHttpError(
"所有 cloudtentacles 账号均不可用,请先到平台配置完成登录校验",
{ statusCode: 409, errorCode: "kuaishou_cloud_all_source_keys_exhausted" }
)
);
}