
简介这是一套基于 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-nameLoginServlet/servlet-name servlet-classcontroller.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login/url-pattern /servlet-mapping也可以直接用WebServlet(/login)注解代替项目用哪种取决于它的 Servlet 版本。web.xml 配url-pattern时要注意以/开头写成login这种不带斜杠的形式启动时不会报错但请求永远匹配不上全是 404。项目结构上src 下一般是 controller 包放 Servletdao 包放数据库访问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_CHECKS0临时关闭检查导入完再打开。这样既不影响平时的插入操作也不会在导数据时翻车。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.0IDE 用 IntelliJ IDEA 社区版就够。JDK 8 是这类老项目的舒适区Tomcat 9 对应 Servlet 4.0 规范兼容性最好如果项目里用了较老的第三方依赖降回 Tomcat 8.5 更稳。组件推荐版本说明JDK1.8老项目最容易兼容的版本Tomcat8.5 / 9.0Servlet 容器9 对应规范更新MySQL5.7 / 8.08.0 需要配好驱动和时区IDEIDEA / Eclipse导入 Web 项目的方式不同MySQL 8.0 的话JDBC 驱动类要换成com.mysql.cj.jdbc.Driver连接串里必须带serverTimezoneAsia/Shanghai否则启动时一访问数据库就报时区错误。这是从 5.7 升到 8.0 最常见的坑后面排查章节细说。3.2 导入项目IDEA 里的 Web 项目配置要点解压压缩包后先把目录结构看清楚。如果里面能直接看到 src 和 web或 WebContent目录这是标准 Web 项目结构。IDEA 里建议选导入项目选择 Eclipse 格式导入而不是新建空项目再手动拖文件。打开后需要配三处Project SDK 选 1.8Language 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 port8080这个端口和 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 contentTypetext/html;charsetUTF-8 %Servlet 读 request 参数时没设置编码JDBC 连接串没指定 characterEncoding或者数据库本身字符集不是 utf8mb4。解决三处一起改。JSP 顶部统一加 contentType 声明每个 Servlet 的 doPost 第一行写request.setCharacterEncoding(UTF-8)JDBC 连接串加?useUnicodetruecharacterEncodingutf8建库用 utf8mb4已经建错的用ALTER DATABASE score_management CHARACTER SET utf8mb4;修补。Tomcat 8.5 以上 GET 请求 URI 默认按 UTF-8 解码不用额外配置老项目的 Tomcat 7 需要在 server.xml 里给 Connector 加 URIEncodingUTF-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连接串补serverTimezoneAsia/ShanghaiuseSSLfalse密码逐个字符核对注意 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-configsession-timeout30/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 项目都会强制自己先走一遍验收清单再动代码——部署路径、权限漏洞、中文乱码这三类坑九成出在没做完整流程验证。这套习惯帮我省下了大量返工时间希望也能帮到你。本文还有配套的精品资源点击获取