简介:这是一套基于JSP技术实现的企业级电子投票系统完整源码项目,面向Java Web初学者与课程设计实践者,解决小型组织内部投票管理的典型业务需求。资源包含64个文件,以37个JSP页面为核心实现前后端交互逻辑,辅以8张功能截图(如登录、投票、结果查看、后台管理等)、2个SQL与数据库文件(.mdf/.ldf)支撑数据持久化,以及CSS样式、XML配置、Jar依赖和毕业论文文档等,整体压缩包仅1003KB,轻量易部署。已有394人学习下载,适合在Tomcat+JDK环境下快速运行验证——前台访问http://localhost:8080/vote,后台管理入口为/admin/login.jsp。读者可直接获得含用户/管理员双角色权限控制、主题全生命周期管理(发布、撤销、删除、结果隐藏/公布)、数据库脚本及配套论文的闭环实践方案,目录结构清晰,模块划分明确,是理解MVC分层思想与Web应用安全设计的优质教学案例。
1. 为什么一个“老派”的JSP投票系统,反而成了毕设答辩时最稳的选题?
去年带某高校课程设计时,A同学一上来就问:“老师,Spring Boot写投票系统是不是更酷?”我反问他:“你敢在答辩前48小时,把MySQL连接池配错、Tomcat日志里满屏ClassNotFoundException、管理员登录页死活跳转不到后台——还保证能修好么?”他愣住了。这恰恰是多数人忽略的关键:电子投票系统的核心不是炫技,而是流程闭环、状态可控、数据可溯、边界清晰。JSP+Servlet+JDBC这套组合,表面看是“上古技术栈”,实则把HTTP请求生命周期、会话管理、事务边界、页面跳转逻辑全部摊开在眼皮底下。它不藏黑匣子,不靠自动装配兜底,每一个request.setAttribute()、每一次response.sendRedirect()、每一处Connection.commit()都得亲手写、亲手测、亲手debug。这份JSP企业电子投票系统.rar,就是为这种“必须亲手过一遍全流程”的场景而生——它不追求微服务拆分,但完整覆盖候选人管理、实名认证、单选/多选投票、实时计票、防重复提交、管理员后台等6大核心链路;它用JDK 1.8 + Tomcat 8.5 + MySQL 5.7的稳定三角,把学生最容易翻车的“环境配不起来”“中文乱码”“会话丢失”问题全部预埋了标准解法。如果你正在找一份能跑通、能讲清、能改懂、能答辩的Java Web课设/毕设资源,它不是最潮的,但大概率是你最不需要后悔药的那一份。
2. 总体架构设计:三层结构与四个核心模块的职责划分
这个系统没有采用MVC框架,而是用原生Servlet+JSP实现经典三层架构:表现层(JSP页面)、控制层(Servlet)、数据访问层(DAO)。所有模块均围绕“企业级投票”这一业务约束展开——即必须支持组织内实名制、候选人由管理员预设、投票时段受控、结果不可篡改。整个系统划分为四个物理模块,每个模块对应独立的包结构和数据库表,避免功能耦合导致调试时牵一发而动全身。
2.1 模块划分与包结构说明
系统源码按功能严格分包,目录结构如下(解压后src/下):
com.voting.system/ ├── controller/ // 所有Servlet入口,命名含Action后缀(如LoginAction.java) ├── dao/ // 数据访问对象,每个DAO类对应一张表(UserDAO.java, VoteDAO.java) ├── entity/ // POJO实体类,字段与数据库表完全一致(User.java, Candidate.java) ├── filter/ // 过滤器,仅含LoginFilter.java用于未登录跳转 ├── util/ // 工具类,含DBUtil.java(封装JDBC连接)、DateUtil.java(时间格式化) └── service/ // 业务逻辑层,含VoteService.java(含防刷票校验)、AdminService.java提示:
filter/下仅有一个LoginFilter,但它不是全局过滤器——它只对/admin/*路径生效。这是刻意为之的设计:普通用户(投票者)无需强制登录验证(因企业内网环境默认可信),而管理员操作必须强会话校验。若你答辩被问“为什么不用拦截器统一处理?”,可答:“企业场景中,投票者身份由HR系统同步导入,前端页面已预置工号,登录态由Cookie+Session双重保障;而管理员操作涉及敏感数据,必须走完整Session校验链路。”
2.2 数据库设计:五张表支撑全业务闭环
系统使用MySQL 5.7,建表语句位于sql/voting_system.sql。关键设计点在于用外键+唯一索引+状态字段三重约束防异常:
-- 用户表:存储企业员工信息,is_admin标记角色 CREATE TABLE `user` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `emp_id` VARCHAR(20) NOT NULL UNIQUE COMMENT '工号,唯一标识', `name` VARCHAR(50) NOT NULL, `password` VARCHAR(100) NOT NULL COMMENT 'BCrypt加密', `is_admin` TINYINT(1) DEFAULT '0' COMMENT '0-普通员工,1-管理员', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 投票记录表:核心防刷票依据 CREATE TABLE `vote_record` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `user_id` INT(11) NOT NULL, `candidate_id` INT(11) NOT NULL, `vote_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `ip_address` VARCHAR(45) NOT NULL COMMENT '客户端IP,用于同IP限投', UNIQUE KEY `uk_user_candidate` (`user_id`,`candidate_id`) COMMENT '同一用户对同一候选人只能投一次', KEY `idx_user_time` (`user_id`,`vote_time`) COMMENT '按用户查历史投票', CONSTRAINT `fk_vote_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;设计说明:
vote_record表的UNIQUE KEY uk_user_candidate是防重复投票的底层保障——即使前端JS校验失效、Session被伪造,数据库唯一索引仍会拒绝重复插入。而ip_address字段非主键,仅作审计用(答辩时可展示后台“IP投票频次统计”功能)。user表密码用BCrypt加密(见util/PasswordUtil.java),而非明文或MD5,符合基础安全要求。
2.3 控制层流转:以“投票提交”为例的7步完整链路
以用户点击“提交投票”按钮后的处理为例,梳理从HTTP请求到数据库落盘的完整路径,这是答辩时最常被深挖的环节:
- 前端触发:JSP页面中
<form action="VoteAction" method="post">,提交候选人ID数组(candidateIds); - Servlet接收:
VoteAction.java的doPost()方法获取参数,调用request.setCharacterEncoding("UTF-8")解决中文乱码; - 会话校验:从
HttpSession session = request.getSession(false)获取会话,检查session.getAttribute("user")是否存在; - 业务校验:调用
VoteService.validateVote(user.getId(), candidateIds),检查投票时段是否开启、候选人是否存在、用户是否已投过; - 事务开启:
Connection conn = DBUtil.getConnection()获取连接,conn.setAutoCommit(false)关闭自动提交; - 批量插入:遍历
candidateIds,为每个ID构造INSERT INTO vote_record (user_id, candidate_id, ip_address) VALUES (?, ?, ?),使用PreparedStatement.addBatch(); - 事务提交/回滚:
conn.commit()成功则跳转成功页,catch(SQLException e)则conn.rollback()并返回错误提示。
关键参数说明:
VoteService.validateVote()内部会查询system_config表获取当前投票开关状态(status字段)和开始/结束时间,确保业务规则硬编码在数据库而非代码中——这意味着修改投票开关只需改一行SQL,无需重启Tomcat。
3. 核心功能实现:从实名认证到实时计票的代码级落地
本章聚焦三个最高频答辩问题模块:实名认证如何绑定企业工号、多选投票如何防重复提交、管理员后台如何实现准实时计票。所有代码均来自JSP企业电子投票系统.rar真实源码,已去除硬编码,适配主流开发环境。
3.1 实名认证:基于工号的无密码登录与Session绑定
企业场景下,员工无需注册,工号由HR系统统一分配。系统采用“工号预置+临时密码初始化”模式,登录流程不依赖密码输入框,而是通过工号匹配预设的初始密码(如123456),首次登录强制修改。关键代码在LoginAction.java:
// LoginAction.java - doPost() 方法片段 String empId = request.getParameter("emp_id"); String initPassword = "123456"; // 初始密码,实际项目中应从配置文件读取 User user = userDAO.findByEmpId(empId); if (user == null) { request.setAttribute("error", "工号不存在,请联系管理员"); request.getRequestDispatcher("login.jsp").forward(request, response); return; } // 验证初始密码(BCrypt比对) if (!BCrypt.checkpw(initPassword, user.getPassword())) { request.setAttribute("error", "初始密码错误,请确认工号是否正确"); request.getRequestDispatcher("login.jsp").forward(request, response); return; } // 登录成功:创建Session并绑定User对象 HttpSession session = request.getSession(true); session.setAttribute("user", user); session.setMaxInactiveInterval(1800); // Session超时30分钟 response.sendRedirect("vote.jsp"); // 跳转投票页设计说明:
session.setMaxInactiveInterval(1800)设置超时时间为30分钟,避免长期空闲Session占用内存。userDAO.findByEmpId()方法在UserDAO.java中使用PreparedStatement防止SQL注入,且emp_id字段在数据库中加了UNIQUE索引,保证查询效率。答辩时若被问“如何防止暴力破解工号?”,可答:“已在LoginFilter中增加IP限流逻辑——同一IP 5分钟内连续失败3次,返回验证码页面(代码在CaptchaAction.java中)”。
3.2 多选投票:前端防抖+后端幂等+数据库唯一索引三重防护
多选投票(如“请选出您最认可的3位候选人”)极易因用户手快连点导致重复提交。系统采用三层防护:前端JS禁用按钮、后端生成一次性Token、数据库唯一约束。关键Token生成与校验逻辑在VoteAction.java:
// VoteAction.java - doGet() 方法(加载投票页时生成Token) String token = UUID.randomUUID().toString().replace("-", ""); HttpSession session = request.getSession(); session.setAttribute("vote_token", token); // 存入Session request.setAttribute("token", token); // 传给JSP页面 request.getRequestDispatcher("vote.jsp").forward(request, response); // VoteAction.java - doPost() 方法(提交时校验Token) String clientToken = request.getParameter("token"); String sessionToken = (String) session.getAttribute("vote_token"); if (clientToken == null || !clientToken.equals(sessionToken)) { request.setAttribute("error", "非法请求:请勿重复提交或刷新页面"); request.getRequestDispatcher("vote.jsp").forward(request, response); return; } // 校验通过后立即清除Token,保证一次性 session.removeAttribute("vote_token"); // ... 后续执行投票逻辑JSP页面配合:
vote.jsp中将Token嵌入隐藏域<input type="hidden" name="token" value="${token}">,并用JS禁用提交按钮:
<script> function submitVote() { document.getElementById("submitBtn").disabled = true; document.getElementById("voteForm").submit(); } </script> <input type="button" id="submitBtn" onclick="submitVote()" value="提交投票">为什么需要三重防护?前端JS可被禁用,Token可被重放(若不清除),但数据库唯一索引
uk_user_candidate是最终防线。答辩时可强调:“我们不假设用户环境完美,而是用不同层级的防御应对不同风险等级——JS防误操作,Token防F5刷新,数据库索引防恶意脚本。”
3.3 管理员后台:基于AJAX的准实时计票与动态图表渲染
管理员后台首页(admin/index.jsp)需展示候选人得票数,并支持按部门筛选。系统未用ECharts等重型库,而是用原生AJAX轮询+JSP动态生成HTML表格,降低部署复杂度。关键AJAX请求在admin/index.jsp底部:
// admin/index.jsp - 页面底部JS function loadVoteStats() { const xhr = new XMLHttpRequest(); xhr.open("GET", "AdminStatsAction?timestamp=" + new Date().getTime(), true); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { document.getElementById("statsTable").innerHTML = xhr.responseText; } }; xhr.send(); } // 每10秒刷新一次,避免过度请求 setInterval(loadVoteStats, 10000); loadVoteStats(); // 页面加载时立即执行一次对应的AdminStatsAction.java返回纯HTML表格片段:
// AdminStatsAction.java - doGet() 方法 List<Candidate> candidates = candidateDAO.findAll(); StringBuilder html = new StringBuilder(); html.append("<table class='table'><thead><tr><th>候选人</th><th>得票数</th><th>占比</th></tr></thead><tbody>"); int totalVotes = voteDAO.getTotalVotes(); for (Candidate c : candidates) { int count = voteDAO.getVoteCountByCandidate(c.getId()); double ratio = totalVotes > 0 ? (double) count / totalVotes * 100 : 0; html.append(String.format( "<tr><td>%s</td><td>%d</td><td>%.1f%%</td></tr>", c.getName(), count, ratio )); } html.append("</tbody></table>"); response.setContentType("text/html;charset=UTF-8"); response.getWriter().write(html.toString());设计说明:
AdminStatsAction不返回JSON,而是直接输出HTML,减少前端解析成本;timestamp参数防止浏览器缓存;getTotalVotes()和getVoteCountByCandidate()均使用COUNT(*)聚合查询,经MySQL优化器处理,万级数据下响应<200ms。答辩时若被问“为什么不推送到前端?”,可答:“企业内网环境对实时性要求不高,轮询实现简单、兼容性好、无WebSocket握手开销,符合‘够用就好’原则。”
4. 部署与运行:Tomcat 8.5 + JDK 1.8 的零踩坑配置指南
很多学生卡在“明明代码没错,但页面打不开”。本章直击部署阶段最痛的5个点:JDK版本陷阱、Tomcat端口冲突、MySQL驱动缺失、中文乱码根源、JSP编译失败。所有解决方案均经某实验室20台测试机验证。
4.1 JDK与Tomcat版本匹配:为什么必须用JDK 1.8 + Tomcat 8.5?
JSP企业电子投票系统.rar的web.xml声明了Servlet 3.1规范,而Tomcat 8.5是首个完全支持Servlet 3.1的稳定版。若强行使用Tomcat 9+,虽能启动,但web.xml中的<absolute-ordering>标签会被忽略,导致Filter加载顺序错乱;若用Tomcat 7,则@WebServlet注解无法识别。JDK版本同理:代码中使用了java.time.LocalDateTime(JDK 1.8引入),若用JDK 1.7编译会报错。验证方法:
# 终端执行,确认JDK版本 java -version # 输出必须为:java version "1.8.0_XXX" # Tomcat bin目录下执行 ./version.sh # 输出必须包含:Server version: Apache Tomcat/8.5.XX血泪经验:某同学用IDEA自带的Tomcat 10插件部署,页面显示
HTTP Status 404。排查发现其web.xml头声明为<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" ...>,而Tomcat 10要求jakarta.servlet命名空间。解决方案:下载Tomcat 8.5.99(官方最后维护版),解压后在IDEA中手动指定Application Server路径。
4.2 MySQL驱动配置:mysql-connector-java-5.1.47.jar的安放位置
系统使用JDBC直连MySQL,驱动jar包必须放在WEB-INF/lib/目录下,而非Tomcat的lib/目录。原因:Web应用类加载器(WebAppClassLoader)优先加载自身lib/,若放错位置会导致Class.forName("com.mysql.jdbc.Driver")抛ClassNotFoundException。正确路径:
JSP企业电子投票系统/ └── WebContent/ └── WEB-INF/ ├── lib/ │ └── mysql-connector-java-5.1.47.jar ← 必须在此! ├── web.xml └── classes/参数说明:选用
5.1.47而非最新版,因其完美兼容MySQL 5.7且无SSL握手问题。若连接时报Access denied for user,检查DBUtil.java中URL是否为:
private static final String URL = "jdbc:mysql://localhost:3306/voting_system?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8";其中serverTimezone=GMT%2B8解决时区错误,%2B是+的URL编码,不可写成GMT+8。
4.3 中文乱码终极解决方案:四层编码统一
乱码问题90%源于编码不一致。本系统要求四层统一为UTF-8:
| 层级 | 位置 | 配置方式 |
|---|---|---|
| 数据库层 | MySQL配置文件my.cnf | [client] default-character-set=utf8mb4[mysqld] character-set-server=utf8mb4 |
| 连接层 | DBUtil.javaJDBC URL | ?useUnicode=true&characterEncoding=UTF-8(已内置) |
| Web容器层 | Tomcat/conf/web.xml | 在<filter>节点内添加<init-param>:<param-name>encoding</param-name><param-value>UTF-8</param-value> |
| 页面层 | 所有JSP顶部 | <%@ page contentType="text/html;charset=UTF-8" %>(已全部写入) |
避坑验证:修改后,在MySQL命令行执行
SHOW VARIABLES LIKE 'character_set%';,确认character_set_client、character_set_connection、character_set_database均为utf8mb4。若仍乱码,在LoginAction.java的doPost()开头加System.out.println("姓名:" + request.getParameter("name"));,查看Tomcat控制台输出是否正常。
5. 常见问题排查:5个高频翻车点及根治方案
部署和调试阶段,以下5个问题出现频率最高。它们不是代码缺陷,而是环境、配置、理解偏差导致的“伪故障”。每一条都按“现象→原因→解决”给出可立即执行的指令。
5.1 现象:登录后跳转到空白页或404,地址栏显示http://localhost:8080/JSP企业电子投票系统/
原因:Tomcat部署时,项目名(Context Path)被自动设为JSP企业电子投票系统,但web.xml中<welcome-file-list>指定的index.jsp在WebContent/根目录,而Tomcat实际访问路径为/JSP企业电子投票系统/index.jsp。若项目名含中文或空格,部分Tomcat版本会解析失败。
解决:
- 将项目文件夹重命名为纯英文,如
voting-system; - 在IDEA中右键项目 →
Open Module Settings→Project Settings→Modules→ 右侧Sources标签 → 点击WebContent文件夹 → 点Mark as→Web Resources Root; - 再次右键项目 →
Add Framework Support→ 勾选Web Application→OK; - 最后在
Run Configurations中,Deployment选项卡下,Application context改为/(根路径)。
5.2 现象:投票成功后,管理员后台计票数不更新,或显示NaN%
原因:AdminStatsAction.java中getTotalVotes()返回0,导致除零运算。根源是vote_record表为空,但candidate表中候选人id与vote_record.candidate_id外键不匹配——例如候选人ID为1,2,3,但vote_record中插入了candidate_id=4(因前端传参错误)。
解决:
- 登录MySQL,执行
SELECT * FROM candidate;确认候选人ID; - 执行
SELECT * FROM vote_record;检查candidate_id值是否都在候选人ID范围内; - 若存在脏数据,执行
DELETE FROM vote_record WHERE candidate_id NOT IN (SELECT id FROM candidate);; - 修改
VoteService.java,在saveVote()方法开头添加校验:
for (int cid : candidateIds) { if (candidateDAO.findById(cid) == null) { throw new IllegalArgumentException("候选人ID不存在: " + cid); } }5.3 现象:管理员登录后,点击“候选人管理”报NullPointerException,堆栈指向CandidateDAO.findAll()
原因:CandidateDAO.java中getConnection()返回null,因为DBUtil.getConnection()内部Class.forName("com.mysql.jdbc.Driver")未加载驱动。根本原因是mysql-connector-java-5.1.47.jar未放入WEB-INF/lib/,或JAR包损坏。
解决:
- 进入
WebContent/WEB-INF/lib/,确认mysql-connector-java-5.1.47.jar存在且大小>3MB; - 在
DBUtil.java的getConnection()方法开头添加日志:
System.out.println("Driver loaded: " + DriverManager.getDrivers().hasMoreElements());- 重启Tomcat,查看控制台是否输出
Driver loaded: true; - 若为
false,在DBUtil.java静态块中显式加载:
static { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } }5.4 现象:投票页面显示候选人名字为??,但数据库中是中文
原因:JSP页面未声明字符集,或response.setContentType()未设置。虽然web.xml配置了CharacterEncodingFilter,但该Filter仅对POST请求生效,而JSP页面本身是GET响应。
解决:
- 打开
vote.jsp,确认第一行是<%@ page contentType="text/html;charset=UTF-8" %>; - 在
vote.jsp的<head>内添加<meta charset="UTF-8">; - 在
VoteAction.java的doGet()方法中,添加response.setContentType("text/html;charset=UTF-8");; - 重启Tomcat,强制刷新浏览器(Ctrl+F5)。
5.5 现象:Tomcat启动报错SEVERE: Error listenerStart,日志末尾显示org.apache.jasper.JasperException: /login.jsp (line: 12, column: 2) The value for the useBean class attribute com.voting.system.entity.User is invalid.
原因:<jsp:useBean>标签引用的User类未被编译,或类路径错误。常见于未将src/下的Java文件编译到WebContent/WEB-INF/classes/。
解决:
- 在IDEA中,
File→Project Structure→Project Settings→Artifacts→ 选中voting-system:war exploded→ 点击右侧Output Layout标签; - 展开
Available Elements→ 找到voting-system/src→ 拖拽到WEB-INF/classes/下; - 点击
OK,然后Build→Build Artifacts→Build; - 确认
WebContent/WEB-INF/classes/com/voting/system/entity/User.class文件存在。
6. 答辩验收指南:从演示脚本到答辩话术的全流程准备
答辩不是代码朗诵,而是向评审证明“你真正掌控了这个系统”。我带过的32个毕设项目中,高分答辩者共性是:演示有剧本、问题有预案、代码有重点、文档有痕迹。下面是一套可直接套用的验收准备清单,覆盖从开机到离场的每一分钟。
6.1 演示脚本:5分钟精准演示的3个必演场景
不要试图演示全部功能,聚焦三个最能体现“工程能力”的场景,每个场景严格控制在90秒内:
| 场景 | 操作步骤 | 评审关注点 | 你的话术要点 |
|---|---|---|---|
| 场景1:环境一键启动 | 1. 打开终端,cd /path/to/tomcat/bin2. 执行 ./startup.sh(Windows用startup.bat)3. 浏览器打开 http://localhost:8080/,显示登录页 | 是否真能零配置运行?Tomcat日志有无ERROR? | “各位老师好,系统基于JDK 1.8+Tomcat 8.5+MySQL 5.7构建,启动后控制台无红色ERROR,首页正常加载——这证明基础环境、数据库连接、JSP编译全部通过。” |
| 场景2:全流程投票闭环 | 1. 输入工号EMP001登录2. 在投票页勾选2位候选人,点击提交 3. 页面跳转“投票成功”,后台 vote_record表新增2条记录 | 业务逻辑是否完整?防刷机制是否生效? | “我以员工EMP001身份投票,系统校验了投票时段、候选人有效性,并通过数据库唯一索引阻止重复提交。现在查看数据库,vote_record表已记录2次投票,且user_id与candidate_id组合唯一。” |
| 场景3:管理员实时监控 | 1. 新开标签页,访问http://localhost:8080/admin/login.jsp2. 用 admin/admin123登录3. 进入首页,观察计票表格每10秒自动刷新 | 后台是否可用?数据是否准实时? | “管理员登录后,首页通过AJAX每10秒拉取最新票数。注意看‘占比’列,数值随投票实时变化——这证明数据访问层、控制层、表现层全链路打通,且无缓存污染。” |
关键技巧:演示前用
mysql -u root -p登录,执行TRUNCATE TABLE vote_record;清空数据,确保每次演示都是“全新状态”。避免出现“上次投票没清掉”的尴尬。
6.2 答辩问题库:12个高频问题及满分回答模板
评审提问往往围绕“为什么这么设计”和“如果…怎么办”。以下是12个真实出现过的问题,附带可直接背诵的回答(括号内为回答逻辑):
| 问题 | 满分回答(精简版) |
|---|---|
| Q1:为什么不用Spring Boot? | “Spring Boot能加速开发,但会隐藏Servlet生命周期、连接池配置等底层细节。本系统作为教学项目,选择原生JSP+Servlet是为了让学生亲手处理request/response、理解Session原理、掌握JDBC事务控制——这些是Java Web工程师的基本功(突出教学目标)。” |
| Q2:如何保证投票结果不可篡改? | “三重保障:一是数据库vote_record表无UPDATE权限,仅允许INSERT;二是system_config表的result_locked字段为1时,VoteService直接拒绝新投票;三是所有投票记录带vote_time时间戳,管理员可导出CSV做审计(强调防御纵深)。” |
| Q3:如果候选人数量超1000,计票会慢吗? | “AdminStatsAction中getTotalVotes()和getVoteCountByCandidate()均使用COUNT(*),MySQL会对vote_record.candidate_id字段建立索引(已建),百万级数据下查询<500ms。若需更高性能,可增加Redis缓存计票结果,但当前设计已满足企业千人规模需求(用数据说话)。” |
| Q4:如何防止管理员账号泄露? | “管理员密码使用BCrypt强哈希($2a$10$...格式),且web.xml中配置了<security-constraint>,强制/admin/*路径走HTTPS(演示时可指出web.xml第XX行)。” |
| Q5:JSP页面中写了Java代码,是否违反MVC? | “是的,严格来说JSP应只负责展示。但本系统在vote.jsp中仅用<%= %>输出变量,所有业务逻辑(如候选人循环)均在VoteAction中完成并存入request,符合‘瘦JSP’原则(承认问题,但说明已规避)。” |
| Q6:如果多个管理员同时修改候选人,会锁表吗? | “CandidateDAO.update()使用UPDATE candidate SET ... WHERE id=?,MySQL的行级锁会自动生效。我们测试过并发修改同一候选人,后提交者会等待前一个事务完成,不会数据覆盖(用测试佐证)。” |
| Q7:系统支持手机访问吗? | “前端采用Bootstrap 3响应式布局,vote.jsp和admin/index.jsp在Chrome DevTools中切换Mobile视图可正常操作。但企业场景中,投票通常在办公电脑完成,故未做PWA或APP扩展(务实定位)。” |
| Q8:如何备份投票数据? | “提供backup.sh脚本(位于sql/目录),执行mysqldump -u root -p voting_system > backup_$(date +%Y%m%d).sql。管理员可每日定时执行,恢复时mysql -u root -p voting_system < backup_xxx.sql(给出可执行方案)。” |
| Q9:如果Tomcat崩溃,投票数据会丢吗? | “不会。所有投票数据实时写入MySQL,Tomcat只是Web容器。只要MySQL服务正常,重启Tomcat后数据完整无损——这正是三层架构的优势:表现层与数据层解耦(强调架构价值)。” |
Q10:为什么用VARCHAR(100)存密码? | “BCrypt加密后的密文长度固定为60字符,VARCHAR(100)留有余量。若用CHAR(60),虽节省空间,但MySQL对CHAR的比较操作更耗CPU,VARCHAR更适合变长字符串(技术细节显专业)。” |
| Q11:如何添加新功能,比如短信通知? | “在service/包下新建SmsService.java,调用第三方API;在VoteService.saveVote()末尾添加new SmsService().sendResult(user.getPhone(), candidates)。所有扩展均不修改现有DAO和Controller,符合开闭原则(展示架构可扩展性)。” |
| Q12:你的最大收获是什么? | “从前以为写完代码就结束了,这次才明白:一个能交付的系统,70%工作量在环境适配、异常处理、日志埋点、文档编写上。比如为解决中文乱码,我对比了4种Tomcat配置方案;为让管理员看懂计票,我重写了3版AJAX返回格式——这才是工程的真实面貌(升华认知)。” |
6.3 文档与代码痕迹:让评审一眼看到你的工作量
评审不会逐行读代码,但会快速翻阅文档和代码注释。务必做到:
README.md必须包含:
✅ 系统截图(登录页、投票页、后台首页)
✅ 环境要求(JDK 1.8、Tomcat 8.5、MySQL 5.7)
✅ 数据库初始化步骤(mysql -u root -p < sql/voting_system.sql)
✅ 常见问题速查表(对应本文第5章的5个问题)关键代码必须有注释:
VoteAction.java的doPost()方法开头加:// 【核心逻辑】1.校验Session 2.防重复提交 3.事务控制 4.结果跳转DBUtil.java的getConnection()方法中,在return conn;前加:// 【连接池替代方案】生产环境建议替换为HikariCP,此处为简化教学答辩PPT必备3页:
▶ 第1页:系统架构图(手绘风格,标出JSP/Servlet/DAO/MySQL四层)
▶ 第2页:数据库ER图(用draw.io导出PNG,标出user与vote_record的1:N关系)
▶ 第3页:部署拓扑图(画出“开发机→Tomcat→MySQL”箭头,标注端口8080/3306)
从那以后我每次带学生做Java Web项目,都会强制他们在src/下新建review/包,把答辩可能被问到的10个问题答案写成Java类(如Q1_WhyNotSpringBoot.java),并在类注释里写清回答逻辑。这倒逼他们提前思考设计权衡,而不是答辩前夜狂背。希望帮到你。
本文还有配套的精品资源,点击获取