ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java Web学生信息管理系统设计与实现:从建表到权限控制与部署避坑

Java Web学生信息管理系统设计与实现:从建表到权限控制与部署避坑 简介一套基于Java技术的学生信息管理系统设计与实现资源面向高校计算机专业学生、程序设计初学者及需要完成课程设计或毕业设计的开发者提供从需求分析到技术落地的完整参考方案。资源详细覆盖登录界面的表单验证、后台请求处理、会话跟踪、页面数据展示与交互、数据库连接与操作、学生信息增删改查、数据表结构设计、注入攻击防护、密码加密存储与安全传输等核心模块清晰展示Java Web相关技术在真实项目中的分工与协作方式帮助读者快速理解Java网络应用开发的整体框架和实现细节。压缩包大小约1.21MB轻量便捷下载后即可对照学习。该资源已有3006人学习浏览适合备战实训项目、课程设计或希望从零搭建同类系统的开发者借鉴使用。1. 这个选题人人会做但大部分人卡在代码之外的地方「基于java web学生信息管理系统的设计与实现」几乎是 Java 方向课设和毕设里出场率最高的题目需求一句话就能说清做一个通过浏览器访问的系统让管理员管学生、老师录成绩、学生查成绩。可恰恰是这种看起来简单的 web 项目翻车率最高。我见过太多人第一天就开写代码结果卡在 Tomcat 起不来、MySQL 连不上、页面中文乱码这些跟业务逻辑毫无关系的地方一卡就是两三天。这篇笔记会带你从数据库设计、项目骨架、登录与 CRUD 落地一路走到部署排错全程按课设和毕设的真实节奏来新手能照做熟手能直接拿走避坑清单。2. 先建表再写代码五张表的结构、外键与三种角色权限这一章解决的是「到底要做哪些功能、建哪些表」的问题。学生信息管理系统听起来只有一张学生表但如果你要做登录和权限就必须把用户、教师、课程、成绩拆开。常见做法是起步先建一张 student 表跑通流程最后再回头补表我一般建议反过来先把表和角色定死代码写起来会顺很多。2.1 从一张学生表到五张表字段类型与建表 SQL最开始的需求通常只有「增删改查学生」但加上「不同身份登录后看到不同页面」这个课设基本要求后表结构就会自然膨胀。一个能支撑答辩的最小设计是五张表用户表、学生表、教师表、课程表、成绩表。用户表用来存登录账号和角色学生表和教师表存业务资料课程表存开课信息成绩表把学生、课程和分数关联起来。这样设计的好处是登录认证只查 user 表业务查询才去关联具体资料权限判定简单数据库查询也干净。CREATE DATABASE student_db DEFAULT CHARSET utf8mb4; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role VARCHAR(20) NOT NULL COMMENT admin / teacher / student, real_name VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, sno VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL, gender VARCHAR(10), birth_date DATE, phone VARCHAR(20), email VARCHAR(100), class_name VARCHAR(50) COMMENT 班级, user_id INT COMMENT 关联登录账号 ); CREATE TABLE teacher ( id INT PRIMARY KEY AUTO_INCREMENT, tno VARCHAR(20) NOT NULL UNIQUE COMMENT 工号, name VARCHAR(50) NOT NULL, dept_name VARCHAR(50) COMMENT 院系, title VARCHAR(50) COMMENT 职称, user_id INT ); CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, cno VARCHAR(20) NOT NULL UNIQUE, cname VARCHAR(100) NOT NULL, credit DECIMAL(3,1), teacher_id INT COMMENT 授课教师 ); CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,1), semester VARCHAR(20) COMMENT 开课学期 );这段 SQL 的取舍值得说明所有业务表都用自增 id 做主键学号、工号、课程编号虽然也是唯一的但只用普通唯一索引不设为主键这样以后调整学号规则时不需要动主键。注意几个字段的设计考量score 用 DECIMAL(5,1) 而不是 INT因为成绩往往有 0.5 分credit 用 DECIMAL(3,1)学分一般是一位小数gender 用 VARCHAR(10) 就够了没必要上枚举类型课设阶段枚举反而给自己找麻烦。class_name 直接以字符串形式存在 student 表里这是课设级的做法。如果班级还要管班主任、入学年份、所属院系就应该拆成 class 表但在这个系统的题量下字符串字段足够答题。user_id 字段目前不设外键约束只作为逻辑关联存在原因后面避坑章会细说。2.2 三种角色的权限边界与页面访问控制表建好了下一步是明确每种角色能做什么。一个常见误用是做完登录后不管什么身份进去都是同一个页面所有功能都摆在侧边栏答辩时老师说「这不就是一个系统换了身衣服吗」直接降档。正确做法是登录后按角色跳转到不同首页并且后端也要做拦截。权限划分按常见方案做就可以管理员维护学生、教师、课程和用户账号教师查看自己教的课程录入和修改成绩查看选课学生名单学生只能查看自己的基本信息以及已经公布的成绩。这个设计的核心逻辑是「教师不能跨课程操作」所以成绩表必须关联 course_id教师查询成绩时要带出自己的 teacher_id 去过滤。-- 教师只能看到自己课程下的成绩 SELECT sc.* FROM score sc JOIN course c ON sc.course_id c.id WHERE c.teacher_id ?; -- 学生只能看到自己的成绩 SELECT * FROM score WHERE student_id ?;这两条查询是学生信息管理系统权限设计的骨架。注意第一条查询里teacher_id 是从登录用户的 Session 里取出来的不是前端传的这是防止水平越权的底线。关于 Session 的用法和拦截器写法第 4 章会给完整代码。表结构和权限边界都定下来之后你手里的「设计文档」就基本可以动手写了。下一步是搭环境这一块看似零基础实际上坑最多。3. Maven Tomcat MySQL 搭骨架版本搭配与连接池配置很多人在这一步翻车不是因为代码难而是因为装了一堆软件互相不兼容。学生信息管理系统是典型的 Java EE 传统 web 项目最常见的组合是 JDK 8 Maven Tomcat 9 MySQL 8 Druid 连接池。这套组合的教程多、报错记录全出问题搜索马上能定位比盲目上新版本稳妥得多。3.1 JDK、Tomcat、MySQL 版本搭配与三个检查命令版本搭配最忌讳的是「哪个新装哪个」。Tomcat 10 之后 Jakarta EE 包名变动很多课设代码里 import javax.servlet 直接编译不过所以除非你想顺势用新版 API否则老老实实用 Tomcat 9。MySQL 8.0 是最常见的数据库版本配套驱动要选 mysql-connector-java 8.x和老驱动 com.mysql.jdbc.Driver 是不同的类名别混。环境装好后先跑三条命令确认现场再往下走java -version mvn -v mysql --version三条命令分别确认 JDK、Maven、MySQL 可用。这里有个细节java -version 输出里的版本号必须是 1.8 或 8 开头如果你是 JDK 17 配 Tomcat 9 也能跑但有些老项目用了非公开 API高版本 JDK 会直接拒绝启动真遇到了不用怀疑人生换 JDK 8 最省事。Maven 仓库下载慢的问题去 settings.xml 里换阿里云镜像这在课设阶段已经是默认操作不用在这上面浪费时间。3.2 Maven 项目目录与 pom.xml 依赖确认环境没问题后创建一个 Maven webapp 项目。目录结构是约定俗成的src/main/java 放代码src/main/resources 放配置文件src/main/webapp 放 JSP 页面和静态资源。有人刚开始喜欢手搓目录我建议直接用 IDEA 的 Maven 骨架生成省事且不会漏。pom.xml 是整个项目的依赖清单这个项目用到的依赖很固定。直接抄下面的配置注意 scope 的区别。project modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdstudent-manager/artifactId version1.0.0/version packagingwar/packaging dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.30/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.20/version /dependency /dependencies build finalNamestudent-manager/finalName plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.10.1/version configuration source1.8/source target1.8/target encodingUTF-8/encoding /configuration /plugin /plugins /build /project这个 pom.xml 有几个关键点要说明。javax.servlet-api 的 scope 是 provided意思是编译和测试时用它但打包进 war 时排除掉因为 Tomcat 自己带了 Servlet 容器实现重复打包会导致版本冲突这是新手最容易忽略的细节。jstl 用于 JSP 页面的标签库没有它 JSP 里的 c:forEach 全都会报错。mysql-connector-java 8.0.30 对应 MySQL 8.x如果你是 MySQL 5.7换成 5.1.49 并把驱动类名改回来。Druid 是阿里开源的连接池比直接 DriverManager 写连接靠谱得多后面会用到。finalNamestudent-manager/finalName决定了打包出来的 war 包名这个值会直接影响访问路径。如果你的项目部署后访问地址是 http://localhost:8080/student-manager/就是这个配置决定的后面部署路径相关的坑也跟它有关先记住这个字段。3.3 Druid 连接池配置参数与工具类依赖配好接着写数据库连接。这里不直接用 JDBC 的 DriverManager 每条请求都建连接而是用线程池复用连接这就是 Druid 存在的原因。课设阶段也许感受不到区别但答辩时老师问「高并发下你的连接够用吗」有连接池和没有连接池答案的含金量完全不同。src/main/resources 下新建 db.propertiesjdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/student_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password你的密码 # 连接池参数 jdbc.initialSize5 jdbc.maxActive20 jdbc.maxWait60000 jdbc.minIdle5然后写一个工具类负责创建数据源整个项目所有 DAO 都从这里拿连接import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; public class DBUtil { private static DataSource dataSource; static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties)) { Properties props new Properties(); props.load(in); dataSource DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(数据库连接池初始化失败: e.getMessage()); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }这段代码的核心在静态代码块里类加载时只初始化一次数据源。DruidDataSourceFactory.createDataSource(props) 会读取 db.properties 里以 jdbc. 开头的 key自动映射到连接池对应属性上所以配置里的 initialSize、maxActive 这些名字是约定的不能随便改。getConnection() 每次调用从池子里借一个连接用完归还后续代码里所有 DAO 都要遵循「用完关闭」的原则否则会耗尽连接这个坑第 5 章专门讲。连 MySQL 8 的 URL 里必须带 serverTimezoneAsia/Shanghai否则会报时区错误allowPublicKeyRetrievaltrue 是应对 MySQL 8 默认认证插件的缓存问题本地开发建议加上。characterEncodingutf8 必须和数据库建库时的 utf8mb4 配合缺一个中文就乱码。这些都是血泪经验先写上能省半天排查时间。骨架搭到这里项目能启动但还没有业务功能。下一章进入核心登录、权限过滤、学生信息 CRUD这是整个系统能不能被老师认可的关键。4. 登录、过滤器、CRUD 落地能跑通的最小 Java Web 代码环境就绪后就开始写业务。很多初学者一上来就写 Servlet、JSP 混在一起所有逻辑堆在 JSP 里页面看着能跑但答辩时一问三不知。我习惯的做法是写一个登录过滤器保证系统入口安全再做学生信息的增删改查代码分三层Servlet 只做参数接收和页面跳转业务逻辑在 Service数据库操作用 DAO。4.1 登录与 Session一个角色校验过滤器守住整个系统登录逻辑本身不复杂登录页提交用户名密码Servlet 查询 user 表比对成功就把用户对象放进 Session跳转对应角色首页。安全的关键在剩下的每个页面都要校验「你有没有登录」。在 Java Web 里做这件事的标准答案是过滤器Filter它能在请求到达 Servlet 之前先拦一道。这里以登录验证过滤器为例说明整个权限控制的最小实现。把过滤器注册在 /* 上除了登录页和静态资源所有请求都必须经过它判断 Session 里有没有登录用户。import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; WebFilter(/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; // 放行登录接口和静态资源 String uri req.getRequestURI(); if (uri.endsWith(/login.jsp) || uri.endsWith(/login) || uri.endsWith(/register) || uri.contains(/static/)) { chain.doFilter(request, response); return; } // 校验 Session HttpSession session req.getSession(false); Object loginUser session null ? null : session.getAttribute(loginUser); if (loginUser null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }过滤器是最容易出 bug 的地方几个容易被忽略的关键点如下。第一是 req.getSession(false)带参数 false 表示如果没有 Session 就返回 null而不是像 getSession(true) 那样强行创建一个新会话否则静态资源一堆未登录请求就会制造大量无意义 Session。第二是 req.getContextPath()部署路径如果不确定是根路径必须用它拼跳转地址写死 /login.jsp 在带上下文路径的 Tomcat 下会 404。第三是放行规则登录页和登录的 Servlet 放行是必须的否则死循环重定向这个错误新手必踩。4.2 学生列表与分页Servlet 里 doGet 的完整写法登录之后第一个页面通常是学生列表也是整个系统的核心页面。这个 Servlet 要同时支撑两件事展示学生列表和删除学生。我习惯把所有操作放在一个 Servlet 里用 op 参数区分这样 web.xml 里配置的 URL 少逻辑也集中。import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; WebServlet(/student) public class StudentServlet extends HttpServlet { private StudentService studentService new StudentService(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String op req.getParameter(op); if (delete.equals(op)) { int id Integer.parseInt(req.getParameter(id)); studentService.deleteById(id); resp.sendRedirect(req.getContextPath() /student); return; } // 分页参数 int page 1; int pageSize 10; String pageStr req.getParameter(page); if (pageStr ! null pageStr.matches(\\d)) { page Integer.parseInt(pageStr); } PageResultStudent result studentService.page(page, pageSize); req.setAttribute(pageResult, result); req.getRequestDispatcher(/list.jsp).forward(req, resp); } Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String id req.getParameter(id); Student student new Student(); student.setSno(req.getParameter(sno)); student.setName(req.getParameter(name)); student.setGender(req.getParameter(gender)); student.setPhone(req.getParameter(phone)); student.setClassName(req.getParameter(className)); if (id null || id.isEmpty()) { studentService.add(student); } else { student.setId(Integer.parseInt(id)); studentService.update(student); } resp.sendRedirect(req.getContextPath() /student); } }这个 Servlet 展示了 Java Web 开发的核心节奏doGet 处理查询和删除doPost 处理新增和修改。分页参数的处理有个容易被忽略的细节——pageStr.matches(\d) 这行正则校验防止用户传 pageabc 导致 Integer.parseInt 抛 500。课设里经常能看到直接 parseInt 不校验的代码属于「看着能跑但经不起问」的实现。删除操作执行完跳转用的是 sendRedirect也就是重定向浏览器地址栏会刷新成新的列表地址刷新页面不会重复提交删除操作而新增和修改成功后同样重定向这是 PRGPost/Redirect/Get模式的课设级体现。如果你用 forward 转发按 F5 刷新页面表单会重复提交这是最容易在演示时翻车的操作必须记住。4.3 新增与修改共用一个表单两种表单回显写法学生新增和修改的页面几乎一样只是在修改时需要把已有数据回填到表单里。常见做法是把表单单独抽成一个 edit.jsp新增时地址参数不带 id修改时带 idxxx。JSP 页面里用 EL 表达式加三元判断就能做到一份 JSP 同时服务新增和编辑。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html body form action${pageContext.request.contextPath}/student methodpost input typehidden nameid value${student null ? : student.id} 学号input namesno value${student null ? : student.sno}br 姓名input namename value${student null ? : student.name}br 性别 input typeradio namegender value男 ${student null || student.gender 男 ? checked : }男 input typeradio namegender value女 ${student ! null student.gender 女 ? checked : }女br 电话input namephone value${student null ? : student.phone}br 班级input nameclassName value${student null ? : student.className}br button typesubmit保存/button /form /body /html这份 JSP 能同时处理新增和编辑核心技巧在 value${student null ? : student.sno}。新增时 request 里没有 student 属性EL 表达式为 null表单就是空的修改时跳转到这个页面的 Servlet 会把查出的 student 放进 request表单自动回填。这里必须用 pageContext.request.contextPath 拼 form 的 action否则部署到带上下文路径的 Tomcat 下表单提交地址会 404。从第 4 章的内容能看出一个学生信息管理系统能跑通的最小集合就是登录页、校验过滤器、学生 CRUD Servlet、列表 JSP、编辑 JSP。这一套逻辑完全理解后教师管理、课程管理、成绩管理都是同构的复制差别只在 SQL 和权限过滤条件上。代码层面的坑相对少真正折腾人的是环境与部署层面的问题下一章把这些高频故障一次性讲透。5. 避坑指南乱码、时区、端口、连接池与 Session 的排查记录学生信息管理系统这类课设代码写到最后几乎所有人都在跟环境故障搏斗。这一章的坑是我见过最多、自己也踩过的五类按「现象 → 原因 → 解决」写清楚每条都配有可操作的排查步骤。遇到问题先对号入座能省下大量搜索时间。5.1 中文乱码页面乱码和数据库乱码是两个问题现象页面上的中文变成问号或乱码或者页面上显示正常但数据库里存的是一串问号。这两个现象经常同时出现但产生原因完全不同。原因页面乱码是 JSP 文件编码、HTTP 响应编码、浏览器解析编码不一致导致的。数据库乱码是请求参数编码没设置或者数据库连接 URL 缺少 characterEncoding 参数。很多同学只在 JSP 顶部写了 pageEncoding但 filter 里没设置请求编码导致 POST 提交的中文到后端就是乱码。解决按三层统一编码。第一层JSP 页面用% page contentTypetext/html;charsetUTF-8 languagejava %。第二层在 web.xml 或过滤器里设置请求和响应编码最简单的方式是在登录和新增的 Servlet 里调用 req.setCharacterEncoding(UTF-8)更规范的写法是用 Spring 的 CharacterEncodingFilter 思路自己写一个编码过滤器。第三层数据库连接 URL 必须有 characterEncodingutf8建库时带上 DEFAULT CHARSET utf8mb4。三层都检查一遍乱码基本消失。5.2 MySQL 8 连接失败驱动类名和时区两个老熟人现象启动项目后第一次访问数据库就报错错误信息里有The server time zone value或者ClassNotFoundException: com.mysql.jdbc.Driver。原因这两个错误是一对都出在 MySQL 8 的使用姿势上。MySQL 8 的 JDBC 驱动类名从 com.mysql.jdbc.Driver 改成了 com.mysql.cj.jdbc.Driver老驱动类在新驱动包里已经不存在。时区错误则是 MySQL 8 的驱动要求必须明确指定时区否则它会用服务器系统时区去猜。解决db.properties 里驱动名写com.mysql.cj.jdbc.DriverURL 拼上serverTimezoneAsia/Shanghai。如果你用的是 MySQL 5.7驱动名反过来写 com.mysql.jdbc.DriverURL 里也不需要 serverTimezone。换数据库版本的人最容易踩这个坑升级数据库版本后驱动没跟着升级。5.3 Tomcat 8080 端口被占用与部署路径 404现象Tomcat 启动报Address already in use: JVM_Bind或者 IDEA 启动成功但访问 404。原因端口被占用通常是系统里已经有一个 Tomcat 或别的程序占着 8080。404 通常是访问路径和项目部署路径不匹配比如 war 包名是 student-manager你却访问了 http://localhost:8080/自然找不到页面。解决端口问题先查占用进程再杀进程Windows 下执行netstat -ano | findstr 8080把最后一列 PID 对应进程结束掉或者直接改 Tomcat 的 server.xml 把端口改成 8081。部署路径 404 的排查路径是看 IDEA 控制台输出的deployment name或 Tomcat 的 webapps 目录下 war 包名访问地址要带着上下文路径。最规整的做法是 pom.xml 里 finalName 设为 student-manager所有跳转都用 req.getContextPath() 拼接这样部署到哪都不会错。5.4 连接池连接耗尽程序卡死报连接获取超时现象系统用着用着突然卡死Tomcat 日志出现GetConnectionTimeoutException或wait millis 60000, active 20之类的错误。原因数据库连接是从 Druid 池里借来的用完之后必须归还。如果你的 DAO 里用了 conn 但没有 finally 块关闭或者关闭顺序不对连接就一直处在借出状态。池子里最大连接数是 20借出去的都不还新请求只能排队超过 maxWait 60 秒就报错。这是学生信息管理系统里出现频率最高的运行时故障因为增删改查每个操作都要取连接。解决在 DAO 的每个方法里把连接的获取、使用、关闭写成 try-catch-finally或者用 try-with-resourcespublic Student findById(int id) { String sql SELECT * FROM student WHERE id ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, id); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { Student s new Student(); s.setId(rs.getInt(id)); s.setName(rs.getString(name)); return s; } } } catch (SQLException e) { throw new RuntimeException(查询学生失败, e); } return null; }这里的关键是 try-with-resources 的嵌套写法Connection、PreparedStatement 都放在 try 的小括号里ResultSet 单独放在内层 try 的小括号里这样三个资源都会按逆序自动关闭。如果你手写 finally也要按 ResultSet → PreparedStatement → Connection 的顺序关顺序反了会造成连接没还回去的假象。Druid 的日志里能通过 active 数看到连接占用情况建议排查时先看监控数据再翻代码找没关闭的连接。5.5 Session 失效登录成功后马上被踢回登录页现象登录成功跳转到首页后点任何一个链接又跳回 login.jsp反复登录都进不去。原因这个 bug 八成出在过滤器的拦截范围上。回顾 4.1 的 LoginFilter如果是WebFilter(/*)它拦截所有路径如果不放行 login.jsp 和 login Servlet请求登录页本身就会被拦形成死循环重定向。另一种情况是过滤器放行了登录接口但没有放行登录页或者 Session 存取的 key 不一致——登录 Servlet 里存的是 loginUser过滤器里取的是 user永远取不到。解决登录成功后打印一下 Session ID过滤器里也打印一下确认是不是同一个 Session。然后重点检查过滤器的放行规则和 Session 存取 key。最稳定的做法是把 Session 的 key 定义成常量比如public static final String LOGIN_USER_KEY loginUser登录时存它过滤器取它杜绝手写字符串拼错的问题。这五类坑覆盖了从环境到运行的绝大多数故障。处理这些问题的通用套路是先看日志再定位是环境问题还是代码问题最后用最小复现方式验证。接下来是收尾部分说说怎么验证系统功能以及做哪些加分改进让这个课设看起来像正经项目。6. 验证与加分把课设改造成能写进简历的 Java Web 项目系统改到能跑之后先别急着做新功能按这套清单把已有功能过一遍。验证方法是整理一张功能测试表按角色逐项操作每项记录通过/不通过。我的习惯是列这么一张表角色功能点操作步骤预期结果管理员登录拦截未登录直接访问 /student跳转登录页管理员新增学生填写表单并提交列表出现新记录管理员删除学生点击删除并确认列表减少一条记录管理员修改学生编辑表单回显后保存列表显示修改后的值学生权限控制学生账号访问 /student被拒绝或只看到可访问内容教师成绩录入登录后录入成绩只能操作自己课程所有人中文数据提交含中文数据页面与数据库均正常这张表每行都验证一个边界。特别提醒的是「未登录直接访问 /student」这一行很多人演示时从登录页进去一路顺畅但没测过绕过登录直接访问内部页面的情况而这一项恰恰是老师最爱问的也是最容易暴露过滤器缺陷的测试。如果这行没过回第 4 章检查过滤器的拦截规则。功能验证通过后加分项按投入产出比排序。第一档是给密码加密现在 user 表里存的都是明文密码用一个 MD5 加盐的工具类包一下登录校验时把输入密码加密后比对。第二档是数据导出用 POI 把学生列表导出成 Excel这个功能在答辩演示时特别醒目。第三档是把项目结构往企业靠比如用 MyBatis Plus 替换手写 JDBC让实体类直接生成建表 SQL代码量会减少一半。最后如果想往 Spring Boot 方向走也可以在现有 Maven 骨架基础上引入 Spring Boot 重写但这才是在你有余力的情况下再做课设阶段先把 JSP 这套跑透。多说一句个人习惯我做完课设会把踩过的坑记在一个 markdown 文件里比如乱码怎么解决的、连接池为什么耗尽后来面试被问到「你项目里遇到过什么问题」时直接有现成素材比临时想效果强得多。希望这篇文章能帮你在学生信息管理系统这条路上少踩几个坑把时间花在真正有积累的事情上。本文还有配套的精品资源点击获取
返回列表