1. 项目背景与核心价值
银行网点、医院门诊、政务大厅这类场所的排队管理一直是老大难问题。去年我在某三甲医院陪家人看病时,亲眼目睹了传统纸质号票的混乱场景——有人插队引发争吵,过号患者堵在诊室门口,护士不得不扯着嗓子反复喊号。这种低效的管理方式不仅浪费人力,更直接影响服务体验。
基于SpringBoot的智能叫号系统正是为了解决这类痛点而生。它用技术手段重构了排队流程,实现了三大核心价值:
- 无接触取号:用户通过微信小程序/现场终端自助取号,减少交叉感染风险
- 智能调度:系统根据业务类型、窗口负载自动分配最优队列
- 实时状态可视化:大屏动态显示叫号进度,短信提醒防止过号
这个毕设选题的巧妙之处在于:既包含了SpringBoot+Vue的主流技术栈实践,又解决了真实场景中的管理痛点。下面我将拆解实现过程中的关键技术方案。
2. 系统架构设计
2.1 技术选型决策
在技术栈选择上,我们采用经典的前后端分离架构:
后端核心组件:
- SpringBoot 2.7.x(平衡稳定性和新特性)
- MyBatis-Plus 3.5.x(避免重复CRUD代码)
- Redis 6.x(高并发号池管理)
- WebSocket(实时推送叫号信息)
前端方案:
- Vue 3 + Element Plus(管理后台)
- Uni-app(跨平台小程序)
- LED控制协议(对接物理叫号屏)
技术选型背后的思考:没有选用SpringCloud是考虑到单体架构足以支撑200+并发的门诊场景,而微服务带来的运维复杂度反而会成为毕设演示的负担。
2.2 数据模型设计
核心表结构设计体现了业务规则:
sql复制CREATE TABLE `ticket` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`ticket_no` VARCHAR(20) NOT NULL COMMENT 'A001格式的号码',
`business_type` ENUM('开户','存取款','理财') NOT NULL,
`status` ENUM('等待','办理中','已完成','过号') DEFAULT '等待',
`window_id` INT COMMENT '分配到的窗口',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`call_count` INT DEFAULT 0 COMMENT '被叫次数',
KEY `idx_status_type` (`status`, `business_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
设计要点:
- 采用枚举类型约束业务分类,避免脏数据
- 组合索引加速高频查询:"查询某类业务的等待队列"
- 叫号次数统计用于分析窗口服务效率
3. 核心业务逻辑实现
3.1 智能分号算法
在银行场景中,VIP客户需要优先服务,但完全插队又会影响普通客户体验。我们采用动态权重算法:
java复制public class TicketDispatcher {
private final RedisTemplate<String, String> redisTemplate;
// 业务类型权重配置
private static final Map<String, Integer> WEIGHT_MAP = Map.of(
"VIP", 3,
"对公业务", 2,
"个人业务", 1
);
public String dispatchTicket(Ticket ticket) {
String queueKey = "queue:" + ticket.getBranchId();
long score = System.currentTimeMillis() / WEIGHT_MAP.get(ticket.getBusinessType());
redisTemplate.opsForZSet().add(queueKey, ticket.getTicketNo(), score);
return generateTicketNo();
}
}
这个方案实现了:
- VIP客户等待时间约为普通客户的1/3
- 相同权重客户严格遵循FIFO原则
- 通过Redis的ZSET实现高性能排序
3.2 过号重排机制
实际运营中发现约15%的客户会错过叫号。我们设计了阶梯式重排策略:
- 首次呼叫:等待2分钟未到场则标记"过号"
- 二次呼叫:将该号码插入当前队列的第3位
- 三次呼叫:降级到队列末尾
对应的状态机实现:
java复制public enum TicketState {
WAITING {
public TicketState nextState() { return CALLING; }
},
CALLING {
public TicketState nextState() {
return (callCount >= 2) ? COMPLETED : MISSED;
}
},
MISSED {
public TicketState nextState() { return WAITING; }
},
COMPLETED {
public TicketState nextState() { return this; }
};
}
4. 关键问题解决方案
4.1 高并发取号控制
在早高峰时段,瞬时并发取号请求可能超过500QPS。我们采用三级缓冲策略:
- 本地缓存:每个服务实例预生成100个号码(避免频繁访问Redis)
- Redis原子操作:使用INCR命令生成唯一序号
- 数据库异步落盘:通过Spring EventListener实现最终一致性
java复制@Transactional
public String generateTicketNo() {
// 1. 从本地缓存获取
String no = localCache.poll();
if (no != null) return no;
// 2. Redis原子递增
Long seq = redisTemplate.opsForValue()
.increment("ticket:seq:" + LocalDate.now());
// 3. 异步保存数据库
eventPublisher.publishEvent(new TicketCreateEvent(seq));
return String.format("A%03d", seq % 1000);
}
4.2 多终端状态同步
叫号状态需要在多个屏幕实时更新,我们采用混合推送方案:
| 终端类型 | 同步方案 | 延迟控制 |
|---|---|---|
| 柜员操作屏 | WebSocket全双工连接 | <100ms |
| 大厅等候屏 | Server-Sent Events | <1s |
| 微信小程序 | 长轮询+本地存储 | <3s |
特别要注意WebSocket的断线重连处理:
javascript复制const socket = new WebSocket('wss://your-domain.com/call');
socket.onclose = function() {
setTimeout(() => {
// 指数退避重连
this.reconnectDelay = Math.min(5000, this.reconnectDelay * 2);
connectWebSocket();
}, this.reconnectDelay || 1000);
};
5. 部署与性能优化
5.1 生产环境配置建议
在阿里云2C4G实例上的实测数据:
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| Tomcat线程池 | maxThreads=200 | 根据压测结果调整 |
| Redis连接池 | lettuce.pool.max-active=50 | 避免连接泄露 |
| MySQL连接池 | hikari.maximum-pool-size=20 | 通常不是瓶颈 |
| JVM参数 | -Xmx2g -XX:+UseG1GC | 关注GC日志中的Full GC |
5.2 常见性能瓶颈排查
问题现象:高峰期叫号延迟超过5秒
排查步骤:
- 使用Arthas查看方法调用耗时:
trace com.example.service.TicketService callNext - 检查Redis监控:
redis-cli --latency -h 127.0.0.1 - 分析MySQL慢查询:
SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 10
典型解决方案:
- Redis大key问题:将ZSET分片存储,按日期+业务类型拆分
- MySQL热点更新:对ticket表采用行级锁优化
- 线程阻塞:避免在叫号逻辑中同步调用第三方接口
6. 毕设答辩加分项
根据指导教授反馈,这些设计细节容易获得高分:
- 容灾方案:演示拔掉网线后,本地终端仍能继续叫号(基于SQLite缓存)
- 数据分析:对历史叫号数据做可视化,比如各时段等待时长热力图
- 无障碍设计:增加语音播报功能,考虑视障人士需求
- 安全防护:在取号接口实现IP限流(使用Guava RateLimiter)
一个让评委眼前一亮的答辩技巧:准备两个版本的演示数据——正常流程用的标准数据集,以及专门展示异常处理的"问题数据集"(包含重复取号、过号重排等边界情况)。
我在实现过程中最大的收获是:业务规则远比技术实现复杂。建议学弟妹们在编码前,一定要去银行实地观察半天排队流程,记录下这些细节:
- 客户取号后的典型行为路径
- 窗口柜员的常见操作习惯
- 突发状况的现场处理方式
这些观察会让你的系统设计更加接地气。
