资讯详情

资讯详情

SSM疫情打卡管理系统毕业设计:核心业务约束与工程落地细节

简介这是一套基于SSM框架的疫情打卡管理系统完整项目面向计算机相关专业做毕业设计的学生以及需要Java项目实战练习的学习者也可用于课程设计与期末大作业。项目已通过导师指导并获高分评价下载后无需修改即可直接运行对新手较为友好。压缩包共139个文件约45.88MB涵盖xml配置、jar依赖、class编译文件、jsp页面、java源码、css与js前端资源、properties配置、sql数据库脚本及docx项目说明等构成从后端逻辑到前端展示的完整工程结构。系统围绕用户登录注册、打卡数据管理等核心模块展开源码与数据库脚本配套齐全便于理解SSM三层架构的实际落地方式。目前已有164人学习关注适合希望快速获得可运行毕设项目、对照源码梳理开发流程并完成答辩准备的读者参考使用。1. 疫情打卡管理系统SSM 毕业设计里最容易被低估的工程细节很多同学拿到「基于 SSM 的疫情打卡管理系统」这个题目时第一反应是「不就是增删改查吗」。我带过几届毕业设计见过太多人三天写完 CRUD然后在答辩现场被老师一句「你这个打卡记录怎么防止同一个人一天提交两次」问到哑口无言。疫情打卡这个场景看着简单但它天然带着几个真实业务约束一人一天一条记录、体温数值有合理区间、打卡时间要卡在当天、异常状态要能追溯。这些约束才是这个题目真正的技术含量所在。SSM 指的是 Spring SpringMVC MyBatis 这套经典组合虽然现在新项目更多用 Spring Boot但毕业设计里 SSM 依然是主流因为它的分层结构清晰答辩时好讲老师也熟悉。这套系统通常包含学生端打卡、教师端查看统计、管理员端管理用户和导出数据三类角色。适合正在做计算机毕业设计、需要一套能跑通、能讲清、能改动的 Java Web 项目的同学。下面我按实际落地的顺序把选型、建库、核心打卡逻辑、统计导出和踩坑一条条讲清楚。2. 环境与选型为什么这套 SSM 组合在答辩时最稳2.1 技术栈版本怎么定才不翻车毕业设计最怕的不是功能少而是环境跑不起来。我一般会推荐一套经过大量项目验证的稳定组合而不是追最新版本。JDK 用 8Tomcat 用 8.5 或 9MySQL 用 5.7 或 8.0Maven 用 3.6 以上。这套组合的兼容性最好网上能查到的报错解决方案也最多。组件推荐版本选它的理由JDK1.8SSM 生态最成熟注解和泛型支持够用Spring5.2.x与 JDK8 配合稳定注解配置完善SpringMVC5.2.x与 Spring 同版本避免依赖冲突MyBatis3.5.x支持注解和 XML 两种写法答辩好讲MySQL5.7 / 8.05.7 兼容老驱动8.0 注意驱动类名变化Tomcat8.5 / 98.5 对老项目友好9 需要留意 javax 包Maven3.6依赖管理清晰打包 war 方便这里有个血泪经验MySQL 8.0 的驱动类是com.mysql.cj.jdbc.Driver而 5.x 是com.mysql.jdbc.Driver连接串还要加时区和 SSL 参数。很多同学本地用 5.7 跑通换到 8.0 就报「Unknown system variable」就是驱动和连接串没同步改。2.2 分层结构怎么划分才经得起追问SSM 的标准分层是 Controller、Service、MapperDAO、Entity。但答辩老师常问的是「你为什么要分这么细」。我的回答逻辑是Controller 只负责接收参数和返回结果不写业务判断Service 承担事务和业务规则比如「一天只能打一次卡」这种校验必须放在 ServiceMapper 只做数据库操作。这样分层的好处是当老师让你改一个规则时你只需要动 Service 一层不会牵一发动全身。包结构我一般这样组织com.example.epidemic ├── controller // 接收请求 ├── service // 业务接口 │ └── impl // 业务实现事务在这里 ├── mapper // MyBatis 接口 ├── entity // 数据库实体 ├── vo // 前端展示对象 └── util // 工具类如日期处理这个结构不是摆设。比如统计功能需要把打卡记录按班级聚合返回的字段和数据库表不一致这时候就用 VO 承接而不是硬改 Entity。老师看到你有 VO 层会认为你理解「数据库模型和展示模型要分离」这个点。2.3 依赖配置里最容易漏的三处pom.xml 里除了 Spring、SpringMVC、MyBatis 三大件还有几个依赖经常被漏掉导致运行时报 ClassNotFound。第一个是jackson-databind返回 JSON 时没有它会报 406第二个是commons-fileupload做 Excel 导入导出时要用第三个是druid或c3p0连接池不配连接池在高并发测试时会连接泄漏。!-- JSON 转换Controller 返回对象时必需 -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.13.3/version /dependency !-- 文件上传Excel 导入用 -- dependency groupIdcommons-fileupload/groupId artifactIdcommons-fileupload/artifactId version1.4/version /dependency !-- 连接池避免每次请求新建连接 -- dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.8/version /dependency参数说明jackson 版本要和 Spring 版本大致匹配2.13 配 Spring 5.2 没问题fileupload 1.4 修复了早期版本的安全问题druid 1.2.8 稳定配置里记得设initialSize和maxActive。漏掉连接池的后果是答辩演示时多人同时点系统直接卡死这个翻车现场我见过不止一次。3. 数据库设计打卡表的时间字段和唯一约束怎么定3.1 核心表结构与字段类型选择疫情打卡系统的表不多但每张表的字段类型都有讲究。用户表、打卡记录表、班级表、健康状态字典表这四张基本够用。重点是打卡记录表它承载了业务核心。CREATE TABLE clock_record ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, user_id bigint(20) NOT NULL COMMENT 打卡用户ID, clock_date date NOT NULL COMMENT 打卡日期精确到天, temperature decimal(4,1) NOT NULL COMMENT 体温如36.5, health_status tinyint(2) NOT NULL DEFAULT 0 COMMENT 0正常 1发热 2咳嗽 3其他, location varchar(100) DEFAULT NULL COMMENT 当前位置, remark varchar(255) DEFAULT NULL COMMENT 备注, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 提交时间, PRIMARY KEY (id), UNIQUE KEY uk_user_date (user_id,clock_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT每日打卡记录;逻辑说明clock_date用 date 而不是 datetime因为业务规则是「一天一条」用日期做唯一约束的一部分最直接。temperature用 decimal(4,1)能存 36.5 这种一位小数范围到 99.9够用且不会出现浮点误差。health_status用 tinyint 做枚举比 varchar 省空间且查询快。参数说明唯一索引uk_user_date是防重复打卡的第一道防线但光靠它不够因为用户重复提交时数据库会抛异常体验差。所以 Service 层要先查一次数据库约束作为兜底。create_time默认当前时间方便统计提交时段。3.2 唯一约束和业务校验的分工很多同学只靠数据库唯一索引结果用户点两次提交第二次直接报 500 错误页面上显示一堆异常堆栈。正确做法是两层防护Service 层先根据 userId 和当天日期查一次存在就返回「今日已打卡」数据库唯一索引作为最后保障防止并发下的极端情况。// Service 层校验先查后插 public Result clockIn(Long userId, ClockRecord record) { // 查询今天是否已打卡 ClockRecord exist clockRecordMapper.selectByUserAndDate(userId, LocalDate.now()); if (exist ! null) { return Result.fail(今日已打卡请勿重复提交); } record.setUserId(userId); record.setClockDate(LocalDate.now()); // 体温合理性校验 if (record.getTemperature().compareTo(new BigDecimal(35.0)) 0 || record.getTemperature().compareTo(new BigDecimal(42.0)) 0) { return Result.fail(体温数值不在合理范围); } clockRecordMapper.insert(record); return Result.success(打卡成功); }逻辑说明先查后插能给出友好提示体温校验放在插入前避免脏数据进库。LocalDate.now()取服务器当天日期注意如果部署在时区不对的服务器上可能差一天这个坑后面会讲。参数说明体温下限设 35.0、上限设 42.0 是常见做法低于 35 可能是测量错误高于 42 在医学上极少见。这个范围可以根据实际需求调整但一定要有否则用户输入 999 也能存进去。3.3 统计查询需要的索引教师端要按班级、按日期统计打卡率这涉及clock_record和user的关联查询。如果数据量到几千条没有索引会明显变慢。除了主键和唯一索引建议在clock_date上单独加一个普通索引因为按日期范围筛选是高频操作。ALTER TABLE clock_record ADD INDEX idx_clock_date (clock_date);这个索引在「查询某天所有打卡记录」时生效。如果还要按班级统计通常先查班级下的用户 ID 列表再用user_id in (...)查打卡记录这时候user_id已经在唯一索引的最左列能复用。不要盲目加一堆索引写入时会变慢毕业设计的数据量下两三个索引足够。4. 打卡与统计功能落地从 Controller 到 Mapper 的完整链路4.1 打卡接口的参数接收与返回格式打卡接口是学生端最核心的入口。Controller 接收前端传来的体温、健康状态、位置从 Session 里取当前登录用户 ID然后调用 Service。返回格式统一用Result对象包含 code、msg、data 三个字段前端好处理。RestController RequestMapping(/api/clock) public class ClockController { Autowired private ClockService clockService; PostMapping(/in) public Result clockIn(RequestBody ClockRecord record, HttpSession session) { // 从 Session 取登录用户避免前端伪造 userId Long userId (Long) session.getAttribute(userId); if (userId null) { return Result.fail(401, 请先登录); } return clockService.clockIn(userId, record); } }逻辑说明userId 从 Session 取而不是前端传这是安全底线。如果前端传 userId别人改一下就能替别人打卡。RequestBody接收 JSON前端用 axios 提交时 content-type 要设 application/json。参数说明Result里的 code 用 200 表示成功401 表示未登录500 表示业务失败。前端根据 code 判断不要只判断 HTTP 状态码因为业务失败时 HTTP 仍是 200。4.2 MyBatis 映射文件里的日期处理MyBatis 处理LocalDate需要额外配置否则会报「No typehandler found」。在 MyBatis 3.5 以上内置了 JSR-310 支持但要在配置文件里确认没有关掉。如果用的是老版本需要自己写 TypeHandler。!-- ClockRecordMapper.xml 片段 -- select idselectByUserAndDate resultTypecom.example.epidemic.entity.ClockRecord SELECT * FROM clock_record WHERE user_id #{userId} AND clock_date #{date} /select select idcountByDateRange resultTypeint SELECT COUNT(*) FROM clock_record WHERE clock_date BETWEEN #{start} AND #{end} /select逻辑说明selectByUserAndDate用于打卡前的重复校验参数是 userId 和 LocalDate。countByDateRange用于统计某段时间的打卡总数返回 int。参数说明#{date}传 LocalDate 时MyBatis 会自动转成 java.sql.Date。如果报类型错误检查 mybatis-config.xml 里是否注册了LocalDateTypeHandler或者升级 MyBatis 到 3.5 以上。4.3 教师端统计的聚合查询教师端要看到「某天某班级的打卡人数和未打卡名单」。这个查询分两步先查班级下所有学生再查这些学生当天的打卡记录最后做差集。不要试图用一条 SQL 搞定可读性差且不好维护。public StatVO getClassStat(Long classId, LocalDate date) { // 1. 查班级所有学生 ListUser students userMapper.selectByClassId(classId); // 2. 查当天已打卡的用户ID ListLong clockedIds clockRecordMapper.selectUserIdsByDate(date); // 3. 计算未打卡名单 ListUser notClocked students.stream() .filter(s - !clockedIds.contains(s.getId())) .collect(Collectors.toList()); StatVO vo new StatVO(); vo.setTotal(students.size()); vo.setClocked(students.size() - notClocked.size()); vo.setNotClockedList(notClocked); return vo; }逻辑说明用 Java 做差集而不是 SQL是因为逻辑清晰老师一眼能看懂。数据量大时可以在 SQL 层用 LEFT JOIN 优化但毕业设计的数据量下这种写法完全够用。参数说明selectUserIdsByDate只查 user_id 字段减少数据传输。StatVO里的notClockedList用于前端展示未打卡名单方便教师催报。4.4 Excel 导出用 POI 还是 EasyExcel教师端常要求导出打卡数据。用 Apache POI 写起来繁琐但可控性强用 EasyExcel 代码少但多一个依赖。我一般推荐 EasyExcel因为毕业设计时间紧少写代码少出错。// EasyExcel 导出示例 public void export(HttpServletResponse response, LocalDate start, LocalDate end) throws IOException { ListClockExportVO list clockService.listForExport(start, end); response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment;filenameclock_data.xlsx); EasyExcel.write(response.getOutputStream(), ClockExportVO.class) .sheet(打卡数据) .doWrite(list); }逻辑说明设置响应头让浏览器识别为下载文件ClockExportVO上用ExcelProperty注解指定列名。doWrite一次性写入数据量大时可以用分页写入。参数说明文件名用英文避免乱码中文文件名需要 URLEncoder 编码。Content-Disposition里的 filename 如果含中文不同浏览器表现不一致稳妥做法是编码后传。5. 避坑与排查这套系统跑不起来时先看这几处5.1 现象启动报「No qualifying bean of type」原因Spring 没扫描到 Service 或 Mapper。常见于包路径写错或者Service、Mapper注解漏加。解决检查 spring 配置文件里的component-scan路径是否覆盖到 service 包MyBatis 的MapperScannerConfigurer里basePackage是否指向 mapper 包。如果用的是注解式MapperScan确认写在配置类上。5.2 现象打卡时间比实际早或晚一天原因服务器时区不是东八区LocalDate.now()取的是 UTC 日期。解决在 JVM 启动参数加-Duser.timezoneAsia/Shanghai或者在数据库连接串里指定serverTimezoneAsia/Shanghai。这个坑在云服务器上特别常见本地跑没事一部署就出问题。5.3 现象中文存入数据库变成问号原因数据库字符集不是 utf8mb4或者连接串没指定编码。解决建库时用CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci连接串加useUnicodetruecharacterEncodingutf8。注意 utf8 在 MySQL 里最多存 3 字节emoji 存不了用 utf8mb4 才彻底。5.4 现象重复提交导致数据库唯一键冲突报 500原因只靠数据库唯一索引没在 Service 层做前置校验或者并发下两个请求同时通过了前置校验。解决Service 层先查后插捕获DuplicateKeyException返回友好提示。极端并发下可以用INSERT ... ON DUPLICATE KEY UPDATE或者分布式锁但毕业设计场景下前置校验加异常捕获就够了。5.5 现象Excel 导出打开是乱码或提示文件损坏原因响应头没设对或者输出流里混入了其他内容。解决确认Content-Type是application/vnd.openxmlformats-officedocument.spreadsheetml.sheet并且在写 Excel 之前没有response.getWriter()输出过任何字符。一旦用了 Writer再拿 OutputStream 就会冲突。6. 让答辩加分的一个技巧把打卡数据做成可视化趋势图功能跑通只是及格线想让老师记住你得有一个亮点。我一般会建议在教师端加一个「近 7 天打卡率趋势图」用 ECharts 在前端渲染后端只返回日期和打卡率两个数组。这个功能代码量不大但视觉效果强答辩时演示很抓眼球。后端接口这样写GetMapping(/trend) public Result trend(RequestParam Long classId) { ListString dates new ArrayList(); ListBigDecimal rates new ArrayList(); LocalDate today LocalDate.now(); for (int i 6; i 0; i--) { LocalDate d today.minusDays(i); dates.add(d.format(DateTimeFormatter.ofPattern(MM-dd))); int total userMapper.countByClassId(classId); int clocked clockRecordMapper.countByClassAndDate(classId, d); // 避免除以零 BigDecimal rate total 0 ? BigDecimal.ZERO : BigDecimal.valueOf(clocked * 100.0 / total).setScale(1, RoundingMode.HALF_UP); rates.add(rate); } MapString, Object data new HashMap(); data.put(dates, dates); data.put(rates, rates); return Result.success(data); }逻辑说明循环取近 7 天每天算一次打卡率。setScale(1, HALF_UP)保留一位小数前端显示更整洁。除以零的判断必须有否则新班级没有学生时会抛异常。参数说明classId从请求参数取前端下拉框选择班级后触发。日期格式用MM-dd前端 X 轴显示短日期更清爽。返回的 rates 是百分比数值ECharts 的 y 轴设max: 100即可。前端用 ECharts 的折线图配置把 dates 和 rates 塞进 series 就行。这个功能的价值在于它把「打卡」这个动作变成了「可观察的趋势」老师会认为你不仅会写 CRUD还有数据意识。我自己的习惯是每做一个管理系统都至少留一个可视化出口因为答辩时间有限一张图比十页代码更能说明问题。最后说个教训别等到答辩前一天才部署到服务器。本地 Tomcat 和服务器 Tomcat 的版本差异、时区差异、数据库权限差异每一个都能让你熬夜。提前一周在目标环境跑一遍把上面那些坑挨个踩一遍比事后补救强得多。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →