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

资讯详情

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

基于 JSP 的实验室考勤管理系统:从源码到部署的 JavaWeb 实战

基于 JSP 的实验室考勤管理系统:从源码到部署的 JavaWeb 实战 简介基于JSP技术开发的实验室考勤管理系统是一个完整可运行的毕业设计项目主要面向计算机专业正在准备课程设计、期末大作业或毕业设计的学生也适合需要Web项目实战练习的学习者。系统以实验室考勤为业务场景实现了用户身份验证、考勤记录管理、考勤数据查询和统计报表等功能涉及JSP、Servlet、JDBC以及MVC分层设计等核心技术。压缩包内共1385个文件包含JSP页面、Java后端代码、CSS与JavaScript前端资源、PNG/JPG图片、XML配置文件、SQL数据库脚本等整体大小约17MB目录结构清楚方便按模块查阅。目前已有26人学习与下载内容质量有参考价值。随包附带论文、开发文档、数据文档和源码源码经过本地编译与调试均可正常运行数据文档还提供了测试用例和数据库文件学习者可以对照完整开发过程从需求分析、数据库设计到编码实现逐层理解还能在现有项目基础上继续修改和扩展。1. 基于 JSP 的实验室考勤管理系统一个 zip 工程到底能解决什么带去实验室给学生开组会时你大概率见过这个场景签到表在门卫手里传来传去代签、漏签、时间混乱期末核算考勤要翻两小时 Excel。于是不少毕设选题和院级小项目都指向同一个方向——基于 JSP 的实验室考勤管理系统网页的设计与实现通常还附带着一个 jsp.zip 源码包。这个标题里的核心不是“管理系统”这四个大字而是“JSP 网页 考勤逻辑”这套落地方案用浏览器做界面用 Tomcat 跑服务用数据库记状态最后合成一个可以导入 IDE 直接启动的工程。这套东西能解决的问题很具体老师扫码或点名学生签到签退系统自动比对时间窗口输出迟到、早退、缺勤记录课程结束后一键导出统计。它适合三类人正在做 JavaWeb 毕业设计的学生需要快速交付一个内部工具的一线工程师以及想把“JSP 程序设计”课程作业做成完整作品的新手。我的态度比较直接——JSP 在 2025 年确实不性感Spring Boot 才是主流但在毕设答辩和百人级内部考勤场景里JSP Servlet Tomcat 这套传统 JavaWeb 组合仍然是资料最多、最容易跑通、最不会被评委挑刺的方案之一。2. 先搞懂 JSP 工程的运行逻辑Servlet 容器、Model2 分层与项目结构2.1 JSP 不是单独跑的Tomcat 才是它的家Nginx 不是很多人拿到 jsp.zip 第一件事是双击某个 .html 或者 .jsp 文件发现浏览器弹出了源码而不是页面然后就慌了。这个现象的原因并不玄学JSP 本质上是 Servlet 的“模板写法”它必须交给 Servlet 容器来编译和执行。容器收到 .jsp 请求后会把 JSP 翻译成 Java 代码再编译成 Class最后调用对应的 service 逻辑把渲染后的 HTML 响应回浏览器。整个过程的实际执行者是 Tomcat、Jetty 这类容器而不是浏览器本身。顺带说一个被问得很多的问题Nginx 支持 JSP 吗答案很干脆——不支持。Nginx 擅长的是静态资源、反向代理和负载均衡它没有 JSP 编译引擎。常见做法是让 Nginx 监听 80 端口转发给后端的 Tomcat 8080由 Tomcat 处理带 .jsp 的请求。如果你在本地开发直接访问 http://localhost:8080/项目名/ 就行根本不需要绕 Nginx 这一层。注意拿到别人的 zip 后不要直接改 JSP 文件后缀或试图静态打开。先确认机器上有 JDK Tomcat并且工程能被打成 war 包或直接以 Exploded 形式部署进 webapps 目录。2.2 按 JSP Model2 思想拆分JSP 只当视图Servlet 干控制器的事热搜词里有一条叫“按照 jsp modeled2 思想实现用户注册功能”——这里的“modeled2”就是 Model2 的笔误。Model2 是 JavaWeb 里约定俗成的 MVC 分层JSP 负责显示Servlet 负责接收请求和调度JavaBean / DAO 负责数据模型和数据库访问。读别人工程源码时先看目录就知道作者有没有按套路来。一个典型的 JSP 实验室考勤管理系统源码目录是这样的lab-attendance/ ├── src │ ├── main │ │ ├── java │ │ │ └── com/lab/attendance │ │ │ ├── controller # Servlet 控制器 │ │ │ │ ├── LoginServlet.java │ │ │ │ ├── AttendanceServlet.java │ │ │ │ └── StudentServlet.java │ │ │ ├── service # 业务层接口与实现 │ │ │ │ ├── AttendanceService.java │ │ │ │ └── impl/AttendanceServiceImpl.java │ │ │ ├── dao # 数据库访问层 │ │ │ │ ├── BaseDao.java │ │ │ │ └── impl/AttendanceDaoImpl.java │ │ │ ├── entity # 实体类 │ │ │ │ ├── User.java │ │ │ │ ├── LabRoom.java │ │ │ │ └── AttendanceRecord.java │ │ │ ├── filter # 登录拦截过滤器 │ │ │ └── util # 时间处理、分页等工具 │ │ └── webapp │ │ ├── WEB-INF │ │ │ ├── web.xml # 部署描述符 │ │ │ └── lib # jar 包没有 Maven 时用 │ │ ├── css │ │ ├── js │ │ ├── login.jsp │ │ ├── index.jsp │ │ ├── attendance.jsp │ │ └── student_list.jsp │ └── test └── sql └── lab_attendance.sql这套结构无论来源是课设还是毕设基本都长这样。第一件事不是打开 JSP 看源码而是先看 java 目录下的 filter 和 controller搞清楚请求从哪里进、登录怎么拦、考勤记录走哪个 Servlet。很多同学拿到 zip 后一个月没跑通就是因为一直在看 JSP 页面却不知道真正干活的是 Servlet。2.3 最小依赖配置Maven 工程里的 JSP 依赖别乱加如果你的 zip 是 Maven 工程pom.xml 里最容易被搞错的依赖就是 servlet-api 和 jsp-api。这两个 jar 由 Tomcat 自己提供如果按默认的 compile 作用域打包进 war 包启动时会因为类冲突报 LinkageError 或 NoSuchMethodError。正确的写法是把作用域设为 provided意思是编译时需要运行时交给容器。dependencies !-- Servlet API编译期使用运行期由 Tomcat 提供 -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency !-- JSP API同样由容器提供 -- dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency !-- JSTL 标签库这个反而是要打进去的 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency !-- MySQL 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.49/version /dependency /dependency如果这不是 Maven 工程而是传统的 WEB-INF/lib 放 jar 的方式记住一个原则Tomcat 的 lib 目录里能看到的包不要重复出现在你的 WEB-INF/lib 里。你只需要放 MySQL 驱动和 JSTL千万不要从网上下一个 jsp-api.jar 丢进去这条我踩过不只一次翻车现象都是 Tomcat 启动时抛异常但浏览器端什么提示都没有。3. 数据库设计与考勤判定逻辑五张表和一个状态机3.1 考勤系统不需要复杂表结构五张表足以覆盖核心流程实验室考勤和公司 OA 考勤有个显著区别它的业务场景是“某学生在某时间进入实验室在某时间离开”判定标准是“是否在管理员规定的时间窗口内完成操作”。围绕这个核心一般只需要五张表用户表、实验室表、排课或开放时间段表、考勤记录表、补签/请假表。下面是建表 SQL 的核心部分设计时尽量用简单的字段名和类型别为了“正规”引入大量冗余列毕设评委更看重的往往是逻辑完整性和代码干净度。CREATE DATABASE IF NOT EXISTS lab_attendance DEFAULT CHARSET utf8mb4; USE lab_attendance; -- 用户表学生和老师用 role 区分 CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT 建议存储 SHA-256 摘要, real_name VARCHAR(30) NOT NULL COMMENT 真实姓名, role TINYINT NOT NULL DEFAULT 0 COMMENT 0学生, 1教师, 2管理员, student_no VARCHAR(20) COMMENT 学号教师和管理员可为空, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 实验室表 CREATE TABLE lab_room ( id INT PRIMARY KEY AUTO_INCREMENT, room_name VARCHAR(50) NOT NULL COMMENT 例如 A302 嵌入式实验室, seats INT DEFAULT 0, status TINYINT DEFAULT 1 COMMENT 1开放, 0关闭 ); -- 开放时间段表定义哪天哪个时间段考勤有效 CREATE TABLE attendance_session ( id INT PRIMARY KEY AUTO_INCREMENT, lab_id INT NOT NULL, date DATE NOT NULL, start_time TIME NOT NULL COMMENT 签到开始时间, end_time TIME NOT NULL COMMENT 签退截止时间, late_time TIME COMMENT 超过该时间算迟到, leave_time TIME COMMENT 早于该时间签退算早退, course_name VARCHAR(100) COMMENT 哪门课或哪次实验 ); -- 考勤记录表一次签到或签退就是一条记录 CREATE TABLE attendance_record ( id INT PRIMARY KEY AUTO_INCREMENT, session_id INT NOT NULL COMMENT 关联开放时间段, user_id INT NOT NULL, sign_in_time DATETIME COMMENT 签到时间, sign_out_time DATETIME COMMENT 签退时间, status TINYINT NOT NULL DEFAULT 0 COMMENT 0缺勤, 1正常, 2迟到, 3早退, 4请假, remark VARCHAR(200) ); -- 补签申请表 CREATE TABLE leave_request ( id INT PRIMARY KEY AUTO_INCREMENT, attendance_id INT NOT NULL COMMENT 对应的考勤记录, user_id INT NOT NULL, reason VARCHAR(255) NOT NULL, status TINYINT DEFAULT 0 COMMENT 0待审批, 1通过, 2拒绝, apply_time DATETIME DEFAULT CURRENT_TIMESTAMP );注意看 attendance_record 表的 status 字段它不是简单的 0/1而是一个状态位。设计时另一个容易踩坑的点是 sign_in_time 和 sign_out_time 用了 DATETIME 而不是拆成两个表。拆表看起来很“细致”但会导致一次出勤要关联两条记录统计时还得 JOIN完全没必要。一次打卡会话用一行记录表示签到更新 sign_in_time签退再 UPDATE 同一条记录的 sign_out_time逻辑更直觉。3.2 考勤状态判定参数迟到、早退、缺勤的规则如何定义复用率高判定规则是整个系统的业务灵魂。把“时间窗口”的判定参数抽出来放进 attendance_session 表是最常见也最灵活的做法。我的经验是硬编码判定条件会让你后期想骂人比如今天老师说迟到线改到 9 点 20 分明天又改回来如果判断逻辑写在 Servlet 里你就得改代码重新部署。推荐做法是把判定做成一个独立的工具类接收 session 的时间参数和实际的签到签退时间输出状态码。public class AttendanceStatusUtil { /** * 根据会话时间参数计算考勤状态 * param signInTime 学生实际签到时间null 表示没签到 * param sessionStart 签到开始时间 * param lateLine 迟到判定线 */ public static int calcSignInStatus(Date signInTime, java.sql.Time sessionStart, java.sql.Time lateLine) { if (signInTime null) { return 0; // 缺勤 } java.sql.Time actual new java.sql.Time(signInTime.getTime()); // 晚于迟到判定线 - 迟到否则正常 if (actual.after(lateLine)) { return 2; } return 1; } public static int calcSignOutStatus(Date signOutTime, java.sql.Time sessionEnd, java.sql.Time earlyLine) { if (signOutTime null) { return 0; // 没签退视同缺勤 } java.sql.Time actual new java.sql.Time(signOutTime.getTime()); // 早于早退判定线 - 早退 if (actual.before(earlyLine)) { return 3; } return 1; } }这段代码逻辑很简单但请注意两点。第一状态计算不能只做一次签退完成后要执行一次“总状态合并”比如签到正常但签退早退最终状态应该显示“早退”而不是两个状态叠加。第二判定线参数来自 attendance_session 表而不是来自前端页面或常量类这样才能做到不同实验室不同时间窗口不打架。3.3 JDBC 还是 MyBatis连接池怎么配Druid 的四个关键参数JSP 时代的老工程大多是 JDBC 连接池也有一部分用了 MyBatis。拿别人的 zip 时先别急着换框架看它原来是 JDBC 就继续用 JDBC因为替换持久层会动到所有 DAO 代码最容易出现“改一处崩三处”的情况。如果原来是裸 JDBC每次都 DriverManager.getConnection我建议至少加一个 Druid 连接池性能提升很明显代码改动也不大。把连接获取逻辑收敛到一个工具类里比在十个 DAO 里反复写 Class.forName 要强得多。import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; import java.util.Properties; public class JdbcUtils { private static DataSource ds; static { try (InputStream in JdbcUtils.class.getClassLoader() .getResourceAsStream(druid.properties)) { Properties props new Properties(); props.load(in); ds DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError( Druid 数据源初始化失败检查 druid.properties 是否存在); } } public static Connection getConnection() throws Exception { return ds.getConnection(); } public static void close(AutoCloseable... resources) { for (AutoCloseable r : resources) { if (r ! null) { try { r.close(); } catch (Exception ignored) {} } } } }对应的 druid.properties 放在 src/main/resources 下driverClassNamecom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/lab_attendance?useSSLfalsecharacterEncodingutf8 usernameroot password你自己的密码 initialSize5 maxActive20 maxWait3000这里有四个参数需要解释。initialSize 表示启动时预热几个连接设教太小会在第一次请求时卡顿。maxActive 是连接池上限毕设级系统 20 就够了设太大只是白白吃内存。maxWait 是获取连接最大等待毫秒数超过就抛异常这个值设成 3000 能避免数据库假死时请求全卡住。最容易被忽略的是 driverClassNameMySQL 5.x 用 com.mysql.jdbc.DriverMySQL 8.x 得用 com.mysql.cj.jdbc.Driver 且 url 要加 serverTimezoneAsia/Shanghai否则你会看到一串关于时区的报错这是老 JSP 工程连新数据库最常见的翻车现场。4. JSP 考勤页面与 Servlet 控制器的打通从登录到签到的最小闭环4.1 登录过滤器为什么每个 JSP 页面都要先过这一关而不是在页面里判断拿到考勤系统第一件事是看登录怎么控制。很多初学者喜欢在所有 JSP 页面头部写一段 Java 判断判断 session 里有没有 user没有就跳转。这在三个页面以内挺有效但页面一旦超过十个漏写一个就是安全的洞。正确做法是写一个 Filter优先级最高拦截所有 .jsp 和 .do 请求统一检查登录状态。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 req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(); String uri request.getRequestURI(); // 放行登录页面、静态资源和根路径 if (uri.endsWith(/login.jsp) || uri.endsWith(/register.jsp) || uri.contains(/css/) || uri.contains(/js/) || uri.endsWith(/LoginServlet) || uri.endsWith(/index.jsp)) { chain.doFilter(req, resp); return; } Object user session.getAttribute(loginUser); if (user null) { // 未登录重定向到登录页 response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }注意这段代码里的细节。request.getContextPath() 很关键如果你把项目部署在 http://localhost:8080/lab-attendance/ 下面重定向地址必须带项目名否则会跳到根路径 404。另外拦截范围写成 / 会把静态资源也拦住所以 CSS/JS 必须显式放行。还有一种更省事的方式是只拦截 /pages/ 这种受保护目录下的 JSP把 login.jsp 放在外面两种方案都行看工程习惯。4.2 签到页面JSP 标签 JS 拿到考勤状态按钮置灰比后端拒绝更友好签到页面是用户每天面对最多的界面不需要花哨但要处理好“按钮状态和时间窗口的联动”。后端只需要返回当前用户、当前有效 session 和今天的签到状态前端根据状态决定显示“签到”“签退”还是“已完成”。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !DOCTYPE html html head title实验室考勤-签到页/title /head body h2欢迎${sessionScope.loginUser.realName}/h2 !-- 后端塞进 request 域的数据 sessionInfo当前生效的考勤时间段对象 myRecord今天的考勤记录可能为 null -- c:choose c:when test${empty sessionInfo} p当前没有生效的考勤时段无法签到。/p /c:when c:otherwise p考勤时段${sessionInfo.courseName}/p p签到开始${sessionInfo.startTime} | 迟到线${sessionInfo.lateTime}/p c:choose c:when test${empty myRecord.signInTime} form action${pageContext.request.contextPath}/AttendanceServlet methodpost input typehidden nameaction valuesignIn input typehidden namesessionId value${sessionInfo.id} button typesubmit签到/button /form /c:when c:when test${empty myRecord.signOutTime} p已签到${myRecord.signInTime}/p form action${pageContext.request.contextPath}/AttendanceServlet methodpost input typehidden nameaction valuesignOut input typehidden namerecordId value${myRecord.id} button typesubmit签退/button /form /c:when c:otherwise p已完成签到签退状态${myRecord.status}/p /c:otherwise /c:choose /c:otherwise /c:choose /body /html这段 JSP 的要点是文案与请求都在服务端控制。如果学生已经签到页面不会再出现“签到”按钮只有“签退”。前端的隐藏字段 sessionId 和 recordId 是给 Servlet 用的拷代码时要检查 form 的 action 路径前面有没有加 pageContext.request.contextPath没有的话部署在子路径下表单会提交到错误地址。JSP 用的标签尽量选 JSTL 而不是 scriptlet即 % % 写 Java评分标准和可维护性都会好很多这也是很多课程答案里强调的“按 JSP Model2 思想”的含义。4.3 打卡 Servlet 的写法事务提交时机和状态合并别放在页面里当签到按钮提交到 AttendanceServlet 后Servlet 要做的事有三件解析参数调 Service 完成业务校验时间、计算状态根据结果跳转回签到页并回显提示。一个常见的错误是在 Servlet 里直接写 JDBC 代码然后把所有判断逻辑堆在 doPost 里。标准做法是 Servlet 只做调度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(/AttendanceServlet) public class AttendanceServlet extends HttpServlet { private AttendanceService attendanceService new AttendanceServiceImpl(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action req.getParameter(action); Integer userId ((User) req.getSession().getAttribute(loginUser)).getId(); if (signIn.equals(action)) { // 签到根据 sessionId 找到考勤时段写入签到时间并算状态 int sessionId Integer.parseInt(req.getParameter(sessionId)); ResultMessage message attendanceService.signIn(userId, sessionId); req.setAttribute(msg, message.getInfo()); } else if (signOut.equals(action)) { int recordId Integer.parseInt(req.getParameter(recordId)); ResultMessage message attendanceService.signOut(userId, recordId); req.setAttribute(msg, message.getInfo()); } req.getRequestDispatcher(/attendance.jsp).forward(req, resp); } }真正的业务逻辑在 Service 层做其中最关键的是事务控制。签到和签退操作都要先 SELECT 当前记录是否存在再 INSERT 或 UPDATE这个“先查后写”的过程如果没有事务包裹高并发下可能产生重复打卡记录。Service 里典型的写法是让 connection 从 JdbcUtils.getConnection() 获取关闭自动提交写完业务提交后 finally 恢复自动提交并归还连接。注意JSP 的 response 输出是带缓冲的如果你用了 forward 跳转但之前在 Servlet 里已经写过 out.print跳转会失效甚至报 IllegalStateException。宁可全部用 req.setAttribute 往页面传数据也别图省事在 Servlet 里拼一串 HTML。5. JSP 考勤系统避坑实录先排查这 5 个高频故障再谈优化5.1 改了 JSP 页面但浏览器不变是 Tomcat 缓存了翻译后的 Class现象修改了 JSP 里的静态文案刷新浏览器页面完全没有变化。Tomcat 在启动时或首次请求时把 JSP 翻译成 Servlet Java 文件并编译为 Class这些文件存放在 Tomcat 的 work/Catalina 目录下。如果你用 Eclipse 或 IDEA 以老工程方式运行IDE 有时没把新文件同步到部署目录看起来就像没改。原因分两类一是 IDE 没有重新发布 webapp 目录二是 Tomcat 开启了 JSP 缓存的编译校验间隔。解决方法是先停止 Tomcat删除 work 目录下的工程缓存再 Clean 整个项目后重新启动。如果每次改 JSP 都要手动清 work去 Tomcat 的 conf/web.xml 把 jsp servlet 的 modificationTestInterval 参数调小默认是 4 秒改成 0 代表每次都检查。5.2 访问页面 404 且控制台报 Servlet 映射找不到web.xml 和注解只能用一种现象昨天还能跑的工程今天从 GitHub 或 U 盘拉下来启动后访问 LoginServlet 直接 404。检查 Tomcat 日志提示 Servlet 加载失败或映射为空。原因多数是代码里既有 WebServlet 注解又有人在 web.xml 中配置了同一个 Servlet 的访问路径。两个系统同时生效时映射表会错乱部分容器直接放弃加载。解决选一种方式。Servlet 3.0 以上建议全注解把 web.xml 里关于 Servlet 的配置全部删掉只保留欢迎页面和编码过滤器。5.3 JDBC 驱动 ClassNotFoundExceptionjar 包在 Maven 里没打进去现象本地 IDEA 运行正常但打成 war 包放到另一台服务器就报 ClassNotFoundException: com.mysql.jdbc.Driver。原因是 Maven 工程里 mysql-connector-java 的 scope 是 provided编译期存在打包时被排除了。解决把 scope 改成 compile 或干脆删除 scope 标签重新打包。如果服务器上没有外网Maven 依赖没有进入本地仓库war 包里也不会带驱动记得用 mvn clean package 确保依赖被解析进 WEB-INF/lib。5.4 MySQL 连接报错 Communications link failure / 时区异常现象页面第一次打开很慢点击登录按钮后报 Communications link failure 或者 The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。原因MySQL 8.x 的驱动要求显式指定 serverTimezone而且 8.x 的驱动类和 URL 参数跟 5.x 完全不同。解决确认驱动版本和 MySQL 版本匹配并在 url 上追加 serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue。一个很容易忽略的细节mysql-connector-java 5.1.49 也不支持 MySQL 8.0 以上的默认加密插件 caching_sha2_password这时要么在 MySQL 里为 root 指定 mysql_native_password要么升级驱动到 8.0.x 版本。5.5 登录成功后所有 JSP 里的 EL 表达式输出原样没有渲染现象页面上出现 ${sessionScope.loginUser.realName} 这样的字符串而不是用户名字。原因web.xml 里配置了较老的 Servlet 2.3 或 2.4 规范默认关闭 EL 表达式。很多老模板工程直接复用了几年前的 web.xml 头DOCTYPE 到 web-app 版本都停留在 2.4 时代。解决把 web.xml 根元素更新到 Servlet 3.0 以上例如使用 web-app_3_0.xsd。如果工程完全没有 web.xml 而是纯注解就不会有这个问题。这个坑不容易想到但遇到后非常好解决往往花十分钟排查改一个根标签就恢复正常。6. 部署与进阶从能跑通到敢拿出手的验证路径把工程跑通只是第一步后续的验证和收尾决定这个系统能不能真正投入使用。常见的部署方式是把 Maven 工程打成 war 包复制到 Tomcat 的 webapps 目录下启动访问 http://ip:8080/lab-attendance/。如果你是本地开发直接运行IDEA 里配置 Tomcat 指向工程后用 Exploded 方式部署可以缩短重启时间但最后答辩或交付时war 包才是唯一可靠的交付物。部署完成后不要直接打开浏览器点点点就宣布完工。拿出几分钟按下面这套流程做冒烟测试先用 curl 验证登录接口能正常返回跳转再打开浏览器走一遍签到、签退、查看列表、老师审核补签四条主链路最后在 MySQL 里检查考勤记录表的 status 是否符合预期。# 1. 确认 Tomcat 已启动进程端口存活 curl -I http://localhost:8080/lab-attendance/login.jsp # 2. 模拟登录请求带上 Cookie 访问受保护页面预期是 302 跳转回登录页 curl -i http://localhost:8080/lab-attendance/attendance.jsp # 3. 查看 Tomcat 实时日志确认没有 API 异常或数据库连接错误 tail -f /usr/local/tomcat/logs/catalina.out进阶方向上如果你不满足于交付一个毕设想让这个考勤系统“真的能用”优先考虑三个改造点。第一给签到接口加 Redis 防重同一个 userId 在 5 秒内的重复请求直接拒绝防止学生手滑连点。第二把统计导出做成 Excel 下载用 Apache POI 生成一个多 Sheet 的考勤汇总表按课程、按学生、按时间段三个维度呈现。第三把 JSP 页面的展示层逐步拆成前后端分离Servlet 改造成返回 JSON 的接口方便以后接小程序或企业微信。从个人习惯来说我每次拿到这类 JavaWeb 课程工程第一件事永远是先跑通再重构绝不开局就改代码。JSP 考勤系统这种规模的工程正确路径是先部署、再按业务链路点一遍、最后才动手优化。跑通以后你会发现难的地方不在于 JSP 语法而在于把数据库状态、Servlet 逻辑和页面反馈三者的时序关系想清楚。先保证不出错再谈用起来顺不顺手希望这片整理能帮你少走一点弯路。本文还有配套的精品资源点击获取
返回列表