Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…into feat/#111
  • Loading branch information
bjo6300 committed Nov 10, 2023
2 parents 598726a + c12c945 commit 04da03b
Show file tree
Hide file tree
Showing 13 changed files with 4,319 additions and 2,082 deletions.
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.awaitility:awaitility'

// S3 AWS
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-aws', version: '2.2.6.RELEASE'
testImplementation 'io.findify:s3mock_2.13:0.2.6'
}

tasks.named('test') {
Expand Down
16 changes: 8 additions & 8 deletions src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,31 @@
=== 보호소 로그인

==== Request

operation::auth-controller-test/shelter-login[snippets='http-request,request-fields']

==== Response

operation::auth-controller-test/shelter-login[snippets='http-response,response-fields,response-cookies']

=== 봉사자 로그인

==== Request

operation::auth-controller-test/volunteer-login[snippets='http-request,request-fields']

==== Response

operation::auth-controller-test/volunteer-login[snippets='http-response,response-fields,response-cookies']

=== 액세스 토큰 갱신

==== Request

operation::auth-controller-test/refresh-access-token[snippets='http-request,request-cookies']

==== Response

operation::auth-controller-test/refresh-access-token[snippets='http-response,response-fields,response-cookies']

== 1. 봉사자
Expand Down Expand Up @@ -83,20 +89,14 @@ operation::review-controller-test/find-shelter-reviews-by-volunteer[snippets='ht

== 2. 보호소

=== 보호소 비밀번호 변경

==== Request
operation::shelter-controller-test/update-password[snippets='http-request,request-headers,request-fields']

==== Response
operation::shelter-controller-test/update-password[snippets='http-response']

=== 보호소 마이 페이지 조회

==== Request

operation::shelter-controller-test/find-shelter-my-page[snippets='http-request,request-headers']

==== Response

operation::shelter-controller-test/find-shelter-my-page[snippets='http-response,response-fields']

== 3. 봉사 모집
Expand Down
35 changes: 35 additions & 0 deletions src/main/java/com/clova/anifriends/global/config/S3Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.clova.anifriends.global.config;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class S3Config {

@Value("${cloud.aws.credentials.accessKey}")
private String accessKey;

@Value("${cloud.aws.credentials.secretKey}")
private String secretKey;

@Value("${cloud.aws.s3.bucket}")
private String bucket;

@Value("${cloud.aws.region.static}")
private String region;

@Bean
public AmazonS3 amazonS3Client() {
AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
return AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.clova.anifriends.global.image;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/images")
public class ImageController {

private final S3Service s3Service;

@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<UploadImagesResponse> uploadImages(
@ModelAttribute @Valid UploadImagesRequest uploadImagesRequest
) {
return ResponseEntity.ok(
UploadImagesResponse.from(s3Service.uploadImages(uploadImagesRequest.images())));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.clova.anifriends.global.image;

import static com.clova.anifriends.global.exception.ErrorCode.BAD_REQUEST;

import com.clova.anifriends.global.exception.BadRequestException;

public class S3BadRequestException extends BadRequestException {

public S3BadRequestException(String message) {
super(BAD_REQUEST, message);
}
}
70 changes: 70 additions & 0 deletions src/main/java/com/clova/anifriends/global/image/S3Service.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.clova.anifriends.global.image;

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Service
@RequiredArgsConstructor
public class S3Service {

@Value("${cloud.aws.s3.bucket}")
private String bucket;

private final AmazonS3 amazonS3;
private static final String FOLDER = "images";

public List<String> uploadImages(List<MultipartFile> multipartFileList) {
ObjectMetadata objectMetadata = new ObjectMetadata();
List<String> list = new ArrayList<>();

for (MultipartFile multipartFile : multipartFileList) {
String fileName = createFileName(multipartFile.getOriginalFilename());
objectMetadata.setContentLength(multipartFile.getSize());
objectMetadata.setContentType(multipartFile.getContentType());

try (InputStream inputStream = multipartFile.getInputStream()) {
amazonS3.putObject(
new PutObjectRequest(bucket + "/" + FOLDER, fileName, inputStream,
objectMetadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
list.add(amazonS3.getUrl(bucket + "/" + FOLDER, fileName).toString());
} catch (IOException e) {
throw new S3BadRequestException("S3에 이미지를 업로드하는데 실패했습니다.");
}
}
return list;
}

private String createFileName(String fileName) {
return UUID.randomUUID().toString().concat(getFileExtension(fileName));
}

private String getFileExtension(String fileName) {
if (fileName.length() == 0) {
throw new S3BadRequestException("잘못된 파일입니다.");
}
ArrayList<String> fileValidate = new ArrayList<>();
fileValidate.add(".jpg");
fileValidate.add(".jpeg");
fileValidate.add(".png");
fileValidate.add(".JPG");
fileValidate.add(".JPEG");
fileValidate.add(".PNG");
String idxFileName = fileName.substring(fileName.lastIndexOf("."));
if (!fileValidate.contains(idxFileName)) {
throw new S3BadRequestException("잘못된 파일 형식입니다.");
}
return fileName.substring(fileName.lastIndexOf("."));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.clova.anifriends.global.image;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.util.List;
import org.springframework.web.multipart.MultipartFile;

public record UploadImagesRequest(
@NotNull(message = "이미지는 필수 입력 항목입니다.")
@Size(min = 1, max = 5, message = "이미지는 1개 이상 5개 이하로 선택하세요.")
List<MultipartFile> images
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.clova.anifriends.global.image;

import java.util.List;

public record UploadImagesResponse(
List<String> imageUrls
) {
public static UploadImagesResponse from(List<String> imageUrls) {
return new UploadImagesResponse(imageUrls);
}
}
2 changes: 1 addition & 1 deletion src/main/resources/backend-config
Loading

0 comments on commit 04da03b

Please sign in to comment.