#29 pass: send notification before class job
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user