addOption('file', null, InputOption::VALUE_REQUIRED, 'daily_push_*.log 文件路径'); $this->addOption('dry-run', null, InputOption::VALUE_NONE, '仅预检,不修改数据库'); } protected function execute(InputInterface $input, OutputInterface $output): int { $file = trim(strval($input->getOption('file') ?? '')); if ($file === '') { $output->writeln('必须提供 --file 日志文件路径'); return self::FAILURE; } $dryRun = boolval($input->getOption('dry-run')); try { $result = (new MallDailyPushLogReplay())->replay($file, $dryRun); } catch (Throwable $e) { $output->writeln('' . $e->getMessage() . ''); return self::FAILURE; } $output->writeln('DailyPush 日志回放' . ($dryRun ? '预检' : '完成') . ''); foreach ([ 'dry_run', 'log_file', 'backup_file', 'lines', 'members', 'unique_members', 'daily_created', 'daily_updated', 'daily_unchanged', 'assets_created', 'assets_updated', 'locked_points_delta', 'invalid_lines', 'failed_members', ] as $key) { $value = $result[$key] ?? ''; if (is_bool($value)) { $value = $value ? 'true' : 'false'; } $output->writeln($key . ': ' . strval($value)); } $errors = $result['errors'] ?? []; if (is_array($errors) && $errors !== []) { $output->writeln('错误明细(最多显示 100 条):'); foreach ($errors as $error) { $output->writeln('- ' . strval($error)); } } return intval($result['failed_members'] ?? 0) > 0 || intval($result['invalid_lines'] ?? 0) > 0 ? self::FAILURE : self::SUCCESS; } }