refactor: extract cleaner retry handler
This commit is contained in:
@@ -10,6 +10,7 @@ import { AsyncMutex, ConcurrencyTracker } from './cleaner/concurrency'
|
|||||||
import { evaluateDeletionSignals as evaluateDeletionSignalsHelper } from './cleaner/deletion-signals'
|
import { evaluateDeletionSignals as evaluateDeletionSignalsHelper } from './cleaner/deletion-signals'
|
||||||
import { MaterialDeletionVerifier } from './cleaner/material-deletion-verifier'
|
import { MaterialDeletionVerifier } from './cleaner/material-deletion-verifier'
|
||||||
import { CleanerNavigation } from './cleaner/navigation'
|
import { CleanerNavigation } from './cleaner/navigation'
|
||||||
|
import { CleanerRetryHandler } from './cleaner/retry-handler'
|
||||||
import { getSkipReason, shouldDeleteMaterial } from './cleaner/rules'
|
import { getSkipReason, shouldDeleteMaterial } from './cleaner/rules'
|
||||||
import {
|
import {
|
||||||
clampNumber,
|
clampNumber,
|
||||||
@@ -46,6 +47,7 @@ export class CleanerService {
|
|||||||
private dryRun: boolean
|
private dryRun: boolean
|
||||||
private deletionVerifier: MaterialDeletionVerifier
|
private deletionVerifier: MaterialDeletionVerifier
|
||||||
private navigation: CleanerNavigation
|
private navigation: CleanerNavigation
|
||||||
|
private retryHandler: CleanerRetryHandler
|
||||||
|
|
||||||
constructor(authService: ErpAuthService, options: CleanerOptions = {}) {
|
constructor(authService: ErpAuthService, options: CleanerOptions = {}) {
|
||||||
this.authService = authService
|
this.authService = authService
|
||||||
@@ -54,6 +56,14 @@ export class CleanerService {
|
|||||||
this.navigation = new CleanerNavigation(log, (page, step, snapshotOptions) =>
|
this.navigation = new CleanerNavigation(log, (page, step, snapshotOptions) =>
|
||||||
this.logPageStateSnapshot(page, step, snapshotOptions)
|
this.logPageStateSnapshot(page, step, snapshotOptions)
|
||||||
)
|
)
|
||||||
|
this.retryHandler = new CleanerRetryHandler(log, {
|
||||||
|
queryOrders: (workFrame, popupPage, orderNumbers) =>
|
||||||
|
this.queryOrders(workFrame, popupPage, orderNumbers),
|
||||||
|
waitForLoading: (workFrame) => this.waitForLoading(workFrame),
|
||||||
|
openDetailPageFromCurrentQuery: (workFrame, popupPage, orderNumber) =>
|
||||||
|
this.openDetailPageFromCurrentQuery(workFrame, popupPage, orderNumber),
|
||||||
|
processDetailPage: (params) => this.processDetailPage(params)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1290,221 +1300,6 @@ export class CleanerService {
|
|||||||
extra?: Partial<import('../../types/cleaner.types').CleanerProgress>
|
extra?: Partial<import('../../types/cleaner.types').CleanerProgress>
|
||||||
) => void
|
) => void
|
||||||
}): Promise<RetryResult> {
|
}): Promise<RetryResult> {
|
||||||
const { workFrame, popupPage, failedDetails, deleteSet, dryRun, onProgress } = params
|
return this.retryHandler.retryFailedOrders(params)
|
||||||
|
|
||||||
const result: RetryResult = {
|
|
||||||
retriedOrders: 0,
|
|
||||||
successfulRetries: 0,
|
|
||||||
updatedDetails: []
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failedDetails.length === 0) {
|
|
||||||
log.info('[重试机制] 没有需要重试的订单', { checkedCount: failedDetails.length })
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
const retryStartTime = Date.now()
|
|
||||||
log.info('[重试机制开始] 准备重试失败的订单', {
|
|
||||||
totalFailures: failedDetails.length,
|
|
||||||
failedOrders: failedDetails
|
|
||||||
.map((d) => d.orderNumber)
|
|
||||||
.slice(0, 5)
|
|
||||||
.concat(failedDetails.length > 5 ? [`... (${failedDetails.length - 5} more)`] : [])
|
|
||||||
})
|
|
||||||
|
|
||||||
const MAX_RETRIES = 2
|
|
||||||
|
|
||||||
// Track overall retry process duration
|
|
||||||
const trackedResult = await trackDuration(
|
|
||||||
async () => {
|
|
||||||
const retryResult: RetryResult = {
|
|
||||||
retriedOrders: 0,
|
|
||||||
successfulRetries: 0,
|
|
||||||
updatedDetails: []
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let detailIndex = 0; detailIndex < failedDetails.length; detailIndex++) {
|
|
||||||
const failedDetail = failedDetails[detailIndex]
|
|
||||||
const orderNumber = failedDetail.orderNumber
|
|
||||||
const retryAttempts: import('../../types/cleaner.types').RetryAttempt[] = []
|
|
||||||
const retryLoopStartTime = Date.now()
|
|
||||||
|
|
||||||
log.info('[重试订单] 开始处理失败订单的重试', {
|
|
||||||
orderNumber,
|
|
||||||
failureReason: failedDetail.errors.join('; '),
|
|
||||||
index: detailIndex + 1,
|
|
||||||
total: failedDetails.length
|
|
||||||
})
|
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
||||||
const attemptStartTime = Date.now()
|
|
||||||
log.info('[重试尝试] 开始第 {attempt} 次重试', {
|
|
||||||
orderNumber,
|
|
||||||
attempt,
|
|
||||||
maxRetries: MAX_RETRIES,
|
|
||||||
elapsedMs: attemptStartTime - retryLoopStartTime,
|
|
||||||
interpolatedAttempt: attempt
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Step 1: Re-query order
|
|
||||||
log.debug('[重试查询] 重新查询订单', { orderNumber, attempt })
|
|
||||||
await this.queryOrders(workFrame, popupPage, [orderNumber])
|
|
||||||
await this.waitForLoading(workFrame)
|
|
||||||
log.debug('[重试查询完成] 查询加载完成', {
|
|
||||||
orderNumber,
|
|
||||||
attempt,
|
|
||||||
elapsedMs: Date.now() - attemptStartTime
|
|
||||||
})
|
|
||||||
|
|
||||||
// Step 2: Check query results
|
|
||||||
const rows = workFrame.locator('tbody tr')
|
|
||||||
const rowCount = await rows.count()
|
|
||||||
if (rowCount === 0) {
|
|
||||||
const errorMsg = '订单重试查询无结果'
|
|
||||||
log.error('[重试失败] 重试查询返回空结果', {
|
|
||||||
orderNumber,
|
|
||||||
attempt,
|
|
||||||
rowCount,
|
|
||||||
elapsedMs: Date.now() - attemptStartTime
|
|
||||||
})
|
|
||||||
throw new Error(errorMsg)
|
|
||||||
}
|
|
||||||
log.debug('[重试查询] 查询到 {rowCount} 行结果', { orderNumber, attempt, rowCount })
|
|
||||||
|
|
||||||
// Step 3: Open detail page
|
|
||||||
log.debug('[重试详情] 打开订单详情页', { orderNumber, attempt })
|
|
||||||
const detailPage = await this.openDetailPageFromCurrentQuery(
|
|
||||||
workFrame,
|
|
||||||
popupPage,
|
|
||||||
orderNumber
|
|
||||||
)
|
|
||||||
log.debug('[重试详情] 详情页已打开', {
|
|
||||||
orderNumber,
|
|
||||||
attempt,
|
|
||||||
elapsedMs: Date.now() - attemptStartTime
|
|
||||||
})
|
|
||||||
|
|
||||||
// Step 4: Process detail page
|
|
||||||
log.debug('[重试处理] 开始处理详情页', { orderNumber, attempt })
|
|
||||||
const retryDetail = await this.processDetailPage({
|
|
||||||
detailPage,
|
|
||||||
deleteSet,
|
|
||||||
dryRun,
|
|
||||||
expectedOrderNumber: orderNumber,
|
|
||||||
progressState: {
|
|
||||||
ordersStarted: detailIndex,
|
|
||||||
ordersCompleted: 0,
|
|
||||||
totalOrders: failedDetails.length
|
|
||||||
},
|
|
||||||
onProgress: (message, progress, extra) => {
|
|
||||||
onProgress?.(
|
|
||||||
`[重试 ${attempt}/${MAX_RETRIES}] ${message}`,
|
|
||||||
progress,
|
|
||||||
extra ? { ...extra, phase: 'processing' as const } : undefined
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
log.info('[重试处理完成] 详情页处理完毕', {
|
|
||||||
orderNumber,
|
|
||||||
attempt,
|
|
||||||
deleted: retryDetail.materialsDeleted,
|
|
||||||
skipped: retryDetail.materialsSkipped,
|
|
||||||
elapsedMs: Date.now() - attemptStartTime
|
|
||||||
})
|
|
||||||
|
|
||||||
// Success - update counters
|
|
||||||
retryResult.successfulRetries += 1
|
|
||||||
retryResult.updatedDetails.push({
|
|
||||||
...retryDetail,
|
|
||||||
retryCount: attempt,
|
|
||||||
retriedAt: Date.now(),
|
|
||||||
retrySuccess: true,
|
|
||||||
retryAttempts
|
|
||||||
})
|
|
||||||
retryResult.retriedOrders += 1
|
|
||||||
|
|
||||||
log.info('[重试成功] 订单重试成功', {
|
|
||||||
orderNumber,
|
|
||||||
attempt,
|
|
||||||
cumulativeSuccesses: retryResult.successfulRetries,
|
|
||||||
cumulativeRetried: retryResult.retriedOrders,
|
|
||||||
deletedMaterials: retryDetail.materialsDeleted,
|
|
||||||
elapsedMs: Date.now() - retryLoopStartTime
|
|
||||||
})
|
|
||||||
break
|
|
||||||
} catch (error) {
|
|
||||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
||||||
const attemptElapsed = Date.now() - attemptStartTime
|
|
||||||
|
|
||||||
log.warn('[重试失败] 当前重试尝试失败', {
|
|
||||||
orderNumber,
|
|
||||||
attempt,
|
|
||||||
maxRetries: MAX_RETRIES,
|
|
||||||
error: message,
|
|
||||||
elapsedMs: attemptElapsed,
|
|
||||||
remainingAttempts: MAX_RETRIES - attempt
|
|
||||||
})
|
|
||||||
|
|
||||||
retryAttempts.push({
|
|
||||||
attempt,
|
|
||||||
error: message,
|
|
||||||
timestamp: Date.now()
|
|
||||||
})
|
|
||||||
|
|
||||||
if (attempt === MAX_RETRIES) {
|
|
||||||
log.error('[重试彻底失败] 所有重试均已失败', {
|
|
||||||
orderNumber,
|
|
||||||
totalAttempts: MAX_RETRIES,
|
|
||||||
errors: retryAttempts.map((a) => a.error),
|
|
||||||
elapsedMs: Date.now() - retryLoopStartTime,
|
|
||||||
finalOutcome: 'exhausted_all_retries',
|
|
||||||
successRate: `${((retryResult.successfulRetries / (detailIndex + 1)) * 100).toFixed(1)}%`
|
|
||||||
})
|
|
||||||
|
|
||||||
retryResult.updatedDetails.push({
|
|
||||||
...failedDetail,
|
|
||||||
retryCount: MAX_RETRIES,
|
|
||||||
retryAttempts,
|
|
||||||
retriedAt: Date.now(),
|
|
||||||
retrySuccess: false
|
|
||||||
})
|
|
||||||
retryResult.retriedOrders += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalRetryTime = Date.now() - retryStartTime
|
|
||||||
const finalSuccessRate =
|
|
||||||
failedDetails.length > 0
|
|
||||||
? ((retryResult.successfulRetries / failedDetails.length) * 100).toFixed(1) + '%'
|
|
||||||
: 'N/A'
|
|
||||||
const avgTimePerRetry = totalRetryTime / (failedDetails.length || 1)
|
|
||||||
|
|
||||||
log.info('[重试机制完成] 所有重试订单处理完毕', {
|
|
||||||
totalRetryOrders: failedDetails.length,
|
|
||||||
successfulRetries: retryResult.successfulRetries,
|
|
||||||
failedRetries: failedDetails.length - retryResult.successfulRetries,
|
|
||||||
successRate: finalSuccessRate,
|
|
||||||
totalElapsedTimeMs: totalRetryTime,
|
|
||||||
avgTimePerRetry,
|
|
||||||
isSlow: totalRetryTime > 30000
|
|
||||||
})
|
|
||||||
|
|
||||||
return retryResult
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operationName: 'retry-failed-orders',
|
|
||||||
message: 'Retry failed orders',
|
|
||||||
slowThresholdMs: 5000,
|
|
||||||
context: {
|
|
||||||
totalRetryOrders: failedDetails.length,
|
|
||||||
dryRun
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return trackedResult.result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
260
src/main/services/erp/cleaner/retry-handler.ts
Normal file
260
src/main/services/erp/cleaner/retry-handler.ts
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
import type { FrameLocator, Page } from 'playwright'
|
||||||
|
import type { CleanerProgress, OrderCleanDetail, RetryAttempt } from '../../../types/cleaner.types'
|
||||||
|
import { trackDuration } from '../../logger'
|
||||||
|
import type { ProgressState, RetryResult } from './types'
|
||||||
|
|
||||||
|
type CleanerLogger = {
|
||||||
|
debug: (message: string, meta?: Record<string, unknown>) => void
|
||||||
|
info: (message: string, meta?: Record<string, unknown>) => void
|
||||||
|
warn: (message: string, meta?: Record<string, unknown>) => void
|
||||||
|
error: (message: string, meta?: Record<string, unknown>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CleanerRetryDependencies {
|
||||||
|
queryOrders: (workFrame: FrameLocator, popupPage: Page, orderNumbers: string[]) => Promise<void>
|
||||||
|
waitForLoading: (workFrame: FrameLocator) => Promise<void>
|
||||||
|
openDetailPageFromCurrentQuery: (
|
||||||
|
workFrame: FrameLocator,
|
||||||
|
popupPage: Page,
|
||||||
|
orderNumber?: string
|
||||||
|
) => Promise<Page>
|
||||||
|
processDetailPage: (params: {
|
||||||
|
detailPage: Page
|
||||||
|
deleteSet: Set<string>
|
||||||
|
dryRun: boolean
|
||||||
|
progressState: ProgressState
|
||||||
|
expectedOrderNumber?: string
|
||||||
|
onProgress?: (message: string, progress?: number, extra?: Partial<CleanerProgress>) => void
|
||||||
|
}) => Promise<OrderCleanDetail>
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CleanerRetryHandler {
|
||||||
|
private static readonly MAX_RETRIES = 2
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly log: CleanerLogger,
|
||||||
|
private readonly dependencies: CleanerRetryDependencies
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async retryFailedOrders(params: {
|
||||||
|
workFrame: FrameLocator
|
||||||
|
popupPage: Page
|
||||||
|
failedDetails: OrderCleanDetail[]
|
||||||
|
deleteSet: Set<string>
|
||||||
|
dryRun: boolean
|
||||||
|
onProgress?: (message: string, progress?: number, extra?: Partial<CleanerProgress>) => void
|
||||||
|
}): Promise<RetryResult> {
|
||||||
|
const { workFrame, popupPage, failedDetails, deleteSet, dryRun, onProgress } = params
|
||||||
|
|
||||||
|
const result: RetryResult = {
|
||||||
|
retriedOrders: 0,
|
||||||
|
successfulRetries: 0,
|
||||||
|
updatedDetails: []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failedDetails.length === 0) {
|
||||||
|
this.log.info('[重试机制] 没有需要重试的订单', { checkedCount: failedDetails.length })
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const retryStartTime = Date.now()
|
||||||
|
this.log.info('[重试机制开始] 准备重试失败的订单', {
|
||||||
|
totalFailures: failedDetails.length,
|
||||||
|
failedOrders: failedDetails
|
||||||
|
.map((detail) => detail.orderNumber)
|
||||||
|
.slice(0, 5)
|
||||||
|
.concat(failedDetails.length > 5 ? [`... (${failedDetails.length - 5} more)`] : [])
|
||||||
|
})
|
||||||
|
|
||||||
|
const trackedResult = await trackDuration(
|
||||||
|
async () => {
|
||||||
|
const retryResult: RetryResult = {
|
||||||
|
retriedOrders: 0,
|
||||||
|
successfulRetries: 0,
|
||||||
|
updatedDetails: []
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let detailIndex = 0; detailIndex < failedDetails.length; detailIndex++) {
|
||||||
|
const failedDetail = failedDetails[detailIndex]
|
||||||
|
const orderNumber = failedDetail.orderNumber
|
||||||
|
const retryAttempts: RetryAttempt[] = []
|
||||||
|
const retryLoopStartTime = Date.now()
|
||||||
|
|
||||||
|
this.log.info('[重试订单] 开始处理失败订单的重试', {
|
||||||
|
orderNumber,
|
||||||
|
failureReason: failedDetail.errors.join('; '),
|
||||||
|
index: detailIndex + 1,
|
||||||
|
total: failedDetails.length
|
||||||
|
})
|
||||||
|
|
||||||
|
for (let attempt = 1; attempt <= CleanerRetryHandler.MAX_RETRIES; attempt++) {
|
||||||
|
const attemptStartTime = Date.now()
|
||||||
|
this.log.info('[重试尝试] 开始第 {attempt} 次重试', {
|
||||||
|
orderNumber,
|
||||||
|
attempt,
|
||||||
|
maxRetries: CleanerRetryHandler.MAX_RETRIES,
|
||||||
|
elapsedMs: attemptStartTime - retryLoopStartTime,
|
||||||
|
interpolatedAttempt: attempt
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.log.debug('[重试查询] 重新查询订单', { orderNumber, attempt })
|
||||||
|
await this.dependencies.queryOrders(workFrame, popupPage, [orderNumber])
|
||||||
|
await this.dependencies.waitForLoading(workFrame)
|
||||||
|
this.log.debug('[重试查询完成] 查询加载完成', {
|
||||||
|
orderNumber,
|
||||||
|
attempt,
|
||||||
|
elapsedMs: Date.now() - attemptStartTime
|
||||||
|
})
|
||||||
|
|
||||||
|
const rows = workFrame.locator('tbody tr')
|
||||||
|
const rowCount = await rows.count()
|
||||||
|
if (rowCount === 0) {
|
||||||
|
const errorMsg = '订单重试查询无结果'
|
||||||
|
this.log.error('[重试失败] 重试查询返回空结果', {
|
||||||
|
orderNumber,
|
||||||
|
attempt,
|
||||||
|
rowCount,
|
||||||
|
elapsedMs: Date.now() - attemptStartTime
|
||||||
|
})
|
||||||
|
throw new Error(errorMsg)
|
||||||
|
}
|
||||||
|
this.log.debug('[重试查询] 查询到 {rowCount} 行结果', {
|
||||||
|
orderNumber,
|
||||||
|
attempt,
|
||||||
|
rowCount
|
||||||
|
})
|
||||||
|
|
||||||
|
this.log.debug('[重试详情] 打开订单详情页', { orderNumber, attempt })
|
||||||
|
const detailPage = await this.dependencies.openDetailPageFromCurrentQuery(
|
||||||
|
workFrame,
|
||||||
|
popupPage,
|
||||||
|
orderNumber
|
||||||
|
)
|
||||||
|
this.log.debug('[重试详情] 详情页已打开', {
|
||||||
|
orderNumber,
|
||||||
|
attempt,
|
||||||
|
elapsedMs: Date.now() - attemptStartTime
|
||||||
|
})
|
||||||
|
|
||||||
|
this.log.debug('[重试处理] 开始处理详情页', { orderNumber, attempt })
|
||||||
|
const retryDetail = await this.dependencies.processDetailPage({
|
||||||
|
detailPage,
|
||||||
|
deleteSet,
|
||||||
|
dryRun,
|
||||||
|
expectedOrderNumber: orderNumber,
|
||||||
|
progressState: {
|
||||||
|
ordersStarted: detailIndex,
|
||||||
|
ordersCompleted: 0,
|
||||||
|
totalOrders: failedDetails.length
|
||||||
|
},
|
||||||
|
onProgress: (message, progress, extra) => {
|
||||||
|
onProgress?.(
|
||||||
|
`[重试 ${attempt}/${CleanerRetryHandler.MAX_RETRIES}] ${message}`,
|
||||||
|
progress,
|
||||||
|
extra ? { ...extra, phase: 'processing' as const } : undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.log.info('[重试处理完成] 详情页处理完毕', {
|
||||||
|
orderNumber,
|
||||||
|
attempt,
|
||||||
|
deleted: retryDetail.materialsDeleted,
|
||||||
|
skipped: retryDetail.materialsSkipped,
|
||||||
|
elapsedMs: Date.now() - attemptStartTime
|
||||||
|
})
|
||||||
|
|
||||||
|
retryResult.successfulRetries += 1
|
||||||
|
retryResult.updatedDetails.push({
|
||||||
|
...retryDetail,
|
||||||
|
retryCount: attempt,
|
||||||
|
retriedAt: Date.now(),
|
||||||
|
retrySuccess: true,
|
||||||
|
retryAttempts
|
||||||
|
})
|
||||||
|
retryResult.retriedOrders += 1
|
||||||
|
|
||||||
|
this.log.info('[重试成功] 订单重试成功', {
|
||||||
|
orderNumber,
|
||||||
|
attempt,
|
||||||
|
cumulativeSuccesses: retryResult.successfulRetries,
|
||||||
|
cumulativeRetried: retryResult.retriedOrders,
|
||||||
|
deletedMaterials: retryDetail.materialsDeleted,
|
||||||
|
elapsedMs: Date.now() - retryLoopStartTime
|
||||||
|
})
|
||||||
|
break
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
const attemptElapsed = Date.now() - attemptStartTime
|
||||||
|
|
||||||
|
this.log.warn('[重试失败] 当前重试尝试失败', {
|
||||||
|
orderNumber,
|
||||||
|
attempt,
|
||||||
|
maxRetries: CleanerRetryHandler.MAX_RETRIES,
|
||||||
|
error: message,
|
||||||
|
elapsedMs: attemptElapsed,
|
||||||
|
remainingAttempts: CleanerRetryHandler.MAX_RETRIES - attempt
|
||||||
|
})
|
||||||
|
|
||||||
|
retryAttempts.push({
|
||||||
|
attempt,
|
||||||
|
error: message,
|
||||||
|
timestamp: Date.now()
|
||||||
|
})
|
||||||
|
|
||||||
|
if (attempt === CleanerRetryHandler.MAX_RETRIES) {
|
||||||
|
this.log.error('[重试彻底失败] 所有重试均已失败', {
|
||||||
|
orderNumber,
|
||||||
|
totalAttempts: CleanerRetryHandler.MAX_RETRIES,
|
||||||
|
errors: retryAttempts.map((retryAttempt) => retryAttempt.error),
|
||||||
|
elapsedMs: Date.now() - retryLoopStartTime,
|
||||||
|
finalOutcome: 'exhausted_all_retries',
|
||||||
|
successRate: `${((retryResult.successfulRetries / (detailIndex + 1)) * 100).toFixed(1)}%`
|
||||||
|
})
|
||||||
|
|
||||||
|
retryResult.updatedDetails.push({
|
||||||
|
...failedDetail,
|
||||||
|
retryCount: CleanerRetryHandler.MAX_RETRIES,
|
||||||
|
retryAttempts,
|
||||||
|
retriedAt: Date.now(),
|
||||||
|
retrySuccess: false
|
||||||
|
})
|
||||||
|
retryResult.retriedOrders += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalRetryTime = Date.now() - retryStartTime
|
||||||
|
const finalSuccessRate =
|
||||||
|
failedDetails.length > 0
|
||||||
|
? `${((retryResult.successfulRetries / failedDetails.length) * 100).toFixed(1)}%`
|
||||||
|
: 'N/A'
|
||||||
|
const avgTimePerRetry = totalRetryTime / (failedDetails.length || 1)
|
||||||
|
|
||||||
|
this.log.info('[重试机制完成] 所有重试订单处理完毕', {
|
||||||
|
totalRetryOrders: failedDetails.length,
|
||||||
|
successfulRetries: retryResult.successfulRetries,
|
||||||
|
failedRetries: failedDetails.length - retryResult.successfulRetries,
|
||||||
|
successRate: finalSuccessRate,
|
||||||
|
totalElapsedTimeMs: totalRetryTime,
|
||||||
|
avgTimePerRetry,
|
||||||
|
isSlow: totalRetryTime > 30000
|
||||||
|
})
|
||||||
|
|
||||||
|
return retryResult
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operationName: 'retry-failed-orders',
|
||||||
|
message: 'Retry failed orders',
|
||||||
|
slowThresholdMs: 5000,
|
||||||
|
context: {
|
||||||
|
totalRetryOrders: failedDetails.length,
|
||||||
|
dryRun
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return trackedResult.result
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user