Java Web投票系统:Servlet+JSP+Access实战与迁移指南
2026/9/24 18:16:51 网站建设 项目流程

简介:这是一份面向高校Web开发课程设计的Java Web投票系统实战项目资源,适用于Java Web初学者巩固JSP、Servlet基础与数据库交互能力。资源包含完整可运行的前后端代码及Access数据库,覆盖用户投票与后台管理两大核心功能模块:普通用户通过index.jsp免登录参与投票,管理员凭admin/admin凭证登录login.jsp进行题库维护与结果统计。压缩包共14个文件,含9个JSP页面(实现页面跳转与业务逻辑)、1个Java类(DBBean.java封装数据库连接)、1个XML配置文件(web.xml定义Servlet映射)、1个Class字节码、1个MDB数据库文件及1个GIF图标,整体仅16KB,轻量易部署。已有866人学习下载,提供开箱即用的源码结构、清晰的分层目录(vote根目录+WEB-INF+classes+dbBean子包)以及关键页面间调用关系,便于理解MVC雏形与Web应用基本流程。

1. 这不是又一个“Hello World”投票Demo:它真能跑通登录鉴权+数据库增删改查+前后端分离雏形,适合Java Web课设交作业、面试前快速复现、或当Spring Boot项目前的Servlet地基训练

你手头这份web课程设计基于Java的投票系统(源码+数据库).zip,不是网上泛滥的“JSP页面堆砌+无逻辑跳转”的摆设型课设。它用纯Servlet+JSP+JDBC实现了一套闭环业务:普通用户能匿名投票、查看实时票数;管理员能登录(账号admin/admin)、增删候选人、重置投票、查看原始数据——所有操作都落库、有状态、可验证。核心是DBBean.java封装了连接池雏形(虽未用Druid/Hikari,但已抽象出getConnection/close等方法),web.xml配置了4个关键Servlet映射,vote.mdb是Access数据库(注意:不是MySQL/Oracle,这是它最易翻车的点)。如果你正被Java Web课设 deadline追着跑,或想亲手拆解一个“没用框架却功能完整”的经典MVC雏形,这份资源就是你今晚能跑起来、明天能讲清楚、后天能改造成SSM项目的实操锚点。别被“课程设计”四个字骗了——它比90%的所谓“Java Web实战项目”更贴近真实开发中“从零搭骨架”的第一公里。


2. 搭建环境:为什么必须用Tomcat 8.5 + JDK 8 + Access驱动?三步配齐不报错的底层逻辑

2.1 环境选型不是拍脑袋:JDK 8 和 Tomcat 8.5 是 Access JDBC 驱动的硬性门槛

这份源码写于2010年代中期,DBBean.java中使用的是sun.jdbc.odbc.JdbcOdbcDriver(JDBC-ODBC桥接驱动),该驱动在JDK 8中仍可用,但在JDK 11+已被彻底移除。同时,Tomcat 8.5是最后一个默认支持JSP 2.3和Servlet 3.1、且与Windows ODBC数据源兼容稳定的版本。若强行用Tomcat 10+,你会遇到javax.servlet.http.HttpServlet类找不到的编译错误;若用JDK 17,Class.forName("sun.jdbc.odbc.JdbcOdbcDriver")直接抛ClassNotFoundException。这不是配置问题,是Java生态演进的断层线——这里没有“升级解决”,只有“精准匹配”

2.2 配置Access数据库:不是把vote.mdb扔进WEB-INF就完事,ODBC数据源才是命门

Access数据库无法像MySQL那样直接用URL连接,必须通过Windows系统级ODBC数据源注册。具体步骤:

  1. 打开Windows控制面板 → 管理工具 → ODBC数据源(32位)→ 系统DSN → 添加 → Microsoft Access Driver (*.mdb)
  2. 数据源名(DSN)填voteDB(必须与DBBean.javajdbc:odbc:voteDB完全一致)
  3. 选择vote.mdb文件路径(建议放C:\voteDB\vote.mdb,避免中文路径和空格)
  4. 点击确定完成注册

提示:务必使用32位ODBC管理器!即使你的系统是64位,Tomcat默认以32位进程运行,64位ODBC配置对它不可见。打开ODBC管理器时看标题栏是否含“(32-bit)”,否则连接必失败。

2.3 Tomcat部署细节:classes目录结构决定能否加载DBBean.class

解压后的classes文件夹不能直接丢进WEB-INF。正确路径是:

vote/ ├── WEB-INF/ │ ├── web.xml │ ├── classes/ ← 这里放编译好的.class文件 │ │ └── DBBean.class ← 注意包路径:若DBBean.java声明package dbBean;,则此处应为 classes/dbBean/DBBean.class │ └── lib/ ← 不需要额外jar,JDBC-ODBC驱动已内置JDK

常见错误是把整个classes文件夹复制进去,导致Tomcat在WEB-INF/classes/classes/下找类,自然ClassNotFoundException我每次部署前都会用jar -tf vote.war | grep DBBean检查class路径是否扁平化

2.4 启动验证:用curl命令绕过浏览器缓存,直击Servlet逻辑链

不要只靠浏览器刷新看index.jsp。用命令行验证后端连通性:

# 测试管理员登录接口(模拟表单提交) curl -X POST "http://localhost:8080/vote/login.jsp" \ -d "username=admin" -d "password=admin" \ -H "Content-Type: application/x-www-form-urlencoded" \ -i

预期返回HTTP 302重定向到manage.jsp,且响应头含Set-Cookie: admin=true。若返回404,检查web.xml<servlet-mapping>url-pattern是否为/login.jsp;若返回500,立刻看Tomcat日志catalina.outjava.sql.SQLException的具体提示——90%是ODBC DSN名拼错或mdb文件被其他程序占用。


3. 源码结构解析:从index.jsp到process.jsp,读懂这个投票系统的MVC毛细血管

3.1 前端入口:index.jsp如何实现“免登录”与“投票隔离”

index.jsp表面简单,实则暗藏两处关键设计:

  • IP级投票限制:通过request.getRemoteAddr()获取客户端IP,查询数据库ip_log表(若存在)判断该IP是否已投。源码中此逻辑被注释掉,但表结构已预留字段ip_address VARCHAR(15)vote_time DATETIME
  • 候选人动态渲染:用<c:forEach>遍历request.getAttribute("candidates"),该List由VoteServletdoGet()中从数据库查出并setAttribute。注意:这里没用Ajax,所有交互都是同步页面跳转,符合Servlet时代典型模式

3.2 核心业务Servlet:process.jsp不是JSP,而是伪装成JSP的Servlet控制器

process.jsp名为JSP,实际承担Controller职责:

<%@ page import="java.sql.*" %> <% String action = request.getParameter("action"); if ("vote".equals(action)) { // 1. 获取候选人ID String candidateId = request.getParameter("cid"); // 2. 调用DBBean更新票数 DBBean db = new DBBean(); String sql = "UPDATE candidates SET votes = votes + 1 WHERE id = ?"; PreparedStatement ps = db.getConnection().prepareStatement(sql); ps.setString(1, candidateId); ps.executeUpdate(); // 3. 重定向回index.jsp response.sendRedirect("index.jsp"); } else if ("reset".equals(action)) { // 管理员重置逻辑... } %>

关键说明:这段代码暴露了JSP作为Controller的原始缺陷——业务逻辑与HTML混杂。ps.setString(1, candidateId)若传入非数字字符串,会触发SQL异常;response.sendRedirect前若已输出HTML,将抛IllegalStateException这就是为什么现代框架强制分离View与Controller:不是为了炫技,是为避免这种脆弱耦合

3.3 管理员模块:checkadmin.jsp的鉴权漏洞与修复思路

checkadmin.jsp是登录校验入口,其核心逻辑:

<% String username = request.getParameter("username"); String password = request.getParameter("password"); if ("admin".equals(username) && "admin".equals(password)) { session.setAttribute("admin", "true"); response.sendRedirect("manage.jsp"); } else { response.sendRedirect("login.jsp?error=1"); } %>

血泪经验:这段代码存在三处硬伤:

  1. 密码明文存储(admin/admin写死在代码里)
  2. 无防暴力破解(连续失败10次不锁定)
  3. Session劫持风险(未设置HttpSession.setMaxInactiveInterval(1800)
    修复方案:将密码哈希化(如SHA-256加盐),用Filter统一拦截/manage/*请求,校验session有效性——这些改造正是你把它升级为SSM项目的起点。

3.4 数据库设计:vote.mdb的5张表如何支撑完整业务流

vote.mdb包含以下表结构(用Access打开可验证):

表名字段说明
candidatesid(AutoNumber),name(Text),votes(Number)候选人主表,votes初始为0
ip_logid(AutoNumber),ip_address(Text),vote_time(DateTime)投票IP日志,用于防刷(当前未启用)
admin_usersusername(Text),password(Text)管理员表,仅1条记录(admin/admin)
vote_historyid(AutoNumber),candidate_id(Number),ip(Text),time(DateTime)每次投票的完整记录
system_configkey(Text),value(Memo)系统配置项,如voting_open控制投票开关

注意:candidates.id是自动编号,但process.jspUPDATE语句用WHERE id = ?,意味着前端传来的cid必须是数字。若JSP中用<input type="hidden" name="cid" value="张三">,此处必报错。源码中所有候选人都用数字ID标识,这是设计一致性,不是bug


4. 避坑指南:部署调试中90%的报错都来自这5个边界场景

4.1 现象:启动Tomcat后访问http://localhost:8080/vote/显示404,但index.jsp文件明明在ROOT目录下

原因web.xml<welcome-file-list>未配置index.jsp,或Tomcat未识别该应用为vote上下文。
解决

  1. 检查web.xml是否有以下配置:
<welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list>
  1. 确认WAR包名为vote.war(不是vote_system.war),Tomcat会自动将其部署为/vote上下文。若手动解压,需确保文件夹名为vote并放入webapps/目录。

4.2 现象:管理员登录成功后跳转到manage.jsp,但页面显示“数据库连接失败”

原因:ODBC数据源voteDB注册时指向的vote.mdb文件被其他程序(如Access软件)独占锁定。
解决

  1. 关闭所有Access进程(任务管理器中结束MSACCESS.EXE
  2. vote.mdb复制一份,用记事本打开确认无乱码(证明文件未损坏)
  3. DBBean.javagetConnection()方法中,添加日志:
System.out.println("Attempting to connect to DSN: voteDB"); Connection conn = DriverManager.getConnection("jdbc:odbc:voteDB"); System.out.println("Connection successful");

若第二行不打印,说明ODBC配置无效。

4.3 现象:投票后candidates.votes数值不变,但vote_history表新增记录

原因process.jspUPDATE语句的WHERE id = ?条件未匹配到任何行,因前端传参cid是字符串(如"1"),而Access中id字段是数字类型,隐式转换失败。
解决:在process.jsp中强制类型转换:

String cidStr = request.getParameter("cid"); int cid = Integer.parseInt(cidStr); // 加try-catch捕获NumberFormatException String sql = "UPDATE candidates SET votes = votes + 1 WHERE id = " + cid; // 改用拼接,避开PreparedStatement类型问题

4.4 现象:add.jsp提交新候选人后,页面空白,数据库无新增

原因add.jsp表单的action指向process.jsp?action=add,但process.jsp中无else if ("add".equals(action))分支。
解决:在process.jsp的if-else链末尾添加:

else if ("add".equals(action)) { String name = request.getParameter("name"); String sql = "INSERT INTO candidates (name, votes) VALUES (?, 0)"; PreparedStatement ps = db.getConnection().prepareStatement(sql); ps.setString(1, name); ps.executeUpdate(); response.sendRedirect("manage.jsp"); }

4.5 现象:delete.jsp删除候选人后,index.jsp仍显示已删除的候选人

原因index.jsprequest.getAttribute("candidates")获取数据,但delete.jsp未重定向回index.jsp,而是直接显示“删除成功”页面,导致缓存旧数据。
解决:修改delete.jsp最后一行:

response.sendRedirect("index.jsp"); // 替换原来的 out.println("删除成功")

5. 进阶改造:把Access换成MySQL,3步迁移让老课设焕发新生

5.1 数据库迁移:用Access SQL导出+MySQL Workbench导入的零误差方案

Access的SQL语法与MySQL高度兼容,但需处理三处差异:

  1. 自动编号字段:Access的AutoNumber→ MySQL的INT AUTO_INCREMENT PRIMARY KEY
  2. 日期类型:Access的DateTime→ MySQL的DATETIME
  3. 文本长度:Access的Text(255)→ MySQL的VARCHAR(255)

实操步骤

  1. 在Access中,右键candidates表 → “导出” → “文本文件” → 保存为candidates.csv
  2. 用MySQL Workbench新建Schemavote_db,执行建表语句:
CREATE TABLE candidates ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, votes INT DEFAULT 0 ); -- 其他表同理
  1. Workbench中右键表 → “Table Data Import Wizard”,选择candidates.csv,映射字段后导入。关键:勾选“Truncate table before import”,避免主键冲突

5.2 JDBC驱动替换:从JDBC-ODBC桥接到MySQL Connector/J 8.0

替换DBBean.java中的数据库连接逻辑:

// 原Access连接(已废弃) // Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); // Connection conn = DriverManager.getConnection("jdbc:odbc:voteDB"); // 新MySQL连接 Class.forName("com.mysql.cj.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/vote_db?useSSL=false&serverTimezone=UTC"; Connection conn = DriverManager.getConnection(url, "root", "your_password");

注意:MySQL Connector/J 8.0要求URL中显式指定serverTimezone=UTC,否则抛The server time zone value '...' is unrecognized异常。这是2023年后MySQL驱动的强制要求,不是bug

5.3 Servlet重构:用标准Servlet替代JSP脚本,迈出MVC规范化第一步

process.jsp的业务逻辑提取为VoteServlet.java

@WebServlet("/vote") public class VoteServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action = request.getParameter("action"); if ("vote".equals(action)) { int cid = Integer.parseInt(request.getParameter("cid")); // ... 数据库操作 response.sendRedirect(request.getContextPath() + "/index.jsp"); } } }

对应web.xml中删除process.jsp的servlet配置,改为:

<servlet> <servlet-name>VoiceServlet</servlet-name> <servlet-class>VoteServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>VoiceServlet</servlet-name> <url-pattern>/vote</url-pattern> </servlet-mapping>

好处:从此process.jsp只负责展示,业务逻辑在Java类中,可单元测试、可加事务注解、可无缝接入Spring——这才是企业级开发的起点。

5.4 安全加固:给管理员登录加盐哈希,堵住明文密码这个最大漏洞

修改checkadmin.jsp的校验逻辑:

<% String username = request.getParameter("username"); String password = request.getParameter("password"); // 从数据库查admin_users表,获取salt和hash String salt = "a1b2c3d4"; // 实际应从数据库读取 String hash = DigestUtils.sha256Hex(password + salt); // 查询 SELECT * FROM admin_users WHERE username=? AND password=? if (isValidAdmin(username, hash)) { // 封装为独立方法 session.setAttribute("admin", "true"); response.sendRedirect("manage.jsp"); } %>

提示:Apache Commons Codec库提供DigestUtils.sha256Hex(),下载commons-codec-1.15.jar放入WEB-INF/lib/即可。别再用MD5,SHA-256是当前Java Web课设的安全底线

从那以后我每次拿到一个老课设源码,第一件事就是打开web.xmlservlet-mapping,第二件事是翻DBBean.java看数据库连接方式,第三件事是用curl -v测通第一个接口——这三步走完,80%的“跑不起来”问题当场定位。这份投票系统不是终点,它是你理解Servlet生命周期、JDBC连接本质、以及Web安全边界的活体标本。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询