☰
微信小程序+SSM+MySQL课堂考勤系统毕设实战解析
2026/10/22 9:24:34 网站建设 项目流程

简介:面向毕业设计场景的课堂考勤微信小程序完整项目,基于微信小程序与SSM、MySQL等技术构建,服务端采用Java,管理端为Vue页面,覆盖学生、教师、管理员三类角色。小程序端实现课程查看、签到、收藏、评论、人脸校验与个人资料维护;Web端支持教师注册、发布课程、提交考勤与出勤管理,管理员可维护学生教师信息、查看统计报表、管理轮播图及签到命令,功能覆盖课堂考勤全流程。资源共923个文件,压缩包约39.9MB,以Java源码、Vue管理页面、微信小程序WXML/WXSS页面、SQL数据库脚本、图片与XML配置为主,并包含MP4演示视频和毕业论文文档,目录结构清晰,便于按功能模块阅读和调试。目前已有184人学习下载,适合需要快速完成类似选题的毕业生参考。附件还提供安装、运行、构建的批处理脚本,结合演示视频可快速搭建环境、检验功能,尤其适合作为开题参考或二次开发基础。

1. 课堂考勤微信小程序毕业设计:这套「小程序 + SSM + MySQL」组合为什么值得复刻

课堂考勤是高校里每天都会发生的真实需求:老师不想挨个点名,学生不想排队签本子,最后的出勤率还要能自动统计。这个毕设标题给的方案很经典:微信小程序做前端,SSM(Spring + SpringMVC + MyBatis)做后端接口,MySQL 存学生、课程和考勤记录,再配上源码、数据库脚本、论文和演示视频。它适合需要独立完成前后端项目、但不想碰分布式和高并发的同学,也适合想用一个垂直场景把整套 Java Web 技术栈讲透的求职者。下面从架构选型、核心代码到联调排错,按我实际做这类考勤系统的路径逐层拆开。

2. SSM 后端如何设计:从表结构到接口的分层逻辑

2.1 三个组件在考勤系统里各自的角色

在写任何代码之前,先把 Spring、SpringMVC、MyBatis 三个框架在考勤项目里的分工讲清楚,这直接决定论文第一章怎么写,也决定后面遇到 Bug 时你从哪一层开始排查。

我的分层习惯是:请求先到 Controller,Controller 只负责接收参数、调用 Service、把结果包成 JSON 返回,绝对不写查库代码;Service 层处理业务规则,比如课程签到窗口是否开放、学生是否已签到、现在算正常还是迟到;MyBatis 的 Mapper 接口负责把对象映射成 SQL,执行结果再映射回 Java 对象。Spring 则是背后的容器,负责创建 Controller、Service、Mapper 代理这些 Bean,并把它们注入到需要的位置。

这样拆的价值在真实排错时特别明显。模拟项目X里出现过一个典型问题:打卡成功后出勤率统计不对。当时只在 Service 里查逻辑,发现统计条件里把正常和迟到两个状态混在一起算,和 Controller 没有任何关系,所以完全不用动接口层就修好了。如果所有代码揉在一个 Controller 里,这次排查至少要翻很多行,答辩时也没法讲清分层的好处。

顺便说一句,很多同学误以为 SSM 就是把三个框架的依赖加进 pom 里,其实更重要的是各自的分工边界。SpringMVC 的 DispatcherServlet 是前端控制器,所有请求先经过它,按 URL 找到对应方法;MyBatis 把原来手写 JDBC 的模板代码收敛成一行 Mapper 接口调用。理解到这个程度,后面配 XML 时就不会被各种玄学报错折磨。

2.2 小程序、SSM、MySQL 是怎么串成一条完整链路的

一次签到请求的完整流转应该能在论文里画成一张请求时序图:小程序端 wx.request 发出 POST 请求;SpringMVC 根据 URL 映射找到 Controller 方法;Controller 校验参数后交给 Service;Service 调用 Mapper 接口;MyBatis 通过 SqlSession 执行 SQL;结果层层包装成 JSON 返回,小程序再根据返回的 code 决定弹「签到成功」还是提示具体错误。

这个链路里有两个关键节点容易被忽略。第一个是 Spring 容器和 SpringMVC 容器的父子关系。通常 spring.xml 扫描 Service、Mapper 和非 Controller 组件,spring-mvc.xml 只扫描 Controller。如果把所有包都交给 spring-mvc.xml 扫描,Service 的事务代理可能失效,因为事务是在父容器里配置的,子容器里扫描出来的 Service 不在事务管理范围内。

第二个是返回结构一定要统一。我常用的 Result 结构是{ code, msg, data },成功时 code=0,业务失败时 code 为非 0,系统异常时 code=500。这样小程序端只需要判断 code 就能决定 toast 内容,不用每个接口各写一套返回格式再特判。

MySQL 在这条链路里负责持久化,最需要注意的是 openid 和学号的关系。小程序端拿到的 openid 是该用户在当前小程序下的唯一身份,不是学号。所以登录接口第一次拿到 openid 时就要在学生表里自动建档,并把 openid 字段加上唯一索引,防止同一个微信号被重复注册出多条学生记录。

2.3 表结构:把考勤行为拆成四张核心表

课堂考勤最朴素的需求可以拆成一句话:谁在哪个班、谁愿意上这节课、这节课到底来了没有。为了不把数据写成一锅粥,我一般按角色和事件拆表:student 学生表、teacher 教师表、course 课程表、course_student 选课中间表、attendance_record 考勤记录表。

student 表的字段不宜太多:id、student_no、name、class_name、openid、create_time。student_no 必须有唯一索引,因为同一个学号不可能对应两个人;openid 也要唯一索引,第一次登录建档后就不会再插入新记录。

course 表要放和签到规则直接相关的字段,比如 signin_start 签到开始时间、signin_end 签到结束时间、late_threshold 迟到阈值时间。我建议这三个字段用 DATETIME 而不是字符串,方便后端直接用 Date 比较。location 字段可以记录上课地点,用来配合定位辅助校验,但定位只能作为参考,因为模拟器里的定位是可以手动改的,真正校验还是看时间。

attendance_record 是真正记录学生行为的表,关键字段如下:

字段类型说明
idINT AUTO_INCREMENT主键
course_idINT所属课程 id
student_idINT学生 id
statusTINYINT1 已到,2 迟到,3 请假,0 未到
attendance_dateDATE上课日期
checkin_timeDATETIME实际打卡时间
create_timeDATETIME记录创建时间

这张表最容易被忽略的是组合唯一索引:UNIQUE KEY uk_course_student_date (course_id, student_id, attendance_date)。它保证同一学生同一天同一门课只有一条考勤记录。没有这个索引,前端的防重复点击和后端的逻辑判断都只是软校验,数据层没法兜底,后面避坑章节会专门展开。

建表时我不建议加物理外键。更推荐用逻辑外键保存课程和学生 id,理由是模拟数据量级下外键约束在插入时会增加检查开销,更重要的是后续做逻辑删除时物理外键会让删除顺序变得很麻烦。在论文里写「用外键语义维护关系但不建立物理约束」完全站得住脚,老师能理解这是施工层面的取舍。

2.4 为什么很多任务书点名要 SSM,它和 Spring Boot 差在哪

这个标题里出现的是 SSM,这是不少高校任务书的经典要求,也是后台管理系统最常见的组合之一。不过我也确实见过同学拿同样的需求直接换成 Spring Boot,最后标题对不上,论文里的框架选型写得很尴尬。

SSM 相对 Spring Boot 最明显差异是配置过程可见。你需要亲手维护 web.xml、spring.xml、spring-mvc.xml 和 mybatis-config.xml,包扫描、数据源、请求映射都要逐个配置。这个过程虽然繁琐,但对答辩反而是加分项:被问到「请求从进入到返回经历了哪些组件」时,可以指着配置文件一步步讲;而 Spring Boot 的自动配置像个黑匣子,平时不看源码的话,现场反而容易卡壳。

如果你确实想用 Spring Boot,也不是不行。但结构上仍然建议保持 Controller-Service-Mapper 三层,接口返回仍然用统一 Result,避免为了少数便利把项目写成一个人看不懂的单体脚本。后面我给出的配置和代码都以最常用、最稳妥的 SSM 配置为准,照着做就能跑通。

3. 把 SSM 后端跑起来:建表、配置与最核心的三个接口

3.1 建库建表:SQL 文件里必须注意的两个地方

数据库脚本是交付物的一部分,建议把建库、建表、插入演示数据都写在一个 init.sql 里,保证自己在全新电脑上也能一键重建。第一步是指定字符集,千万别偷懒用默认值。

CREATE DATABASE IF NOT EXISTS attendance DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE attendance; CREATE TABLE student ( id INT AUTO_INCREMENT PRIMARY KEY, student_no VARCHAR(20) NOT NULL, name VARCHAR(50) NOT NULL, openid VARCHAR(64) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_student_no (student_no), UNIQUE KEY uk_openid (openid) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这段 SQL 有两个细节。一个是 openid 字段长度,微信官方文档里 openid 是 28 个字符左右,但为了兼容以后接入 unionid 或扩展字段,我习惯给到 64 位;另一个是UNIQUE KEY uk_openid,防止同一个微信号被重复建档。如果表已经建好但没加唯一索引,用 ALTER TABLE 补上即可。

接着建课程和考勤记录表,它们才是打卡功能的主战场:

CREATE TABLE course ( id INT AUTO_INCREMENT PRIMARY KEY, course_name VARCHAR(100) NOT NULL, teacher_id INT NOT NULL, signin_start DATETIME NULL, signin_end DATETIME NULL, late_threshold DATETIME NULL, location VARCHAR(100) NULL, is_deleted TINYINT NOT NULL DEFAULT 0, KEY idx_teacher (teacher_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE attendance_record ( id INT AUTO_INCREMENT PRIMARY KEY, course_id INT NOT NULL, student_id INT NOT NULL, status TINYINT NOT NULL DEFAULT 1, attendance_date DATE NOT NULL, checkin_time DATETIME NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_course_student_date (course_id, student_id, attendance_date), KEY idx_checkin_time (checkin_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里我特意没用外键约束,原因前面讲过,主要为逻辑删除和统计性能考虑。course 表里的 is_deleted 做逻辑删除,查询时默认where is_deleted = 0,这样课程下课后数据还在,不影响历史考勤统计。attendance_record 的组合唯一索引就是防重复打卡的最后一道防线,没有它,后面清理重复数据会非常痛苦。

3.2 Spring + SpringMVC 配置:一份能跑通的骨架

SSM 的 XML 配置是老生常谈,但绝大多数「为什么项目起不来」都和这里有关。我用两段最核心的配置说明。

第一段是 web.xml 中的 DispatcherServlet 配置:

<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>

url-pattern 用*.do是不少老项目的写法,好处是不用额外配置静态资源放行。如果写成/,所有请求都进 SpringMVC,还得放行 js/css 和图片,否则页面样式全挂。代价是调用接口时地址必须带 .do,比如/api/user/login.do。在毕设里这不算缺点,反而能让老师一眼看出请求走了 SpringMVC。

第二段是 spring.xml 里的数据源配置:

<context:component-scan base-package="com.example.attendance"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/attendance?useUnicode=true&amp;characterEncoding=utf8mb4&amp;serverTimezone=Asia/Shanghai"/> <property name="user" value="root"/> <property name="password" value="你的密码"/> <property name="initialPoolSize" value="5"/> <property name="maxPoolSize" value="30"/> <property name="maxIdleTime" value="60"/> </bean>

包扫描里 exclude Controller 是父子容器分工的一部分,避免 Controller 被扫描两遍。数据源驱动类用com.mysql.cj.jdbc.Driver是 MySQL 8 的驱动类名,在 MySQL 8 下继续用com.mysql.jdbc.Driver会警告甚至报错。连接串里的characterEncoding=utf8mb4和serverTimezone=Asia/Shanghai缺一个,都会在插入中文或时间字段时报错。

这里还要专门提醒:XML 里&符号必须写成&amp;,上面 jdbcUrl 里已经做了转义。很多同学直接把普通连接串粘进 XML,Tomcat 解析时报错,这就是最常见的第一道坑。

3.3 登录接口与打卡接口:核心代码与参数说明

登录接口的逻辑是拿微信登录的 code 换 openid,再决定是注册还是直接登录。Controller 的关键代码如下:

@RestController @RequestMapping("/api/user") public class UserController { @Autowired private UserService userService; @PostMapping("/login.do") public Result login(@RequestBody Map<String, String> body) { String code = body.get("code"); if (StringUtils.isEmpty(code)) { return Result.error("code 不能为空"); } String openid = userService.code2Openid(code); Student student = userService.findByOpenid(openid); if (student == null) { // 第一次进入小程序,自动建档 student = userService.createStudent(openid, body.get("nickName")); } String token = userService.createToken(student.getId()); return Result.ok() .put("token", token) .put("studentId", student.getId()) .put("role", student.getRole()); } }

参数说明:code 是 wx.login 返回的临时凭证,有效期一般只有几分钟;nickName 是前端拿到的用户昵称,可为空。createToken 可以用 UUID,也可以把 userId 加随机串后返回给前端,并在数据库的 token 表里保存一份。毕设阶段用数据库表存 token 已经足够,每次请求带着 Authorization 头查一次库校验,没必要上 Redis。

打卡接口是业务规则最集中的地方:

@PostMapping("/checkin.do") public Result checkin(@RequestBody CheckinRequest req) { Integer studentId = tokenService.getStudentId(req.getToken()); if (studentId == null) { return Result.error(401, "登录过期,请重新登录"); } Course course = courseService.findById(req.getCourseId()); if (course == null || course.getIsDeleted() == 1) { return Result.error(404, "课程不存在"); } Date now = new Date(); if (now.before(course.getSigninStart()) || now.after(course.getSigninEnd())) { return Result.error(400, "当前不在签到时间范围内"); } // 在迟到阈值前算正常,之后算迟到 int status = now.before(course.getLateThreshold()) ? 1 : 2; attendanceService.save(studentId, req.getCourseId(), status); return Result.ok().put("status", status).put("checkinTime", now); }

这段代码至少解决三个高频问题:登录态校验、签到时间窗口校验、正常与迟到状态判定。判断迟到用的服务器时间now,而不是客户端传入时间,这一点值得在答辩时主动讲,它直接回应了「学生改了手机时间怎么办」。save 方法内部如果命中唯一索引冲突,要主动捕获 DuplicateKeyException 并翻译成「今天已经签过到」,而不是返回系统错误。

关于未到名单,常见做法不是考勤接口生成的,而是教师端加一个「生成未到记录」入口:从 course_student 里查选中该课程的学生,减去当天 attendance_record 已有的学生,剩下的就是未到,批量插入 status=0。这个操作千万别在 Service 里写三层 for 循环逐条查库,正确方式是写一条 LEFT JOIN 的 SQL 一次查出差集,再批量插入。

4. 小程序端接入后端:登录态、打卡页与考勤记录的真实代码

4.1 小程序页面结构:按角色拆路由

小程序端不再有 Maven 多模块,而是页面结构决定一切。我通常把 pages 拆成两类:登录页和主功能页,主功能页再按角色分成 student 和 teacher 两个目录。

app.json 里有几个注意点:pages 数组的第一行就是启动页;如果教师端用 tabBar,页面必须注册在 tabBar.list 里;如果页面太多可以分包,但毕设数据量不大,通常不必强行分包。一个常见的页面规划如下:

页面路径页面用途依赖接口
pages/login/login微信一键登录/api/user/login.do
pages/student/course学生查看今日课程/api/course/myCourse.do
pages/student/checkin学生打卡/api/attendance/checkin.do
pages/student/record个人考勤记录/api/attendance/myRecords.do
pages/teacher/course教师所授课程/api/course/teacherCourse.do
pages/teacher/create教师发起签到/api/course/createSignin.do
pages/teacher/statistics出勤统计/api/attendance/statistics.do

页面数量控制在六到八个最合适,既能体现工作量,又不会在答辩时因为页面太多而讲不完。每个页面的业务逻辑尽量放在 onLoad 和对应事件里,调用封装好的 request,不要在页面里直接写 wx.request,否则 baseUrl 一旦变化,十几个地方都要改。

4.2 封装 wx.request:baseURL、token 与错误提示

小程序端最容易出现重复代码的地方就是请求。我习惯在 utils/request.js 里封装统一方法,把 baseUrl、token 注入、错误弹窗放在一处,业务页面只关心成功后的数据。

const BASE_URL = 'http://localhost:8080' function request(url, method, data = {}) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + url, method: method.toUpperCase(), data, header: { 'Content-Type': 'application/json', 'Authorization': wx.getStorageSync('token') || '' // 统一带 token }, success(res) { const body = res.data if (res.statusCode === 200 && body.code === 0) { resolve(body.data) } else if (res.statusCode === 401 || body.code === 401) { wx.removeStorageSync('token') wx.navigateTo({ url: '/pages/login/login' }) reject(body) } else { wx.showToast({ title: body.msg || '请求失败', icon: 'none' }) reject(body) } }, fail() { wx.showToast({ title: '无法连接服务器,请检查后端', icon: 'none' }) reject(new Error('network error')) } }) }) } module.exports = request

逻辑说明:BASE_URL 在开发阶段写死为 localhost,真机预览时必须改成电脑的局域网 IP,比如http://192.168.1.10:8080,正式部署后再换成 https 域名。token 存在 Storage 里,冷启动也能读到。成功判断用body.code而不是res.statusCode,是因为后端可能返回 200 但业务失败,这时候也必须进业务提示分支。

注意:微信小程序默认有合法域名校验,本地调试时可以在开发者工具详情里勾选「不校验合法域名」,但体验版和发布版必须把接口域名配置到公众平台的 request 合法域名里。很多同学换手机预览时发现接口不通,十有八九是卡在这个地方。

4.3 学生打卡页:从按钮点击到考勤成功的完整代码

打卡页的数据流最值得完整走一遍。进入页面先请求课程详情,拿到 signinStart 和 signinEnd,前端可以提前判断是否在签到时间,但后端必须再校验一次。打卡按钮的响应函数里要做防重复操作:

Page({ data: { courseId: null, courseName: '', timeRange: '', loading: false, alreadyCheckin: false, checkinResult: '' }, handleCheckin() { if (this.data.loading || this.data.alreadyCheckin) return this.setData({ loading: true }) request('/api/attendance/checkin.do', 'POST', { courseId: this.data.courseId }).then(res => { const msg = res.status === 2 ? '签到成功(迟到)' : '签到成功' this.setData({ alreadyCheckin: true, checkinResult: msg }) wx.showToast({ title: msg, icon: 'success' }) }).catch(() => { this.setData({ alreadyCheckin: false }) // 失败恢复按钮 }).finally(() => { this.setData({ loading: false }) }) } })

参数说明:loading 防连续点击,alreadyCheckin 来自后端返回的今日打卡状态,如果已经签到过就直接把按钮置灰。请求参数只需要 courseId,因为学生身份是从 token 解析出来的,前端不需要再传 studentId,这也是和后端接口设计保持一致。

一个小细节是状态刷新最好放在 onShow 而不是 onLoad。因为学生切到后台再回来,可能已经在另一台设备上完成签到,页面回到前台时状态要重新拉取。小程序页面栈的 onShow 每次都会触发,利用这个生命周期刷新是比较稳妥的习惯。

5. 考勤系统联调避坑:5 个最容易让毕设翻车的点

5.1 请求后端报 404:*.do 后缀没对上

现象:小程序端点了登录,开发者工具 Network 面板显示请求http://localhost:8080/api/user/login.do返回 404,后端日志里没有任何一条该请求的记录。

原因:web.xml 里 url-pattern 配置的是*.do,但 Java Controller 的 @RequestMapping 写的是/api/user/login,没有带 .do 后缀;或者反过来,Controller 里带了 .do,但 web.xml 映射的是/。这两种错位都会让请求根本进不到 Controller。

解决:统一接口地址带 .do。Controller 类上写@RequestMapping("/api/user"),方法上写@PostMapping("/login.do"),小程序请求地址写/api/user/login.do。改完必须重启 Tomcat,修改 Java 类和 web.xml 不是热部署能覆盖的。答辩演示时,优先用一个已经调通的页面演示,不要在演讲现场临时改配置。

5.2 提示签到成功,数据库里却没有记录

现象:学生端弹了「签到成功」,后端日志也没有明显异常,但打开 MySQL 查 attendance_record,找不到刚才那条记录。

原因:最常见的是 Service 方法没有加事务,插入时出现唯一键冲突或其他异常,被某层 catch 吞掉了;另一个常见原因是 Controller 把异常统一捕获并返回了 code=0 的成功 JSON,前端误判成成功。

解决:给写操作加@Transactional(rollbackFor = Exception.class);Controller 里不做 try-catch,业务异常交给全局异常处理器统一返回非 0 的 code,前端才能区分成功和失败。验证是否真的落库,不要只相信前端提示,直接用 SQL 按 student_id 和日期查一次最保险。如果之前已经插入过脏数据,先清理再重新测试。

5.3 同一学生一天能打多次卡:唯一索引丢在哪了

现象:演示时同一个学生连续点签到,每次都显示成功,最后的考勤统计人数超过选课人数。

原因:attendance_record 建表时只设了自增主键,没有加 course_id、student_id、attendance_date 组合唯一索引。后端虽然做了「先查今日是否已签到」的判断,但在并发场景下两个请求可能同时通过查询,然后都走到插入。前端防重复只挡普通连续点击,挡不住并发。

解决:建表时补上UNIQUE KEY uk_course_student_date (course_id, student_id, attendance_date)。如果表已经存在,先删除重复数据,再执行 ALTER TABLE 添加唯一索引。加完之后再测重复打卡,应该会触发 DuplicateKeyException,在后端把这个异常翻译成「今天已经签过到」的提示。

5.4 中文乱码:三个环节要一起改

现象:学生姓名存在 MySQL 里显示为问号,小程序端拉取后课程名也是乱码。

原因:字符集问题很少是单点引起的,常见是三处不一致:建库时没指定字符集,表默认是 latin1;JDBC 连接串没带 characterEncoding;HTTP 响应头没指定 UTF-8,前端按系统默认编码解析。

解决:按顺序做三件事。第一,把库表字符集改成 utf8mb4,已有表用ALTER TABLE student CONVERT TO CHARACTER SET utf8mb4;第二,连接串加useUnicode=true&characterEncoding=utf8mb4;第三,Spring 的 StringHttpMessageConverter 设置 UTF-8,或在 web.xml 里配置 CharacterEncodingFilter。改完重启,插入一条中文测试数据验证。历史数据如果已经变成乱码,改配置救不回来,只能删掉重建测试数据,这也是演示前要把数据库重置一遍的原因。

5.5 全班一起打卡时接口超时或卡死

现象:教师端发起签到后全班同时点,部分学生反馈一直转圈,最后请求失败,后端日志出现连接超时。

原因:数据源连接池太小。Tomcat 的并发线程打进来时,c3p0 只初始化了几个连接,全部被占住,后来的请求只能排队等待,造成大面积超时;另一种可能是打卡接口里有慢 SQL,比如查询 course_student 时没走索引,数据量稍微大一点就暴露。

解决:调大连接池,initialPoolSize 至少 5,maxPoolSize 设 30 到 50;给 course_id、student_id、attendance_date 这些经常出现在 where 和 join 条件里的字段建普通索引;未到名单的生成用 LEFT JOIN 一次查出差集,不要用 for 循环逐条查库。演示前可以先在 Jmeter 里开 20 个线程组跑一次并发打卡,让自己心里有数。

6. 让答辩现场更稳:用日志和边界测试证明系统可靠

答辩前的验证,我一般会用三组边界数据证明系统不是「看着能跑」。第一组是时间边界:把课程的 signinStart 设为未来时间,请求打卡必须返回「不在签到时间范围内」;把当前时间刚好卡在 lateThreshold 前一秒,应返回 status=1,后一秒返回 status=2。第二组是重复边界:用同一个 token 连续打两次卡,第二次必须返回「今天已经签过到」。第三组是身份边界:学生 token 调教师端统计接口,必须返回 401 或业务错误码。这三组用例写进论文的测试章节,比贴十几张接口截图更有说服力。

日志方面,建议打开 MyBatis 的 SQL 日志,在 logback 或 log4j 里把 mapper 包级别调到 DEBUG。答辩演示时一旦出问题,现场看 SQL 执行日志比盲猜代码高效得多。还可以在登录接口里故意打一条 warn 日志,演示 token 校验失败时的统一异常出口,让老师看到系统有完整的错误处理链路。

PPT 和技术文档里不要只贴页面截图。把考勤统计拆成「按课程统计出勤人数、按学生统计出勤率、按日期统计趋势」三个接口来讲,配合 Postman 返回 JSON 的截图,把每个字段的统计口径说清楚。口径一致是答辩最容易追问的点,比如请假算不算出勤、迟到算不算出勤,提前讲清楚就少一次被追问的风险。

最后说个血泪经验:我见过一份毕设把「教师发起签到」做成了教师手动勾选所有学生,这本质上不是考勤系统,而是一个签到名单维护页。真正能体现工作量的是教师设置签到窗口、课程自动关联选课学生、未到名单自动生成这三个点。我做模拟项目X时前期也栽在「功能看着多但核心不深」上,后来把重心移到未到名单的自动生成和重复打卡拦截,答辩时老师反而愿意往业务细节里深问。

希望这些落地细节帮到你,按这条路径把后端接口、小程序页面和数据库脚本逐一跑通,这套课堂考勤微信小程序毕业设计就能成为一份拿得出手的完整作品。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询