ARTICLE DETAIL

资讯详情

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

基于 JSP 的实验室考勤管理系统开发实战

基于 JSP 的实验室考勤管理系统开发实战 简介这套基于JSP的实验室考勤管理系统是针对计算机专业毕业设计与期末大作业场景的完整项目包适合需要完成Web课程设计或入门Java Web开发的学习者。系统涵盖用户身份验证、考勤记录管理、数据查询与统计报表等核心功能前后端交互清晰可运行于Tomcat等标准服务器上帮助理解JSP、Servlet、JDBC及MVC分层开发思路。压缩包共1385个文件体积约17MB包含107个jsp页面、105个java源码、397个js脚本以及大量css、png、gif等前端资源另有sql数据库脚本、论文文档、开发说明与数据文档。目录结构按功能模块划分便于按需查阅与二次扩展也适合作为课程设计或毕设的参照模板。目前已有27人学习下载。项目源码经本地编译调试可正常运行配套文档完整记录了从需求分析、系统设计到测试总结的全过程数据文档提供实验数据和增删改查示例。对于希望从零规划并实现一个完整Java Web项目的学习者而言这套资料既能提供可直接运行的范例也能作为后续功能扩展的起点。1. 基于 JSP 的实验室考勤管理系统网页解决的是纸质签到那笔烂账实验室考勤管理系统这类基于 JSP 的 Web 工程放到今天依然不算落后于场景。高校和培训机构的大量实验课还停留在纸质签到表加期末手工汇总 Excel 的阶段学生代签、漏签、教师课后补录整个流程全是漏洞。以 JSP Servlet MySQL 实现的这套系统目标就是把考勤搬上网页学生进实验室签到、出实验室签退教师端自动统计迟到、早退、缺勤名单管理员维护实验室和排课数据。它适合三类人被课程设计题目点名的在校生校内需要低成本考勤工具的实验员以及想用 JSP 把 Java Web 技术栈完整串一遍的初学者。无论你是拿到一个“jsp.zip”源码包正在纠结怎么跑起来还是准备从零动手写这篇文章都会按数据模型、核心流程、统计页面和常见翻车点把落地路径完整讲一遍。2. 需求拆解与表结构设计先把数据模型立住考勤系统的页面可以后期慢慢调表结构一旦建错改起来就是伤筋动骨。我一般动手写 JSP 之前会先用半天把角色、业务流程和数据关系画清楚再落 SQL。这一步省下来的时间往往比后面调页面省得还多。2.1 三种角色与核心业务边界实验室考勤的参与方就三类学生、教师、管理员。学生做的事情很窄——查看自己的课程表、在课程时间段内签到和签退、看自己的考勤记录。教师负责查看每次实验课的出勤情况、按周导出缺勤名单。管理员维护实验室基本信息、维护教师和学生的账号、安排每门实验课的上课时间与对应实验室。不要把权限设计得过于复杂。很多基于 JSP 的管理系统败在过度设计上又是菜单树又是角色配置表最后页面比业务还难维护。这个系统的权限只有三层用户表里用一个 role 字段1 学生、2 教师、3 管理员就足够支撑Filter 拦截一下 URL 前缀即可完成权限控制第 5 章会专门讲 Filter。真正需要花心思的是考勤记录表怎么设计才能支撑迟到、早退、缺勤、补签这些状态。业务流程是这样走的管理员先建课程课程绑定实验室和上课时间段学生选课或者由管理员批量导入选课名单到了上课时间学生在网页上签到系统把当前时间和课程计划开始时间对比得出状态下课时签退再对比计划结束时间判断是否早退。2.2 五张核心表的建表 SQL 与字段说明我一般会建五张表用户表 t_user、实验室表 t_lab、课程表 t_course、选课表 t_course_student、考勤表 t_attendance。如果需要请假补签再补一张 t_leave。下面是可直接执行的 MySQL 建表脚本-- 用户表学生、教师、管理员统一存放用 role 区分 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, -- 登录账号学号或工号 password VARCHAR(64) NOT NULL, -- 建议存加盐摘要 real_name VARCHAR(32) NOT NULL, -- 姓名页面显示这个 role TINYINT NOT NULL DEFAULT 1 -- 1学生 2教师 3管理员 ); -- 实验室表 CREATE TABLE t_lab ( id INT PRIMARY KEY AUTO_INCREMENT, lab_name VARCHAR(64) NOT NULL, location VARCHAR(64), -- 楼栋房间如“实验楼A-301” capacity INT DEFAULT 40 ); -- 课程表一次实验课就是一条记录 CREATE TABLE t_course ( id INT PRIMARY KEY AUTO_INCREMENT, course_name VARCHAR(64) NOT NULL, lab_id INT NOT NULL, -- 对应 t_lab.id teacher_id INT NOT NULL, -- 对应 t_user.idrole2 plan_date DATE NOT NULL, -- 上课日期 start_time TIME NOT NULL, -- 计划开始时间 end_time TIME NOT NULL, -- 计划结束时间 CONSTRAINT fk_course_lab FOREIGN KEY (lab_id) REFERENCES t_lab(id), CONSTRAINT fk_course_user FOREIGN KEY (teacher_id) REFERENCES t_user(id) ); -- 选课表课程和学生是多对多 CREATE TABLE t_course_student ( id INT PRIMARY KEY AUTO_INCREMENT, course_id INT NOT NULL, student_id INT NOT NULL, UNIQUE KEY uk_course_student (course_id, student_id) ); -- 考勤表一次签到/签退一行 CREATE TABLE t_attendance ( id INT PRIMARY KEY AUTO_INCREMENT, course_id INT NOT NULL, user_id INT NOT NULL, attend_date DATE NOT NULL, -- 冗余日期统计时少一次 JOIN check_in_time DATETIME NULL, -- 签到时间NULL 表示未签到 check_out_time DATETIME NULL, -- 签退时间NULL 表示未签退 status TINYINT DEFAULT 1, -- 1正常 2迟到 3早退 4缺勤 5补签 remark VARCHAR(128), -- 补签理由等 UNIQUE KEY uk_att_user_course (user_id, course_id, attend_date), KEY idx_course_date (course_id, attend_date) );这段 DDL 有三个细节值得解释。第一权限不单独建表用 role 数字字段是因为这种规模的项目里角色是写死在程序里的做成动态配置只会给自己添堵。第二考勤表里冗余了 attend_date 日期字段虽然日期可以从 check_in_time 里拆但统计“某课程应到多少人、实到多少人”时这个字段能让 SQL 少做一次函数运算。这是答辩时老师爱问的一个点为什么冗余这个字段。第三唯一约束 uk_att_user_course 是这个表最重要的设计它从数据库层面保证了同一个学生同一门课同一天只能有一条考勤记录直接兜住重复签到。关于这一点在第 5 章还会展开。2.3 JDBC 连接池与 DAO 层封装JSP 项目里最常见的错误写法是用 DriverManager.getConnection() 每次开一个物理连接。实验室考勤的高峰期就是上课前后那几分钟学生集中刷签到页连接数瞬间拉高频繁开关连接会让 Tomcat 假死。我一般用连接池统一管理下面这段是常用的工具类用的是 HikariCPpackage com.lab.util; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; public class DBUtil { private static HikariDataSource dataSource; static { try { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/lab_attendance ?useUnicodetruecharacterEncodingutf8 serverTimezoneAsia/Shanghai); config.setUsername(root); config.setPassword(你的密码); config.setMaximumPoolSize(10); // 峰值期最大连接数 config.setMinimumIdle(2); // 空闲保底连接 config.setConnectionTimeout(30000); dataSource new HikariDataSource(config); } catch (Exception e) { throw new ExceptionInInitializerError(e); // 启动即失败别等请求进来才报错 } } public static Connection getConnection() throws Exception { return dataSource.getConnection(); } }这个工具类里值得注意的配置有三个maximumPoolSize 不要调太大Tomcat 默认线程池也就 200连接池给 10 到 15 足够应付一个实验室 60 台机器同时签到minimumIdle 建议设为 2避免高峰期第一波请求全部去创建新连接serverTimezoneAsia/Shanghai 是 Java 8 搭配 MySQL 8.0 必须写的参数少了它直接报时区错误这个问题在高校机房环境里出现频率极高。DAO 层我习惯按表拆类AttendanceDao、CourseDao、UserDao每个类只写增删改查。考勤表 DAO 的核心是两个方法insertAttendance 负责签到updateCheckOut 负责补签退。具体 SQL 会在第 3 章和第 4 章穿插给出。这里要提醒一句不管项目多小SQL 一律用 PreparedStatement不要用字符串拼接。考勤系统的 course_id、user_id 都是外部传入参数拼接 SQL 等于把注入漏洞直接暴露给全校学生。3. 签到签退流程Servlet 状态判断与并发去重考勤系统的核心业务全在签到和签退这一进一出上。学生点击签到浏览器把 courseId 和 userId 发给 ServletServlet 先判断这个学生今天这门课是否已经签过再判断当前时间相对计划开始时间是正常、迟到还是未到签到窗口最后写一条考勤记录。签退类似但只需要更新 check_out_time 并判断是否早退。下面按这条链路拆开讲。3.1 签到 Servlet 的最小实现为了减少配置文件Servlet 用注解注册web.xml 保持干净。签到接口的代码骨架如下package com.lab.servlet; import com.lab.service.AttendanceService; import com.lab.vo.JsonResult; 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 javax.servlet.http.HttpSession; import java.io.IOException; WebServlet(/attendance/checkin) public class CheckInServlet extends HttpServlet { private AttendanceService svc new AttendanceService(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); // 必须放在读取参数之前 HttpSession session req.getSession(); Integer userId (Integer) session.getAttribute(userId); int courseId Integer.parseInt(req.getParameter(courseId)); // 业务层返回值约定0成功 1重复签到 2未到签到时间 3课程不存在 int code svc.checkIn(userId, courseId, new java.util.Date()); JsonResult result new JsonResult(code, message(code)); resp.setContentType(application/json;charsetUTF-8); resp.getWriter().write(result.toJson()); } private String message(int code) { switch (code) { case 0: return 签到成功; case 1: return 今天已签到请勿重复提交; case 2: return 还没到签到时间; default: return 课程不存在或未安排; } } }这段代码有三个细节。第一userId 不是从请求参数拿的而是从 session 拿的。登录成功后把 userId 放 session签到页只传 courseId这样学生就算手工构造请求也换不了身份。第二Servlet 只做参数接收和响应输出真正的判断在 AttendanceService 里JSP 页面里看不到一行 Java 逻辑后面维护时不用在页面堆里找 Bug。第三返回值用 int 约定而不是直接抛出异常因为学生端操作错误是常态与其让 Tomcat 打印 500 页不如把错误原因直接当 JSON 返回给页面提示。3.2 迟到判定与时间比较的细节迟到判定的核心是比较签到时间和课程计划开始时间的差值。常见做法是把两个 Date 对象转换成毫秒再做差避免 SimpleDateFormat 解析字符串带来的格式坑// 时间比较放 service 层用 long 比较毫秒 long diffMinutes (checkInTime.getTime() - planStartTime.getTime()) / 60000; int status; if (diffMinutes -5) { status 2; // 早于计划开始时间 5 分钟以上本次签到无效 } else if (diffMinutes 15) { status 1; // 提前 5 分钟到迟到 15 分钟内都算正常 } else if (diffMinutes 30) { status 3; // 迟到 } else { status 4; // 超过 30 分钟视为缺勤 }为什么给提前 5 分钟和迟到 15 分钟的窗口因为学生提前到实验室坐好等上课是合理行为系统不该把早到的人拒之门外而迟到 15 分钟以内教师通常还处于开课准备阶段可以酌情计入正常。这个阈值不是拍脑袋定的需要和实验室管理制度对齐你在交付前要确认教务那边认可的具体规则。还有一个细节状态 2 的“未到签到时间”建议只对课程开始前超过 5 分钟生效否则学生手滑点一下就会看到“还没到签到时间”体验很差。时间边界验证时不要真去改服务器系统时间风险太大。我一般直接用 SQL 把 t_course 里的 start_time 临时改大或改小跑一遍签到流程再改回来既安全又能复现边界场景。3.3 并发去重业务判断加数据库唯一索引先查询再插入是最自然的签到实现但这里有个经典的竞态窗口两个请求几乎同时到达都完成了“是否已签到”的查询发现没有记录然后各自执行 INSERT结果同一个学生一天出现两条考勤。业务层代码解决不了这个问题需要在数据库层面兜底-- 如果已经建过表执行这条即可 ALTER TABLE t_attendance ADD UNIQUE KEY uk_att_user_course (user_id, course_id, attend_date);加了唯一索引之后第二条 INSERT 会抛 DuplicateKeyException。在 DAO 层捕获这个异常统一返回“重复签到”的提示比业务层先查后插更可靠// AttendanceDao.java try { // 执行 INSERT return INSERT_SUCCESS; } catch (DuplicateKeyException e) { return DUPLICATE_RECORD; // 数据库唯一约束兜底 } catch (SQLException e) { log.error(insert attendance failed, e); return INSERT_FAIL; }注意唯一索引的粒度是 user_id 加 course_id 加 attend_date也就是“某人某天某门课只能一条记录”。这个粒度恰好匹配实验室考勤的真实业务一门实验课一天只上一次不存在同一门课同一天上两遍的情况。如果你的排课允许同一课程一天多个时段唯一索引就要改成 user_id 加 course_id 加 course_date或者把 plan_date 换成具体的开始时间否则学生上午签了下午就签不了。4. 统计报表与页面呈现考勤数据怎么变成老师要的表格实验室考勤做得再好如果教师期末导数据还要一条条对 Excel那系统就没有闭环。这一章解决页面层的问题出勤率统计 SQL 怎么写、分页怎么做、JSP 页面如何用 JSTL 渲染而不是写 Java 脚本片段。4.1 出勤率统计JOIN 与聚合的应用教师端的核心报表是按课程看应到、实到和迟到人数。先看汇总统计-- 按课程汇总出勤情况 SELECT COUNT(*) AS should_count, SUM(CASE WHEN a.id IS NOT NULL THEN 1 ELSE 0 END) AS actual_count, SUM(CASE WHEN a.status 2 THEN 1 ELSE 0 END) AS late_count, SUM(CASE WHEN a.status 3 THEN 1 ELSE 0 END) AS early_count, SUM(CASE WHEN a.status 4 THEN 1 ELSE 0 END) AS absent_count FROM t_course_student cs LEFT JOIN t_attendance a ON a.user_id cs.student_id AND a.course_id cs.course_id WHERE cs.course_id ?;这条 SQL 的要点在 LEFT JOIN 的关联条件里a.course_id cs.course_id 必须写。很多初学者写成 FROM t_course_student LEFT JOIN t_attendance ON a.user_id cs.student_id学生只要在其他课程签过到就会被关联进来统计结果完全失真。逐人明细用另一条 SQL把学生姓名和考勤状态拼在一起SELECT u.real_name, a.status, a.check_in_time, a.check_out_time, CASE WHEN a.id IS NULL THEN 缺勤 WHEN a.status 1 THEN 正常 WHEN a.status 2 THEN 迟到 WHEN a.status 3 THEN 早退 ELSE 缺勤 END AS status_text FROM t_course_student cs JOIN t_user u ON u.id cs.student_id LEFT JOIN t_attendance a ON a.user_id cs.student_id AND a.course_id cs.course_id WHERE cs.course_id ? ORDER BY u.real_name;status 字段的 CASE WHEN 也可以放到 Java 里转换但放 SQL 里有个好处这个查询结果可以直接给导出功能复用后端拿到 List 不需要再循环翻译状态码。如果统计口径里要处理补签记得在 CASE 里加 WHEN a.status 5 THEN 补签。4.2 分页查询LIMIT 偏移量与 PageBean考勤记录会随着学期推进越积越多一个班 40 人、16 周实验课接近 640 条记录不分页的话页面渲染压力很大。分页的经典做法是 LIMIT 加偏移量// AttendanceDao.pageQuery public ListAttendanceVO pageQuery(int userId, int pageNum, int pageSize) { int offset (pageNum - 1) * pageSize; String sql SELECT a.attend_date, a.check_in_time, a.check_out_time, a.status, c.course_name, l.lab_name FROM t_attendance a JOIN t_course c ON c.id a.course_id JOIN t_lab l ON l.id c.lab_id WHERE a.user_id ? ORDER BY a.attend_date DESC LIMIT ?, ?; // 使用 PreparedStatement 传入 offset 和 pageSize }LIMIT 两个参数第一个是偏移量第二个是每页条数。pageNum 从 1 开始所以 offset 要减 1。这里有个习惯值得养成LIMIT 的页码参数在 DAO 里做 int 解析Servlet 收到请求参数后 try-catch 一次解析失败直接默认 pageNum1。否则前端传一个 pageNumabcTomcat 直接 500。记录量超过几万条时LIMIT 大偏移量会明显变慢比如 LIMIT 20000, 10 依然要扫描前 20010 行。但实验室考勤系统的数据量到不了这个量级不需要上游标或者键集分页别过度设计。真到了好几万条优先考虑给考勤表按月归档分区而不是改复杂的分页方案。4.3 页面数据渲染JSTL 替代 Scriptlet图片坐标与图表方案JSP 页面里最影响可维护性的是 Scriptlet也就是% %包着 Java 代码。同样的列表展示用 JSTL 加 EL 表达式的写法是这样% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % table thead trth课程/thth实验室/thth日期/thth状态/th/tr /thead tbody c:forEach items${page.data} varrow tr td${row.courseName}/td td${row.labName}/td tdfmt:formatDate value${row.attendDate} patternyyyy-MM-dd //td td${row.statusText}/td /tr /c:forEach /tbody /table数据由 Servlet 在转发前准备好查询结果放进 PageBean再 setAttribute 到 requestJSP 只负责渲染。这样改动页面样式时不需要关心 Java 代码Java 代码出问题时也不用到 JSP 里翻。个人信息展示页面同样可以做成组合式教师登录后看到的不只是简单资料而是本周课程表加出勤概览。Servlet 一次查出两个数据集合JSP 分成上下两个区域展示多用一次查询换来的信息密度很划算。如果你要在签到页做实验室机位图让点击图片上的座位号完成签到需要注意“坐标定位”的实现方式。服务端存的是百分比坐标而不是像素坐标因为不同屏幕下绝对像素会错位// 页面加载后把服务端预生成的座位坐标渲染成绝对定位元素 c:forEach items${seatList} varseat div classseat>!-- Tomcat conf/server.xmlConnector 节点增加 URIEncoding -- Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 /Tomcat 8.5 以上版本默认就是 UTF-8不用改。但机房环境经常是旧机器加老 Tomcat这条配置留一手能省很多事。5.2 时间解析12 小时制与时区误差**现象**考勤记录里签到时间总是差 12 个小时上午九点的签到变成晚上九点或者偶尔出现时间差 8 小时的诡异情况。**原因**SimpleDateFormat 的模式写成了 yyyy-MM-dd hh:mm:ss小写 hh 是 12 小时制下午一点之后的时间全部错位。还有一部分是 JDBC 连接串缺少 serverTimezone 参数驱动用服务器默认时区换算和本地时区差出 8 小时。**解决**格式化时间全用大写 HH即 yyyy-MM-dd HH:mm:ss更省心的做法是项目中不手动格式化时间字符串直接用 java.util.Date 传给 PreparedStatement 的 setTimestamp让 JDBC 驱动完成转换。连接串里的 serverTimezoneAsia/Shanghai 必须加上。顺带一个容易忽略的坑存储时间时不要用数据库的 NOW() 函数改用 Java 代码里注入的时间否则应用服务器和数据库服务器跨机房时两边系统时间不一致考勤判定会跟着乱。5.3 重复签到先查后插的竞态窗口**现象**学生快速点了两次签到按钮数据库里同一个学生同一门课出现了两条记录。**原因**业务层先执行 SELECT 判断是否已签到再执行 INSERT。两个并发请求都通过了 SELECT然后各自 INSERT重复记录就产生了。这不是学生手速的问题是代码层面的竞态条件。**解决**分两层处理。第一层是业务层判断减少正常情况下的无效写操作第二层是数据库唯一索引这才是兜底方案。第 2 章建表时加了 UNIQUE KEY uk_att_user_course (user_id, course_id, attend_date)第二个 INSERT 会触发 DuplicateKeyExceptionDAO 捕获后返回“重复签到”的提示即可。如果你用的是改过的表结构检查唯一索引的字段组合是否和业务口径一致别把日期字段漏掉否则同一学生同一课程跨天也会被唯一索引挡住。5.4 JDBC 驱动版本与 MySQL 8.0 时区错误**现象**项目原本在 MySQL 5.7 跑得好好的换到 MySQL 8.0 环境后启动时报 ClassNotFoundException 或 “The server time zone value ‘Öйú±ê׼ʱ¼ä’ is unrecognized”。**原因**MySQL 8.0 对应的 Connector/J 驱动类名变成了 com.mysql.cj.jdbc.Driver连接串也需要显式指定时区否则 JDBC 校验数据库时区直接抛异常。另外很多下载下来的 zip 包里同时混了多个版本的 mysql-connector jarClassLoader 加载到旧版本就会出现奇怪的符号错误。**解决**确认 WEB-INF/lib 下只有一个 mysql-connector-java jar版本和 MySQL 服务端大版本匹配。Class.forName 或配置里的 driver 类名换成 com.mysql.cj.jdbc.Driver连接串补齐下面两段参数jdbc:mysql://localhost:3306/lab_attendance?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseuseSSLfalse 是另一个常见错误源MySQL 8.0 默认开启 SSL 认证本地测试环境通常没配证书不关掉就一直握不上手。5.5 修改 JSP 不生效work 目录与热部署**现象**改了 JSP 文件刷新浏览器还是旧页面重启 Tomcat 之后又正常了。**原因**Tomcat 会把 JSP 编译成 Java 文件再编译成 class缓存在 CATALINA_HOME/work 目录下。IDE 部署项目时不一定会清掉这个缓存旧编译产物被优先加载看起来就是改了没生效。另一种可能浏览器端缓存了旧 HTML 页面尤其是按 F5 刷新而没有强制刷新时。**解决**开发期直接把 Tomcat 的 work 目录清掉再重启路径在 TOMCAT_HOME/work/Catalina/localhost 下删除整个项目同名目录即可。如果用的是 IDE 集成 Tomcat右键 Tomcat 执行 Clean Work Directory 再重启。部署到生产环境前重新打 war 包并删掉旧解压目录不要手动往已解压的目录里覆盖 class。浏览器排查时按 CtrlF5 强制刷新排除缓存干扰。这条虽然和业务无关但在调试 JSP 项目时出现的频率极高值得单独列出来。6. 部署验收与导出技巧交出去之前做这几件事系统功能写完只是第一步交付前一定要做一轮完整的验收测试。我自己的固定流程是三步第一步准备两个浏览器窗口模拟两个学生账号同一时间点击签到验证数据库唯一索引是否把重复请求挡在了门外第二步用 SQL 把 t_course 里某条课程的 start_time 临时改到当前时间前后分别测试未到签到时间、正常、迟到三种状态改完再还原第三步用教师账号跑一遍汇总查询回到 t_course_student 表人工核对报名人数和出勤人数是否一致。这三步走完系统能不能交心里基本有数。导出功能是教师最常用的不一定要为了导出 Excel 引入 Apache POI 这种重依赖。一个轻量方案是用 CSV 加 BOM 头Excel 能直接打开不乱码// ExportAttendanceServlet.java 片段 resp.setContentType(text/csv;charsetUTF-8); resp.setHeader(Content-Disposition, attachment;filenameattendance_2024_01.csv); resp.getWriter().write(\uFEFF); // BOM关键字符 // 按行写入表头和考勤数据BOM 字符 \uFEFF 是 Excel 识别 UTF-8 的关键少了它用 Excel 打开 CSV 中文会乱码。这个方案代码量小、不引入额外 jar适合课设和半内部工具场景。如果教师需要的是带格式的 Excel再考虑 POI但那种需求通常排在系统开发之后没必要一开始就背上。我现在的习惯是这类 JSP 考勤系统交付前一定在干净环境里从零部署一遍停 Tomcat、删 work、清库重建、重新打 war 包、启动、跑冒烟测试。这一套走完就不会再有学生拿着源码包来问“为什么我这边跑不起来”了。希望帮到你。本文还有配套的精品资源点击获取
返回列表