diff --git a/Api/src/main/java/picasso/server/api/HomeController.java b/Api/src/main/java/picasso/server/api/HomeController.java index 01284ec3..2c8dd227 100644 --- a/Api/src/main/java/picasso/server/api/HomeController.java +++ b/Api/src/main/java/picasso/server/api/HomeController.java @@ -10,6 +10,6 @@ public class HomeController { @GetMapping("/") public String index() { - return "/index"; + return "index"; } } diff --git a/Api/src/main/java/picasso/server/api/naver_test/NaverController.java b/Api/src/main/java/picasso/server/api/naver_test/NaverController.java new file mode 100644 index 00000000..f1077cc5 --- /dev/null +++ b/Api/src/main/java/picasso/server/api/naver_test/NaverController.java @@ -0,0 +1,70 @@ +package picasso.server.api.naver_test; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; +import picasso.server.common.properties.NaverObjectStorageProperties; +import picasso.server.common.util.NaverObjectStorageUsageType; +import picasso.server.common.util.NaverObjectStorageUtil; + +import java.util.ArrayList; +import java.util.List; +/* +TODO : 기능개발 완료이후 삭제해야 할 파일 + */ + +@Slf4j +@RequiredArgsConstructor +@Controller +@RequestMapping("/naver") +public class NaverController { + private final NaverObjectStorageProperties naverObjectStorageProperties; + private final NaverObjectStorageUtil naverObjectStorageUtil; + + @GetMapping("/properties-test") + @ResponseBody + public ResponseEntity naverTest() { + return ResponseEntity.ok( + naverObjectStorageProperties.getEndPoint() + " " + + naverObjectStorageProperties.getRegionName() + ); + } + + @GetMapping("/file-upload-test-form") + public String uploadTest() { + return "test/upload-form"; + } + + @PostMapping("/file-upload-test") + @ResponseBody + public ResponseEntity fileUploadTest( + @RequestPart(value = "profile", required = false) MultipartFile profile, + @RequestPart(value = "paint", required = false) MultipartFile paint + ) { + List filePath = new ArrayList<>(); + if (!profile.isEmpty()) + filePath.add( + naverObjectStorageUtil.storageFileUpload( + NaverObjectStorageUsageType.PROFILE, profile + ) + ); + if (!paint.isEmpty()) + filePath.add( + naverObjectStorageUtil.storageFileUpload( + NaverObjectStorageUsageType.PAINT, paint + ) + ); + if (filePath.isEmpty()) + filePath.add("업로드한 파일이 없음"); + + return ResponseEntity.ok(filePath); + } + +} diff --git a/Api/src/main/resources/application.yml b/Api/src/main/resources/application.yml index 562782cb..6f4785fb 100644 --- a/Api/src/main/resources/application.yml +++ b/Api/src/main/resources/application.yml @@ -4,7 +4,7 @@ spring: group: local: - infra-local - - commons-local + - common-local - domain-local dev: - infra-dev @@ -18,9 +18,6 @@ spring: static-path-pattern: static/** servlet: multipart: - file-size-threshold: 1MB -# TODO : MultiPart 활용시 경로 변경 필요 - location: ~/template-project/upload max-file-size: 20MB max-request-size: 200MB web: @@ -31,7 +28,6 @@ spring: server: - port: 8080 shutdown: graceful servlet: session: @@ -45,6 +41,8 @@ spring: devtools: livereload: enabled: true +server: + port: 8080 test-message : local-test @@ -53,5 +51,8 @@ spring: config: activate: on-profile: dev +server: + port: 80 + test-message : dev-test diff --git a/Api/src/main/resources/templates/test/upload-form.html b/Api/src/main/resources/templates/test/upload-form.html new file mode 100644 index 00000000..2acb886d --- /dev/null +++ b/Api/src/main/resources/templates/test/upload-form.html @@ -0,0 +1,20 @@ + + + + + +

Upload Test Page

+
+ 업로드 대상 파일 (프로필) :
+ 업로드 대상 파일 (미술품게시판) :
+ +
+ + + \ No newline at end of file diff --git a/Common/build.gradle b/Common/build.gradle index e6df4500..9e8e670a 100644 --- a/Common/build.gradle +++ b/Common/build.gradle @@ -6,4 +6,10 @@ dependencies { api 'org.springframework.boot:spring-boot-starter-aop' api 'org.springframework.boot:spring-boot-starter-validation' + + //File + api 'commons-io:commons-io:2.13.0' + + // Amazon AWS Java SDK s3 + api 'com.amazonaws:aws-java-sdk-s3:1.12.530' } \ No newline at end of file diff --git a/Common/src/main/java/picasso/server/common/config/ConfigurationPropertiesConfig.java b/Common/src/main/java/picasso/server/common/config/ConfigurationPropertiesConfig.java index 738b2fde..7aec71b9 100644 --- a/Common/src/main/java/picasso/server/common/config/ConfigurationPropertiesConfig.java +++ b/Common/src/main/java/picasso/server/common/config/ConfigurationPropertiesConfig.java @@ -2,9 +2,13 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; +import picasso.server.common.properties.NaverObjectStorageProperties; @Configuration -@EnableConfigurationProperties({ -}) +@EnableConfigurationProperties( + { + NaverObjectStorageProperties.class + } +) public class ConfigurationPropertiesConfig { } diff --git a/Common/src/main/java/picasso/server/common/config/NaverObjectStorageConfig.java b/Common/src/main/java/picasso/server/common/config/NaverObjectStorageConfig.java new file mode 100644 index 00000000..5f2c6d31 --- /dev/null +++ b/Common/src/main/java/picasso/server/common/config/NaverObjectStorageConfig.java @@ -0,0 +1,56 @@ +package picasso.server.common.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import picasso.server.common.properties.NaverObjectStorageProperties; + +@Slf4j +@RequiredArgsConstructor +@Configuration +public class NaverObjectStorageConfig { + private final NaverObjectStorageProperties naverObjectStorageProperties; + + @Bean + public AmazonS3 storageObject() { + log.info("Create NaverObjectStorageConfig Bean"); + return AmazonS3ClientBuilder.standard() + .withEndpointConfiguration(this.getEndpointConfig()) + .withCredentials(this.getCredentialsProvier()) + .build(); + } + + private AwsClientBuilder.EndpointConfiguration getEndpointConfig() { + log.info( + "Create EndPoint Object >>> EndPoint : {}, RegionName : {}", + this.naverObjectStorageProperties.getEndPoint(), + this.naverObjectStorageProperties.getRegionName() + ); + + return new AwsClientBuilder.EndpointConfiguration( + this.naverObjectStorageProperties.getEndPoint(), + this.naverObjectStorageProperties.getRegionName() + ); + } + + private AWSStaticCredentialsProvider getCredentialsProvier() { + log.info( + "Create CredentialsProvider >>> AccessKey : {}, SecretKey : {}", + this.naverObjectStorageProperties.getAccessKey(), + this.naverObjectStorageProperties.getSecretKey() + ); + + return new AWSStaticCredentialsProvider( + new BasicAWSCredentials( + naverObjectStorageProperties.getAccessKey(), + naverObjectStorageProperties.getSecretKey() + ) + ); + } +} diff --git a/Common/src/main/java/picasso/server/common/exception/FileDeleteException.java b/Common/src/main/java/picasso/server/common/exception/FileDeleteException.java new file mode 100644 index 00000000..1f5fb6f3 --- /dev/null +++ b/Common/src/main/java/picasso/server/common/exception/FileDeleteException.java @@ -0,0 +1,14 @@ +package picasso.server.common.exception; + +import static picasso.server.common.exception.GlobalException.FILE_DELETE_ERROR; + +public class FileDeleteException extends BaseException { + + public static final BaseException EXCEPTION = new FileDeleteException(); + + private FileDeleteException() { + super(FILE_DELETE_ERROR); + } + +} + diff --git a/Common/src/main/java/picasso/server/common/exception/FileIOException.java b/Common/src/main/java/picasso/server/common/exception/FileIOException.java new file mode 100644 index 00000000..1fb331b1 --- /dev/null +++ b/Common/src/main/java/picasso/server/common/exception/FileIOException.java @@ -0,0 +1,14 @@ +package picasso.server.common.exception; + +import static picasso.server.common.exception.GlobalException.FILE_IO_ERROR; + +public class FileIOException extends BaseException { + + public static final BaseException EXCEPTION = new FileIOException(); + + private FileIOException() { + super(FILE_IO_ERROR); + } + +} + diff --git a/Common/src/main/java/picasso/server/common/exception/FileUploadException.java b/Common/src/main/java/picasso/server/common/exception/FileUploadException.java new file mode 100644 index 00000000..5e2d8dc6 --- /dev/null +++ b/Common/src/main/java/picasso/server/common/exception/FileUploadException.java @@ -0,0 +1,14 @@ +package picasso.server.common.exception; + +import static picasso.server.common.exception.GlobalException.FILE_UPLOAD_ERROR; + +public class FileUploadException extends BaseException { + + public static final BaseException EXCEPTION = new FileUploadException(); + + private FileUploadException() { + super(FILE_UPLOAD_ERROR); + } + +} + diff --git a/Common/src/main/java/picasso/server/common/exception/GlobalException.java b/Common/src/main/java/picasso/server/common/exception/GlobalException.java index 2637300d..a407be1c 100644 --- a/Common/src/main/java/picasso/server/common/exception/GlobalException.java +++ b/Common/src/main/java/picasso/server/common/exception/GlobalException.java @@ -14,6 +14,9 @@ public enum GlobalException implements BaseErrorCode { METHOD_ARGUMENT_ERROR(BAD_REQUEST.value(), "메서드 인자가 유효하지 않거나 @Valid를 통과하지 못하여 발생하는 예외입니다."), INTERNAL_SERVER_ERRORS(INTERNAL_SERVER_ERROR.value(), "서버 내부 오류입니다."), DATE_FORMAT_ERROR(BAD_REQUEST.value(), "날짜 형식을 확인해주세요."), + FILE_UPLOAD_ERROR(INTERNAL_SERVER_ERROR.value(), "파일 업로드 중 오류가 발생하였습니다"), + FILE_DELETE_ERROR(INTERNAL_SERVER_ERROR.value(), "파일 삭제 중 오류가 발생하였습니다"), + FILE_IO_ERROR(INTERNAL_SERVER_ERROR.value(), "파일 변환 중 오류가 발생하였습니다") ; private final Integer statusCode; diff --git a/Common/src/main/java/picasso/server/common/properties/NaverObjectStorageProperties.java b/Common/src/main/java/picasso/server/common/properties/NaverObjectStorageProperties.java new file mode 100644 index 00000000..632ecbae --- /dev/null +++ b/Common/src/main/java/picasso/server/common/properties/NaverObjectStorageProperties.java @@ -0,0 +1,19 @@ +package picasso.server.common.properties; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@ToString +@AllArgsConstructor +@ConfigurationProperties(prefix = "naver.storage") +public class NaverObjectStorageProperties { + + private String endPoint; + private String regionName; + private String accessKey; + private String secretKey; + private String bucketName; +} diff --git a/Common/src/main/java/picasso/server/common/util/NaverObjectStorageUsageType.java b/Common/src/main/java/picasso/server/common/util/NaverObjectStorageUsageType.java new file mode 100644 index 00000000..7ca19355 --- /dev/null +++ b/Common/src/main/java/picasso/server/common/util/NaverObjectStorageUsageType.java @@ -0,0 +1,14 @@ +package picasso.server.common.util; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum NaverObjectStorageUsageType { + PROFILE("profile") + , PAINT("paint"); + + + private String path; +} diff --git a/Common/src/main/java/picasso/server/common/util/NaverObjectStorageUtil.java b/Common/src/main/java/picasso/server/common/util/NaverObjectStorageUtil.java new file mode 100644 index 00000000..21e7a0f3 --- /dev/null +++ b/Common/src/main/java/picasso/server/common/util/NaverObjectStorageUtil.java @@ -0,0 +1,124 @@ +package picasso.server.common.util; + +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 lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FilenameUtils; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; +import picasso.server.common.exception.FileDeleteException; +import picasso.server.common.exception.FileIOException; +import picasso.server.common.exception.FileUploadException; +import picasso.server.common.properties.NaverObjectStorageProperties; + +import java.io.IOException; +import java.io.InputStream; +import java.util.UUID; + + +@Slf4j +@RequiredArgsConstructor +@Component +public class NaverObjectStorageUtil { + private final AmazonS3 storageObject; + private final NaverObjectStorageProperties naverObjectStorageProperties; + private final Environment environment; + + /** + * 사용처, MultipartFile만 제공하면 업로드가 가능하다. + * + * @param usageType + * @param file + * @return + * @throws FileUploadException + */ + public String storageFileUpload(NaverObjectStorageUsageType usageType, MultipartFile file) throws FileUploadException { + try { + String filePath = getPath(usageType, getFileUUIDNameByMultipartFile(file)); + + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentType(file.getContentType()); + metadata.setContentLength(file.getBytes().length); + + uploadFile( + usageType, filePath, file.getInputStream(), metadata + ); + log.info("File Upload : {}", filePath); + return filePath; + } catch (IOException e) { + throw FileIOException.EXCEPTION; + } catch (Exception e) { + log.error(e.getMessage()); + throw FileUploadException.EXCEPTION; + } + } + + /** + * 파일 삭제를 진행할 경우 사용한다 + * + * @param filePath + * @throws FileDeleteException + */ + public void storageFileDelete(String filePath) throws FileDeleteException { + try { + storageObject.deleteObject( + naverObjectStorageProperties.getBucketName(), + filePath + ); + log.info("File Delete : {}", filePath); + } catch (Exception e) { + throw FileDeleteException.EXCEPTION; + } + } + + + /** + * 실제 File을 NaverObject Storage에 Upload + * + * @param usageType + * @param filePath + * @param uploadTarget + * @param metadata + */ + private void uploadFile(NaverObjectStorageUsageType usageType, String filePath, InputStream uploadTarget, ObjectMetadata metadata) { + storageObject.putObject( + new PutObjectRequest( + naverObjectStorageProperties.getBucketName(), + filePath, + uploadTarget, + metadata + ).withCannedAcl(CannedAccessControlList.PublicRead) + ); + } + + /** + * UUID.png 등으로 제작하기 위함 + * + * @param file + * @return + */ + private String getFileUUIDNameByMultipartFile(MultipartFile file) { + return UUID.randomUUID() + + "." + + FilenameUtils.getExtension(file.getOriginalFilename()); + } + + /** + * ObjectStorage에 저장될 File Path + * + * @param usageType + * @param fileName + * @return + */ + private String getPath(NaverObjectStorageUsageType usageType, String fileName) { + return environment.getProperty("spring.profiles.active", "default") + "/" + + usageType.getPath() + "/" + + fileName; + } + + +} diff --git a/Common/src/main/resources/application-common.yml b/Common/src/main/resources/application-common.yml index bbda7cf7..761cc112 100644 --- a/Common/src/main/resources/application-common.yml +++ b/Common/src/main/resources/application-common.yml @@ -1,8 +1,14 @@ +naver: + storage: + end-point: https://kr.object.ncloudstorage.com + region-name: kr-standard + bucket-name: picasso-bucket + +--- spring: config: activate: on-profile: common-local - --- spring: config: diff --git a/Domain/src/main/resources/data.sql b/Domain/src/main/resources/data.sql new file mode 100644 index 00000000..47af9e12 --- /dev/null +++ b/Domain/src/main/resources/data.sql @@ -0,0 +1,10 @@ +insert into t_test(name, param1,param2,param3,param4) +values ('aaaa', 'a','b','c','d'); +insert into t_test(name, param1,param2,param3,param4) +values ('dddd', 'a','b','c','d'); +insert into t_test(name, param1,param2,param3,param4) +values ('cccc', 'a','b','c','d'); +insert into t_test(name, param1,param2,param3,param4) +values ('dddd', 'a','b','c','d'); +insert into t_test(name, param1,param2,param3,param4) +values ('ffff', 'a','b','c','d');