ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

银行排队叫号系统JSP+MySQL实战:状态流转与并发避坑

银行排队叫号系统JSP+MySQL实战:状态流转与并发避坑 简介这份资源是一篇完整的银行排队叫号系统毕业设计论文文档面向计算机相关专业学生及需要完成课程设计、毕业设计的学习者帮助解决排队管理类系统选题的论文撰写与方案参考问题。压缩包内共1个docx文件约1.15MB内容为论文正文涵盖摘要、绪论、开发技术介绍、系统设计流程、系统架构、数据库设计与系统测试等章节。论文基于Java语言与Jsp技术采用B/S模式围绕系统个人中心、显示管理、客户管理、排队管理、服务业务管理、客户评价管理、等候区管理等模块展开并配有功能模块图、流程图与E-R图完整呈现从市场调研、需求分析到编码测试的软件开发流程。目前已有170人学习下载适合需要借鉴论文结构、功能模块划分与数据库设计思路的读者参考使用。1. 从一份“银行排队叫号系统论文系统论文.docx”说起JSPMySQL 的 B/S 老架构为什么还值得动手做一遍很多人看到“银行排队叫号系统”这几个字第一反应是“这不就是个取号、叫号、窗口显示的增删改查吗”。但真把这份银行排队叫号系统论文系统论文.docx拆开看它背后其实是一套典型的 Java Web B/S 架构落地题JSP 负责页面渲染Servlet 接请求MySQL 存队列状态浏览器端轮询刷新。它不新但它是理解“请求怎么进来、状态怎么流转、并发怎么排队”的极佳样本。适合谁适合正在做课程设计、毕设选题或者想用一套完整案例把 Java、JSP、MySQL 串起来的人。你不需要微服务也不需要前端框架一台装了 JDK 和 Tomcat 的机器就能跑通。真正难的不是写页面而是把“叫号”这件事的状态一致性做对——这也是后面几章要重点拆的地方。2. 银行排队叫号系统的业务闭环从取号到叫号状态到底怎么流转2.1 先定角色和状态机别急着写 JSP做这个系统最容易翻车的地方是一上来就打开 IDEA 新建 JSP 项目然后开始拖表格。结果写到一半发现普通客户、VIP 客户、窗口柜员三种角色的权限混在一起号码状态一会儿是“已取号”一会儿又变成“已叫号”数据库里对不上。我的习惯是先在纸上把状态机画清楚再动手。这个系统里核心只有两个实体号码Ticket和窗口Window。号码的状态流转是固定的状态值含义触发动作0已取号等待中客户点击取号1已叫号正在办理柜员点击叫号2已完成柜员点击完成3已过号柜员点击过号或超时窗口状态更简单空闲 / 忙碌。柜员点“叫号”时系统要做三件事把当前窗口置为忙碌、把队首号码状态改成 1、把号码和窗口绑定。这三步必须在一个事务里完成否则就会出现“号码被叫了但窗口还显示空闲”的玄学问题。角色权限上普通客户只能取号和看大屏柜员只能操作自己窗口的叫号/完成/过号管理员能增删窗口和查看统计。把这三条写进一个role字段登录后存进 session后面每个 Servlet 入口先判断角色能省掉大量后期改权限的血泪经验。2.2 数据库表设计四张表撑起整个叫号逻辑MySQL 这边不需要花哨设计四张表足够。下面是我一般会用的建表语句字段名尽量直白方便后面 JSP 里写 EL 表达式。-- 窗口表 CREATE TABLE window ( id INT PRIMARY KEY AUTO_INCREMENT, window_no VARCHAR(10) NOT NULL COMMENT 窗口号如 A01, status TINYINT DEFAULT 0 COMMENT 0空闲 1忙碌, staff_name VARCHAR(20) COMMENT 柜员姓名 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 号码表 CREATE TABLE ticket ( id INT PRIMARY KEY AUTO_INCREMENT, ticket_no VARCHAR(10) NOT NULL COMMENT 号码如 A001, type TINYINT DEFAULT 0 COMMENT 0普通 1VIP, status TINYINT DEFAULT 0 COMMENT 0等待 1办理中 2完成 3过号, window_id INT DEFAULT NULL COMMENT 办理窗口, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, call_time DATETIME DEFAULT NULL, INDEX idx_status_type (status, type) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 用户表 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role TINYINT DEFAULT 0 COMMENT 0客户 1柜员 2管理员 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 叫号日志表方便统计和排查 CREATE TABLE call_log ( id INT PRIMARY KEY AUTO_INCREMENT, ticket_id INT NOT NULL, window_id INT NOT NULL, action VARCHAR(10) COMMENT call/finish/pass, log_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有两个参数值得说。第一ticket表上建了idx_status_type联合索引因为叫号时查询永远是“status0 且按 type 优先”这个条件没索引的话号码一多就慢。第二password留 64 位是给后面 SHA-256 加密用的别存明文这是课程设计里最容易被老师挑的点。VIP 优先怎么实现不是单独建表而是在查询队首时用ORDER BY type DESC, create_time ASC。这样 VIP 永远排在普通号前面但同类型内还是先来先服务。这个排序逻辑写在 SQL 里比在 Java 里手动挑要可靠得多。2.3 取号与叫号的核心 Servlet 逻辑取号逻辑简单生成号码、插入 ticket 表。号码生成我一般用“前缀 当日序号”前缀按类型分 A/B序号用SELECT COUNT(*)1 FROM ticket WHERE DATE(create_time)CURDATE()。注意这里有个并发坑后面避坑章节会讲。叫号逻辑是重点必须用事务包住。下面是一个简化版的核心代码// CallServlet.java 核心片段 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int windowId Integer.parseInt(req.getParameter(windowId)); Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 查队首号码VIP优先同类型先到先得 String sql SELECT id, ticket_no FROM ticket WHERE status 0 ORDER BY type DESC, create_time ASC LIMIT 1 FOR UPDATE; PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery(); if (!rs.next()) { conn.rollback(); resp.getWriter().write({\code\:1,\msg\:\当前无等待号码\}); return; } int ticketId rs.getInt(id); String ticketNo rs.getString(ticket_no); // 2. 更新号码状态为办理中绑定窗口 ps conn.prepareStatement( UPDATE ticket SET status1, window_id?, call_timeNOW() WHERE id?); ps.setInt(1, windowId); ps.setInt(2, ticketId); ps.executeUpdate(); // 3. 更新窗口为忙碌 ps conn.prepareStatement(UPDATE window SET status1 WHERE id?); ps.setInt(1, windowId); ps.executeUpdate(); // 4. 写日志 ps conn.prepareStatement( INSERT INTO call_log(ticket_id, window_id, action) VALUES(?,?,call)); ps.setInt(1, ticketId); ps.setInt(2, windowId); ps.executeUpdate(); conn.commit(); resp.getWriter().write({\code\:0,\ticketNo\:\ ticketNo \}); } catch (Exception e) { if (conn ! null) try { conn.rollback(); } catch (SQLException ex) {} e.printStackTrace(); } finally { DBUtil.close(conn); } }这段代码有三个关键点。FOR UPDATE是行锁防止两个窗口同时叫到同一个号这是并发场景下最容易被忽略的一行。setAutoCommit(false)到commit()之间是事务边界任何一步失败都回滚保证号码和窗口状态一致。返回用 JSON 而不是转发 JSP是因为大屏和柜员端都用 AJAX 轮询返回 JSON 更通用。参数上windowId从 session 里取更安全这里用参数是为了演示。真实项目里柜员登录后把 windowId 存 session叫号时直接读避免前端篡改。3. 用 JSP 把取号端、柜员端、大屏端三张页面跑起来3.1 取号页面一个表单加一段 AJAX取号端不需要复杂交互一个按钮加一个显示号码的区域就够。JSP 在这里的作用是渲染初始页面和提供上下文路径。% page contentTypetext/html;charsetUTF-8 languagejava % html headtitle取号/title/head body h2请选择业务类型/h2 button onclicktakeTicket(0)普通业务/button button onclicktakeTicket(1)VIP业务/button div idresult/div script function takeTicket(type) { fetch(${pageContext.request.contextPath}/TakeTicketServlet, { method: POST, headers: {Content-Type: application/x-www-form-urlencoded}, body: type type }) .then(res res.json()) .then(data { document.getElementById(result).innerText data.code 0 ? 您的号码 data.ticketNo : data.msg; }); } /script /body /html${pageContext.request.contextPath}是 JSP 里拿项目根路径的标准写法部署到 Tomcat 后不管 context path 叫什么都能正确请求。很多新手直接写/TakeTicketServlet本地跑没事一换部署路径就 404这是典型的环境相关翻车。3.2 柜员端与大屏端轮询刷新怎么做才不卡柜员端需要显示当前窗口状态和操作按钮大屏端只需要显示“当前叫号”和“等待队列”。两端都用定时轮询但频率要区分柜员端 3 秒一次大屏端 2 秒一次。太频繁会给 Tomcat 压力太慢用户觉得卡。// 大屏端轮询 function refreshBoard() { fetch(${pageContext.request.contextPath}/BoardServlet) .then(res res.json()) .then(data { document.getElementById(currentNo).innerText data.currentNo || --; document.getElementById(waitCount).innerText data.waitCount; }); } setInterval(refreshBoard, 2000);BoardServlet里只做两件事查 status1 的最新号码作为当前叫号查 status0 的 count 作为等待人数。这两个查询都走索引数据量再大也是毫秒级。注意大屏端不要查全表只查需要的字段这是保证轮询不拖垮服务器的关键。3.3 传统 JSP 项目打包 war 与部署到 Tomcat写完之后要部署。IDEA 里新建的是 Java Web 项目最终产物是 war 包。用 Maven 的话在pom.xml里把 packaging 设成 war然后执行mvn clean package # 产物在 target/ 下文件名类似 bank-queue-1.0.war把 war 丢进 Tomcat 的webapps目录启动 Tomcat 会自动解压。访问地址是http://localhost:8080/bank-queue/。这里有个常见问题JSP 页面报 500 但日志没细节多半是WEB-INF/lib下缺了 MySQL 驱动 jar。Maven 里加mysql-connector-java依赖并设 scope 为 compile打包时会自动带进去。数据库连接建议用连接池不要每次DriverManager.getConnection。最简单的做法是在context.xml里配Resource或者用 Druid。连接池参数里maxActive设 20 就够这个场景maxWait设 3000 毫秒避免请求堆积时线程一直等。4. 避坑与排查叫号系统最容易翻车的五个地方4.1 号码重复并发取号时 COUNT1 不可靠现象两个人同时点取号拿到同一个号码或者号码跳号。原因SELECT COUNT(*)1在并发下两个事务读到同一个 count插入后自然重复。解决要么给号码生成加唯一索引并捕获重复异常重试要么用一张sequence表配合UPDATE ... SET valval1的行锁。我一般用后者简单可靠。4.2 叫号后窗口状态没更新事务漏了或自动提交没关现象号码显示“办理中”但窗口还是“空闲”下一个号又被叫到同一窗口。原因更新 ticket 和更新 window 不在同一个事务里或者setAutoCommit没设 false。解决把两步放进同一个 Connection 的事务任何一步失败整体回滚。检查代码里有没有在中间调用了DBUtil.close导致连接提前归还。4.3 中文乱码JSP、Servlet、MySQL 三处编码要统一现象取号页面显示“测试”这种乱码或者数据库里存进去就是问号。原因JSP 的pageEncoding、Servlet 的request.setCharacterEncoding、MySQL 的character_set_server三者不一致。解决JSP 统一UTF-8Servlet 入口第一行req.setCharacterEncoding(UTF-8)MySQL 建库时指定utf8mb4连接串加useUnicodetruecharacterEncodingutf8。三处对齐乱码基本绝迹。4.4 大屏轮询把 Tomcat 拖垮查询没走索引或返回了全表现象运行一段时间后 Tomcat 响应变慢CPU 升高。原因BoardServlet里写了SELECT * FROM ticket然后 Java 里过滤数据一多就崩。解决SQL 里直接WHERE status0和WHERE status1 ORDER BY call_time DESC LIMIT 1并且 status 字段建索引。返回给前端的字段只保留 ticket_no 和 count别把整行数据序列化成 JSON。4.5 部署后 404context path 写死或 war 没解压现象本地 IDEA 跑得好好的部署到 Tomcat 后所有请求 404。原因前端请求路径写死了/TakeTicketServlet但实际 context path 是/bank-queue。解决所有请求路径用${pageContext.request.contextPath}拼接或者用c:url标签。另外确认 war 是否被 Tomcat 自动解压webapps下有没有对应的文件夹。5. 把叫号系统做扎实的两个进阶技巧VIP 插队算法与叫号日志回溯5.1 VIP 插队不是简单排序要防止普通号饿死前面用ORDER BY type DESC, create_time ASC实现了 VIP 优先但这有个隐患如果 VIP 一直来普通号可能永远排不到。真实银行的做法是“VIP 优先但设上限”比如每叫 3 个 VIP 必须插 1 个普通号。实现方式是在查询队首时加一个计数判断-- 先查最近10次叫号里VIP占比超过阈值就强制取普通号 SELECT COUNT(*) FROM ( SELECT type FROM call_log cl JOIN ticket t ON cl.ticket_id t.id WHERE cl.actioncall ORDER BY cl.log_time DESC LIMIT 10 ) tmp WHERE type 1;如果这个 count 大于等于 7下一次叫号就只查type0的队首。这个阈值可以根据业务调参数放在配置文件里别写死在代码里。这样既保证 VIP 体验又不让普通客户等到怀疑人生。5.2 用 call_log 做叫号回溯和窗口效率统计call_log表不只是日志它是后期做统计的数据源。比如算每个窗口的平均办理时长可以用相邻两条 call 和 finish 记录的时间差SELECT w.window_no, AVG(TIMESTAMPDIFF(SECOND, c1.log_time, c2.log_time)) AS avg_seconds FROM call_log c1 JOIN call_log c2 ON c1.ticket_id c2.ticket_id AND c1.actioncall AND c2.actionfinish JOIN window w ON c1.window_id w.id GROUP BY w.window_no;这个查询能直接告诉管理员哪个窗口效率低、哪个柜员办理慢。做毕设的话这一张统计表就能撑起“数据分析”章节比堆一堆增删改查页面有说服力得多。5.3 验证方法用 JMeter 模拟 50 个并发取号功能写完不算完得验证并发下号码不重复。用 JMeter 建一个线程组50 个线程同时请求取号接口循环 1 次然后查数据库SELECT ticket_no, COUNT(*) AS cnt FROM ticket GROUP BY ticket_no HAVING cnt 1;如果这条 SQL 返回空说明号码生成逻辑扛住了并发。如果返回了重复行回到 4.1 去改号码生成方式。这个验证步骤花不了十分钟但能帮你提前发现最致命的问题。我自己做这类系统最大的教训是别在页面样式上花太多时间把状态流转和事务边界理清楚系统就稳了一大半。JSP 和 MySQL 这套老组合看起来朴素但把并发和一致性这两个点吃透比盲目上新技术收获更大。希望帮到你。本文还有配套的精品资源点击获取
返回列表