后端迁移领取换码服务
This commit is contained in:
+129
-16
@@ -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)
|
||||
}
|
||||
@@ -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、自动发货
|
||||
|
||||
## 执行原则
|
||||
|
||||
|
||||
Reference in New Issue
Block a user