简介:这是一套基于 JSP + Servlet 的 Web 成绩管理系统,面向计算机相关专业学生、Java Web 课程设计人群和需要快速搭建教务管理原型的开发者。系统包含学生、教师、管理员三大模块:学生可查询个人信息与本人成绩,教师可录入、修改、查看和分析成绩,管理员可管理学生、教师、班级、课程并完成排课与用户维护。压缩包共 870 个文件,约 39.64MB,以 java、jsp 源码为主,辅以大量 js、css 与图片资源,内含完整页面样式、前端交互和可导入的 sql 数据库脚本,xls/xlsx 兼顾成绩数据导出场景,整体目录结构较清晰。已有 173 人学习下载。拿到后可直接对照源码和数据库脚本部署,理解 JSP + Servlet 的分层请求处理流程,也可在课程设计或期末项目中直接复用或二次开发,省去从零搭建基础框架的时间。
1. 成绩管理系统:课程设计里绕不开的 Java Web 综合项目
成绩管理系统是 Java Web 课程设计里出现频率最高的题目之一,它不像电商、论坛那么花哨,但恰好把 JSP、Servlet、MySQL、Session、权限控制、增删改查串成一条完整的线。这份编号 100010041 的项目以 JSP + Servlet 为主干,MySQL 存数据,前端用 Bootstrap 搭界面,还自带 Summernote 富文本编辑器。系统按角色拆成学生、教师、管理员三个模块:学生只能查个人信息和成绩,教师负责成绩录入、修改、分析,管理员掌握学生、教师、班级、课程的全部增删改查,外加排课和分配班级。
它解决的是学校里"成绩数据谁录入、谁查看、谁管理"的问题,权限边界清晰、数据链路完整。适合做课程设计需要参考的在校生,也适合想复盘完整 Java Web 流程的初级开发者。下面按架构、部署、代码走读、排错、进阶五步拆开讲。
2. 项目骨架:JSP + Servlet + MySQL 的请求流转与数据表设计
2.1 从请求到响应:为什么业务逻辑要放在 Servlet 而不是 JSP 里
先看技术选型。JSP 负责页面展示,Servlet 负责接收请求和处理业务,MySQL 负责持久化,这是 Java Web 最经典的三层组合。早期项目常有人把业务代码直接写进 JSP 的<% %>里,页面又乱又难维护;这个项目的做法是让 JSP 只做显示,Servlet 通过doGet/doPost接收前端提交的参数,调用 DAO 层访问数据库,再把结果放进 request 或 session 作用域,转发回 JSP 渲染。
一个典型请求的流转过程是:浏览器提交表单 → Tomcat 根据 web.xml 里的映射找到对应的 Servlet → Servlet 拿到参数、校验角色权限、调用 DAO 查询 → 把结果 setAttribute 进 request → 用request.getRequestDispatcher("xxx.jsp").forward()跳转 → JSP 用 EL 表达式和 JSTL 把数据渲染出来。这里的 forward 是服务器内部跳转,地址栏不变;如果做的是新增、修改这类写操作,需要改用 sendRedirect 重定向,避免用户刷新页面时重复提交表单。
Servlet 和 URL 的对应关系在 web.xml 里配置,像这样:
<servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>controller.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping>也可以直接用@WebServlet("/login")注解代替,项目用哪种取决于它的 Servlet 版本。web.xml 配url-pattern时要注意以/开头,写成login这种不带斜杠的形式,启动时不会报错,但请求永远匹配不上,全是 404。
项目结构上,src 下一般是 controller 包放 Servlet,dao 包放数据库访问,entity 或 bean 包放实体类,web 目录下是 JSP 页面和静态资源。bootstrap.min.css、style.min.css、summernote-bs3.css 这些静态文件在资源目录里,说明界面不需要从零写样式,重点是业务逻辑本身。
2.2 六张核心表:学生、教师、班级、课程、成绩如何关联
数据库是这套系统的基础。核心表有六张:学生表 student、教师表 teacher、管理员表 admin、班级表 class、课程表 course、成绩表 score。班级和学生是一对多,课程和教师是多对一,成绩表通过 student_id 和 course_id 两个外键把学生和课程关联起来,这是典型的学生选课成绩模型。
建表 SQL 的关键部分长这样:
CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) UNIQUE NOT NULL COMMENT '学号', name VARCHAR(50) NOT NULL, password VARCHAR(64) NOT NULL, class_id INT, CONSTRAINT fk_student_class FOREIGN KEY (class_id) REFERENCES class(id) ); CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2), exam_type VARCHAR(20) COMMENT '平时/期中/期末', UNIQUE KEY uk_stu_course (student_id, course_id, exam_type), CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES student(id), CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course(id) );成绩表里建议保留 exam_type 字段,把平时、期中、期末分开存,而不是只存一个最终总评。这样教师在"成绩分析"模块里可以分别统计平时分和期末分的平均分、及格率,答辩时也更有东西可讲。唯一键 (student_id, course_id, exam_type) 能防止同一门课同一个学生重复录入成绩,这是数据多录的第一道防线。
外键在课程设计里应该建,但要注意:如果后期要批量导入成绩,外键约束会让导入顺序变得苛刻,必须先导学生、课程,再导成绩。我一般会先正常建外键保证数据一致性,等到要做批量导入时再用SET FOREIGN_KEY_CHECKS=0临时关闭检查,导入完再打开。这样既不影响平时的插入操作,也不会在导数据时翻车。
2.3 三层权限:Session 里的 userType 与 Filter 拦截
三个模块对应三种身份,权限控制是这套系统的核心。登录成功后,Servlet 会把当前用户信息放进 session,最关键的是一个角色标识,比如 userType,值可能是 1(管理员)、2(教师)、3(学生)。后续每个 Servlet 在处理业务前先检查 session 里的 userType,不匹配就拒绝或重定向到登录页。
如果项目里配了 Filter,权限检查会更集中,一个过滤器拦下所有请求:
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; HttpSession session = request.getSession(false); String uri = request.getRequestURI(); if (session == null || session.getAttribute("userType") == null) { if (uri.endsWith("login.jsp") || uri.contains("/login") || uri.contains("/static/")) { chain.doFilter(req, resp); } else { response.sendRedirect(request.getContextPath() + "/login.jsp"); } return; } chain.doFilter(req, resp); }这段代码的要点是白名单机制:login.jsp、登录 Servlet、静态资源目录放行,其余请求一律检查登录态。request.getSession(false)加了个 false,表示如果没有 session 就返回 null 而不是新建一个,避免恶意请求大量创建空 session 消耗内存。静态资源路径不要写死成/static/,要看你项目里 CSS、JS 实际放在哪个目录,否则会出现页面 HTML 正常但样式全丢的问题。
这里有个容易被忽略的点:session 里建议只放 id、name、userType 这几个必要字段,不要放整个实体对象。放整个对象虽然在页面上取值方便,但 session 存在服务器内存里,并发一高内存压力会很明显,而且数据库记录更新后 session 里的旧对象不会自动同步。
3. 从压缩包到跑起来:本地部署的完整步骤与参数说明
3.1 环境清单:JDK、Tomcat、MySQL 的版本搭配
这类 JSP + Servlet 项目对版本不挑剔,但版本搭错会浪费大量时间。我常用的组合是 JDK 8 + Tomcat 8.5 或 9 + MySQL 5.7(或 8.0),IDE 用 IntelliJ IDEA 社区版就够。JDK 8 是这类老项目的舒适区,Tomcat 9 对应 Servlet 4.0 规范,兼容性最好;如果项目里用了较老的第三方依赖,降回 Tomcat 8.5 更稳。
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 | 老项目最容易兼容的版本 |
| Tomcat | 8.5 / 9.0 | Servlet 容器,9 对应规范更新 |
| MySQL | 5.7 / 8.0 | 8.0 需要配好驱动和时区 |
| IDE | IDEA / Eclipse | 导入 Web 项目的方式不同 |
MySQL 8.0 的话,JDBC 驱动类要换成com.mysql.cj.jdbc.Driver,连接串里必须带serverTimezone=Asia/Shanghai,否则启动时一访问数据库就报时区错误。这是从 5.7 升到 8.0 最常见的坑,后面排查章节细说。
3.2 导入项目:IDEA 里的 Web 项目配置要点
解压压缩包后先把目录结构看清楚。如果里面能直接看到 src 和 web(或 WebContent)目录,这是标准 Web 项目结构。IDEA 里建议选"导入项目",选择 Eclipse 格式导入,而不是新建空项目再手动拖文件。打开后需要配三处:Project SDK 选 1.8,Language level 选 8,然后在 Project Structure → Modules 里把 src 标记为 Sources,把 web 目录标记为 Web Resources,指定 web.xml 的位置。
配完模块后,还要在 Artifacts 里加一个 Web Exploded 或 WAR 包。IDEA 的 Create Artifact 按钮通常会自动识别,但默认的 output 目录有时不对,需要确认它指向模块对应的 target 或 out 目录。这一步决定后面部署到 Tomcat 时,编译后的 class 和 JSP 能不能正确打包。这里我踩过一次:Artifact 里没勾选"Build on make",代码改了之后 Tomcat 跑的永远是旧的 class,排查了半天才发现是构建配置的问题。
3.3 建库建表:SQL 脚本执行与字符集设置
项目里一般会带一个 .sql 文件,比如 score_management.sql。先用命令行或可视化工具登录 MySQL,建库,再执行脚本:
mysql -u root -p CREATE DATABASE IF NOT EXISTS score_management DEFAULT CHARACTER SET utf8mb4; USE score_management; SOURCE /path/to/score_management.sql;执行完用SHOW TABLES;确认表都建全了。注意 SOURCE 命令对路径解析很敏感,路径里不要有中文和空格,否则会报语法错误,其实是路径问题。数据库字符集建议用 utf8mb4,它比 utf8 多支持 emoji 和生僻字,学生姓名、课程名里有特殊字符时不会变成乱码。
建完库之后,找到项目里的数据库连接配置,通常是 db.properties,或者 JDBC 工具类里的常量,把 url、username、password 改成你自己的。这一步很多人漏掉,导致页面能打开但一查数据就报连接拒绝。
提示:如果项目自带的 SQL 脚本里已经写了 USE 语句,你手动建库后直接执行即可;如果没写,要先切到目标库再 SOURCE。
3.4 Tomcat 部署:IDEA 调试运行与 WAR 包独立部署
调试阶段用 IDEA 内置的 Tomcat 集成:Run → Edit Configurations → 添加 Tomcat Server → Local,选择本地 Tomcat 安装目录,Deployment 标签里把 Artifact 加进去,Application context 建议设为/score,保持和 JSP 里的链接一致。启动后访问http://localhost:8080/score/login.jsp或项目里配置的欢迎页。
如果要做独立部署,可以把 Artifact 构建成 WAR 包,拷到 Tomcat 的 webapps 目录下,启动 Tomcat 后它会自动解压部署。注意上下文路径就是 WAR 包的文件名,所以包名决定了访问路径。改端口去 conf/server.xml 里改<Connector port="8080">,这个端口和 JDBC 连接串里的数据库端口是两回事,别改成一样。
4. 三个角色的功能落点:登录、成绩录入与统计的代码走读
4.1 登录与角色分发:一个 LoginServlet 处理三种身份
登录是所有功能的前置。项目里一般只有一个 LoginServlet,接收用户名、密码、角色三个参数,去对应的表里查询。核心设计是"一个 session 存三种身份":登录成功后向 session 写入 userId、userName、userType,然后按 userType 重定向到不同的主页。
String userType = request.getParameter("userType"); String username = request.getParameter("username"); String password = request.getParameter("password"); UserDao dao = new UserDao(); User user = dao.login(username, password, userType); if (user == null) { request.setAttribute("error", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); return; } HttpSession session = request.getSession(); session.setAttribute("userId", user.getId()); session.setAttribute("userName", user.getName()); session.setAttribute("userType", userType); response.sendRedirect(userType + "_index.jsp");login 方法里的 SQL 是SELECT * FROM xxx WHERE username=? AND password=?,这里必须用 PreparedStatement 占位符,不能字符串拼接,否则 SQL 注入一打一个准。密码字段如果项目是明文存储,课程设计阶段可以接受,但答辩时主动说一句"真实项目应该用 MD5 加盐或 BCrypt",是很自然的加分点。登录成功用 sendRedirect 而不是 forward,是为了让地址栏变成目标主页,防止刷新时重复提交登录表单。
4.2 成绩录入与修改:后端校验和更新边界
教师模块的成绩录入是这个项目里业务流程最完整的部分。录入页面的表单包含学生下拉框、课程、考试类型、分数,提交到 ScoreAddServlet。分数校验分两层:前端用 required 和数字范围保证基本输入,后端再解析一次,防止绕过前端直接 POST 非法数据。
String scoreStr = request.getParameter("score"); if (scoreStr == null || scoreStr.trim().isEmpty()) { request.setAttribute("error", "分数不能为空"); request.getRequestDispatcher("score_add.jsp").forward(request, response); return; } double score = Double.parseDouble(scoreStr); if (score < 0 || score > 100) { request.setAttribute("error", "分数必须在 0-100 之间"); request.getRequestDispatcher("score_add.jsp").forward(request, response); return; } ScoreDao dao = new ScoreDao(); int result = dao.insertScore(studentId, courseId, examType, score);Double.parseDouble会抛 NumberFormatException,如果前端有人传了非数字字符串,这里会直接 500。稳妥做法是用 try-catch 包一下,或者用正则先判断。修改成绩时,SQL 更新条件要带成绩记录的主键 id,不能只按 studentId 和 courseId 更新,否则会覆盖同一学生同一课程不同考试类型的记录。更新后最好把受影响行数返回给页面,提示"修改成功"或"记录不存在"。
4.3 成绩统计分析:一条 SQL 算出平均分和及格率
成绩分析模块是教师功能里最能体现 SQL 水平的环节。典型需求是:查看某门课程所有学生的成绩,并统计平均分、最高分、最低分、及格率。推荐直接在 DAO 里写聚合 SQL,数据量大时不会卡页面。
SELECT c.course_name, COUNT(s.id) AS total_count, ROUND(AVG(s.score), 2) AS avg_score, MAX(s.score) AS max_score, MIN(s.score) AS min_score, ROUND(SUM(CASE WHEN s.score >= 60 THEN 1 ELSE 0 END) / COUNT(s.id) * 100, 2) AS pass_rate FROM score s JOIN course c ON s.course_id = c.id WHERE s.course_id = ? GROUP BY s.course_id;及格率用SUM(CASE WHEN ...) / COUNT计算,比查出全部记录再在 Java 里循环判断干净得多。ROUND 保留两位小数,避免页面上显示一长串浮点数。注意 GROUP BY 的字段:MySQL 5.7 以上开启了 ONLY_FULL_GROUP_BY 模式,SELECT 里的非聚合列必须出现在 GROUP BY 中,否则会直接报语法错误。分析结果用表格展示,如果想更直观,用 Bootstrap 的进度条或纯 CSS 画几个分数段条形图就行,不需要引入图表库。
4.4 前端部分:Bootstrap 表格与 Summernote 富文本的安全细节
项目的静态资源里有 bootstrap.min.css、style.min.css 和 summernote-bs3.css,说明界面是基于 Bootstrap 3 搭建的。Bootstrap 负责表格样式、按钮、表单和分页组件,让 JSP 页面在几乎零手写 CSS 的情况下也像模像样。Summernote 是轻量富文本编辑器,在这类系统里一般用来发布公告或编辑通知,它在 textarea 上初始化,提交时把带 HTML 标签的内容通过表单 POST 给后端。
用 Summernote 要留意:提交值包含完整 HTML,后端如果原样存、原样输出,会有 XSS 风险。课程设计项目通常没人恶意攻击,但答辩时能主动说出"富文本内容需要过滤 script 标签",会让人明显觉得你理解安全边界。最简单的过滤是在存储前把<script>相关标签替换掉,或者展示时用<c:out>转义。
5. 排查专章:JSP + Servlet 项目最常见的四个翻车现场
5.1 404 与静态资源加载失败:Context Path 和目录引用不一致
现象:Tomcat 启动正常,但访问http://localhost:8080/score/login.jsp返回 404,或者页面 HTML 出来了,bootstrap.min.css、summernote-bs3.css 全部加载失败,页面光秃秃的没有样式。
原因:第一种是 Application context 配置和实际访问路径不一致,比如 context 配成了/score,但 JSP 里的链接写的是/xxx/login.jsp;第二种是 JSP 里引用了css/bootstrap.min.css这类相对路径,但当前页面在子目录下,浏览器按相对路径解析后找不到文件。
解决:统一约定 context 路径,IDEA 的 Deployment 标签里配好之后不要反复改;JSP 里的静态资源引用全部用${pageContext.request.contextPath}拼绝对路径,例如<link href="${pageContext.request.contextPath}/static/css/bootstrap.min.css">。改完配置后 Clean 一次,清理 out 目录重新构建再重启,很多"改了没生效"其实是构建缓存。
5.2 中文乱码:请求、响应、数据库三处都要设置 UTF-8
现象:页面显示中文变成问号,或者往数据库插入中文后查出来是???,更隐蔽的是 GET 请求带中文参数时乱码。
原因:三层叠加。JSP 页面没设置<%@ page contentType="text/html;charset=UTF-8" %>;Servlet 读 request 参数时没设置编码;JDBC 连接串没指定 characterEncoding;或者数据库本身字符集不是 utf8mb4。
解决:三处一起改。JSP 顶部统一加 contentType 声明;每个 Servlet 的 doPost 第一行写request.setCharacterEncoding("UTF-8");JDBC 连接串加?useUnicode=true&characterEncoding=utf8;建库用 utf8mb4,已经建错的用ALTER DATABASE score_management CHARACTER SET utf8mb4;修补。Tomcat 8.5 以上 GET 请求 URI 默认按 UTF-8 解码,不用额外配置;老项目的 Tomcat 7 需要在 server.xml 里给 Connector 加 URIEncoding="UTF-8"。
5.3 数据库连接报错:驱动类、时区与密码三座大山
现象:页面能打开,一点登录按钮就报Cannot create JDBC driver of class,或者The server time zone value ... is unrecognized,又或者Access denied for user。
原因:第一种是 MySQL 8.0 用了旧的com.mysql.jdbc.Driver,或者驱动 jar 没放进 WEB-INF/lib;第二种是 8.0 连接串缺少 serverTimezone 参数;第三种最基础也最常犯,db.properties 里的密码和本机 MySQL 实际密码不一样。
解决:确认 mysql-connector-java.jar 在 WEB-INF/lib 下且版本匹配;MySQL 8.0 用com.mysql.cj.jdbc.Driver,连接串补serverTimezone=Asia/Shanghai&useSSL=false;密码逐个字符核对,注意 properties 文件里密码如果包含#或空格会被截断,需要转义。这类纯 JDBC 项目不必上连接池,把每次查询的 Connection、Statement、ResultSet 在 finally 里正确关闭才是重点,不然跑几十次就连接耗尽。
5.4 Session 失效与权限绕过:服务端校验不能省
现象:长时间不操作后再点功能会跳回登录页,这是 Session 默认 30 分钟过期,属于正常;但更严重的是,用户直接在地址栏输入某个管理页 URL,不登录也能打开。
原因:部分页面只做了前端隐藏入口,没在服务端鉴权。JSP 是服务器动态渲染的,直接访问 URL 时如果请求没经过任何 Servlet 或 Filter 检查,Tomcat 会照常渲染输出。Filter 如果 mapping 配的是/xxx而不是/*,会漏掉一部分路径。
解决:过滤器用/*拦截全部请求,白名单放行登录页、登录接口、静态资源目录(参考 2.3 节代码);同时每个写操作的 Servlet 开头再校验一次 userType。两层保险看着冗余,但正是这个冗余让项目在答辩时经得起追问。Session 过期时间在 web.xml 里配<session-config><session-timeout>30</session-timeout></session-config>,按需调整。
6. 验收与进阶:把课程设计变成能讲清楚的作品
6.1 按角色走验收清单:从建班到查成绩的完整链路
项目跑起来后,建议按下面的清单过一遍,而不是打开页面看一眼就说"好了"。管理员登录后先建班级、建教师、建课程、建学生,再给学生分配班级;换教师账号登录,录一门成绩,修改其中一条,查看统计结果;最后换学生账号登录,确认能看到自己的成绩、不能看到别人的。
| 验收点 | 操作 | 预期结果 |
|---|---|---|
| 管理员建班建课 | 新增班级、课程、教师 | 列表出现新记录 |
| 分配班级 | 把学生挂到班级下 | 学生详情显示班级 |
| 教师录入成绩 | 选择学生、课程、考试类型 | 成绩保存成功 |
| 教师修改成绩 | 改一条分数 | 更新后统计值变化 |
| 学生查成绩 | 登录学生账号 | 只看到本人成绩 |
| 越权访问 | 学生账号直接访问教师 URL | 被拦截或跳登录页 |
这个清单能把三个模块的权限边界完整验证一遍。特别要测的是越权路径:学生账号直接访问教师功能 URL 能不能进,教师账号访问管理员页面会不会被拦。如果都能进,说明鉴权有漏洞,回到 5.4 节把 Filter 补上。
6.2 三个低成本改造方向:连接池、操作日志、密码加密
想把项目从"能跑的课程设计"提升到"能讲的工程作品",推荐三个性价比高的改造。第一,把 DAO 里的裸 JDBC 换成 Druid 连接池,代码改动不大,但能解释清楚"为什么不用每次新建连接";第二,给登录和成绩修改加日志,写一个 LogDao 把操作人、操作时间、操作内容落表;第三,把明文密码改成 MD5 加盐存储,登录时对输入做同样处理再比对。
第二个改造最容易讲出彩。教师修改成绩时,先查出修改前的值,和修改后的值一起插入 log 表。答辩时这条链路能讲五分钟:从页面表单,到 Servlet 校验,到 DAO 更新,再到日志落库,一条完整的数据流。面试官对"可追溯"这个点的兴趣,远大于对页面样式的兴趣。
6.3 演示顺序与答辩技巧:围绕一条业务链路讲故事
演示项目时,我习惯按"登录 → 鉴权 → 一次完整业务"的顺序走,而不是按模块顺序念功能。先打开 LoginServlet 讲参数怎么接收、session 怎么存角色,再带着 Filter 讲权限拦截,最后选成绩录入这条链路,把 JSP 表单 → Servlet 校验 → DAO 插入 → 页面回显串起来。这样听的人会形成"请求怎么进来、数据怎么流转"的完整认知,而不是记流水账。
Java Web 项目的价值不在技术新,而在于你能把一条链路上的每个环节讲清楚。从那以后,我每次拿到这类 JSP + Servlet 项目,都会强制自己先走一遍验收清单再动代码——部署路径、权限漏洞、中文乱码这三类坑,九成出在没做完整流程验证。这套习惯帮我省下了大量返工时间,希望也能帮到你。
本文还有配套的精品资源,点击获取