본문 바로가기

IT 개발/Spring

@Scheduled Open API사용하여 공휴일 DB 적재하기

반응형

 

달력 구현하는중에 토요일과 일요일은 자동으로 확인되지만..
비정기적으로 있는 음력으로 세어지는 공휴일의 경우, 체크구현이 어렵다.
기존에 있는 달력라이브러리를 가지고와서 구현할수 있겠지만..
그보다 오픈API로 공휴일을 DB에 적재하여 불러오는게 좀 더 안정적으로 보여 그렇게 사용하기로!!

Entity

@Entity
@Getter
@Setter
@Builder
@DynamicInsert
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Description("공휴일 정보")
@Table(name = "holiday_info_t")
public class Holiday {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    @Comment("공휴일아이디")
    private Long id;

    @Column(name = "locdate", length = 8)
    @Comment("날짜")
    private String locDate;

    @Column(name = "seq")
    @Comment("순번")
    private int seq;

    @Column(name = "date_kind", length = 5)
    @Comment("종류")
    private String dateKind;

    @Column(name = "is_holiday", length = 5)
    @Comment("공공기관 휴일여부")
    private String isHoliday;

    @Column(name = "date_name", length = 200)
    @Comment("명칭")
    private String dateName;

    @CreationTimestamp
    @Comment("등록일시")
    @Column(nullable = false, length = 20, updatable = false)
    private LocalDateTime regDttm;

    @UpdateTimestamp
    @Comment("수정일시")
    @Column(length = 20)
    private LocalDateTime modDttm;

    @ColumnDefault("'N'")
    @Comment("삭제여부")
    private String delYn;

}

DTO

@Data
public class HolidayDto implements Serializable {
    private final Long id;
    private final String locDate;
    private final int seq;
    private final String dateKind;
    private final String isHoliday;
    private final String dateName;
    private final LocalDateTime regDttm;
    private final LocalDateTime modDttm;
    private final String delYn;

}

JPA Repository

public interface HolidayRepository extends JpaRepository<Holiday, Long> {

}

본격적으로 스케줄러 서비스코드 구현

https://www.data.go.kr/data/15012690/openapi.do

  1. 특일정보 API 신청하는 페이지에서 가입후 활용신청
  2. 받은 서비스키를 넣어 코드구현

요구조건

1. 당월부터 3개월 간의 휴일정보를 받아온다.

2. 매월 1일 00시 00분에 스케줄러를 실행한다.

3. 데이터 적재 전, 전체 데이터를 삭제한다.

4. Code 테이블의 '공휴일정보' 데이터에 스케줄러 실행시 상태정보를 남긴다.(Y, N, E)

Service

@Service
@RequiredArgsConstructor
public class HolidaySchedulerService {

    private final CodeRepository codeRepository;


    private final HolidayRepository holidayRepository;

    private final String SERVICE_CD = "001004";

    private final RESTUtil restUtil = new RESTUtil();

    private final String URL = "http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo?solYear=";

    private final String SERVICE_KEY = "활용신청하고 받은 서비스키 입력";

    //공휴일 업데이트
//    @Scheduled(fixedDelay = 1000*60*60) //60분마다 실행
    @Scheduled(cron = "* * 0 1 * ?") // 매월 1일 00시 00분에 실행
    @Transactional
    public void saveHD() {
        long now = System.currentTimeMillis() / 1000;
        JLog.logi("schedule tasks start"+ now);
        try {
            // Code 정보에서 스케줄 사용중여부를 확인해서 사용중이 아니면 스케줄 실행
            Code statusCode = getCode(SERVICE_CD);
            if (statusCode == null) return;

            //전체 데이터 삭제
            holidayRepository.deleteAllInBatch();

            String formatDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))+"00";
            String year = formatDate.substring(0, 4);
            String month = formatDate.substring(4, 6);  //해당 달
            Integer nextMon = Integer.parseInt(month) + 1; //다음 달 숫자
            Integer afterNextMon = Integer.parseInt(month) + 2; //다다음 달 숫자
            String nextMonth = 0 + String.valueOf(nextMon); //다음 달
            String afterNextMonth = 0 + String.valueOf(afterNextMon); //다다음 달

            String url = URL + year + "&solMonth=" + month + "&ServiceKey=" + SERVICE_KEY + "&_type=json";

            if(month.equals("12")) { //12월 일 경우 다음달은 다음년도, 1월, 2월으로 구한다
                nextMonth = "01";
                afterNextMonth = "02";
                year = String.valueOf(Integer.parseInt(year) + 1);
            }
            //해당 달부터 3달 간의 공휴일을 가져온다.
            String url2 = URL + year + "&solMonth=" + nextMonth + "&ServiceKey=" + SERVICE_KEY + "&_type=json";
            if(month.equals("11")) {
                afterNextMonth = "01";
                year = String.valueOf(Integer.parseInt(year) + 1);
            }
            String url3 = URL + year + "&solMonth=" + afterNextMonth + "&ServiceKey=" + SERVICE_KEY + "&_type=json";

                String result = restUtil.get(url, null);
                String result2 = restUtil.get(url2, null);
                String result3 = restUtil.get(url3, null);

                saveHDInfoToDB(result);
                saveHDInfoToDB(result2);
                saveHDInfoToDB(result3);


            Thread.sleep(1000);

            JLog.logi(formatDate);

            Code codeEntity = codeRepository.findCodeByCode(SERVICE_CD);
            codeEntity.setUseYn("N");
            codeRepository.save(codeEntity);
        } catch (Exception e) {
            String msg = e.getMessage();
            e.printStackTrace();
            JLog.loge(msg);
            if(msg!=null&& OpenApiUtil.isAcceptableError(msg)){ //재시도 가능한 에러인지 체크하여 다시 시도
                saveHD();
            }
            Code codeEntity = codeRepository.findCodeByCode(SERVICE_CD);
            // 스케줄 사용여부를 E (error) 로 변경
            codeEntity.setUseYn("E");
            codeEntity.setCodeDescription(msg);
            codeRepository.save(codeEntity);
            throw new RuntimeException("공휴일 정보를 가져오는데 실패하였습니다.");
        }
        JLog.logi("schedule tasks end"+ now +" : " + (System.currentTimeMillis() / 1000 - now));
    }

    private void saveHDInfoToDB(String result) {

        String str = result.toString();
        JsonElement element = JsonParser.parseString(str);
        JsonObject rootob = element.getAsJsonObject().get("response").getAsJsonObject();
        JsonObject body = rootob.getAsJsonObject().get("body").getAsJsonObject();   //body

        String itemsObj = body.get("items").toString();
        if(itemsObj.length()==2){ // 데이터가 없을때
            return;
        }

        Object itemObj = body.getAsJsonObject("items").get("item");

        List<Holiday> holidayList = new ArrayList<>();
        if(itemObj instanceof JsonArray){ // 데이터가 여러개일때
            JsonArray items = body.getAsJsonObject("items").getAsJsonArray("item");
            for (int i = 0; i < items.size(); i++) {
                JsonObject item = items.get(i).getAsJsonObject();
                Holiday holidayInfo = Holiday.builder()
                        .locDate(item.get("locdate").toString().replaceAll("\\\"",""))
                        .seq(Integer.parseInt(item.get("seq").toString().replaceAll("\\\"","")))
                        .dateKind(item.get("dateKind").toString().replaceAll("\\\"",""))
                        .isHoliday(item.get("isHoliday").toString().replaceAll("\\\"",""))
                        .dateName(item.get("dateName").toString().replaceAll("\\\"",""))
                        .build();
                holidayRepository.save(holidayInfo);
            }
        }else {
            JsonObject item = body.getAsJsonObject("items").getAsJsonObject("item");
            Holiday holidayInfo = Holiday.builder()
                    .locDate(item.get("locdate").toString().replaceAll("\\\"", ""))
                    .seq(Integer.parseInt(item.get("seq").toString().replaceAll("\\\"", "")))
                    .dateKind(item.get("dateKind").toString().replaceAll("\\\"", ""))
                    .isHoliday(item.get("isHoliday").toString().replaceAll("\\\"", ""))
                    .dateName(item.get("dateName").toString().replaceAll("\\\"", ""))
                    .build();
            holidayRepository.save(holidayInfo);
        }
        return;

    }

    public Code getCode(String code) throws InterruptedException {
        Thread.sleep(getRandomNumber()); //랜덤 초 대기
        Code statusCode = codeRepository.findCodeByCode(code);
        if(statusCode==null||statusCode.getUseYn().equals("Y")) {
            JLog.logd("---------------------------------> 스케줄 사용중(code : "+code+") <---------------------------------");
            return null;
        }
        // 스케줄 사용여부를 Y로 변경
        statusCode.setUseYn("Y");
        codeRepository.save(statusCode);
        return statusCode;
    }

    //1-9999까지 랜덤 수를 리턴한다.
    private int getRandomNumber(){
        Random random = new Random();
        return random.nextInt(9999) + 1;
    }
}
반응형