diff --git a/build.gradle b/build.gradle index df769f5..81b0644 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/hsu/umc/server/aws/s3/AmazonS3Manager.java b/src/main/java/hsu/umc/server/aws/s3/AmazonS3Manager.java new file mode 100644 index 0000000..f838ffa --- /dev/null +++ b/src/main/java/hsu/umc/server/aws/s3/AmazonS3Manager.java @@ -0,0 +1,31 @@ +package hsu.umc.server.aws.s3; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import hsu.umc.server.config.AmazonConfig; +import hsu.umc.server.entity.Uuid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class AmazonS3Manager { + private final AmazonS3 amazonS3; + private final AmazonConfig amazonConfig; + public String uploadFile(String keyName, MultipartFile file)throws IOException { + System.out.println(keyName); + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(file.getSize()); + amazonS3.putObject(new PutObjectRequest(amazonConfig.getBucket(), keyName, file.getInputStream(), metadata)); + return amazonS3.getUrl(amazonConfig.getBucket(),keyName).toString(); + } + public String generatePhotoKeyName(Uuid uuid) { + return amazonConfig.getPhotoPath() + '/' + uuid.getUuid(); + } +} diff --git a/src/main/java/hsu/umc/server/config/AmazonConfig.java b/src/main/java/hsu/umc/server/config/AmazonConfig.java new file mode 100644 index 0000000..6a82f11 --- /dev/null +++ b/src/main/java/hsu/umc/server/config/AmazonConfig.java @@ -0,0 +1,51 @@ +package hsu.umc.server.config; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import jakarta.annotation.PostConstruct; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Getter +public class AmazonConfig { + + private AWSCredentials awsCredentials; + + @Value("${cloud.aws.credentials.accessKey}") + private String accesskey; + @Value("${cloud.aws.credentials.secretKey}") + private String secretKey; + @Value("${cloud.aws.region.static}") + private String region; + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + + @Value("${cloud.aws.s3.path.photo}") + private String photoPath; + + @PostConstruct + public void init(){ + this.awsCredentials = new BasicAWSCredentials(accesskey, secretKey); + } + @Bean + public AmazonS3 amazonS3() { + AWSCredentials awsCredentials = new BasicAWSCredentials(accesskey, secretKey); + return AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } + + @Bean + public AWSCredentialsProvider awsCredentialsProvider(){ + return new AWSStaticCredentialsProvider(awsCredentials); + } +} diff --git a/src/main/java/hsu/umc/server/converter/PhotoConverter.java b/src/main/java/hsu/umc/server/converter/PhotoConverter.java new file mode 100644 index 0000000..e5e93bf --- /dev/null +++ b/src/main/java/hsu/umc/server/converter/PhotoConverter.java @@ -0,0 +1,12 @@ +package hsu.umc.server.converter; + +import hsu.umc.server.entity.Photo; +import hsu.umc.server.web.dto.PhotoResponseDto; + +public class PhotoConverter { + public static PhotoResponseDto.CreatePhotoResultDto toCreatePhotoResultDto(Photo photo){ + return PhotoResponseDto.CreatePhotoResultDto.builder() + .photoUrl(photo.getPhotoUrl()) + .build(); + } +} diff --git a/src/main/java/hsu/umc/server/entity/Photo.java b/src/main/java/hsu/umc/server/entity/Photo.java new file mode 100644 index 0000000..5ee8814 --- /dev/null +++ b/src/main/java/hsu/umc/server/entity/Photo.java @@ -0,0 +1,20 @@ +package hsu.umc.server.entity; + + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.*; + +@Entity +@Builder +@Getter +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Photo extends BaseEntity { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String photoUrl; + +} diff --git a/src/main/java/hsu/umc/server/entity/Uuid.java b/src/main/java/hsu/umc/server/entity/Uuid.java new file mode 100644 index 0000000..2b892c1 --- /dev/null +++ b/src/main/java/hsu/umc/server/entity/Uuid.java @@ -0,0 +1,16 @@ +package hsu.umc.server.entity; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@Getter +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Uuid extends BaseEntity{ + @Id @GeneratedValue(strategy = GenerationType.IDENTITY ) + private Long id; + @Column(unique = true) + private String uuid; +} diff --git a/src/main/java/hsu/umc/server/repository/PhotoRepository.java b/src/main/java/hsu/umc/server/repository/PhotoRepository.java new file mode 100644 index 0000000..d245df2 --- /dev/null +++ b/src/main/java/hsu/umc/server/repository/PhotoRepository.java @@ -0,0 +1,7 @@ +package hsu.umc.server.repository; + +import hsu.umc.server.entity.Photo; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PhotoRepository extends JpaRepository { +} diff --git a/src/main/java/hsu/umc/server/repository/UuidRepository.java b/src/main/java/hsu/umc/server/repository/UuidRepository.java new file mode 100644 index 0000000..bdbf6bb --- /dev/null +++ b/src/main/java/hsu/umc/server/repository/UuidRepository.java @@ -0,0 +1,7 @@ +package hsu.umc.server.repository; + +import hsu.umc.server.entity.Uuid; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UuidRepository extends JpaRepository { +} diff --git a/src/main/java/hsu/umc/server/service/PhotoCommandService.java b/src/main/java/hsu/umc/server/service/PhotoCommandService.java new file mode 100644 index 0000000..28b0d84 --- /dev/null +++ b/src/main/java/hsu/umc/server/service/PhotoCommandService.java @@ -0,0 +1,9 @@ +package hsu.umc.server.service; + +import hsu.umc.server.entity.Photo; +import org.springframework.web.multipart.MultipartFile; + + +public interface PhotoCommandService { + Photo createPhoto(MultipartFile request); +} diff --git a/src/main/java/hsu/umc/server/service/PhotoCommandServiceImpl.java b/src/main/java/hsu/umc/server/service/PhotoCommandServiceImpl.java new file mode 100644 index 0000000..ce85355 --- /dev/null +++ b/src/main/java/hsu/umc/server/service/PhotoCommandServiceImpl.java @@ -0,0 +1,41 @@ +package hsu.umc.server.service; + +import hsu.umc.server.aws.s3.AmazonS3Manager; +import hsu.umc.server.entity.Photo; +import hsu.umc.server.entity.Uuid; +import hsu.umc.server.repository.PhotoRepository; +import hsu.umc.server.repository.UuidRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.UUID; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class PhotoCommandServiceImpl implements PhotoCommandService{ + private final AmazonS3Manager s3Manager; + private final UuidRepository uuidRepository; + private final PhotoRepository photoRepository; + @Override + @Transactional + public Photo createPhoto(MultipartFile photoPicture) { + String uuid = UUID.randomUUID().toString(); + Uuid savedUuid = uuidRepository.save(Uuid.builder().uuid(uuid).build()); + String pictureUrl = null; + try { + pictureUrl = s3Manager.uploadFile(s3Manager.generatePhotoKeyName(savedUuid), photoPicture); + } catch (IOException e) { + throw new RuntimeException(e); + } + + Photo photo = Photo.builder() + .photoUrl(pictureUrl) + .build(); + + return photoRepository.save(photo); + } +} diff --git a/src/main/java/hsu/umc/server/web/controller/PhotoController.java b/src/main/java/hsu/umc/server/web/controller/PhotoController.java new file mode 100644 index 0000000..514184e --- /dev/null +++ b/src/main/java/hsu/umc/server/web/controller/PhotoController.java @@ -0,0 +1,29 @@ +package hsu.umc.server.web.controller; + +import hsu.umc.server.apipayload.ApiResponse; +import hsu.umc.server.converter.PhotoConverter; +import hsu.umc.server.entity.Photo; +import hsu.umc.server.service.PhotoCommandService; +import hsu.umc.server.web.dto.PhotoResponseDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + + +@RestController +@RequestMapping("/api/photo") +@RequiredArgsConstructor +@Tag(name="photo",description = "네컷사진 관련 API") +public class PhotoController { + private final PhotoCommandService photoCommandService; + @PostMapping(value = "/",consumes = "multipart/form-data") + @Operation(summary = "네컷 사진 저장", description = "제공된 사진을 저장하고 url을 반환합니다.") + public ApiResponse createPhoto( + @RequestPart("photoPicture") MultipartFile photoPicture) { + Photo photo = photoCommandService.createPhoto(photoPicture); + return ApiResponse.onSuccess(PhotoConverter.toCreatePhotoResultDto(photo)); + } + +} diff --git a/src/main/java/hsu/umc/server/web/dto/PhotoResponseDto.java b/src/main/java/hsu/umc/server/web/dto/PhotoResponseDto.java new file mode 100644 index 0000000..c876c84 --- /dev/null +++ b/src/main/java/hsu/umc/server/web/dto/PhotoResponseDto.java @@ -0,0 +1,16 @@ +package hsu.umc.server.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class PhotoResponseDto { + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class CreatePhotoResultDto{ + private String photoUrl; + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 14e2f8b..ef89b30 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -16,3 +16,17 @@ spring: allowed-headers: "*" allow-credentials: true +cloud: + aws: + s3: + bucket: umc-7th + path: + photo : photos + region: + static: ap-northeast-2 + stack: + auto: false + credentials: + accessKey: ${aws.access.key.id} + secretKey: ${aws.secret.access.key} + diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 734d4b1..7caf06a 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -15,4 +15,18 @@ spring: format_sql: true use_sql_comments: true hbm2ddl: - auto: update \ No newline at end of file + auto: update + +cloud: + aws: + s3: + bucket: umc-7th + path: + photo : photos + region: + static: ap-northeast-2 + stack: + auto: false + credentials: + accessKey: ${aws.access.key.id} + secretKey: ${aws.secret.access.key} \ No newline at end of file