
简介这是一套面向计算机专业本科生的毕业设计级银行排号系统实战资源完整覆盖Java桌面应用开发全流程解决银行、政务大厅等场景下的客户分流与业务协同管理问题。资源包为69.98MB的RAR压缩文件包含可运行Java源码、配套数据库脚本、系统操作演示视频及结构完整的毕业论文涵盖服务器端取号、统计、删除、查询、通知与客户端登录、叫号、统计、删除、查询双模块功能实现代码分层清晰含DAO数据访问层与界面交互逻辑。已有468人学习下载读者可直接导入IDE调试运行复现多工作台并发叫号流程掌握Socket通信、Swing界面开发、MySQL数据持久化及前后端状态同步等核心技能同时获得论文撰写范式与答辩支撑材料。1. 为什么一个银行柜台前的“叫号声”值得用 Java 重写一遍你见过那种老式银行网点客户一进门先在机器上按个键吐出一张带数字的小票然后盯着墙上一块泛黄的LED屏等“请037号到2号窗口”的机械女声响起——声音还没落037号已经冲到窗口前而036号还在低头刷手机038号刚从ATM区慢悠悠踱过来。这不是效率问题是排队逻辑失控没有优先级老人、残障、紧急业务、没有超时重排客户等了25分钟没被叫系统却仍把它当“活跃队列”、没有状态回溯窗口断电重启后谁该下一个没人知道。这个“基于Java的银行排号系统”不是教科书里的Hello World它是把物理世界的排队熵用Java语言建模成可调度、可审计、可扩展的软件实体。它解决的不是“怎么显示号码”而是如何让银行服务流在并发、断电、窗口增减、业务插队等真实扰动下依然保持确定性响应。适合两类人一是刚学完JDBCSwing/Spring Boot想落地练手的Java新手二是需要快速交付轻量级网点级系统的中小银行IT岗——它不对接核心账务但能立刻替代贴在墙上的纸质号单它不用微服务拆分但已预留窗口状态上报、微信取号、叫号语音合成等接口桩。源码里藏着的是Java工程师对“状态一致性”最朴素的执念。2. 从零搭起排号骨架用 Spring Boot MyBatis-Plus 快速建模核心实体银行排号系统表面是“叫号”底层是三类状态的强一致性维护客户号的生成与分配、窗口的实时占用与释放、业务类型的权重调度。选 Spring Boot 不是因为它时髦而是它能把嵌入式H2数据库、REST API、定时任务、事务管理全打包进一个jar包——网点IT人员双击就能运行无需部署Tomcat或配置数据源。MyBatis-Plus 则省去90%的CRUD样板代码尤其适合“号单状态变更”这种高频但结构固定的SQL操作。下面直接给出最简可行路径跳过所有“创建Maven工程”的冗余步骤聚焦真正影响后续扩展的建模决策。2.1 客户号实体设计不只是自增ID而是带业务语义的编码客户号不能简单用数据库自增ID必须承载业务信息前缀标识业务类型如“CASH”表示现金“LOAN”表示贷款、中间位表示当日序号、末尾校验码防手输错误。MyBatis-Plus 的TableId(type IdType.NONE)配合自定义生成器比UUID更可控// CustomerNumber.java Data TableName(t_customer_number) public class CustomerNumber { TableId(type IdType.NONE) private String number; // 格式CASH-20240520-00123-7 private String businessType; // CASH, LOAN, TRANSFER private LocalDateTime createTime; private Integer status; // 0:已取号, 1:已叫号, 2:已过号, 3:已办结 private String windowNo; // 当前分配窗口号为空表示未分配 }关键参数说明status字段用整型而非枚举因网点可能临时增加“暂停服务”4或“VIP插队”5状态避免后期改enum重编译number字段不设主键约束靠应用层生成逻辑保证唯一性——这是为未来支持多网点号池同步留的活口单库时用数据库唯一索引多库时用雪花ID业务前缀。2.2 窗口实体与状态机用枚举定时任务实现“窗口健康度”自检窗口不是静态资源它有生命周期空闲→叫号中→正在办理→完成→异常中断。硬编码if-else易出错改用状态机模式// WindowStatus.java public enum WindowStatus { IDLE(0, 空闲), CALLED(1, 已叫号), PROCESSING(2, 办理中), COMPLETED(3, 已完成), OFFLINE(9, 离线); private final int code; private final String desc; // 构造、getter省略 } // Window.java Data TableName(t_window) public class Window { TableId private Long id; private String windowNo; // A01, B02 private WindowStatus status; private String currentNumber; // 当前正在办理的客户号 private LocalDateTime lastActiveTime; // 最后一次状态变更时间 }配套一个每30秒扫描的定时任务自动将lastActiveTime超5分钟的窗口置为OFFLINE// WindowHealthCheck.java Scheduled(fixedDelay 30000) public void checkWindowHealth() { LocalDateTime timeout LocalDateTime.now().minusMinutes(5); QueryWrapperWindow wrapper new QueryWrapper(); wrapper.eq(status, WindowStatus.PROCESSING.getCode()) .lt(last_active_time, timeout); ListWindow timeoutWindows windowMapper.selectList(wrapper); timeoutWindows.forEach(w - { w.setStatus(WindowStatus.OFFLINE); w.setCurrentNumber(null); windowMapper.updateById(w); log.warn(窗口 {} 超时未更新状态强制置为离线, w.getWindowNo()); }); }为什么用定时任务而非WebSocket心跳网点网络环境差WebSocket连接频繁断开而定时任务依赖本地时间只要Java进程活着就可靠。血泪经验某农商行试点时用WebSocket心跳导致窗口状态“假死”客户排长队却无人叫号最后靠重启服务才恢复——从此所有状态变更都走DB事务定时兜底。3. 叫号逻辑的核心用Redis分布式锁数据库乐观锁双保险防并发冲突叫号动作看似简单“点击‘下一个’→屏幕显示新号→语音播报”但在高并发下极易出现重复叫号、跳号、漏号。比如两个柜员同时点“下一个”系统若只查statusIDLE的窗口再分配可能都选中A01窗口导致同一客户号被叫两次。必须用两层锁机制Redis锁控制“叫号操作原子性”数据库乐观锁保障“窗口状态变更一致性”。3.1 Redis锁封装用SETNXLua脚本杜绝锁失效Spring Boot整合Redis后不直接用RedisTemplate.opsForValue().setIfAbsent()因其无法原子性设置过期时间存在锁永远不释放风险。改用Lua脚本// RedisLockUtil.java public boolean tryLock(String lockKey, String requestId, long expireSeconds) { String script if redis.call(setnx, KEYS[1], ARGV[1]) 1 then redis.call(expire, KEYS[1], ARGV[2]) return 1 else return 0 end; Object result redisTemplate.execute( new DefaultRedisScript(script, Long.class), Collections.singletonList(lockKey), requestId, String.valueOf(expireSeconds) ); return (Long) result 1; } public void unlock(String lockKey, String requestId) { String script if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end; redisTemplate.execute( new DefaultRedisScript(script, Long.class), Collections.singletonList(lockKey), requestId ); }参数说明lockKey固定为call_next_lock所有叫号请求竞争同一把锁requestId用UUID保证锁归属可追溯expireSeconds10是经验值——叫号全流程查号、更新窗口、发消息在正常网络下绝不会超10秒超时即认为操作失败避免死锁。3.2 数据库乐观锁在UPDATE语句中校验版本号窗口表加version字段每次更新都校验-- t_window 表结构片段 ALTER TABLE t_window ADD COLUMN version INT DEFAULT 0; -- 叫号时的UPDATE语句MyBatis-Plus XML update idcallNextForWindow UPDATE t_window SET current_number #{customerNumber}, status #{processingStatus}, last_active_time NOW(), version version 1 WHERE window_no #{windowNo} AND status #{idleStatus} AND version #{oldVersion} /updateJava层调用时先查当前version再传入UPDATE// CallService.java public Result callNext(String windowNo) { Window window windowMapper.selectOne(new QueryWrapperWindow().eq(window_no, windowNo)); if (!window.getStatus().equals(WindowStatus.IDLE)) { return Result.fail(窗口 windowNo 非空闲状态); } // 尝试获取Redis锁 if (!redisLockUtil.tryLock(call_next_lock, UUID.randomUUID().toString(), 10)) { return Result.fail(叫号繁忙请稍候); } try { // 乐观锁更新只更新version匹配的记录 int updated windowMapper.callNextForWindow(windowNo, nextNumber, WindowStatus.PROCESSING.getCode(), WindowStatus.IDLE.getCode(), window.getVersion()); if (updated 0) { return Result.fail(叫号失败窗口状态已被其他操作修改); } // 发送消息、语音播报... return Result.success(nextNumber); } finally { redisLockUtil.unlock(call_next_lock, requestId); } }为什么不用MySQL行锁行锁在高并发下易引发锁等待甚至死锁且无法跨JVM实例Redis锁乐观锁组合既保证单JVM内操作原子性又兼容未来集群部署——这是中小银行系统演进的真实路径。4. 避坑指南这5个坑让80%的初学者在部署当天就翻车做银行系统稳定压倒一切。以下全是我在三个地市农商行现场实施时客户指着屏幕骂“你们Java写的玩意儿还不如我手写Excel表格”后连夜补上的血泪补丁。每个坑都对应真实日志和复现步骤。4.1 现象客户取号后LED屏显示“请001号”但语音播报却是“请003号”原因LED屏用HTTP轮询API获取最新号语音模块用MQTT订阅号变更事件两者未共享同一号源。当网络抖动时LED屏拿到旧号MQTT收到新号造成视听不同步。解决强制统一号源——所有前端LED屏、微信小程序、柜台PC只读/api/next-number接口该接口返回JSON含currentNumber和nextNumber语音模块不再订阅MQTT改为定时调用此接口对比currentNumber变化再触发播报。用Cacheable(key#root.methodName)缓存接口结果降低DB压力。4.2 现象重启服务后新取号从001开始历史号全部丢失原因开发时用H2内存数据库spring.h2.console.enabledtrue但生产环境未切换为MySQL且未配置spring.datasource.urljdbc:h2:file:~/bank-queue;DB_CLOSE_ON_EXITFALSE导致H2文件未持久化。解决部署前必查application-prod.yml确认数据库URL指向MySQL并添加初始化脚本-- init.sql CREATE TABLE IF NOT EXISTS t_customer_number ( number VARCHAR(32) PRIMARY KEY, business_type VARCHAR(10), create_time DATETIME, status TINYINT, window_no VARCHAR(10) ); INSERT INTO t_customer_number SELECT * FROM t_customer_number_backup WHERE create_time DATE_SUB(NOW(), INTERVAL 7 DAY); -- 恢复近7天号单4.3 现象VIP客户插队后普通客户等待时间暴涨投诉激增原因插队逻辑简单粗暴——把VIP号插入队列头部但未重新计算后续所有客户的预计等待时间LED屏仍显示“前方还有5人”实际变成“前方还有12人”。解决引入动态权重队列。普通号权重1VIP号权重5窗口处理速率按权重累加// QueueCalculator.java public int estimateWaitCount(String windowNo) { // 查询当前窗口分配的所有待办号单 ListCustomerNumber queue customerNumberMapper.selectList( new QueryWrapperCustomerNumber() .eq(window_no, windowNo) .in(status, Arrays.asList(0, 1)) .orderByAsc(create_time) ); return queue.stream() .mapToInt(c - VIP.equals(c.getBusinessType()) ? 5 : 1) .sum(); }LED屏显示“前方约12人含VIP”客户心理预期更准。4.4 现象微信小程序取号成功但柜台PC端查不到该号原因小程序和PC端连接不同数据库实例开发环境用H2生产环境PC连MySQL小程序连另一台MySQL未做主从同步或双写。解决强制所有客户端连同一MySQL主库。若需读写分离用ShardingSphere代理配置readwrite_splitting规则确保取号写和查询读路由到同一节点。禁用任何“小程序直连MySQL”的野路子——安全审计通不过。4.5 现象连续叫号10次后系统CPU飙升至95%响应超时原因语音播报用Runtime.getRuntime().exec(say number)调用系统TTS但未限制并发数10个柜员同时点“下一个”瞬间fork出10个say进程耗尽系统资源。解决改用线程池队列控制语音播报// VoiceService.java private final ThreadPoolExecutor voiceExecutor new ThreadPoolExecutor( 1, 1, 30L, TimeUnit.SECONDS, new LinkedBlockingQueue(10), // 最多排队10条播报 new ThreadFactoryBuilder().setNameFormat(voice-pool-%d).build() ); public void speak(String number) { voiceExecutor.submit(() - { try { // 调用本地TTS或阿里云语音合成API ttsClient.synthesize(number, /tmp/ number .mp3); playAudio(/tmp/ number .mp3); } catch (Exception e) { log.error(语音播报失败, e); } }); }5. 让系统真正“活”起来用数据库触发器日志分析反推业务瓶颈排号系统上线后不能只满足于“能叫号”。真正的价值在于从每天产生的数千条号单日志里挖出网点运营的隐性成本。比如某窗口平均处理时长12分钟但同类型业务在其他网点只要6分钟——是柜员技能问题还是该窗口硬件扫码枪、二代身份证读卡器故障频发答案不在KPI报表里在数据库的UPDATE日志中。5.1 用MySQL触发器捕获窗口状态变更全轨迹在t_window表上建触发器每次UPDATE都写入审计表记录变更前后的完整状态-- 创建审计表 CREATE TABLE t_window_audit ( id BIGINT AUTO_INCREMENT PRIMARY KEY, window_no VARCHAR(10), old_status TINYINT, new_status TINYINT, old_current_number VARCHAR(32), new_current_number VARCHAR(32), update_time DATETIME DEFAULT CURRENT_TIMESTAMP, duration_seconds INT -- 本次状态持续秒数用于计算空闲率 ); -- 触发器UPDATE后写入审计 DELIMITER $$ CREATE TRIGGER window_status_audit AFTER UPDATE ON t_window FOR EACH ROW BEGIN INSERT INTO t_window_audit ( window_no, old_status, new_status, old_current_number, new_current_number ) VALUES ( NEW.window_no, OLD.status, NEW.status, OLD.current_number, NEW.current_number ); END$$ DELIMITER ;为什么不用应用层日志应用日志可能因OOM或GC停顿丢失数据库触发器在事务内执行只要UPDATE成功审计必写入——这是金融级系统对“操作留痕”的底线要求。5.2 用Python脚本每日分析生成《窗口效能热力图》用Pandas读取审计表计算每个窗口的“空闲率”、“平均处理时长”、“异常中断率”# daily_report.py import pandas as pd from sqlalchemy import create_engine engine create_engine(mysqlpymysql://user:passhost:3306/bank_queue) # 读取当日审计日志 df pd.read_sql( SELECT window_no, old_status, new_status, UNIX_TIMESTAMP(update_time) as ts FROM t_window_audit WHERE DATE(update_time) CURDATE() , engine) # 计算空闲率status0空闲的总秒数 / 86400 idle_seconds df[df[new_status]0][ts].diff().sum() idle_rate round(idle_seconds / 86400 * 100, 2) # 计算平均处理时长status2办理中到status3已完成的时间差 proc_df df[(df[old_status]2) (df[new_status]3)] avg_proc_time proc_df[ts].diff().mean() print(f窗口空闲率: {idle_rate}% | 平均处理时长: {avg_proc_time:.1f}秒)将结果写入Excel邮件发送给网点主任。当发现“A03窗口空闲率仅12%”立即检查其硬件——果然扫码枪驱动异常更换后空闲率升至45%。5.3 关键技巧用MyBatis-Plus的SelectProvider动态SQL应对“灵活查询”业务方常提临时需求“查今天所有超时未叫号的客户”、“统计VIP业务占比”。若每个需求都写新Mapper代码膨胀快。用SelectProvider动态拼SQL// CustomerNumberMapper.java SelectProvider(type CustomerNumberSqlProvider.class, method buildQuery) ListCustomerNumber selectByCondition(Param(condition) QueryCondition condition); // CustomerNumberSqlProvider.java public class CustomerNumberSqlProvider { public String buildQuery(QueryCondition condition) { SQL sql new SQL(); sql.SELECT(*).FROM(t_customer_number); if (condition.getStartTime() ! null) { sql.WHERE(create_time #{condition.startTime}); } if (condition.getStatus() ! null) { sql.WHERE(status #{condition.status}); } if (condition.getTimeoutMinutes() ! null) { sql.WHERE(status 0 AND create_time DATE_SUB(NOW(), INTERVAL #{condition.timeoutMinutes} MINUTE)); } sql.ORDER_BY(create_time DESC); return sql.toString(); } }我的习惯是所有报表类查询绝不写死SQL全用SelectProvider。因为业务规则变起来比Java代码编译还快——上周要查“超时5分钟”这周改成“超时3分钟且业务类型为贷款”改个参数就行不用动Mapper XML。希望帮到你。本文还有配套的精品资源点击获取