From 1c844bd74b8222af154764652fa15bb6a6361b4c Mon Sep 17 00:00:00 2001 From: codrin2 Date: Fri, 4 Oct 2024 16:40:52 +0900 Subject: [PATCH 1/6] =?UTF-8?q?[feat]=20email,=20kafka=20config=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 8 +++- build.gradle | 9 +++- docker-compose.yaml | 36 +++++++++++++++ .../kubacknotification/api/AController.java | 7 --- .../application/dto/Request.java | 4 -- .../application/dto/Response.java | 4 -- .../application/service/AService.java | 7 --- .../kubacknotification/domain/entity/A.java | 20 -------- .../domain/repository/ARepository.java | 9 ---- .../global/common/PaymentMessage.java | 24 ++++++++++ .../global/config/EmailConfig.java | 46 +++++++++++++++++++ .../global/config/KafkaConsumerConfig.java | 43 +++++++++++++++++ .../global/config/P6SpyFormatter.java | 38 +++++++++++++++ .../global/config/WebConfig.java | 17 +++++++ .../infra/jpa/entity/AEntity.java | 32 ------------- .../infra/jpa/repository/AJpaRepository.java | 7 --- .../repositoryImpl/AJpaRepositoryImpl.java | 21 --------- .../infra/mapper/AMapper.java | 21 --------- src/main/resources/application.properties | 1 - src/main/resources/application.yaml | 30 ++++++++++++ 20 files changed, 249 insertions(+), 135 deletions(-) create mode 100644 docker-compose.yaml delete mode 100644 src/main/java/com/server/kubacknotification/api/AController.java delete mode 100644 src/main/java/com/server/kubacknotification/application/dto/Request.java delete mode 100644 src/main/java/com/server/kubacknotification/application/dto/Response.java delete mode 100644 src/main/java/com/server/kubacknotification/application/service/AService.java delete mode 100644 src/main/java/com/server/kubacknotification/domain/entity/A.java delete mode 100644 src/main/java/com/server/kubacknotification/domain/repository/ARepository.java create mode 100644 src/main/java/com/server/kubacknotification/global/common/PaymentMessage.java create mode 100644 src/main/java/com/server/kubacknotification/global/config/EmailConfig.java create mode 100644 src/main/java/com/server/kubacknotification/global/config/KafkaConsumerConfig.java create mode 100644 src/main/java/com/server/kubacknotification/global/config/P6SpyFormatter.java create mode 100644 src/main/java/com/server/kubacknotification/global/config/WebConfig.java delete mode 100644 src/main/java/com/server/kubacknotification/infra/jpa/entity/AEntity.java delete mode 100644 src/main/java/com/server/kubacknotification/infra/jpa/repository/AJpaRepository.java delete mode 100644 src/main/java/com/server/kubacknotification/infra/jpa/repositoryImpl/AJpaRepositoryImpl.java delete mode 100644 src/main/java/com/server/kubacknotification/infra/mapper/AMapper.java delete mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/application.yaml diff --git a/.gitignore b/.gitignore index e48b6be..ec5ce4c 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,10 @@ out/ /.nb-gradle/ ### VS Code ### -.vscode/ \ No newline at end of file +.vscode/ + +### macOS ### +.DS_Store + +### applicaion 제외 ### +src/main/resources/application.yaml \ No newline at end of file diff --git a/build.gradle b/build.gradle index cab8cd0..9d429c5 100644 --- a/build.gradle +++ b/build.gradle @@ -25,11 +25,18 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.kafka:spring-kafka' + implementation 'org.springframework.boot:spring-boot-starter-mail' + + runtimeOnly 'com.mysql:mysql-connector-j' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' } tasks.named('test') { diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..81d4656 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,36 @@ +version: '3.7' +services: + mysql: + container_name: kuback-mysql + image: mysql:8 + command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --explicit_defaults_for_timestamp=1 + ports: + - 3307:3306 + environment: + - MYSQL_DATABASE=kuback + - MYSQL_USER=admin + - MYSQL_PASSWORD=1234 + - MYSQL_ROOT_PASSWORD=1234 + - TZ=UTC + volumes: + - ./mysql/init:/docker-entrypoint-initdb.d + + zookeeper: + image: wurstmeister/zookeeper:3.4.6 + ports: + - "2181:2181" + + kafka: + image: wurstmeister/kafka:latest + ports: + - "9092:9092" + expose: + - "9093" + environment: + KAFKA_LISTENERS: INSIDE://0.0.0.0:9093,OUTSIDE://0.0.0.0:9092 + KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka:9093,OUTSIDE://210.109.53.237:9092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + volumes: + - /var/run/docker.sock:/var/run/docker.sock diff --git a/src/main/java/com/server/kubacknotification/api/AController.java b/src/main/java/com/server/kubacknotification/api/AController.java deleted file mode 100644 index 08ae1b9..0000000 --- a/src/main/java/com/server/kubacknotification/api/AController.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.server.kubacknotification.api; - -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class AController { -} diff --git a/src/main/java/com/server/kubacknotification/application/dto/Request.java b/src/main/java/com/server/kubacknotification/application/dto/Request.java deleted file mode 100644 index bf5f64f..0000000 --- a/src/main/java/com/server/kubacknotification/application/dto/Request.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.server.kubacknotification.application.dto; - -public class Request { -} diff --git a/src/main/java/com/server/kubacknotification/application/dto/Response.java b/src/main/java/com/server/kubacknotification/application/dto/Response.java deleted file mode 100644 index eff51fa..0000000 --- a/src/main/java/com/server/kubacknotification/application/dto/Response.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.server.kubacknotification.application.dto; - -public class Response { -} diff --git a/src/main/java/com/server/kubacknotification/application/service/AService.java b/src/main/java/com/server/kubacknotification/application/service/AService.java deleted file mode 100644 index b9ef490..0000000 --- a/src/main/java/com/server/kubacknotification/application/service/AService.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.server.kubacknotification.application.service; - -import org.springframework.stereotype.Service; - -@Service -public class AService { -} diff --git a/src/main/java/com/server/kubacknotification/domain/entity/A.java b/src/main/java/com/server/kubacknotification/domain/entity/A.java deleted file mode 100644 index 7ae2e73..0000000 --- a/src/main/java/com/server/kubacknotification/domain/entity/A.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.server.kubacknotification.domain.entity; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class A { - private Long id; - private String name; - - public static A toDomain( - Long id, - String name) { - return new A(id, name); - } -} diff --git a/src/main/java/com/server/kubacknotification/domain/repository/ARepository.java b/src/main/java/com/server/kubacknotification/domain/repository/ARepository.java deleted file mode 100644 index d4ddb0f..0000000 --- a/src/main/java/com/server/kubacknotification/domain/repository/ARepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.server.kubacknotification.domain.repository; - -import com.server.kubacknotification.domain.entity.A; -import org.springframework.stereotype.Repository; - -@Repository -public interface ARepository { - A findById(Long id); -} diff --git a/src/main/java/com/server/kubacknotification/global/common/PaymentMessage.java b/src/main/java/com/server/kubacknotification/global/common/PaymentMessage.java new file mode 100644 index 0000000..c6f7b05 --- /dev/null +++ b/src/main/java/com/server/kubacknotification/global/common/PaymentMessage.java @@ -0,0 +1,24 @@ +package com.server.kubacknotification.global.common; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class PaymentMessage { + private Long userId; + private String seatNumber; + private String seatGrade; + private String payment; + private LocalDateTime payDate; + private String originalPrice; + private String salePrice; + private String finalPrice; +} + diff --git a/src/main/java/com/server/kubacknotification/global/config/EmailConfig.java b/src/main/java/com/server/kubacknotification/global/config/EmailConfig.java new file mode 100644 index 0000000..e0fbdb4 --- /dev/null +++ b/src/main/java/com/server/kubacknotification/global/config/EmailConfig.java @@ -0,0 +1,46 @@ +package com.server.kubacknotification.global.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import java.util.Properties; + +@Configuration +public class EmailConfig { + + @Value("${mailSender.password}") + private String mailsenderPassword; + @Value("${mailSender.username}") + private String mailsenderUsername; + + @Bean + public JavaMailSender mailSender() { + + JavaMailSenderImpl mailSender = new JavaMailSenderImpl();//JavaMailSender 의 구현체를 생성하고 + mailSender.setHost("smtp.gmail.com");// 속성을 넣기 시작합니다. 이메일 전송에 사용할 SMTP 서버 호스트를 설정 + mailSender.setPort(587);// 587로 포트를 지정 + mailSender.setUsername(mailsenderUsername);//구글계정을 넣습니다. + mailSender.setPassword(mailsenderPassword);//구글 앱 비밀번호를 넣습니다. + + Properties javaMailProperties = getProperties(); + + mailSender.setJavaMailProperties(javaMailProperties); + + return mailSender;//빈으로 등록한다. + } + + private static Properties getProperties() { + Properties javaMailProperties = new Properties();//JavaMail의 속성을 설정하기 위해 Properties 객체를 생성 + javaMailProperties.put("mail.transport.protocol", "smtp");//프로토콜로 smtp 사용 + javaMailProperties.put("mail.smtp.auth", "true");//smtp 서버에 인증이 필요 + javaMailProperties.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");//SSL 소켓 팩토리 클래스 사용 + javaMailProperties.put("mail.smtp.starttls.enable", "true");//STARTTLS(TLS를 시작하는 명령)를 사용하여 암호화된 통신을 활성화 + javaMailProperties.put("mail.debug", "true");//디버깅 정보 출력 + javaMailProperties.put("mail.smtp.ssl.trust", "smtp.gmail.com");//smtp 서버의 ssl 인증서를 신뢰 + javaMailProperties.put("mail.smtp.ssl.protocols", "TLSv1.2");//사용할 ssl 프로토콜 버젼 + return javaMailProperties; + } +} diff --git a/src/main/java/com/server/kubacknotification/global/config/KafkaConsumerConfig.java b/src/main/java/com/server/kubacknotification/global/config/KafkaConsumerConfig.java new file mode 100644 index 0000000..b5752d8 --- /dev/null +++ b/src/main/java/com/server/kubacknotification/global/config/KafkaConsumerConfig.java @@ -0,0 +1,43 @@ +package com.server.kubacknotification.global.config; + +import com.server.kubacknotification.global.common.PaymentMessage; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.core.ConsumerFactory; +import org.springframework.kafka.core.DefaultKafkaConsumerFactory; +import org.springframework.kafka.support.serializer.JsonDeserializer; + +import java.util.HashMap; +import java.util.Map; + +@Configuration +public class KafkaConsumerConfig { + + @Value("${spring.kafka.bootstrap-servers}") + private String bootstrapServers; + + @Bean + public ConsumerFactory paymentMessageConsumerFactory() { + Map config = new HashMap<>(); + config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + config.put(ConsumerConfig.GROUP_ID_CONFIG, "payment"); + config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class); + config.put(JsonDeserializer.TRUSTED_PACKAGES, "*"); + config.put(JsonDeserializer.VALUE_DEFAULT_TYPE, PaymentMessage.class.getName()); + + return new DefaultKafkaConsumerFactory<>(config, new StringDeserializer(), new JsonDeserializer<>(PaymentMessage.class)); + } + + @Bean + public ConcurrentKafkaListenerContainerFactory paymentMessageKafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(paymentMessageConsumerFactory()); + return factory; + } +} + diff --git a/src/main/java/com/server/kubacknotification/global/config/P6SpyFormatter.java b/src/main/java/com/server/kubacknotification/global/config/P6SpyFormatter.java new file mode 100644 index 0000000..77167ab --- /dev/null +++ b/src/main/java/com/server/kubacknotification/global/config/P6SpyFormatter.java @@ -0,0 +1,38 @@ +package com.server.kubacknotification.global.config; + +import com.p6spy.engine.logging.Category; +import com.p6spy.engine.spy.P6SpyOptions; +import com.p6spy.engine.spy.appender.MessageFormattingStrategy; +import org.hibernate.engine.jdbc.internal.FormatStyle; +import org.springframework.context.annotation.Configuration; + +import jakarta.annotation.PostConstruct; +import java.util.Locale; + +@Configuration +public class P6SpyFormatter implements MessageFormattingStrategy { + + @PostConstruct + public void setLogMessageFormat() { + P6SpyOptions.getActiveInstance().setLogMessageFormat(this.getClass().getName()); + } + + @Override + public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) { + sql = formatSql(category, sql); + return String.format("[%s] | %d ms | %s", category, elapsed, formatSql(category, sql)); + } + + private String formatSql(String category, String sql) { + if (sql != null && !sql.trim().isEmpty() && Category.STATEMENT.getName().equals(category)) { + String trimmedSQL = sql.trim().toLowerCase(Locale.ROOT); + if (trimmedSQL.startsWith("create") || trimmedSQL.startsWith("alter") || trimmedSQL.startsWith("comment")) { + sql = FormatStyle.DDL.getFormatter().format(sql); + } else { + sql = FormatStyle.BASIC.getFormatter().format(sql); + } + return sql; + } + return sql; + } +} diff --git a/src/main/java/com/server/kubacknotification/global/config/WebConfig.java b/src/main/java/com/server/kubacknotification/global/config/WebConfig.java new file mode 100644 index 0000000..db4030d --- /dev/null +++ b/src/main/java/com/server/kubacknotification/global/config/WebConfig.java @@ -0,0 +1,17 @@ +package com.server.kubacknotification.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("*") + .allowedMethods(HttpMethod.GET.name(), HttpMethod.POST.name(), HttpMethod.PUT.name(), HttpMethod.DELETE.name()); + } +} \ No newline at end of file diff --git a/src/main/java/com/server/kubacknotification/infra/jpa/entity/AEntity.java b/src/main/java/com/server/kubacknotification/infra/jpa/entity/AEntity.java deleted file mode 100644 index 7fb7182..0000000 --- a/src/main/java/com/server/kubacknotification/infra/jpa/entity/AEntity.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.server.kubacknotification.infra.jpa.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Entity -@Builder -@Getter -@AllArgsConstructor -@NoArgsConstructor -public class AEntity { - @Id - @GeneratedValue - private Long id; - @Column(nullable = false) - private String name; - - public static AEntity toEntity( - Long id, - String name - ) { - return new AEntity(id, name); - } -} diff --git a/src/main/java/com/server/kubacknotification/infra/jpa/repository/AJpaRepository.java b/src/main/java/com/server/kubacknotification/infra/jpa/repository/AJpaRepository.java deleted file mode 100644 index e0134e0..0000000 --- a/src/main/java/com/server/kubacknotification/infra/jpa/repository/AJpaRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.server.kubacknotification.infra.jpa.repository; - -import com.server.kubacknotification.infra.jpa.entity.AEntity; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface AJpaRepository extends JpaRepository { -} diff --git a/src/main/java/com/server/kubacknotification/infra/jpa/repositoryImpl/AJpaRepositoryImpl.java b/src/main/java/com/server/kubacknotification/infra/jpa/repositoryImpl/AJpaRepositoryImpl.java deleted file mode 100644 index 3bfda67..0000000 --- a/src/main/java/com/server/kubacknotification/infra/jpa/repositoryImpl/AJpaRepositoryImpl.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.server.kubacknotification.infra.jpa.repositoryImpl; - -import com.server.kubacknotification.domain.entity.A; -import com.server.kubacknotification.domain.repository.ARepository; -import com.server.kubacknotification.infra.jpa.repository.AJpaRepository; -import com.server.kubacknotification.infra.mapper.AMapper; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class AJpaRepositoryImpl implements ARepository { - private final AJpaRepository aJpaRepository; - private final AMapper aMapper; - - - @Override - public A findById(Long id) { - return aMapper.toDomain(aJpaRepository.findById(id).orElseThrow()); - } -} diff --git a/src/main/java/com/server/kubacknotification/infra/mapper/AMapper.java b/src/main/java/com/server/kubacknotification/infra/mapper/AMapper.java deleted file mode 100644 index f4bde71..0000000 --- a/src/main/java/com/server/kubacknotification/infra/mapper/AMapper.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.server.kubacknotification.infra.mapper; - -import com.server.kubacknotification.domain.entity.A; -import com.server.kubacknotification.infra.jpa.entity.AEntity; -import org.springframework.stereotype.Component; - -@Component -public class AMapper { - public A toDomain(AEntity aEntity) { - return A.toDomain( - aEntity.getId(), - aEntity.getName() - ); - } - public AEntity toEntity(A a) { - return AEntity.toEntity( - a.getId(), - a.getName() - ); - } -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 1364fb2..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=kuback-notification diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..4f507c2 --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,30 @@ +server: + port: 8080 + servlet: + contextPath: + +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3307/kuback?createDatabaseIfNotExist=true + username: admin + password: 1234 + hikari: + auto-commit: false + connection-test-query: SELECT 1 + minimum-idle: 10 + maximum-pool-size: 20 + + jpa: + database-platform: org.hibernate.dialect.MySQL8Dialect + hibernate: + # ddl-auto: create + ddl-auto: update + open-in-view: false + + kafka: + bootstrap-servers: "http://localhost:9092" + +mailSender: + username: + password: \ No newline at end of file From 57c16cbce884c72c84c0bb9e6933dccda29f5cc2 Mon Sep 17 00:00:00 2001 From: codrin2 Date: Fri, 4 Oct 2024 16:42:46 +0900 Subject: [PATCH 2/6] [fix] ignore application.yaml --- src/main/resources/application.yaml | 30 ----------------------------- 1 file changed, 30 deletions(-) delete mode 100644 src/main/resources/application.yaml diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml deleted file mode 100644 index 4f507c2..0000000 --- a/src/main/resources/application.yaml +++ /dev/null @@ -1,30 +0,0 @@ -server: - port: 8080 - servlet: - contextPath: - -spring: - datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3307/kuback?createDatabaseIfNotExist=true - username: admin - password: 1234 - hikari: - auto-commit: false - connection-test-query: SELECT 1 - minimum-idle: 10 - maximum-pool-size: 20 - - jpa: - database-platform: org.hibernate.dialect.MySQL8Dialect - hibernate: - # ddl-auto: create - ddl-auto: update - open-in-view: false - - kafka: - bootstrap-servers: "http://localhost:9092" - -mailSender: - username: - password: \ No newline at end of file From 218dd6a5bef7ba7668ab7b997d4594a514e5b34d Mon Sep 17 00:00:00 2001 From: codrin2 Date: Sun, 6 Oct 2024 00:13:45 +0900 Subject: [PATCH 3/6] =?UTF-8?q?[feat]=20=EA=B2=B0=EC=A0=9C=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EC=A0=84=EC=86=A1=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 ++ .../KubackNotificationApplication.java | 2 + .../application/service/EmailService.java | 57 +++++++++++++++ .../global/common/PaymentMessage.java | 2 + .../global/config/EmailConfig.java | 73 ++++++++++++------- 5 files changed, 114 insertions(+), 25 deletions(-) create mode 100644 src/main/java/com/server/kubacknotification/application/service/EmailService.java diff --git a/build.gradle b/build.gradle index 9d429c5..ff6ddae 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,12 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.kafka:spring-kafka' + + //Mail implementation 'org.springframework.boot:spring-boot-starter-mail' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect' + runtimeOnly 'com.mysql:mysql-connector-j' compileOnly 'org.projectlombok:lombok' diff --git a/src/main/java/com/server/kubacknotification/KubackNotificationApplication.java b/src/main/java/com/server/kubacknotification/KubackNotificationApplication.java index a845c10..d0d1296 100644 --- a/src/main/java/com/server/kubacknotification/KubackNotificationApplication.java +++ b/src/main/java/com/server/kubacknotification/KubackNotificationApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; +@EnableAsync @SpringBootApplication public class KubackNotificationApplication { diff --git a/src/main/java/com/server/kubacknotification/application/service/EmailService.java b/src/main/java/com/server/kubacknotification/application/service/EmailService.java new file mode 100644 index 0000000..0d77267 --- /dev/null +++ b/src/main/java/com/server/kubacknotification/application/service/EmailService.java @@ -0,0 +1,57 @@ +package com.server.kubacknotification.application.service; + +import com.server.kubacknotification.global.common.PaymentMessage; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring6.SpringTemplateEngine; + +@Service +@RequiredArgsConstructor +@Slf4j +@Transactional(readOnly = true) +public class EmailService { + + private final JavaMailSender javaMailSender; + private final SpringTemplateEngine templateEngine; + + // Kafka로부터 PaymentMessage를 수신하고 이메일 발송 + @KafkaListener(topics = "payment", groupId = "group1", containerFactory = "paymentMessageKafkaListenerContainerFactory") + public void consumePaymentMessage(PaymentMessage paymentMessage) { + sendPaymentEmail(paymentMessage); + } + + @Async + public void sendPaymentEmail(PaymentMessage paymentMessage) { + MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + try { + MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8"); + mimeMessageHelper.setTo(paymentMessage.getEmail()); // 수신자 이메일을 PaymentMessage의 email 필드로 설정 + mimeMessageHelper.setSubject("결제 완료 안내"); // 이메일 제목 + + // HTML 콘텐츠 생성 + String htmlContent = setPaymentContext(paymentMessage); + mimeMessageHelper.setText(htmlContent, true); // HTML 여부 true 설정 + + javaMailSender.send(mimeMessage); + log.info("Succeeded to send Email to: {}", paymentMessage.getUserId()); + } catch (Exception e) { + log.error("Failed to send Email to: {}", paymentMessage.getUserId(), e); + throw new RuntimeException(e); + } + } + + // HTML 템플릿에 PaymentMessage 데이터 주입 + public String setPaymentContext(PaymentMessage paymentMessage) { + Context context = new Context(); + context.setVariable("paymentMessage", paymentMessage); + return templateEngine.process("payment", context); // "payment.html" 템플릿 사용 + } +} diff --git a/src/main/java/com/server/kubacknotification/global/common/PaymentMessage.java b/src/main/java/com/server/kubacknotification/global/common/PaymentMessage.java index c6f7b05..3eb65b1 100644 --- a/src/main/java/com/server/kubacknotification/global/common/PaymentMessage.java +++ b/src/main/java/com/server/kubacknotification/global/common/PaymentMessage.java @@ -13,6 +13,8 @@ @NoArgsConstructor public class PaymentMessage { private Long userId; + private String email; + private String userName; private String seatNumber; private String seatGrade; private String payment; diff --git a/src/main/java/com/server/kubacknotification/global/config/EmailConfig.java b/src/main/java/com/server/kubacknotification/global/config/EmailConfig.java index e0fbdb4..a3f5df7 100644 --- a/src/main/java/com/server/kubacknotification/global/config/EmailConfig.java +++ b/src/main/java/com/server/kubacknotification/global/config/EmailConfig.java @@ -1,5 +1,6 @@ package com.server.kubacknotification.global.config; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -9,38 +10,60 @@ import java.util.Properties; @Configuration +@RequiredArgsConstructor public class EmailConfig { - @Value("${mailSender.password}") - private String mailsenderPassword; - @Value("${mailSender.username}") - private String mailsenderUsername; + private static final String MAIL_SMTP_AUTH = "mail.smtp.auth"; + private static final String MAIL_DEBUG = "mail.smtp.debug"; + private static final String MAIL_CONNECTION_TIMEOUT = "mail.smtp.connectiontimeout"; + private static final String MAIL_SMTP_STARTTLS_ENABLE = "mail.smtp.starttls.enable"; - @Bean - public JavaMailSender mailSender() { + // SMTP 서버 + @Value("${spring.mail.host}") + private String host; - JavaMailSenderImpl mailSender = new JavaMailSenderImpl();//JavaMailSender 의 구현체를 생성하고 - mailSender.setHost("smtp.gmail.com");// 속성을 넣기 시작합니다. 이메일 전송에 사용할 SMTP 서버 호스트를 설정 - mailSender.setPort(587);// 587로 포트를 지정 - mailSender.setUsername(mailsenderUsername);//구글계정을 넣습니다. - mailSender.setPassword(mailsenderPassword);//구글 앱 비밀번호를 넣습니다. + // 계정 + @Value("${spring.mail.username}") + private String username; - Properties javaMailProperties = getProperties(); + // 비밀번호 + @Value("${spring.mail.password}") + private String password; - mailSender.setJavaMailProperties(javaMailProperties); + // 포트번호 + @Value("${spring.mail.port}") + private int port; - return mailSender;//빈으로 등록한다. - } + @Value("${spring.mail.properties.mail.smtp.auth}") + private boolean auth; + + @Value("${spring.mail.properties.mail.smtp.debug}") + private boolean debug; + + @Value("${spring.mail.properties.mail.smtp.connectiontimeout}") + private int connectionTimeout; + + @Value("${spring.mail.properties.mail.starttls.enable}") + private boolean startTlsEnable; - private static Properties getProperties() { - Properties javaMailProperties = new Properties();//JavaMail의 속성을 설정하기 위해 Properties 객체를 생성 - javaMailProperties.put("mail.transport.protocol", "smtp");//프로토콜로 smtp 사용 - javaMailProperties.put("mail.smtp.auth", "true");//smtp 서버에 인증이 필요 - javaMailProperties.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");//SSL 소켓 팩토리 클래스 사용 - javaMailProperties.put("mail.smtp.starttls.enable", "true");//STARTTLS(TLS를 시작하는 명령)를 사용하여 암호화된 통신을 활성화 - javaMailProperties.put("mail.debug", "true");//디버깅 정보 출력 - javaMailProperties.put("mail.smtp.ssl.trust", "smtp.gmail.com");//smtp 서버의 ssl 인증서를 신뢰 - javaMailProperties.put("mail.smtp.ssl.protocols", "TLSv1.2");//사용할 ssl 프로토콜 버젼 - return javaMailProperties; + @Bean + public JavaMailSender javaMailService() { + JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl(); + javaMailSender.setHost(host); + javaMailSender.setUsername(username); + javaMailSender.setPassword(password); + javaMailSender.setPort(port); + + Properties properties = javaMailSender.getJavaMailProperties(); + properties.put(MAIL_SMTP_AUTH, auth); + properties.put(MAIL_DEBUG, debug); + properties.put(MAIL_CONNECTION_TIMEOUT, connectionTimeout); + properties.put(MAIL_SMTP_STARTTLS_ENABLE, startTlsEnable); + + javaMailSender.setJavaMailProperties(properties); + javaMailSender.setDefaultEncoding("UTF-8"); + + return javaMailSender; } + } From cc14892e5d757356f2e1bea34b60a93b6aec4c8c Mon Sep 17 00:00:00 2001 From: codrin2 Date: Sun, 6 Oct 2024 00:15:14 +0900 Subject: [PATCH 4/6] =?UTF-8?q?[feat]=20payment.html=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/templates/payment.html | 76 +++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/main/resources/templates/payment.html diff --git a/src/main/resources/templates/payment.html b/src/main/resources/templates/payment.html new file mode 100644 index 0000000..dd785c0 --- /dev/null +++ b/src/main/resources/templates/payment.html @@ -0,0 +1,76 @@ + + + + + + + + +
+ + + + + + + + + + +
+ Company Logo +

+
+

안녕하세요, 님!

+

고객님이 선택하신 좌석 정보와 결제 정보는 아래와 같습니다:

+

좌석 번호:

+

좌석 등급:

+

결제 금액:

+

결제 일시:

+
+

결제 내역

+

원래 가격:

+

할인 금액:

+

최종 결제 금액:

+
+

고객님의 결제가 정상적으로 완료되었습니다. 추가적인 문의 사항이 있으시면 언제든지 연락해 주세요.

+
+
+ + From cbd94a61e0f0308b2b1f2ce21be7d24764d29ff5 Mon Sep 17 00:00:00 2001 From: codrin2 Date: Sun, 6 Oct 2024 01:23:51 +0900 Subject: [PATCH 5/6] =?UTF-8?q?[feat]=20=EC=B9=B4=ED=94=84=EC=B9=B4=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=B0=9C=EA=B8=89=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yaml | 2 +- .../api/TestController.java | 22 +++++++++++ .../application/service/TestService.java | 19 ++++++++++ .../global/common/PaymentMessage.java | 24 ++++++++++++ .../global/config/KafkaProducerConfig.java | 37 +++++++++++++++++++ 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/server/kubacknotification/api/TestController.java create mode 100644 src/main/java/com/server/kubacknotification/application/service/TestService.java create mode 100644 src/main/java/com/server/kubacknotification/global/config/KafkaProducerConfig.java diff --git a/docker-compose.yaml b/docker-compose.yaml index 81d4656..ea43864 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -28,7 +28,7 @@ services: - "9093" environment: KAFKA_LISTENERS: INSIDE://0.0.0.0:9093,OUTSIDE://0.0.0.0:9092 - KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka:9093,OUTSIDE://210.109.53.237:9092 + KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka:9093,OUTSIDE://localhost:9092 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 diff --git a/src/main/java/com/server/kubacknotification/api/TestController.java b/src/main/java/com/server/kubacknotification/api/TestController.java new file mode 100644 index 0000000..469326a --- /dev/null +++ b/src/main/java/com/server/kubacknotification/api/TestController.java @@ -0,0 +1,22 @@ +package com.server.kubacknotification.api; + +import com.server.kubacknotification.application.service.TestService; +import com.server.kubacknotification.global.common.PaymentMessage; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class TestController { + + private final TestService testService; + + @PostMapping("/payments") + public ResponseEntity sendPaymentMessage(@RequestBody PaymentMessage paymentMessage) { + testService.publishPaymentMessage(paymentMessage); + return ResponseEntity.ok("Payment message sent successfully"); + } +} diff --git a/src/main/java/com/server/kubacknotification/application/service/TestService.java b/src/main/java/com/server/kubacknotification/application/service/TestService.java new file mode 100644 index 0000000..e0a6449 --- /dev/null +++ b/src/main/java/com/server/kubacknotification/application/service/TestService.java @@ -0,0 +1,19 @@ +package com.server.kubacknotification.application.service; + +import com.server.kubacknotification.global.common.PaymentMessage; +import lombok.RequiredArgsConstructor; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class TestService { + + private final KafkaTemplate kafkaTemplate; + private static final String TOPIC = "payment"; + + public void publishPaymentMessage(PaymentMessage paymentMessage) { + // Kafka로 paymentMessage 전송 + kafkaTemplate.send(TOPIC, paymentMessage); + } +} diff --git a/src/main/java/com/server/kubacknotification/global/common/PaymentMessage.java b/src/main/java/com/server/kubacknotification/global/common/PaymentMessage.java index 3eb65b1..15783a9 100644 --- a/src/main/java/com/server/kubacknotification/global/common/PaymentMessage.java +++ b/src/main/java/com/server/kubacknotification/global/common/PaymentMessage.java @@ -1,5 +1,6 @@ package com.server.kubacknotification.global.common; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -11,16 +12,39 @@ @Builder @AllArgsConstructor @NoArgsConstructor +@Schema(description = "결제 메시지") public class PaymentMessage { + + @Schema(description = "유저 ID", example = "1") private Long userId; + + @Schema(description = "유저 이메일", example = "@gmail.com") private String email; + + @Schema(description = "유저 이름", example = "문희상") private String userName; + + @Schema(description = "좌석 번호", example = "A12") private String seatNumber; + + @Schema(description = "좌석 등급", example = "VIP") private String seatGrade; + + @Schema(description = "결제 금액", example = "15000") private String payment; + + @Schema(description = "결제 날짜", example = "2024-10-06T14:00:00") private LocalDateTime payDate; + + @Schema(description = "원래 가격", example = "20000") private String originalPrice; + + @Schema(description = "할인 금액", example = "5000") private String salePrice; + + @Schema(description = "최종 결제 금액", example = "15000") private String finalPrice; } + + diff --git a/src/main/java/com/server/kubacknotification/global/config/KafkaProducerConfig.java b/src/main/java/com/server/kubacknotification/global/config/KafkaProducerConfig.java new file mode 100644 index 0000000..75b8b0a --- /dev/null +++ b/src/main/java/com/server/kubacknotification/global/config/KafkaProducerConfig.java @@ -0,0 +1,37 @@ +package com.server.kubacknotification.global.config; + +import com.server.kubacknotification.global.common.PaymentMessage; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.core.ProducerFactory; +import org.springframework.kafka.support.serializer.JsonSerializer; + +import java.util.HashMap; +import java.util.Map; + +@Configuration +public class KafkaProducerConfig { + + @Value("${spring.kafka.bootstrap-servers}") + private String bootstrapServers; + + @Bean + public ProducerFactory paymentMessageProducerFactory() { + Map configProps = new HashMap<>(); + configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); // 메시지를 JSON으로 직렬화 + + return new DefaultKafkaProducerFactory<>(configProps); + } + + @Bean + public KafkaTemplate kafkaTemplate() { + return new KafkaTemplate<>(paymentMessageProducerFactory()); + } +} From 08dc3ff81bab4d4fd50741dcf2d352427527c196 Mon Sep 17 00:00:00 2001 From: codrin2 Date: Sun, 6 Oct 2024 01:50:33 +0900 Subject: [PATCH 6/6] =?UTF-8?q?[remove]=20=ED=86=B5=ED=95=A9=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../KubackNotificationApplicationTests.java | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 src/test/java/com/server/kubacknotification/KubackNotificationApplicationTests.java diff --git a/src/test/java/com/server/kubacknotification/KubackNotificationApplicationTests.java b/src/test/java/com/server/kubacknotification/KubackNotificationApplicationTests.java deleted file mode 100644 index aee98ca..0000000 --- a/src/test/java/com/server/kubacknotification/KubackNotificationApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.server.kubacknotification; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class KubackNotificationApplicationTests { - - @Test - void contextLoads() { - } - -}