#29 pass: send notification before class job

This commit is contained in:
haerong22
2022-12-30 00:49:37 +09:00
parent dc23c3405b
commit a445ff6bbe
7 changed files with 274 additions and 0 deletions

View File

@@ -21,6 +21,8 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-batch'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
// hibernate-types
implementation 'com.vladmihalcea:hibernate-types-52:2.19.2'

View File

@@ -0,0 +1,38 @@
package com.example.passbatch.adapter.message;
import com.example.passbatch.config.KakaoTalkMessageConfig;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
@Service
public class KakaoTalkMessageAdapter {
private final WebClient webClient;
public KakaoTalkMessageAdapter(KakaoTalkMessageConfig config) {
webClient = WebClient.builder()
.baseUrl(config.getHost())
.defaultHeaders(h -> {
h.setBearerAuth(config.getToken());
h.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
}).build();
}
public boolean sendKakaoTalkMessage(final String uuid, final String text) {
KakaoTalkMessageResponse response = webClient.post().uri("/v1/api/talk/friends/message/default/send")
.body(BodyInserters.fromValue(new KakaoTalkMessageRequest(uuid, text)))
.retrieve()
.bodyToMono(KakaoTalkMessageResponse.class)
.block();
if (response == null || response.getSuccessfulReceiverUuids() == null) {
return false;
}
return response.getSuccessfulReceiverUuids().size() > 0;
}
}

View File

@@ -0,0 +1,55 @@
package com.example.passbatch.adapter.message;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.Collections;
import java.util.List;
@Getter
@Setter
@ToString
public class KakaoTalkMessageRequest {
@JsonProperty("template_object")
private TemplateObject templateObject;
@JsonProperty("receiver_uuids")
private List<String> receiverUuids;
@Getter
@Setter
@ToString
public static class TemplateObject {
@JsonProperty("object_type")
private String objectType;
private String text;
private Link link;
@Getter
@Setter
@ToString
public static class Link {
@JsonProperty("web_url")
private String webUrl;
}
}
public KakaoTalkMessageRequest(String uuid, String text) {
List<String> receiverUuids = Collections.singletonList(uuid);
TemplateObject.Link link = new TemplateObject.Link();
TemplateObject templateObject = new TemplateObject();
templateObject.setObjectType("text");
templateObject.setText(text);
templateObject.setLink(link);
this.receiverUuids = receiverUuids;
this.templateObject = templateObject;
}
}

View File

@@ -0,0 +1,17 @@
package com.example.passbatch.adapter.message;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.List;
@Getter
@Setter
@ToString
public class KakaoTalkMessageResponse {
@JsonProperty("successful_receiver_uuids")
private List<String> successfulReceiverUuids;
}

View File

@@ -0,0 +1,16 @@
package com.example.passbatch.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "kakaotalk")
public class KakaoTalkMessageConfig {
private String host;
private String token;
}

View File

@@ -0,0 +1,103 @@
package com.example.passbatch.job.notification;
import com.example.passbatch.repository.booking.BookingEntity;
import com.example.passbatch.repository.notification.NotificationEntity;
import com.example.passbatch.repository.notification.NotificationEvent;
import com.example.passbatch.repository.notification.NotificationModelMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.database.JpaCursorItemReader;
import org.springframework.batch.item.database.JpaItemWriter;
import org.springframework.batch.item.database.JpaPagingItemReader;
import org.springframework.batch.item.database.builder.JpaCursorItemReaderBuilder;
import org.springframework.batch.item.database.builder.JpaItemWriterBuilder;
import org.springframework.batch.item.database.builder.JpaPagingItemReaderBuilder;
import org.springframework.batch.item.support.SynchronizedItemStreamReader;
import org.springframework.batch.item.support.builder.SynchronizedItemStreamReaderBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import javax.persistence.EntityManagerFactory;
import java.util.Map;
@Configuration
@RequiredArgsConstructor
public class SendNotificationBeforeClassJobConfig {
private final int CHUNK_SIZE = 10;
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
private final EntityManagerFactory entityManagerFactory;
private final SendNotificationItemWriter sendNotificationItemWriter;
@Bean
public Job sendNotificationBeforeClassJob() {
return this.jobBuilderFactory.get("sendNotificationBeforeClassJob")
.start(addNotificationStep())
.next(sendNotificationStep())
.build();
}
@Bean
public Step addNotificationStep() {
return this.stepBuilderFactory.get("addNotificationStep")
.<BookingEntity, NotificationEntity>chunk(CHUNK_SIZE)
.reader(addNotificationItemReader())
.processor(addNotificationItemProcessor())
.writer(addNotificationItemWriter())
.build();
}
@Bean
public JpaPagingItemReader<BookingEntity> addNotificationItemReader() {
return new JpaPagingItemReaderBuilder<BookingEntity>()
.name("addNotificationItemReader")
.entityManagerFactory(entityManagerFactory)
.pageSize(CHUNK_SIZE)
.queryString("select b from BookingEntity b join fetch b.userEntity where b.status = :status and b.startedAt <= :startedAt order by b.bookingSeq")
.build();
}
@Bean
public ItemProcessor<BookingEntity, NotificationEntity> addNotificationItemProcessor() {
return bookingEntity ->
NotificationModelMapper.INSTANCE.toNotificationEntity(bookingEntity, NotificationEvent.BEFORE_CLASS);
}
@Bean
public JpaItemWriter<NotificationEntity> addNotificationItemWriter() {
return new JpaItemWriterBuilder<NotificationEntity>()
.entityManagerFactory(entityManagerFactory)
.build();
}
@Bean
public Step sendNotificationStep() {
return this.stepBuilderFactory.get("sendNotificationStep")
.<NotificationEntity, NotificationEntity>chunk(CHUNK_SIZE)
.reader(sendNotificationItemReader())
.writer(sendNotificationItemWriter)
.taskExecutor(new SimpleAsyncTaskExecutor())
.build();
}
@Bean
public SynchronizedItemStreamReader<NotificationEntity> sendNotificationItemReader() {
JpaCursorItemReader<NotificationEntity> itemReader = new JpaCursorItemReaderBuilder<NotificationEntity>()
.name("sendNotificationItemReader")
.entityManagerFactory(entityManagerFactory)
.queryString("select n from NotificationEntity n where n.event = :event and n.sent = :sent")
.parameterValues(Map.of("event", NotificationEvent.BEFORE_CLASS, "sent", false))
.build();
return new SynchronizedItemStreamReaderBuilder<NotificationEntity>()
.delegate(itemReader)
.build();
}
}

View File

@@ -0,0 +1,43 @@
package com.example.passbatch.job.notification;
import com.example.passbatch.adapter.message.KakaoTalkMessageAdapter;
import com.example.passbatch.repository.notification.NotificationEntity;
import com.example.passbatch.repository.notification.NotificationRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.item.ItemWriter;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.List;
@Slf4j
@Component
public class SendNotificationItemWriter implements ItemWriter<NotificationEntity> {
private final NotificationRepository notificationRepository;
private final KakaoTalkMessageAdapter kakaoTalkMessageAdapter;
public SendNotificationItemWriter(NotificationRepository notificationRepository, KakaoTalkMessageAdapter kakaoTalkMessageAdapter) {
this.notificationRepository = notificationRepository;
this.kakaoTalkMessageAdapter = kakaoTalkMessageAdapter;
}
@Override
public void write(List<? extends NotificationEntity> notificationEntities) throws Exception {
int count = 0;
for (NotificationEntity notificationEntity : notificationEntities) {
boolean successful = kakaoTalkMessageAdapter.sendKakaoTalkMessage(notificationEntity.getUuid(), notificationEntity.getText());
if (successful) {
notificationEntity.setSent(true);
notificationEntity.setSentAt(LocalDateTime.now());
notificationRepository.save(notificationEntity);
count++;
}
}
log.info("SendNotificationItemWriter - write: 수업 전 알람 {}/{}건 전송 성공", count, notificationEntities.size());
}
}