Skip to content

Commit

Permalink
Merge pull request #41 from h-jjang/#17-이미지-업로드-구현
Browse files Browse the repository at this point in the history
17 이미지 업로드 구현
  • Loading branch information
0BVer authored May 3, 2022
2 parents 14f0ce8 + a354468 commit c7ac128
Show file tree
Hide file tree
Showing 23 changed files with 294 additions and 207 deletions.
2 changes: 2 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,5 @@ out/
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

/src/main/resources/application-aws.yml
/src/main/resources/application-credentials.yml
1 change: 1 addition & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.flywaydb:flyway-core'
implementation 'org.springframework.kafka:spring-kafka'
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.hjjang.backend.domain.image.controller;

import com.hjjang.backend.domain.image.domain.entity.Image;
import com.hjjang.backend.domain.image.dto.ImagePathResponse;
import com.hjjang.backend.domain.image.service.ImageService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

import static com.hjjang.backend.global.util.HttpStatusResponseEntity.RESPONSE_OK;

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

private final ImageService imageService;

@GetMapping()
public ResponseEntity<List<ImagePathResponse>> findAllImage() {
return ResponseEntity.ok(imageService
.findAll()
.stream()
.map(ImagePathResponse::of)
.collect(Collectors.toList())
);
}

@PostMapping()
public ResponseEntity<ImagePathResponse> uploadImage(@RequestParam("images") MultipartFile multipartFile) throws IOException {
return ResponseEntity.ok(ImagePathResponse
.of(imageService
.uploadNewImage(multipartFile)
)
);
}

@GetMapping("/{imageId}")
public ResponseEntity<ImagePathResponse> findImage(@PathVariable Long imageId) {
return ResponseEntity.ok(ImagePathResponse
.of(imageService
.findImageById(imageId)
)
);
}

@DeleteMapping("/{imageId}")
public ResponseEntity<HttpStatus> deleteImage(@PathVariable Long imageId) {
Image image = imageService.findImageById(imageId);
imageService.removeImage(image);

return RESPONSE_OK;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.hjjang.backend.infra.image.domain.entity;
package com.hjjang.backend.domain.image.domain.entity;

import lombok.*;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.hjjang.backend.infra.image.domain.repository;
package com.hjjang.backend.domain.image.domain.repository;

import com.hjjang.backend.infra.image.domain.entity.Image;
import com.hjjang.backend.domain.image.domain.entity.Image;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ImageRepository extends JpaRepository<Image, Long> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.hjjang.backend.domain.image.dto;

import com.hjjang.backend.domain.image.domain.entity.Image;
import lombok.Builder;
import lombok.Getter;

@Builder
@Getter
public class ImagePathResponse {

private Long id;
private String path;

public static ImagePathResponse of(Image image) {
return ImagePathResponse.builder()
.id(image.getId())
.path(image.getPath())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.hjjang.backend.infra.image.exception;
package com.hjjang.backend.domain.image.exception;

public class ImageNotFoundException extends RuntimeException {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.hjjang.backend.domain.image.service;

import com.hjjang.backend.domain.image.domain.repository.ImageRepository;
import com.hjjang.backend.domain.image.domain.entity.Image;
import com.hjjang.backend.domain.image.exception.ImageNotFoundException;
import com.hjjang.backend.infra.aws.service.S3ImageUploader;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.transaction.Transactional;
import java.io.IOException;
import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional
public class ImageService {
private final ImageRepository imageRepository;

private final S3ImageUploader imageUploader;

public List<Image> findAll() {
return imageRepository.findAll();
}

public Image findImageById(Long imageId) {
return imageRepository.findById(imageId).orElseThrow(ImageNotFoundException::new);
}

public Image uploadNewImage(MultipartFile multipartFile) throws IOException {
return imageRepository.save(new Image(imageUploader.upload(multipartFile)));
}

public void removeImage(Image image) {
image.removeImage();
imageRepository.save(image);
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.hjjang.backend.infra.image.component;
package com.hjjang.backend.domain.image.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
Expand All @@ -13,35 +13,27 @@

@Slf4j
@RequiredArgsConstructor
@Component
public class LocalUploader implements ImageUploader {
@Service
public class ImageUploader {

@Override
public String upload(MultipartFile multipartFile) throws IOException {
File uploadFile =
this.localUpload(multipartFile) // 파일 변환할 수 없으면 에러
.orElseThrow(
() -> new IllegalArgumentException("error: MultipartFile -> File convert fail"));
return uploadFile.getName();
return null;
}

@Override
public String changeFileName(MultipartFile uploadFile) {
return UUID.randomUUID() + uploadFile.getOriginalFilename(); // 저장될 파일 이름 변환
return UUID.randomUUID() + ".png"; // 저장될 파일 이름 변환
}

// 로컬에 파일 업로드 하기
@Override
public Optional<File> localUpload(MultipartFile file) throws IOException {
File convertFile = new File(System.getProperty("user.dir") + "/backend/src/main/resources/images/" + changeFileName(file));
File convertFile = new File(Path.imageSavePath.getPath() + changeFileName(file));
if (convertFile.createNewFile()) { // 바로 위에서 지정한 경로에 File이 생성됨 (경로가 잘못되었다면 생성 불가능)
try (FileOutputStream fos =
new FileOutputStream(convertFile)) { // FileOutputStream 데이터를 파일에 바이트 스트림으로 저장하기 위함
fos.write(file.getBytes());
}
return Optional.of(convertFile);
}

return Optional.empty();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.hjjang.backend.domain.image.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

@Slf4j
@RequiredArgsConstructor
@Service
public class LocalImageUploader extends ImageUploader{

@Override
public String upload(MultipartFile multipartFile) throws IOException {
File uploadFile =
this.localUpload(multipartFile) // 파일 변환할 수 없으면 에러
.orElseThrow(
() -> new IllegalArgumentException("error: MultipartFile -> File convert fail"));
return uploadFile.getName();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.hjjang.backend.domain.image.service;

import lombok.Getter;

@Getter
public enum Path {
imageSavePath(System.getProperty("user.dir") + "/backend/src/main/resources/images/");

private final String path;
Path(String path) {
this.path = path;
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.hjjang.backend.domain.post.domain.entity;

import com.hjjang.backend.domain.user.entity.User;
import com.hjjang.backend.infra.image.domain.entity.Image;
import lombok.*;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.DynamicInsert;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.hjjang.backend.infra.aws.config;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
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 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 AmazonS3Client amazonS3Client() {
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCreds))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.hjjang.backend.infra.aws.service;

import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.hjjang.backend.domain.image.service.ImageUploader;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

@Slf4j
@RequiredArgsConstructor
@Service
public class S3ImageUploader extends ImageUploader {

private final AmazonS3Client amazonS3Client;

@Value("${cloud.aws.s3.bucket}")
public String bucket; // S3 버킷 이름

@Override
public String upload(MultipartFile multipartFile) throws IOException {
File uploadFile =
this.localUpload(multipartFile) // 파일 변환할 수 없으면 에러
.orElseThrow(
() -> new IllegalArgumentException("error: MultipartFile -> File convert fail"));

return upload(uploadFile);
}

// S3로 파일 업로드하기
private String upload(File uploadFile) {
String uploadImageUrl = putS3(uploadFile, "static/" + uploadFile.getName()); // s3로 업로드
removeNewFile(uploadFile);
return uploadImageUrl;
}

// S3로 업로드
private String putS3(File uploadFile, String fileName) {
amazonS3Client.putObject(
new PutObjectRequest(bucket, fileName, uploadFile)
.withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3Client.getUrl(bucket, fileName).toString();
}

// 로컬에 저장된 이미지 지우기
private void removeNewFile(File targetFile) {
if (targetFile.delete()) {
log.info("File delete success");
return;
}
log.info("File delete fail");
}
}

This file was deleted.

Loading

0 comments on commit c7ac128

Please sign in to comment.