@restnfeel/agentc-starter-kit
Version:
한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템
423 lines (371 loc) • 11.5 kB
text/typescript
interface MediaAnalytics {
totalFiles: number;
totalSize: number;
fileTypes: Record<string, number>;
uploadTrends: {
date: string;
uploads: number;
size: number;
}[];
popularFiles: {
id: string;
fileName: string;
views: number;
downloads: number;
}[];
storageUsage: {
local: number;
cdn: number;
total: number;
};
performanceMetrics: {
averageUploadTime: number;
averageProcessingTime: number;
successRate: number;
};
}
interface MediaUsageReport {
period: "daily" | "weekly" | "monthly";
startDate: string;
endDate: string;
analytics: MediaAnalytics;
insights: string[];
recommendations: string[];
}
class MediaAnalyticsService {
private analytics: MediaAnalytics | null = null;
private lastUpdate: Date | null = null;
private cache: Map<string, any> = new Map();
/**
* 미디어 분석 데이터 수집
*/
async collectAnalytics(): Promise<MediaAnalytics> {
try {
// 기본 통계 수집
const totalFiles = await this.getTotalFileCount();
const totalSize = await this.getTotalStorageSize();
const fileTypes = await this.getFileTypeDistribution();
const uploadTrends = await this.getUploadTrends();
const popularFiles = await this.getPopularFiles();
const storageUsage = await this.getStorageUsage();
const performanceMetrics = await this.getPerformanceMetrics();
this.analytics = {
totalFiles,
totalSize,
fileTypes,
uploadTrends,
popularFiles,
storageUsage,
performanceMetrics,
};
this.lastUpdate = new Date();
return this.analytics;
} catch (error) {
console.error("Failed to collect analytics:", error);
throw new Error("분석 데이터 수집에 실패했습니다.");
}
}
/**
* 총 파일 수 조회
*/
private async getTotalFileCount(): Promise<number> {
// 실제 구현에서는 데이터베이스 쿼리
// Mock 데이터 반환
return 1247;
}
/**
* 총 저장소 크기 조회 (bytes)
*/
private async getTotalStorageSize(): Promise<number> {
// 실제 구현에서는 데이터베이스 쿼리
// Mock 데이터 반환
return 5.2 * 1024 * 1024 * 1024; // 5.2GB
}
/**
* 파일 타입별 분포 조회
*/
private async getFileTypeDistribution(): Promise<Record<string, number>> {
// 실제 구현에서는 데이터베이스 쿼리
// Mock 데이터 반환
return {
"image/jpeg": 523,
"image/png": 387,
"image/webp": 215,
"image/gif": 89,
"video/mp4": 23,
"video/webm": 8,
"application/pdf": 2,
};
}
/**
* 업로드 트렌드 조회 (최근 30일)
*/
private async getUploadTrends(): Promise<
{ date: string; uploads: number; size: number }[]
> {
// 실제 구현에서는 데이터베이스 쿼리
// Mock 데이터 생성
const trends = [];
const now = new Date();
for (let i = 29; i >= 0; i--) {
const date = new Date(now);
date.setDate(date.getDate() - i);
trends.push({
date: date.toISOString().split("T")[0],
uploads: Math.floor(Math.random() * 50) + 10,
size: Math.floor(Math.random() * 100 * 1024 * 1024) + 50 * 1024 * 1024, // 50-150MB
});
}
return trends;
}
/**
* 인기 파일 조회
*/
private async getPopularFiles(): Promise<
{ id: string; fileName: string; views: number; downloads: number }[]
> {
// 실제 구현에서는 데이터베이스 쿼리
// Mock 데이터 반환
return [
{ id: "1", fileName: "hero-banner.jpg", views: 1523, downloads: 89 },
{ id: "2", fileName: "product-image.png", views: 1247, downloads: 67 },
{ id: "3", fileName: "logo-transparent.png", views: 998, downloads: 234 },
{ id: "4", fileName: "demo-video.mp4", views: 756, downloads: 45 },
{ id: "5", fileName: "screenshot.jpg", views: 623, downloads: 23 },
];
}
/**
* 저장소 사용량 조회
*/
private async getStorageUsage(): Promise<{
local: number;
cdn: number;
total: number;
}> {
// 실제 구현에서는 실제 저장소 사용량 조회
// Mock 데이터 반환
const local = 2.1 * 1024 * 1024 * 1024; // 2.1GB
const cdn = 3.1 * 1024 * 1024 * 1024; // 3.1GB
return {
local,
cdn,
total: local + cdn,
};
}
/**
* 성능 지표 조회
*/
private async getPerformanceMetrics(): Promise<{
averageUploadTime: number;
averageProcessingTime: number;
successRate: number;
}> {
// 실제 구현에서는 성능 로그 분석
// Mock 데이터 반환
return {
averageUploadTime: 2.3, // seconds
averageProcessingTime: 4.7, // seconds
successRate: 98.5, // percentage
};
}
/**
* 사용량 보고서 생성
*/
async generateUsageReport(
period: "daily" | "weekly" | "monthly" = "monthly"
): Promise<MediaUsageReport> {
const analytics = await this.collectAnalytics();
const { startDate, endDate } = this.getPeriodDates(period);
const insights = this.generateInsights(analytics);
const recommendations = this.generateRecommendations(analytics);
return {
period,
startDate,
endDate,
analytics,
insights,
recommendations,
};
}
/**
* 기간별 날짜 계산
*/
private getPeriodDates(period: "daily" | "weekly" | "monthly"): {
startDate: string;
endDate: string;
} {
const endDate = new Date();
const startDate = new Date();
switch (period) {
case "daily":
startDate.setDate(startDate.getDate() - 1);
break;
case "weekly":
startDate.setDate(startDate.getDate() - 7);
break;
case "monthly":
startDate.setMonth(startDate.getMonth() - 1);
break;
}
return {
startDate: startDate.toISOString().split("T")[0],
endDate: endDate.toISOString().split("T")[0],
};
}
/**
* 인사이트 생성
*/
private generateInsights(analytics: MediaAnalytics): string[] {
const insights: string[] = [];
// 파일 타입 분석
const totalFiles = analytics.totalFiles;
const imageFiles = Object.entries(analytics.fileTypes)
.filter(([type]) => type.startsWith("image/"))
.reduce((sum, [, count]) => sum + count, 0);
const imagePercentage = ((imageFiles / totalFiles) * 100).toFixed(1);
insights.push(`이미지 파일이 전체의 ${imagePercentage}%를 차지합니다.`);
// 저장소 사용량 분석
const { local, cdn, total } = analytics.storageUsage;
const cdnPercentage = ((cdn / total) * 100).toFixed(1);
insights.push(`CDN 저장소 사용률이 ${cdnPercentage}%입니다.`);
// 성능 분석
if (analytics.performanceMetrics.successRate < 95) {
insights.push(
`업로드 성공률이 ${analytics.performanceMetrics.successRate}%로 개선이 필요합니다.`
);
}
// 트렌드 분석
const recentTrend = analytics.uploadTrends.slice(-7);
const avgRecentUploads =
recentTrend.reduce((sum, day) => sum + day.uploads, 0) / 7;
const earlierTrend = analytics.uploadTrends.slice(-14, -7);
const avgEarlierUploads =
earlierTrend.reduce((sum, day) => sum + day.uploads, 0) / 7;
const trendChange = (
((avgRecentUploads - avgEarlierUploads) / avgEarlierUploads) *
100
).toFixed(1);
if (parseFloat(trendChange) > 0) {
insights.push(
`최근 일주일 업로드가 이전 주 대비 ${trendChange}% 증가했습니다.`
);
} else {
insights.push(
`최근 일주일 업로드가 이전 주 대비 ${Math.abs(
parseFloat(trendChange)
)}% 감소했습니다.`
);
}
return insights;
}
/**
* 권장사항 생성
*/
private generateRecommendations(analytics: MediaAnalytics): string[] {
const recommendations: string[] = [];
// 저장소 최적화
const storageUsage = analytics.storageUsage;
const storageUsagePercentage =
(storageUsage.total / (10 * 1024 * 1024 * 1024)) * 100; // 10GB 한도 가정
if (storageUsagePercentage > 80) {
recommendations.push(
"저장소 사용량이 80%를 초과했습니다. 불필요한 파일 정리를 권장합니다."
);
}
// 파일 형식 최적화
const jpegCount = analytics.fileTypes["image/jpeg"] || 0;
const webpCount = analytics.fileTypes["image/webp"] || 0;
if (jpegCount > webpCount * 2) {
recommendations.push(
"WebP 형식 사용을 늘려 파일 크기를 줄이고 로딩 속도를 개선하세요."
);
}
// 성능 최적화
if (analytics.performanceMetrics.averageUploadTime > 5) {
recommendations.push(
"평균 업로드 시간이 길어졌습니다. 네트워크 연결이나 서버 성능을 확인하세요."
);
}
// CDN 활용
const cdnUsagePercentage = (storageUsage.cdn / storageUsage.total) * 100;
if (cdnUsagePercentage < 70) {
recommendations.push(
"CDN 사용률을 늘려 전 세계 사용자의 로딩 속도를 개선하세요."
);
}
// 보안 권장사항
recommendations.push(
"정기적으로 미사용 파일을 정리하고 접근 권한을 검토하세요."
);
return recommendations;
}
/**
* 실시간 통계 조회
*/
async getRealTimeStats(): Promise<{
activeUploads: number;
processingJobs: number;
recentUploads: number;
errorRate: number;
}> {
// 실제 구현에서는 실시간 모니터링 데이터 조회
// Mock 데이터 반환
return {
activeUploads: 3,
processingJobs: 7,
recentUploads: 45, // 최근 1시간
errorRate: 1.2, // percentage
};
}
/**
* 캐시된 분석 데이터 조회
*/
getCachedAnalytics(): MediaAnalytics | null {
const cacheExpiry = 30 * 60 * 1000; // 30분
if (
this.lastUpdate &&
Date.now() - this.lastUpdate.getTime() < cacheExpiry
) {
return this.analytics;
}
return null;
}
/**
* 분석 데이터 내보내기
*/
async exportAnalytics(format: "json" | "csv" = "json"): Promise<string> {
const analytics = await this.collectAnalytics();
if (format === "csv") {
return this.convertToCSV(analytics);
}
return JSON.stringify(analytics, null, 2);
}
/**
* CSV 형식으로 변환
*/
private convertToCSV(analytics: MediaAnalytics): string {
const headers = ["Metric", "Value"];
const rows = [
["Total Files", analytics.totalFiles.toString()],
[
"Total Size (GB)",
(analytics.totalSize / (1024 * 1024 * 1024)).toFixed(2),
],
["Success Rate (%)", analytics.performanceMetrics.successRate.toString()],
[
"Average Upload Time (s)",
analytics.performanceMetrics.averageUploadTime.toString(),
],
];
return [headers, ...rows].map((row) => row.join(",")).join("\n");
}
}
// 싱글톤 인스턴스 생성
let analyticsServiceInstance: MediaAnalyticsService | null = null;
export function getMediaAnalyticsService(): MediaAnalyticsService {
if (!analyticsServiceInstance) {
analyticsServiceInstance = new MediaAnalyticsService();
}
return analyticsServiceInstance;
}
export type { MediaAnalytics, MediaUsageReport };