优化后端目录命名与模块归位
This commit is contained in:
@@ -0,0 +1,778 @@
|
||||
import { createTaskEvent } from "../../../repositories/task-event-repo.js";
|
||||
import { updateTask } from "../../../repositories/task-repo.js";
|
||||
import { buildClaimUrl, createTaskClaimToken } from "../../claim/claim-service.js";
|
||||
import {
|
||||
buyCloudtentaclesSku,
|
||||
getCloudtentaclesAsset,
|
||||
listCloudtentaclesSku,
|
||||
} from "../../platforms/cloudtentacles/catalog-service.js";
|
||||
import { getCloudtentaclesKnapsack } from "../../platforms/cloudtentacles/knapsack-service.js";
|
||||
import {
|
||||
backCloudtentaclesVirtualNumber,
|
||||
getCloudtentaclesBindInfo,
|
||||
probeCloudtentaclesBindUrl,
|
||||
} from "../../platforms/cloudtentacles/virtual-number-service.js";
|
||||
import { resolveCloudtentaclesConfig } from "../../platforms/cloudtentacles/helpers.js";
|
||||
import {
|
||||
notifyKuaishouCloudAssetNotEnough,
|
||||
notifyKuaishouCloudBindUrlRefreshFailed,
|
||||
} from "../../notification/domain-notifications.js";
|
||||
import { createHttpError } from "../../../utils/http.js";
|
||||
import { nowIso } from "../../../utils/time.js";
|
||||
import {
|
||||
KUAISHOU_CLOUD_FIXED_VN_KEY,
|
||||
isKuaishouCloudBindUrlFresh,
|
||||
isKuaishouCloudTask,
|
||||
maskPhone,
|
||||
normalizeKuaishouCloudFlow,
|
||||
normalizeKuaishouCloudRoleInfo,
|
||||
resolveKuaishouCloudBindUrlExpiresAt,
|
||||
type JsonObject,
|
||||
} from "./domain.js";
|
||||
import {
|
||||
prepareKuaishouCloudBindResourceWithFallback,
|
||||
resolveKuaishouCloudBindingResources,
|
||||
resolveKuaishouCloudVnKeyCandidates,
|
||||
} from "./binding-resources.js";
|
||||
import {
|
||||
resolvePersistedCloudtentaclesContextWithFallback,
|
||||
} from "./cloudtentacles-context.js";
|
||||
import {
|
||||
getTaskClaimExpiresAt,
|
||||
isClaimExpired,
|
||||
normalizeActor,
|
||||
parseTaskContext,
|
||||
} from "./task-context.js";
|
||||
import type { TaskRow } from "../../../types/repository-rows.js";
|
||||
|
||||
export {
|
||||
isKuaishouCloudBindUrlFresh,
|
||||
isKuaishouCloudTask,
|
||||
maskCode,
|
||||
maskPhone,
|
||||
normalizeKuaishouCloudFlow,
|
||||
normalizeKuaishouCloudRoleInfo,
|
||||
resolveKuaishouCloudBindUrlExpiresAt,
|
||||
} from "./domain.js";
|
||||
export {
|
||||
resolvePersistedCloudtentaclesContext,
|
||||
resolvePersistedCloudtentaclesContextWithFallback,
|
||||
} from "./cloudtentacles-context.js";
|
||||
|
||||
export async function ensureTaskClaimLink(task: TaskRow) {
|
||||
const tokenStatus = String(task?.primary_claim_token_status || "").trim();
|
||||
const token = String(
|
||||
task?.primary_claim_token || task?.claim_token || ""
|
||||
).trim();
|
||||
const expiredAt = getTaskClaimExpiresAt(task);
|
||||
|
||||
if (tokenStatus === "active" && token && !isClaimExpired(expiredAt)) {
|
||||
return {
|
||||
token,
|
||||
expiredAt,
|
||||
claimUrl: buildClaimUrl(token),
|
||||
};
|
||||
}
|
||||
|
||||
const claimToken = await createTaskClaimToken(task.id);
|
||||
return {
|
||||
token: claimToken.token,
|
||||
expiredAt: claimToken.expired_at,
|
||||
claimUrl: claimToken.claimUrl,
|
||||
};
|
||||
}
|
||||
|
||||
export async function prepareKuaishouCloudFulfillmentTask(
|
||||
task: TaskRow,
|
||||
options: JsonObject = {}
|
||||
) {
|
||||
if (!isKuaishouCloudTask(task)) {
|
||||
throw createHttpError("当前任务不是快手 Cloud 履约任务", {
|
||||
statusCode: 409,
|
||||
errorCode: "kuaishou_cloud_task_invalid",
|
||||
});
|
||||
}
|
||||
|
||||
const now = nowIso();
|
||||
const actor = normalizeActor(options.actor);
|
||||
const force = options.force === true;
|
||||
const taskContext = parseTaskContext(task);
|
||||
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment);
|
||||
const claimLinkState = await ensureTaskClaimLink(task);
|
||||
|
||||
if (
|
||||
!force &&
|
||||
flow.binding.prepareStatus === "ready" &&
|
||||
flow.binding.vnId > 0 &&
|
||||
flow.binding.vnPhone &&
|
||||
flow.binding.bindUrl
|
||||
) {
|
||||
if (!isKuaishouCloudBindUrlFresh(flow)) {
|
||||
return refreshKuaishouCloudTaskBindUrl(task, {
|
||||
source: options.source || "system_refresh_expired_bind_url",
|
||||
actor,
|
||||
claimLinkState,
|
||||
});
|
||||
}
|
||||
|
||||
let readyTask = task;
|
||||
|
||||
if (String(task.task_status || "").trim() !== "waiting_binding") {
|
||||
readyTask = (await updateTask(task.id, {
|
||||
task_status: "waiting_binding",
|
||||
claim_token: claimLinkState.token || task.claim_token || "",
|
||||
claim_expires_at:
|
||||
claimLinkState.expiredAt || getTaskClaimExpiresAt(task),
|
||||
updated_at: now,
|
||||
})) || task;
|
||||
}
|
||||
|
||||
return {
|
||||
task: readyTask,
|
||||
claimUrl: claimLinkState.claimUrl,
|
||||
token: claimLinkState.token,
|
||||
flow,
|
||||
};
|
||||
}
|
||||
|
||||
const cloudContext = resolvePersistedCloudtentaclesContextWithFallback(
|
||||
flow.binding.cloudSourceKey || "default",
|
||||
flow.binding.cloudSourceKeyFallbacks || []
|
||||
);
|
||||
const [knapsack, skuList] = await Promise.all([
|
||||
getCloudtentaclesKnapsack(cloudContext),
|
||||
listCloudtentaclesSku(cloudContext),
|
||||
]);
|
||||
const resolvedBinding = resolveKuaishouCloudBindingResources(flow, {
|
||||
skuItems: Array.isArray(skuList.items) ? skuList.items : [],
|
||||
knapsackItems: Array.isArray(knapsack.items) ? knapsack.items : [],
|
||||
});
|
||||
const vnKeyCandidates = resolveKuaishouCloudVnKeyCandidates({
|
||||
flow,
|
||||
binding: resolvedBinding,
|
||||
});
|
||||
|
||||
if (!resolvedBinding.skuId || vnKeyCandidates.length === 0) {
|
||||
throw createHttpError("当前任务缺少可用 cloud 资源,无法自动准备绑定", {
|
||||
statusCode: 409,
|
||||
errorCode: "kuaishou_cloud_missing_binding_config",
|
||||
});
|
||||
}
|
||||
|
||||
const flowWithResolvedBinding = {
|
||||
...flow,
|
||||
binding: {
|
||||
...flow.binding,
|
||||
skuId: resolvedBinding.skuId,
|
||||
skuName: resolvedBinding.skuName,
|
||||
vnKey: KUAISHOU_CLOUD_FIXED_VN_KEY,
|
||||
},
|
||||
};
|
||||
|
||||
const knapsackItem = resolvedBinding.knapsackItem;
|
||||
let usedKnapsack = Number(knapsackItem?.count || 0) > 0;
|
||||
let purchaseTriggered = false;
|
||||
let assetBefore = 0;
|
||||
let assetAfter = 0;
|
||||
|
||||
if (!usedKnapsack) {
|
||||
if (!flowWithResolvedBinding.purchase.autoBuyEnabled) {
|
||||
throw createHttpError("背包中没有现成库存,且当前配置未开启自动购买", {
|
||||
statusCode: 409,
|
||||
errorCode: "kuaishou_cloud_auto_buy_disabled",
|
||||
});
|
||||
}
|
||||
|
||||
const asset = await getCloudtentaclesAsset(cloudContext);
|
||||
const targetSku = resolvedBinding.skuItem;
|
||||
|
||||
if (!targetSku) {
|
||||
throw createHttpError(
|
||||
`cloudtentacles 未找到 SKU ${flowWithResolvedBinding.binding.skuId}`,
|
||||
{
|
||||
statusCode: 404,
|
||||
errorCode: "kuaishou_cloud_sku_not_found",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
assetBefore = Number(asset.asset || 0) || 0;
|
||||
const targetPrice = Number(targetSku.price || 0) || 0;
|
||||
const requiredAsset =
|
||||
targetPrice + flowWithResolvedBinding.purchase.minAssetReserve;
|
||||
if (assetBefore < requiredAsset) {
|
||||
await notifyKuaishouCloudAssetNotEnough({
|
||||
task,
|
||||
flow: flowWithResolvedBinding,
|
||||
assetBefore,
|
||||
requiredAsset,
|
||||
skuName: targetSku.name,
|
||||
});
|
||||
throw createHttpError(
|
||||
`余额不足,当前 ${assetBefore},至少需要 ${requiredAsset}`,
|
||||
{
|
||||
statusCode: 409,
|
||||
errorCode: "kuaishou_cloud_asset_not_enough",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
await buyCloudtentaclesSku({
|
||||
...cloudContext,
|
||||
id: flowWithResolvedBinding.binding.skuId,
|
||||
count: 1,
|
||||
});
|
||||
purchaseTriggered = true;
|
||||
|
||||
const assetResult = await getCloudtentaclesAsset(cloudContext);
|
||||
assetAfter = Number(assetResult.asset || 0) || 0;
|
||||
}
|
||||
|
||||
const preparedBinding = await prepareKuaishouCloudBindResourceWithFallback({
|
||||
cloudContext,
|
||||
vnKeyCandidates,
|
||||
});
|
||||
|
||||
const nextContext = {
|
||||
...taskContext,
|
||||
kuaishouCloudFulfillment: {
|
||||
...flowWithResolvedBinding,
|
||||
binding: {
|
||||
...flowWithResolvedBinding.binding,
|
||||
resolvedSourceKey: cloudContext.resolvedSourceKey,
|
||||
vnKey: preparedBinding.vnKey,
|
||||
prepareStatus: "ready",
|
||||
vnId: preparedBinding.vnId,
|
||||
vnPhone: preparedBinding.vnPhone,
|
||||
bindUrl: preparedBinding.bindUrl,
|
||||
bindPreparedAt: now,
|
||||
bindExpiresAt: resolveKuaishouCloudBindUrlExpiresAt(now),
|
||||
bindProbeAt: null as null,
|
||||
bindProbeStatus: "pending",
|
||||
bindProbeMessage: "",
|
||||
roleName: "",
|
||||
roleId: "",
|
||||
},
|
||||
role: {
|
||||
status: "pending",
|
||||
name: "",
|
||||
rid: "",
|
||||
refreshedAt: null as null,
|
||||
errorMessage: "",
|
||||
rawInfo: null as null,
|
||||
},
|
||||
purchase: {
|
||||
...flowWithResolvedBinding.purchase,
|
||||
usedKnapsack,
|
||||
purchaseTriggered,
|
||||
assetBefore,
|
||||
assetAfter,
|
||||
purchaseAt: purchaseTriggered
|
||||
? now
|
||||
: flowWithResolvedBinding.purchase.purchaseAt,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const updatedTask = await updateTask(task.id, {
|
||||
task_status: "waiting_binding",
|
||||
user_action_status: "pending_claim",
|
||||
claim_token: claimLinkState.token || task.claim_token || "",
|
||||
claim_expires_at: claimLinkState.expiredAt || getTaskClaimExpiresAt(task),
|
||||
role_id: "",
|
||||
role_name: "",
|
||||
last_error: "",
|
||||
context_json: JSON.stringify(nextContext),
|
||||
updated_at: now,
|
||||
});
|
||||
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
"kuaishou_cloud_binding_prepared",
|
||||
{
|
||||
source: String(options.source || "system").trim() || "system",
|
||||
skuId: flowWithResolvedBinding.binding.skuId,
|
||||
skuName: flowWithResolvedBinding.binding.skuName,
|
||||
vnKey: preparedBinding.vnKey,
|
||||
vnId: preparedBinding.vnId,
|
||||
vnPhoneMasked: maskPhone(preparedBinding.vnPhone),
|
||||
purchaseTriggered,
|
||||
usedKnapsack,
|
||||
resolvedByName: resolvedBinding.resolvedByName,
|
||||
actor,
|
||||
},
|
||||
now
|
||||
);
|
||||
|
||||
return {
|
||||
task: updatedTask,
|
||||
claimUrl: claimLinkState.claimUrl,
|
||||
token: claimLinkState.token,
|
||||
flow: normalizeKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment),
|
||||
};
|
||||
}
|
||||
|
||||
export async function refreshKuaishouCloudTaskBindUrl(
|
||||
task: TaskRow,
|
||||
options: JsonObject = {}
|
||||
) {
|
||||
if (!isKuaishouCloudTask(task)) {
|
||||
throw createHttpError("当前任务不是快手 Cloud 履约任务", {
|
||||
statusCode: 409,
|
||||
errorCode: "kuaishou_cloud_task_invalid",
|
||||
});
|
||||
}
|
||||
|
||||
const now = nowIso();
|
||||
const actor = normalizeActor(options.actor);
|
||||
const taskContext = parseTaskContext(task);
|
||||
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment);
|
||||
|
||||
if (!flow.binding.vnId || !flow.binding.vnKey) {
|
||||
throw createHttpError("当前任务缺少可刷新绑定链接的虚拟号信息", {
|
||||
statusCode: 409,
|
||||
errorCode: "kuaishou_cloud_missing_bind_url_context",
|
||||
});
|
||||
}
|
||||
|
||||
const effectiveSourceKey =
|
||||
flow.binding.resolvedSourceKey || flow.binding.cloudSourceKey || "default";
|
||||
const cloudContext = resolvePersistedCloudtentaclesContextWithFallback(
|
||||
effectiveSourceKey,
|
||||
flow.binding.cloudSourceKeyFallbacks || []
|
||||
);
|
||||
const oldVnKey = flow.binding.vnKey;
|
||||
const oldVnId = flow.binding.vnId;
|
||||
const oldVnPhone = flow.binding.vnPhone;
|
||||
|
||||
await backCloudtentaclesVirtualNumber({
|
||||
...cloudContext,
|
||||
key: oldVnKey,
|
||||
id: oldVnId,
|
||||
});
|
||||
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
"kuaishou_cloud_expired_bind_number_returned",
|
||||
{
|
||||
source: String(options.source || "system").trim() || "system",
|
||||
vnKey: oldVnKey,
|
||||
vnId: oldVnId,
|
||||
vnPhoneMasked: maskPhone(oldVnPhone),
|
||||
actor,
|
||||
},
|
||||
now
|
||||
);
|
||||
|
||||
let preparedBinding;
|
||||
try {
|
||||
preparedBinding = await prepareKuaishouCloudBindResourceWithFallback({
|
||||
cloudContext,
|
||||
vnKeyCandidates: resolveKuaishouCloudVnKeyCandidates({
|
||||
flow,
|
||||
binding: flow.binding,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!String(preparedBinding.bindUrl || "").trim()) {
|
||||
throw createHttpError("cloudtentacles 未返回新的绑定链接", {
|
||||
statusCode: 502,
|
||||
errorCode: "kuaishou_cloud_empty_bind_url",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
const failedTask = await markKuaishouCloudBindUrlRefreshFailed(task, {
|
||||
taskContext,
|
||||
flow,
|
||||
now,
|
||||
actor,
|
||||
error,
|
||||
});
|
||||
|
||||
return {
|
||||
task: failedTask,
|
||||
claimUrl: buildClaimUrl(
|
||||
String(task.primary_claim_token || task.claim_token || "")
|
||||
),
|
||||
token: String(task.primary_claim_token || task.claim_token || ""),
|
||||
flow: normalizeKuaishouCloudFlow(
|
||||
parseTaskContext(failedTask).kuaishouCloudFulfillment
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
const nextContext = {
|
||||
...taskContext,
|
||||
kuaishouCloudFulfillment: {
|
||||
...flow,
|
||||
binding: {
|
||||
...flow.binding,
|
||||
prepareStatus: "ready",
|
||||
vnKey: preparedBinding.vnKey,
|
||||
vnId: preparedBinding.vnId,
|
||||
vnPhone: preparedBinding.vnPhone,
|
||||
bindUrl: preparedBinding.bindUrl,
|
||||
bindPreparedAt: now,
|
||||
bindExpiresAt: resolveKuaishouCloudBindUrlExpiresAt(now),
|
||||
bindProbeAt: null as null,
|
||||
bindProbeStatus: "pending",
|
||||
bindProbeMessage: "",
|
||||
roleName:
|
||||
String(task.task_status || "").trim() === "role_confirmed"
|
||||
? flow.binding.roleName
|
||||
: "",
|
||||
roleId:
|
||||
String(task.task_status || "").trim() === "role_confirmed"
|
||||
? flow.binding.roleId
|
||||
: "",
|
||||
},
|
||||
role:
|
||||
String(task.task_status || "").trim() === "role_confirmed"
|
||||
? flow.role
|
||||
: {
|
||||
status: "pending",
|
||||
name: "",
|
||||
rid: "",
|
||||
refreshedAt: null,
|
||||
errorMessage: "",
|
||||
rawInfo: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const claimLinkState =
|
||||
options.claimLinkState || (await ensureTaskClaimLink(task));
|
||||
const updatedTask = await updateTask(task.id, {
|
||||
task_status:
|
||||
String(task.task_status || "").trim() === "role_confirmed"
|
||||
? task.task_status
|
||||
: "waiting_binding",
|
||||
claim_token: claimLinkState.token || task.claim_token || "",
|
||||
claim_expires_at: claimLinkState.expiredAt || getTaskClaimExpiresAt(task),
|
||||
role_id:
|
||||
String(task.task_status || "").trim() === "role_confirmed"
|
||||
? task.role_id
|
||||
: "",
|
||||
role_name:
|
||||
String(task.task_status || "").trim() === "role_confirmed"
|
||||
? task.role_name
|
||||
: "",
|
||||
last_error: "",
|
||||
context_json: JSON.stringify(nextContext),
|
||||
updated_at: now,
|
||||
});
|
||||
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
"kuaishou_cloud_bind_url_refreshed",
|
||||
{
|
||||
source: String(options.source || "system").trim() || "system",
|
||||
oldVnId,
|
||||
oldVnPhoneMasked: maskPhone(oldVnPhone),
|
||||
vnKey: preparedBinding.vnKey,
|
||||
vnId: preparedBinding.vnId,
|
||||
vnPhoneMasked: maskPhone(preparedBinding.vnPhone),
|
||||
actor,
|
||||
},
|
||||
now
|
||||
);
|
||||
|
||||
return {
|
||||
task: updatedTask,
|
||||
claimUrl: claimLinkState.claimUrl,
|
||||
token: claimLinkState.token,
|
||||
flow: normalizeKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment),
|
||||
};
|
||||
}
|
||||
|
||||
export async function probeKuaishouCloudTaskBindUrl(
|
||||
task: TaskRow,
|
||||
options: JsonObject = {}
|
||||
) {
|
||||
if (!isKuaishouCloudTask(task)) {
|
||||
throw createHttpError("当前任务不是快手 Cloud 履约任务", {
|
||||
statusCode: 409,
|
||||
errorCode: "kuaishou_cloud_task_invalid",
|
||||
});
|
||||
}
|
||||
|
||||
const now = nowIso();
|
||||
const taskContext = parseTaskContext(task);
|
||||
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment);
|
||||
|
||||
if (!flow.binding.bindUrl) {
|
||||
return {
|
||||
task,
|
||||
flow,
|
||||
probe: null as null,
|
||||
};
|
||||
}
|
||||
|
||||
const probeIntervalMs =
|
||||
Number(resolveCloudtentaclesConfig().bindUrlProbeIntervalSeconds || 30) *
|
||||
1000;
|
||||
if (
|
||||
!options.force &&
|
||||
flow.binding.bindProbeAt &&
|
||||
Date.now() - Date.parse(flow.binding.bindProbeAt) < probeIntervalMs
|
||||
) {
|
||||
return {
|
||||
task,
|
||||
flow,
|
||||
probe: null as null,
|
||||
};
|
||||
}
|
||||
|
||||
const probe = await probeCloudtentaclesBindUrl({
|
||||
bindUrl: flow.binding.bindUrl,
|
||||
});
|
||||
|
||||
if (probe.expired || !isKuaishouCloudBindUrlFresh(flow)) {
|
||||
const refreshed = await refreshKuaishouCloudTaskBindUrl(task, {
|
||||
source: options.source || "claim_page_bind_url_expired",
|
||||
actor: options.actor || { source: "system" },
|
||||
});
|
||||
return {
|
||||
...refreshed,
|
||||
probe,
|
||||
};
|
||||
}
|
||||
|
||||
const probeRoleInfo = normalizeKuaishouCloudRoleInfo(probe.roleInfo);
|
||||
const hasRoleInfo = Boolean(probeRoleInfo.name || probeRoleInfo.rid);
|
||||
const nextContext = {
|
||||
...taskContext,
|
||||
kuaishouCloudFulfillment: {
|
||||
...flow,
|
||||
binding: {
|
||||
...flow.binding,
|
||||
bindProbeAt: now,
|
||||
bindProbeStatus: probe.valid ? "valid" : "invalid",
|
||||
bindProbeMessage: String(probe.message || probe.reason || "").trim(),
|
||||
roleName: hasRoleInfo ? probeRoleInfo.name : flow.binding.roleName,
|
||||
roleId: hasRoleInfo ? probeRoleInfo.rid : flow.binding.roleId,
|
||||
},
|
||||
role: {
|
||||
...flow.role,
|
||||
status: hasRoleInfo ? "ready" : flow.role.status,
|
||||
name: hasRoleInfo ? probeRoleInfo.name : flow.role.name,
|
||||
rid: hasRoleInfo ? probeRoleInfo.rid : flow.role.rid,
|
||||
refreshedAt: hasRoleInfo ? now : flow.role.refreshedAt,
|
||||
errorMessage: hasRoleInfo ? "" : flow.role.errorMessage,
|
||||
rawInfo: hasRoleInfo ? probeRoleInfo.rawInfo : flow.role.rawInfo,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const updatedTask = await updateTask(task.id, {
|
||||
role_id: hasRoleInfo ? probeRoleInfo.rid : task.role_id,
|
||||
role_name: hasRoleInfo ? probeRoleInfo.name : task.role_name,
|
||||
context_json: JSON.stringify(nextContext),
|
||||
updated_at: now,
|
||||
});
|
||||
|
||||
return {
|
||||
task: updatedTask,
|
||||
flow: normalizeKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment),
|
||||
probe,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} task
|
||||
* @param {{ taskContext?: any, flow?: any, now?: string, actor?: any, error?: unknown }} [input]
|
||||
*/
|
||||
async function markKuaishouCloudBindUrlRefreshFailed(
|
||||
task: TaskRow,
|
||||
{ taskContext, flow, now, actor, error }: JsonObject = {}
|
||||
) {
|
||||
const errorMessage =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: String(error || "新绑定链接准备失败");
|
||||
const nextContext = {
|
||||
...taskContext,
|
||||
kuaishouCloudFulfillment: {
|
||||
...flow,
|
||||
binding: {
|
||||
...flow.binding,
|
||||
prepareStatus: "pending",
|
||||
vnId: 0,
|
||||
vnPhone: "",
|
||||
bindUrl: "",
|
||||
bindPreparedAt: null,
|
||||
bindExpiresAt: null,
|
||||
bindProbeAt: now,
|
||||
bindProbeStatus: "refresh_failed",
|
||||
bindProbeMessage: errorMessage,
|
||||
roleName: "",
|
||||
roleId: "",
|
||||
},
|
||||
role: {
|
||||
status: "pending",
|
||||
name: "",
|
||||
rid: "",
|
||||
refreshedAt: now,
|
||||
errorMessage:
|
||||
"绑定链接已过期,旧号码已退还,新链接准备失败,请稍后刷新或联系客服处理",
|
||||
rawInfo: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const updatedTask = await updateTask(task.id, {
|
||||
task_status: "pending_binding_prepare",
|
||||
role_id: "",
|
||||
role_name: "",
|
||||
last_error: `绑定链接过期,旧号码已退还,新链接准备失败:${errorMessage}`,
|
||||
context_json: JSON.stringify(nextContext),
|
||||
updated_at: now,
|
||||
});
|
||||
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
"kuaishou_cloud_bind_url_refresh_failed",
|
||||
{
|
||||
source: "system_refresh_expired_bind_url",
|
||||
errorMessage,
|
||||
actor,
|
||||
},
|
||||
now
|
||||
);
|
||||
|
||||
await notifyKuaishouCloudBindUrlRefreshFailed({
|
||||
task: updatedTask,
|
||||
errorMessage,
|
||||
});
|
||||
|
||||
return updatedTask;
|
||||
}
|
||||
|
||||
export async function refreshKuaishouCloudTaskRoleInfo(
|
||||
task: TaskRow,
|
||||
options: JsonObject = {}
|
||||
) {
|
||||
if (!isKuaishouCloudTask(task)) {
|
||||
throw createHttpError("当前任务不是快手 Cloud 履约任务", {
|
||||
statusCode: 409,
|
||||
errorCode: "kuaishou_cloud_task_invalid",
|
||||
});
|
||||
}
|
||||
|
||||
const now = nowIso();
|
||||
const actor = normalizeActor(options.actor);
|
||||
const taskContext = parseTaskContext(task);
|
||||
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment);
|
||||
|
||||
if (!flow.binding.vnId || !flow.binding.vnKey) {
|
||||
throw createHttpError(
|
||||
"当前任务还没有可查询的绑定角色信息,请先准备绑定资源",
|
||||
{
|
||||
statusCode: 409,
|
||||
errorCode: "kuaishou_cloud_missing_bind_info_context",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (flow.binding.bindUrl) {
|
||||
const probed = await probeKuaishouCloudTaskBindUrl(task, {
|
||||
source: options.source || "system_role_refresh_bind_url_probe",
|
||||
actor,
|
||||
force: options.forceProbe === true,
|
||||
});
|
||||
const probedFlow = normalizeKuaishouCloudFlow(
|
||||
parseTaskContext(probed.task).kuaishouCloudFulfillment
|
||||
);
|
||||
if (probed.probe?.expired) {
|
||||
return {
|
||||
task: probed.task,
|
||||
roleInfo: normalizeKuaishouCloudRoleInfo(null),
|
||||
flow: probedFlow,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
(probed.probe === null || probed.probe?.valid) &&
|
||||
(probedFlow.binding.roleName || probedFlow.binding.roleId)
|
||||
) {
|
||||
return {
|
||||
task: probed.task,
|
||||
roleInfo: {
|
||||
name: probedFlow.binding.roleName,
|
||||
rid: probedFlow.binding.roleId,
|
||||
rawInfo: probedFlow.role.rawInfo,
|
||||
},
|
||||
flow: probedFlow,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const effectiveSourceKey =
|
||||
flow.binding.resolvedSourceKey || flow.binding.cloudSourceKey || "default";
|
||||
const cloudContext = resolvePersistedCloudtentaclesContextWithFallback(
|
||||
effectiveSourceKey,
|
||||
flow.binding.cloudSourceKeyFallbacks || []
|
||||
);
|
||||
const bindInfoResult = await getCloudtentaclesBindInfo({
|
||||
...cloudContext,
|
||||
key: flow.binding.vnKey,
|
||||
id: flow.binding.vnId,
|
||||
});
|
||||
|
||||
const bindInfo = normalizeKuaishouCloudRoleInfo(bindInfoResult.bindInfo);
|
||||
const hasRoleInfo = Boolean(bindInfo.name || bindInfo.rid);
|
||||
const nextContext = {
|
||||
...taskContext,
|
||||
kuaishouCloudFulfillment: {
|
||||
...flow,
|
||||
binding: {
|
||||
...flow.binding,
|
||||
roleName: hasRoleInfo ? bindInfo.name : "",
|
||||
roleId: hasRoleInfo ? bindInfo.rid : "",
|
||||
},
|
||||
role: {
|
||||
status: hasRoleInfo ? "ready" : "pending",
|
||||
name: hasRoleInfo ? bindInfo.name : "",
|
||||
rid: hasRoleInfo ? bindInfo.rid : "",
|
||||
refreshedAt: now,
|
||||
errorMessage: hasRoleInfo
|
||||
? ""
|
||||
: "当前还没有查询到角色信息,请完成绑定后稍等片刻再试",
|
||||
rawInfo: bindInfo.rawInfo,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const updatedTask = await updateTask(task.id, {
|
||||
role_id: hasRoleInfo ? bindInfo.rid : "",
|
||||
role_name: hasRoleInfo ? bindInfo.name : "",
|
||||
context_json: JSON.stringify(nextContext),
|
||||
updated_at: now,
|
||||
});
|
||||
|
||||
if (options.recordEvent !== false) {
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
"kuaishou_cloud_role_info_refreshed",
|
||||
{
|
||||
source: String(options.source || "system").trim() || "system",
|
||||
roleName: bindInfo.name,
|
||||
roleId: bindInfo.rid,
|
||||
vnId: flow.binding.vnId,
|
||||
actor,
|
||||
},
|
||||
now
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
task: updatedTask,
|
||||
roleInfo: bindInfo,
|
||||
flow: normalizeKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment),
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
dispatchKuaishouCloudFulfillmentTask,
|
||||
returnKuaishouCloudFulfillmentTask,
|
||||
} from "./task-finalization.js";
|
||||
Reference in New Issue
Block a user