后端迁移领取换码服务

This commit is contained in:
yml
2026-05-21 14:22:20 +08:00
parent ab3a8caa9f
commit b6b6d2d8ca
2 changed files with 138 additions and 17 deletions
@@ -1,5 +1,3 @@
// @ts-check
import { reloadTencentBrowserSession, redeemTencentBrowserSession } from '../../session/session.js'
import { classifyTencentRedeemResult } from '../../session/session-redeem.js'
import { createTaskEvent } from '../../../repositories/task-event-repo.js'
@@ -11,14 +9,127 @@ import { reserveInventoryForTask } from '../../order/inventory-service.js'
import { createHttpError } from '../../../utils/http.js'
import { nowIso } from '../../../utils/time.js'
import { maskCode, REDEEM_REPLACEMENT_LIMIT } from './shared.js'
import type { InventoryItemRow, OrderItemRow, TaskRow } from '../../../types/repository-rows.js'
export async function redeemClaimTaskWithInventoryFallback(context, initialInventoryItem) {
type ClaimRedeemContext = {
task: Pick<TaskRow, 'id' | 'browser_session_id'>
orderItem: Pick<OrderItemRow, 'sku_code'>
}
type TencentRedeemPayload = {
[key: string]: unknown
iRet?: unknown
ret?: unknown
}
type TencentRedeemSession = {
[key: string]: unknown
sessionId?: string
artifacts?: {
[key: string]: unknown
hasScreenshot?: boolean
} | null
redeem?: {
final?: {
redeem?: TencentRedeemPayload | null
} | null
} | null
}
type TencentRedeemClassification = {
success: boolean
outcome: string
message: string
retCode?: unknown
[key: string]: unknown
}
type RedeemAttemptSummary = {
attempt: number
inventoryItemId: number
codeMasked: string
credentialType: string
outcome: string
resultCode: string
resultMessage: string
}
type RedeemTaskState = {
taskStatus: string
inventoryStatus: string
deliveryStatus: string
lastError: string
attempts: RedeemAttemptSummary[]
classification: TencentRedeemClassification | null
}
type RedeemTaskStateError = Error & {
statusCode?: number
errorCode?: string
redeemTaskState: RedeemTaskState
}
type RedeemTaskStateErrorPayload = {
statusCode?: number
errorCode?: string
taskStatus?: string
inventoryStatus?: string
deliveryStatus?: string
attempts?: RedeemAttemptSummary[]
classification?: TencentRedeemClassification | null
}
type RedeemClaimTaskDeps = {
reloadTencentBrowserSession?: (sessionId: string) => Promise<unknown>
redeemTencentBrowserSession?: (
sessionId: string,
payload: { code: string },
) => Promise<TencentRedeemSession>
classifyTencentRedeemResult?: (finalRedeem: TencentRedeemPayload | null) => TencentRedeemClassification
markInventoryItemConsumed?: (
inventoryItemId: number | string,
reason: string,
consumedAt: string,
) => Promise<InventoryItemRow | null>
createTaskEvent?: (
taskId: number | string,
eventType: string,
payload?: unknown,
createdAt?: string,
) => Promise<unknown>
invalidateReservedInventoryItem?: (
inventoryItemId: number | string,
reason: string,
updatedAt: string,
) => Promise<InventoryItemRow | null>
reserveInventoryForTask?: (payload: {
skuCode: string
taskId: number | string
credentialType?: string
roleKey?: string
inventoryGroupCodes?: string[] | null
}) => Promise<InventoryItemRow | null>
nowIso?: () => string
redeemReplacementLimit?: number
}
type RedeemClaimTaskResult = {
session: TencentRedeemSession
inventoryItem: InventoryItemRow
classification: TencentRedeemClassification
attempts: RedeemAttemptSummary[]
}
export async function redeemClaimTaskWithInventoryFallback(
context: ClaimRedeemContext,
initialInventoryItem: InventoryItemRow,
): Promise<RedeemClaimTaskResult> {
return redeemClaimTaskWithInventoryFallbackWithDeps(context, initialInventoryItem)
}
export async function redeemClaimTaskWithInventoryFallbackWithDeps(
context,
initialInventoryItem,
context: ClaimRedeemContext,
initialInventoryItem: InventoryItemRow,
{
reloadTencentBrowserSession: reloadRedeemSession = reloadTencentBrowserSession,
redeemTencentBrowserSession: redeemSession = redeemTencentBrowserSession,
@@ -29,9 +140,9 @@ export async function redeemClaimTaskWithInventoryFallbackWithDeps(
reserveInventoryForTask: reserveReplacementInventory = reserveInventoryForTask,
nowIso: getNowIso = nowIso,
redeemReplacementLimit = REDEEM_REPLACEMENT_LIMIT,
} = {},
) {
const attempts = []
}: RedeemClaimTaskDeps = {},
): Promise<RedeemClaimTaskResult> {
const attempts: RedeemAttemptSummary[] = []
let currentInventoryItem = initialInventoryItem
let shouldReloadBeforeNextAttempt = false
@@ -156,15 +267,14 @@ export async function redeemClaimTaskWithInventoryFallbackWithDeps(
})
}
/**
* @param {string} message
* @param {{ statusCode?: number, errorCode?: string, taskStatus?: string, inventoryStatus?: string, deliveryStatus?: string, attempts?: unknown[], classification?: { retCode?: unknown } | null }} [payload]
*/
function createRedeemTaskStateError(message, payload = {}) {
const error = /** @type {Error & import('../../../utils/http.js').HttpErrorLike & { redeemTaskState: { taskStatus: string, inventoryStatus: string, deliveryStatus: string, lastError: string, attempts: unknown[], classification: { retCode?: unknown } | null } }} */ (createHttpError(message, {
function createRedeemTaskStateError(
message: string,
payload: RedeemTaskStateErrorPayload = {},
): RedeemTaskStateError {
const error = createHttpError(message, {
statusCode: payload.statusCode || 409,
errorCode: payload.errorCode || 'claim_redeem_failed',
}))
}) as unknown as RedeemTaskStateError
error.redeemTaskState = {
taskStatus: payload.taskStatus || 'retry_pending',
@@ -178,7 +288,10 @@ function createRedeemTaskStateError(message, payload = {}) {
return error
}
export function resolveTencentRedeemResultCode(finalRedeem, classification) {
export function resolveTencentRedeemResultCode(
finalRedeem: TencentRedeemPayload | null,
classification: Pick<TencentRedeemClassification, 'retCode'> | null,
): string {
if (classification?.retCode != null) {
return String(classification.retCode)
}
+9 -1
View File
@@ -264,13 +264,21 @@
- `npm run typecheck`
- `npm run build`
- `npm test` 共 125 个用例通过
28. claim 换码服务迁移到 `.ts`
- `src/services/claim/session/redeem.ts`
29. 换码流程的上下文、腾讯兑换返回、分类结果、尝试记录、错误状态、依赖注入函数已显式类型化
30. Docker 内验证通过:
- claim 换码相关 2 个测试文件共 8 个用例通过
- `npm run typecheck`
- `npm run build`
- `npm test` 共 125 个用例通过
## 下一步建议
第一批继续推进时,建议按这个顺序:
1. 继续迁移服务层中最核心、最常改的订单 / 履约 / claim 模块
2. 逐步移除服务层 `@ts-nocheck`,优先处理 claim session、webhook service、自动发货
2. 逐步移除服务层 `@ts-nocheck`,优先处理 webhook service、自动发货
## 执行原则