资讯动态

SpringBoot+微信小程序:学生定位考勤系统从0到1实战解析

发布时间:2026/10/17 4:32:25 来源:尧图企业网站定制
这套基于SpringBoot微信小程序的学生定位考勤系统是我在帮某高校几位同学打磨毕业设计时反复折腾过的一套典型项目。题目说白了不新鲜——学生打卡、老师看考勤、后台出统计但真正把它从“能跑”做到“能演示、能答辩、能过审”中间涉及定位精度、防作弊、并发写入、微信平台限制等一堆细节踩过的坑比写过的代码还多。这篇文章就把整个项目从需求拆解到前后端实现、再到部署演示的完整链路梳理一遍给正在做同类毕设或想快速搭建定位考勤系统的同学一个可以直接参考的范本。1. 选这个毕设题目之前先把需求边界盘清楚1.1 为什么定位考勤系统适合做毕设很多同学选毕设题目时有个误区要么选得太空比如“基于大数据的某某分析平台”结果数据全靠自己造要么选得太窄比如“一个待办事项小程序”工作量撑不起论文。定位考勤系统属于那种“看起来常规但扩展空间很大”的题目刚好卡在中间。从高校真实场景看课堂点名一直是刚需。大课几百人老师一个个喊名字不现实让课代表代签又容易舞弊用传统签到二维码又没法确认人是否真的到了教室。基于地理位置打卡正好补上这个缺口学生进入教室范围后打开小程序系统通过定位判断人是否在场同时记录签到时间和位置老师后台一眼就能看到出勤统计。这个痛点真实存在而且需求边界清晰——学生端、教师端、管理端各干什么一句话就能说明白。从技术训练角度看这个题覆盖面非常广小程序端涉及微信登录、定位授权、页面状态管理、请求封装后端涉及SpringBoot工程搭建、MyBatis-Plus操作、JWT鉴权、定时任务数据层面涉及多表关联、考勤状态算、统计报表运维层面涉及HTTPS部署、域名校验、内网穿透联调。全栈链路走一遍该练的技术基本都练到了论文里“工作量”也好写。这也是我建议同学选它的核心原因。1.2 角色、流程与功能清单这个系统我做的时候按三个角色划分学生、教师、管理员。注意这三个角色不是简单地加个字段区分而是直接影响页面结构、接口权限和数据隔离方式。学生端核心功能微信授权登录自动绑定小程序账号查看今日待上课表进入课程详情后在考勤时间段内发起定位打卡查看自己的考勤记录出勤、迟到、缺勤、早退状态如果定位不在教室内或者不在考勤时间段给出明确提示。教师端核心功能维护所授课程导入或手动添加选课学生设置每节课的考勤时间窗口比如课前10分钟到课后5分钟实时查看某次课已签到学生、未签到名单对异常考勤记录手工调整补签、标记请假。管理员端核心功能管理教师账号、课程信息查看全校各课程的考勤率统计导出考勤数据报表。功能清单看似不复杂但每一块背后都有对应表结构和接口设计。我的做法是先把功能列成表格再对照表格去设计数据库字段避免写着写着发现缺字段、回头改表。这里给出一份精简版清单模块功能点关键数据对应角色登录微信授权、token签发openid、session_key、token全部课程管理创建课程、绑定教师课程表、教师表教师、管理员选课管理学生选择课程选课关系表教师、管理员考勤规则上课时间、考勤窗口、围栏半径课程表扩展字段教师定位打卡上报定位、判定是否有效经纬度、时间戳、考勤状态学生考勤查询个人/课程维度的考勤记录考勤记录表学生、教师统计报表出勤率、缺勤名单考勤记录表聚合管理员1.3 技术选型的判断SpringBoot微信小程序这对组合技术选型不是越新越好而是越稳越好。SpringBoot加微信小程序这对组合在国内征信、教育、政务类系统里被用了无数年生态成熟、资料多、踩坑记录丰富对毕设来说是最不容易出幺蛾子的选择。后端用SpringBoot主要看中几点内嵌Tomcat打jar包就能跑不用单独配服务器环境和MyBatis-Plus搭配写数据访问层CRUD几乎零成本Spring Security或JWT做鉴权都有现成轮子。版本我建议用SpringBoot 2.7.x别追SpringBoot 3.x。3.x虽然新但部分依赖和教程还没完全跟上毕设阶段没必要冒险。小程序端我建议直接用原生开发不要为了“显得高级”引入uni-app。原生小程序对微信API的支持最直接wx.getLocation、wx.login这些接口在原生环境里调起来最顺遇到问题搜解决方案也最方便。如果后续想扩展到支付宝小程序再考虑跨端框架不迟毕设的核心是先把场景跑通。数据库用MySQL 8.0Redis看情况用——如果只是单人演示、每天几百次打卡不用Redis也没问题如果考勤记录量大、还要做防重复提交Redis做一层缓存会更从容。我在项目里用了Redis做打卡防重后面会细说。2. 小程序端定位上报与考勤交互的实现细节2.1 申请定位权限app.json 与 wx.getLocation 的坑小程序端第一个容易翻车的地方就是定位权限。微信官方对定位接口的管理越来越严格如果不在app.json里正确声明代码写了也不会生效。我平时调试时最常遇到的报错是getLocation:fail the api need to be declared in the requiredPrivateInfos field in app.json。这是新版基础库的硬性要求必须在app.json的requiredPrivateInfos数组中声明getLocation同时在permission字段里配置用途说明否则接口直接拒绝调用。这里给一段可用的配置示例{ permission: { scope.userLocation: { desc: 你的位置信息将用于定位打卡考勤 } }, requiredPrivateInfos: [getLocation], pages: [pages/index/index, pages/course/course, pages/clock/clock] }另外要注意wx.getLocation有type参数可选wgs84和gcj02。国内地图坐标系默认是gcj02火星坐标如果你直接用wgs84拿到的坐标去做距离计算在真实地理环境下会有几十米到几百米的偏差考勤围栏半径设个100米的话人明明在教室里却被判定不在范围内非常尴尬。所以定位上报时我统一用type: gcj02后端距离计算也是基于gcj02一套坐标系走到黑不来回转换。2.2 距离计算放在哪里前端只采集后端才判定很多同学做定位功能时习惯在前端直接算距离、直接判断是否在教室内结果就是页面显示“打卡成功”后端记录的却只有一条“无脑写入”的数据。这样做有两个隐患一是前端代码可以被轻易篡改或模拟调用判定逻辑根本没有说服力二是距离算法分散在前端后人维护困难。我当时的划分原则是前端只负责采集原始定位数据和设备信息一切业务判定全部交给后端。具体流程是这样小程序获取定位后把latitude、longitude、accuracy、定位时间戳、openid一起POST到后端接口后端拿到这批数据后先校验该学生是否有权对当前课程打卡、当前时间是否在考勤窗口内、上报坐标与教室坐标的距离是否在围栏半径内最后才写入考勤记录。这样设计有几个直接好处数据库里存的是原始证据数据论文里可以描述“服务端具备最终判定权前端不做业务逻辑防止绕过”接口可以独立做单元测试不用依赖小程序环境演示时只要后端逻辑不变换任何前端壳都能跑。在距离计算方面我推荐直接用Haversine公式。它假设地球是球体计算两点间球面距离在小尺度范围内精度足够。前端可以简单调用wx.getLocation的accuracy字段辅助判断定位质量后端则用这个公式校验定位点与教室中心点的距离。// 前端定位采集核心代码 Page({ data: { lat: , lng: , accuracy: 0 }, onLoad() { this.getLocation(); }, getLocation() { wx.getLocation({ type: gcj02, isHighAccuracy: true, success: (res) { this.setData({ lat: res.latitude, lng: res.longitude, accuracy: res.accuracy }); this.submitLocation(res); }, fail: (err) { wx.showToast({ title: 定位失败请检查权限, icon: none }); } }); }, submitLocation(location) { wx.request({ url: https://yourdomain.com/api/attendance/checkin, method: POST, data: { courseId: this.data.courseId, latitude: location.latitude, longitude: location.longitude, accuracy: location.accuracy, locationTime: Date.now() }, header: { Authorization: Bearer wx.getStorageSync(token) } }); } });当然这里有个和“后端判定”方向一致的延伸设计计算距离也可以直接放后端用Java写同样的Haversine公式。前端代码保持简单后端接口做最终裁决整条链路逻辑清晰答辩时讲起来也顺畅。2.3 打卡页面的状态流转与防重复提交打卡页面体验好不好直接影响演示效果。我最开始写的版本逻辑混乱点一次按钮成功点第二次又给签了一次页面退出再进来状态居然还在“可打卡”状态。后来我把页面状态机老老实实梳理了一遍。状态分为四类未开始当前时间早于考勤窗口开始时间按钮置灰可打卡当前时间在窗口内定位获取后点击提交已打卡后端返回成功显示签到时间和定位距离异常定位失败、不在范围内、时间已过显示具体原因打卡按钮的防重复提交我放在前后端两层做。前端在点击后立刻把按钮设为disabled并显示“提交中”拿到成功响应后再允许跳转或返回后端则用Redis保存attendance:{openid}:{courseId}:{scheduleId}作为幂等键第一次打卡成功后写入缓存并设置过期时间后续重复请求直接返回“已完成”。这个幂等键设计很关键。微信小程序网络不稳定时用户点了打卡但响应超时往往会再次点击。没有幂等保护就会出现同一个学生同一节课被记录多次考勤统计报表全部乱套。后端核心接口示意PostMapping(/api/attendance/checkin) public Result checkin(RequestBody CheckinDTO dto) { String key attendance: dto.getOpenid() : dto.getCourseId() : dto.getScheduleId(); boolean first stringRedisTemplate.opsForValue().setIfAbsent(key, 1, Duration.ofHours(3)); if (!first) { return Result.error(您已完成本次打卡请勿重复提交); } // 继续校验时间窗口、距离范围写入考勤记录 return attendanceService.doCheckin(dto); }3. SpringBoot后端从表设计到考勤判定的完整闭环3.1 数据库设计至少要有这几张表数据库设计是毕设的第一步也是决定后期开发顺不顺的关键。我见过太多人上来就写代码写到一半发现“考勤记录里没有课程节次信息”“同一个老师无法管理多门课”回头改表改到崩溃。先花两小时把表设计理顺后面能省两天时间。我最终设计的核心表结构如下用户表userid主键自增openid微信唯一标识设为唯一索引role角色STUDENT、TEACHER、ADMINname姓名avatar_url头像create_time创建时间课程表courseid主键name课程名teacher_id教师用户IDstart_time / end_time上课时间latitude / longitude教室定位点坐标radius打卡围栏半径米invite_code选课邀请码选课关系表course_studentid主键course_id课程IDstudent_id学生用户IDstatus选课状态考勤记录表attendance_recordid主键course_id课程IDstudent_id学生IDschedule_id课时ID区分同一课程的不同上课日期checkin_time打卡时间latitude / longitude上报坐标distance与教室中心点距离米status考勤状态正常、迟到、缺勤、早退、请假is_valid是否有效有了schedule_id这个字段同一门课的多次考勤才能区分开。有的同学会再单独建一张上课计划表提前把一学期的课次排好教师选择对应课次开启考勤。这种设计更规范不过毕设阶段用schedule_id也可以应付。我个人推荐建一张course_schedule表把每个上课日期、考勤窗口时间、围栏规则都挂在这张表下后续扩展请假和补签逻辑会方便很多。CREATE TABLE course_schedule ( id bigint PRIMARY KEY AUTO_INCREMENT, course_id bigint NOT NULL, lesson_date date NOT NULL, start_time datetime NOT NULL, end_time datetime NOT NULL, late_time datetime NOT NULL, radius int DEFAULT 100, status tinyint DEFAULT 0 );3.2 登录与会话wx.login 换 openidJWT 做鉴权微信小程序端不能直接用传统用户名密码登录标准做法是用wx.login换取临时code后端通过code调用微信接口jscode2session拿到用户的openid和session_key。这里有个细节微信接口返回的openid是用户在小程序下的唯一标识但同一个用户在不同的微信小程序下openid不同如果以后要跨小程序统一用户需要用到unionid。毕设阶段直接用openid当用户标识就够了不用过度设计。登录流程简要拆解小程序端调用wx.login()拿到code请求后端/api/auth/login携带code和用户昵称头像信息后端用code请求微信接口换取openid查数据库如果用户不存在就自动注册存在则直接走登录后端签发JWT返回给小程序小程序把token存到wx.setStorageSync后续请求在header带上Authorization: Bearer token。JWT里我只放了userId和role过期时间设为7天。注意不要把openid直接放JWT里当用户身份用——用户表主键ID才是内部唯一标识openid相当于微信发的外部ID。两者对应关系存在数据库里后端统一用userId做关联查询这样后续要加手机号登录、账号密码登录时不影响主体结构。application.yml里需要配置微信小程序的appid和secret这两个值在小程序管理后台获取wx: mini: appid: your-app-id secret: your-app-secret后端登录服务里调用微信接口的核心逻辑大概长这样public String wxLogin(String code) { String url https://api.weixin.qq.com/sns/jscode2session?appid appid secret secret js_code code grant_typeauthorization_code; RestTemplate restTemplate new RestTemplate(); String result restTemplate.getForObject(url, String.class); JSONObject json JSON.parseObject(result); String openid json.getString(openid); return openid; }3.3 考勤判定规则时间、位置、围栏怎么组合考勤判定是整篇文章的核心也是答辩时评委最喜欢追问的部分。我建议把规则做成类似“评审链”的结构任一环节不通过直接返回失败不继续后续逻辑。第一环身份校验。当前登录用户是否是该课程的有效学生。如果不是返回“您未选择该课程无法打卡”。第二环时间校验。当前服务器时间是否在考勤窗口内。考勤窗口我按三段设计正常签到窗口上课前10分钟到上课后5分钟签到状态记为“正常”迟到窗口上课后5分钟到上课后20分钟签到状态记为“迟到”缺勤判定超过迟到窗口仍未签到系统在课程结束后自动把未打卡记录标记为“缺勤”。这里有一个关键点时间必须以服务器为准前端传的locationTime只作为定位采集的辅助依据。原因很简单手机时间可以随意修改一个把时间拨回课前的学生理论上可以骗过前端校验但骗不过服务器。第三环位置校验。后端算出上报坐标与课程中心点的距离这里我结合了accuracy定位精度做加权。比如上报的accuracy是200米那么即便上报坐标恰好落在围栏内也不能太信任需要放宽或标记可疑。实际实现中我用了一个简单策略当accuracy大于围栏半径时直接返回“定位精度不足请移动到信号更好的位置”。距离计算的Java实现public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) { double R 6371000; double dLat Math.toRadians(lat2 - lat1); double dLon Math.toRadians(lon2 - lon1); double a Math.sin(dLat / 2) * Math.sin(dLat / 2) Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); double c 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; }第四环写入。前三个环节全通过后才把考勤记录写入数据库。这个判定链设计成职责分离每一步单独抽成方法方便测试和调试。我当时写测试用例时就是分别模拟“错误学生”“错误时间”“错误位置”三种输入逐步调通每个环节的返回信息。答辩时把这个判定链的流程图讲清楚评委基本不会再追问细节。4. 最容易翻车的三个点定位漂移、作弊绕过与并发4.1 坐标漂移与坐标系GCJ-02是默认别自作聪明定位考勤系统最大的硬件现实就是GPS定位不准。室内场景尤其严重教学楼里的GPS信号会被墙体遮挡定位点可能漂个几十上百米。如果教室坐标就定在一个点上围栏半径又只设100米学生坐在教室另一侧就可能被误判“不在范围内”。处理办法比较实用的是把围栏半径设成150到200米的合理值同时记录定位精度。另外教室中心点不要靠“感觉”填最好用真机在教室中央用wx.getLocation实际采一次点然后把坐标写进课程定时更新。我的经验是定位点采集时至少要测三次取相对稳定的坐标避免某次漂移值污染了中心点。还有坐标系问题。微信返回的gcj02坐标和原始GPS的wgs84有偏移。如果教室中心点是用高德地图、微信地图做的打点那本来就在gcj02体系里和后端接收的坐标一致不需要转换。千万别前端收一个wgs84、数据库存一个gcj02那个距离计算一定出问题。4.2 模拟定位与作弊防范能防的不是绝对防定位考勤绕不开的一个话题就是作弊。安卓机可以开开发者模式模拟定位iOS越狱后也有模拟工具学生完全能神不知鬼不觉地“宅在宿舍打卡”。完全杜绝不可能但系统的目标是让作弊成本高于收益。我在项目里做了三层防范第一层定位数据本身。wx.getLocation在某些版本会返回is_simulated字段标记是否检测到模拟定位。拿到后直接记录到考勤表状态置为“可疑”人工审核时可直观看到。第二层多维度数据交叉。除了定位前端把运行时的经纬度、精度、采集时间一起上报。后端可以对同一个学生两次打卡记录做速度校验——比如A次打卡在教室A10分钟后B次打卡出现在几公里外的教室B平均速度飙到几百公里每小时明显不合理。这个逻辑不用很复杂一个基于时间和坐标差的“移动速度阈值”就够了。第三层管理端兜底。系统提供教师复核入口教师可以查看某条考勤记录的定位精度、上报坐标、时间对有疑问的记录标记为异常甚至手动补签或撤销。这种人工兜底比纯算法可靠得多演示时给评委看也显心思。4.3 并发与重复提交接口幂等必须做前文提到用Redis做幂等这里再展开说一说为什么。考勤场景存在明显的并发高峰上课铃响前后五分钟一个班几十个学生同时打卡接口QPS不算高但集中。如果服务端和数据库没有保护同一个学生快速连续点击两次、或者网络重试导致两条记录落库报表里就会出现双份打卡。除了Redis幂等键我在数据库层面也加了唯一约束用(course_id, student_id, schedule_id)作为联合唯一索引。两道锁同时存在即便Redis缓存过期或者代码逻辑漏掉某一环数据库也会拒绝重复插入。这个“应用层幂等数据库兜底”的双保险是我在实际项目中验证比较稳妥的做法。建索引的SQLALTER TABLE attendance_record ADD UNIQUE KEY uk_course_student_schedule (course_id, student_id, schedule_id);并发层面的另一个注意点是考勤记录的统计接口不要用多次count来算否则数据稍微大一点就会慢。我用一条SQL聚合搞定比如统计某门课的出勤率、迟到率、缺勤率SELECT course_id, COUNT(*) AS total_count, SUM(CASE WHEN status 正常 THEN 1 ELSE 0 END) AS normal_count, SUM(CASE WHEN status 迟到 THEN 1 ELSE 0 END) AS late_count, SUM(CASE WHEN status 缺勤 THEN 1 ELSE 0 END) AS absent_count FROM attendance_record WHERE course_id ? GROUP BY course_id;5. 调试、部署与答辩演示的实操经验5.1 本地联调内网穿透与合法域名微信小程序有个让新手特别头疼的限制生产环境请求必须走HTTPS且域名必须在小程序后台配置为“request合法域名”。本地开发时你不可能真买域名配证书于是内网穿透成了标配。我个人的经验是用内网穿透工具把本地SpringBoot的8080端口映射到一个公网临时域名然后在小程序开发者工具里勾选“不校验合法域名”。这样每次改了后端代码前端立刻能联调效率非常高。等真正要部署演示环境时再买域名配SSL证书。联调过程中最常遇到的问题是内网穿透不稳定、HTTPS证书链不完整、GET请求能通但POST请求失败等。如果遇到POST报404先检查后端拦截器有没有把OPTIONS请求放行。小程序请求默认不带Content-Type: application/json时后端CORS配置可能会拦截推荐在后端统一开放跨域Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true); } }5.2 演示前的数据准备与场景演练答辩演示是最容易翻车的环节很多同学写好了代码但演示时状况百出测试数据是乱的、定位权限没弹窗、手机上没装开发者模式、评委临时问“帮我演示一下某个学生缺勤怎么操作”结果当场卡壳。我的建议是提前一天做一次完整的“演示彩排”准备两台或三台手机分别登录学生号、教师号。实在没有多台手机可以用一台手机、一台带微信开发者工具的电脑配合。提前把课程数据造好至少一门课程、五个学生覆盖正常、迟到、缺勤、请假、可疑五种状态、两次不同日期的考勤记录。在教师端预先截好“考勤统计页”的图万一现场网络不稳至少能用截图撑场子。把Redis和MySQL服务用脚本一键启动避免现场敲命令。关掉开发者工具里的调试模式用真实真机演示一遍定位打卡全流程。演示时我还有一个技巧把教室中心点设在一个真实可达的坐标然后用测试账号在演示现场真实打卡评委看到“签到成功距离32.5米”这种实时反馈说服力远大于你口头讲解。5.3 说明文档与论文LW怎么写得像样毕设交付物里说明文档和论文LW即论文简写或联文的重要性不亚于代码。很多同学代码写得不错但文档敷衍最后分数被拉低。这里讲讲我写说明文档和论文时的组织思路。说明文档建议按这个结构写项目概述与环境说明用什么技术栈、运行环境要求部署步骤MySQL初始化脚本、Redis启动、后端打包部署、小程序配置接口文档每个接口的请求参数、响应示例、错误码数据库表结构说明每张表的字段含义常见问题排查定位权限、域名配置、证书问题接口文档部分不用引入SwaggerUI那种重型工具用Markdown表格列出接口信息就够了。例如接口方法路径权限说明登录POST/api/auth/login匿名微信code换token课程列表GET/api/course/list学生获取当前用户课程定位打卡POST/api/attendance/checkin学生提交定位数据考勤记录GET/api/attendance/history学生查询个人记录考勤统计GET/api/attendance/statistics教师课程维度统计论文部分我采用的是经典六章结构绪论背景、意义、国内外现状需求分析角色分析、功能需求、非功能需求系统设计架构设计、数据库设计、接口设计系统实现前端关键页面、后端核心模块系统测试功能测试用例、结果分析总结与展望。写论文最容易得分的地方是“测试章节”。不要只写“功能测试通过”而是用表格列清楚每个测试用例的输入、预期结果、实际结果例如用例编号测试场景操作步骤预期结果实际结果T01正常打卡在教室内点击打卡显示签到成功距离100米内通过T02不在教室内在教学楼外1000米点击打卡提示距教室过远通过T03重复打卡同一用户同节课点两次第二次提示已打卡通过T04模拟定位开启模拟定位后打卡标记可疑状态通过跑完这些用例论文的“工作量”立刻就有了支撑。最后分享一个我自己带毕设时的习惯代码写好后先把README和部署文档写完再开始写论文。文档写一遍等于把整个项目重新过一遍很多隐藏问题比如依赖版本不兼容、启动顺序要求都会在写文档的过程中暴露出来比到最后两天才现查错误输出要轻松得多。如果你正在做这套基于SpringBoot微信小程序的学生定位考勤系统建议你按这个顺序来先跑通登录和打卡闭环再补统计和报表最后做权限和异常处理。基础链路越早打通后面每一步都有明确的前进方向。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑