开发必备:一套可复用的节假日接口!

175 阅读6分钟

沉默是金,总会发光

大家好,我是沉默

逢年过节,程序员都得面临一个灵魂拷问:「今年到底休几天?」
有的同事用 Excel 做表,有的依赖钉钉/飞书,有的干脆去百度“法定节假日”。

结果常常遇到 —— 调休规则变了没人更新、公司自定义假期和国家政策打架、不同国家的团队算工时完全乱套

更要命的是:

  • 排班系统算错工作日,工厂少招了人,生产线直接“停摆”;

  • 财务工资结算不准,多发/少发奖金,员工怨声载道;

  • 海外团队会议排到法定长假,跨国项目节奏彻底乱了。

所以,一个可扩展、可追溯、带缓存的“节假日服务” ,就成了现代企业 IT 架构里不可缺的基础能力。

今天这篇文章,就是教你如何设计一套企业级 节假日 API 服务:既能秒查“今天是否上班”,又能兼容历史规则、自定义假期、全球扩展

**-**01-

到底要解决哪些痛点?

需求漏斗图:图片

可以看到:需求像漏斗一样逐层收紧,最终沉淀为核心能力。

- 02-

核心能力清单

能力项说明难点
✅ 判断是否工作日单日查询需考虑周末、调休
✅ 列出节假日年/月级查询自定义覆盖国家规则
✅ 计算工作日天数起止区间边界含/不含日期
✅ 多国家/地区支持ISO 国家码规则差异大
✅ 自定义假期企业专属覆盖优先级
✅ 历史回溯有效期 & 版本政策调整频繁
✅ 高可用 & 缓存QPS 级接口双层缓存/灰度发布
✅ 管理端能力新增/删除/回滚审计 & 版本管理

- 03-

REST API 设计(实战接口)

1. 查询某天是否为工作日

GET /api/v1/holiday/isWorkday
Query

  • date (YYYY-MM-DD) 必填
  • country (ISO 2/3, default: CN) 可选
  • bizId (companyId / tenant) 可选 —— 用于公司自定义日历覆盖

Response

{  "date""2025-10-01",  "isWorkday"false,  "type""HOLIDAY"// HOLIDAY | WORKDAY | ADJUSTED_WORKDAY  "name""国庆节",  "source""national|custom"}

2. 列出某年/某月的节假日

GET /api/v1/holiday/list
Query

  • year 或 month (YYYY-MM 或 year)
  • countrybizId 可选

Response

{  "year": 2025,  "items": [    {"date":"2025-01-01","type":"HOLIDAY","name":"元旦","source":"national"},    {"date":"2025-01-02","type":"HOLIDAY","name":"元旦补休","source":"national"},    {"date":"2025-04-05","type":"WORKDAY","name":"清明节调休","source":"national"}  ]}

3. 计算工作日天数

POST /api/v1/holiday/countWorkdays
Body

{  "startDate":"2025-09-01",  "endDate":"2025-09-30",  "includeStart": true,  "includeEnd": false,  "country":"CN",  "bizId":"tenant_123"}

Response

{  "workdays": 21,  "totalDays": 29,  "details": [    {"date":"2025-09-01","isWorkday":true},    ...  ]}

**-****04-**核心数据模型(SQL 建表)

CREATE TABLE holiday_rules (  id BIGINT PRIMARY KEY AUTO_INCREMENT,  country VARCHAR(8) NOT NULL,  biz_id VARCHAR(64) NULL,  date DATE NOT NULL,  type VARCHAR(32) NOT NULL,   -- HOLIDAY | WORKDAY | ADJUSTED_WORKDAY  name VARCHAR(128),  source VARCHAR(32) NOT NULL, -- national | custom  effective_from DATE NULL,  effective_to DATE NULL,  created_by VARCHAR(64),  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP);CREATE UNIQUE INDEX idx_rule_unique ON holiday_rules(country, biz_id, date);

**-****05-**规则冲突时怎么办? 图片




-06-实现要点(Spring Boot 实战) 
设计原则

国家/公共假期数据:定期批量更新(每年一次或每次政策发布时)

公司自定义假期:可随时 CRUD,覆盖国家层级

缓存策略:本地 Caffeine 缓存 + Redis 共享缓存;更新时清理/刷新(pub/sub 通知)

提供只读高 QPS 接口:使用缓存优先、DB 回退

时间处理:统一使用 UTC 存储/或明确记录时区(建议日期类用 LocalDate)



Service 代码
@RestController@RequestMapping("/api/v1/holiday")public class HolidayController {    @Autowired HolidayService holidayService;    @GetMapping("/isWorkday")    public ResponseEntity<IsWorkdayResp> isWorkday(       @RequestParam String date,       @RequestParam(required=false, defaultValue="CN") String country,       @RequestParam(required=false) String bizId) {       LocalDate dLocalDate.parse(date);       return ResponseEntity.ok(holidayService.isWorkday(d, country, bizId));    }}@Servicepublic class HolidayService {    @Autowired HolidayRepository repo;    @Cacheable(value="isWorkday", key="#country + ':' + (#bizId?:'global') + ':' + #date.toString()")    public IsWorkdayResp isWorkday(LocalDate date, String country, String bizId) {       // 1. 检查自定义bizId规则       Optional<Rule> r = repo.findByDate(country, bizId, date);       if (r.isPresent()) return map(r.get());       // 2. 回退到国家       r = repo.findByDate(country, null, date);       if (r.isPresent()) return map(r.get());       // 3. 默认设置:工作日(如果为工作日),否则为周六/周日节假日       boolean isWeekend = date.getDayOfWeek()==DayOfWeek.SATURDAY || date.getDayOfWeek()==DayOfWeek.SUNDAY;       return new IsWorkdayResp(date, !isWeekend, isWeekend? "WEEKEND" : "WORKDAY", "default");    }}
注:@Cacheable 可以配合 Caffeine 本地(低延迟)以及 Redis(分布式)做二级缓存。更新时要用 @CacheEvict 或通过 Redis pub/sub 清本地缓存。




运维、部署与更新策略

年度更新:国家法定节假日通常按年发布 → 提供运维脚本批量导入(Excel/CSV),并做 dry-run 校验。

灰度发布:公司大改规则时,先灰度发布 1~2 个部门,再全面生效。

回滚能力:任何 admin 修改都应该有版本ID与撤销 API。

审计日志:记录操作者、时间、变更前后内容、审批流程(若公司需要)。

高可用:读流量走缓存,写操作(admin)走 DB+消息通知刷缓存。

监控:统计缓存命中率、慢查询、导入失败率、API 误报率。



典型边界场景与注意点
场景	说明	建议/注意事项时区	仅处理日期(LocalDate)时,不包含具体时区信息	接口层明确客户端时区;存储使用 date 类型,不带时区
历史规则	节假日政策可能调整(回溯/修订)	支持 effective_from / effective_to,查询历史日期时返回当时规则
替代工作日(调休) 	周末可能被调为工作日	接口支持 ADJUSTED_WORKDAY 类型,并提供原因(如“春节调休”)
重复/冲突规则	国家规则与公司自定义规则可能冲突	定义覆盖优先级:一般公司规则 > 国家规则
性能	大量细粒度查询可能导致 DB 压力	使用缓存(Caffeine/Redis),避免逐条 DB 查询;支持批量预热
框架兼容	ORM 框架可能出现 Optional/NULL 问题	注意 JPA/ORM 字段映射;导入工具需避免 SQL 注入
并发修改	管理端修改节假日规则时可能存在竞态	使用悲观/乐观锁或审批流程,保证一致性与安全性




总结

看似小小的“节假日服务”,其实牵一发动全身 —— 从排班、财务、考勤到国际项目协同,全都要用到。



如果没有统一的服务,企业内每个系统都要自己算节假日,结果就是 Bug 和混乱无限放大。 




所以,与其让开发团队年年“手写补丁”,不如一劳永逸,打造一套 高可用、可审计、可扩展的节假日 API 服务。




当别人还在百度“明天放假吗”,你的系统已经精准返回 + 缓存毫秒级响应。




这,才是程序员真正的快乐!

**-****07-**粉丝福利

我这里创建一个程序员成长&副业交流群, 


 和一群志同道合的小伙伴,一起聚焦自身发展, 

可以聊:


技术成长与职业规划,分享路线图、面试经验和效率工具, 




探讨多种副业变现路径,从写作课程到私活接单, 




主题活动、打卡挑战和项目组队,让志同道合的伙伴互帮互助、共同进步。 




如果你对这个特别的群,感兴趣的, 
可以加一下, 微信通过后会拉你入群, 
 但是任何人在群里打任何广告,都会被我T掉。