优化连接过期验证与戒色信息-1

This commit is contained in:
yml2213
2026-05-14 14:42:30 +08:00
parent c7a9787814
commit 486ba14dc4
18 changed files with 815 additions and 15 deletions
@@ -21,8 +21,10 @@ import {
generateCloudtentaclesLoginCode,
getCloudtentaclesBindInfo,
getCloudtentaclesBindUrl,
probeCloudtentaclesBindUrl,
verifyCloudtentaclesLoginCode,
} from '../platforms/cloudtentacles/virtual-number-service.js'
import { resolveCloudtentaclesConfig } from '../platforms/cloudtentacles/shared.js'
import { consumeKuaishouEticket } from '../platforms/kuaishou-eticket/consume-service.js'
import {
getKuaishouEticketSourceConfig,
@@ -49,6 +51,8 @@ export function normalizeKuaishouCloudFlow(value) {
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)
return {
...source,
@@ -75,7 +79,11 @@ export function normalizeKuaishouCloudFlow(value) {
vnId: Number(binding.vnId || 0) || 0,
vnPhone: String(binding.vnPhone || '').trim(),
bindUrl: String(binding.bindUrl || '').trim(),
bindPreparedAt: binding.bindPreparedAt || null,
bindPreparedAt,
bindExpiresAt,
bindProbeAt: binding.bindProbeAt || null,
bindProbeStatus: String(binding.bindProbeStatus || '').trim(),
bindProbeMessage: String(binding.bindProbeMessage || '').trim(),
roleName,
roleId,
},
@@ -123,10 +131,12 @@ export function normalizeKuaishouCloudFlow(value) {
export function normalizeKuaishouCloudRoleInfo(value) {
const rawInfo = value && typeof value === 'object' ? value : null
const nestedBindInfo = rawInfo?.sBindInfo && typeof rawInfo.sBindInfo === 'object' ? rawInfo.sBindInfo : null
const source = nestedBindInfo || rawInfo
return {
name: String(rawInfo?.name || rawInfo?.roleName || rawInfo?.nickname || '').trim(),
rid: String(rawInfo?.rid || rawInfo?.roleId || rawInfo?.uid || '').trim(),
name: String(source?.name || source?.roleName || source?.nickname || source?.sRoleName || '').trim(),
rid: String(source?.rid || source?.roleId || source?.uid || source?.sRoleId || source?.sUserId || '').trim(),
rawInfo,
}
}
@@ -188,6 +198,14 @@ export async function prepareKuaishouCloudFulfillmentTask(task, options = {}) {
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') {
@@ -300,6 +318,10 @@ export async function prepareKuaishouCloudFulfillmentTask(task, options = {}) {
vnPhone: preparedBinding.vnPhone,
bindUrl: preparedBinding.bindUrl,
bindPreparedAt: now,
bindExpiresAt: resolveKuaishouCloudBindUrlExpiresAt(now),
bindProbeAt: null,
bindProbeStatus: 'pending',
bindProbeMessage: '',
roleName: '',
roleId: '',
},
@@ -356,6 +378,180 @@ export async function prepareKuaishouCloudFulfillmentTask(task, options = {}) {
}
}
export async function refreshKuaishouCloudTaskBindUrl(task, options = {}) {
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 cloudContext = resolvePersistedCloudtentaclesContext()
const bindUrlResult = await getCloudtentaclesBindUrl({
...cloudContext,
key: flow.binding.vnKey,
id: flow.binding.vnId,
})
const bindUrl = String(bindUrlResult.bindUrl || '').trim()
if (!bindUrl) {
throw createHttpError('cloudtentacles 未返回新的绑定链接', {
statusCode: 502,
errorCode: 'kuaishou_cloud_empty_bind_url',
})
}
const nextContext = {
...taskContext,
kuaishouCloudFulfillment: {
...flow,
binding: {
...flow.binding,
prepareStatus: 'ready',
bindUrl,
bindPreparedAt: now,
bindExpiresAt: resolveKuaishouCloudBindUrlExpiresAt(now),
bindProbeAt: 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',
vnId: flow.binding.vnId,
vnPhoneMasked: maskPhone(flow.binding.vnPhone),
actor,
}, now)
return {
task: updatedTask,
claimUrl: claimLinkState.claimUrl,
token: claimLinkState.token,
flow: normalizeKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment),
}
}
export async function probeKuaishouCloudTaskBindUrl(task, options = {}) {
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,
}
}
if (!options.force && flow.binding.bindProbeAt && Date.now() - Date.parse(flow.binding.bindProbeAt) < 15000) {
return {
task,
flow,
probe: 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,
}
}
export async function refreshKuaishouCloudTaskRoleInfo(task, options = {}) {
if (!isKuaishouCloudTask(task)) {
throw createHttpError('当前任务不是快手 Cloud 履约任务', {
@@ -376,6 +572,34 @@ export async function refreshKuaishouCloudTaskRoleInfo(task, options = {}) {
})
}
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 cloudContext = resolvePersistedCloudtentaclesContext()
const bindInfoResult = await getCloudtentaclesBindInfo({
...cloudContext,
@@ -701,6 +925,36 @@ export function maskCode(value) {
return `${text.slice(0, 4)}****${text.slice(-4)}`
}
export function resolveKuaishouCloudBindUrlExpiresAt(preparedAt) {
const preparedTime = Date.parse(String(preparedAt || ''))
if (!Number.isFinite(preparedTime)) {
return null
}
const config = resolveCloudtentaclesConfig()
const ttlSeconds = Number(config.bindUrlTtlSeconds || 300)
return new Date(preparedTime + Math.max(1, ttlSeconds) * 1000).toISOString()
}
export function isKuaishouCloudBindUrlFresh(flow, now = new Date()) {
const normalizedFlow = normalizeKuaishouCloudFlow(flow)
if (!normalizedFlow.binding.bindUrl) {
return false
}
const expiresAt = normalizedFlow.binding.bindExpiresAt
if (!expiresAt) {
return false
}
const expiresTime = Date.parse(String(expiresAt || ''))
if (!Number.isFinite(expiresTime)) {
return false
}
return expiresTime > now.getTime()
}
function resolveKuaishouCloudBindingResources(flow, { skuItems = [], knapsackItems = [] } = {}) {
const normalizedSkuItems = Array.isArray(skuItems) ? skuItems.filter(isCloudSkuLikeItem) : []
const normalizedKnapsackItems = Array.isArray(knapsackItems) ? knapsackItems.filter(isCloudSkuLikeItem) : []