值班排班核心逻辑建模需绑定「人」「时间」「规则」,用Person和Shift结构体+time.Time字段定义,以map[ShiftKey][]*Person索引提升查询效率;通过ValidateAssignment预检冲突并贪心/迭代回溯分配;并发用chan串行化,数据库存date+shift_type双字段、UTC时区,导出用excelize防乱码。

值班排班的核心逻辑怎么建模?
值班排班不是简单轮换,关键在于把「人」「时间」「规则」三者绑定成可计算的结构。Go 里最实用的方式是定义两个核心结构体:Person(含姓名、技能标签、不可排班日期等)、Shift(含开始时间Start、结束时间End、所需角色Role、最大人数MaxCount)。不要用字符串存时间段——用 time.Time 才能做重叠检测、时区转换和跨日判断。
常见错误是把排班表做成二维切片 [][]*Person,结果无法快速查「某人在某天是否有冲突」或「某班次是否满员」。建议用 map 建索引:map[string][]*Person(key 为 "2024-06-15|backend-oncall")或更推荐的:用 map[ShiftKey][]*Person,其中 ShiftKey 是自定义 struct,字段包括日期、班次类型、优先级,便于排序和去重。
如何避免排班冲突和硬性规则失效?
冲突不只指时间重叠,还包括:同一人连续值班超过 2 天、单月超 120 小时、某角色无人可排。Go 没有内置约束求解器,得靠预检 + 回溯试探。实际做法是写一个 ValidateAssignment(person *Person, shift Shift) error 函数,里面检查:
-
person.UnavailableDays是否包含shift.Start.Date() - 查该人过去 7 天内已排班次数是否 ≥ 2(用
getRecentAssignments(person, 7)) - 查该班次当前已分配人数是否已达
shift.MaxCount - 若
shift.Role == "db-admin",确认person.Skills中有"mysql"或"postgres"
别在分配时才校验——先批量生成候选集(比如所有符合角色+可用性的 *Person),再按权重(如上月值班少者优先)排序,逐个尝试 ValidateAssignment,失败就跳过。这样比随机塞再回滚快得多。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
自动排班算法用贪心还是回溯?
小团队(≤15 人、≤3 班次/天)用贪心足够:按时间顺序遍历每个 Shift,对每个班次,从候选人中选「最近值班时间最久」的人,调用 ValidateAssignment,成功就提交,失败则跳过并记录 warning。大团队或规则复杂(如必须 A 和 B 不能同班、需预留替补)就得用回溯,但 Go 的 goroutine 不适合深度递归,建议改用迭代式回溯 + 状态快照:
- 用
type State struct { Assignments map[ShiftKey][]*Person; Pending []Shift } - 每次取一个
Pending[0],生成合法分配后 push 新State到队列 - 加限界:超时 3 秒或尝试 > 10000 次就放弃,退化为贪心补全
别碰 CSP(约束满足问题)库——Go 生态里没成熟方案,自己实现比集成更可控。
Web 接口和持久化要注意什么?
排班系统最常崩在并发写和时区。HTTP handler 里别直接操作全局 map,用 sync.RWMutex 包裹读写;更稳妥的是用 chan 把写请求串行化:type ScheduleCmd struct{ Op string; Shift Shift; Person *Person },所有修改走一个 cmdCh chan ScheduleCmd,后台 goroutine 消费执行。
数据库存班次别只存开始时间——必须存 date DATE 和 shift_type VARCHAR 两字段,否则跨年查询或按周统计极慢。PostgreSQL 示例:CREATE TABLE shifts (id SERIAL, date DATE NOT NULL, shift_type TEXT NOT NULL, person_id INT, created_at TIMESTAMPTZ DEFAULT NOW());。时区统一用 UTC 存,前端展示时再转本地时间,避免 time.Local 在服务器环境不一致引发错乱。
导出 Excel 功能容易被忽略:别用纯文本拼 CSV,中文会乱码;用 github.com/xuri/excelize/v2,写入前调 f.SetCellValue("Sheet1", "A1", "值班日期"),并显式设单元格格式为文本或日期类型。

















