再跳回第 1 步

This commit is contained in:
yml2213
2026-05-14 16:07:17 +08:00
parent 486ba14dc4
commit 89ffc9b087
8 changed files with 145 additions and 16 deletions
+1
View File
@@ -26,6 +26,7 @@ TENCENT_BROWSER_KEEP_ALIVE=true
TENCENT_BROWSER_SLOW_MO=0 TENCENT_BROWSER_SLOW_MO=0
TENCENT_SESSION_DEBUG=false TENCENT_SESSION_DEBUG=false
CLOUDTENTACLES_BIND_URL_TTL_SECONDS=300 CLOUDTENTACLES_BIND_URL_TTL_SECONDS=300
CLOUDTENTACLES_BIND_URL_PROBE_INTERVAL_SECONDS=30
CLOUDTENTACLES_BIND_URL_PROBE_TIMEOUT_MS=5000 CLOUDTENTACLES_BIND_URL_PROBE_TIMEOUT_MS=5000
CLOUDTENTACLES_BIND_URL_PROBE_ENDPOINT=https://comm.ams.game.qq.com/ide/ CLOUDTENTACLES_BIND_URL_PROBE_ENDPOINT=https://comm.ams.game.qq.com/ide/
CLOUDTENTACLES_BIND_URL_PROBE_CHART_ID=323794 CLOUDTENTACLES_BIND_URL_PROBE_CHART_ID=323794
+1
View File
@@ -100,6 +100,7 @@ module.exports = {
vnBindInfoPath: '/vn/bind_info', vnBindInfoPath: '/vn/bind_info',
vnBackPath: '/vn/back', vnBackPath: '/vn/back',
bindUrlTtlSeconds: 300, bindUrlTtlSeconds: 300,
bindUrlProbeIntervalSeconds: 30,
bindUrlProbeTimeoutMs: 5000, bindUrlProbeTimeoutMs: 5000,
bindUrlProbeUserAgent: 'Mozilla/5.0 (Linux; Android 12; M2012K11AC Build/SKQ1.211006.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/98.0.4758.102 MQQBrowser/6.2 TBS/046141 Mobile Safari/537.36 V1_AND_SQ_8.8.85_2674_YYB_D QQ/8.8.85.7685 NetType/WIFI WebP/0.3.0 Pixel/1080 StatusBarHeight/108 SimpleUISwitch/0 QQTheme/1000', bindUrlProbeUserAgent: 'Mozilla/5.0 (Linux; Android 12; M2012K11AC Build/SKQ1.211006.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/98.0.4758.102 MQQBrowser/6.2 TBS/046141 Mobile Safari/537.36 V1_AND_SQ_8.8.85_2674_YYB_D QQ/8.8.85.7685 NetType/WIFI WebP/0.3.0 Pixel/1080 StatusBarHeight/108 SimpleUISwitch/0 QQTheme/1000',
bindUrlProbeEndpoint: 'https://comm.ams.game.qq.com/ide/', bindUrlProbeEndpoint: 'https://comm.ams.game.qq.com/ide/',
+5
View File
@@ -370,6 +370,11 @@ function applyEnvOverrides(baseConfig) {
nextConfig.platforms.cloudtentacles.bindUrlTtlSeconds = cloudtentaclesBindUrlTtlSeconds nextConfig.platforms.cloudtentacles.bindUrlTtlSeconds = cloudtentaclesBindUrlTtlSeconds
} }
const cloudtentaclesBindUrlProbeIntervalSeconds = parseInteger(process.env.CLOUDTENTACLES_BIND_URL_PROBE_INTERVAL_SECONDS)
if (cloudtentaclesBindUrlProbeIntervalSeconds !== null) {
nextConfig.platforms.cloudtentacles.bindUrlProbeIntervalSeconds = cloudtentaclesBindUrlProbeIntervalSeconds
}
const cloudtentaclesBindUrlProbeTimeoutMs = parseInteger(process.env.CLOUDTENTACLES_BIND_URL_PROBE_TIMEOUT_MS) const cloudtentaclesBindUrlProbeTimeoutMs = parseInteger(process.env.CLOUDTENTACLES_BIND_URL_PROBE_TIMEOUT_MS)
if (cloudtentaclesBindUrlProbeTimeoutMs !== null) { if (cloudtentaclesBindUrlProbeTimeoutMs !== null) {
nextConfig.platforms.cloudtentacles.bindUrlProbeTimeoutMs = cloudtentaclesBindUrlProbeTimeoutMs nextConfig.platforms.cloudtentacles.bindUrlProbeTimeoutMs = cloudtentaclesBindUrlProbeTimeoutMs
@@ -1,5 +1,6 @@
import { import {
normalizeKuaishouCloudFlow, normalizeKuaishouCloudFlow,
prepareKuaishouCloudFulfillmentTask,
probeKuaishouCloudTaskBindUrl, probeKuaishouCloudTaskBindUrl,
refreshKuaishouCloudTaskRoleInfo, refreshKuaishouCloudTaskRoleInfo,
} from '../fulfillment/kuaishou-cloud-task-service.js' } from '../fulfillment/kuaishou-cloud-task-service.js'
@@ -8,6 +9,18 @@ export async function syncKuaishouCloudRoleInfo(task) {
const flow = normalizeKuaishouCloudFlow(parseTaskContext(task).kuaishouCloudFulfillment) const flow = normalizeKuaishouCloudFlow(parseTaskContext(task).kuaishouCloudFulfillment)
if (!flow.binding.vnId || !flow.binding.vnKey || !flow.binding.vnPhone) { if (!flow.binding.vnId || !flow.binding.vnKey || !flow.binding.vnPhone) {
if (flow.ticket.status === 'verified' && flow.binding.prepareStatus !== 'ready') {
try {
const prepared = await prepareKuaishouCloudFulfillmentTask(task, {
source: 'claim_page_retry_binding_prepare',
actor: { source: 'system' },
})
return prepared.task
} catch {
return task
}
}
return task return task
} }
@@ -399,18 +399,55 @@ export async function refreshKuaishouCloudTaskBindUrl(task, options = {}) {
} }
const cloudContext = resolvePersistedCloudtentaclesContext() const cloudContext = resolvePersistedCloudtentaclesContext()
const bindUrlResult = await getCloudtentaclesBindUrl({ const oldVnKey = flow.binding.vnKey
...cloudContext, const oldVnId = flow.binding.vnId
key: flow.binding.vnKey, const oldVnPhone = flow.binding.vnPhone
id: flow.binding.vnId,
})
const bindUrl = String(bindUrlResult.bindUrl || '').trim()
if (!bindUrl) { await backCloudtentaclesVirtualNumber({
throw createHttpError('cloudtentacles 未返回新的绑定链接', { ...cloudContext,
statusCode: 502, key: oldVnKey,
errorCode: 'kuaishou_cloud_empty_bind_url', 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 = { const nextContext = {
@@ -420,7 +457,10 @@ export async function refreshKuaishouCloudTaskBindUrl(task, options = {}) {
binding: { binding: {
...flow.binding, ...flow.binding,
prepareStatus: 'ready', prepareStatus: 'ready',
bindUrl, vnKey: preparedBinding.vnKey,
vnId: preparedBinding.vnId,
vnPhone: preparedBinding.vnPhone,
bindUrl: preparedBinding.bindUrl,
bindPreparedAt: now, bindPreparedAt: now,
bindExpiresAt: resolveKuaishouCloudBindUrlExpiresAt(now), bindExpiresAt: resolveKuaishouCloudBindUrlExpiresAt(now),
bindProbeAt: null, bindProbeAt: null,
@@ -456,8 +496,11 @@ export async function refreshKuaishouCloudTaskBindUrl(task, options = {}) {
await createTaskEvent(task.id, 'kuaishou_cloud_bind_url_refreshed', { await createTaskEvent(task.id, 'kuaishou_cloud_bind_url_refreshed', {
source: String(options.source || 'system').trim() || 'system', source: String(options.source || 'system').trim() || 'system',
vnId: flow.binding.vnId, oldVnId,
vnPhoneMasked: maskPhone(flow.binding.vnPhone), oldVnPhoneMasked: maskPhone(oldVnPhone),
vnKey: preparedBinding.vnKey,
vnId: preparedBinding.vnId,
vnPhoneMasked: maskPhone(preparedBinding.vnPhone),
actor, actor,
}, now) }, now)
@@ -489,7 +532,8 @@ export async function probeKuaishouCloudTaskBindUrl(task, options = {}) {
} }
} }
if (!options.force && flow.binding.bindProbeAt && Date.now() - Date.parse(flow.binding.bindProbeAt) < 15000) { const probeIntervalMs = Number(resolveCloudtentaclesConfig().bindUrlProbeIntervalSeconds || 30) * 1000
if (!options.force && flow.binding.bindProbeAt && Date.now() - Date.parse(flow.binding.bindProbeAt) < probeIntervalMs) {
return { return {
task, task,
flow, flow,
@@ -552,6 +596,65 @@ export async function probeKuaishouCloudTaskBindUrl(task, options = {}) {
} }
} }
/**
* @param {any} task
* @param {{ taskContext?: any, flow?: any, now?: string, actor?: any, error?: unknown }} [input]
*/
async function markKuaishouCloudBindUrlRefreshFailed(task, {
taskContext,
flow,
now,
actor,
error,
} = {}) {
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)
return updatedTask
}
export async function refreshKuaishouCloudTaskRoleInfo(task, options = {}) { export async function refreshKuaishouCloudTaskRoleInfo(task, options = {}) {
if (!isKuaishouCloudTask(task)) { if (!isKuaishouCloudTask(task)) {
throw createHttpError('当前任务不是快手 Cloud 履约任务', { throw createHttpError('当前任务不是快手 Cloud 履约任务', {
@@ -28,6 +28,7 @@ export function resolveCloudtentaclesConfig(overrides = {}) {
vnBindInfoPath: '/vn/bind_info', vnBindInfoPath: '/vn/bind_info',
vnBackPath: '/vn/back', vnBackPath: '/vn/back',
bindUrlTtlSeconds: 300, bindUrlTtlSeconds: 300,
bindUrlProbeIntervalSeconds: 30,
bindUrlProbeTimeoutMs: 5000, bindUrlProbeTimeoutMs: 5000,
bindUrlProbeUserAgent: '', bindUrlProbeUserAgent: '',
bindUrlProbeEndpoint: 'https://comm.ams.game.qq.com/ide/', bindUrlProbeEndpoint: 'https://comm.ams.game.qq.com/ide/',
@@ -71,6 +72,10 @@ export function resolveCloudtentaclesConfig(overrides = {}) {
vnBindInfoPath: normalizePath(overrides.vnBindInfoPath || baseConfig.vnBindInfoPath, '/vn/bind_info'), vnBindInfoPath: normalizePath(overrides.vnBindInfoPath || baseConfig.vnBindInfoPath, '/vn/bind_info'),
vnBackPath: normalizePath(overrides.vnBackPath || baseConfig.vnBackPath, '/vn/back'), vnBackPath: normalizePath(overrides.vnBackPath || baseConfig.vnBackPath, '/vn/back'),
bindUrlTtlSeconds: normalizePositiveInteger(overrides.bindUrlTtlSeconds || baseConfig.bindUrlTtlSeconds, 300), bindUrlTtlSeconds: normalizePositiveInteger(overrides.bindUrlTtlSeconds || baseConfig.bindUrlTtlSeconds, 300),
bindUrlProbeIntervalSeconds: normalizePositiveInteger(
overrides.bindUrlProbeIntervalSeconds || baseConfig.bindUrlProbeIntervalSeconds,
30,
),
bindUrlProbeTimeoutMs: normalizePositiveInteger(overrides.bindUrlProbeTimeoutMs || baseConfig.bindUrlProbeTimeoutMs, 5000), bindUrlProbeTimeoutMs: normalizePositiveInteger(overrides.bindUrlProbeTimeoutMs || baseConfig.bindUrlProbeTimeoutMs, 5000),
bindUrlProbeUserAgent: String(overrides.bindUrlProbeUserAgent || baseConfig.bindUrlProbeUserAgent || '').trim(), bindUrlProbeUserAgent: String(overrides.bindUrlProbeUserAgent || baseConfig.bindUrlProbeUserAgent || '').trim(),
bindUrlProbeEndpoint: String(overrides.bindUrlProbeEndpoint || baseConfig.bindUrlProbeEndpoint || 'https://comm.ams.game.qq.com/ide/').trim(), bindUrlProbeEndpoint: String(overrides.bindUrlProbeEndpoint || baseConfig.bindUrlProbeEndpoint || 'https://comm.ams.game.qq.com/ide/').trim(),
+1
View File
@@ -120,6 +120,7 @@ export {}
* vnBindInfoPath: string * vnBindInfoPath: string
* vnBackPath: string * vnBackPath: string
* bindUrlTtlSeconds: number * bindUrlTtlSeconds: number
* bindUrlProbeIntervalSeconds: number
* bindUrlProbeTimeoutMs: number * bindUrlProbeTimeoutMs: number
* bindUrlProbeUserAgent: string * bindUrlProbeUserAgent: string
* bindUrlProbeEndpoint: string * bindUrlProbeEndpoint: string
@@ -51,7 +51,7 @@ const isBindingPrepared = computed(() => (
&& !isBindUrlExpired.value && !isBindUrlExpired.value
)) ))
const isBindingPreparing = computed(() => flow.value?.binding.prepareStatus === 'pending') const isBindingPreparing = computed(() => flow.value?.binding.prepareStatus === 'pending')
const canEnterBindingStep = computed(() => isTicketVerified.value && isBindingPrepared.value) const canEnterBindingStep = computed(() => isTicketVerified.value)
const isRoleReady = computed(() => Boolean(roleName.value || roleId.value)) const isRoleReady = computed(() => Boolean(roleName.value || roleId.value))
const isRoleConfirmed = computed(() => String(task.value?.status || '').trim() === 'role_confirmed') const isRoleConfirmed = computed(() => String(task.value?.status || '').trim() === 'role_confirmed')
const isDispatched = computed(() => String(flow.value?.dispatch.status || '').trim() === 'success') const isDispatched = computed(() => String(flow.value?.dispatch.status || '').trim() === 'success')
@@ -85,7 +85,7 @@ const progressText = computed(() => {
return '角色已确认,等待兑换' return '角色已确认,等待兑换'
} }
if (isTicketVerified.value) { if (isTicketVerified.value) {
return isBindingPrepared.value ? '请完成扫码绑定' : '绑定资源未准备完成,请重新验证核销码' return isBindingPrepared.value ? '请完成扫码绑定' : '绑定链接刷新中,请稍候'
} }
return '等待提交核销码' return '等待提交核销码'
}) })