优化连接过期验证与戒色信息-1
This commit is contained in:
@@ -99,6 +99,16 @@ module.exports = {
|
||||
vnBindUrlPath: '/vn/bind_url',
|
||||
vnBindInfoPath: '/vn/bind_info',
|
||||
vnBackPath: '/vn/back',
|
||||
bindUrlTtlSeconds: 300,
|
||||
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',
|
||||
bindUrlProbeEndpoint: 'https://comm.ams.game.qq.com/ide/',
|
||||
bindUrlProbeChartId: '323794',
|
||||
bindUrlProbeSubChartId: '323794',
|
||||
bindUrlProbeIdeToken: 'z90Syo',
|
||||
bindUrlProbeActivityUrl: 'http%3A%2F%2Fgp.qq.com%2Fcp%2Fa20240828cmcc%2F',
|
||||
bindUrlProbeReferer: 'https://gp.qq.com/',
|
||||
bindUrlProbeExtraCookie: '',
|
||||
clientSource: 'ct-client',
|
||||
deviceId: '-',
|
||||
deviceType: 0,
|
||||
|
||||
@@ -355,6 +355,66 @@ function applyEnvOverrides(baseConfig) {
|
||||
nextConfig.platforms.cloudtentacles.vnBindUrlPath = cloudtentaclesVnBindUrlPath
|
||||
}
|
||||
|
||||
const cloudtentaclesVnBindInfoPath = String(process.env.CLOUDTENTACLES_VN_BIND_INFO_PATH || '').trim()
|
||||
if (cloudtentaclesVnBindInfoPath) {
|
||||
nextConfig.platforms.cloudtentacles.vnBindInfoPath = cloudtentaclesVnBindInfoPath
|
||||
}
|
||||
|
||||
const cloudtentaclesVnBackPath = String(process.env.CLOUDTENTACLES_VN_BACK_PATH || '').trim()
|
||||
if (cloudtentaclesVnBackPath) {
|
||||
nextConfig.platforms.cloudtentacles.vnBackPath = cloudtentaclesVnBackPath
|
||||
}
|
||||
|
||||
const cloudtentaclesBindUrlTtlSeconds = parseInteger(process.env.CLOUDTENTACLES_BIND_URL_TTL_SECONDS)
|
||||
if (cloudtentaclesBindUrlTtlSeconds !== null) {
|
||||
nextConfig.platforms.cloudtentacles.bindUrlTtlSeconds = cloudtentaclesBindUrlTtlSeconds
|
||||
}
|
||||
|
||||
const cloudtentaclesBindUrlProbeTimeoutMs = parseInteger(process.env.CLOUDTENTACLES_BIND_URL_PROBE_TIMEOUT_MS)
|
||||
if (cloudtentaclesBindUrlProbeTimeoutMs !== null) {
|
||||
nextConfig.platforms.cloudtentacles.bindUrlProbeTimeoutMs = cloudtentaclesBindUrlProbeTimeoutMs
|
||||
}
|
||||
|
||||
const cloudtentaclesBindUrlProbeUserAgent = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_USER_AGENT || '').trim()
|
||||
if (cloudtentaclesBindUrlProbeUserAgent) {
|
||||
nextConfig.platforms.cloudtentacles.bindUrlProbeUserAgent = cloudtentaclesBindUrlProbeUserAgent
|
||||
}
|
||||
|
||||
const cloudtentaclesBindUrlProbeEndpoint = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_ENDPOINT || '').trim()
|
||||
if (cloudtentaclesBindUrlProbeEndpoint) {
|
||||
nextConfig.platforms.cloudtentacles.bindUrlProbeEndpoint = cloudtentaclesBindUrlProbeEndpoint
|
||||
}
|
||||
|
||||
const cloudtentaclesBindUrlProbeChartId = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_CHART_ID || '').trim()
|
||||
if (cloudtentaclesBindUrlProbeChartId) {
|
||||
nextConfig.platforms.cloudtentacles.bindUrlProbeChartId = cloudtentaclesBindUrlProbeChartId
|
||||
}
|
||||
|
||||
const cloudtentaclesBindUrlProbeSubChartId = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_SUB_CHART_ID || '').trim()
|
||||
if (cloudtentaclesBindUrlProbeSubChartId) {
|
||||
nextConfig.platforms.cloudtentacles.bindUrlProbeSubChartId = cloudtentaclesBindUrlProbeSubChartId
|
||||
}
|
||||
|
||||
const cloudtentaclesBindUrlProbeIdeToken = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_IDE_TOKEN || '').trim()
|
||||
if (cloudtentaclesBindUrlProbeIdeToken) {
|
||||
nextConfig.platforms.cloudtentacles.bindUrlProbeIdeToken = cloudtentaclesBindUrlProbeIdeToken
|
||||
}
|
||||
|
||||
const cloudtentaclesBindUrlProbeActivityUrl = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_ACTIVITY_URL || '').trim()
|
||||
if (cloudtentaclesBindUrlProbeActivityUrl) {
|
||||
nextConfig.platforms.cloudtentacles.bindUrlProbeActivityUrl = cloudtentaclesBindUrlProbeActivityUrl
|
||||
}
|
||||
|
||||
const cloudtentaclesBindUrlProbeReferer = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_REFERER || '').trim()
|
||||
if (cloudtentaclesBindUrlProbeReferer) {
|
||||
nextConfig.platforms.cloudtentacles.bindUrlProbeReferer = cloudtentaclesBindUrlProbeReferer
|
||||
}
|
||||
|
||||
const cloudtentaclesBindUrlProbeExtraCookie = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_EXTRA_COOKIE || '').trim()
|
||||
if (cloudtentaclesBindUrlProbeExtraCookie) {
|
||||
nextConfig.platforms.cloudtentacles.bindUrlProbeExtraCookie = cloudtentaclesBindUrlProbeExtraCookie
|
||||
}
|
||||
|
||||
const cloudtentaclesPublicKeyPem = String(process.env.CLOUDTENTACLES_PUBLIC_KEY_PEM || '').trim()
|
||||
if (cloudtentaclesPublicKeyPem) {
|
||||
nextConfig.platforms.cloudtentacles.publicKeyPem = cloudtentaclesPublicKeyPem
|
||||
|
||||
@@ -61,6 +61,10 @@ export function mapKuaishouCloudFulfillmentContext(value) {
|
||||
vnPhone: String(binding.vnPhone || '').trim(),
|
||||
bindUrl: String(binding.bindUrl || '').trim(),
|
||||
bindPreparedAt: binding.bindPreparedAt || null,
|
||||
bindExpiresAt: binding.bindExpiresAt || null,
|
||||
bindProbeAt: binding.bindProbeAt || null,
|
||||
bindProbeStatus: String(binding.bindProbeStatus || '').trim(),
|
||||
bindProbeMessage: String(binding.bindProbeMessage || '').trim(),
|
||||
},
|
||||
role: {
|
||||
status: String(role.status || 'pending').trim() || 'pending',
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
normalizeKuaishouCloudFlow,
|
||||
normalizeKuaishouCloudRoleInfo,
|
||||
prepareKuaishouCloudBindResourceWithFallback,
|
||||
resolveKuaishouCloudBindUrlExpiresAt,
|
||||
resolveKuaishouCloudBindingResources,
|
||||
resolveKuaishouCloudVnKeyCandidates,
|
||||
resolvePersistedCloudtentaclesContext,
|
||||
@@ -169,6 +170,10 @@ export async function prepareAdminTaskKuaishouCloudFulfillment(taskId, session =
|
||||
vnPhone,
|
||||
bindUrl: preparedBinding.bindUrl,
|
||||
bindPreparedAt: now,
|
||||
bindExpiresAt: resolveKuaishouCloudBindUrlExpiresAt(now),
|
||||
bindProbeAt: null,
|
||||
bindProbeStatus: 'pending',
|
||||
bindProbeMessage: '',
|
||||
roleName: '',
|
||||
roleId: '',
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { normalizeProductName } from '../../order/product-match-service.js'
|
||||
import { getCloudtentaclesSourceConfig } from '../../platforms/cloudtentacles/source-config-service.js'
|
||||
import { getCloudtentaclesSessionState } from '../../platforms/cloudtentacles/session-state-service.js'
|
||||
import { resolveCloudtentaclesConfig } from '../../platforms/cloudtentacles/shared.js'
|
||||
import {
|
||||
appointCloudtentaclesVirtualNumber,
|
||||
backCloudtentaclesVirtualNumber,
|
||||
@@ -48,6 +49,7 @@ export function normalizeKuaishouCloudFlow(value) {
|
||||
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 bindPreparedAt = binding.bindPreparedAt || null
|
||||
|
||||
return {
|
||||
...source,
|
||||
@@ -74,7 +76,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: binding.bindExpiresAt || resolveKuaishouCloudBindUrlExpiresAt(bindPreparedAt),
|
||||
bindProbeAt: binding.bindProbeAt || null,
|
||||
bindProbeStatus: String(binding.bindProbeStatus || '').trim(),
|
||||
bindProbeMessage: String(binding.bindProbeMessage || '').trim(),
|
||||
roleName: String(binding.roleName || '').trim(),
|
||||
roleId: String(binding.roleId || '').trim(),
|
||||
},
|
||||
@@ -121,14 +127,27 @@ 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,
|
||||
}
|
||||
}
|
||||
|
||||
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 resolveKuaishouCloudBindingResources(flow, { skuItems = [], knapsackItems = [] } = {}) {
|
||||
const normalizedSkuItems = Array.isArray(skuItems) ? skuItems.filter(isCloudSkuLikeItem) : []
|
||||
const normalizedKnapsackItems = Array.isArray(knapsackItems) ? knapsackItems.filter(isCloudSkuLikeItem) : []
|
||||
|
||||
@@ -189,6 +189,7 @@ export async function confirmKuaishouCloudClaimRole(token) {
|
||||
source: 'claim_page_role_confirm',
|
||||
actor: { source: 'claim_page' },
|
||||
recordEvent: false,
|
||||
forceProbe: true,
|
||||
})
|
||||
const flow = normalizeKuaishouCloudFlow(parseTaskContext(refreshed.task).kuaishouCloudFulfillment)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
normalizeKuaishouCloudFlow,
|
||||
probeKuaishouCloudTaskBindUrl,
|
||||
refreshKuaishouCloudTaskRoleInfo,
|
||||
} from '../fulfillment/kuaishou-cloud-task-service.js'
|
||||
|
||||
@@ -10,11 +11,13 @@ export async function syncKuaishouCloudRoleInfo(task) {
|
||||
return task
|
||||
}
|
||||
|
||||
if (flow.binding.roleName && flow.binding.roleId) {
|
||||
return task
|
||||
}
|
||||
|
||||
try {
|
||||
const probed = await probeKuaishouCloudTaskBindUrl(task, {
|
||||
source: 'claim_page_polling_bind_url_probe',
|
||||
actor: { source: 'system' },
|
||||
})
|
||||
task = probed.task
|
||||
|
||||
const result = await refreshKuaishouCloudTaskRoleInfo(task, {
|
||||
source: 'claim_page_polling',
|
||||
actor: { source: 'system' },
|
||||
|
||||
@@ -105,6 +105,10 @@ export function mapClaimKuaishouCloudFulfillment(task, order) {
|
||||
vnId: Number(binding.vnId || 0) || 0,
|
||||
bindUrl: String(binding.bindUrl || '').trim(),
|
||||
bindPreparedAt: binding.bindPreparedAt || null,
|
||||
bindExpiresAt: binding.bindExpiresAt || null,
|
||||
bindProbeAt: binding.bindProbeAt || null,
|
||||
bindProbeStatus: String(binding.bindProbeStatus || '').trim(),
|
||||
bindProbeMessage: String(binding.bindProbeMessage || '').trim(),
|
||||
vnPhone: String(binding.vnPhone || '').trim(),
|
||||
roleName: String(binding.roleName || '').trim(),
|
||||
roleId: String(binding.roleId || '').trim(),
|
||||
|
||||
@@ -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) : []
|
||||
|
||||
@@ -115,6 +115,10 @@ export async function syncDeliveryTasksForOrder(order, orderItems) {
|
||||
vnPhone: '',
|
||||
bindUrl: '',
|
||||
bindPreparedAt: null,
|
||||
bindExpiresAt: null,
|
||||
bindProbeAt: null,
|
||||
bindProbeStatus: '',
|
||||
bindProbeMessage: '',
|
||||
},
|
||||
role: {
|
||||
status: 'pending',
|
||||
|
||||
@@ -27,6 +27,16 @@ export function resolveCloudtentaclesConfig(overrides = {}) {
|
||||
vnBindUrlPath: '/vn/bind_url',
|
||||
vnBindInfoPath: '/vn/bind_info',
|
||||
vnBackPath: '/vn/back',
|
||||
bindUrlTtlSeconds: 300,
|
||||
bindUrlProbeTimeoutMs: 5000,
|
||||
bindUrlProbeUserAgent: '',
|
||||
bindUrlProbeEndpoint: 'https://comm.ams.game.qq.com/ide/',
|
||||
bindUrlProbeChartId: '323794',
|
||||
bindUrlProbeSubChartId: '323794',
|
||||
bindUrlProbeIdeToken: 'z90Syo',
|
||||
bindUrlProbeActivityUrl: 'http%3A%2F%2Fgp.qq.com%2Fcp%2Fa20240828cmcc%2F',
|
||||
bindUrlProbeReferer: 'https://gp.qq.com/',
|
||||
bindUrlProbeExtraCookie: '',
|
||||
publicKeyPem: '',
|
||||
clientSource: 'ct-client',
|
||||
deviceId: '-',
|
||||
@@ -60,6 +70,16 @@ export function resolveCloudtentaclesConfig(overrides = {}) {
|
||||
vnBindUrlPath: normalizePath(overrides.vnBindUrlPath || baseConfig.vnBindUrlPath, '/vn/bind_url'),
|
||||
vnBindInfoPath: normalizePath(overrides.vnBindInfoPath || baseConfig.vnBindInfoPath, '/vn/bind_info'),
|
||||
vnBackPath: normalizePath(overrides.vnBackPath || baseConfig.vnBackPath, '/vn/back'),
|
||||
bindUrlTtlSeconds: normalizePositiveInteger(overrides.bindUrlTtlSeconds || baseConfig.bindUrlTtlSeconds, 300),
|
||||
bindUrlProbeTimeoutMs: normalizePositiveInteger(overrides.bindUrlProbeTimeoutMs || baseConfig.bindUrlProbeTimeoutMs, 5000),
|
||||
bindUrlProbeUserAgent: String(overrides.bindUrlProbeUserAgent || baseConfig.bindUrlProbeUserAgent || '').trim(),
|
||||
bindUrlProbeEndpoint: String(overrides.bindUrlProbeEndpoint || baseConfig.bindUrlProbeEndpoint || 'https://comm.ams.game.qq.com/ide/').trim(),
|
||||
bindUrlProbeChartId: String(overrides.bindUrlProbeChartId || baseConfig.bindUrlProbeChartId || '323794').trim(),
|
||||
bindUrlProbeSubChartId: String(overrides.bindUrlProbeSubChartId || baseConfig.bindUrlProbeSubChartId || '323794').trim(),
|
||||
bindUrlProbeIdeToken: String(overrides.bindUrlProbeIdeToken || baseConfig.bindUrlProbeIdeToken || 'z90Syo').trim(),
|
||||
bindUrlProbeActivityUrl: String(overrides.bindUrlProbeActivityUrl || baseConfig.bindUrlProbeActivityUrl || 'http%3A%2F%2Fgp.qq.com%2Fcp%2Fa20240828cmcc%2F').trim(),
|
||||
bindUrlProbeReferer: String(overrides.bindUrlProbeReferer || baseConfig.bindUrlProbeReferer || 'https://gp.qq.com/').trim(),
|
||||
bindUrlProbeExtraCookie: String(overrides.bindUrlProbeExtraCookie || baseConfig.bindUrlProbeExtraCookie || '').trim(),
|
||||
publicKeyPem: normalizePem(overrides.publicKeyPem || baseConfig.publicKeyPem),
|
||||
clientSource: String(overrides.clientSource || baseConfig.clientSource || 'ct-client').trim() || 'ct-client',
|
||||
deviceId: String(overrides.deviceId ?? baseConfig.deviceId ?? '-').trim() || '-',
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
// @ts-check
|
||||
|
||||
import http from 'node:http'
|
||||
import https from 'node:https'
|
||||
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import { cloudtentaclesRequest } from './http-client.js'
|
||||
import { resolveCloudtentaclesConfig } from './shared.js'
|
||||
|
||||
const BIND_URL_PROBE_MAX_BODY_BYTES = 64 * 1024
|
||||
const AMS_SIGNATURE_EXPIRED_CODE = '99998'
|
||||
|
||||
export async function listCloudtentaclesVirtualNumbers(payload = {}) {
|
||||
const token = requireToken(payload.token, 'cloudtentacles 虚拟号列表缺少 token', 'cloudtentacles_vn_list_missing_token')
|
||||
const key = requireKey(payload.key, 'cloudtentacles 虚拟号列表缺少 key', 'cloudtentacles_vn_list_missing_key')
|
||||
@@ -159,6 +165,80 @@ export async function getCloudtentaclesBindUrl(payload = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function probeCloudtentaclesBindUrl(payload = {}) {
|
||||
const bindUrl = String(payload.bindUrl || '').trim()
|
||||
if (!bindUrl) {
|
||||
return {
|
||||
valid: false,
|
||||
status: 0,
|
||||
finalUrl: '',
|
||||
reason: 'empty_url',
|
||||
}
|
||||
}
|
||||
|
||||
const config = resolveCloudtentaclesConfig(payload)
|
||||
const signatureParams = extractBindUrlSignatureParams(bindUrl)
|
||||
if (!signatureParams) {
|
||||
return {
|
||||
valid: false,
|
||||
expired: false,
|
||||
status: 0,
|
||||
finalUrl: bindUrl,
|
||||
reason: 'missing_signature_params',
|
||||
roleInfo: normalizeAmsBindRoleInfo(null),
|
||||
raw: null,
|
||||
}
|
||||
}
|
||||
|
||||
const timeoutMs = Number(payload.timeoutMs || config.bindUrlProbeTimeoutMs || 5000)
|
||||
const userAgent = String(payload.userAgent || config.bindUrlProbeUserAgent || '').trim()
|
||||
const endpoint = String(payload.endpoint || config.bindUrlProbeEndpoint || 'https://comm.ams.game.qq.com/ide/').trim()
|
||||
const startedAt = Date.now()
|
||||
|
||||
try {
|
||||
const response = await requestAmsIdeProbe(endpoint, {
|
||||
timeoutMs,
|
||||
body: buildAmsIdeProbeBody(signatureParams, config, payload),
|
||||
headers: buildAmsIdeProbeHeaders(signatureParams, config, {
|
||||
userAgent,
|
||||
}),
|
||||
})
|
||||
const raw = tryParseJson(response.bodyText)
|
||||
const expired = isAmsSignatureExpired(raw)
|
||||
const bindInfo = extractAmsBindInfo(raw)
|
||||
const roleInfo = normalizeAmsBindRoleInfo(bindInfo)
|
||||
const ok = response.status >= 200
|
||||
&& response.status < 300
|
||||
&& !expired
|
||||
&& isAmsBusinessOk(raw)
|
||||
|
||||
return {
|
||||
valid: ok,
|
||||
expired,
|
||||
status: response.status,
|
||||
finalUrl: endpoint,
|
||||
ret: String(raw?.ret ?? raw?.iRet ?? ''),
|
||||
iRet: String(raw?.iRet ?? raw?.jData?.iRet ?? ''),
|
||||
message: String(raw?.sMsg || raw?.jData?.sMsg || ''),
|
||||
reason: expired ? 'signature_expired' : (ok ? 'ok' : 'ams_business_not_ok'),
|
||||
roleInfo,
|
||||
raw,
|
||||
durationMs: Date.now() - startedAt,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
valid: false,
|
||||
expired: false,
|
||||
status: 0,
|
||||
finalUrl: bindUrl,
|
||||
reason: error instanceof Error ? error.message : String(error || 'probe_failed'),
|
||||
roleInfo: normalizeAmsBindRoleInfo(null),
|
||||
raw: null,
|
||||
durationMs: Date.now() - startedAt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCloudtentaclesBindInfo(payload = {}) {
|
||||
const token = requireToken(payload.token, 'cloudtentacles 获取绑定信息缺少 token', 'cloudtentacles_vn_bind_info_missing_token')
|
||||
const key = requireKey(payload.key, 'cloudtentacles 获取绑定信息缺少 key', 'cloudtentacles_vn_bind_info_missing_key')
|
||||
@@ -275,6 +355,242 @@ function parseBindInfo(value) {
|
||||
return tryParseJson(bindInfoText) || bindInfoText || null
|
||||
}
|
||||
|
||||
async function requestAmsIdeProbe(endpoint, options = {}) {
|
||||
const url = new URL(endpoint)
|
||||
if (!['http:', 'https:'].includes(url.protocol)) {
|
||||
throw new Error('unsupported_ams_probe_protocol')
|
||||
}
|
||||
|
||||
const timeoutMs = Number(options.timeoutMs || 5000)
|
||||
const isHttps = url.protocol === 'https:'
|
||||
const transport = isHttps ? https : http
|
||||
const body = options.body instanceof URLSearchParams ? options.body.toString() : String(options.body || '')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = transport.request(url, {
|
||||
method: 'POST',
|
||||
rejectUnauthorized: false,
|
||||
headers: {
|
||||
...normalizeHeaderMap(options.headers),
|
||||
'content-length': Buffer.byteLength(body),
|
||||
'accept-encoding': 'identity',
|
||||
},
|
||||
}, (response) => {
|
||||
const chunks = []
|
||||
let receivedBytes = 0
|
||||
|
||||
response.on('data', (chunk) => {
|
||||
if (receivedBytes >= BIND_URL_PROBE_MAX_BODY_BYTES) {
|
||||
return
|
||||
}
|
||||
|
||||
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
|
||||
const remainingBytes = BIND_URL_PROBE_MAX_BODY_BYTES - receivedBytes
|
||||
chunks.push(buffer.length > remainingBytes ? buffer.subarray(0, remainingBytes) : buffer)
|
||||
receivedBytes += Math.min(buffer.length, remainingBytes)
|
||||
})
|
||||
|
||||
response.on('end', () => {
|
||||
clearTimeout(timer)
|
||||
resolve({
|
||||
status: Number(response.statusCode || 0),
|
||||
headers: createHeaderAdapter(response.headers),
|
||||
bodyText: Buffer.concat(chunks).toString('utf8'),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
request.destroy(new Error('bind_url_probe_timeout'))
|
||||
}, timeoutMs)
|
||||
|
||||
request.on('error', (error) => {
|
||||
clearTimeout(timer)
|
||||
reject(error)
|
||||
})
|
||||
|
||||
request.write(body)
|
||||
request.end()
|
||||
})
|
||||
}
|
||||
|
||||
function createHeaderAdapter(headers) {
|
||||
const normalized = new Map()
|
||||
|
||||
for (const [key, value] of Object.entries(headers || {})) {
|
||||
if (Array.isArray(value)) {
|
||||
normalized.set(String(key || '').toLowerCase(), value.join(', '))
|
||||
continue
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
normalized.set(String(key || '').toLowerCase(), value)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
get(name) {
|
||||
return normalized.get(String(name || '').toLowerCase()) || null
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function buildAmsIdeProbeBody(signatureParams, config, payload = {}) {
|
||||
const body = new URLSearchParams()
|
||||
const chartId = String(payload.chartId || config.bindUrlProbeChartId || '323794').trim()
|
||||
const subChartId = String(payload.subChartId || config.bindUrlProbeSubChartId || chartId).trim()
|
||||
|
||||
body.set('iChartId', chartId)
|
||||
body.set('iSubChartId', subChartId)
|
||||
body.set('sIdeToken', String(payload.ideToken || config.bindUrlProbeIdeToken || 'z90Syo').trim())
|
||||
body.set('e_code', '0')
|
||||
body.set('g_code', '0')
|
||||
body.set('eas_url', String(payload.easUrl || config.bindUrlProbeActivityUrl || 'http%3A%2F%2Fgp.qq.com%2Fcp%2Fa20240828cmcc%2F').trim())
|
||||
body.set('eas_refer', String(payload.easRefer || `http%3A%2F%2Fnoreferrer%2F%3Freqid%3D${Date.now()}%26version%3D27`).trim())
|
||||
body.set('sMiloTag', String(payload.miloTag || `AMS-gp-${Date.now()}`).trim())
|
||||
body.set('userId', signatureParams.userId)
|
||||
body.set('timestamp', signatureParams.timestamp)
|
||||
body.set('nonce', signatureParams.nonce)
|
||||
body.set('sign', signatureParams.sign)
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
function buildAmsIdeProbeHeaders(signatureParams, config, options = {}) {
|
||||
const userAgent = String(options.userAgent || '').trim()
|
||||
const referer = String(config.bindUrlProbeReferer || 'https://gp.qq.com/').trim()
|
||||
|
||||
return {
|
||||
accept: 'application/json, text/plain, */*',
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
'accept-language': 'zh-CN,zh;q=0.9,fr;q=0.8,de;q=0.7,en;q=0.6',
|
||||
'cache-control': 'no-cache',
|
||||
origin: 'https://gp.qq.com',
|
||||
pragma: 'no-cache',
|
||||
referer,
|
||||
'sec-ch-ua': '""',
|
||||
'sec-ch-ua-mobile': '?1',
|
||||
'sec-ch-ua-platform': '""',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-site',
|
||||
...(userAgent ? { 'user-agent': userAgent } : {}),
|
||||
cookie: buildAmsIdeProbeCookie(signatureParams, config),
|
||||
}
|
||||
}
|
||||
|
||||
function buildAmsIdeProbeCookie(signatureParams, config) {
|
||||
const tokenParams = `?userId=${signatureParams.userId}×tamp=${signatureParams.timestamp}&nonce=${signatureParams.nonce}&sign=${signatureParams.sign}`
|
||||
const cookies = [
|
||||
`tokenParams=${encodeURIComponent(tokenParams)}`,
|
||||
'gpqqcomrouteLine=a20240828cmcc_a20240828cmcc_a20240828cmcc_a20240828cmcc_a20240828cmcc_a20240828cmcc_a20240828cmcc',
|
||||
String(config.bindUrlProbeExtraCookie || '').trim(),
|
||||
].filter(Boolean)
|
||||
|
||||
return cookies.join('; ')
|
||||
}
|
||||
|
||||
function extractBindUrlSignatureParams(bindUrl) {
|
||||
try {
|
||||
const url = new URL(bindUrl)
|
||||
const params = new URLSearchParams(url.search)
|
||||
const hashQueryIndex = String(url.hash || '').indexOf('?')
|
||||
if (hashQueryIndex >= 0) {
|
||||
const hashParams = new URLSearchParams(String(url.hash || '').slice(hashQueryIndex + 1))
|
||||
for (const [key, value] of hashParams.entries()) {
|
||||
if (!params.has(key)) {
|
||||
params.set(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
const tokenParams = String(params.get('tokenParams') || '').trim()
|
||||
if (tokenParams) {
|
||||
const nested = new URLSearchParams(tokenParams.startsWith('?') ? tokenParams.slice(1) : tokenParams)
|
||||
for (const [key, value] of nested.entries()) {
|
||||
if (!params.has(key)) {
|
||||
params.set(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const userId = String(params.get('userId') || '').trim()
|
||||
const timestamp = String(params.get('timestamp') || '').trim()
|
||||
const nonce = String(params.get('nonce') || '').trim()
|
||||
const sign = String(params.get('sign') || '').trim()
|
||||
|
||||
if (!userId || !timestamp || !nonce || !sign) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
userId,
|
||||
timestamp,
|
||||
nonce,
|
||||
sign,
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function isAmsBusinessOk(raw) {
|
||||
const ret = String(raw?.ret ?? '').trim()
|
||||
const iRet = String(raw?.iRet ?? raw?.jData?.iRet ?? '').trim()
|
||||
return ret === '0' || iRet === '0'
|
||||
}
|
||||
|
||||
function isAmsSignatureExpired(raw) {
|
||||
const codes = [
|
||||
raw?.ret,
|
||||
raw?.iRet,
|
||||
raw?.jData?.ret,
|
||||
raw?.jData?.iRet,
|
||||
raw?.jData?.jData?.iRet,
|
||||
raw?.arrErrNodeInfo?.errorCode,
|
||||
raw?.jData?.arrErrNodeInfo?.errorCode,
|
||||
].map((value) => String(value ?? '').trim())
|
||||
|
||||
const messages = [
|
||||
raw?.sMsg,
|
||||
raw?.jData?.sMsg,
|
||||
raw?.jData?.jData?.sMsg,
|
||||
].map((value) => String(value ?? '').trim())
|
||||
|
||||
return codes.includes(AMS_SIGNATURE_EXPIRED_CODE) || messages.some((message) => message.includes('签名已过期'))
|
||||
}
|
||||
|
||||
function extractAmsBindInfo(raw) {
|
||||
const candidates = [
|
||||
raw?.jData?.sBindInfo,
|
||||
raw?.jData?.jData?.sBindInfo,
|
||||
raw?.sBindInfo,
|
||||
]
|
||||
|
||||
return candidates.find((item) => isPlainObject(item)) || null
|
||||
}
|
||||
|
||||
function normalizeAmsBindRoleInfo(bindInfo) {
|
||||
const source = isPlainObject(bindInfo) ? bindInfo : {}
|
||||
|
||||
return {
|
||||
name: String(source.sRoleName || source.roleName || source.name || '').trim(),
|
||||
rid: String(source.sRoleId || source.roleId || source.rid || '').trim(),
|
||||
rawInfo: isPlainObject(bindInfo) ? bindInfo : null,
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeHeaderMap(headers) {
|
||||
if (!headers || typeof headers !== 'object') {
|
||||
return {}
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(headers)
|
||||
.map(([key, value]) => [String(key || '').trim().toLowerCase(), String(value || '').trim()])
|
||||
.filter(([key, value]) => key && value),
|
||||
)
|
||||
}
|
||||
|
||||
function isPlainObject(value) {
|
||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
||||
}
|
||||
|
||||
@@ -119,6 +119,16 @@ export {}
|
||||
* vnBindUrlPath: string
|
||||
* vnBindInfoPath: string
|
||||
* vnBackPath: string
|
||||
* bindUrlTtlSeconds: number
|
||||
* bindUrlProbeTimeoutMs: number
|
||||
* bindUrlProbeUserAgent: string
|
||||
* bindUrlProbeEndpoint: string
|
||||
* bindUrlProbeChartId: string
|
||||
* bindUrlProbeSubChartId: string
|
||||
* bindUrlProbeIdeToken: string
|
||||
* bindUrlProbeActivityUrl: string
|
||||
* bindUrlProbeReferer: string
|
||||
* bindUrlProbeExtraCookie: string
|
||||
* publicKeyPem: string
|
||||
* clientSource: string
|
||||
* deviceId: string
|
||||
|
||||
@@ -671,6 +671,10 @@ export interface AdminTaskDetail {
|
||||
vnPhone: string
|
||||
bindUrl: string
|
||||
bindPreparedAt: string | null
|
||||
bindExpiresAt: string | null
|
||||
bindProbeAt: string | null
|
||||
bindProbeStatus: string
|
||||
bindProbeMessage: string
|
||||
}
|
||||
role: {
|
||||
status: string
|
||||
|
||||
@@ -85,6 +85,10 @@ export interface ClaimKuaishouCloudFlowInfo {
|
||||
vnId: number
|
||||
bindUrl: string
|
||||
bindPreparedAt: string | null
|
||||
bindExpiresAt: string | null
|
||||
bindProbeAt: string | null
|
||||
bindProbeStatus: string
|
||||
bindProbeMessage: string
|
||||
vnPhone: string
|
||||
roleName: string
|
||||
roleId: string
|
||||
|
||||
@@ -33,10 +33,23 @@ const order = computed(() => detail.value?.order || null)
|
||||
const orderItem = computed(() => detail.value?.orderItem || null)
|
||||
const task = computed(() => detail.value?.task || null)
|
||||
|
||||
const roleName = computed(() => flow.value?.binding.roleName || flow.value?.role.name || '')
|
||||
const roleId = computed(() => flow.value?.binding.roleId || flow.value?.role.rid || '')
|
||||
const roleName = computed(() => flow.value?.role.name || flow.value?.binding.roleName || '')
|
||||
const roleId = computed(() => flow.value?.role.rid || flow.value?.binding.roleId || '')
|
||||
const isTicketVerified = computed(() => flow.value?.ticket.status === 'verified')
|
||||
const isBindingPrepared = computed(() => flow.value?.binding.prepareStatus === 'ready' && Boolean(String(flow.value?.binding.bindUrl || '').trim()))
|
||||
const isBindUrlExpired = computed(() => {
|
||||
const expiresAt = String(flow.value?.binding.bindExpiresAt || '').trim()
|
||||
if (!expiresAt) {
|
||||
return false
|
||||
}
|
||||
|
||||
const expiresTime = Date.parse(expiresAt)
|
||||
return Number.isFinite(expiresTime) && expiresTime <= Date.now()
|
||||
})
|
||||
const isBindingPrepared = computed(() => (
|
||||
flow.value?.binding.prepareStatus === 'ready'
|
||||
&& Boolean(String(flow.value?.binding.bindUrl || '').trim())
|
||||
&& !isBindUrlExpired.value
|
||||
))
|
||||
const isBindingPreparing = computed(() => flow.value?.binding.prepareStatus === 'pending')
|
||||
const canEnterBindingStep = computed(() => isTicketVerified.value && isBindingPrepared.value)
|
||||
const isRoleReady = computed(() => Boolean(roleName.value || roleId.value))
|
||||
@@ -408,7 +421,10 @@ onBeforeUnmount(() => {
|
||||
如果当前角色不正确,请重新打开绑定页完成重新绑定,然后刷新角色信息。
|
||||
</p>
|
||||
<p class="muted meta-line">
|
||||
最近刷新:{{ formatDateTime(flow.role.refreshedAt) }}
|
||||
最近刷新:{{ formatDateTime(flow.role.refreshedAt) }};链接有效期:{{ formatDateTime(flow.binding.bindExpiresAt) }}
|
||||
</p>
|
||||
<p v-if="flow.binding.bindProbeMessage" class="muted meta-line">
|
||||
链接检测:{{ flow.binding.bindProbeStatus || '-' }} / {{ flow.binding.bindProbeMessage }}
|
||||
</p>
|
||||
|
||||
<div class="action-row">
|
||||
@@ -425,7 +441,7 @@ onBeforeUnmount(() => {
|
||||
type="primary"
|
||||
size="large"
|
||||
:loading="confirmingRole"
|
||||
:disabled="!isBindingPrepared"
|
||||
:disabled="!isBindingPrepared || !isRoleReady"
|
||||
@click="confirmRole"
|
||||
>
|
||||
我已完成绑定,下一步
|
||||
|
||||
Reference in New Issue
Block a user