添加快手 Cloud 换绑角色功能
This commit is contained in:
@@ -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