Skip to content

Commit

Permalink
feat: Apply asynchronous processing for uploading and image merging
Browse files Browse the repository at this point in the history
  • Loading branch information
minjoon-98 committed Jul 24, 2024
1 parent cf1f455 commit 50a16eb
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 29 deletions.
42 changes: 18 additions & 24 deletions src/main/java/ongjong/namanmoo/controller/ChallengeController.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import ongjong.namanmoo.dto.openAI.WhisperTranscriptionResponse;
import ongjong.namanmoo.service.*;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -23,6 +24,7 @@

import java.util.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -294,32 +296,24 @@ public ApiResponse<Map<String, String>> saveFaceTimeAnswer(
}
Lucky lucky = luckyService.findCurrentLucky(family.getFamilyId());

FileType fileType;
if (Objects.requireNonNull(answerFile.getContentType()).startsWith("image/")) {
fileType = FileType.IMAGE;
Map<String, String> response = sharedFileService.uploadImageFile(challenge, answerFile, fileType);
// TODO: 방법 2: 병합을 서버 측에서 스케줄링 (첫번째 cut만 생성됨)
// 이미지 업로드가 완료된 후에 병합을 예약합니다.
sharedFileService.scheduleMergeImages(challenge.getChallengeNum(), lucky);
return new ApiResponse<>("200", response.get("message"), response);
FileType fileType = FileType.IMAGE;
CompletableFuture<Map<String, String>> uploadFuture = sharedFileService.uploadImageFileAsync(challenge, answerFile, fileType);
uploadFuture.thenRun(() -> sharedFileService.scheduleMergeImagesAsync(challenge.getChallengeNum(), lucky));
return new ApiResponse<>("200", "Image upload scheduled successfully", null);
} else if (answerFile.getContentType().startsWith("video/")) {
fileType = FileType.VIDEO;

// S3에 파일 업로드 및 URL 저장
String uploadedUrl = awsS3Service.uploadFile(answerFile);

// Answer 업데이트
answerService.modifyAnswer(challengeId, uploadedUrl);

// // TODO: 방법 1: 이미지 업로드와 병합 분리 (4번째 cut 1장 만들어짐) -> 채택!
// // 이미지 업로드가 완료된 후에 병합을 시도합니다.
// sharedFileService.mergeImagesIfNeeded(challenge.getChallengeNum(), lucky);

// // TODO: 비동기 시도...
// // 비디오 업로드 후 이미지 병합 작업 스케줄링
// sharedFileService.scheduleMergeImages(challenge.getChallengeNum(), lucky);

return new ApiResponse<>("200", "Video uploaded successfully", Map.of("url", uploadedUrl));
FileType fileType = FileType.VIDEO;

CompletableFuture.runAsync(() -> {
try {
String uploadedUrl = awsS3Service.uploadFile(answerFile);
answerService.modifyAnswer(challengeId, uploadedUrl);
sharedFileService.scheduleMergeImagesAsync(challenge.getChallengeNum(), lucky);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
return new ApiResponse<>("200", "Video upload scheduled successfully", null);
} else {
return new ApiResponse<>("400", "Invalid file type: " + answerFile.getContentType(), null);
}
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/ongjong/namanmoo/service/AwsS3Service.java
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,32 @@ public void delete(String fileName) {
amazonS3Client.deleteObject(bucket, fileName);
}

// 업로드 실패 시 재시도 로직 추가
public String uploadFileWithRetry(MultipartFile multipartFile, int retries) throws IOException, InterruptedException {
File uploadFile = convertFile(multipartFile)
.orElseThrow(() -> new IllegalArgumentException("Incorrect conversion from MultipartFile to File"));

String fileType = determineFileType(multipartFile);
String fileName = generateFileName(uploadFile, fileType);

for (int i = 0; i < retries; i++) {
try {
amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile)
.withCannedAcl(CannedAccessControlList.PublicRead));

String uploadedUrl = getS3FileURL(fileName);
log.info("Successfully uploaded file to S3 on retry " + i + ": {}", uploadedUrl);

removeNewFile(uploadFile); // Remember to clean up local file after upload
return uploadedUrl;
} catch (Exception ex) {
log.warn("업로드에 실패하였습니다. 재시도하는중... (시도: {}/{})", i + 1, retries);
Thread.sleep(3000); // waiting for 3 seconds before the next retry
}
}
throw new RuntimeException("Failed to upload file after " + retries + " retries.");
}

// 오디오 파일 고정 경로 생성
public String uploadAudioFile(MultipartFile multipartFile, String s3Path) throws IOException {
log.info("Converting MultipartFile to File...");
Expand Down
33 changes: 28 additions & 5 deletions src/main/java/ongjong/namanmoo/service/SharedFileService.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
Expand All @@ -41,6 +42,7 @@
import java.util.stream.Collectors;

@Slf4j
@EnableAsync
@Service
public class SharedFileService {

Expand Down Expand Up @@ -96,7 +98,7 @@ public Map<String, String> uploadImageFile(Challenge challenge, MultipartFile ph
}

// S3에 파일 업로드 및 URL 저장
String uploadedUrl = awsS3Service.uploadFile(photo);
String uploadedUrl = awsS3Service.uploadFileWithRetry(photo, 3);

Optional<Lucky> optionalLucky = luckyRepository.findByFamilyFamilyIdAndRunningTrue(family.getFamilyId());
Lucky lucky = optionalLucky.orElseThrow(() -> new RuntimeException("Running Lucky not found for family"));
Expand All @@ -110,10 +112,9 @@ public Map<String, String> uploadImageFile(Challenge challenge, MultipartFile ph
sharedFile.setCreateDate(System.currentTimeMillis());
sharedFile.setLucky(lucky); // Lucky 엔티티 설정
sharedFileRepository.save(sharedFile);
// 저장이 성공했는지 확인하는 로그
System.out.println("SharedFile 저장 성공: " + sharedFile.getSharedFileId());
log.info("SharedFile 저장 성공: {}", sharedFile.getSharedFileId());
} catch (Exception e) {
e.printStackTrace(); // Or use appropriate logging
log.error("SharedFile 저장 실패", e);
}

response.put("url", uploadedUrl);
Expand All @@ -122,6 +123,28 @@ public Map<String, String> uploadImageFile(Challenge challenge, MultipartFile ph
return response;
}

@Async
public CompletableFuture<Map<String, String>> uploadImageFileAsync(Challenge challenge, MultipartFile photo, FileType fileType) {
return CompletableFuture.supplyAsync(() -> {
try {
return uploadImageFile(challenge, photo, fileType);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}

@Async
public void scheduleMergeImagesAsync(int challengeNum, Lucky lucky) {
CompletableFuture.runAsync(() -> {
try {
scheduleMergeImages(challengeNum, lucky);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}

// // TODO: 방법 1: (동기) 이미지 업로드와 병합 분리
// // 병합을 수행하는 메소드
// public void mergeImagesIfNeeded(int challengeNum, Lucky lucky) throws IOException {
Expand Down Expand Up @@ -255,7 +278,7 @@ public CompletableFuture<Void> scheduleMergeImages(int challengeNum, Lucky lucky
// 모든 이미지가 업로드된 후 병합 수행
mergeImagesIfNeeded(challengeNum, lucky);
} catch (IOException e) {
e.printStackTrace(); // Or use appropriate logging
log.error("이미지 병합 작업 실패", e);
} finally {
// 병합 작업이 완료되면 플래그를 제거
mergeTasks.remove(key);
Expand Down

0 comments on commit 50a16eb

Please sign in to comment.