1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
/**
* XMLHttpRequest를 사용하여 파일 업로드 진행도 추적
*/
export interface UploadProgressCallback {
onProgress: (fileIndex: number, progress: number) => void;
onFileComplete: (fileIndex: number) => void;
onFileError: (fileIndex: number, error: string) => void;
}
export interface UploadFilesWithProgressOptions {
uploadId: string;
userId: string;
files: File[];
callbacks: UploadProgressCallback;
}
export interface UploadResult {
success: boolean;
uploadedCount?: number;
error?: string;
}
/**
* 진행도 추적을 지원하는 파일 업로드 함수
*/
export async function uploadFilesWithProgress({
uploadId,
userId,
files,
callbacks,
}: UploadFilesWithProgressOptions): Promise<UploadResult> {
return new Promise((resolve) => {
const formData = new FormData();
formData.append("uploadId", uploadId);
formData.append("userId", userId);
formData.append("fileCount", String(files.length));
files.forEach((file, index) => {
formData.append(`file_${index}`, file);
});
const xhr = new XMLHttpRequest();
// 전체 업로드 진행도 (단순화: 전체 진행도를 각 파일에 분배)
xhr.upload.addEventListener("progress", (event) => {
if (event.lengthComputable) {
const totalProgress = (event.loaded / event.total) * 100;
// 현재 업로드 중인 파일 인덱스 추정
const filesCompleted = Math.floor((totalProgress / 100) * files.length);
const currentFileIndex = Math.min(filesCompleted, files.length - 1);
// 각 파일별 진행도 계산
files.forEach((_, index) => {
if (index < filesCompleted) {
callbacks.onProgress(index, 100);
callbacks.onFileComplete(index);
} else if (index === currentFileIndex) {
const fileProgress = ((totalProgress / 100) * files.length - filesCompleted) * 100;
callbacks.onProgress(index, Math.min(fileProgress, 99));
} else {
callbacks.onProgress(index, 0);
}
});
}
});
xhr.addEventListener("load", () => {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const response = JSON.parse(xhr.responseText);
// 모든 파일 완료 처리
files.forEach((_, index) => {
callbacks.onProgress(index, 100);
callbacks.onFileComplete(index);
});
resolve(response);
} catch (error) {
const errorMsg = "응답 파싱 실패";
files.forEach((_, index) => {
callbacks.onFileError(index, errorMsg);
});
resolve({
success: false,
error: errorMsg,
});
}
} else {
const errorMsg = `업로드 실패: ${xhr.status} ${xhr.statusText}`;
files.forEach((_, index) => {
callbacks.onFileError(index, errorMsg);
});
resolve({
success: false,
error: errorMsg,
});
}
});
xhr.addEventListener("error", () => {
const errorMsg = "네트워크 오류";
files.forEach((_, index) => {
callbacks.onFileError(index, errorMsg);
});
resolve({
success: false,
error: errorMsg,
});
});
xhr.addEventListener("abort", () => {
const errorMsg = "업로드가 취소되었습니다";
files.forEach((_, index) => {
callbacks.onFileError(index, errorMsg);
});
resolve({
success: false,
error: errorMsg,
});
});
xhr.open("POST", "/api/dolce/upload-files");
xhr.send(formData);
});
}
|