Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[refactor/#267] 이미지 service 관련 리팩 #307

Open
wants to merge 9 commits into
base: dev
Choose a base branch
from
34 changes: 10 additions & 24 deletions src/main/java/umc/th/juinjang/controller/ImageController.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -19,49 +18,36 @@
import umc.th.juinjang.apiPayload.ApiResponse;
import umc.th.juinjang.apiPayload.code.status.SuccessStatus;
import umc.th.juinjang.model.dto.image.ImageDeleteRequestDTO;
import umc.th.juinjang.model.dto.image.ImageListResponseDTO;
import umc.th.juinjang.model.dto.image.ImagesGetResponse;
import umc.th.juinjang.service.image.ImageCommandService;
import umc.th.juinjang.service.image.ImageQueryService;
import umc.th.juinjang.service.limjang.LimjangCommandService;

@RestController
@RequestMapping("/api/limjang/image")
@RequiredArgsConstructor
@Validated
public class ImageController {

private final LimjangCommandService limjangCommandService;
private final ImageCommandService imageCommandService;
private final ImageQueryService imageQueryService;

@CrossOrigin
@Operation(summary = "사진 생성 API", description = "사진 업로드 api입니다.")
@PostMapping(value = "", consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ApiResponse uploadImages(
@RequestParam(name = "limjangId") Long limjangId, @RequestPart(name = "images") List<MultipartFile> images)
{
imageCommandService.uploadImages(limjangId ,images);
return ApiResponse.onSuccess(SuccessStatus.IMAGE_UPDATE);
}
@Operation(summary = "사진 생성 API", description = "사진 업로드 api입니다.")
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ApiResponse uploadImages(@RequestParam(name = "limjangId") Long limjangId, @RequestPart(name = "images") List<MultipartFile> images) {
imageCommandService.createImages(limjangId, images);
return ApiResponse.onSuccess(SuccessStatus.IMAGE_UPDATE);
}

@CrossOrigin
@Operation(summary = "사진 조회 API", description = "사진을 조회하는 api입니다.")
@GetMapping(value = "{limjangId}")
public ApiResponse<ImageListResponseDTO.ImagesListDTO> uploadImages(
@PathVariable(name = "limjangId") @Valid Long limjangId)
{
@GetMapping(value = "/{limjangId}")
public ApiResponse<ImagesGetResponse> uploadImages(@PathVariable(name = "limjangId") @Valid Long limjangId) {
return ApiResponse.onSuccess(imageQueryService.getImageList(limjangId));
}

@CrossOrigin
@Operation(summary = "이미지 선택 삭제", description = "이미지 게시글을 여러 개 선택해서 삭제하는 api입니다.")
@PostMapping("/delete")
public ApiResponse deleteImage(@RequestBody @Valid ImageDeleteRequestDTO.DeleteDto deleteIds
){

public ApiResponse deleteImage(@RequestBody @Valid ImageDeleteRequestDTO.DeleteDto deleteIds) {
imageCommandService.deleteImages(deleteIds);
return ApiResponse.onSuccess(SuccessStatus.IMAGE_DELETE);
}

}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package umc.th.juinjang.model.dto.image;

import java.util.List;
import umc.th.juinjang.model.entity.Image;

public record ImagesGetResponse (List<ImageResponse> images) {
record ImageResponse(Long imageId, String imageUrl) {
static ImageResponse of(Long imageId, String imageUrl) {
return new ImageResponse(imageId, imageUrl);
}
}

public static ImagesGetResponse of(List<Image> images) {
return new ImagesGetResponse(images.stream().map(it -> ImageResponse.of(it.getImageId(), it.getImageUrl())).toList());
}
}


6 changes: 6 additions & 0 deletions src/main/java/umc/th/juinjang/model/entity/Image.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,10 @@ public class Image extends BaseEntity {
@Column(nullable = false)
private String imageUrl;

public static Image create(String imageUrl, Limjang limjang) {
return Image.builder()
.imageUrl(imageUrl)
.limjangId(limjang)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,8 @@ public interface LimjangRepository extends JpaRepository<Limjang, Long>, Limjang
Optional<Limjang> findByLimjangIdAndMemberIdWithLimjangPriceAndDeletedIsFalse(@Param("id") Long id, @Param("member") Member member);

@Query("SELECT l FROM Limjang l join fetch l.limjangPrice left join fetch l.report WHERE l.limjangId = :id AND l.memberId = :member AND l.deleted = false")
Optional<Limjang> findByLimjangIdAndDeletedIsFalse(@Param("id") Long id, @Param("member") Member member);
Optional<Limjang> findByLimjangIdAndMemberAndDeletedIsFalse(@Param("id") Long id, @Param("member") Member member);

@Query("SELECT l FROM Limjang l WHERE l.limjangId = :id AND l.deleted = false")
Optional<Limjang> findByLimjangIdAndDeletedIsFalse(@Param("id") Long id);
}
50 changes: 30 additions & 20 deletions src/main/java/umc/th/juinjang/service/external/S3Service.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,41 @@
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import java.io.File;
import java.io.IOException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.beans.factory.annotation.Value;
import umc.th.juinjang.apiPayload.code.status.ErrorStatus;
import umc.th.juinjang.apiPayload.exception.handler.S3Handler;

@Slf4j
@RequiredArgsConstructor // final 멤버변수가 있으면 생성자 항목에 포함시킴
@Component
@RequiredArgsConstructor
@Service
public class S3Service {

private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final AmazonS3Client amazonS3Client;

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

// MultipartFile을 전달받아 File로 전환한 후 S3에 업로드
public String upload(MultipartFile multipartFile, String dirName) throws IOException {
File uploadFile = convert(multipartFile)
.orElseThrow(() -> new IllegalArgumentException("MultipartFile -> File 전환 실패"));
return upload(uploadFile, dirName, multipartFile.getInputStream(), multipartFile.getSize(), multipartFile.getContentType());
public String upload(MultipartFile multipartFile, String dirName) {
File uploadFile = convert(multipartFile).orElseThrow(() -> new S3Handler(ErrorStatus.IMAGE_EMPTY));

try {
return upload(uploadFile, dirName, multipartFile.getInputStream(), multipartFile.getSize(), multipartFile.getContentType());
} catch (Exception e) {
logger.error("파일 업로드 중 error 발생");
throw new S3Handler(ErrorStatus._INTERNAL_SERVER_ERROR);
}
}

private String upload(File uploadFile,String dirName, InputStream inputStream, Long fileSize, String contentType) {
Expand Down Expand Up @@ -72,16 +76,22 @@ private void removeNewFile(File targetFile) {
}
}

private Optional<File> convert(MultipartFile file) throws IOException {
String originalFilename = file.getOriginalFilename();
String safeFilename = originalFilename.replaceAll("[^a-zA-Z0-9.-]", "_");
File convertFile = new File(safeFilename);

if(convertFile.createNewFile()) {
try (FileOutputStream fos = new FileOutputStream(convertFile)) {
fos.write(file.getBytes());
private Optional<File> convert(MultipartFile file) {
try {
String originalFilename = file.getOriginalFilename();
String safeFilename = originalFilename.replaceAll("[^a-zA-Z0-9.-]", "_");
File convertFile = new File(safeFilename);

if (convertFile.createNewFile()) {
try (FileOutputStream fos = new FileOutputStream(convertFile)) {
fos.write(file.getBytes());
}
return Optional.of(convertFile);
} else {
return Optional.empty();
}
return Optional.of(convertFile);
} catch (Exception e) {
logger.error("파일 변환 중 error 발생" +e);
}
return Optional.empty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import umc.th.juinjang.model.dto.image.ImageDeleteRequestDTO;

public interface ImageCommandService {
void uploadImages(Long limjangId, List<MultipartFile> images);
void createImages(long limjangId, List<MultipartFile> images);

void deleteImages(ImageDeleteRequestDTO.DeleteDto ids);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,73 +1,49 @@
package umc.th.juinjang.service.image;

import java.io.IOException;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import umc.th.juinjang.apiPayload.code.status.ErrorStatus;
import umc.th.juinjang.apiPayload.exception.handler.LimjangHandler;
import umc.th.juinjang.converter.image.ImageUploadConverter;
import umc.th.juinjang.model.dto.image.ImageDeleteRequestDTO;
import umc.th.juinjang.model.entity.Image;
import umc.th.juinjang.model.entity.Limjang;
import umc.th.juinjang.repository.image.ImageRepository;
import umc.th.juinjang.repository.limjang.LimjangRepository;
import umc.th.juinjang.service.external.S3Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class ImageCommandServiceImpl implements ImageCommandService {

private final ImageRepository imageRepository;
private final LimjangRepository limjangRepository;
private final S3Service s3Service;
private final String DIR_NAME = "image";

@Override
@Transactional
public void uploadImages(Long limjangId, List<MultipartFile> images) {

Limjang limjang = limjangRepository.findById(limjangId)
.orElseThrow(()-> new LimjangHandler(ErrorStatus.LIMJANG_NOTFOUND_ERROR));

images.forEach(it -> {
try {
if (!it.isEmpty()) {
String storedFileName = s3Service.upload(it, "image");
Image image = ImageUploadConverter.toImageDto(storedFileName, limjang);
limjang.saveImages(image);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
});
public void createImages(final long limjangId, final List<MultipartFile> files) {
Limjang limjang = getLimjangById(limjangId);
for (MultipartFile file : files) {
String imageUrl = s3Service.upload(file, DIR_NAME);
imageRepository.save(Image.create(imageUrl, limjang));
}
}

private Limjang getLimjangById(final long limjangId) {
return limjangRepository.findById(limjangId).orElseThrow(()-> new LimjangHandler(ErrorStatus.LIMJANG_NOTFOUND_ERROR));
}

@Override
@Transactional
public void deleteImages(ImageDeleteRequestDTO.DeleteDto ids
) { //이미지 id로 삭제한다...!
List<Long> deleteIds = ids.getImageIdList();

try {

//s3에서 삭제
List<Image> imageList = imageRepository.findAllById(deleteIds);

imageList.forEach(image -> {
s3Service.deleteFile(image.getImageUrl());
imageRepository.deleteById(image.getImageId());
});
} catch (DataIntegrityViolationException e) {
throw new LimjangHandler(ErrorStatus.IMAGE_DELETE_NOT_COMPLETE);
} catch (EmptyResultDataAccessException e) {
throw new LimjangHandler(ErrorStatus.IMAGE_DELETE_NOT_FOUND);
}
public void deleteImages(final ImageDeleteRequestDTO.DeleteDto ids) {
List<Image> images = imageRepository.findAllById(ids.getImageIdList());
for (Image image : images) {
s3Service.deleteFile(image.getImageUrl());
imageRepository.deleteById(image.getImageId());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package umc.th.juinjang.service.image;

import umc.th.juinjang.model.dto.image.ImageListResponseDTO;
import umc.th.juinjang.model.dto.image.ImagesGetResponse;

public interface ImageQueryService {
ImageListResponseDTO.ImagesListDTO getImageList(Long limjangId);
ImagesGetResponse getImageList(long limjangId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
import org.springframework.transaction.annotation.Transactional;
import umc.th.juinjang.apiPayload.code.status.ErrorStatus;
import umc.th.juinjang.apiPayload.exception.handler.LimjangHandler;
import umc.th.juinjang.converter.image.ImageListConverter;
import umc.th.juinjang.model.dto.image.ImageListResponseDTO;
import umc.th.juinjang.model.dto.image.ImagesGetResponse;
import umc.th.juinjang.model.entity.Image;
import umc.th.juinjang.model.entity.Limjang;
import umc.th.juinjang.repository.image.ImageRepository;
Expand All @@ -24,13 +23,13 @@ public class ImageQueryServiceImpl implements ImageQueryService {

@Override
@Transactional(readOnly = true)
public ImageListResponseDTO.ImagesListDTO getImageList(Long limjangId) {
Limjang findLimjang = limjangRepository.findById(limjangId)
.orElseThrow(() -> new LimjangHandler(ErrorStatus.LIMJANG_NOTFOUND_ERROR));

List<Image> imageList = imageRepository.findImagesByLimjangId(findLimjang);

return ImageListConverter.toImageListDto(imageList);
public ImagesGetResponse getImageList(final long limjangId) {
Limjang limjang = getLimjang(limjangId);
List<Image> images = imageRepository.findImagesByLimjangId(limjang);
return ImagesGetResponse.of(images);
}

private Limjang getLimjang(final long limjangId) {
return limjangRepository.findByLimjangIdAndDeletedIsFalse(limjangId).orElseThrow(() -> new LimjangHandler(ErrorStatus.LIMJANG_NOTFOUND_ERROR));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public LimjangDetailGetResponse getDetail(long id, Member member) {
}

private Limjang getByIdAndMember(Long id, Member member) {
return limjangRepository.findByLimjangIdAndDeletedIsFalse(id, member).orElseThrow(() -> new LimjangHandler(ErrorStatus.LIMJANG_NOTFOUND_ERROR));
return limjangRepository.findByLimjangIdAndMemberAndDeletedIsFalse(id, member).orElseThrow(() -> new LimjangHandler(ErrorStatus.LIMJANG_NOTFOUND_ERROR));
}

@Override
Expand Down