Skip to content

Commit

Permalink
Merge pull request #253 from MOONSHOT-Team/feature/#252
Browse files Browse the repository at this point in the history
[Refactor] #252 - S3 Presigned Url 서비스 리팩토링
  • Loading branch information
its-sky authored Mar 13, 2024
2 parents 769dbdf + fc841a5 commit 20f90a8
Show file tree
Hide file tree
Showing 16 changed files with 152 additions and 50 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package org.moonshot.user.controller;

import lombok.RequiredArgsConstructor;
import org.moonshot.model.Logging;
import org.moonshot.response.MoonshotResponse;
import org.moonshot.response.SuccessType;
import org.moonshot.s3.S3Service;
import org.moonshot.s3.dto.request.GetPresignedUrlRequestDto;
import org.moonshot.s3.dto.request.NotifyImageSaveSuccessRequestDto;
import org.moonshot.s3.dto.response.PresignedUrlVO;
import org.moonshot.user.model.LoginUser;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -15,27 +18,23 @@
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/v1")
@RequestMapping("/v1/image")
@RequiredArgsConstructor
public class ImageController {

private final S3Service s3Service;

//TODO
// 추후 로그인 유저를 확인하여 해당 유저에 대한 데이터로 getUploadPreSignedUrl로 username을 넘기는 로직으로 변경해야 함.
@GetMapping("/image")
public ResponseEntity<MoonshotResponse<PresignedUrlVO>> getPresignedUrl() {
return ResponseEntity.status(HttpStatus.OK).body(
MoonshotResponse.success(
SuccessType.GET_PRESIGNED_URL_SUCCESS, s3Service.getUploadPreSignedUrl("test", "SMC")));
@GetMapping
@Logging(item = "Image", action = "Get")
public ResponseEntity<MoonshotResponse<PresignedUrlVO>> getPresignedUrl(@LoginUser Long userId, @RequestBody GetPresignedUrlRequestDto request) {
return ResponseEntity.status(HttpStatus.OK)
.body(MoonshotResponse.success(SuccessType.GET_PRESIGNED_URL_SUCCESS, s3Service.getUploadPreSignedUrl(request, userId)));
}

//TODO
// 해당 API도 username을 @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : username")
// 등을 이용하여 Annotation화 하여 바로 username을 넘길 수 있도록 변경해야 함.
@PostMapping("/image")
public ResponseEntity<MoonshotResponse<?>> notifyImageSaveSuccess(@RequestBody final NotifyImageSaveSuccessRequestDto request) {
s3Service.notifyImageSaveSuccess(request);
@PostMapping
@Logging(item = "Image", action = "Post")
public ResponseEntity<MoonshotResponse<?>> notifyImageSaveSuccess(@LoginUser Long userId, @RequestBody final NotifyImageSaveSuccessRequestDto request) {
s3Service.notifyImageSaveSuccess(userId, request);
return ResponseEntity.status(HttpStatus.CREATED).body(MoonshotResponse.success(SuccessType.POST_NOTIFY_IMAGE_SAVE_SUCCESS));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public record UserInfoResponse(
public static UserInfoResponse of(User user) {
return new UserInfoResponse(
user.getSocialPlatform().getValue(),
user.getProfileImage(),
user.getImageUrl(),
user.getNickname(),
user.getDescription());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.moonshot.user.service;

import lombok.RequiredArgsConstructor;
import org.moonshot.s3.ImageEvent;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;

@Component
@RequiredArgsConstructor
public class ImageEventListener {

private final UserService userService;

@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleImageEvent(ImageEvent imageEvent) {
userService.updateUserProfileImage(imageEvent.userId(), imageEvent.imageUrl());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public SocialLoginResponse googleLogin(final SocialLoginRequest request) {
.socialId(userResponse.sub())
.socialPlatform(request.socialPlatform())
.name(userResponse.name())
.profileImage(userResponse.picture())
.imageUrl(userResponse.picture())
.email(userResponse.email())
.build());
user = newUser;
Expand Down Expand Up @@ -121,7 +121,7 @@ public SocialLoginResponse kakaoLogin(final SocialLoginRequest request) {
.socialId(userResponse.id())
.socialPlatform(request.socialPlatform())
.name(userResponse.kakaoAccount().profile().nickname())
.profileImage(userResponse.kakaoAccount().profile().profileImageUrl())
.imageUrl(userResponse.kakaoAccount().profile().profileImageUrl())
.email(null)
.build());
user = newUser;
Expand Down Expand Up @@ -176,14 +176,19 @@ public UserInfoResponse getMyProfile(final Long userId) {
return UserInfoResponse.of(user);
}

public void updateUserProfileImage(final Long userId, final String imageUrl) {
User user = userRepository.findById(userId).orElseThrow(() -> new NotFoundException(NOT_FOUND_USER));
user.modifyProfileImage(imageUrl);
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void publishSignUpEvent(final User user) {
eventPublisher.publishEvent(SignUpEvent.of(
user.getName(),
user.getEmail() == null ? "" : user.getEmail(),
user.getSocialPlatform().toString(),
LocalDateTime.now(),
user.getProfileImage()
user.getImageUrl()
));
}

Expand Down
2 changes: 1 addition & 1 deletion moonshot-api/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ spring:

flyway:
baseline-on-migrate: false
baseline-version: 1
baseline-version: 2
enabled: false

google:
Expand Down
1 change: 1 addition & 0 deletions moonshot-api/src/main/resources/db/migration/V3__DDL.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE user CHANGE profile_image image_url varchar(255);
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.moonshot.exception.MoonshotException;
import org.moonshot.exception.BadRequestException;
import org.moonshot.response.ErrorType;

@Getter
Expand All @@ -31,7 +31,7 @@ public static KRState fromValue(String value) {
return krState;
}
}
throw new MoonshotException(ErrorType.INVALID_TYPE);
throw new BadRequestException(ErrorType.INVALID_TYPE);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.moonshot.exception.MoonshotException;
import org.moonshot.exception.BadRequestException;
import org.moonshot.response.ErrorType;

@Getter
Expand Down Expand Up @@ -33,7 +33,7 @@ public static Category fromValue(String value) {
return category;
}
}
throw new MoonshotException(ErrorType.INVALID_TYPE);
throw new BadRequestException(ErrorType.INVALID_TYPE);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.moonshot.exception.MoonshotException;
import org.moonshot.exception.BadRequestException;
import org.moonshot.response.ErrorType;

@Getter
Expand All @@ -30,7 +30,7 @@ public static Criteria fromValue(String value) {
return criteria;
}
}
throw new MoonshotException(ErrorType.INVALID_TYPE);
throw new BadRequestException(ErrorType.INVALID_TYPE);
}

}
31 changes: 22 additions & 9 deletions moonshot-domain/src/main/java/org/moonshot/user/model/User.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package org.moonshot.user.model;

import jakarta.persistence.*;
import lombok.*;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import java.time.LocalDateTime;
import java.util.List;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
Expand Down Expand Up @@ -32,7 +40,7 @@ public class User {
private String name;

@Column(nullable = false)
private String profileImage;
private String imageUrl;

private String email;

Expand All @@ -43,24 +51,24 @@ public class User {
private LocalDateTime deleteAt;

@Builder
private User(String socialId, SocialPlatform socialPlatform, String name, String profileImage, String email,
private User(String socialId, SocialPlatform socialPlatform, String name, String imageUrl, String email,
String nickname, String description) {
this.socialId = socialId;
this.socialPlatform = socialPlatform;
this.name = name;
this.profileImage = profileImage;
this.imageUrl = imageUrl;
this.email = email;
this.nickname = nickname;
this.description = description;
}

@Builder(builderMethodName = "builderWithSignIn")
public static User of(String socialId, SocialPlatform socialPlatform, String name, String profileImage, String email) {
public static User of(String socialId, SocialPlatform socialPlatform, String name, String imageUrl, String email) {
return builder()
.socialId(socialId)
.socialPlatform(socialPlatform)
.name(name)
.profileImage(profileImage)
.imageUrl(imageUrl)
.email(email)
.build();
}
Expand All @@ -69,9 +77,14 @@ public static User of(String socialId, SocialPlatform socialPlatform, String nam

public void modifyDescription(String description) { this.description = description; }

public void modifyProfileImage(String imageUrl) {
this.imageUrl = imageUrl;
}

public void resetDeleteAt() {
this.deleteAt = null;
}

public void setDeleteAt(){
this.deleteAt = LocalDateTime.now().plusDays(USER_RETENTION_PERIOD);
}
Expand Down
7 changes: 6 additions & 1 deletion moonshot-external/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'

// JWT
// Transaction Retry
implementation 'org.springframework.retry:spring-retry'
implementation 'org.springframework:spring-aspects'

// JWT
implementation group: "io.jsonwebtoken", name: "jjwt-api", version: "0.11.2"
implementation group: "io.jsonwebtoken", name: "jjwt-impl", version: "0.11.2"
implementation group: "io.jsonwebtoken", name: "jjwt-jackson", version: "0.11.2"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.moonshot.s3;

public record ImageEvent(Long userId, String imageUrl, ImageType imageType) {

public static ImageEvent of(Long userId, String imageUrl, ImageType imageType) {
return new ImageEvent(userId, imageUrl, imageType);
}

}
28 changes: 28 additions & 0 deletions moonshot-external/src/main/java/org/moonshot/s3/ImageType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.moonshot.s3;

import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.moonshot.exception.BadRequestException;
import org.moonshot.response.ErrorType;

@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum ImageType {

PROFILE("프로필");

private final String value;

@JsonCreator
public static ImageType fromValue(String value) {
for (ImageType imageType : ImageType.values()) {
if (imageType.getValue().equals(value)) {
return imageType;
}
}
throw new BadRequestException(ErrorType.INVALID_TYPE);
}

}
40 changes: 27 additions & 13 deletions moonshot-external/src/main/java/org/moonshot/s3/S3Service.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.Date;
import org.springframework.beans.factory.annotation.Value;
import org.moonshot.config.AWSConfig;
import org.moonshot.constants.AWSConstants;
import org.moonshot.s3.dto.request.GetPresignedUrlRequestDto;
import org.moonshot.s3.dto.request.NotifyImageSaveSuccessRequestDto;
import org.moonshot.s3.dto.response.PresignedUrlVO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;
Expand All @@ -18,17 +23,19 @@ public class S3Service {

private final String bucketName;
private final AWSConfig awsConfig;
private final ApplicationEventPublisher eventPublisher;
private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSSS");

public S3Service(@Value("${aws.s3-bucket-name}") final String bucketName,
AWSConfig awsConfig) {
AWSConfig awsConfig, ApplicationEventPublisher eventPublisher) {
this.bucketName = bucketName;
this.awsConfig = awsConfig;
this.eventPublisher = eventPublisher;
}

public PresignedUrlVO getUploadPreSignedUrl(String prefix, String username) {
final String fileName = generateFileName(username);
final String key = prefix + "/" + fileName;
public PresignedUrlVO getUploadPreSignedUrl(final GetPresignedUrlRequestDto request, final Long userId) {
final String fileName = generateFileName(userId);
final String key = request.imageType().toString() + "/" + fileName;

S3Presigner preSigner = awsConfig.getS3Presigner();

Expand All @@ -47,16 +54,23 @@ public PresignedUrlVO getUploadPreSignedUrl(String prefix, String username) {
return PresignedUrlVO.of(key, url);
}

public void notifyImageSaveSuccess(final NotifyImageSaveSuccessRequestDto request) {
//TODO
// 추후 User 엔티티 개발 후
// username 아이디를 가진 User 정보에 profile image를 bucketName + key로 삽입하면 됨.
// 이는 UserService로 위임하여 데이터 처리하도록 하면 됨.
// 또한 기존의 S3에 저장되어 있던 이미지를 삭제해야 함.
@Transactional
@Retryable(maxAttempts = 3, backoff = @Backoff(2000))
public void notifyImageSaveSuccess(final Long userId, final NotifyImageSaveSuccessRequestDto request) {
String imageUrl = getImageUrl(request.fileName());
publishImageEvent(userId, imageUrl, request.imageType());
}

private String generateFileName(final Long userId) {
return userId + "-" + simpleDateFormat.format(new Date());
}

private String getImageUrl(final String fileName) {
return "https://" + bucketName + ".s3.ap-northeast-2.amazonaws.com/" + fileName;
}

private String generateFileName(String username) {
return username + "-" + simpleDateFormat.format(new Date());
private void publishImageEvent(final Long userId, final String imageUrl, final ImageType imageType) {
eventPublisher.publishEvent(ImageEvent.of(userId, imageUrl, imageType));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.moonshot.s3.dto.request;

import org.moonshot.s3.ImageType;

public record GetPresignedUrlRequestDto(ImageType imageType) {
}
Loading

0 comments on commit 20f90a8

Please sign in to comment.