基于安卓的学生签到系统:定位漂移与防代签的完整落地实践
简介这是一套面向高校课程考勤与教学管理场景的安卓学生签到系统完整源码适合计算机相关专业学生做课程设计、毕业设计或安卓与SSM框架练手。项目采用Android Studio开发移动端后端基于SSM框架并在IntelliJ IDEA中搭建支持扫码签到、数字签到、定位签到三种方式签到记录可修改另附加课码选课功能覆盖考勤与选课两大教学管理需求。资源包共3个文件包含2个rar压缩包与1个sql数据库文件分别对应安卓前端工程、后端服务工程和初始数据库脚本整体约139.01MB解压后可直接导入部署。目前已有837人学习下载读者可借此获得一套结构完整、功能可运行的前后端分离项目方案快速理解移动端与SSM后端的接口对接、数据库表设计及签到逻辑实现也可在此基础上二次开发或作为答辩演示素材。1. 基于安卓的学生签到系统从定位漂移到防代签的完整落地思路做过校园信息化的人大多被同一个问题折磨过老师拿着纸质名单挨个点名一节课五分钟没了数据还停留在纸上。基于安卓的学生签到系统要解决的就是这件事——把点名搬到手机端让签到数据实时进库、可统计、可追溯。它适合两类人一类是学校信息化岗的开发者需要一套能跑在普通安卓机上的轻量方案另一类是带课老师或学生干部想自己搭一套不依赖第三方平台的小工具。但真正动手就会发现难点从来不是画个界面点个按钮而是定位漂移、代签、弱网提交失败这三座大山。这篇笔记按我实际踩过的路径从技术选型讲到参数调优把每一步能复现的细节摊开说。2. 技术选型安卓端、服务端和定位方案怎么定2.1 为什么优先选原生安卓而不是跨平台框架标题锁死了基于安卓所以第一件事是确认技术栈。市面上做签到类应用常见三条路原生 AndroidKotlin/Java、跨平台框架Flutter/React Native、以及套壳 WebView。我一般推荐原生理由很实在签到系统重度依赖定位、蓝牙、相机扫码、后台保活这些系统能力跨平台框架在定位精度和后台权限上经常隔一层出问题时排查成本高。原生直接调FusedLocationProviderClient拿到的是系统融合定位结果精度和功耗都可控。服务端不用搞复杂一个 Spring Boot 或 Node.js 的 REST 接口加 MySQL 就够了。核心表就三张课程表、签到任务表、签到记录表。签到记录表里必须存经纬度、定位精度、设备标识、时间戳这几个字段是后面防代签和申诉的依据。提示如果学校已有统一身份认证登录环节直接对接别自己再造一套账号体系否则后期数据对不上是常态。2.2 定位方案GPS、基站、WiFi 三者怎么取舍签到能不能用八成取决于定位准不准。安卓的定位来源有三类GPS卫星、基站蜂窝、WiFi热点。室内教室 GPS 信号弱经常飘到几十米外WiFi 定位在密集教学楼里反而更稳。实际做法是让系统自动融合代码里设置定位优先级为高精度模式同时接受这三种来源。关键参数是签到半径。设太大学生在隔壁楼也能签设太小站在教室后排都签不上。我的经验值是室内 50 到 80 米室外 100 到 150 米。这个值不是拍脑袋要结合定位精度动态判断——如果手机上报的accuracy是 30 米半径却只给 20 米那必然误判。// 请求高精度定位融合 GPS/WiFi/基站 val locationRequest LocationRequest.Builder( Priority.PRIORITY_HIGH_ACCURACY, // 高精度模式 5000L // 5 秒更新一次签到场景够用 ).apply { setMinUpdateDistanceMeters(5f) // 移动 5 米才回调省电 setWaitForAccurateLocation(true) // 等待精度达标再返回 }.build() // 拿到位置后判断是否在签到半径内 fun isInRange( userLat: Double, userLng: Double, userAcc: Float, targetLat: Double, targetLng: Double, radius: Double ): Boolean { val results FloatArray(1) Location.distanceBetween(userLat, userLng, targetLat, targetLng, results) val distance results[0] // 把定位误差算进去避免精度差导致误判 return distance radius userAcc }这段代码有两个要点。第一setWaitForAccurateLocation(true)会让系统多等一会儿拿更准的结果签到场景值得这点延迟。第二判断距离时把userAcc定位精度加进半径这是很多人忽略的地方——手机自己都承认误差 30 米你却按精确点算必然出问题。2.3 服务端接口设计一次签到要传哪些字段接口设计决定了后面能不能防代签。签到请求不能只传学生ID 课程ID那样谁都能伪造。至少要带上经纬度、定位精度、设备唯一标识、客户端时间戳、以及一个服务端下发的动态令牌。-- 签到记录表核心字段 CREATE TABLE sign_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, course_id BIGINT NOT NULL, student_id BIGINT NOT NULL, sign_time DATETIME NOT NULL, -- 服务端时间不信客户端 latitude DOUBLE, -- 签到纬度 longitude DOUBLE, -- 签到经度 accuracy FLOAT, -- 定位精度米 device_id VARCHAR(64), -- 设备标识防一机多签 token VARCHAR(64), -- 动态令牌防重放 status TINYINT DEFAULT 1, -- 1正常 2异常待审 UNIQUE KEY uk_course_student (course_id, student_id) );sign_time一定要用服务端时间客户端时间可以被改。device_id用来识别同一台设备给多个账号签到的情况。token是老师发起签到时服务端生成的有效期设 2 到 5 分钟过期作废这样截个二维码慢慢传也来不及。3. 动手实现从发起签到到数据落库的完整链路3.1 老师端发起签到动态令牌怎么生成和刷新老师点发起签到服务端做三件事生成一个随机令牌、记录签到开始时间、设定有效期。令牌用 UUID 加时间戳哈希别用自增 ID否则能被猜。// 服务端发起签到生成动态令牌 public String startSign(Long courseId, int validSeconds) { String token UUID.randomUUID().toString().replace(-, ); // 存 Rediskey 带课程维度过期自动失效 String key sign:token: courseId; redisTemplate.opsForValue().set(key, token, validSeconds, TimeUnit.SECONDS); // 同时记录签到开始时间用于判断迟到 redisTemplate.opsForValue().set( sign:start: courseId, String.valueOf(System.currentTimeMillis()), validSeconds, TimeUnit.SECONDS ); return token; }validSeconds我一般设 120 到 300 秒。太短学生来不及操作太长代签空间大。令牌存 Redis 而不是数据库是因为它天然带过期不用自己写清理逻辑。老师端界面把这个令牌转成二维码显示学生扫码后带着令牌提交签到。3.2 学生端签到流程扫码、定位、提交三步学生端流程要顺任何一步卡住都会有人放弃。顺序是扫码拿令牌 → 请求定位 → 组装数据提交。定位这步要加超时和降级不能无限等。// 学生端扫码后执行签到 suspend fun doSign(token: String, courseId: Long): SignResult { // 1. 拿定位最多等 8 秒 val location withTimeoutOrNull(8000L) { getCurrentLocation() } ?: return SignResult.Fail(定位超时请到窗边重试) // 2. 组装请求体 val body mapOf( courseId to courseId, token to token, latitude to location.latitude, longitude to location.longitude, accuracy to location.accuracy, deviceId to getDeviceId() ) // 3. 提交失败进本地队列等重试 return try { val resp api.sign(body) if (resp.code 0) SignResult.Success else SignResult.Fail(resp.msg) } catch (e: IOException) { LocalQueue.enqueue(body) // 弱网兜底 SignResult.Pending(网络异常已缓存待提交) } }withTimeoutOrNull(8000L)是血泪经验——不设超时室内定位偶尔要等半分钟学生以为卡死了就退出重来。8 秒拿不到就提示换位置。弱网时把请求塞进本地队列等网络恢复再补交这是保证签到率的关键。3.3 服务端校验五道关卡拦住异常签到收到请求后不能直接入库要过五道校验。任何一道不过标记为异常待审而不是直接拒绝给申诉留口子。校验项判断逻辑不通过处理令牌有效性Redis 中是否存在且未过期直接拒绝时间窗口服务端时间在签到有效期内标记迟到距离范围距离 ≤ 半径 精度标记异常待审设备唯一同课程同设备只允许一个账号标记异常待审重复签到唯一索引冲突返回已签到// 服务端校验核心逻辑 public SignResult verify(SignRequest req) { // 1. 令牌校验 String cached redisTemplate.opsForValue() .get(sign:token: req.getCourseId()); if (cached null || !cached.equals(req.getToken())) { return SignResult.reject(签到已结束或令牌无效); } // 2. 距离校验把精度算进去 double dist GeoUtil.distance( req.getLatitude(), req.getLongitude(), course.getLatitude(), course.getLongitude() ); boolean inRange dist course.getRadius() req.getAccuracy(); // 3. 设备校验 boolean deviceUsed recordMapper.existsByCourseAndDevice( req.getCourseId(), req.getDeviceId() ); // 任一异常都入库但标记待审不直接拒绝 int status (inRange !deviceUsed) ? 1 : 2; recordMapper.insert(buildRecord(req, status)); return SignResult.ok(status); }注意这里标记待审而不是直接拒绝的设计。定位偶尔飘、设备换新机直接拒绝会误伤真实学生留个待审让老师人工判断体验好很多。4. 避坑与排查那些让签到系统翻车的细节4.1 定位一直返回 null 或精度极差现象学生反馈一直提示定位超时日志里location为 null 或accuracy上百米。原因通常是权限没给全——安卓 6.0 以后定位权限分粗略和精确两级只申请粗略权限拿不到 GPS。解决在 Manifest 里同时声明ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION运行时两个一起请求。另外室内要提示用户开 WiFi纯 GPS 在楼里基本没戏。4.2 同一台手机给多个账号签到现象一个学生拿室友手机挨个签。原因只校验了账号没校验设备。解决device_id用Settings.Secure.ANDROID_ID加应用签名哈希生成同一课程内限制一台设备只能签一个账号。注意别用 IMEI新系统拿不到。4.3 弱网下签到请求丢失现象学生说我点了签到但系统没记录。原因请求发出后网络断了客户端没做重试。解决本地用 Room 建个待提交队列提交失败先落库起个 WorkManager 定时任务在网络恢复后补交补交时带上原始时间戳服务端按时间戳判断是否在有效期内。4.4 服务端时间与客户端时间对不上现象明明在有效期内签到却被判迟到。原因用了客户端时间做判断。解决所有时间判断以服务端为准客户端时间只做展示。签到有效期从服务端发起那一刻算起别信手机时钟。4.5 二维码被拍照外传代签现象老师把二维码投屏有人拍照发给没来的人。解决令牌有效期压到 2 分钟以内并且令牌绑定发起签到的设备或教室 WiFi 的 BSSID扫码时校验 BSSID 是否一致。这招能挡住大部分远程代签。5. 进阶技巧把签到数据用起来和防代签的最后一公里签到系统做完基础功能只是及格真正拉开差距的是数据利用和防代签深度。先说数据。签到记录表积累一学期后能算出每个学生的出勤率、迟到频次、以及总是踩点签到的行为模式。我一般会加一个统计接口按课程和学生两个维度聚合老师一眼能看到谁需要关注。SQL 层面用GROUP BY加条件聚合就够不用上大数据那套。-- 按学生统计出勤情况 SELECT student_id, COUNT(*) AS total, SUM(CASE WHEN status 1 THEN 1 ELSE 0 END) AS normal, SUM(CASE WHEN status 2 THEN 1 ELSE 0 END) AS abnormal, ROUND(SUM(CASE WHEN status 1 THEN 1 ELSE 0 END) / COUNT(*), 3) AS rate FROM sign_record WHERE course_id ? GROUP BY student_id ORDER BY rate ASC;再说防代签的最后一公里。前面讲的令牌、设备、BSSID 能挡住大部分但挡不住人来了手机没来或者手机来了人没来。进阶做法是加一步活体校验签到成功后随机要求拍一张现场照片或者做一次人脸比对。照片方案成本低但要注意隐私合规只存特征不存原图用完即删。人脸比对要接第三方能力小项目不一定值得。还有一个容易被忽略的点签到系统的后悔药机制。学生确实到了但定位飘了得有个申诉入口让他上传一张现场照片或让老师手动补签。这个入口不做客服压力会全压到你身上。我的习惯是异常记录自动进待审列表老师端一键批量处理比让学生挨个找你强得多。最后说个验证方法上线前别只测自己一个人。找三五个同学一个在教室、一个在隔壁楼、一个在操场同时签到看系统判断是否符合预期。再模拟一次断网签到确认补交队列能正常工作。这套流程跑通基本就能交付了。我自己第一次做的时候没测弱网结果正式用的时候一半人签到失败那场面至今记得。希望帮到你。本文还有配套的精品资源点击获取