feat(cleaner): organize videos by batch ID and rename by order number
- Video directory now includes batchId: logs/video/{batchId}/
- Each order's video is renamed to {orderNumber}.webm after the
detail page is closed
- Handles name collisions (retries) by appending timestamp
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -131,7 +131,7 @@ export class CleanerApplicationService {
|
||||
return emptyResult
|
||||
}
|
||||
|
||||
const videoDir = input.recordVideo ? this.ensureVideoDir() : undefined
|
||||
const videoDir = input.recordVideo ? this.ensureVideoDir(batchId) : undefined
|
||||
|
||||
authService = new ErpAuthService({
|
||||
url: erpConfig.url,
|
||||
@@ -178,7 +178,7 @@ export class CleanerApplicationService {
|
||||
processConcurrency: input.processConcurrency ?? 1
|
||||
})
|
||||
|
||||
let cleaner = new CleanerService(authService)
|
||||
let cleaner = new CleanerService(authService, { videoDir })
|
||||
let result = await cleaner.clean(modifiedInput)
|
||||
|
||||
// Outer retry: re-login and re-run all orders on fatal crash
|
||||
@@ -246,7 +246,7 @@ export class CleanerApplicationService {
|
||||
await historyDao.insertOrderRecords(batchId, 2, orderInputs)
|
||||
}
|
||||
|
||||
cleaner = new CleanerService(authService)
|
||||
cleaner = new CleanerService(authService, { videoDir })
|
||||
result = await cleaner.clean(modifiedInput)
|
||||
|
||||
// Save attempt 2 result
|
||||
@@ -342,8 +342,8 @@ export class CleanerApplicationService {
|
||||
}
|
||||
}
|
||||
|
||||
private ensureVideoDir(): string | undefined {
|
||||
const videoDir = path.join(getLogDir(), 'video')
|
||||
private ensureVideoDir(batchId: string): string {
|
||||
const videoDir = path.join(getLogDir(), 'video', batchId)
|
||||
if (!fs.existsSync(videoDir)) {
|
||||
fs.mkdirSync(videoDir, { recursive: true })
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import type {
|
||||
} from '../../types/cleaner.types'
|
||||
import type { ErpSession } from '../../types/erp.types'
|
||||
import type { FrameLocator, Locator, Page } from 'playwright'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import { createLogger, run, trackDuration } from '../logger'
|
||||
import { capturePageContext } from './erp-error-context'
|
||||
|
||||
@@ -108,6 +110,7 @@ class ConcurrencyTracker {
|
||||
export interface CleanerOptions {
|
||||
dryRun?: boolean
|
||||
verbose?: boolean
|
||||
videoDir?: string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,10 +179,12 @@ export async function runWithConcurrency<T, R>(
|
||||
export class CleanerService {
|
||||
private authService: ErpAuthService
|
||||
private dryRun: boolean
|
||||
private videoDir: string | undefined
|
||||
|
||||
constructor(authService: ErpAuthService, options: CleanerOptions = {}) {
|
||||
this.authService = authService
|
||||
this.dryRun = options.dryRun ?? false
|
||||
this.videoDir = options.videoDir
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1275,6 +1280,31 @@ export class CleanerService {
|
||||
log.debug('[详情页面清理] 准备关闭详情页面', { pageUrl: detailPage.url() })
|
||||
await detailPage.close()
|
||||
log.debug('[详情页面清理完成] 详情页已关闭')
|
||||
|
||||
// Rename video file to order number if video recording is enabled
|
||||
if (this.videoDir && expectedOrderNumber) {
|
||||
try {
|
||||
const video = detailPage.video()
|
||||
if (video) {
|
||||
const originalPath = await video.path()
|
||||
if (originalPath && fs.existsSync(originalPath)) {
|
||||
const ext = path.extname(originalPath) || '.webm'
|
||||
const orderVideoPath = path.join(this.videoDir, `${expectedOrderNumber}${ext}`)
|
||||
// Avoid overwriting existing file (e.g. retry)
|
||||
const finalPath = fs.existsSync(orderVideoPath)
|
||||
? path.join(this.videoDir, `${expectedOrderNumber}_${Date.now()}${ext}`)
|
||||
: orderVideoPath
|
||||
fs.renameSync(originalPath, finalPath)
|
||||
log.debug('[视频录制] 视频已重命名', { from: originalPath, to: finalPath })
|
||||
}
|
||||
}
|
||||
} catch (videoError) {
|
||||
log.warn('[视频录制] 重命名视频失败', {
|
||||
orderNumber: expectedOrderNumber,
|
||||
error: videoError instanceof Error ? videoError.message : String(videoError)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user