微信小程序课堂考勤系统:SSM后端与扫码签到毕业设计实战
简介这是一套面向高校计算机相关专业毕业设计的课堂考勤微信小程序完整项目采用微信小程序SSMMySQL技术栈适合正在准备毕设或需要小程序与Java后端联动实战的开发者。项目区分学生小程序端、教师Web端与管理员后台三种角色学生可浏览推荐课程、查看课程详情、签到、收藏、评论、修改资料及人脸校验教师可注册登录、发布课程、查看评论、提交签到与出勤管理管理员则负责学生教师信息、签到统计报表、课程分类、签到命令、出勤报表及轮播图管理。压缩包共923个文件约39.9MB涵盖106个Java源码、98个Vue组件、112个JS脚本、25个WXSS与24个WXML页面文件以及SQL数据库脚本、PNG/SVG界面素材和MP4演示视频另附毕业论文与运行说明。已有184人学习可作为完整赛题方案参考帮助快速理解三端权限划分、签到流程与报表统计的实现思路。1. 课堂考勤微信小程序从扫码签到到后台统计一套能跑通的毕业设计落地方案课堂考勤这件事做过助教或者带过课的人都清楚传统点名方式有多低效。一个班五六十人老师挨个念名字学生举手或者答到一轮下来少说三五分钟大班课甚至要花掉十分钟。更麻烦的是代答、早退、中途溜号这些情况根本管不住。我见过最离谱的一次一个学生帮三个人答到老师完全没发现。所以当毕业设计选题落到「课堂考勤微信小程序」上时我觉得这个方向是站得住的——它解决的是一个真实存在的痛点而且技术栈足够典型微信小程序做前端、SSM 做后端、MySQL 存数据这套组合在计算机毕业设计里属于主流且资料丰富的选择。这个方案适合谁如果你是计算机相关专业的学生正在找毕业设计题目或者你已经选了类似方向但不知道从哪下手那这篇内容就是写给你的。我会把整个系统的搭建路径拆开讲——从环境准备、数据库设计、后端接口开发、小程序端对接到最后的联调和避坑。不堆概念只讲能跑通的步骤和参数。你照着做至少能拿到一个可演示、可答辩的完整系统。2. 环境搭建与数据库设计把地基打牢再动工2.1 开发环境清单与版本选择做这套系统之前先把工具链配齐。我一般会建议用以下组合版本不要太新也不要太旧稳定优先工具推荐版本用途说明JDK1.8 或 11后端运行环境SSM 对高版本兼容性一般Maven3.6依赖管理构建 SSM 项目MySQL5.7 或 8.0数据存储8.0 注意驱动包要换IntelliJ IDEA2021后端开发主力 IDE微信开发者工具最新稳定版小程序端开发调试Tomcat8.5 或 9.0Web 容器SSM 项目部署这里有个血泪经验MySQL 8.0 和 5.7 的 JDBC 驱动类名不一样。5.7 用com.mysql.jdbc.Driver8.0 要用com.mysql.cj.jdbc.Driver而且连接 URL 后面要加时区参数。很多同学在这里翻车报错Unknown system variable query_cache_size或者时区错误其实就是版本没对齐。MySQL 安装配置这块网上教程很多核心步骤就几步下载安装包、设置 root 密码、配置环境变量、验证连接。安装完成后用命令行测试一下mysql -u root -p # 输入密码后进入 MySQL 命令行 SHOW DATABASES; # 能看到系统库就说明安装成功参数说明-u指定用户名-p表示需要输入密码。如果提示mysql 不是内部或外部命令说明环境变量没配好把 MySQL 的 bin 目录加到 PATH 里就行。2.2 数据库表结构设计与建表语句考勤系统的数据模型不复杂核心就几张表学生表、教师表、课程表、考勤记录表。我一般会这样设计CREATE DATABASE attendance_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE attendance_db; -- 学生表 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, password VARCHAR(100) NOT NULL COMMENT 密码, class_name VARCHAR(50) COMMENT 班级, openid VARCHAR(100) COMMENT 微信openid, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 课程表 CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, course_name VARCHAR(100) NOT NULL, teacher_id INT NOT NULL, class_name VARCHAR(50), schedule VARCHAR(100) COMMENT 上课时间, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 考勤记录表 CREATE TABLE attendance_record ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, status TINYINT DEFAULT 0 COMMENT 0缺勤 1出勤 2迟到 3请假, sign_time DATETIME COMMENT 签到时间, remark VARCHAR(200) COMMENT 备注, INDEX idx_student_course (student_id, course_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明学生表用student_no做唯一索引防止重复录入考勤记录表用student_id course_id建联合索引因为查询某个学生某门课的考勤记录是最高频的操作。status字段用 TINYINT 而不是 ENUM方便后续扩展状态类型。字符集统一用utf8mb4避免学生姓名里有生僻字导致乱码。参数说明AUTO_INCREMENT让主键自增DEFAULT CURRENT_TIMESTAMP让创建时间自动填充。ENGINEInnoDB支持事务考勤批量插入时有用。注意建表时字段长度要留够余量。我见过有同学把name设成VARCHAR(10)结果遇到四个字的名字加上空格就溢出了。宁可多给一点不要卡边界。3. SSM 后端接口开发从登录到考勤统计的完整链路3.1 SSM 框架整合与配置文件要点SSM 就是 Spring SpringMVC MyBatis 的缩写这套组合在国内 Java 开发里用了很多年资料全、坑也透明。整合的核心是三个配置文件applicationContext.xmlSpring 容器、spring-mvc.xmlMVC 配置、mybatis-config.xmlMyBatis 配置。我一般会先配applicationContext.xml把数据源、SqlSessionFactory、事务管理都放进去!-- 数据源配置 -- bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName valuecom.mysql.cj.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/attendance_db?useUnicodetrueamp;characterEncodingutf8amp;serverTimezoneAsia/Shanghai/ property nameusername valueroot/ property namepassword value你的密码/ /bean !-- MyBatis SqlSessionFactory -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ property nametypeAliasesPackage valuecom.attendance.entity/ /bean !-- 扫描 Mapper 接口 -- bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.attendance.mapper/ /bean逻辑说明数据源用 Druid 连接池比默认的 DriverManager 性能好很多。mapperLocations指定 MyBatis 映射文件的位置typeAliasesPackage让实体类可以用短名引用。MapperScannerConfigurer自动扫描 Mapper 接口并生成代理对象省去手动配置。参数说明URL 里的serverTimezoneAsia/Shanghai是 MySQL 8.0 必须加的不加会报时区错误。useUnicodetruecharacterEncodingutf8保证中文不乱码。密码不要硬编码在 XML 里生产环境应该用加密或者外部配置但毕业设计阶段先跑通再说。3.2 考勤签到接口的实现与防重复策略考勤签到是整个系统最核心的接口。学生在小程序端点击签到按钮后端要完成验证学生身份、检查是否已签到、写入考勤记录、返回结果。RestController RequestMapping(/api/attendance) public class AttendanceController { Autowired private AttendanceService attendanceService; PostMapping(/sign) public Result sign(RequestBody SignRequest request) { // 1. 参数校验 if (request.getStudentId() null || request.getCourseId() null) { return Result.error(参数不完整); } // 2. 检查是否已签到防重复 AttendanceRecord exist attendanceService.findByStudentAndCourse( request.getStudentId(), request.getCourseId()); if (exist ! null) { return Result.error(您已签到请勿重复操作); } // 3. 判断是否迟到假设上课后15分钟算迟到 Course course attendanceService.findCourseById(request.getCourseId()); int status calculateStatus(course.getStartTime()); // 4. 写入记录 AttendanceRecord record new AttendanceRecord(); record.setStudentId(request.getStudentId()); record.setCourseId(request.getCourseId()); record.setStatus(status); record.setSignTime(new Date()); attendanceService.insert(record); return Result.success(签到成功); } private int calculateStatus(Date startTime) { long diff System.currentTimeMillis() - startTime.getTime(); return diff 15 * 60 * 1000 ? 2 : 1; // 2迟到 1出勤 } }逻辑说明先做参数校验再做重复签到检查然后根据当前时间和课程开始时间的差值判断出勤状态最后写入数据库。calculateStatus方法里 15 分钟是迟到阈值这个参数可以根据实际需求调整。参数说明RequestBody接收 JSON 格式的请求体小程序端用wx.request发送 POST 请求时content-type要设为application/json。Result是自定义的统一返回格式包含code、msg、data三个字段。提示防重复签到除了在业务层判断数据库层也可以加唯一约束。在attendance_record表上建UNIQUE KEY uk_student_course (student_id, course_id)这样即使并发请求同时进来数据库也会拦住重复插入。3.3 考勤统计接口与 MyBatis 动态 SQL老师端需要看到某门课的考勤汇总比如出勤多少人、缺勤多少人、迟到多少人。这个统计用 MyBatis 的动态 SQL 来做很合适select idcountByCourse resultTypemap SELECT status, COUNT(*) as count FROM attendance_record WHERE course_id #{courseId} if teststartDate ! null AND sign_time gt; #{startDate} /if if testendDate ! null AND sign_time lt; #{endDate} /if GROUP BY status /select逻辑说明if标签根据传入参数动态拼接 SQL有开始日期就加开始日期条件有结束日期就加结束日期条件。GROUP BY status按考勤状态分组统计。返回类型用map因为统计结果的字段是动态的。参数说明#{courseId}是预编译参数能防止 SQL 注入。gt;和lt;是 XML 转义写法对应和。如果不想转义可以用![CDATA[ ]]包裹。这个接口返回的数据格式大概是[{status:1, count:45}, {status:2, count:3}, {status:0, count:2}]前端拿到后渲染成饼图或者柱状图答辩演示效果很好。4. 微信小程序端对接从登录授权到扫码签到4.1 小程序登录与 openid 获取流程微信小程序的登录流程和普通 Web 不一样它依赖微信的授权体系。核心步骤是小程序端调用wx.login()拿到临时 code传给后端后端用 code 换 openid 和 session_key。// 小程序端登录 wx.login({ success: function(res) { if (res.code) { wx.request({ url: http://localhost:8080/api/user/login, method: POST, data: { code: res.code }, success: function(response) { // 后端返回 openid 和自定义登录态 if (response.data.code 200) { wx.setStorageSync(openid, response.data.data.openid); wx.setStorageSync(token, response.data.data.token); } } }); } } });逻辑说明wx.login()获取的 code 只能用一次有效期五分钟。后端拿到 code 后调用微信的code2Session接口换取 openid。openid 是用户在当前小程序里的唯一标识用它来关联学生账号。参数说明wx.setStorageSync是同步存储适合存少量关键数据。token 是后端生成的登录凭证后续请求都要带上。注意wx.request的 URL 必须是 HTTPS但开发阶段可以在开发者工具里勾选「不校验合法域名」。后端换取 openid 的代码public String getOpenid(String code) { String url https://api.weixin.qq.com/sns/jscode2session ?appid appid secret secret js_code code grant_typeauthorization_code; // 用 HttpClient 发送 GET 请求解析返回的 JSON String response HttpUtil.get(url); JSONObject json JSON.parseObject(response); return json.getString(openid); }逻辑说明appid和secret在小程序管理后台获取。这个接口返回 openid 和 session_keysession_key 不要传给前端留在后端用。参数说明grant_type固定为authorization_code。如果返回错误码 40029说明 code 无效或者已过期需要重新调用wx.login()。4.2 扫码签到与位置校验的实现扫码签到有两种做法一种是学生扫老师展示的二维码另一种是老师扫学生的二维码。我一般推荐前者因为老师只需要展示一次所有学生扫同一个码效率高。二维码里可以编码课程 ID 和一个时间戳后端验证时间戳是否在有效期内// 学生端扫码 wx.scanCode({ success: function(res) { // res.result 是二维码内容格式如 courseId1timestamp1699999999 wx.request({ url: http://localhost:8080/api/attendance/signByQr, method: POST, data: { qrContent: res.result, studentId: wx.getStorageSync(studentId) }, success: function(response) { wx.showToast({ title: response.data.msg, icon: response.data.code 200 ? success : none }); } }); } });逻辑说明wx.scanCode调起摄像头扫码返回的res.result就是二维码里的字符串。后端解析这个字符串提取 courseId 和 timestamp验证时间戳是否在有效期内比如 5 分钟然后走正常的签到逻辑。参数说明二维码有效期建议设 5 到 10 分钟太短学生来不及扫太长容易被转发给没来上课的人。如果需要更严格可以加上位置校验用wx.getLocation获取学生经纬度和教室坐标比对超出范围就拒绝签到。注意wx.getLocation需要在app.json里声明permission字段并且从 2022 年起需要在小程序管理后台申请接口权限。毕业设计阶段如果申请不下来可以先用扫码时间戳做防作弊位置校验作为可选功能。4.3 考勤记录列表与状态渲染学生端需要看到自己的考勤历史老师端需要看到全班考勤情况。列表渲染用wx:for指令view classrecord-list view classrecord-item wx:for{{records}} wx:keyid text classcourse-name{{item.courseName}}/text text classsign-time{{item.signTime}}/text text classstatus {{item.status 1 ? normal : abnormal}} {{item.status 1 ? 出勤 : item.status 2 ? 迟到 : item.status 3 ? 请假 : 缺勤}} /text /view /view逻辑说明wx:for遍历 records 数组wx:key用唯一标识 id 提升渲染性能。状态文字用三元表达式根据 status 值动态显示同时绑定不同的 class 来控制颜色。参数说明item是默认的循环变量名可以通过wx:for-item自定义。wx:key的值必须是数组中每个对象的唯一属性名不写会有警告。5. 避坑与排查那些年我踩过的坑5.1 小程序请求域名校验导致接口调不通现象开发者工具里接口能通真机预览时所有请求都失败报request:fail url not in domain list。原因微信小程序正式环境要求所有请求域名必须在管理后台配置白名单且必须是 HTTPS。开发者工具默认不校验真机默认校验。解决开发阶段在开发者工具「详情」→「本地设置」里勾选「不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书」。上线前需要在微信公众平台配置服务器域名且域名必须备案。5.2 MySQL 8.0 时区错误导致连接失败现象后端启动时报The server time zone value ?D1ú±ê×?ê±?? is unrecognized。原因MySQL 8.0 默认时区是 UTC而 JDBC 驱动需要明确的时区设置中文系统下会解析出乱码时区名。解决在 JDBC URL 后面加serverTimezoneAsia/Shanghai。如果还不行在 MySQL 命令行执行SET GLOBAL time_zone 8:00;临时修改或者改my.cnf配置文件永久生效。5.3 MyBatis 映射文件不生效导致 Mapper 注入失败现象启动时报Invalid bound statement (not found)Mapper 接口找不到对应的 SQL。原因mapperLocations路径配错或者 XML 文件名和 Mapper 接口名不一致或者 XML 没有放在 resources 目录下。解决检查applicationContext.xml里mapperLocations的值确保是classpath:mapper/*.xml。XML 文件的 namespace 必须是 Mapper 接口的全限定名比如com.attendance.mapper.AttendanceMapper。如果 Mapper 接口和 XML 在同一个包下需要在 pom.xml 里配置 resources 插件把 XML 一起打包。5.4 小程序端 storage 缓存过期导致登录态失效现象学生前一天还能正常签到第二天打开小程序提示未登录。原因wx.setStorageSync存储的数据默认永久有效但后端 token 可能设置了过期时间或者 openid 对应的 session_key 已失效。解决在app.js的onLaunch里检查 token 是否存在不存在就重新走登录流程。后端 token 建议设 7 天有效期过期后返回 401前端拦截 401 响应并重新登录。不要依赖 storage 的过期时间微信小程序的 storage 没有原生的过期机制需要自己存一个时间戳来判断。5.5 考勤统计数字对不上现象老师端看到的出勤人数和实际签到人数不一致有时候多有时候少。原因统计 SQL 没有排除重复记录或者 status 字段的值和前端渲染逻辑不匹配。还有一种情况是学生换了微信号重新绑定导致同一个学生有两条记录。解决在attendance_record表上加唯一约束UNIQUE KEY uk_student_course (student_id, course_id)从数据库层面杜绝重复。统计前先确认 status 的枚举值定义0 缺勤、1 出勤、2 迟到、3 请假前后端保持一致。如果学生换号需要在绑定新 openid 时把旧记录迁移或者标记失效。6. 答辩演示与进阶优化让系统看起来更「完整」6.1 答辩演示的流程设计毕业设计答辩通常只有 10 到 15 分钟演示环节要提前排练。我一般会按这个流程走先用教师账号登录展示创建课程和生成签到二维码然后切换到学生账号扫码签到展示签到成功提示再切回教师端刷新考勤列表展示实时统计数据最后打开数据库展示底层数据确实写入了。整个流程控制在 5 分钟内留出时间回答老师提问。演示前一定要做数据准备提前建好两门课程录入十几个学生账号签到几条记录。不要现场从零开始建时间不够而且容易出错。如果条件允许用手机真机演示扫码比模拟器更有说服力。6.2 三个能加分的进阶功能如果基础功能已经跑通可以考虑加这几个功能答辩时是加分项功能实现思路难度考勤数据导出 Excel后端用 POI 或 EasyExcel 生成文件小程序端用wx.downloadFile下载中等签到位置校验用wx.getLocation获取经纬度和教室坐标算距离超过 100 米拒绝签到中等考勤异常提醒定时任务扫描缺勤记录通过小程序订阅消息推送给学生较高导出 Excel 这个功能最实用老师期末统计成绩时直接能用。实现上后端加一个/api/attendance/export接口返回文件流小程序端下载后调用wx.openDocument打开。注意文件格式要设成xlsxwx.openDocument支持这个格式。6.3 我踩过的一个真实教训最后说一个我自己的教训。第一次做这个系统的时候我把所有考勤逻辑都写在了 Controller 里一个方法两百多行又查学生又查课程又写记录还做统计。后来老师要加一个「请假审批」功能我发现根本改不动因为逻辑全缠在一起了。没办法花了两天时间重构成 Service 层 策略模式把签到、请假、统计拆成独立的服务类。所以我的习惯是Controller 只做参数校验和结果封装业务逻辑全部放 Service数据库操作放 Mapper。哪怕毕业设计代码量不大分层清晰也能让答辩老师看出你的工程素养。这个习惯带到工作里受益无穷。希望帮到你。本文还有配套的精品资源点击获取