-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
스프링 이벤트를 사용한 S3 이미지 파일 삭제 기능 추가 #714
Changes from 12 commits
9181f38
fc40a6a
575dd23
eda8458
40b5682
0319c60
b81c316
3db499b
aa4b8cf
003eeff
ff85e4d
09713b7
efa5c8d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package hanglog.image.domain; | ||
|
||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Getter | ||
@RequiredArgsConstructor | ||
public class S3ImageEvent { | ||
|
||
private final String imageName; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package hanglog.image.infrastructure; | ||
|
||
import static hanglog.global.exception.ExceptionCode.INVALID_IMAGE; | ||
import static hanglog.global.exception.ExceptionCode.INVALID_IMAGE_PATH; | ||
|
||
import com.amazonaws.AmazonServiceException; | ||
import com.amazonaws.services.s3.AmazonS3; | ||
import com.amazonaws.services.s3.model.ObjectMetadata; | ||
import hanglog.global.exception.ImageException; | ||
import hanglog.image.domain.ImageFile; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.util.List; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class ImageUploader { | ||
|
||
private static final String CACHE_CONTROL_VALUE = "max-age=3153600"; | ||
|
||
private final AmazonS3 s3Client; | ||
|
||
@Value("${cloud.aws.s3.bucket}") | ||
private String bucket; | ||
|
||
@Value("${cloud.aws.s3.folder}") | ||
private String folder; | ||
|
||
public List<String> uploadImages(final List<ImageFile> imageFiles) { | ||
return imageFiles.stream() | ||
.map(this::uploadImage) | ||
.toList(); | ||
} | ||
|
||
private String uploadImage(final ImageFile imageFile) { | ||
final String path = folder + imageFile.getHashedName(); | ||
final ObjectMetadata metadata = new ObjectMetadata(); | ||
metadata.setContentType(imageFile.getContentType()); | ||
metadata.setContentLength(imageFile.getSize()); | ||
metadata.setCacheControl(CACHE_CONTROL_VALUE); | ||
|
||
try (final InputStream inputStream = imageFile.getInputStream()) { | ||
s3Client.putObject(bucket, path, inputStream, metadata); | ||
} catch (final AmazonServiceException e) { | ||
throw new ImageException(INVALID_IMAGE_PATH); | ||
} catch (final IOException e) { | ||
throw new ImageException(INVALID_IMAGE); | ||
} | ||
return imageFile.getHashedName(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package hanglog.listener; | ||
|
||
import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW; | ||
|
||
import com.amazonaws.services.s3.AmazonS3; | ||
import hanglog.image.domain.S3ImageEvent; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.scheduling.annotation.Async; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.transaction.annotation.Transactional; | ||
import org.springframework.transaction.event.TransactionalEventListener; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class S3ImageEventListener { | ||
|
||
private final AmazonS3 s3Client; | ||
|
||
@Value("${cloud.aws.s3.bucket}") | ||
private String bucket; | ||
|
||
@Value("${cloud.aws.s3.folder}") | ||
private String folder; | ||
|
||
@Async | ||
@Transactional(propagation = REQUIRES_NEW) | ||
@TransactionalEventListener(fallbackExecution = true) | ||
public void deleteImageFileInS3(final S3ImageEvent event) { | ||
final String imageName = event.getImageName(); | ||
s3Client.deleteObject(bucket, folder + imageName); | ||
Comment on lines
+30
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기는 예외처리 필요없을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기서 catch를 받아 customException을 던져 줄까요.? |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,19 @@ | ||
package hanglog.member.service; | ||
|
||
import static hanglog.global.exception.ExceptionCode.DUPLICATED_MEMBER_NICKNAME; | ||
import static hanglog.global.exception.ExceptionCode.INVALID_IMAGE_URL; | ||
import static hanglog.global.exception.ExceptionCode.NOT_FOUND_MEMBER_ID; | ||
|
||
import hanglog.global.exception.BadRequestException; | ||
import hanglog.image.domain.S3ImageEvent; | ||
import hanglog.member.domain.Member; | ||
import hanglog.member.domain.repository.MemberRepository; | ||
import hanglog.member.dto.request.MyPageRequest; | ||
import hanglog.member.dto.response.MyPageResponse; | ||
import java.net.MalformedURLException; | ||
import java.net.URL; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.context.ApplicationEventPublisher; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
|
@@ -17,7 +22,10 @@ | |
@Transactional | ||
public class MemberService { | ||
|
||
private static final String HANG_LOG_HOST = "image.hanglog.com"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 날카로운 지적입니다 홍고라니 |
||
|
||
private final MemberRepository memberRepository; | ||
private final ApplicationEventPublisher publisher; | ||
|
||
@Transactional(readOnly = true) | ||
public MyPageResponse getMyPageInfo(final Long memberId) { | ||
|
@@ -33,19 +41,34 @@ public void updateMyPageInfo(final Long memberId, final MyPageRequest myPageRequ | |
if (member.isNicknameChanged(myPageRequest.getNickname())) { | ||
checkDuplicatedNickname(myPageRequest.getNickname()); | ||
} | ||
|
||
final Member updateMember = new Member( | ||
memberId, | ||
member.getSocialLoginId(), | ||
myPageRequest.getNickname(), | ||
myPageRequest.getImageUrl() | ||
); | ||
deletePreviousImage(member.getImageUrl(), updateMember.getImageUrl()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 얘는 previous네요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 인정해버렸습니다 오리진으로 통일하겠습니다. |
||
memberRepository.save(updateMember); | ||
} | ||
|
||
private void checkDuplicatedNickname(final String nickname) { | ||
if(memberRepository.existsByNickname(nickname)) { | ||
if (memberRepository.existsByNickname(nickname)) { | ||
throw new BadRequestException(DUPLICATED_MEMBER_NICKNAME); | ||
} | ||
} | ||
|
||
private void deletePreviousImage(final String previousUrl, final String updatedUrl) { | ||
if (previousUrl.equals(updatedUrl)) { | ||
return; | ||
} | ||
try { | ||
final URL targetUrl = new URL(previousUrl); | ||
if (targetUrl.getHost().equals(HANG_LOG_HOST)) { | ||
final String targetName = previousUrl.substring(previousUrl.lastIndexOf("/") + 1); | ||
publisher.publishEvent(new S3ImageEvent(targetName)); | ||
} | ||
} catch (final MalformedURLException e) { | ||
throw new BadRequestException(INVALID_IMAGE_URL); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ | |
import hanglog.community.domain.PublishedTrip; | ||
import hanglog.global.exception.AuthException; | ||
import hanglog.global.exception.BadRequestException; | ||
import hanglog.image.domain.S3ImageEvent; | ||
import hanglog.member.domain.Member; | ||
import hanglog.member.domain.repository.MemberRepository; | ||
import hanglog.share.domain.repository.SharedTripRepository; | ||
|
@@ -120,6 +121,7 @@ public void update(final Long tripId, final TripUpdateRequest updateRequest) { | |
|
||
updateTripCities(tripId, cities); | ||
updateDayLog(updateRequest, trip); | ||
updateImage(trip.getImageName(), updateRequest.getImageName()); | ||
trip.update(updateRequest); | ||
tripRepository.save(trip); | ||
} | ||
|
@@ -144,6 +146,13 @@ private void updateDayLog(final TripUpdateRequest updateRequest, final Trip trip | |
} | ||
} | ||
|
||
private void updateImage(final String beforeImageName, final String updateImageName) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오리진 의견도 나와버렸군요 |
||
if (beforeImageName.equals(updateImageName)) { | ||
return; | ||
} | ||
publisher.publishEvent(new S3ImageEvent(beforeImageName)); | ||
} | ||
|
||
private void updateDayLogByPeriod(final Trip trip, final int currentPeriod, final int requestPeriod) { | ||
final DayLog extraDayLog = trip.getDayLogs().get(currentPeriod); | ||
if (currentPeriod < requestPeriod) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Value
때문에 분리해준 건가요? 분리한 이유는 무엇인가요? 어차피 서비스에서 밖에 사용 안되던데...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
레이어드 아키텍처 관점에서 분리했습니다.!