-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
347 additions
and
126 deletions.
There are no files selected for viewing
141 changes: 141 additions & 0 deletions
141
module-batch/src/main/java/inspiration/application/member/count/MemberCountJobConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package inspiration.application.member.count; | ||
|
||
import inspiration.application.slack.SlackService; | ||
import inspiration.domain.member.Member; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.apache.commons.csv.CSVFormat; | ||
import org.apache.commons.csv.CSVPrinter; | ||
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.JobScope; | ||
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; | ||
import org.springframework.batch.core.launch.support.RunIdIncrementer; | ||
import org.springframework.batch.item.database.builder.JpaPagingItemReaderBuilder; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
import javax.persistence.EntityManagerFactory; | ||
import java.io.File; | ||
import java.io.FileWriter; | ||
import java.io.IOException; | ||
import java.time.LocalDate; | ||
import java.time.LocalDateTime; | ||
import java.time.YearMonth; | ||
import java.util.Map; | ||
import java.util.stream.Collectors; | ||
|
||
@Slf4j | ||
@ConditionalOnProperty( | ||
name = "spring.batch.job.names", | ||
havingValue = MemberCountJobConfig.JOB_NAME | ||
) | ||
@Configuration | ||
@RequiredArgsConstructor | ||
public class MemberCountJobConfig { | ||
static final String JOB_NAME = "member-count-job"; | ||
static final String STEP_NAME = "member-count-step"; | ||
private static final int CHUNK_SIZE = 1000; | ||
private final JobBuilderFactory jobBuilderFactory; | ||
private final StepBuilderFactory stepBuilderFactory; | ||
private final SlackService slackService; | ||
|
||
@Value("ygtang.temporary-directory-path") | ||
private String temporaryDirectoryPath; | ||
|
||
@Bean | ||
public Job memberDailyCountJob(Step memberDailyCountStep) { | ||
return jobBuilderFactory.get(JOB_NAME) | ||
.incrementer(new RunIdIncrementer()) | ||
.start(memberDailyCountStep) | ||
.build(); | ||
} | ||
|
||
@Bean | ||
@JobScope | ||
public Step memberDailyCountStep( | ||
EntityManagerFactory entityManagerFactory | ||
) { | ||
return stepBuilderFactory.get(STEP_NAME) | ||
.<Member, Member>chunk(CHUNK_SIZE) | ||
.reader(new JpaPagingItemReaderBuilder<Member>() | ||
.name("memberItemReader") | ||
.entityManagerFactory(entityManagerFactory) | ||
.queryString("SELECT m FROM Member m") | ||
.pageSize(CHUNK_SIZE) | ||
.build()) | ||
.writer(items -> { | ||
LocalDateTime now = LocalDateTime.now(); | ||
|
||
// daily | ||
Map<LocalDate, Integer> dailyCountMap = items.stream() | ||
.collect(Collectors.toMap( | ||
it -> it.getCreatedDateTime().toLocalDate(), | ||
it -> 1, | ||
Integer::sum | ||
)); | ||
slackService.sendCsv( | ||
toDailyCsvFile(dailyCountMap), | ||
"Member Daily Count at " + now, | ||
"member_daily_count_" + now.toLocalDate() + ".csv" | ||
); | ||
|
||
// monthly | ||
Map<YearMonth, Integer> monthlyCountMap = items.stream() | ||
.collect(Collectors.toMap( | ||
it -> YearMonth.from(it.getCreatedDateTime()), | ||
it -> 1, | ||
Integer::sum | ||
)); | ||
slackService.sendCsv( | ||
toMonthlyCsvFile(monthlyCountMap), | ||
"Member Monthly Count at " + now, | ||
"member_monthly_count_" + now.toLocalDate() + ".csv" | ||
); | ||
}) | ||
.build(); | ||
} | ||
|
||
private File toDailyCsvFile(Map<LocalDate, Integer> dailyCountMap) throws IOException { | ||
File file = File.createTempFile("memberDailyCount", "csv", new File(temporaryDirectoryPath)); | ||
file.deleteOnExit(); | ||
FileWriter out = new FileWriter(file); | ||
CSVFormat csvFormat = CSVFormat.Builder.create() | ||
.setHeader("date", "count") | ||
.build(); | ||
try (CSVPrinter printer = new CSVPrinter(out, csvFormat)) { | ||
dailyCountMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(it -> { | ||
try { | ||
printer.printRecord(it.getKey(), it.getValue()); | ||
} catch (IOException e) { | ||
throw new IllegalStateException(e); | ||
} | ||
} | ||
); | ||
} | ||
return file; | ||
} | ||
|
||
private File toMonthlyCsvFile(Map<YearMonth, Integer> monthlyCountMap) throws IOException { | ||
File file = File.createTempFile("memberMonthlyCount", "csv", new File(temporaryDirectoryPath)); | ||
file.deleteOnExit(); | ||
FileWriter out = new FileWriter(file); | ||
CSVFormat csvFormat = CSVFormat.Builder.create() | ||
.setHeader("yearMonth", "count") | ||
.build(); | ||
try (CSVPrinter printer = new CSVPrinter(out, csvFormat)) { | ||
monthlyCountMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(it -> { | ||
try { | ||
printer.printRecord(it.getKey(), it.getValue()); | ||
} catch (IOException e) { | ||
throw new IllegalStateException(e); | ||
} | ||
} | ||
); | ||
} | ||
return file; | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
module-batch/src/main/java/inspiration/application/slack/SlackService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package inspiration.application.slack; | ||
|
||
import java.io.File; | ||
|
||
public interface SlackService { | ||
void sendCsv(File file, String title, String filename); | ||
} |
40 changes: 40 additions & 0 deletions
40
module-batch/src/main/java/inspiration/application/slack/SlackServiceImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package inspiration.application.slack; | ||
|
||
import com.slack.api.Slack; | ||
import com.slack.api.methods.MethodsClient; | ||
import com.slack.api.methods.request.files.FilesUploadRequest; | ||
import com.slack.api.methods.response.files.FilesUploadResponse; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.io.File; | ||
import java.util.Collections; | ||
|
||
@Slf4j | ||
@Service | ||
public class SlackServiceImpl implements SlackService { | ||
@Value("${ygtang.slack.token.bot}") | ||
private String botToken; | ||
@Value("${ygtang.slack.channel.report}") | ||
private String reportChannel; | ||
|
||
@Override | ||
public void sendCsv(File file, String title, String filename) { | ||
try (Slack slack = Slack.getInstance()) { | ||
MethodsClient methods = slack.methods(botToken); | ||
FilesUploadResponse response = methods.filesUpload( | ||
FilesUploadRequest.builder() | ||
.channels(Collections.singletonList(reportChannel)) | ||
.title(title) | ||
.filename(filename) | ||
.filetype("text/csv") | ||
.file(file) | ||
.build() | ||
); | ||
log.info("response: {}", response); | ||
} catch (Exception e) { | ||
log.error("Failed to send file to slack", e); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.