OWL ITS + 탐지시스템(인터넷 진흥원)
이민희
2022-03-07 aadc5ed13dc855b3e57b3e6178e36a347b324ec9
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
package kr.wisestone.owl.service.impl;
 
 
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*;
import com.amazonaws.services.s3.transfer.*;
import com.google.common.collect.Lists;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.domain.AttachedFile;
import kr.wisestone.owl.domain.Issue;
import kr.wisestone.owl.domain.Workspace;
import kr.wisestone.owl.domain.enumType.AttachedType;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.AttachedFileMapper;
import kr.wisestone.owl.repository.AttachedFileRepository;
import kr.wisestone.owl.service.AttachedFileService;
import kr.wisestone.owl.service.IssueService;
import kr.wisestone.owl.service.WorkspaceService;
import kr.wisestone.owl.util.CommonUtil;
import kr.wisestone.owl.util.ConvertUtil;
import kr.wisestone.owl.util.MapUtil;
import kr.wisestone.owl.util.WebAppUtil;
import kr.wisestone.owl.vo.AttachedFileVo;
import kr.wisestone.owl.vo.ExportExcelAttrVo;
import kr.wisestone.owl.vo.ExportExcelVo;
import kr.wisestone.owl.web.condition.AttachedFileCondition;
import kr.wisestone.owl.web.form.IssueForm;
import kr.wisestone.owl.web.view.ExcelView;
import kr.wisestone.owl.web.view.FileDownloadView;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import java.io.File;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
@Service
public class AttachedFileServiceImpl extends AbstractServiceImpl<AttachedFile, Long, JpaRepository<AttachedFile, Long>>
        implements AttachedFileService {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(AttachedFileServiceImpl.class);
 
    @Autowired
    private AttachedFileRepository attachedFileRepository;
 
    @Autowired
    private WorkspaceService workspaceService;
 
    @Autowired
    private IssueService issueService;
 
    @Autowired
    private AttachedFileMapper attachedFileMapper;
 
    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;
 
    @Value("${use.aws}")
    private boolean bUseAWS;
 
    @Value("${attached.file.path}")
    private String uploadFolder;
 
    @Value("${aws.bucket.name}")
    private String bucketName;
 
    @Value("${aws.s3.url}")
    private String awsS3Url;
 
    @Autowired
    private FileDownloadView fileDownloadView;
 
    @Autowired
    private AmazonS3 amazonS3;
 
    @Autowired
    private ExcelView excelView;
 
 
    @Override
    protected JpaRepository<AttachedFile, Long> getRepository() {
        return this.attachedFileRepository;
    }
 
    //  첨부 파일을 등록한다. - 이슈 섬머 노트에서 사용
    @Override
    @Transactional
    public List<AttachedFile> addAttachedFile(List<MultipartFile> multipartFiles, Map<String, Object> content) {
        Long workspaceId = MapUtil.getLong(content, "workspaceId");
        Long issueId = MapUtil.getLong(content, "issueId");
        Workspace workspace = this.workspaceService.getWorkspace(workspaceId);
        List<Map<String, Object>> convertFileMaps = Lists.newArrayList();
 
        for (MultipartFile multipartFile : multipartFiles) {
            convertFileMaps.add(CommonUtil.makeFileMap(multipartFile));
        }
 
        if (issueId != null) {
            Issue issue = this.issueService.getIssue(issueId);
            return this.addAttachedFiles(workspace, convertFileMaps, issue, null, AttachedType.SUMMER);
        }
        else {
            return this.addAttachedFiles(workspace, convertFileMaps, null, null, AttachedType.TEMP_SUMMER);
        }
    }
 
    //  첨부 파일을 등록한다. - API 에서 사용
    @Override
    @Transactional
    public List<AttachedFile> addAttachedFile(Workspace workspace, Issue issue, List<Map<String, Object>> files) {
        if (workspace == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.WORKSPACE_NOT_EXIST));
        }
 
        if (issue == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_EXIST));
        }
 
        if (files != null && files.size() > 0) {
            List<Map<String, Object>> convertFileMaps = Lists.newArrayList();
 
            for (Map<String, Object> file : files) {
                String fileName = MapUtil.getString(file, "fileName");
                String fileStr = MapUtil.getString(file, "file");
                String contentType = MapUtil.getString(file, "contentType");
                convertFileMaps.add(CommonUtil.makeFileMap(fileName, fileStr, contentType));
            }
 
            return this.addAttachedFiles(workspace, convertFileMaps, issue, null, AttachedType.SUMMER);
        }
        return null;
    }
 
    //  첨부 파일을 등록한다. - 이슈 생성, 수정에서 사용
    @Override
    @Transactional
    public void addAttachedFile(List<Map<String, Object>> convertFileMaps, Issue issue, String userAccount) {
        Workspace workspace = issue.getIssueStatus().getWorkspace();
 
        if (workspace == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.WORKSPACE_NOT_EXIST));
        }
 
        this.addAttachedFiles(workspace, convertFileMaps, issue, userAccount, AttachedType.ISSUE_ATTACHED);
    }
 
    private List<AttachedFile> addAttachedFiles(Workspace workspace, List<Map<String, Object>> convertFileMaps, Issue issue, String userAccount, AttachedType attachedType) {
        List<AttachedFile> attachedFiles = Lists.newArrayList();
        AttachedFileCondition attachedFileCondition = new AttachedFileCondition();
        attachedFileCondition.setWorkspaceId(workspace.getId());
 
        Long useStorageSize = this.attachedFileMapper.findUseStorage(attachedFileCondition);
 
        if (useStorageSize == null) {
            useStorageSize = 0L;
        }
 
        //  용량 및 파일 확장자 허용 여부 체크
        this.checkStorageSizeAndFileType(convertFileMaps, workspace.getStorageSize(), useStorageSize);
 
        int totalFileCount = convertFileMaps.size();    //  전체 업로드 파일 개수
        int uploadFileCount = 1;    //  현재 업로드 파일 순서
 
        for (Map<String, Object> convertFileMap : convertFileMaps) {
            //  파일 업로드 후 awsKey(파일 명)을 가져온다.
            String awsKey = this.uploadFile(convertFileMap, this.uploadFolder + workspace.getId(), userAccount, totalFileCount, uploadFileCount);
 
            attachedFiles.add(new AttachedFile(MapUtil.getString(convertFileMap, "fileName"), MapUtil.getLong(convertFileMap, "fileSize"), MapUtil.getString(convertFileMap, "contentType"),
                    this.setMakeFilePath(awsKey, workspace), awsKey, issue, workspace, CommonUtil.getFileType(MapUtil.getString(convertFileMap, "fileName")), attachedType));
 
            uploadFileCount++;
        }
 
        if (attachedFiles.size() > 0) {
            this.attachedFileRepository.saveAll(attachedFiles);
        }
 
        return attachedFiles;
    }
 
    //  용량 및 파일 확장자 허용 여부 체크
    private void checkStorageSizeAndFileType(List<Map<String, Object>> convertFileMaps, Long totalStorageSize, Long useStorageSize) {
        for (Map<String, Object> convertFileMap : convertFileMaps) {
            Long fileSize = MapUtil.getLong(convertFileMap, "fileSize");
            String fileName = MapUtil.getString(convertFileMap, "fileName");
 
            if (fileSize == null) {
                fileSize = 0L;
            }
 
            //  용량 초과 체크
            if (totalStorageSize < (useStorageSize + fileSize)) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.WORKSPACE_STORAGE_SIZE_EXCESS));
            }
 
            //  파일 확장자 체크
            if (!CommonUtil.checkFileType(fileName)) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.FILE_TYPE_NOT_ALLOW));
            }
        }
    }
 
    //  이슈 생성, 수정에서 섬머 노트로 업로드한 이미지와 이슈를 연결시킨다.
    @Override
    @Transactional
    public void connectIssueIdAttachedFile(Issue issue, IssueForm issueForm) {
        for (Long attachedFileId : issueForm.getAttachedFileIds()) {
            AttachedFile attachedFile = this.getAttachedFile(attachedFileId);
            attachedFile.setIssue(issue);
            attachedFile.setAttachedType(AttachedType.SUMMER);
            this.attachedFileRepository.save(attachedFile);
        }
 
        this.attachedFileRepository.flush();
    }
 
    //  이슈 목록을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<AttachedFileVo> findAttachedFile(Map<String, Object> resJsonData, AttachedFileCondition condition) {
        List<AttachedFileVo> attachedFileVos = Lists.newArrayList();
 
        for (AttachedFile attachedFile : this.findByIssueId(condition.getIssueId())) {
            AttachedFileVo attachedFileVo = ConvertUtil.copyProperties(attachedFile, AttachedFileVo.class, "fileType");
            attachedFileVo.setFileType(attachedFile.getFileType().toString());
            attachedFileVos.add(attachedFileVo);
        }
 
        resJsonData.put(Constants.RES_KEY_CONTENTS, attachedFileVos);
 
        return attachedFileVos;
    }
 
    //  이슈 아이디로 첨부 파일을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<AttachedFile> findByIssueId(Long issueId) {
        return this.attachedFileRepository.findByIssueId(issueId);
    }
 
    //  첨부 파일 아이디로 첨부 파일을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public AttachedFile getAttachedFile(Long attachedFileId) {
        AttachedFile attachedFile = this.findOne(attachedFileId);
 
        if (attachedFile == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ATTACHED_FILE_NOT_EXIST));
        }
 
        return attachedFile;
    }
 
    //  첨부파일 삭제
    @Override
    @Transactional
    public void removeAttachedFiles(List<Long> removeIds) {
        for (Long attachedId : removeIds) {
            AttachedFile attachedFile = this.getAttachedFile(attachedId);
 
            switch (attachedFile.getAttachedType()) {
                case SUMMER:
                    //  이슈와 첨부 파일 연결을 해제한다.
                    attachedFile.setIssue(null);
                    break;
                case ISSUE_ATTACHED:
                    //  첨부 파일을 삭제한다.
                    this.removeAttachedFiles(attachedFile);
                    break;
            }
        }
    }
 
    //  첨부 파일을 삭제한다.
    private void removeAttachedFiles(AttachedFile attachedFile) {
        //  파일을 삭제한다.
        this.removeFile(attachedFile.getAwsKey(), attachedFile.getWorkspace().getId());
 
        this.attachedFileRepository.delete(attachedFile);
    }
 
    //  업무 공간 삭제시 이슈에 첨부된 파일을 시스템에서 삭제한다.
    @Override
    @Transactional
    public void deleteWorkspaceCascadeAttachedFile(Workspace workspace) {
        List<Map<String, Object>> attachedFiles = this.attachedFileMapper.findByWorkspaceId(workspace.getId());
 
        for (Map<String, Object> attachedFile : attachedFiles) {
            //  파일을 삭제한다.
            this.removeFile(MapUtil.getString(attachedFile, "awsKey"), MapUtil.getLong(attachedFile, "workspaceId"));
        }
 
        //  첨부 파일 삭제
        this.attachedFileMapper.deleteAttachedFileByWorkspaceId(workspace.getId());
    }
 
    //  프로젝트 삭제시 이슈에 첨부된 파일을 시스템에서 삭제한다.
    @Override
    @Transactional
    public void deleteIssueCascadeAttachedFile(List<Long> issueIds, Workspace workspace) {
        //  이슈가 없을 경우에는 아래 로직을 타지 않는다. -> 모든 업무공간에 첨부파일이 삭제될 위험이 있음.
        if (issueIds.size() < 1) {
            return;
        }
 
        AttachedFileCondition attachedFileCondition = new AttachedFileCondition();
        attachedFileCondition.setIssueIds(issueIds);
        attachedFileCondition.setWorkspaceId(workspace.getId());
 
        List<Map<String, Object>> attachedFiles = this.attachedFileMapper.findByIssueIds(attachedFileCondition);
 
        for (Map<String, Object> attachedFile : attachedFiles) {
            //  파일을 삭제한다.
            this.removeFile(MapUtil.getString(attachedFile, "awsKey"), MapUtil.getLong(attachedFile, "workspaceId"));
        }
 
        //  첨부 파일 삭제
        this.attachedFileMapper.deleteAttachedFileByIssueIds(attachedFileCondition);
    }
 
 
    //  업무 공간에서 사용중인 저장 용량을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public Long findUseStorage(Workspace workspace) {
        AttachedFileCondition attachedFileCondition = new AttachedFileCondition();
        attachedFileCondition.setWorkspaceId(workspace.getId());
        return this.attachedFileMapper.findUseStorage(attachedFileCondition);
    }
 
    //  업무 공간에서 사용 트래픽을 체크하고 트래픽 초과시 다운로드를 막는다.
    @Override
    @Transactional
    public ModelAndView checkUseWorkspaceTraffic(Long id, Model model) {
        AttachedFile attachedFile = this.getAttachedFile(id);
        //  트래픽 사용량을 저장하고 초과할 경우에는 해당 업무 공간에서 다운로드를 일시적으로 금지한다.
        if (!this.workspaceService.checkUseTraffic(attachedFile.getSize())) {
            ExportExcelVo excelInfo = new ExportExcelVo();
            excelInfo.setFileName("해당 업무 공간에서 사용할 수 있는 트래픽이 초과되었습니다. 트래픽을 추가하려면 와이즈스톤 담당자에게 문의하세요. - supportowl@wisestone.kr");
            excelInfo.addAttrInfos(new ExportExcelAttrVo("name", "", 120, ExportExcelAttrVo.ALIGN_LEFT));
            model.addAttribute(Constants.EXCEL, excelInfo);
            return new ModelAndView(this.excelView);
        }
 
        ModelAndView objModelView = null;
 
        if( this.bUseAWS )
        {
            objModelView = downloadFileFromAWS(id, model);
        }
        else
        {
            objModelView = downloadFileFromLocal(id, model);
        }
 
        return objModelView;
    }
 
    private ModelAndView downloadFileFromLocal(Long id, Model model) {
        AttachedFile attachedFile = this.getAttachedFile(id);
 
        InputStream objectInputStream = CommonUtil.getFileInputStream(this.bucketName + this.uploadFolder + attachedFile.getWorkspace().getId(), attachedFile.getAwsKey());
 
        try {
            byte[] bytes = IOUtils.toByteArray(objectInputStream);
            AttachedFileVo attachedFileVo = ConvertUtil.copyProperties(attachedFile, AttachedFileVo.class);
            attachedFileVo.setBytes(bytes);
            model.addAttribute("fileDownloadTarget", attachedFileVo);
        } catch (Exception e) {
            LOGGER.error("첨부 파일 다운로드에 실패하였습니다.");
        }
 
        return new ModelAndView(this.fileDownloadView);
    }
 
    private ModelAndView downloadFileFromAWS(Long id, Model model) {
        AttachedFile attachedFile = this.getAttachedFile(id);
 
        GetObjectRequest getObjectRequest = new GetObjectRequest(this.bucketName + this.uploadFolder + attachedFile.getWorkspace().getId(), attachedFile.getAwsKey());
        S3Object s3Object = this.amazonS3.getObject(getObjectRequest);
        S3ObjectInputStream objectInputStream = s3Object.getObjectContent();
 
        try {
            byte[] bytes = IOUtils.toByteArray(objectInputStream);
            AttachedFileVo attachedFileVo = ConvertUtil.copyProperties(attachedFile, AttachedFileVo.class);
            attachedFileVo.setBytes(bytes);
            model.addAttribute("fileDownloadTarget", attachedFileVo);
        } catch (Exception e) {
            LOGGER.error("아마존 클라우드에서 첨부 파일 다운로드에 실패하였습니다.");
        }
 
        return new ModelAndView(this.fileDownloadView);
    }
 
    //  이슈와 연결되지 않은 첨부파일 삭제
    @Override
    @Transactional
    public void deleteAttachedFileNotId() {
        this.attachedFileMapper.deleteAttachedFileNotId();
    }
 
    // 파일을 업로드 한다.
    @Override
    @Transactional
    public String uploadFile(Map<String, Object> convertFileMap, String awsUploadFolder, String userAccount, int totalFileCount, int uploadFileCount) {
        String strKeyName = "";
 
        if( this.bUseAWS )
        {
            strKeyName = uploadFileToAws(convertFileMap, awsUploadFolder, userAccount, totalFileCount, uploadFileCount);
        }
        else
        {
            strKeyName = uploadFileToLocal(convertFileMap, awsUploadFolder, userAccount, totalFileCount, uploadFileCount);
        }
 
        return strKeyName;
    }
 
    // local storage로 이동한다.
    private String uploadFileToLocal(Map<String, Object> convertFileMap, String awsUploadFolder, String userAccount, int totalFileCount, int uploadFileCount) {
        String awsKeyName = CommonUtil.getFileNameByUUID(MapUtil.getString(convertFileMap, "fileName"));
        StopWatch serviceStart = new StopWatch();
        serviceStart.start();
        File file = (File) convertFileMap.get("file");
 
        try {
            //  이슈 생성, 수정에서 파일을 업로드할때는 비동기로 올리며 업로드 진행률을 표시해준다.
            if (!StringUtils.isEmpty(userAccount)) {
            }
 
            CommonUtil.moveToSaveStorage(this.bucketName + awsUploadFolder, awsKeyName, file);
 
            if (file.exists()) {
                file.delete();
            }
        } catch (Exception e) {
            LOGGER.error("파일 업로드 에러 :" + e.getMessage());
        }
 
        serviceStart.stop();
 
        return awsKeyName;
    }
 
    //  아마존 클라우드에 파일을 업로드한다.
    private String uploadFileToAws(Map<String, Object> convertFileMap, String awsUploadFolder, String userAccount, int totalFileCount, int uploadFileCount) {
        String awsKeyName = CommonUtil.getFileNameByUUID(MapUtil.getString(convertFileMap, "fileName"));
        StopWatch serviceStart = new StopWatch();
        serviceStart.start();
        File file = (File) convertFileMap.get("file");
 
        TransferManager transferManager = TransferManagerBuilder
                .standard()
                .withS3Client(this.amazonS3)
                /*.withMultipartUploadThreshold((long) 5*1024*1024)*/
                /*.withExecutorFactory(() -> Executors.newFixedThreadPool(20))*/
                .build();
 
        /*TransferManager transferManager = TransferManagerBuilder
                .standard()
                .withS3Client(this.amazonS3)
                .withDisableParallelDownloads(false)
                .withMinimumUploadPartSize((long)(5 * MB))
                .withMultipartUploadThreshold((long)(16 * MB))
                .withMultipartCopyPartSize((long)(5 * MB))
                .withMultipartCopyThreshold((long)(100 * MB))
                .withExecutorFactory(() -> Executors.newFixedThreadPool(20))
                .build();*/
 
        try {
            PutObjectRequest putObjectRequest =
                    new PutObjectRequest(this.bucketName + awsUploadFolder, awsKeyName, file);
            putObjectRequest.setCannedAcl(CannedAccessControlList.PublicRead); // file permission
            //this.amazonS3.putObject(putObjectRequest); // upload file
 
            long fileSize = MapUtil.getLong(convertFileMap, "fileSize");
            String fileName = MapUtil.getString(convertFileMap, "fileName");
 
            //  이슈 생성, 수정에서 파일을 업로드할때는 비동기로 올리며 업로드 진행률을 표시해준다.
            if (!StringUtils.isEmpty(userAccount)) {
                com.amazonaws.event.ProgressListener progressListener = new com.amazonaws.event.ProgressListener() {
                    long bytesUploaded = 0;
 
                    SimpMessagingTemplate webSocket = simpMessagingTemplate;
 
                    @Override
                    public void progressChanged(com.amazonaws.event.ProgressEvent progressEvent) {
                        this.bytesUploaded += progressEvent.getBytesTransferred();// add counter
                        double uploadProcess = this.bytesUploaded * 100.0 / fileSize;
                        String percent = new DecimalFormat("###").format(uploadProcess);
                        Map<String, Object> fileMap = new HashMap<>();
                        fileMap.put("display", (uploadProcess < 100));
                        fileMap.put("serverFileName", fileName);
                        fileMap.put("serverProgress", percent + "%");
                        fileMap.put("totalFileCount", totalFileCount);
                        fileMap.put("uploadFileCount", uploadFileCount);
 
                        this.webSocket.convertAndSendToUser(userAccount, "/notification/file-upload-process", fileMap);
                    }
                };
 
                putObjectRequest.setGeneralProgressListener(progressListener);
            }
 
            Upload upload = transferManager.upload(putObjectRequest);
            upload.waitForCompletion();
            transferManager.shutdownNow(false);
 
            if (file.exists()) {
                file.delete();
            }
        } catch (Exception e) {
            LOGGER.error("파일 업로드 에러 :" + e.getMessage());
        }
 
        serviceStart.stop();
 
        return awsKeyName;
    }
 
    //  파일을 삭제한다.
    @Override
    @Transactional
    public void removeFile(String key, Long workspaceId) {
        try {
            if( this.bUseAWS )
            {
                removeFileToAws(key, workspaceId);
            }
            else
            {
                removeFileToLocal(key, workspaceId);
            }
        } catch (Exception e) {
            LOGGER.error("파일 삭제 에러 :" + e.getMessage());
        }
    }
 
    //  파일을 삭제한다.
    private void removeFileToLocal(String key, Long workspaceId) {
        try {
            if(workspaceId > 0 )
            {
                CommonUtil.deleteToSaveStorage(this.bucketName + this.uploadFolder, key);
            }
            else
            {
                CommonUtil.deleteToSaveStorage(this.bucketName + this.uploadFolder + workspaceId, key);
            }
        } catch (Exception e) {
            LOGGER.error("파일 삭제 에러 :" + e.getMessage());
        }
    }
 
    //  아마존클라우드에서 파일을 삭제한다.
    private void removeFileToAws(String key, Long workspaceId) {
        try {
            if(workspaceId > 0 )
            {
                this.amazonS3.deleteObject(this.bucketName + this.uploadFolder, key);
            }
            else
            {
                this.amazonS3.deleteObject(this.bucketName + this.uploadFolder + workspaceId, key);
            }
        } catch (Exception e) {
            LOGGER.error("파일 삭제 에러 :" + e.getMessage());
        }
    }
 
    //  업로드되는 전체 경로를 가져온다.
    private String setMakeFilePath(String path, Workspace workspace) {
        String strFilePath = "";
 
        if( this.bUseAWS )
        {
            strFilePath = setMakeAwsFilePath(path, workspace);
        }
        else
        {
            strFilePath = setMakeLocalFilePath(path, workspace);
        }
 
        return strFilePath;
    }
 
    //  업로드되는 전체 경로를 가져온다.
    private String setMakeLocalFilePath(String path, Workspace workspace) {
        return this.awsS3Url + this.bucketName + this.uploadFolder + workspace.getId() + "/" + path;
    }
 
    //  아마존 클라우드에 업로드되는 전체 경로를 가져온다.
    private String setMakeAwsFilePath(String path, Workspace workspace) {
        return this.awsS3Url + this.bucketName + this.uploadFolder + workspace.getId() + "/" + path;
    }
}