Skip to content

Commit

Permalink
feat :: File upload
Browse files Browse the repository at this point in the history
  • Loading branch information
Kim-Wongi-1 committed Nov 29, 2024
1 parent 490d3be commit c73a591
Show file tree
Hide file tree
Showing 10 changed files with 247 additions and 0 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ dependencies {

implementation 'org.jsoup:jsoup:1.17.2'

implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3:3.2.1'

annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
Expand Down
2 changes: 2 additions & 0 deletions scripts/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ nohup java -jar \
-Dspring.profiles.active=$IDLE_PROFILE \
-DEMAIL_PASSWORD="hwvt jjxa qipu jyci" \
-DEMAIL_USERNAME="[email protected]" \
-DAWS_ACCESS_KEY="AKIA4J3VX2R65LRGD3X6" \
-DAWS_SECRET_KEY="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" \
$JAR_NAME > $REPOSITORY/nohup.out 2>&1 &
38 changes: 38 additions & 0 deletions src/main/java/com/galendar/domain/upload/UploadController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.galendar.domain.upload;

import com.galendar.global.common.dto.response.ResponseData;
import com.galendar.global.s3.S3Service;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@Tag(name = "File", description = "")
@RestController
@RequestMapping("/file")
@RequiredArgsConstructor
public class UploadController {

private final S3Service s3Service;

@Operation(summary = "이미지 업로드", description = "")
@PostMapping
public ResponseEntity<ResponseData> upload(@RequestParam("file") MultipartFile file) {
String url = s3Service.putFile(file);
return ResponseEntity.ok(ResponseData.ok(url, "업로드 성공"));
}

@Operation(summary = "이미지 삭제", description = "")
@DeleteMapping
public ResponseEntity<ResponseData> upload(@RequestParam("url") String url) {
s3Service.deleteFile(url);
return ResponseEntity.ok(ResponseData.ok("삭제 성공"));
}

}
9 changes: 9 additions & 0 deletions src/main/java/com/galendar/global/s3/S3Service.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.galendar.global.s3;

import org.springframework.web.multipart.MultipartFile;

public interface S3Service {
String putFile(MultipartFile file);
void deleteFile(String url);

}
103 changes: 103 additions & 0 deletions src/main/java/com/galendar/global/s3/S3ServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.galendar.global.s3;

import com.galendar.global.s3.exception.S3Exception;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.DeleteObjectResponse;
import software.amazon.awssdk.services.s3.model.GetUrlRequest;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
import software.amazon.awssdk.services.s3.model.S3Response;

import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

@Service
@RequiredArgsConstructor
public class S3ServiceImpl implements S3Service {

private final S3Client s3Client;

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

public String putFile(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new S3Exception("파일을 확인해주세요.");
}

String originalFilename = file.getOriginalFilename();
String fileExtension = validateImageFileExtention(originalFilename);
String fileName = UUID.randomUUID() + "." + fileExtension;

PutObjectRequest putObjectRequest = PutObjectRequest.builder()
.bucket(bucketName)
.key(fileName)
.contentType(file.getContentType())
.build();

PutObjectResponse putObjectResponse = null;

try {
putObjectResponse = s3Client.putObject(
putObjectRequest,
RequestBody.fromBytes(file.getBytes())
);
} catch (IOException e) {
throw new S3Exception("파일 업로드 실패");
}

if (!isSuccess(putObjectResponse)) {
throw new S3Exception("파일 업로드 실패");
}

GetUrlRequest request = GetUrlRequest.builder().bucket(bucketName).key(fileName).build();
return s3Client.utilities().getUrl(request).toString();
}

public void deleteFile(String url) {
String objectKey = s3Client.utilities().parseUri(URI.create(url)).key().orElseThrow(() -> new S3Exception("파일을 찾을 수 없습니다."));
DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
.bucket(bucketName)
.key(objectKey)
.build();
DeleteObjectResponse deleteObjectResponse = s3Client.deleteObject(deleteObjectRequest);
if (!isSuccess(deleteObjectResponse)) {
throw new S3Exception("파일 삭제 실패");
}
}

private String validateImageFileExtention(String filename) {
int lastDotIndex = filename.lastIndexOf(".");
if (lastDotIndex == -1) {
throw new S3Exception("파일 확장자가 올바른지 확인해주세요.");
}

String extention = filename.substring(lastDotIndex + 1).toLowerCase();
List<String> allowedExtentionList = Arrays.asList("jpg", "jpeg", "png", "gif");

if (!allowedExtentionList.contains(extention)) {
throw new S3Exception("지원하지 않는 이미지 확장자입니다.");
}

return extention;
}

private boolean isSuccess(S3Response response) {
return isSuccessfulStatusCode(response.sdkHttpResponse().statusCode());
}

private boolean isSuccessfulStatusCode(int statusCode) {
return statusCode == HttpStatus.OK.value() || statusCode == HttpStatus.NO_CONTENT.value();
}

}
41 changes: 41 additions & 0 deletions src/main/java/com/galendar/global/s3/config/S3Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.galendar.global.s3.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;

@Configuration
public class S3Config {

@Value("${spring.cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${spring.cloud.aws.credentials.secret-key}")
private String secretKey;
@Value("${spring.cloud.aws.region.static}")
private String region;

public AwsCredentials awsCredentials() {
return new AwsCredentials() {
@Override
public String accessKeyId() {
return accessKey;
}
@Override
public String secretAccessKey() {
return secretKey;
}
};
}

@Bean
public S3Client s3Client() {
return S3Client.builder()
.credentialsProvider(this::awsCredentials)
.region(Region.of(region))
.build();
}

}
13 changes: 13 additions & 0 deletions src/main/java/com/galendar/global/s3/exception/S3Exception.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.galendar.global.s3.exception;

import com.galendar.global.exception.CustomException;

public class S3Exception extends CustomException {
public S3Exception(int status, String message) {
super(status, message);
}

public S3Exception(String message) {
super(400, message);
}
}
13 changes: 13 additions & 0 deletions src/main/resources/application-real1.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ spring:
writetimeout: 5000
ssl:
trust: smtp.gmail.com

cloud:
aws:
s3:
bucket: lift-s3-bucket
region:
static: ap-northeast-2
credentials:
access-key: ${AWS_ACCESS_KEY}
secret-key: ${AWS_SECRET_KEY}
stack:
auto: false

crawling:
contest-korea:
base-url: https://www.contestkorea.com
Expand Down
13 changes: 13 additions & 0 deletions src/main/resources/application-real2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ spring:
writetimeout: 5000
ssl:
trust: smtp.gmail.com

cloud:
aws:
s3:
bucket: lift-s3-bucket
region:
static: ap-northeast-2
credentials:
access-key: ${AWS_ACCESS_KEY}
secret-key: ${AWS_SECRET_KEY}
stack:
auto: false

crawling:
contest-korea:
base-url: https://www.contestkorea.com
Expand Down
13 changes: 13 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ spring:
writetimeout: 5000
ssl:
trust: smtp.gmail.com

cloud:
aws:
s3:
bucket: lift-s3-bucket
region:
static: ap-northeast-2
credentials:
access-key: ${AWS_ACCESS_KEY}
secret-key: ${AWS_SECRET_KEY}
stack:
auto: false

crawling:
contest-korea:
base-url: https://www.contestkorea.com
Expand Down

0 comments on commit c73a591

Please sign in to comment.