优化 UID 履约闸门、运营可见性与一键兑换
lewan 发货强制 expectedUid 并取消无 UID 旧路径回落;任务详情展示 UID 匹配态;领取页匹配后可一键兑换;补充 identity 单测并收口短链说明。
This commit is contained in:
@@ -4,7 +4,7 @@ import type { TaskRow } from "../../../types/repository/rows.js";
|
||||
import { createHttpError } from "../../../utils/http.js";
|
||||
import { getCloudtentaclesBindInfo } from "../../platforms/cloudtentacles/virtual-number-service.js";
|
||||
import {
|
||||
getClaimIdentityFromContext,
|
||||
assertLewanAutoFulfillmentUidReady,
|
||||
isClaimUidMatched,
|
||||
normalizeClaimUid,
|
||||
} from "../../claim/claim-identity.js";
|
||||
@@ -36,7 +36,12 @@ export async function syncKuaishouCloudRoleInfoBeforeDispatch(
|
||||
const flow = normalizeKuaishouCloudFlow(
|
||||
input.flow || taskContext.kuaishouCloudFulfillment
|
||||
);
|
||||
const expectedUid = getClaimIdentityFromContext(taskContext).expectedUid;
|
||||
|
||||
// 新策略:lewan 自动发货必须有 expectedUid,旧单无 UID 不可静默回落
|
||||
const expectedUid = assertLewanAutoFulfillmentUidReady(task, {
|
||||
allowMockSkip: true,
|
||||
errorCodePrefix,
|
||||
});
|
||||
|
||||
if (!flow.binding.vnId || !flow.binding.vnKey) {
|
||||
throw createHttpError("当前任务缺少可同步角色的虚拟号信息,暂时不能发货", {
|
||||
@@ -62,76 +67,30 @@ export async function syncKuaishouCloudRoleInfoBeforeDispatch(
|
||||
);
|
||||
}
|
||||
|
||||
const confirmedRoleName = String(
|
||||
flow.binding.roleName || flow.role.name || task.role_name || ""
|
||||
).trim();
|
||||
const confirmedRoleId = String(
|
||||
flow.binding.roleId || flow.role.rid || task.role_id || ""
|
||||
).trim();
|
||||
const liveBoundUid = normalizeClaimUid(roleInfo.rid);
|
||||
|
||||
// 优先:声明 UID 与云端当前绑定角色一致
|
||||
if (expectedUid) {
|
||||
if (!isClaimUidMatched(expectedUid, liveBoundUid)) {
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
"kuaishou_cloud_dispatch_uid_mismatch",
|
||||
{
|
||||
source,
|
||||
vnId: flow.binding.vnId,
|
||||
expectedUid,
|
||||
cloudtentaclesRoleName: roleInfo.name,
|
||||
cloudtentaclesRoleId: roleInfo.rid,
|
||||
actor: input.actor || null,
|
||||
},
|
||||
now
|
||||
);
|
||||
|
||||
throw createHttpError(
|
||||
`cloudtentacles 当前绑定角色 ID(${roleInfo.rid})与用户填写 UID(${expectedUid})不一致,不能发货`,
|
||||
{
|
||||
statusCode: 409,
|
||||
errorCode: `${errorCodePrefix}_dispatch_uid_mismatch`,
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 兼容旧任务:无 expectedUid 时仍比对「确认角色」
|
||||
const roleMatches = isSameKuaishouCloudRole(
|
||||
if (expectedUid && !isClaimUidMatched(expectedUid, liveBoundUid)) {
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
"kuaishou_cloud_dispatch_uid_mismatch",
|
||||
{
|
||||
name: confirmedRoleName,
|
||||
rid: confirmedRoleId,
|
||||
source,
|
||||
vnId: flow.binding.vnId,
|
||||
expectedUid,
|
||||
cloudtentaclesRoleName: roleInfo.name,
|
||||
cloudtentaclesRoleId: roleInfo.rid,
|
||||
actor: input.actor || null,
|
||||
},
|
||||
{
|
||||
name: roleInfo.name,
|
||||
rid: roleInfo.rid,
|
||||
}
|
||||
now
|
||||
);
|
||||
|
||||
if (!roleMatches) {
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
"kuaishou_cloud_dispatch_role_mismatch",
|
||||
{
|
||||
source,
|
||||
vnId: flow.binding.vnId,
|
||||
confirmedRoleName,
|
||||
confirmedRoleId,
|
||||
cloudtentaclesRoleName: roleInfo.name,
|
||||
cloudtentaclesRoleId: roleInfo.rid,
|
||||
actor: input.actor || null,
|
||||
},
|
||||
now
|
||||
);
|
||||
|
||||
throw createHttpError(
|
||||
`cloudtentacles 当前绑定角色(${roleInfo.name}/${roleInfo.rid})与客户确认角色(${confirmedRoleName || "-"}/${confirmedRoleId || "-"})不一致,请重新刷新角色后再发货`,
|
||||
{
|
||||
statusCode: 409,
|
||||
errorCode: `${errorCodePrefix}_dispatch_role_mismatch`,
|
||||
}
|
||||
);
|
||||
}
|
||||
throw createHttpError(
|
||||
`cloudtentacles 当前绑定角色 ID(${roleInfo.rid})与用户填写 UID(${expectedUid})不一致,不能发货`,
|
||||
{
|
||||
statusCode: 409,
|
||||
errorCode: `${errorCodePrefix}_dispatch_uid_mismatch`,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const nextFlow = normalizeKuaishouCloudFlow({
|
||||
@@ -191,62 +150,4 @@ export async function syncKuaishouCloudRoleInfoBeforeDispatch(
|
||||
};
|
||||
}
|
||||
|
||||
function isSameKuaishouCloudRole(
|
||||
confirmedRole: { name: string; rid: string },
|
||||
cloudtentaclesRole: { name: string; rid: string }
|
||||
) {
|
||||
if (confirmedRole.rid && cloudtentaclesRole.rid) {
|
||||
return confirmedRole.rid === cloudtentaclesRole.rid;
|
||||
}
|
||||
|
||||
const confirmedName = normalizeKuaishouCloudRoleNameForCompare(
|
||||
confirmedRole.name
|
||||
);
|
||||
const cloudtentaclesName = normalizeKuaishouCloudRoleNameForCompare(
|
||||
cloudtentaclesRole.name
|
||||
);
|
||||
|
||||
if (!confirmedName || !cloudtentaclesName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return confirmedName === cloudtentaclesName;
|
||||
}
|
||||
|
||||
function normalizeKuaishouCloudRoleNameForCompare(value: unknown) {
|
||||
const name = String(value || "").trim();
|
||||
if (!name) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const parts = name
|
||||
.split("-")
|
||||
.map((part) => part.trim())
|
||||
.filter(Boolean);
|
||||
const devicePart = parts[0] || "";
|
||||
const channelPart = parts[1] || "";
|
||||
if (
|
||||
parts.length >= 3 &&
|
||||
isKuaishouCloudDevicePart(devicePart) &&
|
||||
isKuaishouCloudChannelPart(channelPart)
|
||||
) {
|
||||
return normalizeTextForCompare(parts.slice(2).join("-"));
|
||||
}
|
||||
|
||||
return normalizeTextForCompare(name);
|
||||
}
|
||||
|
||||
function isKuaishouCloudDevicePart(value: string) {
|
||||
return ["安卓", "android", "ios", "苹果"].includes(value.toLowerCase());
|
||||
}
|
||||
|
||||
function isKuaishouCloudChannelPart(value: string) {
|
||||
return ["qq", "微信", "vx", "wechat", "v"].includes(value.toLowerCase());
|
||||
}
|
||||
|
||||
function normalizeTextForCompare(value: unknown) {
|
||||
return String(value || "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user