继续收紧 TypeScript:ClaimIdentity 契约与 flow 出口类型
- 新增 types/claim-identity,统一 ClaimIdentity / Payload / AdminSummary - 导出 KuaishouCloudFlow、KuaishouFeifeiFlow 作为 normalize 出口 - 生产路径 JsonObject 统一从 types/json 导入,去掉 rebind as any - 前后端 claimIdentity 类型字段对齐
This commit is contained in:
@@ -34,18 +34,7 @@ export type CloudSkuLikeItem = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type KuaishouCloudFlow = JsonObject & {
|
||||
configId: string
|
||||
internalSkuCode: string
|
||||
internalSkuName: string
|
||||
ticket: JsonObject
|
||||
binding: JsonObject
|
||||
role: JsonObject
|
||||
purchase: JsonObject
|
||||
dispatch: JsonObject
|
||||
returnNumber: JsonObject
|
||||
consume: JsonObject
|
||||
}
|
||||
export type { KuaishouCloudFlow } from '../../fulfillment/kuaishou-cloud/domain.js'
|
||||
|
||||
export type KuaishouCloudBindingResources = {
|
||||
skuId: number
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { parseTaskContext } from '../../utils/task-json.js'
|
||||
import { isTaskFinalStatus, normalizeTaskStatus, TASK_STATUS } from '../../domain/task-status.js'
|
||||
import type {
|
||||
ClaimIdentity,
|
||||
ClaimIdentityAdminSummary,
|
||||
ClaimIdentityPayload,
|
||||
} from '../../types/claim-identity.js'
|
||||
import type { TaskRow } from '../../types/repository/rows.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
|
||||
export type ClaimIdentity = {
|
||||
expectedUid: string
|
||||
submittedAt: string | null
|
||||
source: string
|
||||
}
|
||||
export type {
|
||||
ClaimIdentity,
|
||||
ClaimIdentityAdminSummary,
|
||||
ClaimIdentityPayload,
|
||||
} from '../../types/claim-identity.js'
|
||||
|
||||
const CLAIM_UID_MAX_LENGTH = 64
|
||||
|
||||
@@ -130,18 +135,6 @@ export function assertLewanAutoFulfillmentUidReady(
|
||||
return expectedUid
|
||||
}
|
||||
|
||||
export type ClaimIdentityAdminSummary = {
|
||||
expectedUid: string
|
||||
submittedAt: string | null
|
||||
source: string
|
||||
ready: boolean
|
||||
boundUid: string
|
||||
boundRoleName: string
|
||||
uidMatched: boolean | null
|
||||
compatibilityMode: 'uid' | 'legacy_no_uid'
|
||||
note: string
|
||||
}
|
||||
|
||||
export function buildClaimIdentityAdminSummary(
|
||||
context: unknown,
|
||||
options: { flowLike?: unknown; taskRoleId?: unknown; taskRoleName?: unknown } = {},
|
||||
@@ -261,7 +254,7 @@ export function appendUidToUrl(targetUrl: unknown, uid: unknown): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function buildClaimIdentityPayload(value: unknown) {
|
||||
export function buildClaimIdentityPayload(value: unknown): ClaimIdentityPayload {
|
||||
const identity = normalizeClaimIdentity(value)
|
||||
return {
|
||||
expectedUid: identity.expectedUid,
|
||||
|
||||
@@ -18,7 +18,103 @@ export function isIndustryEVoucherTask(task: unknown) {
|
||||
return String(source.executor_key || '').trim() === 'kuaishou-industry'
|
||||
}
|
||||
|
||||
export function normalizeKuaishouCloudFlow(value: unknown) {
|
||||
export type KuaishouCloudDeliveryItem = {
|
||||
cloudSkuId: number
|
||||
cloudSkuName: string
|
||||
quantity: number
|
||||
}
|
||||
|
||||
/** normalizeKuaishouCloudFlow 出口类型(履约主路径应经此 normalize) */
|
||||
export type KuaishouCloudFlow = {
|
||||
configId: string
|
||||
internalSkuCode: string
|
||||
internalSkuName: string
|
||||
deliveryItems: KuaishouCloudDeliveryItem[]
|
||||
ticket: {
|
||||
code: string
|
||||
status: string
|
||||
capturedAt: unknown
|
||||
capturedBy: unknown
|
||||
verifiedAt: unknown
|
||||
oid: string
|
||||
formToken: string
|
||||
leftCount: number
|
||||
goodsTitle: string
|
||||
}
|
||||
binding: {
|
||||
prepareStatus: string
|
||||
cloudSourceKeys: string[]
|
||||
resolvedSourceKey: string
|
||||
skuId: number
|
||||
skuName: string
|
||||
vnKey: string
|
||||
vnId: number
|
||||
vnPhone: string
|
||||
bindUrl: string
|
||||
bindPreparedAt: unknown
|
||||
bindExpiresAt: unknown
|
||||
bindProbeAt: unknown
|
||||
bindProbeStatus: string
|
||||
bindProbeMessage: string
|
||||
roleName: string
|
||||
roleId: string
|
||||
}
|
||||
role: {
|
||||
status: string
|
||||
name: string
|
||||
rid: string
|
||||
refreshedAt: unknown
|
||||
errorMessage: string
|
||||
rawInfo: unknown
|
||||
defaultName: string
|
||||
defaultRid: string
|
||||
defaultCapturedAt: unknown
|
||||
defaultCaptureStatus: string
|
||||
defaultErrorMessage: string
|
||||
isDefaultRole: boolean
|
||||
}
|
||||
purchase: {
|
||||
autoBuyEnabled: boolean
|
||||
minAssetReserve: number
|
||||
usedKnapsack: boolean
|
||||
purchaseTriggered: boolean
|
||||
assetBefore: number
|
||||
assetAfter: number
|
||||
purchaseAt: unknown
|
||||
items: unknown[]
|
||||
}
|
||||
dispatch: {
|
||||
status: string
|
||||
dispatchAt: unknown
|
||||
dispatchBy: unknown
|
||||
sendType: number
|
||||
note: string
|
||||
items: unknown[]
|
||||
}
|
||||
returnNumber: {
|
||||
status: string
|
||||
returnedAt: unknown
|
||||
returnedBy: unknown
|
||||
autoReturnEnabled: boolean
|
||||
}
|
||||
consume: {
|
||||
status: string
|
||||
shopId: string
|
||||
shopName: string
|
||||
autoConsumeEnabled: boolean
|
||||
consumedAt: unknown
|
||||
errorMessage: string
|
||||
}
|
||||
rebind: {
|
||||
currentAttempt: number
|
||||
history: unknown[]
|
||||
}
|
||||
notes: string
|
||||
/** 未建模扩展(mock 等)仍可落库 */
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export function normalizeKuaishouCloudFlow(value: unknown): KuaishouCloudFlow {
|
||||
const source: JsonObject = value && typeof value === 'object' ? (value as JsonObject) : {}
|
||||
const binding = asJsonObject(source.binding)
|
||||
const role = asJsonObject(source.role)
|
||||
|
||||
@@ -14,6 +14,10 @@ export {
|
||||
isSameKuaishouCloudRoleIdentity,
|
||||
resolveKuaishouCloudBindUrlExpiresAt,
|
||||
} from './domain.js'
|
||||
export type {
|
||||
KuaishouCloudDeliveryItem,
|
||||
KuaishouCloudFlow,
|
||||
} from './domain.js'
|
||||
export { resolvePersistedCloudtentaclesContextBySourceKeys } from './cloudtentacles-context.js'
|
||||
export { ensureTaskClaimLink } from './ensure-claim-link.js'
|
||||
export {
|
||||
|
||||
@@ -41,10 +41,13 @@ export async function probeKuaishouCloudTaskBindUrl(task: TaskRow, options: Json
|
||||
|
||||
const probeIntervalMs =
|
||||
Number(resolveCloudtentaclesConfig().bindUrlProbeIntervalSeconds || 30) * 1000
|
||||
const lastProbeAt = flow.binding.bindProbeAt
|
||||
? Date.parse(String(flow.binding.bindProbeAt))
|
||||
: NaN
|
||||
if (
|
||||
!options.force &&
|
||||
flow.binding.bindProbeAt &&
|
||||
Date.now() - Date.parse(flow.binding.bindProbeAt) < probeIntervalMs
|
||||
Number.isFinite(lastProbeAt) &&
|
||||
Date.now() - lastProbeAt < probeIntervalMs
|
||||
) {
|
||||
return {
|
||||
task,
|
||||
|
||||
@@ -38,14 +38,13 @@ export async function rebindKuaishouCloudTaskRole(task: TaskRow, options: JsonOb
|
||||
}
|
||||
|
||||
const normalizedStatus = normalizeTaskStatus(task.task_status)
|
||||
if (
|
||||
![
|
||||
const rebindAllowedStatuses = new Set<string>([
|
||||
TASK_STATUS.WAITING_BINDING,
|
||||
TASK_STATUS.ROLE_CONFIRMED,
|
||||
TASK_STATUS.MANUAL_REVIEW,
|
||||
TASK_STATUS.RETRY_PENDING,
|
||||
].includes(normalizedStatus as any)
|
||||
) {
|
||||
])
|
||||
if (!rebindAllowedStatuses.has(normalizedStatus)) {
|
||||
throw createHttpError('当前任务状态不可换绑角色', {
|
||||
statusCode: 409,
|
||||
errorCode: 'kuaishou_cloud_rebind_not_allowed',
|
||||
|
||||
@@ -6,9 +6,9 @@ import { getCloudtentaclesBindInfo } from '../../platforms/cloudtentacles/virtua
|
||||
import {
|
||||
hasKuaishouCloudDefaultRoleSnapshot,
|
||||
isSameKuaishouCloudRoleIdentity,
|
||||
normalizeKuaishouCloudFlow,
|
||||
normalizeKuaishouCloudRoleInfo,
|
||||
type JsonObject,
|
||||
type KuaishouCloudFlow,
|
||||
} from './domain.js'
|
||||
|
||||
export type KuaishouCloudDefaultRoleSnapshot = {
|
||||
@@ -79,7 +79,7 @@ export function buildRoleStateFromCurrentInfo({
|
||||
now,
|
||||
emptyMessage,
|
||||
}: {
|
||||
flow: ReturnType<typeof normalizeKuaishouCloudFlow>
|
||||
flow: KuaishouCloudFlow
|
||||
roleInfo: { name: string; rid: string; rawInfo?: unknown }
|
||||
now: string
|
||||
emptyMessage: string
|
||||
|
||||
@@ -207,7 +207,28 @@ export async function syncKuaishouFeifeiTaskStatus(task: TaskRow) {
|
||||
return updatedTask || task
|
||||
}
|
||||
|
||||
export function normalizeKuaishouFeifeiFlow(value: unknown) {
|
||||
/** normalizeKuaishouFeifeiFlow 出口类型 */
|
||||
export type KuaishouFeifeiFlow = {
|
||||
flowType: 'kuaishou_feifei'
|
||||
productCode: string
|
||||
productName: string
|
||||
platformOrderNo: string
|
||||
orderNo: string
|
||||
rechargeStatus: number
|
||||
rechargeStatusLabel: string
|
||||
rechargeResultMessage: string
|
||||
claimUrl: string
|
||||
consumeStatus: string
|
||||
shortLink: ReturnType<typeof normalizeShortLinkPayload>
|
||||
h5: {
|
||||
entryUrl: string
|
||||
rechargeUrl: string
|
||||
}
|
||||
lastSyncedAt: unknown
|
||||
raw: unknown
|
||||
}
|
||||
|
||||
export function normalizeKuaishouFeifeiFlow(value: unknown): KuaishouFeifeiFlow {
|
||||
const source = value && typeof value === 'object' && !Array.isArray(value)
|
||||
? value as JsonObject
|
||||
: {}
|
||||
@@ -270,7 +291,7 @@ export function resolveKuaishouFeifeiH5UrlWithTaskUid(
|
||||
}
|
||||
|
||||
function mergeKuaishouFeifeiOrder(
|
||||
flow: ReturnType<typeof normalizeKuaishouFeifeiFlow>,
|
||||
flow: KuaishouFeifeiFlow,
|
||||
order: Awaited<ReturnType<typeof createKuaishouFeifeiOrder>>,
|
||||
patch: {
|
||||
platformOrderNo?: string
|
||||
@@ -299,7 +320,7 @@ function mergeKuaishouFeifeiOrder(
|
||||
}
|
||||
|
||||
function resolveKuaishouFeifeiDirectClaimUrl(
|
||||
flow: ReturnType<typeof normalizeKuaishouFeifeiFlow>,
|
||||
flow: KuaishouFeifeiFlow,
|
||||
) {
|
||||
const h5ClaimUrl = flow.h5.rechargeUrl || flow.h5.entryUrl
|
||||
if (h5ClaimUrl) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getFulfillmentProfileByKey } from '../../repositories/fulfillment-profile-repo.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import type { OrderItemRow, OrderRow } from '../../types/repository/rows.js'
|
||||
import {
|
||||
FULFILLMENT_EXECUTOR_KEYS,
|
||||
@@ -6,8 +7,6 @@ import {
|
||||
isKuaishouFeifeiExecutor,
|
||||
} from './executors/types.js'
|
||||
|
||||
type JsonObject = Record<string, unknown>
|
||||
|
||||
export type FulfillmentBindingLike = {
|
||||
id: number
|
||||
profile_id?: number
|
||||
|
||||
@@ -7,10 +7,9 @@ import {
|
||||
saveAppConfigEntry,
|
||||
} from '../config/app-config-store.js'
|
||||
import { normalizeCloudtentaclesMatchName } from '../order/cloudtentacles-match-utils.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import { FULFILLMENT_EXECUTOR_KEYS } from './executors/types.js'
|
||||
|
||||
type JsonObject = Record<string, unknown>
|
||||
|
||||
export type FulfillmentRoutingRuleMatchType = 'exact' | 'contains'
|
||||
|
||||
export type FulfillmentRoutingExecutorConfig = {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
|
||||
const DEFAULT_TIMEOUT_MS = 10000
|
||||
|
||||
type JsonObject = Record<string, unknown>
|
||||
type BarkSendInput = {
|
||||
serverUrl?: string
|
||||
recipient?: {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import { logWarn } from '../../utils/logger.js'
|
||||
import { sendInternalNotification } from './notification-service.js'
|
||||
|
||||
const DEFAULT_COOLDOWN_MS = 10 * 60 * 1000
|
||||
const notificationCooldownMap = new Map<string, number>()
|
||||
|
||||
type JsonObject = Record<string, unknown>
|
||||
type InternalNotificationPayload = {
|
||||
title: string
|
||||
body?: string
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import http from 'node:http'
|
||||
import https from 'node:https'
|
||||
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
|
||||
const DEFAULT_WPUSH_ENDPOINT = 'https://api.wpush.cn/api/v1/send'
|
||||
const DEFAULT_TIMEOUT_MS = 10000
|
||||
|
||||
type JsonObject = Record<string, unknown>
|
||||
type NodeHttpResponse = {
|
||||
ok: boolean
|
||||
status: number
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
import type { ClaimIdentityAdminSummary } from '../claim-identity.js'
|
||||
import type { JsonObject } from '../json.js'
|
||||
|
||||
/**
|
||||
* 后台任务详情 API 返回形状(与前端 AdminTaskDetail 对齐的后端契约)。
|
||||
* 深层履约字段仍用 JsonObject,避免一次改完所有 mapper。
|
||||
*/
|
||||
export type AdminClaimIdentitySummary = {
|
||||
expectedUid: string
|
||||
submittedAt: string | null
|
||||
source: string
|
||||
ready: boolean
|
||||
boundUid: string
|
||||
boundRoleName: string
|
||||
uidMatched: boolean | null
|
||||
compatibilityMode: 'uid' | 'legacy_no_uid'
|
||||
note: string
|
||||
}
|
||||
export type AdminClaimIdentitySummary = ClaimIdentityAdminSummary
|
||||
|
||||
export type AdminTaskDetailView = {
|
||||
task: JsonObject
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 领取页 UID 身份与后台摘要的共享类型(纯数据契约,无运行时依赖)。
|
||||
*/
|
||||
|
||||
export type ClaimIdentity = {
|
||||
expectedUid: string
|
||||
submittedAt: string | null
|
||||
source: string
|
||||
}
|
||||
|
||||
/** 领取页 / claim API 返回的 UID 身份(含 ready) */
|
||||
export type ClaimIdentityPayload = ClaimIdentity & {
|
||||
ready: boolean
|
||||
}
|
||||
|
||||
/** 后台任务详情中的 UID 兼容摘要 */
|
||||
export type ClaimIdentityAdminSummary = ClaimIdentityPayload & {
|
||||
boundUid: string
|
||||
boundRoleName: string
|
||||
uidMatched: boolean | null
|
||||
compatibilityMode: 'uid' | 'legacy_no_uid'
|
||||
note: string
|
||||
}
|
||||
|
||||
/** context_json 中 claimIdentity 的宽松写入形态(兼容历史 uid 字段) */
|
||||
export type ClaimIdentityContext = {
|
||||
expectedUid?: string
|
||||
submittedAt?: string | null
|
||||
source?: string
|
||||
/** @deprecated 历史字段,normalize 时映射到 expectedUid */
|
||||
uid?: string
|
||||
}
|
||||
@@ -1,18 +1,16 @@
|
||||
import type { ClaimIdentityContext } from './claim-identity.js'
|
||||
import type { JsonObject } from './json.js'
|
||||
|
||||
export type { ClaimIdentityContext }
|
||||
|
||||
/**
|
||||
* 履约任务 context_json 的已知字段。
|
||||
* 未建模扩展仍可通过索引访问(JsonObject)。
|
||||
* kuaishouCloudFulfillment / kuaishouFeifei 在 normalize 出口有具名类型;
|
||||
* 此处保留 JsonObject 以兼容未 normalize 的原始落库形态。
|
||||
*/
|
||||
export type ClaimIdentityContext = {
|
||||
expectedUid?: string
|
||||
submittedAt?: string | null
|
||||
source?: string
|
||||
uid?: string
|
||||
}
|
||||
|
||||
export type TaskContext = JsonObject & {
|
||||
claimIdentity?: ClaimIdentityContext | JsonObject
|
||||
claimIdentity?: ClaimIdentityContext
|
||||
kuaishouCloudFulfillment?: JsonObject
|
||||
kuaishouFeifei?: JsonObject
|
||||
kuaishouIndustryVoucher?: JsonObject
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { JsonObject } from '../types/json.js'
|
||||
import { createHttpError } from './http.js'
|
||||
|
||||
const LARGE_INTEGER_PATTERN = /(:\s*)(-?\d{16,})(\s*[,}\]])/g
|
||||
|
||||
type JsonObject = Record<string, unknown>
|
||||
|
||||
type ParseJsonObjectOptions = {
|
||||
preserveLargeIntegers?: boolean
|
||||
throwOnError?: boolean
|
||||
|
||||
@@ -58,6 +58,19 @@ export interface AdminTaskActionResponse {
|
||||
token?: string
|
||||
}
|
||||
|
||||
/** 与后端 ClaimIdentityAdminSummary / AdminClaimIdentitySummary 对齐 */
|
||||
export interface AdminClaimIdentitySummary {
|
||||
expectedUid: string
|
||||
submittedAt: string | null
|
||||
source: string
|
||||
ready: boolean
|
||||
boundUid: string
|
||||
boundRoleName: string
|
||||
uidMatched: boolean | null
|
||||
compatibilityMode: 'uid' | 'legacy_no_uid'
|
||||
note: string
|
||||
}
|
||||
|
||||
export interface AdminTaskDetail {
|
||||
task: AdminTaskListItem
|
||||
order: null | {
|
||||
@@ -105,17 +118,7 @@ export interface AdminTaskDetail {
|
||||
resultMessage: string
|
||||
}>
|
||||
}
|
||||
claimIdentity: null | {
|
||||
expectedUid: string
|
||||
submittedAt: string | null
|
||||
source: string
|
||||
ready: boolean
|
||||
boundUid: string
|
||||
boundRoleName: string
|
||||
uidMatched: boolean | null
|
||||
compatibilityMode: 'uid' | 'legacy_no_uid'
|
||||
note: string
|
||||
}
|
||||
claimIdentity: AdminClaimIdentitySummary | null
|
||||
kuaishouCloudFulfillment: null | {
|
||||
flowType: string
|
||||
configId: string
|
||||
|
||||
@@ -4,6 +4,7 @@ export type ClaimTokenStatus = 'active' | 'used' | 'expired' | 'revoked' | (stri
|
||||
|
||||
export type ClaimTaskStatus = KnownTaskStatus | (string & {})
|
||||
|
||||
/** 与后端 ClaimIdentityPayload 对齐 */
|
||||
export interface ClaimIdentityInfo {
|
||||
expectedUid: string
|
||||
submittedAt: string | null
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
- `JsonObject`:中心定义(当前值仍为 any 以兼容深层 context 访问)
|
||||
- `StrictJson` / `StrictJsonObject`:新代码可用的 unknown 严格形态
|
||||
- `asJsonObject` / `isPlainObject` 工具
|
||||
3. **去掉约 60 处本地** `type JsonObject = Record<string, any>`
|
||||
3. **去掉生产路径本地** `type JsonObject = …`(统一从 `types/json` 导入;scripts 可保留本地别名)
|
||||
4. **TaskContext**:`types/task-context.ts`,`parseTaskContext` 返回 `TaskContext`
|
||||
5. **Admin 任务详情契约**:`types/admin/task-detail.ts`,`getAdminTaskDetail(): Promise<AdminTaskDetailView>`
|
||||
6. **测试 fixture**:`delivery-task-service.test.ts` 减少 `as any`
|
||||
5. **ClaimIdentity 契约**:`types/claim-identity.ts`(`ClaimIdentity` / `ClaimIdentityPayload` / `ClaimIdentityAdminSummary`)
|
||||
6. **Admin 任务详情契约**:`types/admin/task-detail.ts`,`getAdminTaskDetail(): Promise<AdminTaskDetailView>`
|
||||
7. **normalize 出口具名类型**:`KuaishouCloudFlow`、`KuaishouFeifeiFlow`
|
||||
8. **前后端 claimIdentity 字段对齐**:前端 `ClaimIdentityInfo` / `AdminClaimIdentitySummary`
|
||||
9. **测试 fixture**:`delivery-task-service.test.ts` 减少 `as any`;生产路径去掉 `as any` 状态列表
|
||||
|
||||
## 未做完(后续)
|
||||
|
||||
@@ -18,6 +21,7 @@
|
||||
- 前后端共享类型包 / OpenAPI
|
||||
- 前端对齐 `noUncheckedIndexedAccess`
|
||||
- 全量测试 fixture 工厂化
|
||||
- scripts 本地 JsonObject 别名可按需迁入中心类型
|
||||
|
||||
## 约定
|
||||
|
||||
|
||||
Reference in New Issue
Block a user