Files
sam-api/app/Services/Stats/SystemStatService.php
권혁성 4d8dac1091 feat: sam_stat P2 도메인 + 통계 API + 대시보드 전환 (Phase 4)
- 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>
2026-01-29 21:56:53 +09:00

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;
}
}