简介这是一套面向计算机、数学、电子信息等专业学生的课堂签到微信小程序完整源码后台基于SSM框架与MySQL开发适合用作课程设计、期末大作业或毕业设计的学习参考。项目实现了限时签到、密码签到、手势签到和位置签到四种模式前端为微信小程序页面后端提供接口与数据持久化能帮助读者理解签到业务从请求到落库的完整链路。压缩包共367个文件约18.3MB包含40个Java源文件、28个XML配置、33个JAR依赖、27个JS脚本以及WXML、WXSS页面文件另有SQL建表脚本和项目说明文档覆盖前后端与数据库各层。已有32人学习关注。读者可据此掌握SSM分层结构、小程序页面组织与签到逻辑实现并在此基础上自行调试扩展。1. 课堂签到小程序四种签到方式背后的技术选型与落地边界课堂签到这件事看起来只是点一下按钮真正做过的人都知道坑有多深。老师要的是「人到没到、有没有代签」学生要的是「三秒内签完不卡顿」而开发者要面对的是定位漂移、手势误判、限时过期、并发写入这一堆现实问题。这套基于微信小程序前端加 SSMSpring SpringMVC MyBatis后台、MySQL 存储的课堂签到方案核心就是把限时签到、密码签到、手势签到、位置签到四种模式统一到一套数据模型里让老师按课堂场景自由切换。它适合正在做 Java 课程设计的学生、需要快速搭一套签到系统的助教以及想理解小程序与 Java 后台如何配合的初中级开发者。源码和项目文档的价值不在于代码多漂亮而在于四种签到模式的判定逻辑、状态流转和防代签思路能直接复用。下面按「先讲清每种签到怎么判定再讲后台怎么落库最后讲踩过的坑」这条线展开。2. 四种签到模式的判定逻辑与数据模型设计签到系统的复杂度几乎全部集中在「怎么判定一次签到有效」上。限时、密码、手势、位置这四种模式判定维度完全不同限时看时间窗口密码看字符串匹配手势看路径序列位置看经纬度距离。如果每种模式各写一套表和接口后期加一种模式就要动全身。所以第一步是把它们抽象成统一的签到任务模型再用一个「校验策略」字段区分。2.1 签到任务表与签到记录表怎么拆核心两张表sign_task签到任务和sign_record签到记录。任务表由老师创建记录表由学生触发写入。任务表里用一个sign_type字段标记模式1 限时、2 密码、3 手势、4 位置再用若干可空字段承载各模式的参数。这样加新模式只需加字段和分支不用新建表。CREATE TABLE sign_task ( id BIGINT PRIMARY KEY AUTO_INCREMENT, course_id BIGINT NOT NULL COMMENT 课程ID, teacher_id BIGINT NOT NULL COMMENT 发起教师, sign_type TINYINT NOT NULL COMMENT 1限时 2密码 3手势 4位置, start_time DATETIME NOT NULL COMMENT 签到开始时间, end_time DATETIME NOT NULL COMMENT 签到截止时间, sign_password VARCHAR(32) DEFAULT NULL COMMENT 密码签到用, gesture_path VARCHAR(64) DEFAULT NULL COMMENT 手势路径,如 1-2-5-8, target_lng DECIMAL(10,7) DEFAULT NULL COMMENT 位置签到目标经度, target_lat DECIMAL(10,7) DEFAULT NULL COMMENT 位置签到目标纬度, allow_radius INT DEFAULT 200 COMMENT 允许半径(米), status TINYINT DEFAULT 1 COMMENT 1进行中 0已结束, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;sign_record表则记录谁在什么时候、以什么方式签的并冗余存一份当时的经纬度和距离方便事后申诉核对。这里有个关键设计sign_record对(task_id, student_id)建唯一索引从数据库层面杜绝同一人重复签到比在代码里查一遍再插入可靠得多。CREATE TABLE sign_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, task_id BIGINT NOT NULL, student_id BIGINT NOT NULL, sign_time DATETIME DEFAULT CURRENT_TIMESTAMP, sign_lng DECIMAL(10,7) DEFAULT NULL, sign_lat DECIMAL(10,7) DEFAULT NULL, distance INT DEFAULT NULL COMMENT 距目标点米数, device_info VARCHAR(128) DEFAULT NULL, UNIQUE KEY uk_task_student (task_id,student_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段类型上有个容易翻车的地方经纬度千万别用FLOAT。FLOAT单精度只有约 7 位有效数字存到小数点后 7 位时精度已经丢失算距离会莫名差出几十米。用DECIMAL(10,7)或DOUBLE才稳。allow_radius用米做单位前端传上来的也是米避免单位换算引入误差。2.2 限时与密码签到时间窗口和字符串校验限时签到本质是服务端时间比对。学生点签到后端拿当前服务器时间和任务的start_time、end_time比。这里必须用服务器时间不能用小程序端时间——手机时间可以随便改用客户端时间等于把判定权交给学生。// SignServiceImpl.java 限时签到校验 public boolean checkTimeLimit(SignTask task) { Date now new Date(); // 服务器时间 if (now.before(task.getStartTime())) { throw new BizException(签到尚未开始); } if (now.after(task.getEndTime())) { throw new BizException(签到已截止); } return true; }密码签到就是字符串比对但要注意两点一是密码存明文还是哈希。课堂签到密码是短时有效的临时口令不是用户密码存明文可接受但比对时要用equals而非。二是大小写和空格前端输入框容易带首尾空格后端比对前先trim()。public boolean checkPassword(SignTask task, String input) { if (input null) return false; return task.getSignPassword().equals(input.trim()); }限时和密码这两种模式实现最简单但恰恰是并发最容易出问题的地方——签到截止那一秒可能有几十个请求同时进来后面讲避坑时会专门说。2.3 手势签到路径序列的存储与比对手势签到是四种里最容易被低估的。九宫格手势本质是一串有序的点位编号比如1-2-5-8。存储时用连字符拼接的字符串即可比对时直接字符串相等判断。但要注意手势的「方向性」1-2-5和5-2-1是不同手势字符串比对天然满足这一点不需要额外处理。前端小程序用 canvas 或 view 网格捕获触摸轨迹把经过的点位按顺序收集去重相邻重复点后拼成字符串传给后端。后端只做一件事和任务里存的gesture_path比对。public boolean checkGesture(SignTask task, String inputPath) { if (inputPath null || task.getGesturePath() null) return false; // 前端已保证格式为 1-2-5-8这里只做等值比对 return task.getGesturePath().equals(inputPath); }有个细节手势点位至少要连 4 个点少于 4 个点的手势太容易猜防代签意义不大。这个约束放在前端做即时提示后端也校验一次长度双保险。2.4 位置签到Haversine 算距离与半径判定位置签到是四种里技术含量最高、也最容易出玄学问题的。核心是拿学生上报的经纬度和老师设定的目标点算球面距离再和允许半径比。算距离用 Haversine 公式别用平面勾股定理——在纬度高的地方平面算法误差能到几百米。// 地球平均半径,单位米 private static final double EARTH_RADIUS 6371000; public static double haversine(double lat1, double lng1, double lat2, double lng2) { double dLat Math.toRadians(lat2 - lat1); double dLng Math.toRadians(lng2 - lng1); double a Math.sin(dLat / 2) * Math.sin(dLat / 2) Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2); double c 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return EARTH_RADIUS * c; } public boolean checkLocation(SignTask task, double lng, double lat) { double dist haversine(task.getTargetLat().doubleValue(), task.getTargetLng().doubleValue(), lat, lng); return dist task.getAllowRadius(); }参数上allow_radius一般设 100 到 300 米。设太小GPS 漂移会导致学生在教室里也签不上设太大隔壁教学楼的人也能签。经验值是 200 米起步教学楼密集的学校可以放宽到 300 米。小程序端拿定位用wx.getLocationtype选gcj02因为国内地图坐标系是 GCJ-02用 WGS-84 会整体偏移几百米这是位置签到最常见的翻车点。3. SSM 后台接口实现从 Controller 到 MyBatis 落库前端判定逻辑清楚了接下来是后台怎么把这些校验串起来并落库。SSM 的分层是 Controller 收请求、Service 做校验和业务、Mapper 管数据库。签到接口的关键是「先校验、再落库、唯一索引兜底」这个顺序任何一步颠倒都可能产生脏数据。3.1 签到接口的 Controller 与统一返回Controller 只做参数接收和结果包装不写业务逻辑。签到接口接收taskId、学生身份从 token 解析不信前端传的 studentId、以及各模式对应的参数密码、手势路径、经纬度。RestController RequestMapping(/api/sign) public class SignController { Autowired private SignService signService; PostMapping(/do) public Result doSign(RequestBody SignDTO dto, HttpServletRequest request) { // 从 token 解析学生ID,防止前端伪造 Long studentId (Long) request.getAttribute(studentId); if (studentId null) { return Result.fail(未登录); } try { signService.doSign(dto, studentId); return Result.ok(签到成功); } catch (BizException e) { return Result.fail(e.getMessage()); } } }SignDTO里放taskId、password、gesturePath、lng、lat这些可空字段Service 根据任务的sign_type决定用哪个。统一返回Result对象前端只看code和msg避免每个接口返回结构不一致导致前端写一堆兼容代码。3.2 Service 层的校验顺序与事务边界Service 是核心。doSign方法的执行顺序必须是查任务是否存在且进行中 → 按类型校验 → 插入签到记录。校验和插入之间要尽量短减少并发窗口。Transactional public void doSign(SignDTO dto, Long studentId) { SignTask task signTaskMapper.selectById(dto.getTaskId()); if (task null || task.getStatus() ! 1) { throw new BizException(签到任务不存在或已结束); } // 先做通用时间校验,所有模式都必须在时间窗口内 checkTimeLimit(task); switch (task.getSignType()) { case 1: break; // 限时,时间校验已覆盖 case 2: if (!checkPassword(task, dto.getPassword())) throw new BizException(密码错误); break; case 3: if (!checkGesture(task, dto.getGesturePath())) throw new BizException(手势错误); break; case 4: if (dto.getLng() null || dto.getLat() null) throw new BizException(未获取到定位); if (!checkLocation(task, dto.getLng(), dto.getLat())) throw new BizException(不在签到范围内); break; default: throw new BizException(未知签到类型); } SignRecord record new SignRecord(); record.setTaskId(task.getId()); record.setStudentId(studentId); record.setSignLng(dto.getLng()); record.setSignLat(dto.getLat()); try { signRecordMapper.insert(record); } catch (DuplicateKeyException e) { // 唯一索引拦截重复签到 throw new BizException(你已签到,请勿重复提交); } }注意Transactional加在这里但真正防重复靠的是数据库唯一索引不是事务。事务只能保证插入的原子性挡不住两个请求同时通过校验再同时插入。DuplicateKeyException的捕获是最后一道防线也是唯一可靠的一道。3.3 MyBatis Mapper 与唯一索引兜底Mapper 层用注解或 XML 都行插入语句没什么特别关键是依赖前面建的唯一索引uk_task_student。查询任务用主键查走索引很快。public interface SignRecordMapper { Insert(INSERT INTO sign_record(task_id, student_id, sign_lng, sign_lat, sign_time) VALUES(#{taskId}, #{studentId}, #{signLng}, #{signLat}, NOW())) int insert(SignRecord record); }这里sign_time用数据库的NOW()而不是 Java 传时间保证时间基准统一在数据库侧避免应用服务器和数据库服务器时钟不一致。如果部署在多台应用服务器上这一点尤其重要。3.4 小程序端请求封装与定位获取小程序端把wx.request封装一层统一带 token、统一处理错误码。定位用wx.getLocation注意它需要用户授权且要在app.json里声明requiredPrivateInfos。// utils/request.js const BASE_URL https://your-domain.com/api; function request(url, method, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: method, data: data, header: { Authorization: wx.getStorageSync(token) }, success: (res) { if (res.data.code 200) resolve(res.data); else { wx.showToast({ title: res.data.msg, icon: none }); reject(res.data); } }, fail: reject }); }); } // 位置签到前获取定位 function getLocation() { return new Promise((resolve, reject) { wx.getLocation({ type: gcj02, // 国内必须用 gcj02 success: resolve, fail: () reject(new Error(定位失败,请检查权限)) }); }); }type: gcj02是硬性要求。用默认的wgs84在国内会整体偏移学生明明在教室却显示在几百米外这是位置签到最高频的「玄学」问题。另外wx.getLocation在部分安卓机型上首次调用会返回缓存位置建议连续取两次、取第二次的结果或者提示用户稍等两秒再签。4. 部署与联调MySQL 配置、SSM 打包和小程序域名代码写完只是开始能不能跑起来取决于环境配置。这一章把 MySQL 安装配置、SSM 项目打包部署、小程序合法域名这三件事讲清楚都是新手最容易卡住的地方。4.1 MySQL 安装配置与建库建表MySQL 建议用 5.7 或 8.0两者在utf8mb4和默认认证插件上有差异。8.0 默认用caching_sha2_password老版本 JDBC 驱动连不上要么升级驱动要么改回mysql_native_password。-- 建库,字符集必须 utf8mb4,否则存 emoji 或特殊字符会报错 CREATE DATABASE sign_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 建专用账号,别用 root 跑应用 CREATE USER sign_user% IDENTIFIED BY YourStrongPwd123; GRANT ALL PRIVILEGES ON sign_db.* TO sign_user%; FLUSH PRIVILEGES;连接串里几个关键参数useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai。serverTimezone不配8.0 驱动会报时区错误characterEncoding不配中文可能乱码。这两个是血泪经验配一次记一辈子。4.2 SSM 项目打包与 Tomcat 部署SSM 项目用 Maven 打成 war 包丢进 Tomcat或者打成可执行 jar 用内嵌 Tomcat。课程设计场景推荐 war 外置 Tomcat改配置方便。# 打包,跳过测试加快速度 mvn clean package -DskipTests # 产物在 target/ 下,复制到 Tomcat webapps cp target/sign-system.war /opt/tomcat/webapps/数据库连接配置放在jdbc.properties里别硬编码在 XML。生产环境密码用环境变量注入别提交到代码仓库。Tomcat 的server.xml里Connector的URIEncoding设成UTF-8否则 GET 请求中文参数乱码。4.3 小程序合法域名与 HTTPS 要求微信小程序请求的域名必须是 HTTPS且要在微信公众平台后台配置到「request 合法域名」里。本地开发时可以在开发者工具里勾选「不校验合法域名」但上线前必须配好否则真机上所有请求都失败。配置项位置要求request 合法域名公众平台-开发-开发设置HTTPS已备案定位权限声明app.json requiredPrivateInfos含 getLocation服务器域名证书服务器有效非自签名自签名证书在小程序里不被信任必须用正规 CA 签发的证书。开发阶段用开发者工具的「不校验」开关过渡上线前务必换成正式证书并配好域名。5. 签到系统的避坑与排查那些让判定失效的细节这一章集中讲实际跑起来后遇到的问题。每一条都是「现象 → 原因 → 解决」的结构都是真实会遇到的不是理论推演。5.1 位置签到总是提示「不在范围内」现象学生明明坐在教室里位置签到却反复提示不在签到范围内换个手机又正常。原因一是坐标系用错前端用了wgs84而目标点是gcj02两者在国内差几百米二是室内 GPS 信号弱wx.getLocation返回的是基站或 WiFi 定位精度可能到几百米三是allow_radius设得太小。解决前端强制type: gcj02目标点也用同一坐标系录入allow_radius至少设 200 米室内场景放宽到 300 米前端连续取两次定位取第二次或提示用户到窗边再签。如果还不行在签到记录里存下实际距离方便老师人工核对。5.2 签到截止瞬间出现重复记录或报错现象签到截止那一秒部分学生收到「系统错误」或者数据库里出现同一人两条记录。原因并发请求同时通过校验再同时插入唯一索引虽然能挡住重复但抛出的DuplicateKeyException没被捕获时会变成 500 错误返回给前端。解决Service 里显式捕获DuplicateKeyException并转成友好提示把校验和插入之间的逻辑压到最短截止时间前几秒前端可以禁用按钮防止连点。唯一索引是底线捕获异常是体验。5.3 手势签到在部分机型上识别不准现象同样的手势在 A 手机上能签B 手机上提示手势错误。原因不同机型触摸事件采样率不同快速滑动时可能漏掉中间经过的点位导致前端拼出的路径和老师设定的不一致。解决前端手势采集时对相邻点做插值补全或者降低采样要求只记录停留超过一定时间的点老师设定手势时避免用快速划过的斜线用相邻点组成的路径更稳。后端比对前对路径做一次规范化比如统一去重相邻重复点。5.4 服务器时间和数据库时间不一致导致限时误判现象限时签到明明没到截止时间部分学生却提示已截止。原因应用服务器和数据库服务器时钟不同步或者应用服务器时区配置错误new Date()拿到的时间比实际差几小时。解决所有时间判定统一用数据库时间SQL 里用NOW()比对别用 Java 的new Date()服务器统一配 NTP 时间同步JDBC 连接串配serverTimezoneAsia/Shanghai。多台应用服务器部署时这一点尤其关键。5.5 小程序真机上请求全部失败现象开发者工具里一切正常真机预览时所有接口都请求失败。原因没在公众平台配置 request 合法域名或者域名没备案、证书无效。解决公众平台后台配置合法域名确保 HTTPS 证书有效且已备案开发阶段真机调试可在开发者工具里开启「不校验合法域名」临时过渡但上线前必须配好正式域名。这是上线前最后一道坎也是最容易忘的。6. 从能签到到防代签设备指纹与行为校验的进阶思路基础签到跑通后真正要解决的是「防代签」。密码可以转发手势可以截图位置可以虚拟定位限时也挡不住同学之间互相帮忙。要提升可信度得从「验证知识」转向「验证在场」。一个成本低、见效快的做法是采集设备指纹。学生签到时不只传业务参数还传device_info包括设备型号、系统版本、微信版本后端把这些和student_id绑定。如果同一个设备短时间内给多个学号签到就标记为可疑。这不是绝对可靠但能挡住大部分「一个手机帮全宿舍签」的情况。// 签到后异步做设备聚集检测 public void checkDeviceCluster(Long taskId, String deviceInfo, Long studentId) { // 查同一任务下,该设备关联的不同学号数量 int count signRecordMapper.countDistinctStudentByDevice(taskId, deviceInfo); if (count 3) { // 超过阈值,标记异常,供老师复核 signRecordMapper.markSuspicious(taskId, deviceInfo); } }阈值设 3 是个经验值一个设备给超过 3 个学号签到基本可以判定是代签。这个检测放在签到成功后异步执行不阻塞主流程也不影响正常签到体验。另一个思路是行为校验。位置签到可以要求学生在签到后 30 秒内保持定位开启后端随机抽查一次位置两次位置差异过大说明中途离开。手势签到可以要求手势路径包含至少一个「非最短路径」的转折增加截图复制的难度。这些手段单独用都有漏洞组合起来能把代签成本抬到大多数人懒得做的程度。验证方法上我一般会做一次「红队测试」找几个同学一个用虚拟定位、一个转发密码、一个截图手势看系统能不能识别出来。识别不出来的就针对性加校验。这个测试比写多少单元测试都管用。最后说个我自己的习惯签到系统的日志一定要留全谁在什么时间、用什么设备、什么位置签的全部落库。不是为了监控而是当学生对签到结果有异议时你有据可查。我吃过一次亏学生说签了系统没记录查日志发现是他自己网络超时重试导致前端显示成功但后端没收到从那以后所有签到接口都加了幂等和重试日志。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
计及光伏逆变器快速无功响应的分布式电源优化配置 1. 为什么传统分布式电源配置方案会“浪费”光伏的无功价值?先抛个实际场景。你在做一个园区或县域配电网的分布式电源规划,手里拿着几个光伏电站的接入申请,业主要求在满足电压质量的前提下尽量多装容量。于是你按常规思路建了个优化模型&am… · 2026/9/25 6:29:05
OOK/ASK/FSK/GFSK调制方式详解:从原理到射频实战选型 OOK、ASK、FSK、GFSK这四种调制方式,是我这些年调试各种无线模块时绕不开的老伙计。无论是做315MHz遥控、433MHz数传,还是搞BLE蓝牙低功耗和LoRa物联网节点,最后都得回来跟这几个名字打交道。很多人一看到这串缩写,第一反应是“这… · 2026/9/25 6:29:05
悉尼大学DBMS课程实战指南:从SQL到事务恢复的底层认知 简介:悉尼大学数据库管理系统课程资料,内容覆盖数据库核心理论与应用,适合正在学习数据库原理的高校学生、准备课程考试或希望系统复习数据库管理系统的技术人员。压缩包共65个文件,大小约17.04MB,以PDF课件和带答案的… · 2026/9/25 6:28:59
Atlas 300V Pro 24G推理加速卡部署YOLO全流程实战 跑AI推理的工程师,最近应该没少在各种群里看到“Atlas部署YOLO”这类话题。尤其当热搜词里同时出现“atlas 300v 24g 是运算加速卡吗”这种问题时,我意识到很多刚接触昇腾推理的开发者,对这块卡的身份定位、部署链路和性能边界,其… · 2026/9/25 7:34:39
Atlas 300V 24G推理加速卡实战:YOLOv5部署与避坑指南 1. 先回答热搜问题:Atlas 300V 24G到底算不算“运算加速卡”最近“atlas 300v 24g 是运算加速卡吗”这个问题被问得很多,再加上“atlas部署yolo”这个热搜词,我大概能猜到提问者的处境:要么是刚把这张卡买到手,正在纠结… · 2026/9/25 7:34:39
AIGC短漫剧工业化生产方法论:从生成到交付的全流程管控 1. 短漫剧不是“AI画图配音”拼凑,而是有完整工业逻辑的轻量级影视生产最近三个月,我带团队落地了7部AIGC短漫剧项目,最长的一部24集,单集时长98秒,全网总播放量破1.2亿。但最让我意外的,不是数据ÿ… · 2026/9/25 7:34:39
Atlas 300V 24G加速卡实战:从NPU原理到YOLO模型推理部署全流程 1. 从热搜问题聊起:Atlas 300V 24G 到底是什么最近后台一直被同一个问题刷屏,很多人拿着一块“Atlas 300V 24G”问我这算不算运算加速卡,还有些人直接问能不能拿它来跑 YOLO。我琢磨了一圈,这不光是新手在选型上犯迷糊,… · 2026/9/25 7:34:33
Atlas 300V 24G推理卡部署YOLOv5全流程实战与避坑指南 开篇先把话说清楚:以“atlas”这个词搜到我这篇内容的人,大部分不是来看星座神话的,而是手里已经拿到或正打算入手一张华为 Atlas 300V 推理卡,想在上面把 YOLO 跑起来。这卡在深度学习圈子里一直有点“低调”,官方资料… · 2026/9/25 7:34:33
python-for-android 命令行完全指南:toolchain.py 全部命令与参数详解 开发工具构建工具移动开发 【免费下载链接】python-for-android Turn your Python application into an Android APK 项目地址: https://gitcode.com/gh_mirrors/py/python-for-android 点击查看 免费下载 本篇指南以 python-for-android(p4a࿰… · 2026/9/25 7:34:27
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37