Skip to content

Commit

Permalink
[Feat] 축제 행사 정보 연동 개발 및 전시 정보 연동 기능 이식
Browse files Browse the repository at this point in the history
- 축제 행사 정보 연동 기능 구현
- 전시 정보 연동 기능 artfriendly 앱에서 이식
- restTemplate에서 open feign으로 api 호출 방식 리팩토링
- Tasklet 방식 -> Chunk 방식으로 구현
  • Loading branch information
yjy8501 committed Aug 1, 2024
1 parent 09e2ea8 commit 696caf1
Show file tree
Hide file tree
Showing 51 changed files with 1,854 additions and 103 deletions.
26 changes: 26 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ plugins {
id 'io.spring.dependency-management' version '1.1.5'
}

ext {
springCloudVersion = "2023.0.3"
}

group = 'org.com'
version = '0.0.1-SNAPSHOT'

Expand All @@ -29,12 +33,28 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.jsoup:jsoup:1.15.3'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.batch:spring-batch-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

// mapstruct 라이브러리
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'

// queryDSL
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml'
// Feign-Jackson
implementation 'io.github.openfeign:feign-jackson'

}

tasks.named('test') {
Expand All @@ -43,4 +63,10 @@ tasks.named('test') {
// 중복 파일의 경우 제외한다
bootJar {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:$springCloudVersion"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableJpaAuditing
@EnableScheduling
@EnableFeignClients
@SpringBootApplication
public class ArtfriendlyBatchApplication {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.com.artfriendlybatch.domain.exhibition.callable;

import org.com.artfriendlybatch.domain.exhibition.dto.ExhibitionUpdateDto;
import org.com.artfriendlybatch.domain.exhibition.dto.ExhibitionUrlUpdateDto;
import org.com.artfriendlybatch.domain.exhibition.entity.ExhibitionInfo;
import org.com.artfriendlybatch.global.utils.HtmlEntityRemover;
import org.jsoup.Jsoup;
Expand All @@ -10,19 +10,19 @@
import java.io.IOException;
import java.util.concurrent.Callable;

public class ExhibitionUrlCallable implements Callable<ExhibitionUpdateDto> {
public class ExhibitionUrlCallable implements Callable<ExhibitionUrlUpdateDto> {
private final ExhibitionInfo exhibitionInfo;

public ExhibitionUrlCallable(ExhibitionInfo exhibitionInfo) {
this.exhibitionInfo = exhibitionInfo;
}

@Override
public ExhibitionUpdateDto call() throws Exception {
public ExhibitionUrlUpdateDto call() throws Exception {
return getExhibitionUpdateDto();
}

private ExhibitionUpdateDto getExhibitionUpdateDto() throws IOException {
private ExhibitionUrlUpdateDto getExhibitionUpdateDto() throws IOException {
// 검색어 설정
String searchWord = HtmlEntityRemover.removeHtmlEntities(exhibitionInfo.getTitle());
String url = "https://www.mcst.go.kr/kor/s_culture/culture/cultureList.jsp?pSeq=&pRo=&pCurrentPage=1&pType=&pPeriod=&fromDt=&toDt=&pArea=&pSearchType=01&pSearchWord=" + searchWord;
Expand All @@ -34,7 +34,7 @@ private ExhibitionUpdateDto getExhibitionUpdateDto() throws IOException {

if (link != null) {
String href = link.attr("href");
return ExhibitionUpdateDto.builder()
return ExhibitionUrlUpdateDto.builder()
.exhibitionInfo(exhibitionInfo)
.url(exhibitionSite+href).build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package org.com.artfriendlybatch.domain.exhibition.chunk;

import org.com.artfriendlybatch.domain.exhibition.dto.ExhibitionInfoIntegrateDto;
import org.com.artfriendlybatch.domain.exhibition.dto.apiIntegrationDto.callExhibitionDto.DetailPerformInfo;
import org.com.artfriendlybatch.domain.exhibition.dto.apiIntegrationDto.callExhibitionsDto.PerformList;
import org.com.artfriendlybatch.domain.exhibition.entity.ExhibitionInfo;
import org.com.artfriendlybatch.domain.exhibition.mapper.ExhibitionInfoMapper;
import org.com.artfriendlybatch.domain.exhibition.repository.ExhibitionInfoRepository;
import org.com.artfriendlybatch.domain.exhibition.service.ExhibitionInfoService;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Optional;

@Configuration
public class ExhibitionInfoChunk {
private final ExhibitionInfoService exhibitionInfoService;
private final ExhibitionInfoRepository exhibitionInfoRepository;

public ExhibitionInfoChunk(ExhibitionInfoService exhibitionInfoService, ExhibitionInfoRepository exhibitionInfoRepository) {
this.exhibitionInfoService = exhibitionInfoService;
this.exhibitionInfoRepository = exhibitionInfoRepository;
}

@Bean
public ItemReader<PerformList> exhibitionInfoIntegrateReader() {
return new PerformReader(exhibitionInfoService); // API를 호출하여 데이터 읽기
}

@Bean
public ItemProcessor<PerformList, ExhibitionInfoIntegrateDto> exhibitionInfoIntegrateProcessor(ExhibitionInfoMapper exhibitionInfoMapper) {
return item -> {
// API 호출 및 ExhibitionInfo 생성 로직(이미 있는 값이면 업데이트로 수정)
int seq = Integer.parseInt(item.getSeq());
DetailPerformInfo detailPerformInfo = exhibitionInfoService.getDetailPerformInfo(seq);
Optional<ExhibitionInfo> optionalExhibitionInfo = Optional.ofNullable(exhibitionInfoRepository.findExhibitionInfoBySeq(seq));

// 새로운 dto 하나 더 만들어서 저장 할 때 업뎃 or 새로 생성 나눠서 할 수 있도록!!
return optionalExhibitionInfo
.map(existingInfo -> {
// 업데이트
existingInfo.updateForm(exhibitionInfoMapper.perforInfoToExhibitionInfo(detailPerformInfo));
return ExhibitionInfoIntegrateDto.builder()
.exhibitionInfo(existingInfo)
.updateAble(true)
.build();
})
.orElseGet(() -> {
// 생성
ExhibitionInfo newExhibitionInfo = exhibitionInfoMapper.perforInfoToExhibitionInfo(detailPerformInfo);
return ExhibitionInfoIntegrateDto.builder()
.exhibitionInfo(newExhibitionInfo)
.updateAble(false)
.build();
});
};
}

@Bean
public ItemWriter<ExhibitionInfoIntegrateDto> exhibitionInfoIntegrateWriter() {
return items -> {
for (ExhibitionInfoIntegrateDto dto : items) {
if (dto.isUpdateAble()) {
// 업데이트 로직
exhibitionInfoRepository.save(dto.getExhibitionInfo());
} else {
// 새로 생성하는 로직
exhibitionInfoService.createExhibition(dto.getExhibitionInfo());
}
}
};
}

@Bean
public ItemReader<ExhibitionInfo> exhibitionInfoReader() {
return new ExhibitionInfoReader(exhibitionInfoService);
}

@Bean
public ItemProcessor<ExhibitionInfo, ExhibitionInfo> exhibitionInfoProcessor() {
return ExhibitionInfo::updateProgressStatus;
}

@Bean
public ItemWriter<ExhibitionInfo> exhibitionInfoWriter() {
return exhibitionInfoRepository::saveAll;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.com.artfriendlybatch.domain.exhibition.chunk;

import org.com.artfriendlybatch.domain.exhibition.entity.ExhibitionInfo;
import org.com.artfriendlybatch.domain.exhibition.service.ExhibitionInfoService;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;

import java.util.List;

public class ExhibitionInfoReader implements ItemReader<ExhibitionInfo> {
private final ExhibitionInfoService exhibitionInfoService;
private final List<ExhibitionInfo> details;
private int currentIndex = 0;

public ExhibitionInfoReader(ExhibitionInfoService exhibitionInfoService) {
this.exhibitionInfoService = exhibitionInfoService;
this.details = this.fetchDataFromDb();
}


@Override
public ExhibitionInfo read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
if (currentIndex < details.size()) {
return details.get(currentIndex++); // 현재 인덱스의 항목 반환 후 인덱스 증가
}
return null; // 더 이상 읽을 항목이 없으면 null 반환
}

private List<ExhibitionInfo> fetchDataFromDb() {
return exhibitionInfoService.getExhibitionInfoByProgressStatusIn(List.of("inProgress", "scheduled"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.com.artfriendlybatch.domain.exhibition.chunk;

import org.com.artfriendlybatch.domain.exhibition.dto.apiIntegrationDto.callExhibitionsDto.PerformList;
import org.com.artfriendlybatch.domain.exhibition.service.ExhibitionInfoService;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;

import java.util.List;

public class PerformReader implements ItemReader<PerformList> {

private final ExhibitionInfoService exhibitionInfoService;
private final List<PerformList> details;
private int currentIndex = 0;

public PerformReader(ExhibitionInfoService exhibitionInfoService) {
this.exhibitionInfoService = exhibitionInfoService;
this.details = this.fetchDataFromApi();
}

@Override
public PerformList read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
if (currentIndex < details.size()) {
return details.get(currentIndex++); // 현재 인덱스의 항목 반환 후 인덱스 증가
}
return null; // 더 이상 읽을 항목이 없으면 null 반환
}

private List<PerformList> fetchDataFromApi() {
return exhibitionInfoService.getPerformInfo();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.com.artfriendlybatch.domain.exhibition.dto;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import org.com.artfriendlybatch.domain.exhibition.entity.ExhibitionInfo;

@Getter
@Setter
@Builder
public class ExhibitionInfoIntegrateDto {
ExhibitionInfo exhibitionInfo;
boolean updateAble;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
@Getter
@Setter
@Builder
public class ExhibitionUpdateDto {
public class ExhibitionUrlUpdateDto {
private ExhibitionInfo exhibitionInfo;
private String url;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.com.artfriendlybatch.domain.exhibition.dto.apiIntegrationDto;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ApiRequestBuilder {
private String realmCode;
private String startDate;
private String endDate;
private int cPage;
private int rows;
private String sido;
private String gugun;
private String place;
private String gpsxfrom;
private String gpsyform;
private String gpsxto;
private String gpsyto;
private String keyword;
private int sortStdr;

@Builder
public ApiRequestBuilder(String realmCode, String startDate, String endDate, int cPage, int rows, String sido, String gugun, String place, Double gpsxfrom, Double gpsyform, Double gpsxto, Double gpsyto, String keyword, int sortStdr) {
this.realmCode = realmCode;
this.startDate = startDate;
this.endDate = endDate;
this.cPage = cPage;
this.rows = rows;
this.sido = sido == null ? "" : sido;
this.gugun = gugun == null ? "" : gugun;
this.place = place == null ? "" : place;
this.gpsxfrom = gpsxfrom == null ? "" : String.valueOf(gpsxfrom);
this.gpsyform = gpsyform == null ? "" : String.valueOf(gpsyform);
this.gpsxto = gpsxto == null ? "" : String.valueOf(gpsxto);
this.gpsyto = gpsyto == null ? "" : String.valueOf(gpsyto);
this.keyword = keyword == null ? "" : keyword;
this.sortStdr = sortStdr;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.com.artfriendlybatch.domain.exhibition.dto.apiIntegrationDto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import lombok.*;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class ComMsgHeader {
@JacksonXmlProperty(localName = "RequestMsgID")
private String requestMsgID;

@JacksonXmlProperty(localName = "ResponseTime")
private String responseTime;

@JacksonXmlProperty(localName = "ResponseMsgID")
private String responseMsgID;

@JacksonXmlProperty(localName = "SuccessYN")
private String successYN;

@JacksonXmlProperty(localName = "ReturnCode")
private String returnCode;

@JacksonXmlProperty(localName = "ErrMsg")
private String errMsg;
}
Loading

0 comments on commit 696caf1

Please sign in to comment.