尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

原生Servlet+JSP实现教务系统权限分层与事务闭环

原生Servlet+JSP实现教务系统权限分层与事务闭环 简介本资源是一套基于JSPServletMySQL开发的Web版学生成绩管理系统适用于高校Java Web课程设计与毕业设计实践面向初学者至中级开发者解决多角色协同管理成绩的核心教学需求。系统采用B/S架构完整实现学生、教师、管理员三类用户权限划分学生可查改个人信息及成绩教师支持成绩录入、修改与统计分析管理员负责全量基础数据班级、课程、师生及用户权限的CRUD操作。压缩包共870个文件含93个Java后端逻辑文件、46个JSP页面、212个JS交互脚本、174个CSS样式文件及大量图片资源PNG/JPG/SVG/GIF整体39.64MB结构清晰、模块完整便于理解MVC分层实现与前后端协作流程。目前已有173人学习下载配套Yuque文档详述功能逻辑与部署说明附带SQL建表语句、完整目录结构及BootstrapSummernote等主流前端组件集成示例是掌握传统Java Web开发全流程的优质实操范例。1. 这不是又一个“Hello World”JSP项目它用原生ServletJSP跑通了真实教务场景的权限分层与事务闭环学生成绩管理系统常被当作Java Web入门练手项目但编号100010041这个版本明显越过了“模拟登录增删改查”的初级阶段——它把学生、教师、管理员三类角色的数据可见性边界和操作原子性约束落到了实处。比如教师修改成绩时系统会校验该教师是否教授该课程、该学生成绩是否已归档管理员为学生分配班级前会检查班级容量与当前人数学生修改手机号需二次密码确认。这些逻辑不是写在前端JavaScript里糊弄浏览器而是由Servlet层拦截请求、调用Service事务方法、经DAO层执行带Transactional语义的MySQL操作完成。整个架构没用Spring框架却通过HttpSession生命周期管理角色状态、用Filter统一处理未登录跳转、靠web.xml中security-constraint定义URL级访问控制。适合正在做课程设计的本科生复现完整MVC链路也适合想回溯Servlet原生开发范式的开发者拆解权限模型落地细节。2. 从war包结构到三层职责划分解析JSPServlet如何承载教务业务复杂度2.1 项目物理结构暴露的设计意图静态资源与动态逻辑的严格分离解压war包后可观察到典型Java Web目录结构/WEB-INF/ ├── web.xml ← 核心配置servlet映射、filter链、session超时 ├── lib/ │ ├── mysql-connector-java-5.1.47.jar ← MySQL驱动注意版本兼容性 │ └── jstl-1.2.jar ← JSTL标签库用于成绩分析页表格渲染 ├── classes/ │ ├── com/example/dao/ ← DAO层StudentDao.java, TeacherDao.java等 │ ├── com/example/service/ ← Service层GradeService.java含事务注解 │ └── com/example/servlet/ ← Servlet层LoginServlet.java, GradeUpdateServlet.java └── views/ ← JSP视图student/grade.jsp, admin/class_manage.jsp /static/ ├── css/ │ ├── bootstrap.min.css ← 响应式布局基础非CDN引入确保离线可用 │ ├── summernote-bs3.css ← 富文本编辑器样式教师录入评语时使用 │ └── style.min.css ← 自定义主题表单边框色、按钮hover效果 └── js/ └── common.js ← 封装AJAX请求统一处理401未登录重定向提示summernote-bs3.css重复出现四次是构建脚本误操作所致实际部署时仅需保留一份。若发现页面富文本编辑器加载异常优先检查/static/css/下是否存在多个同名CSS文件导致浏览器缓存冲突。这种结构刻意规避了现代框架的自动扫描机制所有Servlet必须在web.xml中显式声明。例如教师录入成绩的入口servlet servlet-nameGradeInsertServlet/servlet-name servlet-classcom.example.servlet.GradeInsertServlet/servlet-class /servlet servlet-mapping servlet-nameGradeInsertServlet/servlet-name url-pattern/teacher/insertGrade/url-pattern /servlet-mapping这意味着每个URL路径都对应明确的业务动作而非RESTful风格的资源抽象——这正是课程设计对“功能点可追溯性”的硬性要求。2.2 DAO层实现细节手动管理Connection与PreparedStatement防SQL注入以StudentDao.java中查询学生个人信息为例不使用ORM框架而是直连MySQLpublic Student getStudentById(String studentId) throws SQLException { String sql SELECT id, name, gender, phone, email, class_id FROM student WHERE id ?; try (Connection conn DataSourceUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, studentId); // 参数化防止注入 try (ResultSet rs ps.executeQuery()) { if (rs.next()) { Student s new Student(); s.setId(rs.getString(id)); s.setName(rs.getString(name)); s.setGender(rs.getString(gender)); s.setPhone(rs.getString(phone)); s.setEmail(rs.getString(email)); s.setClassId(rs.getString(class_id)); return s; } } } return null; }关键点在于DataSourceUtil.getConnection()封装了数据库连接池DBCP避免每次请求新建Connection所有WHERE条件均用?占位符杜绝字符串拼接try-with-resources确保ResultSet、PreparedStatement、Connection按序关闭返回对象Student是纯POJO无业务逻辑符合DAO层单一职责对比常见错误写法// ❌ 危险直接拼接SQL String sql SELECT * FROM student WHERE id studentId ; // ✅ 正确参数化查询 ps.setString(1, studentId);2.3 Service层事务控制用Filter拦截并传递Connection实现跨DAO操作由于未引入Spring事务需手动管理。核心思路是将Connection对象绑定到当前线程ThreadLocal使同一请求下的多个DAO操作共享同一事务// TransactionFilter.java public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { Connection conn null; try { conn DataSourceUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 ConnectionHolder.setConnection(conn); // 绑定到ThreadLocal chain.doFilter(req, resp); conn.commit(); // 全部成功则提交 } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ignored) {} } throw new ServletException(事务执行失败, e); } finally { ConnectionHolder.removeConnection(); // 清理ThreadLocal if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }配套的ConnectionHolder工具类public class ConnectionHolder { private static final ThreadLocalConnection connectionHolder new ThreadLocal(); public static void setConnection(Connection conn) { connectionHolder.set(conn); } public static Connection getConnection() { return connectionHolder.get(); } public static void removeConnection() { connectionHolder.remove(); } }这样当GradeService.updateGrade()调用GradeDao.update()和LogDao.insert()时两者获取的Connection是同一个实例从而保证成绩更新与操作日志写入的原子性。3. 三角色权限路由与JSP视图渲染如何让不同用户看到完全不同的功能界面3.1 登录认证流程Session属性值决定后续所有页面分支LoginServlet验证账号密码后不返回JSON而是设置HttpSession属性// LoginServlet.java if (user ! null user.getPassword().equals(inputPassword)) { session.setAttribute(user, user); // 存储用户对象 session.setAttribute(role, user.getRole()); // 角色标识student/teacher/admin session.setAttribute(userId, user.getId()); // 用户ID用于后续查询 response.sendRedirect(request.getContextPath() /index.jsp); } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); }关键在于role属性的值直接驱动前端JSP的显示逻辑。index.jsp作为门户页通过JSTL标签判断角色c:choose c:when test${sessionScope.role student} jsp:include page/views/student/dashboard.jsp / /c:when c:when test${sessionScope.role teacher} jsp:include page/views/teacher/dashboard.jsp / /c:when c:otherwise jsp:include page/views/admin/dashboard.jsp / /c:otherwise /c:choose3.2 教师模块实战成绩录入页的双向数据绑定与校验逻辑/views/teacher/grade_input.jsp页面包含动态课程选择与学生成绩输入表单!-- 课程下拉框 -- select namecourseId onchangeloadStudents(this.value) c:forEach items${courseList} varcourse option value${course.id}${course.name}/option /c:forEach /select !-- 学生成绩表格AJAX动态加载 -- tbody idstudentTableBody c:forEach items${studentList} varstudent tr td${student.id}/td td${student.name}/td tdinput typenumber namescore_${student.id} min0 max100 required //td tdtextarea namecomment_${student.id} rows2 placeholder评语.../textarea/td /tr /c:forEach /tbodyJavaScript部分处理课程切换function loadStudents(courseId) { fetch(/teacher/loadStudents?courseId courseId) .then(res res.json()) .then(data { const tbody document.getElementById(studentTableBody); tbody.innerHTML data.map(s tr td${s.id}/td td${s.name}/td tdinput typenumber namescore_${s.id} min0 max100 required //td tdtextarea namecomment_${s.id} rows2/textarea/td /tr ).join(); }); }对应的LoadStudentsServlet返回JSONprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String courseId request.getParameter(courseId); ListStudent students studentService.getStudentsByCourse(courseId); response.setContentType(application/json;charsetUTF-8); response.getWriter().write(new JSONArray(students).toString()); }3.3 管理员模块关键操作班级分配事务中的外键约束与并发控制管理员为学生分配班级时AdminServlet需同时更新student表的class_id字段并检查班级容量public void assignClass(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String studentId request.getParameter(studentId); String classId request.getParameter(classId); // 1. 查询班级当前人数 int currentCount classDao.getStudentCount(classId); int capacity classDao.getCapacity(classId); if (currentCount capacity) { request.setAttribute(error, 班级已满无法分配); request.getRequestDispatcher(/views/admin/class_assign.jsp).forward(request, response); return; } // 2. 更新学生班级事务内执行 try { studentDao.updateClassId(studentId, classId); classDao.incrementStudentCount(classId); // 原子性增加计数 request.setAttribute(msg, 分配成功); } catch (SQLException e) { request.setAttribute(error, 分配失败 e.getMessage()); } request.getRequestDispatcher(/views/admin/class_assign.jsp).forward(request, response); }这里隐含两个重要设计classDao.incrementStudentCount(classId)使用MySQL的UPDATE class SET student_count student_count 1 WHERE id ?避免先查后更新的竞态条件studentDao.updateClassId()执行UPDATE student SET class_id ? WHERE id ?依赖student.class_id外键指向class.id确保分配的班级真实存在4. 部署与排错Tomcat 8环境下JSP编译、MySQL连接及常见404/500问题定位4.1 JSP编译产物位置与调试技巧定位class文件与Java源码Tomcat运行时会将JSP编译为Servlet类默认存储在$CATALINA_HOME/work/Catalina/localhost/[your-app-name]/org/apache/jsp/例如/views/student/grade.jsp编译后生成grade_jsp.javaJSP转换的Java源文件含_jspService()方法grade_jsp.class编译后的字节码文件若遇到JSP语法错误如EL表达式写错查看grade_jsp.java第X行能快速定位问题。例如${student.name} !-- 正确 -- ${student.nam} !-- 错误属性不存在 --编译后的Java代码会生成out.write((java.lang.String) org.apache.jasper.runtime.JspRuntimeLibrary.toString( ((com.example.model.Student) pageContext.findAttribute(student)).getNam())); // 注意getNam() 方法不存在 → 编译失败此时需检查Student类是否有getNam()方法而非getName()。4.2 MySQL连接配置要点解决“Access denied for user”与“Communications link failure”DataSourceUtil.java中连接配置需匹配实际环境private static final String URL jdbc:mysql://localhost:3306/scoredb?useSSLfalseserverTimezoneGMT%2B8; private static final String USERNAME score_user; // ❌ 不要使用root private static final String PASSWORD ScorePass123!; // ✅ 强密码策略常见错误及修复错误现象原因解决方案Access denied for user score_userlocalhostMySQL用户无权限GRANT ALL PRIVILEGES ON scoredb.* TO score_userlocalhost IDENTIFIED BY ScorePass123!; FLUSH PRIVILEGES;Communications link failureMySQL服务未启动或端口被占用sudo systemctl status mysql检查服务状态netstat -tulnUnknown database scoredb数据库未创建CREATE DATABASE scoredb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;注意useSSLfalse在开发环境可省略SSL握手开销但生产环境必须启用SSL并配置证书。4.3 404错误精准定位区分资源路径与Servlet映射路径当访问http://localhost:8080/grade/teacher/insertGrade返回404按顺序排查检查URL是否匹配web.xml中url-pattern确认GradeInsertServlet的映射为/teacher/insertGrade非/grade/teacher/insertGrade确认请求方法是否匹配Servlet的doPost()/doGet()表单提交默认POST若Servlet只实现doGet()则报405验证web.xml是否被Tomcat正确加载查看Tomcat启动日志中是否有INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive字样检查JSP文件路径是否在/WEB-INF/views/下直接访问/views/xxx.jsp必然404WEB-INF目录受保护4.4 500错误日志分析从堆栈追踪到具体DAO方法当GradeUpdateServlet抛出500Tomcat日志关键信息Caused by: java.sql.SQLException: Column score cannot be null at com.example.dao.GradeDao.updateGrade(GradeDao.java:87) at com.example.service.GradeService.updateGrade(GradeService.java:45) at com.example.servlet.GradeUpdateServlet.doPost(GradeUpdateServlet.java:62)说明第87行ps.setDouble(2, grade.getScore())传入了null值。此时需检查前端表单中input namescore是否设置了required属性Grade实体类的score字段是否为Double类型允许null还是double基本类型自动赋0GradeService.updateGrade()是否对grade.getScore()做了空值校验5. 进阶技巧用Filter实现操作日志审计与敏感字段脱敏5.1 记录关键业务操作谁在何时修改了哪门课的成绩在web.xml中注册OperationLogFilterfilter filter-nameOperationLogFilter/filter-name filter-classcom.example.filter.OperationLogFilter/filter-class /filter filter-mapping filter-nameOperationLogFilter/filter-name url-pattern/teacher/*/url-pattern url-pattern/admin/*/url-pattern /filter-mappingOperationLogFilter截获请求参数并写入数据库public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; String uri request.getRequestURI(); String method request.getMethod(); // 只记录POST请求的业务操作 if (POST.equals(method)) { MapString, String[] params request.getParameterMap(); if (uri.contains(insertGrade) || uri.contains(updateGrade)) { String userId (String) request.getSession().getAttribute(userId); String courseId params.get(courseId)[0]; String studentId params.get(studentId)[0]; // 记录日志异步写入避免阻塞主流程 new Thread(() - logService.record(userId, uri, 成绩录入, course: courseId ,student: studentId)).start(); } } chain.doFilter(req, resp); }5.2 JSP页面敏感信息脱敏手机号中间四位替换为星号在/views/student/profile.jsp中展示手机号时不直接输出!-- ❌ 原始写法 -- td${student.phone}/td !-- ✅ 脱敏写法 -- td${fn:replace(fn:replace(student.phone, fn:substring(student.phone, 3, 7), ****), -, )}/td更优雅的方式是自定义JSTL函数// PhoneMaskFunction.java public class PhoneMaskFunction { public static String maskPhone(String phone) { if (phone null || phone.length() 11) return phone; return phone.substring(0, 3) **** phone.substring(7); } }在TLD文件中声明function function-namemaskPhone/function-name function-classcom.example.util.PhoneMaskFunction/function-class function-signaturejava.lang.String maskPhone(java.lang.String)/function-signature /functionJSP中调用td${fn:maskPhone(student.phone)}/td5.3 防止JSP页面被直接访问通过web.xml限制/WEB-INF/views/路径在web.xml中添加安全约束禁止外部直接请求JSPsecurity-constraint web-resource-collection web-resource-nameProtected Views/web-resource-name url-pattern/views/*/url-pattern /web-resource-collection auth-constraint/ /security-constraint这样当用户尝试访问http://localhost:8080/views/student/grade.jsp时Tomcat会返回403 Forbidden强制所有视图必须经Servlet转发如request.getRequestDispatcher(/views/student/grade.jsp).forward(request, response)确保权限校验不被绕过。最后检查/WEB-INF/web.xml中welcome-file-list是否配置为index.jsp避免部署后首页空白welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list本文还有配套的精品资源点击获取
返回列表