diff --git "a/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/build.gradle" "b/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/build.gradle" index 3a02eb8..791088e 100644 --- "a/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/build.gradle" +++ "b/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/build.gradle" @@ -39,6 +39,10 @@ dependencies { //Security implementation 'org.springframework.boot:spring-boot-starter-security' + + //Multipart file + implementation("software.amazon.awssdk:bom:2.21.0") + implementation("software.amazon.awssdk:s3:2.21.0") } tasks.named('test') { diff --git "a/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/controller/BlogController.java" "b/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/controller/BlogController.java" index 64db10f..36f9ac7 100644 --- "a/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/controller/BlogController.java" +++ "b/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/controller/BlogController.java" @@ -25,7 +25,7 @@ public class BlogController { //성공했을 때 값을 반환하기 위한 SuccessStatusResponse @PostMapping("/blog") public ResponseEntity createBlog( - BlogCreateRequest blogCreateRequest + @ModelAttribute BlogCreateRequest blogCreateRequest ) { return ResponseEntity.created(URI.create(blogService.create( principalHandler.getUserIdFromPrincipal(), blogCreateRequest))).build(); diff --git "a/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/domain/Blog.java" "b/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/domain/Blog.java" index aa23bf0..e5ff43b 100644 --- "a/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/domain/Blog.java" +++ "b/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/domain/Blog.java" @@ -20,15 +20,17 @@ public class Blog extends BaseTimeEntity{ private String title; private String description; + private String imageUrl; - private Blog(Member member, String title, String description){ + private Blog(Member member, String title,String imageUrl, String description){ this.member = member; this.title = title; this.description= description; + this.imageUrl = imageUrl; } - public static Blog create(Member member, String title, String description){ - return new Blog(member, title, description); + public static Blog create(Member member, String title, String imageUrl, String description){ + return new Blog(member, title,imageUrl, description); } public void updateTitle( String title diff --git "a/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/dto/BlogCreateRequest.java" "b/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/dto/BlogCreateRequest.java" index f6102ae..7811dd8 100644 --- "a/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/dto/BlogCreateRequest.java" +++ "b/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/dto/BlogCreateRequest.java" @@ -1,4 +1,11 @@ package org.sopt.practice.dto; -public record BlogCreateRequest(String title, String description) { +import org.springframework.web.multipart.MultipartFile; + +public record BlogCreateRequest( + String title, + String description, + MultipartFile image +) { } + diff --git "a/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/external/AwsConfig.java" "b/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/external/AwsConfig.java" new file mode 100644 index 0000000..a2e0051 --- /dev/null +++ "b/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/external/AwsConfig.java" @@ -0,0 +1,49 @@ +package org.sopt.practice.external; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.auth.credentials.SystemPropertyCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + + +@Configuration +public class AwsConfig { + + private static final String AWS_ACCESS_KEY_ID = "aws.accessKeyId"; + private static final String AWS_SECRET_ACCESS_KEY = "aws.secretAccessKey"; + + private final String accessKey; + private final String secretKey; + private final String regionString; + + public AwsConfig(@Value("${aws-property.access-key}") final String accessKey, + @Value("${aws-property.secret-key}") final String secretKey, + @Value("${aws-property.aws-region}") final String regionString) { + this.accessKey = accessKey; + this.secretKey = secretKey; + this.regionString = regionString; + } + + + @Bean + public SystemPropertyCredentialsProvider systemPropertyCredentialsProvider() { + System.setProperty(AWS_ACCESS_KEY_ID, accessKey); + System.setProperty(AWS_SECRET_ACCESS_KEY, secretKey); + return SystemPropertyCredentialsProvider.create(); + } + + @Bean + public Region getRegion() { + return Region.of(regionString); + } + + @Bean + public S3Client getS3Client() { + return S3Client.builder() + .region(getRegion()) + .credentialsProvider(systemPropertyCredentialsProvider()) + .build(); + } +} diff --git "a/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/external/S3Service.java" "b/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/external/S3Service.java" new file mode 100644 index 0000000..ea05662 --- /dev/null +++ "b/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/external/S3Service.java" @@ -0,0 +1,80 @@ +package org.sopt.practice.external; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +@Component +public class S3Service { + + private final String bucketName; + private final AwsConfig awsConfig; + private static final List IMAGE_EXTENSIONS = Arrays.asList("image/jpeg", "image/png", "image/jpg", "image/webp"); + + + public S3Service(@Value("${aws-property.s3-bucket-name}") final String bucketName, AwsConfig awsConfig) { + this.bucketName = bucketName; + this.awsConfig = awsConfig; + } + + + public String uploadImage(String directoryPath, MultipartFile image) throws IOException { + final String key = directoryPath + generateImageFileName(); + final S3Client s3Client = awsConfig.getS3Client(); + + validateExtension(image); + validateFileSize(image); + + PutObjectRequest request = PutObjectRequest.builder() + .bucket(bucketName) + .key(key) + .contentType(image.getContentType()) + .contentDisposition("inline") + .build(); + + RequestBody requestBody = RequestBody.fromBytes(image.getBytes()); + s3Client.putObject(request, requestBody); + return key; + } + + public void deleteImage(String key) throws IOException { + final S3Client s3Client = awsConfig.getS3Client(); + + s3Client.deleteObject((DeleteObjectRequest.Builder builder) -> + builder.bucket(bucketName) + .key(key) + .build() + ); + } + + + private String generateImageFileName() { + return UUID.randomUUID() + ".jpg"; + } + + + private void validateExtension(MultipartFile image) { + String contentType = image.getContentType(); + if (!IMAGE_EXTENSIONS.contains(contentType)) { + throw new RuntimeException("이미지 확장자는 jpg, png, webp만 가능합니다."); + } + } + + private static final Long MAX_FILE_SIZE = 5 * 1024 * 1024L; + + private void validateFileSize(MultipartFile image) { + if (image.getSize() > MAX_FILE_SIZE) { + throw new RuntimeException("이미지 사이즈는 5MB를 넘을 수 없습니다."); + } + } + +} diff --git "a/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/service/BlogService.java" "b/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/service/BlogService.java" index 84393c5..b03e883 100644 --- "a/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/service/BlogService.java" +++ "b/\354\204\270\353\257\270\353\202\230/6\354\260\250 \354\204\270\353\257\270\353\202\230/practice/src/main/java/org/sopt/practice/service/BlogService.java" @@ -9,21 +9,33 @@ import org.sopt.practice.dto.BlogCreateRequest; import org.sopt.practice.dto.BlogTitleUpdateRequest; import org.sopt.practice.exception.NotFoundException; +import org.sopt.practice.external.S3Service; import org.sopt.practice.repository.BlogRepository; import org.springframework.stereotype.Service; +import java.io.IOException; + @Service @RequiredArgsConstructor public class BlogService { private final BlogRepository blogRepository; private final MemberService memberService; + private final S3Service s3Service; + private static final String BLOG_S3_UPLOAD_FOLER = "blog/"; + - public String create(Long memberId, BlogCreateRequest blogCreateRequest) { + @Transactional + public String create(Long memberId, BlogCreateRequest createRequest) { //member찾기 Member member = memberService.findById(memberId); - Blog blog = blogRepository.save(Blog.create(member, blogCreateRequest.title(), blogCreateRequest.description())); - return blog.toString(); + try { + Blog blog = blogRepository.save(Blog.create(member, createRequest.title(), createRequest.description(), + s3Service.uploadImage(BLOG_S3_UPLOAD_FOLER, createRequest.image()))); + return blog.getId().toString(); + } catch (RuntimeException | IOException e) { + throw new RuntimeException(e.getMessage()); + } } @Transactional