简介这份资源是面向高校计算机专业学生与Java初学者的一套银行排号系统完整毕业设计资料围绕服务器端与客户端双模块架构展开可用于课程设计、毕设选题或Java桌面应用练手。系统功能划分清晰服务器端涵盖取号、统计、删除、查询与通知客户端则实现业务员登录、叫号、统计、删除及查询同一时刻支持多个工作台并行办理业务能帮助读者理解C/S通信、数据库存取与排队调度逻辑。压缩包为rar格式整体约69.98MB内含源码、演示视频、数据库脚本与配套论文文件类型覆盖代码、视频与文档便于对照学习与二次开发。目前已有468人学习下载适合需要完整赛题方案、可运行工程与论文写作参考的读者也能为排错与功能扩展提供思路。1. 银行排号系统到底在解决什么问题从叫号大厅到 Java 后台去银行办业务最直观的体验就是取号、等待、被叫号。很多人以为排号系统只是「打印一张小票」但真正落到 Java 后台它要解决的是并发取号不重号、多窗口公平调度、VIP 优先级插队、业务类型分流、断线重连后状态不丢这一整套问题。一个能跑通的银行排号系统本质是一个带状态机的队列服务号码生成器负责发号队列管理器负责排队窗口调度器负责叫号前端大屏负责展示。它适合做 Java 课程设计、毕业设计也适合想练手 Java 并发与数据库增删改查的开发者。标题里提到的源码、数据库、论文对应的正是「能跑起来 能讲清楚 能写出来」三件事下面按落地顺序拆开讲。2. 技术选型与数据库设计为什么用 Java 而不是别的2.1 后端为什么锁定 Java Spring Boot银行排号系统的核心诉求是稳定、易维护、生态成熟。Java 在这个场景里几乎是默认答案Spring Boot 把 Web 层、依赖注入、事务管理一次性配好省掉大量样板代码Java 的线程池和并发包天然适合处理「多个取号机同时发号」这种场景。常见做法是用 Spring Boot 2.x 搭单体应用前端用 Thymeleaf 或直接静态页面加 AJAX数据库用 MySQL。不推荐一上来就上微服务排号系统业务边界清晰、并发量有限单体足够拆开反而增加部署和调试成本。选型时还要考虑一个现实问题课程设计或毕设的评审老师通常关心「你有没有用到 Java 的核心特性」。所以代码里最好能体现 synchronized、ReentrantLock、线程池、事务注解这些点而不是纯 CRUD 堆砌。这也是为什么很多 Java 课程设计案例源码会选排号系统——它小而全能覆盖 Java 基础、数据库、并发、前端交互四条线。2.2 数据库表怎么设计才不返工排号系统的数据库设计有个血泪经验号码状态一定要有独立字段不要靠时间戳推断。下面是我一般会用的核心表结构字段名和类型可以直接抄。表名关键字段类型说明ticketidBIGINT 主键自增号码记录 IDticketticket_noVARCHAR(10)展示号码如 A001ticketbiz_typeVARCHAR(20)业务类型个人/对公/VIPticketstatusTINYINT0 等待 1 办理中 2 已完成 3 过号ticketcreate_timeDATETIME取号时间ticketwindow_idINT办理窗口未叫号时为 NULLwindowidINT 主键窗口编号windowstatusTINYINT0 空闲 1 忙碌windowcurrent_ticketVARCHAR(10)当前正在办理的号码建表时注意两点ticket_no 加唯一索引防止并发取号重号status 用 TINYINT 而不是字符串查询和更新都更快。业务类型如果要做优先级可以在 biz_type 上再加一个 priority 字段VIP 给高值叫号时按 priority 降序、create_time 升序取第一条。2.3 号码生成器并发取号不重号的最小实现取号是整个系统最容易翻车的地方。两台取号机同时点「取号」如果先查最大号再插入中间有时间窗口必然重号。常见做法有两种数据库唯一索引 重试或者用数据库自增主键拼号码。我一般用后者简单可靠。// 基于数据库自增 ID 生成号码避免并发重号 Service public class TicketService { Autowired private TicketMapper ticketMapper; // 业务类型前缀映射 private static final MapString, String PREFIX Map.of( PERSONAL, A, CORPORATE, B, VIP, V ); Transactional public String generateTicket(String bizType) { Ticket ticket new Ticket(); ticket.setBizType(bizType); ticket.setStatus(0); ticket.setCreateTime(new Date()); // 先插入拿到自增 ID ticketMapper.insert(ticket); // 用自增 ID 拼号码保证全局唯一 String prefix PREFIX.getOrDefault(bizType, A); String ticketNo prefix String.format(%03d, ticket.getId()); ticket.setTicketNo(ticketNo); ticketMapper.updateTicketNo(ticket.getId(), ticketNo); return ticketNo; } }这段代码的逻辑是先插入一条记录拿到数据库自增 ID再用 ID 拼出展示号码。自增 ID 由数据库保证唯一所以号码不会重。参数方面PREFIX 决定不同业务的前缀VIP 用 V 开头方便窗口识别String.format(%03d, id) 保证号码至少三位超过 999 会自动扩展。注意 Transactional 要加否则插入和更新之间失败会留下脏数据。如果并发量特别大可以把 updateTicketNo 合并成一次插入用触发器或应用层计算但课程设计级别这样写足够。3. 叫号调度与窗口管理队列怎么排、窗口怎么叫3.1 队列调度策略FIFO 还是优先级排号系统的队列不是简单先进先出。真实银行里 VIP 要优先对公业务可能单独排队过号后要重新排队或直接作废。我一般会设计成「按业务类型分队列 优先级排序」每个业务类型一个逻辑队列叫号时先看 VIP 队列有没有人再看普通队列。实现上不用真的建多个 Queue直接在数据库查询时排序即可。-- 叫号时取下一个号码VIP 优先同优先级按取号时间 SELECT * FROM ticket WHERE status 0 AND biz_type #{bizType} ORDER BY priority DESC, create_time ASC LIMIT 1;这条 SQL 的关键是 ORDER BY priority DESC, create_time ASC。priority 字段在取号时根据业务类型写入VIP 给 10对公给 5个人给 1。这样叫号逻辑不用改代码改数据就行。注意 status 0 这个条件必须加否则会把已完成的号码又叫一遍。如果要做「过号重排」把过号号码的 create_time 更新为当前时间再放回等待队列即可。3.2 窗口叫号的状态流转窗口叫号是一个典型的状态机窗口空闲 → 请求叫号 → 取到号码 → 窗口忙碌 → 办理完成 → 窗口空闲。每一步都要更新 ticket 和 window 两张表而且要在同一个事务里否则会出现「号码显示办理中但窗口还是空闲」的玄学问题。Transactional public String callNext(int windowId, String bizType) { // 1. 查窗口状态必须是空闲 Window window windowMapper.selectById(windowId); if (window.getStatus() ! 0) { throw new BizException(窗口正在办理业务); } // 2. 取下一个等待号码 Ticket next ticketMapper.selectNext(bizType); if (next null) { return null; // 无人等待 } // 3. 更新号码状态为办理中 ticketMapper.updateStatus(next.getId(), 1, windowId); // 4. 更新窗口状态为忙碌 windowMapper.updateStatus(windowId, 1, next.getTicketNo()); return next.getTicketNo(); }逻辑说明四步操作包在一个事务里任何一步失败整体回滚。参数 windowId 是窗口编号bizType 是当前窗口办理的业务类型。注意第 1 步的窗口状态检查不能省否则两个窗口同时叫号可能叫到同一个号码。如果要做「转移窗口」把 updateStatus 的 windowId 改掉即可状态不用变。3.3 大屏展示与前端轮询大屏展示一般用轮询不用 WebSocket因为排号系统对实时性要求没那么高轮询实现简单、调试方便。前端每隔 2 秒请求一次接口拿到当前叫号号码和等待人数。// 大屏轮询每 2 秒刷新一次叫号信息 setInterval(async () { const res await fetch(/api/display/current); const data await res.json(); document.getElementById(currentNo).innerText data.currentTicket || --; document.getElementById(waitCount).innerText data.waitingCount; document.getElementById(windowNo).innerText data.windowId || --; }, 2000);这段代码每 2 秒拉一次 /api/display/current更新大屏上的当前号码、等待人数和窗口号。参数 2000 是轮询间隔毫秒。如果银行大厅人多可以降到 1000如果服务器压力大升到 3000 也行。注意接口要做缓存或限流避免大屏多了以后数据库压力大。常见做法是在后端加一个 1 秒的本地缓存多个大屏共享。4. 避坑与排查排号系统最容易翻车的 5 个点4.1 并发取号重号现象两台取号机同时操作出现两个 A001。原因先查后插有时间窗口或者号码生成用了「查最大值 1」。解决用数据库自增 ID 拼号码或者给 ticket_no 加唯一索引并在插入失败时重试。我一般直接用自增 ID省事。4.2 叫号叫到已完成的号码现象窗口叫号叫出来的号码是昨天已经办完的。原因查询下一个号码时漏了 status 0 条件或者 status 更新失败但事务没回滚。解决SQL 里强制加 status 条件叫号方法加 Transactional更新失败直接抛异常。4.3 窗口状态和号码状态不一致现象大屏显示某窗口正在办理 A005但窗口实际空闲。原因更新 ticket 和 window 不在同一个事务或者更新顺序反了。解决把两步更新放进同一个 Transactional 方法先更新 ticket 再更新 window失败一起回滚。4.4 过号后无法重新排队现象客户过号了想重新排系统里找不到入口。原因过号时直接把 status 改成 3没有提供「重新激活」接口。解决加一个接口把 status 从 3 改回 0同时把 create_time 更新为当前时间让它排到队尾。注意要限制次数防止恶意刷号。4.5 数据库连接池耗尽现象大屏轮询频繁系统跑一段时间后报「连接池已满」。原因每次请求都开新连接或者慢查询没加索引。解决给 ticket 表的 status 和 biz_type 加联合索引大屏接口加缓存连接池最大连接数调到 20 以上。课程设计级别用 HikariCP 默认配置基本够但索引一定要加。5. 从能跑到能写论文框架与源码组织的进阶技巧5.1 论文框架怎么搭才不空银行排号系统的论文最容易写成「需求分析 截图 总结」的流水账。我一般会按「问题 → 方案 → 验证」三段式搭第一章讲排号系统的并发和调度难点第二章讲技术选型和数据库设计第三章讲核心模块实现号码生成、队列调度、窗口管理第四章讲测试和并发验证第五章讲不足和改进。关键是把「为什么这么设计」写清楚比如为什么用自增 ID 而不是查最大值为什么用轮询而不是长连接。这些决策点才是论文的得分点。5.2 源码怎么组织才像真实项目很多课程设计源码把所有类塞在一个包评审一看就减分。我一般按分层组织controller 放接口service 放业务逻辑mapper 放数据库操作entity 放实体类config 放配置。号码生成、队列调度、窗口管理各一个 service互不干扰。这样论文里写「模块化设计」才有东西可写。5.3 一个验证并发的小技巧想验证取号不重号不用真的开两台机器。写一个单元测试起 50 个线程同时调 generateTicket最后检查 ticket_no 有没有重复。Test public void testConcurrentGenerate() throws Exception { int threadCount 50; ExecutorService pool Executors.newFixedThreadPool(threadCount); SetString ticketNos ConcurrentHashMap.newKeySet(); CountDownLatch latch new CountDownLatch(threadCount); for (int i 0; i threadCount; i) { pool.submit(() - { try { String no ticketService.generateTicket(PERSONAL); ticketNos.add(no); } finally { latch.countDown(); } }); } latch.await(); pool.shutdown(); // 如果 set 大小等于线程数说明没有重号 assertEquals(threadCount, ticketNos.size()); }这段测试用 50 个线程并发取号用 ConcurrentHashMap 的 keySet 去重最后断言集合大小等于线程数。参数 threadCount 可以按需调整课程设计跑 50 足够说明问题。注意 latch 要放在 finally 里否则线程异常会导致主线程一直等。这个测试跑通论文里的「并发验证」章节就有实打实的数据了。我自己做这类系统最大的教训是别一上来就追求功能多先把取号、叫号、完成这三步的状态流转跑通再往上加 VIP、过号、大屏。状态机稳了后面都是锦上添花状态机不稳功能越多越乱。希望帮到你。本文还有配套的精品资源点击获取
