免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Java C/S银行排号系统:Socket+JDBC双端同步实战

Java C/S银行排号系统:Socket+JDBC双端同步实战 简介这是一套面向计算机专业本科生的毕业设计级银行排号系统完整实现方案聚焦于Java桌面应用开发与数据库协同业务逻辑设计适用于课程设计、毕设参考及Java Web/CS架构入门实践。资源包含可直接运行的Java源码、配套功能演示视频、MySQL数据库脚本及结构清晰的毕业论文文档覆盖从取号、叫号、统计、删除到跨端通知的全流程业务闭环。压缩包为RAR格式共69.98MB内含源码工程、视频文件、SQL脚本与论文PDF等核心内容各类文件分工明确源码支撑双端交互逻辑视频直观展示系统操作流程数据库脚本确保一键初始化论文则提供需求分析、系统设计与实现细节说明。目前已有468人学习下载适合希望掌握Java GUI开发、Socket通信基础、JDBC数据库操作及多角色管理员/业务员/客户权限协同建模的初学者与进阶学习者。1. 这不是个“Java小练习”一个能真跑在Windows服务端多个WinForm客户端的银行排号系统含完整数据库建表逻辑、双端状态同步机制、论文可直接套用框架你手头那份标着“毕业设计 源码”的.rar包如果只当它是 Java Swing 写的“取号叫号小 demo”那大概率会在答辩前夜翻车——它实际是一套带真实业务闭环的 C/S 架构系统服务器端监听 TCP 端口接收取号请求、维护内存队列与数据库双写一致性客户端业务员工作台通过 Socket 长连接实时获取叫号指令、显示当前服务号码、支持多窗口并发处理所有操作日志落库统计报表直接从ticket_log和counter_status表聚合生成。它不依赖 Tomcat 或 Spring Boot纯 JDK 1.8 JDBC Socket 实现但数据库事务控制、连接池复用、异常重试、断线重连这些工业级细节全在线上。适合两类人一是需要交差但不想被导师问“你怎么保证并发叫号不重复”的本科生二是想快速搭建银行/政务大厅原型、又不愿啃 Spring Cloud 分布式复杂度的实施工程师。别被“毕业设计”四个字骗了——它的DBUtil.java里藏着try-with-resources套PreparedStatement的三层嵌套ServerThread.java中的synchronized (queue)锁粒度精准到单个号段这才是能过答辩、也能真上线的分水岭。2. 从解压到双端启动5 分钟跑通核心流程含数据库初始化与端口配置2.1 解压后目录结构与关键文件定位解压.rar后你会看到四个一级目录src/Java 源码、db/数据库脚本与备份、doc/论文 Word 设计说明书 PDF、video/30 分钟部署演示视频。重点盯住以下三个路径src/com/bank/queue/主包名所有核心类都在这ServerMain.java和ClientMain.java是双端入口db/mysql_init.sqlMySQL 建表语句注意它默认用utf8mb4字符集且ticket_info表的status字段是TINYINT(1)而非ENUM这是为后续 MyBatis 扩展留的伏笔src/config.properties配置文件里面server.port8080、db.urljdbc:mysql://localhost:3306/bank_queue?useSSLfalseserverTimezoneGMT%2B8这两行必须按你本地环境改。提示video/目录里的部署流程.mp4第 7 分 23 秒开始演示如何修改config.properties但没讲清楚db.password默认是空字符串——如果你 MySQL root 密码非空这里必须填对否则启动时会卡在DBUtil.getConnection()抛SQLException。2.2 数据库初始化执行 SQL 脚本并验证表结构用 MySQL 客户端如 Navicat 或命令行新建数据库bank_queue字符集选utf8mb4排序规则utf8mb4_0900_ai_ci。然后执行db/mysql_init.sql-- mysql_init.sql 关键片段不要直接复制用完整脚本 CREATE TABLE ticket_info ( id INT PRIMARY KEY AUTO_INCREMENT, ticket_no VARCHAR(10) NOT NULL COMMENT 取号编号如A001, counter_id VARCHAR(10) DEFAULT NULL COMMENT 办理柜台ID, status TINYINT(1) DEFAULT 0 COMMENT 0-待叫号,1-已叫号,2-已过号,3-已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE counter_status ( id INT PRIMARY KEY AUTO_INCREMENT, counter_id VARCHAR(10) NOT NULL, current_ticket VARCHAR(10) DEFAULT NULL, last_call_time DATETIME DEFAULT NULL, status TINYINT(1) DEFAULT 1 COMMENT 1-空闲,2-忙碌,3-暂停 );执行后检查三张表是否创建成功ticket_info存所有号票、counter_status存每个柜台状态、user_info存业务员账号密码明文存储这是毕业设计常见做法但生产环境必须加盐哈希。特别注意ticket_info.ticket_no字段长度是VARCHAR(10)这意味着它支持字母前缀如 A001、B002而counter_status.counter_id是VARCHAR(10)允许你配置 “A1”、“B2” 这样的柜台编号——这个设计直接影响后续叫号逻辑的分组策略。2.3 启动服务器端监听端口并初始化内存队列进入src/目录用 JDK 1.8 编译并运行# Windows 下推荐用 cmd避免 PowerShell 编码问题 cd src javac -encoding UTF-8 -d ../bin com/bank/queue/ServerMain.java com/bank/queue/DBUtil.java com/bank/queue/ServerThread.java java -cp ../bin com.bank.queue.ServerMain成功启动后控制台会输出[INFO] 服务器启动成功监听端口8080 [INFO] 数据库连接正常共加载 0 条待处理号票 [INFO] 内存队列初始化完成当前等待数0此时ServerMain.java做了三件事① 加载config.properties② 调用DBUtil.getConnection()建立连接池默认最大连接数 5由db.maxPoolSize5控制③ 启动ServerSocket并开启while(true)循环accept()新客户端连接。注意它没有用 NIO而是传统 BIO 模型每个客户端连接由独立ServerThread处理——这对几十个柜台的场景足够但若要支撑上百终端得自己改造成Selector。2.4 启动客户端登录柜台并触发叫号流程新开一个命令行窗口编译并运行客户端cd src javac -encoding UTF-8 -d ../bin com/bank/queue/ClientMain.java com/bank/queue/ClientSocket.java java -cp ../bin com.bank.queue.ClientMain启动后弹出 WinForm 登录框。用户名和密码必须从user_info表查SELECT username, password FROM user_info; -- 默认数据通常是admin / 123456staff01 / 123456staff02 / 123456登录成功后界面显示当前柜台 ID如A1、等待队列初始为空、以及“取号”“叫号”“查询”按钮。点击“取号”服务器端控制台立刻打印[INFO] 收到取号请求生成新号A001 [INFO] 已插入 ticket_info 表ID1同时ticket_info表新增一行status0。这时你在服务器端界面点“统计”能看到“总取票人数1等待人数1”——说明双端数据已通过数据库同步而非仅靠内存队列。3. 双端协同的核心机制Socket 通信协议与状态同步策略3.1 客户端与服务器的通信协议基于文本行的轻量级约定整个系统不用 JSON 或 Protobuf而是用最朴素的\n分隔文本协议。客户端发送格式为ACTION:TAKE_TICKET\n COUNTER_ID:A1\n服务器返回格式为RESULT:SUCCESS\n TICKET_NO:A001\n QUEUE_SIZE:1\n这种设计牺牲了扩展性但极大降低了调试成本——你用telnet localhost 8080就能手动发指令测试。ClientSocket.java中关键代码段// 发送请求 out.write(ACTION: action \n); if (CALL_TICKET.equals(action)) { out.write(COUNTER_ID: counterId \n); // 叫号时必须带柜台ID } out.write(\n); // 协议结束标志 out.flush(); // 接收响应阻塞读取直到\n String line; while ((line in.readLine()) ! null !line.trim().isEmpty()) { if (line.startsWith(TICKET_NO:)) { currentTicket line.substring(10); // 截取A001 } else if (line.startsWith(QUEUE_SIZE:)) { queueSize Integer.parseInt(line.substring(11)); } }注意in.readLine()的阻塞特性它会一直等服务器返回完整响应所以客户端 UI 在叫号时会短暂卡顿。这是 BIO 的固有缺陷但对柜台场景影响不大——没人会一秒点十次“叫号”。3.2 服务器端的双状态维护内存队列 数据库持久化ServerMain.java启动时会从数据库加载所有status0的号票到ConcurrentLinkedQueueString内存队列但所有状态变更都先更新数据库再通知客户端。以“叫号”为例ServerThread.java中逻辑// 步骤1从DB查下一个待叫号按create_time升序 String sql SELECT ticket_no FROM ticket_info WHERE status 0 ORDER BY create_time LIMIT 1; // 步骤2UPDATE status1 并记录counter_id String updateSql UPDATE ticket_info SET status 1, counter_id ?, update_time NOW() WHERE ticket_no ?; // 步骤3向对应柜台客户端推送消息通过Socket clientOut.write(RESULT:CALLING\nTICKET_NO: ticketNo \nCOUNTER_ID: counterId \n);这里的关键是数据库是唯一真相源内存队列只是加速读取的缓存。如果服务器崩溃重启ServerMain会重新从 DB 加载status0的号票重建队列不会丢号。但要注意status1已叫号的号票不会进内存队列——它们只存在 DB 中由客户端自行轮询或等待推送。3.3 客户端的状态感知轮询 vs 推送的混合策略客户端有两种方式获知新号推送模式服务器在叫号成功后主动clientOut.write(...)客户端ClientSocket的readResponse()方法立即解析并更新 UI轮询模式当网络中断时客户端每 5 秒执行一次SELECT * FROM counter_status WHERE counter_id ?查询当前柜台状态若current_ticket变更则刷新显示。ClientMain.java中轮询代码// 启动定时器每5秒检查一次 Timer timer new Timer(); timer.schedule(new TimerTask() { Override public void run() { String sql SELECT current_ticket FROM counter_status WHERE counter_id ?; // 执行查询若current_ticket ! 当前显示值则更新UI } }, 0, 5000);这种混合策略是毕业设计里少有的工程妥协既保证低延迟推送又兜底网络抖动轮询。但轮询 SQL 没加索引counter_status.counter_id字段必须手动建索引否则高并发下会拖慢整个系统ALTER TABLE counter_status ADD INDEX idx_counter_id (counter_id);3.4 统计功能的数据聚合逻辑跨表 JOIN 与时间范围过滤服务器端“统计”按钮触发的 SQL 是SELECT COUNT(*) AS total_tickets, SUM(CASE WHEN status 0 THEN 1 ELSE 0 END) AS waiting_count, SUM(CASE WHEN status 1 THEN 1 ELSE 0 END) AS calling_count, SUM(CASE WHEN status 2 THEN 1 ELSE 0 END) AS missed_count FROM ticket_info WHERE create_time DATE_SUB(NOW(), INTERVAL 1 DAY);客户端“统计”则查counter_status表SELECT COUNT(*) AS total_counters, SUM(CASE WHEN status 1 THEN 1 ELSE 0 END) AS free_counters, SUM(CASE WHEN status 2 THEN 1 ELSE 0 END) AS busy_counters FROM counter_status;注意服务端统计默认只查近 24 小时数据INTERVAL 1 DAY避免历史数据膨胀拖慢查询。如果你需要月报得改config.properties里的stat.timeRange7单位天然后在 Java 代码中动态拼接INTERVAL ? DAY。4. 避坑指南那些让答辩老师皱眉、让部署现场抓狂的 5 个真实陷阱4.1 现象客户端登录后界面空白控制台无报错原因ClientMain.java中initComponents()方法调用了setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)但config.properties里server.host配置错误如写成127.0.0.1:8080而不是127.0.0.1导致ClientSocket构造时new Socket(host, port)抛UnknownHostException异常被catch吞掉UI 初始化继续执行但数据加载失败。解决打开ClientMain.java在initComponents()后加日志System.out.println([DEBUG] 尝试连接服务器 host : port); try { socket new Socket(host, port); } catch (Exception e) { JOptionPane.showMessageDialog(this, 连接服务器失败 e.getMessage()); throw e; // 不要静默吞异常 }4.2 现象取号后服务器显示“等待人数1”但客户端列表不更新原因客户端取号成功后ClientSocket只解析了TICKET_NO但没触发 UI 列表刷新。ClientMain.java的addTicketToList()方法被注释掉了毕业设计常见删减行为。解决找到ClientMain.java中// TODO: 更新列表注释取消下面三行注释// listModel.addElement(ticketNo); // ticketList.setModel(listModel); // ticketList.updateUI();并确保listModel是DefaultListModelString类型不是Vector。4.3 现象多柜台同时叫号时出现重复叫同一个号如 A001 被 A1 和 A2 同时叫原因ServerThread.java中SELECT ... FOR UPDATE未加锁两个线程同时查到status0的同一行然后都执行UPDATE。MySQL 默认隔离级别REPEATABLE READ下SELECT不加锁这就是经典的“幻读”场景。解决在取号 SQL 后显式加锁// 原SQL String sql SELECT ticket_no FROM ticket_info WHERE status 0 ORDER BY create_time LIMIT 1; // 改为 String sql SELECT ticket_no FROM ticket_info WHERE status 0 ORDER BY create_time LIMIT 1 FOR UPDATE;注意FOR UPDATE必须在事务中生效所以DBUtil.getConnection()获取的连接不能自动提交需手动conn.setAutoCommit(false)并在UPDATE后conn.commit()。4.4 现象删除功能在客户端点击“清空全部”后服务器端统计仍显示旧数据原因客户端执行DELETE FROM ticket_info后没通知服务器刷新内存队列。服务器端的ConcurrentLinkedQueue仍持有已删除的号票引用导致“等待人数”统计虚高。解决在客户端deleteAll()方法末尾增加推送指令// 向服务器发送清空队列通知 out.write(ACTION:CLEAR_QUEUE\n); out.flush();并在ServerThread.java中添加对该指令的处理if (CLEAR_QUEUE.equals(action)) { // 清空内存队列 ticketQueue.clear(); // 同步清空DB已执行 System.out.println([INFO] 内存队列已清空); }4.5 现象论文里写的“采用 Redis 缓存队列”但源码中根本没 Redis 依赖原因这是典型“文档与代码脱节”。doc/论文.docx第 3.2 节提到“引入 Redis 降低数据库压力”但pom.xml不存在因为是纯 JDK 项目或build.xml里找不到任何 Redis 客户端 jar。作者可能把其他项目模板复制过来忘了删。解决两种选择① 删除论文中所有 Redis 相关描述改为“采用内存队列 数据库双写保证一致性”② 真加 Redis需引入jedis-3.7.0.jar改DBUtil为RedisUtilticketQueue改用Jedis.lpop()。建议选①——毕竟这是毕业设计不是架构升级。5. 让系统真正可用的 3 个硬核改造从“能跑”到“能交差”再到“能演示”5.1 改造一给取号加前缀规则支持多业务类型分流原系统所有号都是A001、A002这样单调递增无法区分“对公业务”和“个人业务”。我们给ServerMain.java的取号逻辑加业务类型参数// 修改取号SQL支持前缀 String prefix A; // 默认A类 if (CORP.equals(businessType)) prefix C; // 对公业务用C String ticketNo prefix String.format(%03d, getNextNumber()); // getNextNumber() 从数据库查 MAX(id) 1但按prefix分组 String sql SELECT IFNULL(MAX(CAST(SUBSTR(ticket_no, 2) AS UNSIGNED)), 0) 1 FROM ticket_info WHERE ticket_no LIKE prefix %;然后在客户端登录后UI 上加个下拉框选择“业务类型”取号时传businessTypeCORP。这样ticket_info.ticket_no就变成C001、C002服务器端统计时就能GROUP BY SUBSTR(ticket_no,1,1)分开计算各类业务等待数。这个改动只需 12 行代码却能让答辩老师眼前一亮——说明你理解了“业务建模”而不仅是“CRUD”。5.2 改造二用 Log4j2 替换System.out.println实现日志分级与文件输出原系统所有日志都是System.out.println无法按ERROR/INFO过滤也不能滚动保存。替换步骤下载log4j-api-2.20.0.jar和log4j-core-2.20.0.jar放入lib/目录创建src/log4j2.xml?xml version1.0 encodingUTF-8? Configuration statusWARN Appenders File nameFileAppender fileNamelogs/server.log PatternLayout pattern%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n/ /File /Appenders Loggers Root levelinfo AppenderRef refFileAppender/ /Root /Loggers /Configuration修改ServerMain.java// 替换所有 System.out.println 为 private static final Logger logger LogManager.getLogger(ServerMain.class); logger.info(服务器启动成功监听端口{}, port);重启后日志自动写入logs/server.log且ERROR级别日志会单独高亮——这比满屏System.out专业十倍。5.3 改造三导出 Excel 统计报表让演示环节有“交付物”答辩时老师常问“数据能导出来吗”原系统只有界面上的数字。我们用 Apache POI 实现一键导出// 在 ServerMain.java 添加 exportToExcel() 方法 public void exportToExcel() { XSSFWorkbook workbook new XSSFWorkbook(); XSSFSheet sheet workbook.createSheet(今日统计); // 写表头 Row headerRow sheet.createRow(0); headerRow.createCell(0).setCellValue(号票编号); headerRow.createCell(1).setCellValue(柜台); headerRow.createCell(2).setCellValue(状态); // 查数据库填充数据 String sql SELECT ticket_no, counter_id, status FROM ticket_info WHERE create_time CURDATE(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { int rowNum 1; while (rs.next()) { Row row sheet.createRow(rowNum); row.createCell(0).setCellValue(rs.getString(ticket_no)); row.createCell(1).setCellValue(rs.getString(counter_id)); row.createCell(2).setCellValue(getStatusDesc(rs.getInt(status))); } } // 写文件 try (FileOutputStream fileOut new FileOutputStream(report_ new Date().getTime() .xlsx)) { workbook.write(fileOut); logger.info(报表导出成功report_{}.xlsx, System.currentTimeMillis()); } }编译时需加入poi-5.2.4.jar和poi-ooxml-5.2.4.jar。点击“导出报表”按钮瞬间生成 Excel——这比截图更有说服力。6. 我的血泪经验从第一次部署失败到答辩满分这 4 个习惯让我少踩 80% 的坑6.1 每次改完代码强制执行“三查一跑”清单这不是玄学是我在三次部署翻车后总结的 checklist查配置config.properties里db.url的端口、数据库名、server.port是否与本地环境一致尤其注意 MySQL 8.0 默认端口是 3306不是 3307查依赖src/目录下是否有com/mysql/cj/jdbc/Driver.class如果没有说明mysql-connector-java-8.0.33.jar没放对位置——它必须在java -cp的 classpath 里不能只扔在lib/目录却不加-cp参数查权限Windows 下如果用cmd运行右键“以管理员身份运行”Linux 下确保chmod x *.sh更重要的是logs/目录必须有写权限否则 Log4j2 会静默失败一跑不直接点 IDE 的 Run 按钮而是用命令行javacjava手动编译运行——IDE 的自动构建有时会缓存旧 class导致你以为改了代码其实跑的还是旧版本。我至今保留着一个deploy_check.bat脚本每次部署前双击运行它自动检查上述四项并高亮失败项。从那以后再没出现过“代码明明改了怎么还是老样子”的抓狂时刻。6.2 数据库脚本必须带SET FOREIGN_KEY_CHECKS0;开头mysql_init.sql里建表顺序是user_info→ticket_info→counter_status但ticket_info.counter_id外键指向counter_status.id。如果 MySQL 严格模式开启建ticket_info时会因counter_status尚未创建而报错。解决方案是在脚本最开头加SET FOREIGN_KEY_CHECKS0; -- 所有 CREATE TABLE 语句 SET FOREIGN_KEY_CHECKS1;这个细节在video/部署流程.mp4里完全没提但它是 Windows 10 MySQL 8.0 环境下的必填项。我第一次部署时卡在这里 2 小时最后发现 Navicat 执行脚本默认不启用外键检查而命令行mysql -u root mysql_init.sql会启用——所以必须显式关闭。6.3 客户端 UI 的 DPI 感知问题高分屏上字体小到看不见在 2K 屏幕的笔记本上运行ClientMain.javaWinForm 界面文字细如发丝。这是因为 Java Swing 默认不缩放。解决方案是启动 JVM 时加参数java -Dsun.java2d.uiScale1.5 -cp ../bin com.bank.queue.ClientMainuiScale1.5表示 150% 缩放。你也可以在ClientMain.java的main方法最开头加System.setProperty(sun.java2d.uiScale, 1.5); SwingUtilities.invokeLater(() - new ClientMain().setVisible(true));但硬编码不灵活所以推荐 JVM 参数方式。这个坑在答辩演示时特别致命——老师凑近屏幕才看清“叫号”按钮印象分会大打折扣。6.4 论文里的“系统测试”章节必须贴真实截图时间戳别信doc/论文.docx里那些模糊的“测试结果截图”。我重做了全部测试用手机录屏打开服务器端然后用三个 CMD 窗口分别启动ClientMain模拟 A1/A2/A3 三个柜台录下“取号→叫号→统计→导出”的全流程截取关键帧如服务器控制台显示已叫号A001客户端 A1 界面显示当前服务A001用 Photoshop 加上红色箭头和时间戳精确到秒。答辩时放这段 90 秒视频比写 500 字文字描述有力得多。老师问“怎么证明多柜台不冲突”我就暂停视频指给他看A1 叫 A001 的同时A2 界面显示等待A002A3 显示等待A003——证据确凿。希望帮到你。本文还有配套的精品资源点击获取
返回列表