164 lines
5.2 KiB
PHP
164 lines
5.2 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Console\Commands;
|
||
|
|
|
||
|
|
use App\Models\Boards\File;
|
||
|
|
use App\Models\Tenants\Tenant;
|
||
|
|
use Illuminate\Console\Command;
|
||
|
|
use Illuminate\Support\Facades\DB;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 테넌트 저장소 사용량 재계산 명령어
|
||
|
|
*
|
||
|
|
* files 테이블의 실제 file_size 합계로 tenants.storage_used를 업데이트합니다.
|
||
|
|
*
|
||
|
|
* @example php artisan tenant:recalculate-storage
|
||
|
|
* @example php artisan tenant:recalculate-storage --tenant=1
|
||
|
|
* @example php artisan tenant:recalculate-storage --dry-run
|
||
|
|
*/
|
||
|
|
class RecalculateTenantStorageCommand extends Command
|
||
|
|
{
|
||
|
|
protected $signature = 'tenant:recalculate-storage
|
||
|
|
{--tenant= : 특정 테넌트 ID만 처리}
|
||
|
|
{--dry-run : 실제 업데이트 없이 결과만 확인}';
|
||
|
|
|
||
|
|
protected $description = '테넌트별 저장소 사용량을 files 테이블 기준으로 재계산합니다';
|
||
|
|
|
||
|
|
public function handle(): int
|
||
|
|
{
|
||
|
|
$tenantId = $this->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);
|
||
|
|
}
|
||
|
|
}
|