添加快手 Cloud 换绑角色功能
This commit is contained in:
@@ -1,98 +1,77 @@
|
||||
import { resolveCloudtentaclesConfig } from "../../platforms/cloudtentacles/helpers.js";
|
||||
import {
|
||||
maskCode as maskCodeValue,
|
||||
maskPhone as maskPhoneValue,
|
||||
} from "../../../utils/masking.js";
|
||||
import { resolveCloudtentaclesConfig } from '../../platforms/cloudtentacles/helpers.js'
|
||||
import { maskCode as maskCodeValue, maskPhone as maskPhoneValue } from '../../../utils/masking.js'
|
||||
|
||||
export const KUAISHOU_CLOUD_FIXED_VN_KEY = "1";
|
||||
export const KUAISHOU_CLOUD_FIXED_VN_KEY = '1'
|
||||
|
||||
export type JsonObject = Record<string, any>;
|
||||
export type JsonObject = Record<string, any>
|
||||
|
||||
export function isKuaishouCloudTask(task: unknown) {
|
||||
const source = task && typeof task === "object" ? task as JsonObject : {};
|
||||
return String(source.executor_key || "").trim() === "kuaishou_ct_assisted";
|
||||
const source = task && typeof task === 'object' ? (task as JsonObject) : {}
|
||||
return String(source.executor_key || '').trim() === 'kuaishou_ct_assisted'
|
||||
}
|
||||
|
||||
export function normalizeKuaishouCloudFlow(value: unknown) {
|
||||
const source: JsonObject = value && typeof value === "object" ? value as JsonObject : {};
|
||||
const binding =
|
||||
source.binding && typeof source.binding === "object" ? source.binding : {};
|
||||
const role =
|
||||
source.role && typeof source.role === "object" ? source.role : {};
|
||||
const purchase =
|
||||
source.purchase && typeof source.purchase === "object"
|
||||
? source.purchase
|
||||
: {};
|
||||
const dispatch =
|
||||
source.dispatch && typeof source.dispatch === "object"
|
||||
? source.dispatch
|
||||
: {};
|
||||
const source: JsonObject = value && typeof value === 'object' ? (value as JsonObject) : {}
|
||||
const binding = source.binding && typeof source.binding === 'object' ? source.binding : {}
|
||||
const role = source.role && typeof source.role === 'object' ? source.role : {}
|
||||
const purchase = source.purchase && typeof source.purchase === 'object' ? source.purchase : {}
|
||||
const dispatch = source.dispatch && typeof source.dispatch === 'object' ? source.dispatch : {}
|
||||
const returnNumber =
|
||||
source.returnNumber && typeof source.returnNumber === "object"
|
||||
? source.returnNumber
|
||||
: {};
|
||||
const consume =
|
||||
source.consume && typeof source.consume === "object" ? source.consume : {};
|
||||
const ticket =
|
||||
source.ticket && typeof source.ticket === "object" ? source.ticket : {};
|
||||
const deliveryItems = normalizeDeliveryItems(source, binding);
|
||||
source.returnNumber && typeof source.returnNumber === 'object' ? source.returnNumber : {}
|
||||
const consume = source.consume && typeof source.consume === 'object' ? source.consume : {}
|
||||
const ticket = source.ticket && typeof source.ticket === 'object' ? source.ticket : {}
|
||||
const rebind = source.rebind && typeof source.rebind === 'object' ? source.rebind : {}
|
||||
const deliveryItems = normalizeDeliveryItems(source, binding)
|
||||
|
||||
const roleName = String(role.name || binding.roleName || "").trim();
|
||||
const roleId = String(role.rid || binding.roleId || "").trim();
|
||||
const bindPreparedAt = binding.bindPreparedAt || null;
|
||||
const roleName = String(role.name || binding.roleName || '').trim()
|
||||
const roleId = String(role.rid || binding.roleId || '').trim()
|
||||
const bindPreparedAt = binding.bindPreparedAt || null
|
||||
const bindExpiresAt =
|
||||
binding.bindExpiresAt ||
|
||||
resolveKuaishouCloudBindUrlExpiresAt(bindPreparedAt);
|
||||
binding.bindExpiresAt || resolveKuaishouCloudBindUrlExpiresAt(bindPreparedAt)
|
||||
|
||||
return {
|
||||
...source,
|
||||
configId: String(source.configId || "").trim(),
|
||||
internalSkuCode: String(source.internalSkuCode || "").trim(),
|
||||
internalSkuName: String(source.internalSkuName || "").trim(),
|
||||
configId: String(source.configId || '').trim(),
|
||||
internalSkuCode: String(source.internalSkuCode || '').trim(),
|
||||
internalSkuName: String(source.internalSkuName || '').trim(),
|
||||
deliveryItems,
|
||||
ticket: {
|
||||
code: String(ticket.code || "").trim(),
|
||||
status: String(ticket.status || "pending").trim() || "pending",
|
||||
code: String(ticket.code || '').trim(),
|
||||
status: String(ticket.status || 'pending').trim() || 'pending',
|
||||
capturedAt: ticket.capturedAt || null,
|
||||
capturedBy: ticket.capturedBy || null,
|
||||
verifiedAt: ticket.verifiedAt || null,
|
||||
oid: String(ticket.oid || "").trim(),
|
||||
formToken: String(ticket.formToken || "").trim(),
|
||||
oid: String(ticket.oid || '').trim(),
|
||||
formToken: String(ticket.formToken || '').trim(),
|
||||
leftCount: Number(ticket.leftCount || 0) || 0,
|
||||
goodsTitle: String(ticket.goodsTitle || "").trim(),
|
||||
goodsTitle: String(ticket.goodsTitle || '').trim(),
|
||||
},
|
||||
binding: {
|
||||
prepareStatus:
|
||||
String(binding.prepareStatus || "pending").trim() || "pending",
|
||||
prepareStatus: String(binding.prepareStatus || 'pending').trim() || 'pending',
|
||||
cloudSourceKeys: normalizeStringArray(binding.cloudSourceKeys),
|
||||
resolvedSourceKey: String(binding.resolvedSourceKey || "").trim(),
|
||||
resolvedSourceKey: String(binding.resolvedSourceKey || '').trim(),
|
||||
skuId: Number(binding.skuId || 0) || 0,
|
||||
skuName: String(binding.skuName || "").trim(),
|
||||
skuName: String(binding.skuName || '').trim(),
|
||||
vnKey:
|
||||
String(binding.vnKey || KUAISHOU_CLOUD_FIXED_VN_KEY).trim() ||
|
||||
KUAISHOU_CLOUD_FIXED_VN_KEY,
|
||||
String(binding.vnKey || KUAISHOU_CLOUD_FIXED_VN_KEY).trim() || KUAISHOU_CLOUD_FIXED_VN_KEY,
|
||||
vnId: Number(binding.vnId || 0) || 0,
|
||||
vnPhone: String(binding.vnPhone || "").trim(),
|
||||
bindUrl: String(binding.bindUrl || "").trim(),
|
||||
vnPhone: String(binding.vnPhone || '').trim(),
|
||||
bindUrl: String(binding.bindUrl || '').trim(),
|
||||
bindPreparedAt,
|
||||
bindExpiresAt,
|
||||
bindProbeAt: binding.bindProbeAt || null,
|
||||
bindProbeStatus: String(binding.bindProbeStatus || "").trim(),
|
||||
bindProbeMessage: String(binding.bindProbeMessage || "").trim(),
|
||||
bindProbeStatus: String(binding.bindProbeStatus || '').trim(),
|
||||
bindProbeMessage: String(binding.bindProbeMessage || '').trim(),
|
||||
roleName,
|
||||
roleId,
|
||||
},
|
||||
role: {
|
||||
status:
|
||||
String(
|
||||
role.status || (roleName || roleId ? "ready" : "pending")
|
||||
).trim() || "pending",
|
||||
status: String(role.status || (roleName || roleId ? 'ready' : 'pending')).trim() || 'pending',
|
||||
name: roleName,
|
||||
rid: roleId,
|
||||
refreshedAt: role.refreshedAt || null,
|
||||
errorMessage: String(role.errorMessage || "").trim(),
|
||||
rawInfo:
|
||||
role.rawInfo && typeof role.rawInfo === "object" ? role.rawInfo : null,
|
||||
errorMessage: String(role.errorMessage || '').trim(),
|
||||
rawInfo: role.rawInfo && typeof role.rawInfo === 'object' ? role.rawInfo : null,
|
||||
},
|
||||
purchase: {
|
||||
autoBuyEnabled: purchase.autoBuyEnabled !== false,
|
||||
@@ -105,171 +84,166 @@ export function normalizeKuaishouCloudFlow(value: unknown) {
|
||||
items: Array.isArray(purchase.items) ? purchase.items : [],
|
||||
},
|
||||
dispatch: {
|
||||
status: String(dispatch.status || "pending").trim() || "pending",
|
||||
status: String(dispatch.status || 'pending').trim() || 'pending',
|
||||
dispatchAt: dispatch.dispatchAt || null,
|
||||
dispatchBy: dispatch.dispatchBy || null,
|
||||
sendType: Number(dispatch.sendType || 0) || 0,
|
||||
note: String(dispatch.note || "").trim(),
|
||||
note: String(dispatch.note || '').trim(),
|
||||
items: Array.isArray(dispatch.items) ? dispatch.items : [],
|
||||
},
|
||||
returnNumber: {
|
||||
status: String(returnNumber.status || "pending").trim() || "pending",
|
||||
status: String(returnNumber.status || 'pending').trim() || 'pending',
|
||||
returnedAt: returnNumber.returnedAt || null,
|
||||
returnedBy: returnNumber.returnedBy || null,
|
||||
autoReturnEnabled: returnNumber.autoReturnEnabled === true,
|
||||
},
|
||||
consume: {
|
||||
status: String(consume.status || "pending").trim() || "pending",
|
||||
shopId: String(consume.shopId || "").trim(),
|
||||
shopName: String(consume.shopName || "").trim(),
|
||||
status: String(consume.status || 'pending').trim() || 'pending',
|
||||
shopId: String(consume.shopId || '').trim(),
|
||||
shopName: String(consume.shopName || '').trim(),
|
||||
autoConsumeEnabled: consume.autoConsumeEnabled === true,
|
||||
consumedAt: consume.consumedAt || null,
|
||||
errorMessage: String(consume.errorMessage || "").trim(),
|
||||
errorMessage: String(consume.errorMessage || '').trim(),
|
||||
},
|
||||
notes: String(source.notes || "").trim(),
|
||||
};
|
||||
rebind: {
|
||||
currentAttempt: Math.max(0, Number(rebind.currentAttempt || 0) || 0),
|
||||
history: Array.isArray(rebind.history) ? rebind.history : [],
|
||||
},
|
||||
notes: String(source.notes || '').trim(),
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeKuaishouCloudDeliveryItems(value: unknown) {
|
||||
const source: JsonObject = value && typeof value === "object" ? value as JsonObject : {};
|
||||
const binding =
|
||||
source.binding && typeof source.binding === "object" ? source.binding : {};
|
||||
return normalizeDeliveryItems(source, binding);
|
||||
const source: JsonObject = value && typeof value === 'object' ? (value as JsonObject) : {}
|
||||
const binding = source.binding && typeof source.binding === 'object' ? source.binding : {}
|
||||
return normalizeDeliveryItems(source, binding)
|
||||
}
|
||||
|
||||
export function normalizeKuaishouCloudRoleInfo(value: unknown) {
|
||||
const rawInfo: JsonObject | null = value && typeof value === "object" ? value as JsonObject : null;
|
||||
const rawInfo: JsonObject | null =
|
||||
value && typeof value === 'object' ? (value as JsonObject) : null
|
||||
const nestedBindInfo =
|
||||
rawInfo?.sBindInfo && typeof rawInfo.sBindInfo === "object"
|
||||
? rawInfo.sBindInfo
|
||||
: null;
|
||||
const source = nestedBindInfo || rawInfo;
|
||||
rawInfo?.sBindInfo && typeof rawInfo.sBindInfo === 'object' ? rawInfo.sBindInfo : null
|
||||
const source = nestedBindInfo || rawInfo
|
||||
|
||||
return {
|
||||
name: String(
|
||||
source?.name ||
|
||||
source?.roleName ||
|
||||
source?.nickname ||
|
||||
source?.sRoleName ||
|
||||
""
|
||||
source?.name || source?.roleName || source?.nickname || source?.sRoleName || '',
|
||||
).trim(),
|
||||
rid: String(
|
||||
source?.rid ||
|
||||
source?.roleId ||
|
||||
source?.uid ||
|
||||
source?.sRoleId ||
|
||||
source?.sUserId ||
|
||||
""
|
||||
source?.rid || source?.roleId || source?.uid || source?.sRoleId || source?.sUserId || '',
|
||||
).trim(),
|
||||
rawInfo,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function maskPhone(value: unknown) {
|
||||
return maskPhoneValue(value);
|
||||
return maskPhoneValue(value)
|
||||
}
|
||||
|
||||
export function maskCode(value: unknown) {
|
||||
return maskCodeValue(value);
|
||||
return maskCodeValue(value)
|
||||
}
|
||||
|
||||
export function resolveKuaishouCloudBindUrlExpiresAt(preparedAt: unknown) {
|
||||
const preparedTime = Date.parse(String(preparedAt || ""));
|
||||
const preparedTime = Date.parse(String(preparedAt || ''))
|
||||
if (!Number.isFinite(preparedTime)) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
const config = resolveCloudtentaclesConfig();
|
||||
const ttlSeconds = Number(config.bindUrlTtlSeconds || 600);
|
||||
return new Date(preparedTime + Math.max(1, ttlSeconds) * 1000).toISOString();
|
||||
const config = resolveCloudtentaclesConfig()
|
||||
const ttlSeconds = Number(config.bindUrlTtlSeconds || 600)
|
||||
return new Date(preparedTime + Math.max(1, ttlSeconds) * 1000).toISOString()
|
||||
}
|
||||
|
||||
export function isKuaishouCloudBindUrlFresh(flow: unknown, now = new Date()) {
|
||||
const normalizedFlow = normalizeKuaishouCloudFlow(flow);
|
||||
const normalizedFlow = normalizeKuaishouCloudFlow(flow)
|
||||
if (!normalizedFlow.binding.bindUrl) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
const expiresAt = normalizedFlow.binding.bindExpiresAt;
|
||||
const expiresAt = normalizedFlow.binding.bindExpiresAt
|
||||
if (!expiresAt) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
const expiresTime = Date.parse(String(expiresAt || ""));
|
||||
const expiresTime = Date.parse(String(expiresAt || ''))
|
||||
if (!Number.isFinite(expiresTime)) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
return expiresTime > now.getTime();
|
||||
return expiresTime > now.getTime()
|
||||
}
|
||||
|
||||
export function normalizeStringArray(value: unknown) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((v) => String(v || "").trim()).filter(Boolean);
|
||||
return value.map((v) => String(v || '').trim()).filter(Boolean)
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
if (typeof value === 'string') {
|
||||
return value
|
||||
.split(",")
|
||||
.split(',')
|
||||
.map((v) => v.trim())
|
||||
.filter(Boolean);
|
||||
.filter(Boolean)
|
||||
}
|
||||
return [];
|
||||
return []
|
||||
}
|
||||
|
||||
function normalizeDeliveryItems(source: JsonObject, binding: JsonObject) {
|
||||
const rawItems = Array.isArray(source.deliveryItems) ? source.deliveryItems : [];
|
||||
const items = rawItems
|
||||
.map((item) => normalizeDeliveryItem(item))
|
||||
.filter(Boolean) as Array<{ cloudSkuId: number; cloudSkuName: string; quantity: number }>;
|
||||
const rawItems = Array.isArray(source.deliveryItems) ? source.deliveryItems : []
|
||||
const items = rawItems.map((item) => normalizeDeliveryItem(item)).filter(Boolean) as Array<{
|
||||
cloudSkuId: number
|
||||
cloudSkuName: string
|
||||
quantity: number
|
||||
}>
|
||||
|
||||
if (items.length > 0) {
|
||||
return mergeDeliveryItems(items);
|
||||
return mergeDeliveryItems(items)
|
||||
}
|
||||
|
||||
const skuId = Number(binding.skuId || 0) || 0;
|
||||
const skuId = Number(binding.skuId || 0) || 0
|
||||
if (!skuId) {
|
||||
return [];
|
||||
return []
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
cloudSkuId: skuId,
|
||||
cloudSkuName: String(binding.skuName || "").trim(),
|
||||
cloudSkuName: String(binding.skuName || '').trim(),
|
||||
quantity: 1,
|
||||
},
|
||||
];
|
||||
]
|
||||
}
|
||||
|
||||
function normalizeDeliveryItem(value: unknown) {
|
||||
const source = value && typeof value === "object" ? value as JsonObject : {};
|
||||
const cloudSkuId = Number(source.cloudSkuId || source.skuId || 0) || 0;
|
||||
const source = value && typeof value === 'object' ? (value as JsonObject) : {}
|
||||
const cloudSkuId = Number(source.cloudSkuId || source.skuId || 0) || 0
|
||||
if (!Number.isInteger(cloudSkuId) || cloudSkuId <= 0) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
const quantity = Number(source.quantity || 1) || 1;
|
||||
const quantity = Number(source.quantity || 1) || 1
|
||||
return {
|
||||
cloudSkuId,
|
||||
cloudSkuName: String(source.cloudSkuName || source.skuName || "").trim(),
|
||||
cloudSkuName: String(source.cloudSkuName || source.skuName || '').trim(),
|
||||
quantity: Number.isInteger(quantity) && quantity > 0 ? quantity : 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function mergeDeliveryItems(
|
||||
items: Array<{ cloudSkuId: number; cloudSkuName: string; quantity: number }>
|
||||
items: Array<{ cloudSkuId: number; cloudSkuName: string; quantity: number }>,
|
||||
) {
|
||||
const merged = new Map<number, { cloudSkuId: number; cloudSkuName: string; quantity: number }>();
|
||||
const merged = new Map<number, { cloudSkuId: number; cloudSkuName: string; quantity: number }>()
|
||||
|
||||
for (const item of items) {
|
||||
const existing = merged.get(item.cloudSkuId);
|
||||
const existing = merged.get(item.cloudSkuId)
|
||||
if (existing) {
|
||||
existing.quantity += item.quantity;
|
||||
existing.cloudSkuName = existing.cloudSkuName || item.cloudSkuName;
|
||||
continue;
|
||||
existing.quantity += item.quantity
|
||||
existing.cloudSkuName = existing.cloudSkuName || item.cloudSkuName
|
||||
continue
|
||||
}
|
||||
|
||||
merged.set(item.cloudSkuId, { ...item });
|
||||
merged.set(item.cloudSkuId, { ...item })
|
||||
}
|
||||
|
||||
return Array.from(merged.values());
|
||||
return Array.from(merged.values())
|
||||
}
|
||||
|
||||
@@ -456,6 +456,286 @@ export async function refreshKuaishouCloudTaskBindUrl(task: TaskRow, options: Js
|
||||
}
|
||||
}
|
||||
|
||||
export async function rebindKuaishouCloudTaskRole(task: TaskRow, options: JsonObject = {}) {
|
||||
if (!isKuaishouCloudTask(task)) {
|
||||
throw createHttpError('当前任务不是快手 Cloud 履约任务', {
|
||||
statusCode: 409,
|
||||
errorCode: 'kuaishou_cloud_task_invalid',
|
||||
})
|
||||
}
|
||||
|
||||
const normalizedStatus = normalizeTaskStatus(task.task_status)
|
||||
if (
|
||||
![
|
||||
TASK_STATUS.WAITING_BINDING,
|
||||
TASK_STATUS.ROLE_CONFIRMED,
|
||||
TASK_STATUS.MANUAL_REVIEW,
|
||||
TASK_STATUS.RETRY_PENDING,
|
||||
].includes(normalizedStatus as any)
|
||||
) {
|
||||
throw createHttpError('当前任务状态不可换绑角色', {
|
||||
statusCode: 409,
|
||||
errorCode: 'kuaishou_cloud_rebind_not_allowed',
|
||||
})
|
||||
}
|
||||
|
||||
const now = nowIso()
|
||||
const actor = normalizeActor(options.actor)
|
||||
const taskContext = parseTaskContext(task)
|
||||
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)
|
||||
|
||||
if (
|
||||
flow.dispatch.status === 'success' ||
|
||||
normalizeTaskStatus(task.task_status) === TASK_STATUS.DISPATCHED_PENDING_RETURN
|
||||
) {
|
||||
throw createHttpError('当前任务已经发货,不能换绑角色', {
|
||||
statusCode: 409,
|
||||
errorCode: 'kuaishou_cloud_rebind_after_dispatch_forbidden',
|
||||
})
|
||||
}
|
||||
|
||||
if (!flow.binding.vnId || !flow.binding.vnKey) {
|
||||
throw createHttpError('当前任务缺少可退还的虚拟号信息,请先准备绑定资源', {
|
||||
statusCode: 409,
|
||||
errorCode: 'kuaishou_cloud_rebind_missing_binding_context',
|
||||
})
|
||||
}
|
||||
|
||||
const source = String(options.source || 'system_rebind_role').trim() || 'system_rebind_role'
|
||||
const cloudContext = resolvePersistedCloudtentaclesContextBySourceKeys([
|
||||
flow.binding.resolvedSourceKey,
|
||||
...flow.binding.cloudSourceKeys,
|
||||
])
|
||||
const oldBinding = {
|
||||
vnKey: flow.binding.vnKey,
|
||||
vnId: flow.binding.vnId,
|
||||
vnPhone: flow.binding.vnPhone,
|
||||
bindUrl: flow.binding.bindUrl,
|
||||
bindPreparedAt: flow.binding.bindPreparedAt,
|
||||
bindExpiresAt: flow.binding.bindExpiresAt,
|
||||
roleName: flow.binding.roleName || flow.role.name || task.role_name || '',
|
||||
roleId: flow.binding.roleId || flow.role.rid || task.role_id || '',
|
||||
}
|
||||
const previousRebind: JsonObject =
|
||||
flow.rebind && typeof flow.rebind === 'object' ? (flow.rebind as JsonObject) : {}
|
||||
const history = Array.isArray(previousRebind.history) ? previousRebind.history : []
|
||||
const attempt = Math.max(1, Number(previousRebind.currentAttempt || history.length || 0) + 1)
|
||||
const baseHistoryItem = {
|
||||
attempt,
|
||||
source,
|
||||
requestedAt: now,
|
||||
requestedBy: actor,
|
||||
oldBinding,
|
||||
}
|
||||
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
'kuaishou_cloud_rebind_requested',
|
||||
{
|
||||
source,
|
||||
attempt,
|
||||
oldVnId: oldBinding.vnId,
|
||||
oldVnPhoneMasked: maskPhone(oldBinding.vnPhone),
|
||||
oldRoleName: oldBinding.roleName,
|
||||
oldRoleId: oldBinding.roleId,
|
||||
actor,
|
||||
},
|
||||
now,
|
||||
)
|
||||
|
||||
await backCloudtentaclesVirtualNumber({
|
||||
...cloudContext,
|
||||
key: oldBinding.vnKey,
|
||||
id: oldBinding.vnId,
|
||||
})
|
||||
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
'kuaishou_cloud_rebind_old_number_returned',
|
||||
{
|
||||
source,
|
||||
attempt,
|
||||
vnKey: oldBinding.vnKey,
|
||||
vnId: oldBinding.vnId,
|
||||
vnPhoneMasked: maskPhone(oldBinding.vnPhone),
|
||||
actor,
|
||||
},
|
||||
now,
|
||||
)
|
||||
|
||||
let preparedBinding
|
||||
try {
|
||||
preparedBinding = await prepareKuaishouCloudBindResourceWithFallback({
|
||||
cloudContext,
|
||||
vnKeyCandidates: resolveKuaishouCloudVnKeyCandidates({
|
||||
flow,
|
||||
binding: flow.binding,
|
||||
}),
|
||||
})
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : '新绑定资源准备失败'
|
||||
const failedContext = {
|
||||
...taskContext,
|
||||
kuaishouCloudFulfillment: {
|
||||
...flow,
|
||||
binding: {
|
||||
...flow.binding,
|
||||
prepareStatus: 'pending',
|
||||
vnId: 0,
|
||||
vnPhone: '',
|
||||
bindUrl: '',
|
||||
bindPreparedAt: null,
|
||||
bindExpiresAt: null,
|
||||
bindProbeAt: now,
|
||||
bindProbeStatus: 'rebind_failed',
|
||||
bindProbeMessage: errorMessage,
|
||||
roleName: '',
|
||||
roleId: '',
|
||||
},
|
||||
role: {
|
||||
status: 'pending',
|
||||
name: '',
|
||||
rid: '',
|
||||
refreshedAt: now,
|
||||
errorMessage: `旧虚拟号已退还,新绑定资源准备失败:${errorMessage}`,
|
||||
rawInfo: null,
|
||||
},
|
||||
rebind: {
|
||||
...previousRebind,
|
||||
currentAttempt: attempt,
|
||||
history: [
|
||||
...history,
|
||||
{
|
||||
...baseHistoryItem,
|
||||
status: 'failed',
|
||||
errorMessage,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
const failedTask = await updateTask(task.id, {
|
||||
task_status: TASK_STATUS.PENDING_BINDING_PREPARE,
|
||||
role_id: '',
|
||||
role_name: '',
|
||||
role_confirmed_at: null,
|
||||
last_error: `换绑失败,旧虚拟号已退还,新绑定资源准备失败:${errorMessage}`,
|
||||
context_json: JSON.stringify(failedContext),
|
||||
updated_at: now,
|
||||
})
|
||||
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
'kuaishou_cloud_rebind_failed',
|
||||
{
|
||||
source,
|
||||
attempt,
|
||||
errorMessage,
|
||||
actor,
|
||||
},
|
||||
now,
|
||||
)
|
||||
|
||||
return {
|
||||
task: failedTask || task,
|
||||
claimUrl: buildClaimUrl(String(task.primary_claim_token || task.claim_token || '')),
|
||||
token: String(task.primary_claim_token || task.claim_token || ''),
|
||||
flow: normalizeKuaishouCloudFlow(
|
||||
parseTaskContext(failedTask || task).kuaishouCloudFulfillment,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
const claimLinkState = await ensureTaskClaimLink(task)
|
||||
const nextBindExpiresAt = resolveKuaishouCloudBindUrlExpiresAt(now)
|
||||
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: nextBindExpiresAt,
|
||||
bindProbeAt: null,
|
||||
bindProbeStatus: 'pending',
|
||||
bindProbeMessage: '',
|
||||
roleName: '',
|
||||
roleId: '',
|
||||
},
|
||||
role: {
|
||||
status: 'pending',
|
||||
name: '',
|
||||
rid: '',
|
||||
refreshedAt: null,
|
||||
errorMessage: '',
|
||||
rawInfo: null,
|
||||
},
|
||||
rebind: {
|
||||
...previousRebind,
|
||||
currentAttempt: attempt,
|
||||
history: [
|
||||
...history,
|
||||
{
|
||||
...baseHistoryItem,
|
||||
newBinding: {
|
||||
vnKey: preparedBinding.vnKey,
|
||||
vnId: preparedBinding.vnId,
|
||||
vnPhone: preparedBinding.vnPhone,
|
||||
bindUrl: preparedBinding.bindUrl,
|
||||
bindPreparedAt: now,
|
||||
bindExpiresAt: nextBindExpiresAt,
|
||||
},
|
||||
status: 'success',
|
||||
errorMessage: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const updatedTask = await updateTask(task.id, {
|
||||
task_status: 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: '',
|
||||
role_confirmed_at: null,
|
||||
last_error: '',
|
||||
context_json: JSON.stringify(nextContext),
|
||||
updated_at: now,
|
||||
})
|
||||
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
'kuaishou_cloud_rebind_prepared',
|
||||
{
|
||||
source,
|
||||
attempt,
|
||||
oldVnId: oldBinding.vnId,
|
||||
oldVnPhoneMasked: maskPhone(oldBinding.vnPhone),
|
||||
vnKey: preparedBinding.vnKey,
|
||||
vnId: preparedBinding.vnId,
|
||||
vnPhoneMasked: maskPhone(preparedBinding.vnPhone),
|
||||
bindUrl: preparedBinding.bindUrl,
|
||||
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 履约任务', {
|
||||
|
||||
Reference in New Issue
Block a user