diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 31ae8ad..f2d62a0 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -94,7 +94,7 @@ jobs: # 3. 최신 이미지를 컨테이너화하여 실행 - name: docker run new container - run: sudo docker run --name helfoome --rm -d -p 8080:8080 -e TZ=Asia/Seoul ${{ secrets.DOCKERHUB_USERNAME }}/team01 + run: sudo docker run --name team01 --rm -d -p 8080:8080 -e TZ=Asia/Seoul ${{ secrets.DOCKERHUB_USERNAME }}/team01 # 4. 미사용 이미지를 정리 - name: delete old docker image diff --git a/Dockerfile b/Dockerfile index 0f6ae90..9eda918 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -# JDK21 -FROM openjdk:21 +# JDK17 +FROM openjdk:17 # 인자 설정, jar 파일 복제 COPY build/libs/*.jar app.jar # 실행 명령어 -ENTRYPOINT ["java", "-jar", "app.jar"] +ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/build.gradle b/build.gradle index b9fa8d7..9434dcb 100644 --- a/build.gradle +++ b/build.gradle @@ -36,7 +36,7 @@ dependencies { compileOnly 'org.projectlombok:lombok' // DB - runtimeOnly 'com.mysql:mysql-connector-j' + implementation 'mysql:mysql-connector-java:8.0.33' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' diff --git a/src/main/java/com/uggthon/team01/config/S3Config.java b/src/main/java/com/uggthon/team01/config/S3Config.java new file mode 100644 index 0000000..b08fc81 --- /dev/null +++ b/src/main/java/com/uggthon/team01/config/S3Config.java @@ -0,0 +1,33 @@ +package com.uggthon.team01.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; + +@Configuration +public class S3Config { + + @Value("${aws.credentials.access-key}") + private String accessKey; + + @Value("${aws.credentials.secret-key}") + private String secretKey; + + @Value("${aws.region}") + private String region; + + @Bean + public AmazonS3Client amazonS3Client() { + BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + return (AmazonS3Client) + AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } +} diff --git a/src/main/java/com/uggthon/team01/config/SwaggerConfig.java b/src/main/java/com/uggthon/team01/config/SwaggerConfig.java index 857f644..b40ab60 100644 --- a/src/main/java/com/uggthon/team01/config/SwaggerConfig.java +++ b/src/main/java/com/uggthon/team01/config/SwaggerConfig.java @@ -11,7 +11,7 @@ // Swagger 접속 주소 // http://localhost:8080/swagger-ui/index.html#/ -// https://{ip-address}:8080/swagger-ui/index.html +// https://13.60.193.191:8080/swagger-ui/index.html @Configuration public class SwaggerConfig { @@ -26,7 +26,7 @@ public OpenAPI customOpenAPI() { localServer.setDescription("Local Server"); Server prodServer = new Server(); - prodServer.setUrl("https://16.171.3.150:8080/swagger-ui/index.html"); + prodServer.setUrl("http://13.60.193.191:8080/swagger-ui/index.html"); prodServer.setDescription("Production Server"); return new OpenAPI() diff --git a/src/main/java/com/uggthon/team01/service/S3Service.java b/src/main/java/com/uggthon/team01/service/S3Service.java new file mode 100644 index 0000000..84b9db2 --- /dev/null +++ b/src/main/java/com/uggthon/team01/service/S3Service.java @@ -0,0 +1,111 @@ +package com.uggthon.team01.service; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.UUID; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.PutObjectRequest; + +import lombok.extern.slf4j.Slf4j; + +// 이 서비스는 데베에 저장하는 서비스 X +// S3에 실제 파일을 업로드 (이름 겹치치 않게 파일이름 = 날짜-파일이름 으로 변경해서 올리는 기능) +// 이미지를 업로드할 일이 생기면 본인 서비스에서 요기 함수 호출해서 s3에 파일 업로드하고 +// 파일 경로랑 원본 파일명은 호출한 서비스에서 따로 저장해주셔야됭미 + +@Slf4j +@Service +public class S3Service { + + private final AmazonS3 s3Client; + + @Value("${aws.s3.bucket}") + private String bucketName; + + public S3Service( + @Value("${aws.credentials.access-key}") String accessKey, + @Value("${aws.credentials.secret-key}") String secretKey, + @Value("${aws.region}") String region) { + BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey); + this.s3Client = + AmazonS3ClientBuilder.standard() + .withRegion(Regions.fromName(region)) + .withCredentials(new AWSStaticCredentialsProvider(awsCreds)) + .build(); + } + + // dirName은 S3의 어떤 폴더인지 같이 넣어주면 됨 예를들면 profileImage.. shopImage.....etc + public String upload(MultipartFile multipartFile, String dirName) throws IOException { + String originalFileName = multipartFile.getOriginalFilename(); + String uuid = UUID.randomUUID().toString(); + String uniqueFileName = uuid + "_" + originalFileName.replaceAll("\\s", "_"); + + String fileName = dirName + "/" + uniqueFileName; + log.info("fileName: " + fileName); + + File uploadFile = convert(multipartFile); + String uploadImageUrl = putS3(uploadFile, fileName); + removeNewFile(uploadFile); + return uploadImageUrl; + } + + private File convert(MultipartFile file) throws IOException { + File convertFile = new File(file.getOriginalFilename()); + if (convertFile.createNewFile()) { + try (FileOutputStream fos = new FileOutputStream(convertFile)) { + fos.write(file.getBytes()); + } + return convertFile; + } + throw new IllegalArgumentException("파일 변환에 실패했습니다: " + file.getOriginalFilename()); + } + + private String putS3(File uploadFile, String fileName) { + s3Client.putObject( + new PutObjectRequest(bucketName, fileName, uploadFile) + .withCannedAcl(CannedAccessControlList.PublicRead)); + return s3Client.getUrl(bucketName, fileName).toString(); + } + + // 전환할 때 임시파일이 생성돼서 삭제해야함 + private void removeNewFile(File targetFile) { + if (targetFile.delete()) { + log.info("파일이 삭제되었습니다."); + } else { + log.info("파일이 삭제되지 못했습니다."); + } + } + + public void deleteFile(String fileName) { + try { + // URL 디코딩을 통해 원래의 파일 이름을 가져옴 + String decodedFileName = URLDecoder.decode(fileName, "UTF-8"); + log.info("Deleting file from S3: " + decodedFileName); + s3Client.deleteObject(bucketName, decodedFileName); + } catch (UnsupportedEncodingException e) { + log.error("Error while decoding the file name: {}", e.getMessage()); + } + } + + public String updateFile(MultipartFile newFile, String oldFileName, String dirName) + throws IOException { + // 기존 파일 삭제 + log.info("S3 oldFileName: " + oldFileName); + deleteFile(oldFileName); + // 새 파일 업로드 + return upload(newFile, dirName); + } +}