修复 lewan 发货后 claim 轮询换号污染绑定与 UID 展示
发货成功时写入 shipped 身份快照,并冻结绑定资源变更;后台 UID 匹配优先使用发货快照,避免误报不一致。
This commit is contained in:
@@ -90,6 +90,7 @@ test('buildClaimIdentityAdminSummary reports match state', () => {
|
||||
assert.equal(summary.uidMatched, true)
|
||||
assert.equal(summary.compatibilityMode, 'uid')
|
||||
assert.equal(summary.boundUid, '10001')
|
||||
assert.equal(summary.displaySource, 'live')
|
||||
})
|
||||
|
||||
test('buildClaimIdentityAdminSummary marks legacy without uid', () => {
|
||||
@@ -97,4 +98,36 @@ test('buildClaimIdentityAdminSummary marks legacy without uid', () => {
|
||||
assert.equal(summary.ready, false)
|
||||
assert.equal(summary.compatibilityMode, 'legacy_no_uid')
|
||||
assert.equal(summary.uidMatched, null)
|
||||
assert.equal(summary.displaySource, 'live')
|
||||
})
|
||||
|
||||
test('buildClaimIdentityAdminSummary prefers shipped snapshot after dispatch pollution', () => {
|
||||
const summary = buildClaimIdentityAdminSummary(
|
||||
{
|
||||
claimIdentity: { expectedUid: '3781015014', submittedAt: 't', source: 'claim_page' },
|
||||
},
|
||||
{
|
||||
flowLike: {
|
||||
dispatch: { status: 'success', dispatchAt: '2026-07-16T06:25:13.000Z' },
|
||||
shipped: {
|
||||
expectedUid: '3781015014',
|
||||
roleId: '3781015014',
|
||||
roleName: 'l迈',
|
||||
vnId: 189363,
|
||||
vnPhone: '18641685924',
|
||||
dispatchedAt: '2026-07-16T06:25:13.000Z',
|
||||
},
|
||||
// 发货后换号污染:当前绑定已变成别的角色
|
||||
binding: { roleId: '3805904834', roleName: '他爆炸' },
|
||||
role: { rid: '3805904834', name: '他爆炸' },
|
||||
},
|
||||
},
|
||||
)
|
||||
assert.equal(summary.displaySource, 'shipped')
|
||||
assert.equal(summary.boundUid, '3781015014')
|
||||
assert.equal(summary.boundRoleName, 'l迈')
|
||||
assert.equal(summary.uidMatched, true)
|
||||
assert.equal(summary.shippedUid, '3781015014')
|
||||
assert.match(summary.note, /已发货/)
|
||||
assert.match(summary.note, /当前虚拟号角色已变化/)
|
||||
})
|
||||
|
||||
@@ -140,15 +140,61 @@ export function buildClaimIdentityAdminSummary(
|
||||
options: { flowLike?: unknown; taskRoleId?: unknown; taskRoleName?: unknown } = {},
|
||||
): ClaimIdentityAdminSummary {
|
||||
const identity = getClaimIdentityFromContext(context)
|
||||
const boundUid = resolveBoundRoleUid(options.flowLike) || normalizeClaimUid(options.taskRoleId)
|
||||
const flow = isPlainObject(options.flowLike) ? options.flowLike : {}
|
||||
const binding = isPlainObject(flow.binding) ? flow.binding : {}
|
||||
const role = isPlainObject(flow.role) ? flow.role : {}
|
||||
const boundRoleName = String(
|
||||
const shipped = isPlainObject(flow.shipped) ? flow.shipped : null
|
||||
const dispatch = isPlainObject(flow.dispatch) ? flow.dispatch : {}
|
||||
const shippedUid = normalizeClaimUid(shipped?.roleId || shipped?.expectedUid || '')
|
||||
const shippedRoleName = String(shipped?.roleName || '').trim()
|
||||
const shippedAt = shipped?.dispatchedAt ? String(shipped.dispatchedAt) : null
|
||||
const dispatchSucceeded = String(dispatch.status || '').trim() === 'success'
|
||||
// 已发货优先用快照,避免 claim 轮询换号后误报 UID 不一致
|
||||
const useShipped = Boolean(shippedUid && (dispatchSucceeded || shippedAt))
|
||||
const liveBoundUid =
|
||||
resolveBoundRoleUid(options.flowLike) || normalizeClaimUid(options.taskRoleId)
|
||||
const liveBoundRoleName = String(
|
||||
role.name || binding.roleName || options.taskRoleName || '',
|
||||
).trim()
|
||||
const boundUid = useShipped ? shippedUid : liveBoundUid
|
||||
const boundRoleName = useShipped
|
||||
? shippedRoleName || liveBoundRoleName
|
||||
: liveBoundRoleName
|
||||
const ready = Boolean(identity.expectedUid)
|
||||
const uidMatched = ready && boundUid ? isClaimUidMatched(identity.expectedUid, boundUid) : null
|
||||
const liveMismatched =
|
||||
useShipped &&
|
||||
liveBoundUid &&
|
||||
shippedUid &&
|
||||
!isClaimUidMatched(shippedUid, liveBoundUid)
|
||||
|
||||
let note = '旧单或未提交 UID:用户须先打开领取页填写 UID,否则禁止自动发货'
|
||||
if (ready) {
|
||||
if (useShipped) {
|
||||
if (uidMatched === true) {
|
||||
note = liveMismatched
|
||||
? `已发货:道具发至 UID ${shippedUid}${shippedRoleName ? `(${shippedRoleName})` : ''};当前虚拟号角色已变化,请勿以当前绑定为准`
|
||||
: `已发货:道具发至 UID ${shippedUid}${shippedRoleName ? `(${shippedRoleName})` : ''},与填写 UID 一致`
|
||||
} else if (uidMatched === false) {
|
||||
note = `已发货快照角色 ID(${boundUid})与填写 UID(${identity.expectedUid})不一致,请核对事件日志`
|
||||
} else {
|
||||
note = '已发货,但快照缺少角色 ID'
|
||||
}
|
||||
} else if (dispatchSucceeded) {
|
||||
note =
|
||||
uidMatched === true
|
||||
? '已发货:当前绑定角色与填写 UID 一致(无发货快照,建议以事件日志为准)'
|
||||
: uidMatched === false
|
||||
? '已发货,但当前绑定角色与填写 UID 不一致(可能为发货后换号污染;请核对 dispatch 事件中的 roleId)'
|
||||
: '已发货,等待核对绑定角色'
|
||||
} else if (uidMatched === true) {
|
||||
note = 'UID 已匹配,可继续履约'
|
||||
} else if (uidMatched === false) {
|
||||
note = '绑定角色 ID 与填写 UID 不一致'
|
||||
} else {
|
||||
note = '已填 UID,等待绑定识别'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
expectedUid: identity.expectedUid,
|
||||
@@ -159,13 +205,11 @@ export function buildClaimIdentityAdminSummary(
|
||||
boundRoleName,
|
||||
uidMatched,
|
||||
compatibilityMode: ready ? ('uid' as const) : ('legacy_no_uid' as const),
|
||||
note: ready
|
||||
? uidMatched === true
|
||||
? 'UID 已匹配,可继续履约'
|
||||
: uidMatched === false
|
||||
? '绑定角色 ID 与填写 UID 不一致'
|
||||
: '已填 UID,等待绑定识别'
|
||||
: '旧单或未提交 UID:用户须先打开领取页填写 UID,否则禁止自动发货',
|
||||
note,
|
||||
displaySource: useShipped ? 'shipped' : 'live',
|
||||
shippedUid,
|
||||
shippedRoleName,
|
||||
shippedAt,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,21 @@ import {
|
||||
prepareFulfillmentBinding,
|
||||
refreshFulfillmentRole,
|
||||
} from '../fulfillment/executors/registry.js'
|
||||
import { normalizeKuaishouCloudFlow } from '../fulfillment/kuaishou-cloud/index.js'
|
||||
import {
|
||||
isKuaishouCloudBindingMutationFrozen,
|
||||
normalizeKuaishouCloudFlow,
|
||||
} from '../fulfillment/kuaishou-cloud/index.js'
|
||||
import type { TaskRow } from '../../types/repository/rows.js'
|
||||
import { parseTaskContext as parseTaskContextValue } from '../../utils/task-json.js'
|
||||
|
||||
export async function syncKuaishouCloudRoleInfo(task: TaskRow) {
|
||||
const flow = normalizeKuaishouCloudFlow(parseTaskContext(task).kuaishouCloudFulfillment)
|
||||
|
||||
// 已发货/兑换中:禁止 claim 轮询换号或覆盖角色(脏单 status 可能已回退 waiting_binding)
|
||||
if (isKuaishouCloudBindingMutationFrozen(task, flow)) {
|
||||
return task
|
||||
}
|
||||
|
||||
if (!flow.binding.vnId || !flow.binding.vnKey || !flow.binding.vnPhone) {
|
||||
// ticket verified 或绑定尚未 ready:补 prepare(覆盖提前核销后未取号场景)
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user