From 6d65f49f2c6843591ac518b876c7410743e40f3e Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Fri, 17 Apr 2026 14:34:12 +0800 Subject: [PATCH] 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 --- .../cleaner/cleaner-application-service.ts | 10 +++---- src/main/services/erp/cleaner.ts | 30 +++++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/main/services/cleaner/cleaner-application-service.ts b/src/main/services/cleaner/cleaner-application-service.ts index 5eba88d..478f2e4 100644 --- a/src/main/services/cleaner/cleaner-application-service.ts +++ b/src/main/services/cleaner/cleaner-application-service.ts @@ -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 }) } diff --git a/src/main/services/erp/cleaner.ts b/src/main/services/erp/cleaner.ts index c99dcd3..54d84de 100644 --- a/src/main/services/erp/cleaner.ts +++ b/src/main/services/erp/cleaner.ts @@ -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( 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) + }) + } + } } }