- 4.1: stat_project_monthly + ProjectStatService (건설/프로젝트 월간) - 4.2: stat_system_daily + SystemStatService (API/감사/FCM/파일/결재) - 4.3: stat_events, stat_snapshots + StatEventService + StatEventObserver - 4.4: StatController (summary/daily/monthly/alerts) + StatQueryService + FormRequest 3개 + routes/stats.php - 4.5: DashboardService sam_stat 우선 조회 + 원본 DB 폴백 패턴 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
136 lines
5.2 KiB
PHP
136 lines
5.2 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Stats;
|
|
|
|
use App\Models\Stats\Daily\StatSystemDaily;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class SystemStatService implements StatDomainServiceInterface
|
|
{
|
|
public function aggregateDaily(int $tenantId, Carbon $date): int
|
|
{
|
|
$dateStr = $date->format('Y-m-d');
|
|
|
|
// API 사용량
|
|
$apiStats = DB::connection('mysql')
|
|
->table('api_request_logs')
|
|
->where('tenant_id', $tenantId)
|
|
->whereDate('created_at', $dateStr)
|
|
->selectRaw('
|
|
COUNT(*) as request_count,
|
|
SUM(CASE WHEN response_status >= 400 THEN 1 ELSE 0 END) as error_count,
|
|
COALESCE(AVG(duration_ms), 0) as avg_response_ms
|
|
')
|
|
->first();
|
|
|
|
// 사용자 활동 (고유 사용자 수, 로그인 수)
|
|
$activeUsers = DB::connection('mysql')
|
|
->table('api_request_logs')
|
|
->where('tenant_id', $tenantId)
|
|
->whereDate('created_at', $dateStr)
|
|
->whereNotNull('user_id')
|
|
->distinct('user_id')
|
|
->count('user_id');
|
|
|
|
// personal_access_tokens에 tenant_id 없으므로 user_tenants 조인으로 로그인 수 집계
|
|
$loginCount = DB::connection('mysql')
|
|
->table('personal_access_tokens as pat')
|
|
->join('user_tenants as ut', function ($join) use ($tenantId) {
|
|
$join->on('pat.tokenable_id', '=', 'ut.user_id')
|
|
->where('pat.tokenable_type', '=', 'App\\Models\\Members\\User')
|
|
->where('ut.tenant_id', '=', $tenantId);
|
|
})
|
|
->whereDate('pat.created_at', $dateStr)
|
|
->count();
|
|
|
|
// 감사 로그
|
|
$auditStats = DB::connection('mysql')
|
|
->table('audit_logs')
|
|
->where('tenant_id', $tenantId)
|
|
->whereDate('created_at', $dateStr)
|
|
->selectRaw("
|
|
SUM(CASE WHEN action = 'created' THEN 1 ELSE 0 END) as create_count,
|
|
SUM(CASE WHEN action = 'updated' THEN 1 ELSE 0 END) as update_count,
|
|
SUM(CASE WHEN action = 'deleted' THEN 1 ELSE 0 END) as delete_count
|
|
")
|
|
->first();
|
|
|
|
// FCM 알림
|
|
$fcmStats = DB::connection('mysql')
|
|
->table('fcm_send_logs')
|
|
->where('tenant_id', $tenantId)
|
|
->whereDate('created_at', $dateStr)
|
|
->selectRaw('
|
|
COALESCE(SUM(success_count), 0) as sent_count,
|
|
COALESCE(SUM(failure_count), 0) as failed_count
|
|
')
|
|
->first();
|
|
|
|
// 파일 업로드
|
|
$fileStats = DB::connection('mysql')
|
|
->table('files')
|
|
->where('tenant_id', $tenantId)
|
|
->whereDate('created_at', $dateStr)
|
|
->selectRaw('
|
|
COUNT(*) as upload_count,
|
|
COALESCE(SUM(file_size), 0) / 1048576 as upload_size_mb
|
|
')
|
|
->first();
|
|
|
|
// 결재
|
|
$approvalSubmitted = DB::connection('mysql')
|
|
->table('approvals')
|
|
->where('tenant_id', $tenantId)
|
|
->whereDate('drafted_at', $dateStr)
|
|
->whereNull('deleted_at')
|
|
->count();
|
|
|
|
$approvalCompleted = DB::connection('mysql')
|
|
->table('approvals')
|
|
->where('tenant_id', $tenantId)
|
|
->whereDate('completed_at', $dateStr)
|
|
->whereNull('deleted_at')
|
|
->count();
|
|
|
|
$approvalAvgHours = DB::connection('mysql')
|
|
->table('approvals')
|
|
->where('tenant_id', $tenantId)
|
|
->whereDate('completed_at', $dateStr)
|
|
->whereNotNull('drafted_at')
|
|
->whereNotNull('completed_at')
|
|
->whereNull('deleted_at')
|
|
->selectRaw('AVG(TIMESTAMPDIFF(HOUR, drafted_at, completed_at)) as avg_hours')
|
|
->value('avg_hours');
|
|
|
|
StatSystemDaily::updateOrCreate(
|
|
['tenant_id' => $tenantId, 'stat_date' => $dateStr],
|
|
[
|
|
'api_request_count' => $apiStats->request_count ?? 0,
|
|
'api_error_count' => $apiStats->error_count ?? 0,
|
|
'api_avg_response_ms' => (int) ($apiStats->avg_response_ms ?? 0),
|
|
'active_user_count' => $activeUsers,
|
|
'login_count' => $loginCount,
|
|
'audit_create_count' => $auditStats->create_count ?? 0,
|
|
'audit_update_count' => $auditStats->update_count ?? 0,
|
|
'audit_delete_count' => $auditStats->delete_count ?? 0,
|
|
'fcm_sent_count' => $fcmStats->sent_count ?? 0,
|
|
'fcm_failed_count' => $fcmStats->failed_count ?? 0,
|
|
'file_upload_count' => $fileStats->upload_count ?? 0,
|
|
'file_upload_size_mb' => $fileStats->upload_size_mb ?? 0,
|
|
'approval_submitted_count' => $approvalSubmitted,
|
|
'approval_completed_count' => $approvalCompleted,
|
|
'approval_avg_hours' => (float) ($approvalAvgHours ?? 0),
|
|
]
|
|
);
|
|
|
|
return 1;
|
|
}
|
|
|
|
public function aggregateMonthly(int $tenantId, int $year, int $month): int
|
|
{
|
|
// 시스템 도메인은 일간 테이블만 운영
|
|
return 0;
|
|
}
|
|
}
|