沉默是金,总会发光
大家好,我是沉默
逢年过节,程序员都得面临一个灵魂拷问:「今年到底休几天?」
有的同事用 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)country,bizId可选
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 d = LocalDate.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掉。