简介:本资源是一套基于Java Web技术栈开发的毕业设计选题管理系统,面向高校计算机类专业师生,解决毕业设计课题发布、分配与过程管理中的多角色协同难题。系统采用JSP+Servlet+MySQL架构,部署于Tomcat环境,完整实现管理员、教师、学生三类角色的权限隔离与核心业务流程:管理员可维护用户与课题信息并执行系统维护;教师可发布及查看课题;学生可浏览并自主选题。资源包共221个文件,含98个JSP页面(实现前后端交互)、58个GIF图标(界面资源)、24个Class字节码(编译后的业务逻辑)、12个Java源码(含TopicManager、TeacherManager等核心管理类)、7个XML配置文件及SQL初始化脚本等,整体压缩后仅5.57MB,结构清晰、模块分明。已有2416人学习下载,提供开箱即用的完整工程、可直接运行的数据库脚本及典型Web分层架构实践范例,适合Java Web初学者巩固MVC开发流程,也便于课程设计或毕设快速原型搭建。
1. 为什么用 Java+JSP+MySQL 做毕业设计选题系统,不是“过时”,而是稳扎稳打的工程选择
你手头正赶着计算机专业本科毕业设计,导师说“必须是 Web 系统”,同学在卷 Spring Boot + Vue,而你翻到一份写着「Java+JSP+MySQL」的毕设模板——第一反应可能是:这玩意儿是不是该进博物馆了?
但现实是:全国高校计算机类毕设答辩现场,每年仍有超 65% 的 Web 类选题系统(教务选课、论文管理、实验室预约、实习双选)跑在 Tomcat 9 + JSP + MySQL 5.7/8.0 这套组合上。它不炫技,但胜在零外部依赖、单机可部署、调试链路透明、答辩老师一眼看懂架构。尤其对没接触过微服务、没配过 Nginx 反向代理、连mvn clean install都要查三次命令的新手,这套技术栈意味着:不用猜 jar 包冲突、不用调跨域、不用啃 YAML 配置玄学、数据库报错直接贴堆栈就能定位到 SQL 拼写。它解决的不是“高并发秒杀”,而是“300 个学生同时刷选题页面不卡死、管理员改一个状态字段立刻生效、导出 Excel 表格格式不乱码”这种真实、琐碎、但答辩必问的落地问题。如果你的目标是两周搭出可演示、可截图、可写进论文第三章系统设计的完整闭环系统,而不是花三周调通 Spring Security 的权限拦截器——那这套组合不是退而求其次,而是精准卡点。
2. 从零建起:用最简路径跑通选题系统骨架
2.1 环境准备:只装这 4 个东西,别碰任何“一键安装包”
提示:所有工具必须用稳定长期支持版(LTS),拒绝最新版。MySQL 用 5.7.44(兼容性最好),Tomcat 用 9.0.83(JSP 2.3 兼容无坑),JDK 用 1.8.0_391(不是 17,不是 21)。这些版本在各大高校机房、答辩演示机上预装率最高,避免答辩当天因 JDK 版本报
Unsupported major.minor version直接翻车。
- JDK 1.8:官网下载
jdk-8u391-windows-x64.exe,安装后配置JAVA_HOME指向C:\Program Files\Java\jdk1.8.0_391,PATH加%JAVA_HOME%\bin - Tomcat 9.0.83:解压到
D:\tomcat9,不要放 C 盘 Program Files 下(Windows 权限坑多),启动bin\startup.bat,浏览器访问http://localhost:8080出现猫图标即成功 - MySQL 5.7.44:用官方 MSI 安装器,安装时勾选 “Add to PATH” 和 “Configure MySQL as a service”,root 密码设为
123456(答辩演示用,非生产环境) - Eclipse IDE for Enterprise Developers(2023-09 版):不是 VS Code,不是 IntelliJ,是 Eclipse。原因:内置 Tomcat 插件成熟、JSP 编辑器语法高亮准、Servlet 向导一键生成、Debug 时变量值实时可见——这对新手 debug
request.getParameter("title")返回 null 这种问题,效率提升 3 倍
验证命令行:
java -version # 必须输出 java version "1.8.0_391" mysql -uroot -p123456 -e "SELECT VERSION();" # 必须输出 5.7.442.2 数据库建模:3 张表撑起核心业务,拒绝过度设计
选题系统本质是“多对多关系管理”:学生选题、教师出题、管理员审核。别一上来就建 10 张表、加外键级联、搞视图存储过程。先跑通主干,再迭代。以下是答辩通过率最高的最小可行结构(SQL 直接复制执行):
-- 创建数据库(注意字符集!答辩机常因 utf8mb4 导致中文乱码) CREATE DATABASE thesis_selection CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE thesis_selection; -- 教师表:存工号、姓名、职称、可带学生数上限 CREATE TABLE teacher ( id INT PRIMARY KEY AUTO_INCREMENT, emp_id VARCHAR(20) UNIQUE NOT NULL, -- 工号,登录账号 name VARCHAR(20) NOT NULL, title VARCHAR(10) DEFAULT '讲师', -- 职称 max_students INT DEFAULT 5 -- 最多带几个学生 ); -- 学生表:存学号、姓名、专业、年级、已选题目ID(冗余字段,加速查询) CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, stu_id VARCHAR(12) UNIQUE NOT NULL, -- 学号,登录账号 name VARCHAR(20) NOT NULL, major VARCHAR(30) DEFAULT '计算机科学与技术', grade YEAR DEFAULT 2024, -- 入学年份,用于筛选应届生 selected_topic_id INT DEFAULT 0 -- 当前已选题目ID,0=未选 ); -- 题目表:存题目ID、标题、描述、指导教师ID、状态(0=待审核,1=已发布,2=已选满) CREATE TABLE topic ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, description TEXT, teacher_id INT NOT NULL, status TINYINT DEFAULT 0, -- 0:待审核 1:已发布 2:已选满 created_time DATETIME DEFAULT NOW(), FOREIGN KEY (teacher_id) REFERENCES teacher(id) );参数说明:
utf8mb4是必须项,否则 JSP 页面提交中文标题会变???;selected_topic_id在 student 表中冗余存储,省去每次查选题状态都要 JOIN topic 表,降低答辩演示时页面加载延迟;status用TINYINT而非ENUM,因为 MySQL 5.7 对 ENUM 排序和 JDBC 读取有兼容性问题,答辩老师用 Navicat 查数据时可能显示异常。
2.3 项目结构:Eclipse 里手动建 Web Project,不走 Maven
为什么不用 Maven?因为毕设答辩环境大概率没配 Maven 镜像源,
pom.xml里一个spring-webmvc依赖下载失败,整个项目红叉,你当场崩溃。手动管理 jar 包反而可控。
在 Eclipse 中:
File → New → Dynamic Web Project- 项目名填
ThesisSelectionSystem,Target runtime 选你装好的 Tomcat 9 - 点击
Finish后,右键项目 →Properties → Deployment Assembly,确认WebContent映射到/ - 手动创建目录结构:
ThesisSelectionSystem/ ├── WebContent/ │ ├── index.jsp ← 首页(登录入口) │ ├── login.jsp ← 登录页(区分学生/教师/管理员) │ ├── student/ ← 学生功能页 │ │ ├── dashboard.jsp ← 学生首页,显示可选题目列表 │ │ └── select.jsp ← 选题提交页 │ ├── teacher/ ← 教师功能页 │ │ ├── add_topic.jsp ← 发布题目页 │ │ └── manage.jsp ← 管理自己题目页 │ └── admin/ ← 管理员页(简单审核) │ └── audit.jsp ├── WEB-INF/ │ ├── web.xml ← 核心配置,定义 Servlet 和欢迎页 │ └── lib/ ← 手动放 jar 包的地方 └── src/ ← Java 类放这里
关键 jar 包(全部从 Tomcat 自带或 MySQL 官网下载,不搜第三方):
servlet-api.jar:从D:\tomcat9\lib\复制过来(不要删,这是编译 JSP 必需)mysql-connector-java-5.1.47.jar:MySQL 官网下载的 5.1.x 版(不是 8.0.x!JSP 用 JDBC 4.0,5.1.47 兼容性最好)jstl-1.2.jar:用于<c:if>等标签,从 Jakarta 官网下载
血泪经验:
mysql-connector-java-8.0.33.jar在 JSP 中Class.forName("com.mysql.cj.jdbc.Driver")会报ClassNotFoundException,因为驱动类名变了,而老式 JSP 写法没适配。用 5.1.47,一行代码搞定:Class.forName("com.mysql.jdbc.Driver");
3. 核心功能实现:3 个 Servlet + 5 个 JSP,覆盖全部答辩场景
3.1 登录控制:用 Filter 拦截未登录请求,比 Session 判断更可靠
很多同学用session.getAttribute("user") == null做登录校验,结果在多标签页切换时 session 丢失,页面空白。用 Filter 是更底层、更稳定的方案。
新建src/filter/LoginFilter.java:
package filter; import javax.servlet.*; import javax.servlet.http.*; import java.io.IOException; public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; // 放行静态资源和登录页 String uri = request.getRequestURI(); if (uri.contains("/login.jsp") || uri.contains("/css/") || uri.contains("/js/") || uri.contains("/images/")) { chain.doFilter(req, resp); return; } // 检查 session 中是否有 user 属性(登录后存的) HttpSession session = request.getSession(false); if (session == null || session.getAttribute("user") == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } chain.doFilter(req, resp); } @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void destroy() {} }在WEB-INF/web.xml中注册(必须放在<servlet>标签之前):
<filter> <filter-name>LoginFilter</filter-name> <filter-class>filter.LoginFilter</filter-class> </filter> <filter-mapping> <filter-name>LoginFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>逻辑说明:Filter 在请求到达 Servlet 前执行,比在每个 JSP 顶部写
<% if(session.getAttribute("user")==null) response.sendRedirect("login.jsp"); %>更干净。它统一拦截所有请求,且对/student/dashboard.jsp和/teacher/add_topic.jsp一视同仁,避免漏掉某个页面导致未登录也能访问。
3.2 学生选题:JSP 表单 + Servlet 处理 + JDBC 更新,三步闭环
student/dashboard.jsp显示可选题目列表(status=1 且未满):
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head><title>可选题目</title></head> <body> <h2>请选择毕业设计题目</h2> <table border="1"> <tr><th>题目编号</th><th>题目名称</th><th>指导教师</th><th>操作</th></tr> <c:forEach items="${topicList}" var="t"> <tr> <td>${t.id}</td> <td>${t.title}</td> <td>${t.teacherName}</td> <td><a href="SelectTopicServlet?id=${t.id}">选择</a></td> </tr> </c:forEach> </table> </body> </html>src/servlet/SelectTopicServlet.java处理点击事件:
package servlet; import dao.TopicDao; import dao.StudentDao; import model.Student; import javax.servlet.ServletException; import javax.servlet.http.*; import java.io.IOException; public class SelectTopicServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int topicId = Integer.parseInt(request.getParameter("id")); Student student = (Student) request.getSession().getAttribute("user"); // 1. 检查该题目是否可选(状态=1,且未满) if (!new TopicDao().canSelect(topicId)) { request.setAttribute("msg", "该题目暂不可选,请刷新页面重试"); request.getRequestDispatcher("/student/dashboard.jsp").forward(request, response); return; } // 2. 检查学生是否已选题 if (student.getSelectedTopicId() != 0) { request.setAttribute("msg", "您已选择题目,不可重复选择"); request.getRequestDispatcher("/student/dashboard.jsp").forward(request, response); return; } // 3. 执行选题:更新 student 表 selected_topic_id,更新 topic 表选中人数(隐含在 canSelect 里) boolean success = new StudentDao().selectTopic(student.getId(), topicId); if (success) { // 刷新 session 中的学生对象 student.setSelectedTopicId(topicId); request.getSession().setAttribute("user", student); request.setAttribute("msg", "选题成功!"); } else { request.setAttribute("msg", "选题失败,请重试"); } request.getRequestDispatcher("/student/dashboard.jsp").forward(request, response); } }dao/StudentDao.java中的selectTopic()方法(关键事务控制):
public boolean selectTopic(int studentId, int topicId) { String sql = "UPDATE student SET selected_topic_id = ? WHERE id = ?"; String sql2 = "UPDATE topic SET status = 2 WHERE id = ? AND " + "(SELECT COUNT(*) FROM student WHERE selected_topic_id = ?) >= " + "(SELECT max_students FROM teacher WHERE id = (SELECT teacher_id FROM topic WHERE id = ?))"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps1 = conn.prepareStatement(sql); PreparedStatement ps2 = conn.prepareStatement(sql2)) { conn.setAutoCommit(false); // 开启事务 ps1.setInt(1, topicId); ps1.setInt(2, studentId); int rows1 = ps1.executeUpdate(); ps2.setInt(1, topicId); ps2.setInt(2, topicId); ps2.setInt(3, topicId); int rows2 = ps2.executeUpdate(); conn.commit(); // 两步都成功才提交 return rows1 > 0; } catch (SQLException e) { try { conn.rollback(); // 任一步失败回滚 } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } }参数说明:
conn.setAutoCommit(false)是防止“学生选上了但题目状态没更新”这种数据不一致;sql2中嵌套子查询判断是否已达教师带生上限,避免手动查再更新带来的并发问题(两个学生同时点同一个题目);DBUtil.getConnection()是封装好的工具类,确保每次获取连接都用utf8mb4字符集,代码略(需保证?useUnicode=true&characterEncoding=utf8mb4在 URL 中)。
3.3 教师发布题目:JSP 表单校验 + Servlet 参数清洗,防 SQL 注入
teacher/add_topic.jsp表单必须做前端校验(防误操作)+ 后端清洗(防注入):
<form action="AddTopicServlet" method="post"> <label>题目名称:<input type="text" name="title" required maxlength="100"></label><br> <label>题目描述:<textarea name="description" rows="4" cols="50" required></textarea></label><br> <input type="submit" value="发布题目"> </form>AddTopicServlet.java中的关键清洗逻辑:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // 防止中文乱码 String title = request.getParameter("title").trim(); String description = request.getParameter("description").trim(); // 【关键清洗】移除危险字符,保留中文、字母、数字、常见标点 title = title.replaceAll("[^\\u4e00-\\u9fa5a-zA-Z0-9\\s\\-\\_\\.,;:!?]", ""); description = description.replaceAll("[^\\u4e00-\\u9fa5a-zA-Z0-9\\s\\-\\_\\.,;:!?\\n\\r]", ""); // 长度校验 if (title.length() < 5 || title.length() > 100 || description.length() < 20) { request.setAttribute("msg", "题目名称需5-100字,描述需不少于20字"); request.getRequestDispatcher("/teacher/add_topic.jsp").forward(request, response); return; } Teacher teacher = (Teacher) request.getSession().getAttribute("user"); boolean success = new TopicDao().addTopic(title, description, teacher.getId()); if (success) { request.setAttribute("msg", "题目发布成功,等待管理员审核"); } else { request.setAttribute("msg", "发布失败,请重试"); } request.getRequestDispatcher("/teacher/add_topic.jsp").forward(request, response); }为什么不用 PreparedStatement 防注入?PreparedStatement 确实能防 SQL 注入,但 JSP 场景下用户输入还会渲染到页面(如题目描述展示),若没清洗
<script>alert(1)</script>,就会 XSS。所以清洗是双重保险:既防 DB 层注入,也防前端 XSS。此处正则[^\\u4e00-\\u9fa5a-zA-Z0-9\\s\\-\\_\\.,;:!?]保留中文、英文字母、数字、空格及安全标点,其他一律剔除——答辩演示时,哪怕学生输一堆乱码,页面也不会崩。
4. 避坑指南:答辩前必查的 5 个致命细节
4.1 现象:JSP 页面中文显示为??,数据库里却是正常中文
原因:JDBC 连接 URL 缺少useUnicode=true&characterEncoding=utf8mb4,或 Tomcat 的server.xml中 Connector 缺少URIEncoding="UTF-8"
解决:
DBUtil.java中连接字符串必须为:jdbc:mysql://localhost:3306/thesis_selection?useUnicode=true&characterEncoding=utf8mb4&useSSL=falseD:\tomcat9\conf\server.xml中找到<Connector port="8080",添加属性:URIEncoding="UTF-8"- JSP 文件顶部必须声明:
<%@ page contentType="text/html;charset=UTF-8" %>
4.2 现象:登录后跳转到index.jsp,但页面空白,控制台无报错
原因:web.xml中<welcome-file-list>未配置,或配置了index.html却没这个文件
解决:在web.xml的<web-app>标签下添加:
<welcome-file-list> <welcome-file>login.jsp</welcome-file> </welcome-file-list>注意:不要写index.jsp,因为你的首页就是login.jsp,避免多一层跳转。
4.3 现象:点击“选择题目”后报HTTP Status 404 – /ThesisSelectionSystem/SelectTopicServlet
原因:Servlet 没在web.xml中注册,或 URL 映射路径写错
解决:必须在web.xml中显式声明(JSP 时代不支持注解自动注册):
<servlet> <servlet-name>SelectTopicServlet</servlet-name> <servlet-class>servlet.SelectTopicServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>SelectTopicServlet</servlet-name> <url-pattern>/SelectTopicServlet</url-pattern> </servlet-mapping>血泪经验:路径大小写敏感!SelectTopicServlet和selecttopicservlet是两个东西。
4.4 现象:管理员审核题目后,学生页面不刷新,仍显示“待审核”
原因:JSP 页面缓存,浏览器读了旧 HTML,没发新请求
解决:在所有 JSP 页面<head>中加入强制不缓存头:
<% response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); response.setHeader("Pragma", "no-cache"); response.setDateHeader("Expires", 0); %>玄学补充:答辩演示前,用 Chrome 无痕窗口打开,避免本地缓存干扰。
4.5 现象:导出 Excel 功能报java.lang.NoClassDefFoundError: org/apache/poi/ss/usermodel/Workbook
原因:POI jar 包没放进WEB-INF/lib/,或版本冲突(用了 POI 5.x,但 JDK 1.8 不支持)
解决:
- 下载
poi-3.17.jar、poi-scratchpad-3.17.jar、poi-ooxml-3.17.jar(3.17 是 JDK 1.8 兼容的最后稳定版) - 全部放入
WEB-INF/lib/,不要只放 poi-3.17.jar,poi-ooxml依赖xmlbeans,必须一起放 - 代码中用
HSSFWorkbook(xls)而非XSSFWorkbook(xlsx),避免xmlbeans版本问题
5. 答辩加分技巧:3 个让老师眼前一亮的轻量优化
5.1 给 JSP 页面加“最后更新时间”,证明你真写了代码
很多同学交的系统,页面 footer 写着©2024,但老师一问“这个时间怎么来的”,答“手动改的”。其实只要一行 JSP 表达式:
<!-- 在所有 JSP 页面底部 --> <div style="font-size:12px; color:#999; text-align:center; margin-top:20px;"> 系统最后更新:<%= new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()) %> </div>为什么有效:它证明你理解 JSP 的表达式
<%= %>是运行时执行 Java 代码,不是静态文本。老师可能会追问“如果服务器时间不准怎么办”,你可以答:“实际项目中会对接 NTP 服务,毕设环境以本地时间为准”。
5.2 用 CSS 实现“选题成功”绿色弹窗,比 alert() 更专业
把原始的alert("选题成功")替换成内嵌样式弹窗,代码少、效果稳、不打断流程:
<!-- 在 dashboard.jsp 底部 --> <c:if test="${not empty msg}"> <style> .toast { position:fixed; top:20px; right:20px; background:#4CAF50; color:white; padding:12px 24px; border-radius:4px; z-index:9999; box-shadow:0 2px 10px rgba(0,0,0,0.2); } </style> <div class="toast">${msg}</div> <script> setTimeout(() => document.querySelector('.toast').remove(), 3000); </script> </c:if>参数说明:
z-index:9999确保弹窗盖在所有元素上;setTimeout3 秒后自动消失,避免遮挡按钮;box-shadow加阴影提升质感。这个技巧不需要额外 JS 库,纯原生,答辩时老师点选题,弹窗滑入,他点头说“嗯,交互做了”。
5.3 导出学生选题汇总 Excel:用 POI 写真实业务逻辑
别只导空表头。按答辩要求,老师常要看“各专业选题分布”、“教师带题数量统计”。用 POI 写一个真实报表:
// src/servlet/ExportReportServlet.java protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<Object[]> reportData = new ReportDao().getTopicDistribution(); // 返回 [专业, 题目数, 平均难度] HSSFWorkbook workbook = new HSSFWorkbook(); HSSFSheet sheet = workbook.createSheet("选题分布报表"); // 表头 HSSFRow headerRow = sheet.createRow(0); String[] headers = {"专业", "题目数量", "平均难度"}; for (int i = 0; i < headers.length; i++) { headerRow.createCell(i).setCellValue(headers[i]); } // 数据行 for (int i = 0; i < reportData.size(); i++) { HSSFRow row = sheet.createRow(i + 1); Object[] data = reportData.get(i); row.createCell(0).setCellValue((String) data[0]); row.createCell(1).setCellValue((Integer) data[1]); row.createCell(2).setCellValue((Double) data[2]); } // 设置列宽 sheet.setColumnWidth(0, 5000); sheet.setColumnWidth(1, 3000); sheet.setColumnWidth(2, 4000); response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment; filename=topic_report.xls"); workbook.write(response.getOutputStream()); }落地价值:这个功能在答辩 PPT 里放一张 Excel 截图,老师会认为“这学生真把系统当产品做了,不是 Demo”。而且代码里
HSSFWorkbook是 POI 3.17 的标准写法,不会因版本问题报错。
我带过 12 届毕设,最常看到的翻车是:学生花三周调通 Spring Cloud Gateway,结果答辩机没装 Docker,连镜像都拉不下来;而用这套 Java+JSP+MySQL 的,只要环境配对,Tomcat 一启,页面就出来,debug 时断点打在SelectTopicServlet第 15 行,变量值清清楚楚。它不教你最潮的架构,但它教会你:一个能跑、能讲、能改、能答的系统,才是毕业设计的本质。希望帮到你。
本文还有配套的精品资源,点击获取