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

资讯详情

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

SSM+Maven学生考勤管理系统:从架构设计到Excel导出实战详解

SSM+Maven学生考勤管理系统:从架构设计到Excel导出实战详解 学生考勤管理系统这类项目在Java学习圈和企业内部系统里都属于“万金油”级别的练手项目但能把它做扎实、讲清楚的人并不多。这个基于JavaWeb和MySQL的SSMMaven学生考勤管理系统核心就是用Spring、SpringMVC、MyBatis这套经典组合配合Maven管理依赖加上POI做Excel导出、EasyUI做后台界面、JFreeChart出统计图表最终跑在JSP页面上。它解决的是学校里最真实的痛老师要记录学生出勤、请假、迟到情况还要按班级、按课程统计导出报表。对正在学Java的人来说这个系统几乎把所有JavaWeb高频考点都串起来了对要做毕业设计或者企业小工具的人来说它又是一个可以直接改改就用的模板。我前前后后带人做过几版类似的考勤系统踩过的坑比写出来的代码多。这篇文章就把整个项目的架构思路、数据库设计、核心实现、排错过程全部拆开讲包括为什么这么选型、每个配置文件的用意、POI导出时那些让人崩溃的编码问题尽量把文档里不写的经验也补上。1. 项目定位与整体技术选型1.1 为什么还是SSM Maven而不是Spring Boot说实话现在新项目用Spring Boot的越来越多但SSM这个组合并不过时很多学校、传统企业的内部系统至今还在维护着SSM的代码。选SSM最大的理由是“原理透明”。Spring管对象、SpringMVC管请求分发、MyBatis管SQL三层之间边界非常清楚你可以看到一次请求从浏览器到数据库再返回的完整路径。而Spring Boot把这些都通过自动配置封装在背后遇到问题反而容易两眼一抹黑。Maven在这个项目里的作用同样关键。用SSM意味着你要面对一长串依赖spring-core、spring-webmvc、mybatis、mybatis-spring、mysql-connector、jackson、poi、jfreechart、pagehelper。如果没有Maven光是把这些jar包一个个下载好、版本对应上、放进lib目录就足够消耗半天时间。Maven用坐标来管理jar包pom.xml里声明依赖它自动去仓库下载并传入classpath构建时还能统一打包。这也是为什么很多公司明确规定Java项目必须用Maven目的就是让构建过程可重复、可复制。我见过不少同学拿着Spring Boot练习了好几个项目回到SSM却连web.xml都配不明白这其实就是基础没打牢。做这个考勤系统你不需要会特别高深的技术但需要对“一个Web应用是如何跑起来的”有完整认知。这个认知SSM项目能给你Spring Boot给不了。1.2 EasyUI、JFreeChart、POI这三个工具为什么合适前端框架选了EasyUI而不是Vue或React不是因为它时髦而是因为它“刚刚好”。这是一个典型的管理系统页面左侧菜单、顶部工具栏、中间表格、弹窗表单。EasyUI基于jQuery通过HTML属性就能渲染出表格和弹窗后端只要返回特定格式的JSON数据比如{total: 100, rows: [...]}它就能自动分页、显示数据。前后端没有分离不需要考虑跨域、Token鉴权、前端构建这些额外复杂度能把精力聚焦在业务逻辑上。JFreeChart的选择也类似它是纯Java的图表库由后端生成柱状图、饼图的图片字节流直接输出到浏览器。你不需要引入ECharts、Chart.js这类前端图表库再写一堆JavaScript配置对JSPServlet技术栈的项目特别友好。虽然JFreeChart的默认样式看起来比较朴素但做后台报表够用了而且它是服务器端生成的图片放到哪里都能显示不受前端框架限制。POI则是Java操作Excel的事实标准这个项目里用它做考勤报表导出。为什么不直接在前端用表格转Excel因为POI能精确控制单元格合并、列宽、样式还能生成多Sheet这是前端方案很难做到的。业绩要点是POI导出的Excel不是“看起来像表格的文本”而是真正的二进制Excel文件Excel、WPS都能正常打开和编辑。1.3 系统功能模块拆解考勤系统看着简单拆开之后功能其实不少。我从实际使用者角度把系统分成五个模块首先是登录认证模块用户分为管理员和教师登录成功后通过拦截器校验Session未登录不能访问任何页面。第二个是学生信息管理维护学生的基础数据包括学号、姓名、班级、专业支持增删改查和批量导入。第三是考勤模块这是核心中的核心支持按课程和日期记录每个学生的出勤状态状态包括出勤、迟到、早退、请假、缺勤。第四是请假管理学生提交请假申请教师审核审核通过后自动算作出勤。第五是统计报表模块支持按班级、按课程、按时间段统计出勤率用JFreeChart生成图表用POI导出Excel明细。模块划分最大的作用是让开发可以并行推进。比如一个人写学生管理另一个人写考勤记录只要提前约定好接口和表结构互相之间不影响。而且这种模块化思路本身就是一种设计能力面试官问“你项目里有哪些模块”不是想听你背术语是想看你能不能把一个业务拆成可实现的单元。2. 数据库设计与考勤业务拆解2.1 考勤业务到底在管什么考勤业务容易想简单了以为就是在表格里打勾。实际上完整的考勤业务是一个闭环先录入学号和课程信息再记录每天的考勤状态接着处理请假申请最后汇总统计。这里最关键的是搞清楚“考勤状态”和“请假申请”是两个独立概念不能混在一起。请假要先提交申请审核通过后系统在统计时自动把请假状态算作出勤的一种特殊情况如果审核不通过则该学生还是缺勤。另外一个业务细节是“一个学生一天可能有多条考勤记录”因为他要上多门课程。所以考勤记录表不能只存学生和日期还要存课程和节次。如果不设计好这个维度后面统计“某门课的出勤率”时就会出问题。我见过有人把考勤记录表设计成每天一条一个学生一天只能有一个状态结果遇到上午出勤、下午请假的情况根本没法存。所以设计和课程ID的关联是必须的这是这个项目表结构里最容易踩的坑。2.2 核心表结构设计整个系统我推荐设计四张核心表学生表、课程表、考勤记录表、请假表。先看学生表主键用自增id学号用唯一索引。为什么不直接把学号当主键因为学号有可能因为转专业、休学而复用或者变更自增id与业务无关能避免主键变更引发连锁问题。课程表比较简单但要注意加一个教师字段因为考勤是按课程记录的统计时也经常要按任课教师筛选。考勤记录表是整个系统的枢纽字段包括id、student_id、course_id、attendance_date、status。status用int类型约定0缺勤、1出勤、2迟到、3早退、4请假。“这里一定要在数据库里写注释别问我为什么强调我见过太多人半年后回来改需求对着0、1、2完全不知道是什么意思。”请假表则需要保存请假时间段、原因、状态status用来区分待审核、已通过、已驳回。我拿之前项目里用到的表结构做了一个简化版表名关键字段说明studentid, student_no, name, gender, class_name, majorstudent_no加唯一索引courseid, course_name, teacher, credit按教师统计时要用attendanceid, student_id, course_id, attendance_date, statusstatus0缺勤 1出勤 2迟到 3早退 4请假leaveid, student_id, start_date, end_date, reason, statusstatus0待审 1通过 2驳回关于外键我的建议是逻辑外键而不是物理外键。很多教学视频里都喜欢加上FOREIGN KEY但实际开发中主流做法并不加物理外键理由很现实物理外键会让插入和删除操作多做几次约束检查数据量大时影响性能而且一旦需要分表、分库或者做数据迁移物理外键会变成巨大的障碍。逻辑外键就是通过student_id这个字段在应用层维护关联配合合理索引效果完全够用。当然前提是你的代码里绝对不能出现“删了一个学生却留下考勤记录”这种孤儿数据这也是为什么Service层要写删除学生时级联清理考勤记录的逻辑。2.3 统计报表的SQL怎么写报表的本质是对考勤记录进行分组聚合。最常用的统计维度是个人出勤率、班级出勤率、课程出勤率。以班级出勤率为例核心逻辑是“每人每门课应出勤次数”和“实际出勤次数”的比值。如果一个班有40人一门课上了10次那么应出勤次数是400次实际出勤状态为出勤的有380次出勤率就是95%。SQL上可以用条件聚合来写比如统计某课程各状态数量SELECT course_id, COUNT(*) AS total_count, SUM(CASE WHEN status 1 THEN 1 ELSE 0 END) AS present_count, SUM(CASE WHEN status 2 THEN 1 ELSE 0 END) AS late_count, SUM(CASE WHEN status 3 THEN 1 ELSE 0 END) AS early_count FROM attendance WHERE course_id #{courseId} GROUP BY course_id;这里用SUM(CASE WHEN ...)而不是WHERE ...好处是只需要一次分组就能同时算出所有状态的数量性能好很多。如果你统计的是全班出勤率就需要把attendance表JOIN student表再按class_name分组。这些SQL都不复杂但它们是报表功能的地基写得好不好直接影响页面加载速度。我的经验是统计SQL宁可多写几行也要保持可读性尤其是表别名和字段别名一定要命名清楚不然三周之后你自己都看不懂。3. 实操过程与核心环节实现3.1 环境准备与Maven仓库加速开发这个系统建议用JDK 1.8虽然现在JDK 17、21已经很普及但SSM生态下1.8依然是兼容性最好的版本很多老框架在高版本JDK上会有模块化问题。数据库用MySQL 5.7或者8.0都行注意8.0的驱动类名和连接参数和5.x不一样后面排错部分会专门讲。Maven推荐3.6.3太新或太老的版本在IDE里都有可能出现兼容性问题。Maven安装好之后第一件事不是建项目而是改配置文件。修改conf/settings.xml在mirrors节点加入阿里云镜像mirror idaliyunmaven/id mirrorOf*/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror这一步做完pom.xml里声明的依赖下载速度能有质的提升。我用中央仓库和一个新配置好的镜像分别试过同样的项目中央仓库在高峰期能卡十几分钟镜像通常几秒就完成。另外建议把本地仓库路径从默认的C:\Users\用户名\.m2\repository改到其他盘比如D:\maven-repo这样重装系统或者迁移开发环境时直接把整个仓库目录拷走项目就能离线构建省去重新下载的时间。别小看这个操作我帮同事处理过好几次因为系统盘满了导致Maven构建失败的问题换了仓库路径之后再也没有发生过。3.2 核心依赖清单pom.xml是这个项目的命脉我把核心依赖整理成一个简表方便你对照着引入依赖版本建议用途spring-webmvc5.1.xMVC框架spring-jdbc5.1.x事务与数据访问支持mybatis3.4.xORM框架mybatis-spring2.0.xMyBatis与Spring整合mysql-connector-java5.1.49或8.0.xJDBC驱动druid1.1.x数据库连接池jackson-databind2.9.xJSON序列化poi4.1.xExcel导出jfreechart1.5.x图表生成pagehelper5.1.x分页插件jstl1.2JSP标签库这些版本在我实际项目中都用过互相之间是兼容的。但要注意Spring 5.x至少需要JDK 8如果你还在用JDK 7就必须降级到Spring 4.x这也是为什么前面强调JDK选型要谨慎。引入依赖的时候强烈建议给mysql-connector-java、poi这些容易出兼容性问题的依赖加上正确的版本号不要用最新版本因为最新版本对老项目不一定友好。3.3 SSM整合的四个关键配置文件SSM整合本质上就是把Spring、SpringMVC、MyBatis三个框架通过配置文件串起来。我习惯用四个配置文件来管理分工明确出了问题也容易定位。第一个是web.xml。它负责配置DispatcherServlet、ContextLoaderListener和字符编码过滤器。有几个容易错的点DispatcherServlet的url-pattern推荐用/而不是/*/*会把JSP请求也拦截掉导致页面全部404。字符编码过滤器要放在所有过滤器最前面不然POST请求的中文参数会乱码。ContextLoaderListener负责加载Spring根容器DispatcherServlet加载SpringMVC子容器父子容器的分工是父容器管理Service和Mapper子容器管理Controller。第二个是spring-mvc.xml。核心配置包括开启注解驱动、配置组件扫描到Controller包、配置视图解析器。视图解析器里有一个非常容易被忽略的细节prefix设为/WEB-INF/views/suffix设为.jsp这样Controller返回的字符串就会自动拼接成真正的JSP路径。同时一定要加mvc:default-servlet-handler/和mvc:resources配置把静态资源放行否则JS、CSS、图片全部会被拦截。第三个是spring-mybatis.xml这是整合的关键。要配置Druid数据源包括url、用户名、密码和驱动类名。然后配置SqlSessionFactoryBean里面引用数据源同时指定MyBatis全局配置文件和mapper-locations的位置。最后用MapperScannerConfigurer扫描DAO接口包让它自动生成实现代理。常见的错误是忘了配置mapper-locations结果启动不报错一调用Mapper方法就报“Invalid bound statement”这个问题会让我在排错部分专门提一次。第四个是mybatis-config.xml。这里最值得开的是驼峰命名映射mapUnderscoreToCoreCase设为true。因为Java属性是驼峰写法比如studentName数据库字段是下划线比如student_name开启这个配置后MyBatis会自动映射省去写大量resultMap的麻烦。日志配置也推荐开启控制台能看到SQL语句对调试非常有用。事务配置放在spring-mybatis.xml或者单独的spring-tx.xml里用DataSourceTransactionManager再配合tx:annotation-driven/开启注解事务。这样Service层方法只要加上Transactional就能自动提交和回滚。3.4 POI导出Excel的核心实现POI导出考勤报表是这个项目里最值得讲透的部分因为它直接面对用户做得不好会显得项目很业余。我用的是POI 4.1.x支持HSSF和XSSF两种工作簿。简单来说HSSF对应2003版的.xls文件最多支持65536行XSSF对应2007版之后的.xlsx文件行数上限大幅增加但内存占用也高。如果导出数据量大更推荐用SXSSFWorkbook它是XSSF的流式版本滑动窗口式写入内存占用可以控制在几十MB以内。以导出考勤明细表为例核心流程是这样的创建Workbook创建Sheet创建表头行并设置样式遍历数据逐行写入最后写到HttpServletResponse的输出流。表头样式里常用的操作是合并单元格比如大标题跨列居中用addMergedRegion实现。列宽也需要手动设置不然默认宽度下中文显示很挤用sheet.setColumnWidth逐个指定。文件名的中文编码问题是我见过最多人踩的坑。很多人直接response.setHeader(Content-Disposition, attachment;filename考勤报表.xlsx)结果浏览器下载的文件名全是乱码。正确做法是转成URL编码String fileName URLEncoder.encode(考勤报表.xlsx, UTF-8); response.setHeader(Content-Disposition, attachment;filename fileName);ContentType也要设对导出xlsx要写成application/vnd.openxmlformats-officedocument.spreadsheetml.sheet导出xls则用application/vnd.ms-excel。这两个MIME类型搞错的话浏览器会试图用文本方式打开Excel文件。如果你需要导出多表单比如每个班级一个Sheet思路其实很简单创建多个Sheet对象循环写数据即可。但要记住给每个Sheet设置不同的名称并且把样式对象复用起来不要每行都新建CellStyle那样内存会涨得非常快。实际导出时建议先用一个几十条数据的小表测试确认样式和编码没问题再导全量数据这样能省下很多排查时间。3.5 EasyUI表格和JFreeChart统计图的接入方式EasyUI的datagrid是页面上的核心组件。比如说考勤记录列表在JSP里这样初始化$(#dg).datagrid({ url: attendance/list, method: get, rownumbers: true, singleSelect: true, pagination: true, columns: [[ { field: studentName, title: 姓名, width: 100 }, { field: courseName, title: 课程, width: 100 }, { field: attendanceDate, title: 日期, width: 100 }, { field: statusName, title: 状态, width: 80 } ]] });这里的核心约定在于后端attendance/list接口返回的数据格式必须是{total: n, rows: [{...}]}datagrid才能正确渲染。所以Controller里用PageHelper分页后要把PageInfo里的total和list组装成这个结构。前端通过$(‘#dg’).datagrid(‘reload’)就能刷新列表编辑和删除后都要记得调用它。这个组件的学习曲线非常平缓只要有一次成功实践后面所有管理页面都能复用。JFreeChart接入方式和普通接口不太一样它返回的不是JSON而是图片字节流。在Controller里用JFreeChart创建一个柱状图再用ChartUtils.writeChartAsPNG把图写到响应输出流JFreeChart chart ChartFactory.createBarChart( 班级出勤率统计, 班级, 出勤率(%), dataset, PlotOrientation.VERTICAL, true, true, false); ChartUtils.writeChartAsPNG(response.getOutputStream(), chart, 800, 400);这样前端用一个普通的img srcattendance/chart标签就能显示图片。饼图也是类似通过createPieChart创建。统计图表的数据集来自Service层聚合计算核心还是2.3节里的SQL把不同状态的数量汇总好塞进DefaultCategoryDataset即可。JFreeChart的默认样式确实比较素但报表场景追求的是信息准确不是视觉华丽够用就好。4. 常见问题与排查技巧实录4.1 Maven、MySQL的环境类问题先整理我遇到过的环境问题这些问题我几乎在每次辅导时都会碰到属于高频雷区。现象原因解决方案依赖一直下载失败pom.xml报错没配镜像或镜像配置被覆盖settings.xml中mirrorOf设为*并放阿里云仓库启动报ClassNotFoundException: com.mysql.jdbc.Driver驱动版本与MySQL版本不匹配MySQL 8.x必须用com.mysql.cj.jdbc.Driver连接报The server time zone value时区参数缺失url加serverTimezoneAsia/Shanghai中文乱码编码过滤器缺失或放错位置web.xml中CharacterEncodingFilter必须放在最前端口被占用Tomcat默认8080被其他进程占用换端口或关闭占用进程MySQL 8.0的驱动类名变化是最坑人的。老项目配置里写的是com.mysql.jdbc.Driver换成MySQL 8之后必须改成com.mysql.cj.jdbc.Driver同时URL最好加上useSSLfalseserverTimezoneAsia/Shanghai否则还会因为SSL握手和时区问题报错。Maven里mysql-connector-java的版本也要对应调整别再锁死5.1.49直接用8.0.x版本。4.2 SSM整合运行期的典型异常SSM整合完成后启动Tomcat时会暴露出一批问题我列几个最常见的。第一个是Invalid bound statement (not found)意思是Mapper接口找到了但对应的XML文件没有绑定上。原因九成是spring-mybatis.xml里没写mapper-locations或者扫描路径写错了导致XML不会被加载。另外还有一个特别隐蔽的原因Mapper接口和XML文件不在同一个包路径下即使扫描了接口XML也找不到。排查时先看控制台有没有加载XML的日志再用mvn clean清一次target目录再重新启动。第二个是Property dataSource is required。原因通常是SqlSessionFactoryBean里没配置数据源或者数据源Bean的id写错了。核查一下spring-mybatis.xml确保dataSource的ref指向正确的Bean。如果用了Druid还要确认Druid的类有没有被Maven引进来。第三个是启动后访问页面404。如果Tomcat本身没问题多半是web.xml里url-pattern写成了/*把所有JSP请求都拦截了。改成/就好。还有可能是视图解析器配置不对Controller返回字符串时拼出来的路径不存在这个在控制台输出去看了就一目了然。第四个是启动时报找不到transactionManager。检查tx:annotation-driven有没有配transaction-managertransactionManager并且事务管理器的Bean id是否一致。4.3 POI导出和前端交互的坑POI导出这块最容易出现“文件打不开”的情况。当你用XSSFWorkbook生成数据但文件名后缀写成.xls时Excel打开就会提示格式不匹配。反过来也一样HSSFWorkbook生成的数据文件名后缀不能写成.xlsx。两者对应关系必须一眼就能对应上。还有一个打不开的原因是write方法写出去之后没有关闭Workbook导致文件流不完整。正确做法是在finally里关闭workbook调用workbook.close()释放资源。如果是SXSSFWorkbook还可以用dispose()方法清理临时文件这些临时文件默认生成在系统临时目录数据量大时不清理会占满磁盘。前端交互的经典问题是会话过期。用户登录后闲置很久再去操作Session已经失效此时AJAX请求会被拦截器重定向到登录页返回的不是JSON而是HTML。前端JS尝试解析HTML会报错表现是页面无反应或者弹空白框。解决思路是写一个后端响应过滤如果请求是AJAX并且Session失效返回一个JSON状态码如401前端统一判断后跳转登录页。具体判断请求是不是AJAX看请求头X-Requested-With是否等于XMLHttpRequest即可。EasyUI表格不刷新是另一个高频问题。新增、编辑、删除之后列表还是老样子需要显式调用$(#dg).datagrid(reload)。如果reload后仍然没有反应检查分页参数是否被缓存了把pageNumber和pageSize手动重置一下就好。5. 从“能用”到“好用”的扩展方向5.1 权限控制与数据可视化升级当前系统里登录后所有用户权限都一样真实场景下肯定不够。管理员和教师应该有不同的操作范围管理员管理学生和课程基础数据教师只能维护自己所教课程的考勤记录。要落地这个功能最简单的方案是引入Shiro或Spring Security在学生表或用户表里增加role字段再用拦截器做URL级别权限控制。如果不想引入重量级框架自己写一个基于Session的角色字段配合拦截器也能实现对小项目来说完全够用。数据可视化这块JFreeChart适合展示静态统计图但要想做交互式看板比如鼠标悬浮显示详情、点击柱状图联动表格就需要引入ECharts这类前端图表库了。考虑到SSM项目没有前端构建流程建议把ECharts的JS文件下载到本地静态目录后端依然返回JSON数据前端用Ajax拉取后交给ECharts渲染。这样既保留了后端统计能力又提升了页面观感。5.2 这个项目在面试里的正确讲法考勤系统虽然常见但它是面试时最好“展开”的项目。面试官问Spring事务你就可以讲批量导入考勤记录时为什么加Transactional以及事务在什么情况下会失效问MyBatis你可以讲#{}和${}的区别以及为什么要用前者防止SQL注入问Maven你可以讲依赖冲突怎么排查、镜像仓库怎么配置问POI你可以讲大数据量导出时HSSF、XSSF、SXSSF的选择逻辑。这些真实场景里的取舍比背八股文有说服力得多。我经常跟新人讲项目不在多在于你真正理解了多少。就算只做一个考勤系统只要你把分页、事务、导出、图表、权限这几条链路都摸透了回答任何JavaWeb相关的问题都能举出实例。这比简历上堆十个“xxx管理系统”有用得多。做这个项目的过程中我最大的体会是先把完整的请求链路跑通再回头优化细节。很多人一开始就纠结于页面样式好不好看、配置要不要分离结果核心的增删改查还没跑起来人先被一堆框架问题劝退了。考勤系统这种业务骨架搭好之后剩下的功能都是往骨架上填肉难度不大但量不小耐心和细心比聪明更重要。最后再分享一个小技巧开发时把MyBatis的日志级别设为DEBUG控制台会打印每一条SQL和参数很多诡异的数据问题看一眼SQL立刻就能定位这是调试效率提升最大的一步。
返回列表