전체적인 flow
@Override
@CacheEvict(cacheNames = "products", allEntries = true)
public void createProduct(Long memberId, RegisterProductDto command) throws Exception {
...
eventPublisher.publishEvent(new ImageUploadEvent(command.productImages());
}
public record RegisterProductDto(List<MultipartFile> productImages) {
}
@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void listenImageUpload(ImageUploadEvent event) {
imageUploadService.uploadImages(event);
}
에러 지점
try (InputStream inputStream = file.getInputStream()) {
amazonS3Client.putObject(new PutObjectRequest(bucket, s3Path, inputStream, objectMetadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3Client.getUrl(bucket, s3Path).toString();
} catch (IOException e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드 실패");
}
==에러==
java.nio.file.NoSuchFileException:
file.getInputStream() 메소드가 호출될 때 파일이 존재하지 않아서 **NoSuchFileException**이 발생합니다.**NoSuchFileException**이 발생하는 이유는 **MultipartFile**이 비동기 이벤트 핸들러로 전달되는 과정에서, **MultipartFile**의 입력 스트림이 소비되었기 때문입니다. **MultipartFile**은 한 번만 읽을 수 있는 입력 스트림을 가지고 있으며, 비동기 이벤트 방식으로 처리될 때 그 스트림이 이미 닫혀버릴 수 있습니다.
**MultipartFile**의 내용을 직접 사용하기 전에 파일의 내용을 바이트 배열로 변환하여 메모리에 저장합니다. 이를 통해 비동기 처리 중에도 원본 데이터를 유지할 수 있습니다.
@Override
@CacheEvict(cacheNames = "products", allEntries = true)
public void createProduct(Long memberId, RegisterProductDto command) throws Exception {
...
eventPublisher.publishEvent(new ImageUploadEvent(command.productImages());
}
public record ImageUploadEvent(List<byte[]> productImages) {
public ImageUploadEvent(List<MultipartFile> images, String company, Long productId) {
this(
images.stream()
.map(image -> {
try {
return image.getBytes();
} catch (IOException e) {
throw new RuntimeException("Error while reading image bytes: " + image.getOriginalFilename(), e);
}
})
.toList()
);
}
}
@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void listenImageUpload(ImageUploadEvent event) {
imageUploadService.uploadImages(event);
}