Skip to content

Commit

Permalink
7차 세미나 - S3 #15
Browse files Browse the repository at this point in the history
  • Loading branch information
PgmJun committed May 27, 2023
1 parent 33bfb85 commit d2ea153
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 15 deletions.
19 changes: 10 additions & 9 deletions .idea/workspace.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions seventhSeminar/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ dependencies {

// S3 AWS
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-aws', version: '2.2.6.RELEASE'

// swagger
implementation 'org.springdoc:springdoc-openapi-ui:1.7.0'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,112 @@
package sopt.org.fourthSeminar.aws;public class S3Service {
package sopt.org.fourthSeminar.aws;


import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
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 org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import sopt.org.fourthSeminar.controller.exception.Error;
import sopt.org.fourthSeminar.controller.exception.model.BadRequestException;
import sopt.org.fourthSeminar.controller.exception.model.NotFoundException;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Service
@RequiredArgsConstructor
public class S3Service {
private final AmazonS3 amazonS3;

@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;

@PostConstruct
public AmazonS3Client amazonS3Client() {
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
}

public String uploadImage(MultipartFile multipartFile, String folder) {
String fileName = createFileName(multipartFile.getOriginalFilename());
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(multipartFile.getSize());
objectMetadata.setContentType(multipartFile.getContentType());

try(InputStream inputStream = multipartFile.getInputStream()) {
amazonS3.putObject(new PutObjectRequest(bucket+"/"+ folder + "/image", fileName, inputStream, objectMetadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3.getUrl(bucket+"/"+ folder + "/image", fileName).toString();
} catch(IOException e) {
throw new NotFoundException(Error.NOT_FOUND_SAVE_IMAGE_EXCEPTION, Error.NOT_FOUND_SAVE_IMAGE_EXCEPTION.getMessage());
}
}

public List<String> uploadImages(List<MultipartFile> multipartFileList, String folder) {
List<String> uploadImageUrls = new ArrayList<>();
String fileName;
ObjectMetadata objectMetadata;
for (MultipartFile multipartFile : multipartFileList) {
fileName = createFileName(multipartFile.getOriginalFilename());
objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(multipartFile.getSize());
objectMetadata.setContentType(multipartFile.getContentType());

try(InputStream inputStream = multipartFile.getInputStream()) {
amazonS3.putObject(new PutObjectRequest(bucket+"/"+ folder + "/image", fileName, inputStream, objectMetadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
uploadImageUrls.add(amazonS3.getUrl(bucket+"/"+ folder + "/image", fileName).toString());
} catch(IOException e) {
throw new NotFoundException(Error.NOT_FOUND_SAVE_IMAGE_EXCEPTION, Error.NOT_FOUND_SAVE_IMAGE_EXCEPTION.getMessage());
}
}

return uploadImageUrls;
}

// 파일명 (중복 방지)
private String createFileName(String fileName) {
return UUID.randomUUID().toString().concat(getFileExtension(fileName));
}

// 파일 유효성 검사
private String getFileExtension(String fileName) {
if (fileName.length() == 0) {
throw new NotFoundException(Error.NOT_FOUND_IMAGE_EXCEPTION, Error.NOT_FOUND_IMAGE_EXCEPTION.getMessage());
}
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 BadRequestException(Error.INVALID_MULTIPART_EXTENSION_EXCEPTION, Error.INVALID_MULTIPART_EXTENSION_EXCEPTION.getMessage());
}
return fileName.substring(fileName.lastIndexOf("."));
}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,23 @@
package sopt.org.fourthSeminar.config.swagger;public class SwaggerConfig {
package sopt.org.fourthSeminar.config.swagger;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SwaggerConfig {

@Bean
public OpenAPI openAPI() {
Info info = new Info()
.title("SOPT32nd Seminar project")
.description("SOPT32nd Seminar project API Document")
.version("1.0.0");

return new OpenAPI()
.components(new Components())
.info(info);
}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,27 @@
package sopt.org.fourthSeminar.controller.dto.request;public class BoardImageListDto {
package sopt.org.fourthSeminar.controller.dto.request;

import lombok.*;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;

@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class BoardImageListRequestDto {
private List<MultipartFile> boardImages;

@NotBlank
private String title;

@NotBlank
private String content;

@NotNull
private Boolean isPublic;

public void setBoardImages(List<MultipartFile> boardImages) {
this.boardImages = boardImages;
}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
package sopt.org.fourthSeminar.controller.exception.model;public class BacRequestException {
package sopt.org.fourthSeminar.controller.exception.model;

import sopt.org.fourthSeminar.controller.exception.Error;

public class BadRequestException extends SoptException{

public BadRequestException(Error error, String message) {
super(error, message);
}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,34 @@
package sopt.org.fourthSeminar.domain;public class Image {
package sopt.org.fourthSeminar.domain;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import sopt.org.fourthSeminar.AuditingTimeEntity;

import javax.persistence.*;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Image extends AuditingTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id", nullable = false, foreignKey = @ForeignKey(ConstraintMode.CONSTRAINT))
private Board board;

@Column(nullable = false)
private String imageUrl;

private Image(Board board, String imageUrl) {
this.board = board;
this.imageUrl = imageUrl;
}

public static Image newInstance(Board board, String imageUrl) {
return new Image(board, imageUrl);
}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
package sopt.org.fourthSeminar.infrastructure;public class ImageRepository {
package sopt.org.fourthSeminar.infrastructure;


import org.springframework.data.repository.Repository;
import sopt.org.fourthSeminar.domain.Image;

public interface ImageRepository extends Repository<Image, Long> {
// CREATE
void save(Image image);
}

0 comments on commit d2ea153

Please sign in to comment.