From 3a26ca4a33d9faa825e2d8f4e09382587802eacc Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Tue, 31 Dec 2024 22:09:30 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[FEAT]=20ChatGPT=20api=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20dependency=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit application-chatgpt.yml 파일 추가 요망 --- src/main/java/com/movelog/MoveLogApplication.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/movelog/MoveLogApplication.java b/src/main/java/com/movelog/MoveLogApplication.java index 940069d..5d1292b 100644 --- a/src/main/java/com/movelog/MoveLogApplication.java +++ b/src/main/java/com/movelog/MoveLogApplication.java @@ -13,11 +13,10 @@ @PropertySource(value = { "classpath:oauth2/application-oauth2.yml" }, factory = YamlPropertySourceFactory.class) @PropertySource(value = { "classpath:database/application-database.yml" }, factory = YamlPropertySourceFactory.class) @PropertySource(value = { "classpath:swagger/application-springdoc.yml" }, factory = YamlPropertySourceFactory.class) -//@PropertySource(value = { "classpath:s3/application-s3.yml" }, factory = YamlPropertySourceFactory.class) +@PropertySource(value = { "classpath:s3/application-s3.yml" }, factory = YamlPropertySourceFactory.class) +@PropertySource(value = { "classpath:chatgpt/application-chatgpt.yml" }, factory = YamlPropertySourceFactory.class) public class MoveLogApplication { - public static void main(String[] args) { SpringApplication.run(MoveLogApplication.class, args); } - } From 98335bd6667222545664d923daaa4973aa753145 Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Thu, 2 Jan 2025 11:08:17 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[FEAT]=20S3=20=EB=B0=8F=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit yml 파일 추가 --- build.gradle | 9 ++ .../java/com/movelog/MoveLogApplication.java | 1 + .../com/movelog/domain/common/BaseEntity.java | 14 +-- .../com/movelog/domain/common/Status.java | 5 + .../movelog/domain/record/domain/Keyword.java | 32 ++++++ .../movelog/domain/record/domain/Record.java | 51 +++++++++ .../domain/record/domain/VerbType.java | 6 + .../com/movelog/domain/user/domain/User.java | 4 + .../global/config/WebClientConfig.java | 28 +++++ .../config/security/AmazonS3Config.java | 40 +++++++ .../java/com/movelog/global/util/S3Util.java | 105 ++++++++++++++++++ 11 files changed, 288 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/movelog/domain/common/Status.java create mode 100644 src/main/java/com/movelog/domain/record/domain/Keyword.java create mode 100644 src/main/java/com/movelog/domain/record/domain/Record.java create mode 100644 src/main/java/com/movelog/domain/record/domain/VerbType.java create mode 100644 src/main/java/com/movelog/global/config/WebClientConfig.java create mode 100644 src/main/java/com/movelog/global/config/security/AmazonS3Config.java create mode 100644 src/main/java/com/movelog/global/util/S3Util.java diff --git a/build.gradle b/build.gradle index 616a590..df42d67 100644 --- a/build.gradle +++ b/build.gradle @@ -54,6 +54,10 @@ dependencies { implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.1.0' } + // GPT + implementation 'io.github.flashvayne:chatgpt-spring-boot-starter:1.0.4' + + // Amazon S3 implementation platform('com.amazonaws:aws-java-sdk-bom:1.11.1000') implementation 'com.amazonaws:aws-java-sdk-s3' @@ -61,6 +65,11 @@ dependencies { implementation 'org.projectlombok:lombok:1.18.28' + implementation 'org.springframework.boot:spring-boot-starter-webflux' + implementation "io.netty:netty-all:4.1.68.Final" + implementation "io.netty:netty-resolver-dns-native-macos:4.1.68.Final" + implementation "io.projectreactor.netty:reactor-netty-core:1.1.0" + implementation "io.projectreactor.netty:reactor-netty-http:1.1.0" annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/src/main/java/com/movelog/MoveLogApplication.java b/src/main/java/com/movelog/MoveLogApplication.java index 5d1292b..a20b262 100644 --- a/src/main/java/com/movelog/MoveLogApplication.java +++ b/src/main/java/com/movelog/MoveLogApplication.java @@ -15,6 +15,7 @@ @PropertySource(value = { "classpath:swagger/application-springdoc.yml" }, factory = YamlPropertySourceFactory.class) @PropertySource(value = { "classpath:s3/application-s3.yml" }, factory = YamlPropertySourceFactory.class) @PropertySource(value = { "classpath:chatgpt/application-chatgpt.yml" }, factory = YamlPropertySourceFactory.class) +@PropertySource(value = { "classpath:webclient/application-webclient.yml" }, factory = YamlPropertySourceFactory.class) public class MoveLogApplication { public static void main(String[] args) { SpringApplication.run(MoveLogApplication.class, args); diff --git a/src/main/java/com/movelog/domain/common/BaseEntity.java b/src/main/java/com/movelog/domain/common/BaseEntity.java index 293195a..a3dffc8 100644 --- a/src/main/java/com/movelog/domain/common/BaseEntity.java +++ b/src/main/java/com/movelog/domain/common/BaseEntity.java @@ -20,11 +20,11 @@ public class BaseEntity { @Column(name = "updated_at") private LocalDateTime updatedAt; -// @Enumerated(value = EnumType.STRING) -// @Column(name = "status") -// private Status status = Status.ACTIVE; -// -// public void updateStatus(Status status) { -// this.status = status; -// } + @Enumerated(value = EnumType.STRING) + @Column(name = "status") + private Status status = Status.ACTIVE; + + public void updateStatus(Status status) { + this.status = status; + } } diff --git a/src/main/java/com/movelog/domain/common/Status.java b/src/main/java/com/movelog/domain/common/Status.java new file mode 100644 index 0000000..be4d803 --- /dev/null +++ b/src/main/java/com/movelog/domain/common/Status.java @@ -0,0 +1,5 @@ +package com.movelog.domain.common; + +public enum Status { + ACTIVE, DELETE +} diff --git a/src/main/java/com/movelog/domain/record/domain/Keyword.java b/src/main/java/com/movelog/domain/record/domain/Keyword.java new file mode 100644 index 0000000..494e60f --- /dev/null +++ b/src/main/java/com/movelog/domain/record/domain/Keyword.java @@ -0,0 +1,32 @@ +package com.movelog.domain.record.domain; + +import com.movelog.domain.common.BaseEntity; +import jakarta.persistence.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "Keyword") +@NoArgsConstructor +@Getter +public class Keyword extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "keyword_id", updatable = false) + private Long keywordId; + + private String keyword; + + @OneToMany(mappedBy = "keyword") + private List records = new ArrayList<>(); + + @Builder + public Keyword(String keyword) { + this.keyword = keyword; + } +} diff --git a/src/main/java/com/movelog/domain/record/domain/Record.java b/src/main/java/com/movelog/domain/record/domain/Record.java new file mode 100644 index 0000000..aea6789 --- /dev/null +++ b/src/main/java/com/movelog/domain/record/domain/Record.java @@ -0,0 +1,51 @@ +package com.movelog.domain.record.domain; + +import com.movelog.domain.common.BaseEntity; +import com.movelog.domain.user.domain.User; +import jakarta.persistence.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.joda.time.LocalDateTime; + +@Entity +@Table(name = "Record") +@NoArgsConstructor +@Getter +public class Record extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "record_id", updatable = false) + private Long recordId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "keyword_id") + private Keyword keyword; + + @Enumerated(value = EnumType.STRING) + @Column(name = "verb_type") + private VerbType recordType; + + @Column(name = "record_image") + private String recordImage; + + @Column(name = "action_time") + private LocalDateTime actionTime; + + @Builder + public Record(User user, Keyword keyword, VerbType recordType, String recordImage) { + this.user = user; + this.keyword = keyword; + this.recordType = recordType; + this.recordImage = recordImage; + this.actionTime = actionTime == null? LocalDateTime.now():actionTime; + } + + + +} diff --git a/src/main/java/com/movelog/domain/record/domain/VerbType.java b/src/main/java/com/movelog/domain/record/domain/VerbType.java new file mode 100644 index 0000000..63b951e --- /dev/null +++ b/src/main/java/com/movelog/domain/record/domain/VerbType.java @@ -0,0 +1,6 @@ +package com.movelog.domain.record.domain; + +public enum VerbType { + // 했어요, 먹었어요, 갔어요 + DO, EAT, GO +} diff --git a/src/main/java/com/movelog/domain/user/domain/User.java b/src/main/java/com/movelog/domain/user/domain/User.java index 9b317ac..45f60da 100644 --- a/src/main/java/com/movelog/domain/user/domain/User.java +++ b/src/main/java/com/movelog/domain/user/domain/User.java @@ -3,6 +3,7 @@ import com.movelog.domain.common.BaseEntity; +import com.movelog.domain.record.domain.Record; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Builder; @@ -38,6 +39,9 @@ public class User extends BaseEntity { private String fcmToken; + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private List records = new ArrayList<>(); + @Builder public User(String nickname, String username, String email, String role, String provider, String providerId) { diff --git a/src/main/java/com/movelog/global/config/WebClientConfig.java b/src/main/java/com/movelog/global/config/WebClientConfig.java new file mode 100644 index 0000000..d68a9dc --- /dev/null +++ b/src/main/java/com/movelog/global/config/WebClientConfig.java @@ -0,0 +1,28 @@ +package com.movelog.global.config; + + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; + +@Configuration +public class WebClientConfig { + + @Value("${chatgpt.url}") + private String gptUrl; + + @Bean + public WebClient gptWebClient() { + return createWebClient(gptUrl); + } + + private WebClient createWebClient(String baseUrl) { + return WebClient.builder() + .baseUrl(baseUrl) + .codecs(configurer -> configurer + .defaultCodecs() + .maxInMemorySize(1000 * 1024 * 1024)) + .build(); + } +} diff --git a/src/main/java/com/movelog/global/config/security/AmazonS3Config.java b/src/main/java/com/movelog/global/config/security/AmazonS3Config.java new file mode 100644 index 0000000..4270b19 --- /dev/null +++ b/src/main/java/com/movelog/global/config/security/AmazonS3Config.java @@ -0,0 +1,40 @@ +package com.movelog.global.config.security; + + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +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 awsAccessKeyId; + + @Value("${cloud.aws.credentials.secret-key}") + private String awsSecretKey; + + @Value("${cloud.aws.region.static}") + private String awsRegion; + + @Value("${cloud.aws.s3.bucketName}") + private String bucketName; + + @Bean + public AmazonS3 amazonS3() { + BasicAWSCredentials awsCredentials = new BasicAWSCredentials(awsAccessKeyId, awsSecretKey); + return AmazonS3ClientBuilder.standard() + .withRegion(awsRegion) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } + + @Bean + public String bucketName() { + return bucketName; + } +} diff --git a/src/main/java/com/movelog/global/util/S3Util.java b/src/main/java/com/movelog/global/util/S3Util.java new file mode 100644 index 0000000..79df667 --- /dev/null +++ b/src/main/java/com/movelog/global/util/S3Util.java @@ -0,0 +1,105 @@ +package com.movelog.global.util; + + + +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.DeleteObjectRequest; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import jakarta.annotation.PostConstruct; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +@Service +@RequiredArgsConstructor +public class S3Util { + + private final AmazonS3 s3Client; + + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + + @Value("${cloud.aws.s3.bucketName}") + private String bucket; + + @Value("${cloud.aws.region.static}") + private String region; + + @PostConstruct + public AmazonS3Client amazonS3Client() { + BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey); + return (AmazonS3Client)AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCreds)) + .build(); + } + + public String upload(MultipartFile file) { + String imageUrl = ""; + String fileName = createFileName(file.getOriginalFilename()); + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentLength(file.getSize()); + objectMetadata.setContentType(file.getContentType()); + + try (InputStream inputStream = file.getInputStream()) { + s3Client.putObject(new PutObjectRequest(bucket, fileName, inputStream, objectMetadata) + .withCannedAcl(CannedAccessControlList.PublicRead)); + imageUrl = s3Client.getUrl(bucket, fileName).toString(); + } catch (IOException e) { + throw new IllegalArgumentException("IMAGE_UPLOAD_ERROR"); + } + return imageUrl; + } + + // 이미지파일명 중복 방지 + private String createFileName(String fileName) { + return UUID.randomUUID().toString().concat(getFileExtension(fileName)); + } + + // 파일 유효성 검사 + private String getFileExtension(String fileName) { + if (fileName.length() == 0) { + throw new IllegalArgumentException("IMAGE_UPLOAD_ERROR"); + } + ArrayList fileValidate = new ArrayList<>(); + fileValidate.add(".jpg"); + fileValidate.add(".jpeg"); + fileValidate.add(".png"); + fileValidate.add(".JPG"); + fileValidate.add(".JPEG"); + fileValidate.add(".PNG"); + fileValidate.add(".mp4"); + String idxFileName = fileName.substring(fileName.lastIndexOf(".")); + if (!fileValidate.contains(idxFileName)) { + throw new IllegalArgumentException("IMAGE_UPLOAD_ERROR"); + } + return fileName.substring(fileName.lastIndexOf(".")); + } + + // DeleteObject를 통해 S3 파일 삭제 + public void deleteFile(String fileName) { + String objectKey = parseObjectKeyFromUrl(fileName); + // 삭제 + DeleteObjectRequest deleteObjectRequest = new DeleteObjectRequest(bucket, objectKey); + s3Client.deleteObject(deleteObjectRequest); + } + + private String parseObjectKeyFromUrl(String objectUrl) { + return objectUrl.substring(objectUrl.lastIndexOf('/') + 1); + } + +} \ No newline at end of file