diff --git a/src/main/java/com/jangburich/domain/store/domain/Store.java b/src/main/java/com/jangburich/domain/store/domain/Store.java index 0bf97ae..e45c097 100644 --- a/src/main/java/com/jangburich/domain/store/domain/Store.java +++ b/src/main/java/com/jangburich/domain/store/domain/Store.java @@ -104,7 +104,6 @@ public static Store of(Owner owner, StoreCreateRequestDTO storeCreateRequestDTO) newStore.setOwner(owner); newStore.setName(storeCreateRequestDTO.getName()); newStore.setCategory(storeCreateRequestDTO.getCategory()); - newStore.setRepresentativeImage(storeCreateRequestDTO.getRepresentativeImage()); newStore.setIntroduction(storeCreateRequestDTO.getIntroduction()); newStore.setLatitude(storeCreateRequestDTO.getLatitude()); newStore.setLongitude(storeCreateRequestDTO.getLongitude()); diff --git a/src/main/java/com/jangburich/domain/store/domain/StoreCreateRequestDTO.java b/src/main/java/com/jangburich/domain/store/domain/StoreCreateRequestDTO.java index de78afb..cb59b9e 100644 --- a/src/main/java/com/jangburich/domain/store/domain/StoreCreateRequestDTO.java +++ b/src/main/java/com/jangburich/domain/store/domain/StoreCreateRequestDTO.java @@ -22,7 +22,6 @@ public class StoreCreateRequestDTO { @Enumerated(EnumType.STRING) private Category category; - private String representativeImage; private String introduction; private String contactNumber; diff --git a/src/main/java/com/jangburich/domain/store/domain/controller/StoreController.java b/src/main/java/com/jangburich/domain/store/domain/controller/StoreController.java index 4413a9d..b25685f 100644 --- a/src/main/java/com/jangburich/domain/store/domain/controller/StoreController.java +++ b/src/main/java/com/jangburich/domain/store/domain/controller/StoreController.java @@ -2,6 +2,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; @@ -12,10 +13,11 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; import com.jangburich.domain.store.domain.Category; -import com.jangburich.domain.store.domain.StoreAdditionalInfoCreateRequestDTO; import com.jangburich.domain.store.domain.StoreCreateRequestDTO; import com.jangburich.domain.store.domain.StoreGetResponseDTO; import com.jangburich.domain.store.domain.StoreTeamResponseDTO; @@ -30,6 +32,7 @@ import com.jangburich.utils.parser.AuthenticationParser; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -63,14 +66,18 @@ public ResponseCustom> searchStores( } @Operation(summary = "가게 등록", description = "신규 파트너 가게를 등록합니다.") - @PostMapping("/create") + @PostMapping(value = "/create", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseCustom createStore( Authentication authentication, - @RequestBody StoreCreateRequestDTO storeCreateRequestDTO) { - storeService.createStore(AuthenticationParser.parseUserId(authentication), storeCreateRequestDTO); + @Parameter(name = "image", description = "업로드 사진 데이터") + @RequestPart(value = "image") MultipartFile image, + @RequestPart(value = "store") StoreCreateRequestDTO storeCreateRequestDTO) { + + storeService.createStore(AuthenticationParser.parseUserId(authentication), storeCreateRequestDTO, image); return ResponseCustom.OK(Message.builder().message("success").build()); } + @Operation(summary = "가게 정보 수정", description = "가게 정보를 수정합니다.") @PatchMapping("/update") public ResponseCustom updateStore( diff --git a/src/main/java/com/jangburich/domain/store/domain/service/StoreService.java b/src/main/java/com/jangburich/domain/store/domain/service/StoreService.java index 2738656..561a452 100644 --- a/src/main/java/com/jangburich/domain/store/domain/service/StoreService.java +++ b/src/main/java/com/jangburich/domain/store/domain/service/StoreService.java @@ -4,6 +4,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import com.jangburich.domain.menu.domain.Menu; import com.jangburich.domain.menu.domain.MenuCreateRequestDTO; @@ -31,6 +32,7 @@ import com.jangburich.domain.team.domain.repository.TeamRepository; import com.jangburich.domain.user.domain.User; import com.jangburich.domain.user.repository.UserRepository; +import com.jangburich.global.config.s3.S3Service; import com.jangburich.global.error.DefaultNullPointerException; import com.jangburich.global.payload.ErrorCode; @@ -47,9 +49,10 @@ public class StoreService { private final TeamRepository teamRepository; private final TeamChargeHistoryRepository teamChargeHistoryRepository; private final MenuRepository menuRepository; + private final S3Service s3Service; @Transactional - public void createStore(String authentication, StoreCreateRequestDTO storeCreateRequestDTO) { + public void createStore(String authentication, StoreCreateRequestDTO storeCreateRequestDTO, MultipartFile image) { User user = userRepository.findByProviderId(authentication) .orElseThrow(() -> new DefaultNullPointerException(ErrorCode.INVALID_AUTHENTICATION)); @@ -58,6 +61,8 @@ public void createStore(String authentication, StoreCreateRequestDTO storeCreate Store store = storeRepository.save(Store.of(owner, storeCreateRequestDTO)); + store.setRepresentativeImage(s3Service.uploadImageToS3(image)); + for (MenuCreateRequestDTO menuCreateRequestDTO : storeCreateRequestDTO.getMenuCreateRequestDTOS()) { menuRepository.save(Menu.create(menuCreateRequestDTO.getName(), menuCreateRequestDTO.getDescription(), menuCreateRequestDTO.getImage_url(), menuCreateRequestDTO.getPrice(), store)); diff --git a/src/main/java/com/jangburich/global/config/s3/AmazonS3Config.java b/src/main/java/com/jangburich/global/config/s3/AmazonS3Config.java new file mode 100644 index 0000000..1813172 --- /dev/null +++ b/src/main/java/com/jangburich/global/config/s3/AmazonS3Config.java @@ -0,0 +1,29 @@ +package com.jangburich.global.config.s3; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; + +@Configuration +public class AmazonS3Config { + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3 amazonS3() { + BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + return AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } +} diff --git a/src/main/java/com/jangburich/global/config/s3/MultipartConfig.java b/src/main/java/com/jangburich/global/config/s3/MultipartConfig.java new file mode 100644 index 0000000..a96467d --- /dev/null +++ b/src/main/java/com/jangburich/global/config/s3/MultipartConfig.java @@ -0,0 +1,36 @@ +package com.jangburich.global.config.s3; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.MultipartConfigFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.unit.DataSize; +import org.springframework.web.multipart.MultipartResolver; +import org.springframework.web.multipart.support.StandardServletMultipartResolver; + +import jakarta.servlet.MultipartConfigElement; + +@Configuration +public class MultipartConfig { + + @Value("${file.multipart.maxUploadSize}") + private long maxUploadSize; + + @Value("${file.multipart.maxUploadSizePerFile}") + private long maxUploadSizePerFile; + + @Bean + public MultipartResolver multipartResolver() { + StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver(); + return multipartResolver; + } + + @Bean + public MultipartConfigElement multipartConfigElement() { + MultipartConfigFactory factory = new MultipartConfigFactory(); + factory.setMaxRequestSize(DataSize.ofBytes(maxUploadSize)); + factory.setMaxFileSize(DataSize.ofBytes(maxUploadSizePerFile)); + + return factory.createMultipartConfig(); + } +} \ No newline at end of file diff --git a/src/main/java/com/jangburich/global/config/s3/MultipartJackson2HttpMessageConverter.java b/src/main/java/com/jangburich/global/config/s3/MultipartJackson2HttpMessageConverter.java new file mode 100644 index 0000000..0f23811 --- /dev/null +++ b/src/main/java/com/jangburich/global/config/s3/MultipartJackson2HttpMessageConverter.java @@ -0,0 +1,35 @@ +package com.jangburich.global.config.s3; + +import java.lang.reflect.Type; + +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@Component +public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter { + + /** + * Converter for support http request with header Content-Type: multipart/form-data + */ + public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) { + super(objectMapper, MediaType.APPLICATION_OCTET_STREAM); + } + + @Override + public boolean canWrite(Class clazz, MediaType mediaType) { + return false; + } + + @Override + public boolean canWrite(Type type, Class clazz, MediaType mediaType) { + return false; + } + + @Override + protected boolean canWrite(MediaType mediaType) { + return false; + } +} \ No newline at end of file diff --git a/src/main/java/com/jangburich/global/config/s3/S3Service.java b/src/main/java/com/jangburich/global/config/s3/S3Service.java new file mode 100644 index 0000000..51589b8 --- /dev/null +++ b/src/main/java/com/jangburich/global/config/s3/S3Service.java @@ -0,0 +1,53 @@ +package com.jangburich.global.config.s3; + +import java.io.IOException; +import java.util.UUID; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.DeleteObjectRequest; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import com.amazonaws.services.s3.model.PutObjectResult; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Service +public class S3Service { + private final AmazonS3 amazonS3; + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + private String changedImageName(String originName) { + String random = UUID.randomUUID().toString(); + return random +'-' + originName; + } + + public String uploadImageToS3(MultipartFile image) { + String originName = image.getOriginalFilename(); + String ext = originName.substring(originName.lastIndexOf(".")); + String changedName = changedImageName(originName); + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(image.getSize()); + metadata.setContentType("image/" + ext); + + try { + PutObjectResult putObjectResult = amazonS3.putObject(new PutObjectRequest( + bucket, changedName, image.getInputStream(), metadata + )); + } catch (IOException e) { + throw new RuntimeException("ImageUploadException"); + } + return amazonS3.getUrl(bucket, changedName).toString(); + } + + public void deleteImageOnS3(String imgURL) { + String fileName = imgURL.substring(imgURL.lastIndexOf("-"));; + + amazonS3.deleteObject(new DeleteObjectRequest(bucket, fileName)); + } +} \ No newline at end of file