美容行业作为典型的服务密集型产业,其运营效率直接取决于人力资源的合理调配。传统排班方式通常依赖纸质表格或基础电子表格,存在三大核心痛点:
我们团队为某连锁美容机构实施的SpringBoot排班系统,上线后实现了:
后端核心框架:
数据库方案对比:
| 选项 | QPS性能 | 事务支持 | 运维成本 | 最终选择 |
|---|---|---|---|---|
| MySQL 8.0 | 12万 | ACID | 低 | ✓ |
| PostgreSQL | 9万 | ACID | 中 | |
| MongoDB | 15万 | 最终一致 | 高 |
提示:美容院业务需要强事务保证(如排班冲突检测),故放弃NoSQL方案
java复制// 典型代码结构示例
src/main/java
├── com.beauty.schedule
│ ├── config # JWT安全配置
│ ├── controller # 暴露REST API
│ │ ├── ScheduleController.java
│ │ └── StaffController.java
│ ├── service # 业务逻辑
│ │ ├── impl # 实现类
│ │ └── ScheduleConflictDetector.java # 排班冲突检测
│ ├── repository # 数据访问
│ │ ├── ScheduleRepository.java
│ │ └── StaffRepository.java
│ └── entity # 数据库映射
│ ├── Schedule.java
│ └── StaffSkill.java
关键设计要点:
核心参数模型:
java复制public class ScheduleRule {
private Long staffId;
private LocalDate workDate;
private Integer maxAppointments; // 最大服务人数
private Set<Long> skillIds; // 技能标签
private Integer priorityLevel; // 排班优先级
}
冲突检测流程:
sql复制/* 使用CTE实现多条件查询 */
WITH available_staff AS (
SELECT s.* FROM staff s
JOIN staff_skill sk ON s.id = sk.staff_id
WHERE sk.skill_id IN (:requiredSkills)
AND NOT EXISTS (
SELECT 1 FROM schedule sc
WHERE sc.staff_id = s.id
AND sc.time_slot && :timeRange
)
)
SELECT * FROM available_staff
ORDER BY
current_workload ASC, -- 优先选择当前负荷低的
skill_match_count DESC -- 其次选技能匹配多的
LIMIT 1;
采用WebSocket+Redis发布订阅模式:
java复制// 事件发布示例
@Transactional
public void updateSchedule(ScheduleDTO dto) {
// 持久化操作...
redisTemplate.convertAndSend("schedule.update",
new ScheduleEvent(dto.getStaffId(), "UPDATE"));
}
JWT实现要点:
yaml复制# 安全配置示例
security:
jwt:
secret: ${JWT_SECRET:Beauty@2024!}
expiration: 1800
header: Authorization
缓存设计:
数据库优化:
sql复制CREATE INDEX idx_staff_time ON schedule(staff_id, time_slot);
properties复制spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.idle-timeout=30000
现象:系统提示"时段冲突"但实际无冲突
排查过程:
java复制@Entity
public class Schedule {
@Column(columnDefinition="TIMESTAMP WITH TIME ZONE")
private OffsetDateTime timeSlot;
}
优化步骤:
java复制@Bean
public FilterRegistrationBean<GzipFilter> gzipFilter() {
FilterRegistrationBean<GzipFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new GzipFilter());
registration.addUrlPatterns("/*");
return registration;
}
实际部署时发现,美容院网络环境普遍较差,我们最终采用:
重要经验:一定要在需求阶段明确门店的硬件条件,我们曾遇到某分店仍在使用Windows XP系统,不得不额外开发兼容IE11的降级方案