一、主要功能總結
- 自動獲取節假日:從 GitHub 開源項目
NateScarlet/holiday-cn獲取權威節假日數據。 - 智能合併:將法定節假日與週末合併,同時剔除調休補班日,生成準確的“放假日曆”。
- 高可用設計:支持多數據源,失敗自動重試。
- 定時執行:通過
@Scheduled實現自動化任務。 - 日誌記錄:使用
slf4j記錄關鍵步驟和異常,便於排查問題。
二、代碼實現
package com.xfcy.service;
import cn.hutool.core.date.DateUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 節假日信息獲取與處理服務
* 該類負責定時從外部數據源獲取指定年份的節假日安排(包括法定節假日、調休補班等),
* 並結合週末信息,生成全年完整的“非工作日”列表(即放假日期)。
*
* 使用了 Spring 的定時任務功能,通過 HTTP 請求獲取 JSON 格式的節假日數據。
*/
@Configuration
@EnableScheduling // 啓用 Spring 的定時任務支持
@Slf4j // Lombok 註解,自動生成日誌記錄器(logger)
public class TaskPublicHoliday {
/**
* 獲取指定年份中所有的週六和週日日期列表
*
* @param year 目標年份
* @return 包含所有周末日期的字符串列表,格式為 "yyyy-MM-dd"
*/
public List<String> getYearAllWeekends(int year) {
List<String> resultList = new ArrayList<>();
SimpleDateFormat simdf = new SimpleDateFormat("yyyy-MM-dd");
// 創建日曆實例,起始為指定年份的1月1日
Calendar calendar = new GregorianCalendar(year, 1, 1); // 注意:月份從0開始,這裏1表示2月,但後續通過WEEK_OF_YEAR調整
int i = 1;
// 循環遍歷每年的每一週
while (calendar.get(Calendar.YEAR) < year + 1) {
calendar.set(Calendar.WEEK_OF_YEAR, i++); // 設置為第 i 周
// 先獲取本週的星期日
calendar.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
if (calendar.get(Calendar.YEAR) == year) {
resultList.add(simdf.format(calendar.getTime()));
}
// 再獲取本週的星期六
calendar.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);
if (calendar.get(Calendar.YEAR) == year) {
resultList.add(simdf.format(calendar.getTime()));
}
}
return resultList;
}
/**
* 將指定日期轉換為中文星期幾的文本表示
*
* @param date 日期字符串,格式應為 "yyyy-MM-dd"
* @return 對應的中文星期幾,如 "星期一"、"星期二" 等
*/
public String getWeekNoText(String date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(DateUtil.parseDate(date)); // 使用 Hutool 工具類解析日期
int wkDay = calendar.get(Calendar.DAY_OF_WEEK); // 獲取星期幾(1-7)
switch (wkDay) {
case Calendar.SUNDAY:
return "星期日";
case Calendar.MONDAY:
return "星期一";
case Calendar.TUESDAY:
return "星期二";
case Calendar.WEDNESDAY:
return "星期三";
case Calendar.THURSDAY:
return "星期四";
case Calendar.FRIDAY:
return "星期五";
case Calendar.SATURDAY:
return "星期六";
default:
return "";
}
}
/**
* 定時任務:獲取下一年度的節假日安排並生成非工作日列表
*
* 原計劃每年12月30日凌晨2點執行,以便獲取次年的放假安排。
* 當前為測試方便,設置為每1秒執行一次(fixedDelay = 1000 * 1)。
*
* 數據源來自 GitHub 上的開源項目,提供中國節假日 JSON 數據。
*/
@Scheduled(cron = "0 0 2 30 12 *") // 原定計劃:每年12月30日 02:00:00 執行
// @Scheduled(fixedDelay = 1000 * 1) // 測試用:每1秒執行一次
public void getNextYearHolidays() {
// 定義節假日數據源地址數組(支持多源,提高容錯性)
String[] holidayInfoUrl = {
"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/"
};
// 獲取當前年份
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
int year = calendar.get(Calendar.YEAR); // 當前年份(實際應為下一年,但此處為當前年用於測試)
JSONArray holidayObjArry = null; // 存儲從接口獲取的節假日數據
// 遍歷所有數據源,嘗試獲取節假日信息,直到成功
for (int i = 0; i < holidayInfoUrl.length; i++) {
String reqUrl = holidayInfoUrl[i] + year + ".json";
try {
log.info("開始獲取節日信息-" + i + ": url=" + reqUrl);
// 發送 HTTP GET 請求獲取節假日 JSON 數據
HttpResponse response = HttpRequest.get(reqUrl).execute();
log.info("獲取節日信息返回" + i + ":" + response.body());
// 解析返回的 JSON 響應
JSONObject rtnObj = JSONUtil.parseObj(response.body());
holidayObjArry = rtnObj.getJSONArray("days"); // 提取 "days" 數組
// 成功獲取數據,跳出循環
break;
} catch (Exception e) {
log.error("獲取節假日數據異常:", e);
// 繼續嘗試下一個數據源
}
}
// 解析獲取到的節假日數據
List<String> notWorkDays = new ArrayList<>(); // 存儲法定節假日(放假日)
List<String> workDays = new ArrayList<>(); // 存儲調休補班日(本應休息但需上班)
if (holidayObjArry != null) {
for (int i = 0; i < holidayObjArry.size(); i++) {
JSONObject dayObj = holidayObjArry.getJSONObject(i);
String date = dayObj.getStr("date"); // 日期,格式:yyyy-MM-dd
// 只處理目標年份的數據,避免包含跨年數據
if (date.startsWith(String.valueOf(year) + '-')) {
if (dayObj.getBool("isOffDay")) {
// 是休息日(放假)
notWorkDays.add(date);
} else {
// 不是休息日(即補班日)
workDays.add(date);
}
}
}
}
// 獲取該年份所有周末(週六、週日)的日期列表
List<String> weekEndDays = getYearAllWeekends(year);
// 從週末列表中剔除需要補班的日期(這些天實際要上班)
Collection<String> realWeekEndDays = org.apache.commons.collections4.CollectionUtils.subtract(weekEndDays, workDays);
List<String> realWeekEndDaysList = new ArrayList<>(realWeekEndDays);
// 合併:法定節假日 + 實際週末 = 全年非工作日
notWorkDays.addAll(realWeekEndDaysList);
// 去重(可能存在節假日與週末重合的情況)
Set<String> set = new LinkedHashSet<>(notWorkDays);
List<String> notWorkDayList = new ArrayList<>(set);
// 按日期排序(升序)
Collections.sort(notWorkDayList);
// 輸出最終的非工作日列表
for (String oneDay : notWorkDayList) {
try {
Date date = DateUtils.parseDate(oneDay, "yyyy-MM-dd");
log.info("今天不用上班: {}, 今天是{}", DateUtil.format(date, "yyyy-MM-dd"), getWeekNoText(oneDay));
} catch (ParseException e) {
log.error("日期解析失敗: " + oneDay, e);
throw new RuntimeException(e);
}
}
}
}
本文章為轉載內容,我們尊重原作者對文章享有的著作權。如有內容錯誤或侵權問題,歡迎原作者聯繫我們進行內容更正或刪除文章。