option('tenant'); $dryRun = $this->option('dry-run'); $this->info(''); $this->info('==========================================='); $this->info(' 테넌트 저장소 사용량 재계산'); $this->info('==========================================='); if ($dryRun) { $this->warn(' [DRY-RUN 모드] 실제 업데이트가 수행되지 않습니다.'); } $this->info(''); // 테넌트별 실제 파일 사용량 계산 $query = File::query() ->select('tenant_id', DB::raw('SUM(file_size) as total_size'), DB::raw('COUNT(*) as file_count')) ->whereNotNull('tenant_id') ->groupBy('tenant_id'); if ($tenantId) { $query->where('tenant_id', $tenantId); } $actualUsage = $query->get()->keyBy('tenant_id'); // 모든 테넌트 조회 $tenantsQuery = Tenant::query(); if ($tenantId) { $tenantsQuery->where('id', $tenantId); } $tenants = $tenantsQuery->get(); if ($tenants->isEmpty()) { $this->warn('처리할 테넌트가 없습니다.'); return Command::SUCCESS; } $results = []; $updated = 0; $errors = 0; foreach ($tenants as $tenant) { $currentStored = $tenant->storage_used ?? 0; $actualData = $actualUsage->get($tenant->id); $actualSize = $actualData?->total_size ?? 0; $fileCount = $actualData?->file_count ?? 0; $difference = $actualSize - $currentStored; $status = '✅ 정상'; if ($difference !== 0) { $status = $difference > 0 ? '⚠️ 부족' : '⚠️ 초과'; } $results[] = [ $tenant->id, $tenant->company_name ?? 'N/A', $fileCount, $this->formatBytes($currentStored), $this->formatBytes($actualSize), $this->formatDifference($difference), $status, ]; // 업데이트 필요 시 if ($difference !== 0 && ! $dryRun) { try { $tenant->storage_used = $actualSize; $tenant->save(); $updated++; } catch (\Exception $e) { $this->error("테넌트 {$tenant->id} 업데이트 실패: {$e->getMessage()}"); $errors++; } } } // 결과 테이블 출력 $this->table( ['ID', '회사명', '파일수', '저장된 값', '실제 값', '차이', '상태'], $results ); $this->info(''); $this->info('==========================================='); $this->info(" 총 테넌트: {$tenants->count()}개"); if ($dryRun) { $needsUpdate = collect($results)->filter(fn ($r) => $r[6] !== '✅ 정상')->count(); $this->warn(" 업데이트 필요: {$needsUpdate}개"); $this->info(' [DRY-RUN] 실제 업데이트를 수행하려면 --dry-run 옵션을 제거하세요.'); } else { $this->info(" 업데이트됨: {$updated}개"); if ($errors > 0) { $this->error(" 실패: {$errors}개"); } } $this->info('==========================================='); $this->info(''); return $errors > 0 ? Command::FAILURE : Command::SUCCESS; } /** * 바이트를 읽기 쉬운 형식으로 변환 */ private function formatBytes(int $bytes): string { if ($bytes >= 1073741824) { return number_format($bytes / 1073741824, 2).' GB'; } elseif ($bytes >= 1048576) { return number_format($bytes / 1048576, 2).' MB'; } elseif ($bytes >= 1024) { return number_format($bytes / 1024, 2).' KB'; } return number_format($bytes).' B'; } /** * 차이값 포맷팅 (양수/음수 표시) */ private function formatDifference(int $diff): string { if ($diff === 0) { return '0'; } $prefix = $diff > 0 ? '+' : ''; $absBytes = abs($diff); return $prefix.$this->formatBytes($absBytes); } }