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

[FEAT] csv 파일 업로드 dependency 및 기능 추가 #328

Merged
merged 2 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,23 +43,31 @@ dependencies {
// concurrent-trees for trie
implementation 'com.googlecode.concurrent-trees:concurrent-trees:2.6.1'

// apache commons csv for csv generating csv file
implementation 'org.apache.commons:commons-csv:1.11.0'

// springboot
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'

// mail
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

// database
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'

// test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
testImplementation 'org.springframework.security:spring-security-test'

// mail
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

// monitoring
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import java.util.List;

import static com.spaceclub.global.annotation.profanity.BadWordExceptionMessage.BAD_WORD_DETECTED;
import static com.spaceclub.global.annotation.profanity.ProfanityExceptionMessage.BAD_WORD_DETECTED;
import static com.spaceclub.global.exception.GlobalExceptionCode.INVALID_REQUEST;
import static com.spaceclub.global.exception.GlobalExceptionCode.MAX_IMAGE_SIZE_EXCEEDED;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import static com.spaceclub.global.annotation.profanity.BadWordExceptionMessage.BAD_WORD_DETECTED;
import static com.spaceclub.global.annotation.profanity.ProfanityExceptionMessage.BAD_WORD_DETECTED;

@Component
@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.spaceclub.global.annotation.profanity;

import lombok.Getter;

@Getter
public enum ProfanityExceptionMessage {

FAIL_BAD_WORD_SETUP("비속어 목록 Trie 생성 실패"),
BAD_WORD_DETECTED("비속어가 발견 되었습니다"),
INVALID_EXTENSION("md,txt 파일만 업로드 가능합니다."),
FAILED_TO_SAVE("금칙어 저장에 실패하였습니다."),
BAD_WORD_ALREADY_EXISTS("이미 존재하는 금칙어입니다."),
FAILED_TO_CREATE_CSV("CSV 파일 생성에 실패하였습니다.")
;

private final String message;

ProfanityExceptionMessage(String message) {
this.message = message;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import java.util.Spliterator;
import java.util.stream.StreamSupport;

import static com.spaceclub.global.annotation.profanity.BadWordExceptionMessage.FAIL_BAD_WORD_SETUP;
import static com.spaceclub.global.annotation.profanity.ProfanityExceptionMessage.FAIL_BAD_WORD_SETUP;

@Slf4j
@Component
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/spaceclub/global/aws/S3Properties.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

@ConfigurationProperties(prefix = "s3")
public record S3Properties(
String bucket,
String imageBucket,
String fileBucket,
String region,
String url,
List<String> validExtensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
public class FileNameCreator {

private static final String DOT = ".";
private static final String CSV_EXTENSION = "csv";
private static final String FILE_FORMAT = "%s/%s_%s.%s";

private final S3Properties s3Properties;

public String createFileName(S3Folder folder, String originalName, String timestamp) {
public String createImageFileKey(S3Folder folder, String originalName, String timestamp) {
int lastDot = originalName.lastIndexOf(DOT);
String fileName = originalName.substring(0, lastDot);
String fileExtension = originalName.substring(lastDot + 1);
Expand All @@ -23,6 +24,10 @@ public String createFileName(S3Folder folder, String originalName, String timest
return String.format(FILE_FORMAT, folder.getFolder(), fileName, timestamp, fileExtension);
}

public String createCSVFileKey(S3Folder folder, String fileName, String timestamp) {
return String.format(FILE_FORMAT, folder.getFolder(), fileName, timestamp, CSV_EXTENSION);
}

private void validateFileExtension(String fileExtension) {
boolean invalidExtension = s3Properties.validExtensions()
.stream()
Expand Down
52 changes: 52 additions & 0 deletions src/main/java/com/spaceclub/global/aws/s3/S3FileUploader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.spaceclub.global.aws.s3;

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 com.spaceclub.global.aws.S3Properties;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import static com.spaceclub.global.exception.GlobalExceptionCode.FAIL_FILE_UPLOAD;

@Component
@RequiredArgsConstructor
public class S3FileUploader {

private static final String DATE_FORMAT = "yyyyMMdd_HHmmss";
private static final String CSV_FILE_NAME = "profanity_info";

private final AmazonS3 amazonS3;
private final S3Properties s3Properties;

public String uploadProfanityInfo(String content) {
final String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern(DATE_FORMAT));
String csvFileKey = new FileNameCreator(s3Properties).createCSVFileKey(S3Folder.PROFANITY_INFO, CSV_FILE_NAME, timestamp);
ObjectMetadata objectMetaData = createMetaData(content);

try (InputStream inputStream = new ByteArrayInputStream(content.getBytes())) {
final PutObjectRequest request = new PutObjectRequest(s3Properties.fileBucket(), csvFileKey, inputStream, objectMetaData)
.withCannedAcl(CannedAccessControlList.PublicRead);
amazonS3.putObject(request);

return s3Properties.url() + csvFileKey;
} catch (IOException e) {
throw new IllegalStateException(FAIL_FILE_UPLOAD.toString());
}
}

private ObjectMetadata createMetaData(String content) {
ObjectMetadata objectMetaData = new ObjectMetadata();
objectMetaData.setContentType("text/csv");
objectMetaData.setContentLength(content.length());
return objectMetaData;
}

}
4 changes: 3 additions & 1 deletion src/main/java/com/spaceclub/global/aws/s3/S3Folder.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ public enum S3Folder {
LOGO("club-logo"),
COVER("club-cover"),
USER_PROFILE("user-profile-image"),
POST_IMAGE("post-image");
POST_IMAGE("post-image"),
PROFANITY_INFO("profanity"),
;

private final String folder;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ public String upload(MultipartFile image, S3Folder folder) {
if (originalFilename == null) throw new IllegalArgumentException(FAIL_FILE_UPLOAD.toString());

String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern(DATE_FORMAT));
String fileName = new FileNameCreator(s3Properties).createFileName(folder, originalFilename, timestamp);
String fileName = new FileNameCreator(s3Properties).createImageFileKey(folder, originalFilename, timestamp);
ObjectMetadata objectMetaData = createMetaData(image);

try (InputStream inputStream = image.getInputStream()) {
amazonS3Client.putObject(
new PutObjectRequest(s3Properties.bucket(), fileName, inputStream, objectMetaData)
new PutObjectRequest(s3Properties.imageBucket(), fileName, inputStream, objectMetaData)
.withCannedAcl(CannedAccessControlList.PublicRead));
} catch (IOException e) {
throw new IllegalStateException(FAIL_FILE_UPLOAD.toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.List;

@ConfigurationProperties(prefix = "profanity")
public record ProfanityConfig(
String filePath
String filePath,
List<String> validExtensions
) {

}
6 changes: 5 additions & 1 deletion src/main/resources/application-develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ oauth:
admin-key-prefix: KakaoAK

s3:
bucket: ENC(JoDpjsUcxIviI7KX5vVl47Ey3PAFn6GffGgyuKeWoek=)
file-bucket: ENC(62oqQc22U4+n1L8xyyRqtb10UWqpPwChIQ7WHe6gJ3s=)
image-bucket: ENC(JoDpjsUcxIviI7KX5vVl47Ey3PAFn6GffGgyuKeWoek=)
region: ENC(906H8BMOAumiA+H0nevzxJUoUGzBG2Nh)
url: ENC(iMleKm/uvFwQ7k0EGbj94AFJkend3u7iqYYb8QxSNMPU/nCmbB1kXG/PdovxHoc41yeIHkhSwjfjGSyp5DlMmHEqib5VWr6LkptpQ9KX9Hs=)

Expand All @@ -46,3 +47,6 @@ web:

profanity:
file-path: /home/ubuntu/bad_word_list.txt
valid-extensions:
- txt
- md
6 changes: 5 additions & 1 deletion src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ oauth:
admin-key-prefix: KakaoAK

s3:
bucket: ENC(JoDpjsUcxIviI7KX5vVl47Ey3PAFn6GffGgyuKeWoek=)
image-bucket: ENC(JoDpjsUcxIviI7KX5vVl47Ey3PAFn6GffGgyuKeWoek=)
file-bucket: ENC(62oqQc22U4+n1L8xyyRqtb10UWqpPwChIQ7WHe6gJ3s=)
region: ENC(906H8BMOAumiA+H0nevzxJUoUGzBG2Nh)
url: ENC(iMleKm/uvFwQ7k0EGbj94AFJkend3u7iqYYb8QxSNMPU/nCmbB1kXG/PdovxHoc41yeIHkhSwjfjGSyp5DlMmHEqib5VWr6LkptpQ9KX9Hs=)

Expand All @@ -51,3 +52,6 @@ web:

profanity:
file-path: src/main/resources/secrets/bad_word_list.txt
valid-extensions:
- txt
- md
12 changes: 6 additions & 6 deletions src/test/java/com/spaceclub/global/s3/FileNameCreatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ class FileNameCreatorTest {
@Test
void 폴더명_파일명_타임스탬프_확장자_형식으로_파일명을_생성한다() {
// given
S3Properties s3Properties = new S3Properties("s3bucket", "s3region", "s3url", List.of("jpg", "jpeg", "png"));
S3Properties s3Properties = new S3Properties("s3ImageBucket", "s3FileBucket", "s3region", "s3url", List.of("jpg", "jpeg", "png"));
FileNameCreator fileNameCreator = new FileNameCreator(s3Properties);
S3Folder folder = S3Folder.COVER;
String folderName = folder.getFolder();
String originalFileName = "originalName.jpg";
String timestamp = "202404191111";

// when
String fileName = fileNameCreator.createFileName(folder, originalFileName, timestamp);
String fileName = fileNameCreator.createImageFileKey(folder, originalFileName, timestamp);

// then
assertThat(fileName).isEqualTo(folderName + "/originalName_202404191111.jpg");
Expand All @@ -40,14 +40,14 @@ class FileNameCreatorTest {
@Test
void 확장자가_jpg_jpeg_png_가_아니면_예외를_생성한다() {
// given
S3Properties s3Properties = new S3Properties("s3bucket", "s3region", "s3url", List.of("jpg", "jpeg", "png"));
S3Properties s3Properties = new S3Properties("s3ImageBucket", "s3FileBucket", "s3region", "s3url", List.of("jpg", "jpeg", "png"));
FileNameCreator fileNameCreator = new FileNameCreator(s3Properties);
S3Folder folder = S3Folder.COVER;
String originalFileName = "originalName.gif";
String timestamp = "202404191111";

// when, then
assertThatThrownBy(() -> fileNameCreator.createFileName(folder, originalFileName, timestamp))
assertThatThrownBy(() -> fileNameCreator.createImageFileKey(folder, originalFileName, timestamp))
.isInstanceOf(MultipartException.class)
.hasMessage(INVALID_FILE_EXTENSION.toString());
}
Expand All @@ -56,15 +56,15 @@ class FileNameCreatorTest {
@ValueSource(strings = {"jpg", "jpeg", "png"})
void 확장자가_jpg_jpeg_png이면_예외를_발생하지_않는다(String extension) {
// given
S3Properties s3Properties = new S3Properties("s3bucket", "s3region", "s3url", List.of("jpg", "jpeg", "png"));
S3Properties s3Properties = new S3Properties("s3ImageBucket", "s3FileBucket", "s3region", "s3url", List.of("jpg", "jpeg", "png"));
FileNameCreator fileNameCreator = new FileNameCreator(s3Properties);
S3Folder folder = S3Folder.COVER;
String originalFileName = "originalName." + extension;
String timestamp = "202404191111";

// when, then
assertThatNoException()
.isThrownBy(() -> fileNameCreator.createFileName(folder, originalFileName, timestamp));
.isThrownBy(() -> fileNameCreator.createImageFileKey(folder, originalFileName, timestamp));
}

}
3 changes: 2 additions & 1 deletion src/test/resources/application-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ spring:
defer-datasource-initialization: true

s3:
bucket: space-club-image-bucket
file-bucket: file-bucket-test
image-bucket: image-bucket-test
region: region
url: https://test.com/

Expand Down
3 changes: 3 additions & 0 deletions src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ web:

profanity:
file-path: src/main/resources/secrets/bad_word_list.txt
valid-extensions:
- txt
- md

invite:
link-prefix: "https://space-club.site/api/v1/clubs/invite/"
Loading