资讯动态

宿舍考勤系统开发实战:Ruoyi+uniapp三种签到方式全解析

发布时间:2026/10/14 3:39:19 来源:尧图企业网站定制
简介一套基于若依Ruoyi框架与uniapp技术的前后端分离学生宿舍考勤管理系统源码包面向需要毕业设计参考、课程实训或校园信息化开发学习的前后端技术人员。系统兼顾多种签到场景口头点名、普通签到、自定义范围位置签到、二维码签到、人脸识别签到、九宫格手势签到和签到码签到并接入阿里云人脸识别与OSS存储同时支持微信小程序密码登录与微信授权登录配套可视化数据看板可直观查看出勤统计与异常记录。压缩包共包含82个文件以Java与class源码、XML/yml配置、PNG/JPG界面截图和说明文档为主整体大小约1.77MB目录结构便于按模块查阅。目前已有176人学习下载。通过阅读源码、配置及截图可以掌握Ruoyi权限管理、uniapp跨端开发、位置范围校验、阿里云人脸识别对接、OSS文件存储以及数据图表展示等关键思路适合直接导入开发工具梳理工程脉络也能作为宿舍考勤类项目二次开发的参考基础。1. 宿舍考勤不是打卡三种签到方式背后是三种管理粒度做过宿舍考勤的人都知道最难的不是写代码而是回答宿管老师一句话“我要的点名和你做的签到不是一回事。”学生宿舍考勤和公司打卡最大的区别在于打卡只需要证明“人来了”宿舍考勤要证明“人确实在宿舍”而且不同场景下“在宿舍”的证据强度完全不一样。基于 Ruoyi 和 uniapp 做前后端分离的学生宿舍考勤管理系统核心就是把口头点名签到、普通签到、位置签到这三种方式揉进同一套业务模型里让宿管老师按场景去选而不是替系统做决定。本文写给准备动手做这套系统的开发者从表设计、后端校验到 uniapp 端的定位签到把完整的落地路径和踩过的坑一次说清。2. Ruoyi 后端与 uniapp 前端的分离架构表设计和接口边界2.1 前后端分离的职责划分用 Ruoyi 做后端、uniapp 做学生端这是目前比较顺手的组合。Ruoyi 的前后端分离版自带 Spring Boot MyBatis 的后端服务、Vue 的管理后台、Shiro 或 Sa-Token 的权限体系、代码生成器和一套现成的用户/角色/菜单模型。你要做的不是从零搭框架而是在这套体系里长出“宿舍考勤”这个业务模块。uniapp 负责的是学生手机端签到页面、记录查询、消息提醒以及宿管老师手持端的口头点名操作界面。职责边界我一般这样切Ruoyi 后端管所有业务规则——时间窗口、位置半径、重复签到校验、统计报表、导出uniapp 只做两件事——展示规则和采集证据。采集证据包括用户点了哪个按钮、当前定位坐标、口头确认的学生编号。为什么强调这个边界因为签到系统的信任模型天然要求“规则在服务端、证据在客户端”。如果你把时间判断、防重复逻辑写在前端学生改个手机时间、清个缓存、抓个包就能绕过后面统计和导出全乱。接口层面全部走 JSON统一返回{ code, msg, data }uniapp 端封装一个request.js统一处理 token 和错误码这套在 Ruoyi 里已经有现成约定直接沿用即可。2.2 考勤表设计把三种签到落进同一套表结构表设计是这套系统最值得花时间的部分。很多初学者会为三种签到方式各建一张表这是典型的给自己挖坑——后面统计出勤率要 UNION 三张表口径稍微不对就对不上。正确的做法是一张attendance_record表用sign_type字段区分方式用状态字段记录结果。先看核心建表 SQL-- 宿舍楼 CREATE TABLE dorm_building ( building_id BIGINT PRIMARY KEY AUTO_INCREMENT, building_name VARCHAR(64) NOT NULL COMMENT 楼栋名称, latitude DECIMAL(10, 6) NULL COMMENT 楼栋中心纬度, longitude DECIMAL(10, 6) NULL COMMENT 楼栋中心经度, radius INT NULL COMMENT 默认签到半径(米), manager_id BIGINT NULL COMMENT 宿管老师用户ID, status CHAR(1) DEFAULT 0 COMMENT 0正常 1停用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 宿舍楼栋表; -- 宿舍房间 CREATE TABLE dorm_room ( room_id BIGINT PRIMARY KEY AUTO_INCREMENT, building_id BIGINT NOT NULL COMMENT 所属楼栋, room_no VARCHAR(16) NOT NULL COMMENT 房间号, floor INT NULL COMMENT 楼层, capacity INT DEFAULT 4 COMMENT 可住人数, UNIQUE KEY uk_building_room (building_id, room_no) ) COMMENT 宿舍房间表; -- 住宿学生 CREATE TABLE dorm_student ( student_id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT 关联系统用户表, student_no VARCHAR(32) NOT NULL COMMENT 学号, room_id BIGINT NOT NULL COMMENT 宿舍房间, real_name VARCHAR(32) NOT NULL COMMENT 姓名, phone VARCHAR(16) NULL COMMENT 手机号, status CHAR(1) DEFAULT 0 COMMENT 0在住 1退宿, UNIQUE KEY uk_user (user_id) ) COMMENT 住宿学生表; -- 考勤规则 CREATE TABLE attendance_rule ( rule_id BIGINT PRIMARY KEY AUTO_INCREMENT, rule_name VARCHAR(64) NOT NULL COMMENT 规则名称, building_id BIGINT NULL COMMENT 楼栋ID空表示全校通用, room_id BIGINT NULL COMMENT 房间ID空表示楼栋级, sign_type TINYINT NOT NULL COMMENT 1口头点名 2普通签到 3位置签到, start_time TIME NOT NULL COMMENT 签到开始时间, end_time TIME NOT NULL COMMENT 签到结束时间, latitude DECIMAL(10, 6) NULL COMMENT 有效位置纬度(位置签到时用), longitude DECIMAL(10, 6) NULL COMMENT 有效位置经度, radius INT DEFAULT 100 COMMENT 有效半径米, status CHAR(1) DEFAULT 0 COMMENT 0启用 1停用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 考勤规则表; -- 考勤记录 CREATE TABLE attendance_record ( record_id BIGINT PRIMARY KEY AUTO_INCREMENT, student_id BIGINT NOT NULL COMMENT 学生ID, room_id BIGINT NOT NULL COMMENT 宿舍房间, rule_id BIGINT NULL COMMENT 使用的规则ID, sign_type TINYINT NOT NULL COMMENT 1口头 2普通 3位置, sign_date DATE NOT NULL COMMENT 签到日期, sign_time DATETIME NOT NULL COMMENT 签到时间, latitude DECIMAL(10, 6) NULL COMMENT 上报纬度, longitude DECIMAL(10, 6) NULL COMMENT 上报经度, address VARCHAR(255) NULL COMMENT 解析出的地址文本, source CHAR(1) DEFAULT 0 COMMENT 0学生端 1宿管端代录, remark VARCHAR(255) NULL COMMENT 备注(口头点名填老师备注), create_by VARCHAR(64) NULL COMMENT 登记人, UNIQUE KEY uk_student_date (student_id, sign_date) ) COMMENT 考勤签到记录表;表设计有三个关键决策。第一attendance_record用student_id sign_date做唯一索引这是防重复签到的最后一道物理屏障后面接口层再防一遍双保险。第二attendance_rule里sign_type是单值字段说明一条规则只对应一种签到方式不要试图做“同时支持普通和位置”的混合规则统计口径会被你搞死。第三经纬度字段用DECIMAL(10, 6)精度约 0.1 米足够宿舍楼场景使用别用FLOAT也别用VARCHAR存坐标字符串后面做距离计算要转来转去。请假和外出登记可以单独建attendance_leave表和考勤记录通过student_id sign_date关联统计时先排除请假学生这块后面再细说。2.3 给 uniapp 端暴露的 6 个核心接口前后端分离的项目接口契约要先定清楚不然前端等到后端全部写完再联调周期会拖很长。我一般先列接口清单定义好入参出参两边并行开发。这套系统最少需要 6 个接口接口方法入参出参说明/attendance/rule/currentGETroomId当前生效的规则及签到状态学生进页面先拉规则/attendance/record/submitPOSTstudentId、roomId、signType、lat、lng、address签到结果三种签到方式统一入口/attendance/record/myTodayGETstudentId今日签到记录、迟到/未归状态学生查看今日状态/attendance/record/monthGETstudentId、month月度签到日历数据按月展示/attendance/record/voiceConfirmPOSTstudentIds、roomId、remark批量确认结果宿管口头点名代录/attendance/statistics/buildingGETbuildingId、date楼栋出勤、迟到、未归统计宿管看板需要特别注意的是第三个和第六个。/attendance/record/submit是唯一的写入口无论是学生普通签到还是宿管口头代录都走这一个接口用参数区分。/attendance/record/voiceConfirm看名字是单独接口实际内部也是循环调用submit只是把source标记为宿管端、sign_type标记为口头。这样设计的好处是统计层只需要查attendance_record一张表过滤条件也只有sign_date和sign_type不会有口径分叉。token 校验走 Ruoyi 的拦截器uniapp 端登录后把Authorization头带上即可。3. 用 Ruoyi 代码生成器把考勤接口落地建表到签到校验3.1 代码生成器的使用路径与配置要点Ruoyi 的代码生成器能省掉大量重复的 CRUD 代码但很多人用不好原因是对它的预期不对。它不是业务引擎它只负责生成“表对应的基础增删改查 前端页面”复杂的业务校验必须自己改写。我通常的做法是先把 2.2 节里的表全部建好然后登录 Ruoyi 管理后台进入“系统工具 → 代码生成”点击“导入”把表导入进来逐表编辑配置。配置阶段有四个地方必须改。一是“生成模板”选单表别选树表考勤表没有树形结构。二是“字段信息”里把create_by、create_time这类字段的插入和编辑勾选去掉交给 MyBatis 自动填充。三是latitude、longitude的表单类型改成数字框默认是文本框校验规则也要去掉必填因为口头签到和普通签到不上传坐标。四是每张表的“业务名称”填成中文比如attendance_record的业务名称填“考勤记录”生成的前端菜单和注释会舒服很多。配置完成后点击“生成代码”会下载一个 zip里面是 controller、service、mapper、xml 和 Vue 页面文件按 Ruoyi 文档的路径覆盖到工程里即可。这里我要强调一个血泪经验生成的代码先别急着跑要把生成的文件覆盖到工程后重启服务然后逐个功能在管理后台点一遍。很多“翻车”发生在 mapper XML 里因为表名和字段名是驼峰还是下划线由mybatis配置决定Ruoyi 默认开了驼峰映射但你如果手工微调过表结构生成的文件不会跟着自动变。跑通基础 CRUD 之后再开始改业务逻辑。3.2 改写签到接口时间窗、防重复与位置半径校验代码生成器给的是通用接口签到系统真正的核心在校验逻辑。我一般把签到入口收敛成一个 service 方法三段校验时间窗校验、防重复校验、位置校验。看核心代码Override public AjaxResult submitSign(SignRequest req) { // 1. 查出当前对该学生生效的规则 AttendanceRule rule attendanceRuleMapper.selectActiveRule( req.getRoomId(), req.getSignType(), LocalDate.now()); if (rule null) { return AjaxResult.error(当前没有生效的签到规则); } // 2. 时间窗校验一律以服务器时间为准 LocalTime now LocalTime.now(); if (now.isBefore(rule.getStartTime()) || now.isAfter(rule.getEndTime())) { return AjaxResult.error(不在签到时间范围内); } // 3. 防重复校验数据库唯一索引是最后屏障 AttendanceRecord existing attendanceRecordMapper .selectByStudentAndDate(req.getStudentId(), LocalDate.now()); if (existing ! null) { return AjaxResult.error(今日已签到请勿重复提交); } // 4. 位置校验只有 sign_type 3 才做 if (rule.getSignType() 3) { if (req.getLatitude() null || req.getLongitude() null) { return AjaxResult.error(未获取到定位信息); } double distance GeoUtil.distance( req.getLatitude(), req.getLongitude(), rule.getLatitude(), rule.getLongitude()); if (distance rule.getRadius()) { return AjaxResult.error(当前位置超出签到范围 String.format(%.0f, distance) 米); } // 距离在半径 80% 以内正常通过80%-100% 之间记录告警 req.setRemark(distance rule.getRadius() * 0.8 ? 边缘签到 : req.getRemark()); } // 5. 组装记录落库 AttendanceRecord record new AttendanceRecord(); record.setStudentId(req.getStudentId()); record.setRoomId(req.getRoomId()); record.setRuleId(rule.getRuleId()); record.setSignType(rule.getSignType()); record.setSignDate(LocalDate.now()); record.setSignTime(LocalDateTime.now()); record.setLatitude(req.getLatitude()); record.setLongitude(req.getLongitude()); record.setAddress(req.getAddress()); record.setSource(req.getSource() null ? 0 : req.getSource()); record.setRemark(req.getRemark()); attendanceRecordMapper.insert(record); return AjaxResult.success(签到成功); }逻辑说明第一步根据房间和签到方式查出当前生效规则selectActiveRule的排序规则要考虑优先级——先精确匹配房间级规则没有再匹配楼栋级最后匹配全校通用规则SQL 里用ORDER BY加限定条件实现。第二步时间窗注意LocalTime.now()取的是服务器时间不是客户端传上来的时间。第四步的位置校验是整个接口的灵魂坐标为空直接拒绝距离超过半径直接拒绝边缘签到不拒绝但打上标记这个标记在月底统计“疑似代签”时非常有用。参数说明SignRequest是前端传参的对象包含studentId、roomId、signType、latitude、longitude、address、source、remark八个字段。GeoUtil.distance是工具类内部实现用 Haversine 公式计算两个经纬度点之间的球面距离精度在几十米范围内足够。radius 的默认值我建议设 100 米宿舍楼场景下 50 米太紧GPS 漂移会让你天天处理投诉后面章节专门讲这个坑。3.3 三种签到方式的适用场景与选择判断这一节不讲代码讲业务判断。口头点名签到适合低年级本科生宿管老师晚上查寝时挨个宿舍走一圈口头确认学生在不在然后在手持端勾选名单批量提交。普通签到适合研究生、走读生这类不需要核实物理位置的人群规则定一个时间窗比如 22:00 到 23:00学生打开应用点一下即可老师看统计结果。位置签到适合需要确认“人在楼内”的场景借助宿舍楼中心的经纬度和半径圈定范围学生到楼内才能签上。三种方式的选择原则是“证据强度和管理成本成正比”。口头点名证据最强但宿管成本最高位置签到证据中等但成本低普通签到证据弱但最省事。实际部署时同一个楼栋可以早晚用不同规则晚查寝用口头点名白天签到用位置签到。attendance_rule表里start_time和end_time字段决定了规则的时间生效范围同一天可以配多条记录查询时用“当前时间落在区间内”作为过滤条件。这个设计在 Ruoyi 的管理后台配置时要注意规则的生效和停用不要只用status字段控制要结合时间区间去判断不然到点忘了切状态考勤就废了。4. uniapp 端三套签到流程的实现与定位参数设置4.1 页面结构与签到入口设计uniapp 这边的页面结构我按角色拆成两个入口学生端和宿管端同一套代码用角色字段区分。学生端的核心页面是pages/sign/index.vue—— 进入后先请求/attendance/rule/current拿到今天适用的规则类型然后根据类型动态渲染签到区域口头点名时显示“等待宿管确认”状态普通签到显示一个大的“签到”按钮位置签到显示当前定位距离和签到按钮。宿管端的核心页面是pages/manager/voice.vue—— 按楼栋拉取学生列表逐个口头确认后批量提交。页面的状态设计有一个容易忽略的点签到页要处理“规则未配置”“不在时间窗”“已签到”三种前置状态。很多人只做了“已签到”判断结果规则没配的时候学生点签到按钮后端返回“当前没有生效的规则”体验很差。正确做法是进入页面先拉规则和今日签到状态用一个pageStatus字段区分四种状态no_rule、out_of_time、already_signed、ready前三种直接渲染对应文案和图标只有ready才展示签到按钮。这样后端接口的报错压力也小很多。4.2 普通签到和口头点名的前端实现普通签到的前端逻辑很直白页面加载时请求今日状态如果状态是ready渲染一个带倒计时的按钮用户点击后调用/attendance/record/submit。这里的关键是倒计时结束后要主动重新拉取状态而不是只把按钮置灰——因为时间窗过了以后后端会拒绝请求前端要提前响应这个变化。看一个简化的提交逻辑// pages/sign/index.vue import request from /utils/request export default { data() { return { rule: null, pageStatus: loading, // no_rule / out_of_time / already_signed / ready submitting: false, location: null } }, onLoad() { this.loadPageData() }, async loadPageData() { // 并行拉规则和今日状态减少一次请求往返 const [ruleRes, statusRes] await Promise.all([ request.get(/attendance/rule/current, { roomId: this.roomId }), request.get(/attendance/record/myToday, { studentId: this.studentId }) ]) this.rule ruleRes.data if (!this.rule || this.rule.status ! 0) { this.pageStatus no_rule } else if (statusRes.data.signed) { this.pageStatus already_signed } else { this.pageStatus ready } }, async submitSign() { if (this.submitting) return this.submitting true try { const payload { studentId: this.studentId, roomId: this.roomId, signType: this.rule.signType, latitude: null, longitude: null, address: } // 位置签到类型才需要获取定位 if (this.rule.signType 3) { const loc await this.getLocation() payload.latitude loc.latitude payload.longitude loc.longitude payload.address loc.address || } const res await request.post(/attendance/record/submit, payload) if (res.code 200) { uni.showToast({ title: 签到成功, icon: success }) this.pageStatus already_signed } else { uni.showToast({ title: res.msg, icon: none }) } } finally { this.submitting false } } }逻辑说明submitSign里先判断规则类型只有signType 3才调用getLocation()普通签到不请求定位权限——这点很重要因为 iOS 的定位权限弹窗一次误点“拒绝”后要到设置里手动开启很多学生不会操作直接导致签到失败。submitting标志位防止重复点击同时按钮上绑定了:disabledsubmitting属性前后端双层防重复。口头点名的前端实现不一样不是学生主动点而是宿管老师操作。页面从楼栋列表进入房间列表再到学生列表每个学生后面两个按钮“在”“不在”点击后把状态暂存在本地数组里最后统一提交到/attendance/record/voiceConfirm。这里有个体验细节如果某个学生已经签过到再点“在”后端会返回“已签到”这时候前端要从暂存列表里移除该学生不然批量提交时报错会中断整个流程。口头点名适合大屏平板操作uniapp 编译到 iPad 或 Android 平板上效果会比手机好很多。4.3 位置签到gcj02 定位、半径参数与权限配置位置签到是三种方式里技术坑最多的主要集中在坐标系和定位精度两个地方。uniapp 的uni.getLocation在 App 端默认返回 gcj02 坐标国测局坐标在小程序端也是返回 gcj02但安卓 WebView 里偶尔会返回 wgs84。后端如果拿高德地图 API 解析地址或者做距离计算必须统一用 gcj02否则会有 200 到 500 米的偏差直接导致“人在宿舍却提示超出范围”。看定位代码// utils/location.js export function getLocation() { return new Promise((resolve, reject) { uni.getLocation({ type: gcj02, // 强制使用国测局坐标系 isHighAccuracy: true, highAccuracyExpireTime: 3000, success: (res) { resolve({ latitude: res.latitude, longitude: res.longitude, accuracy: res.accuracy || 0 }) }, fail: (err) { // 常见错误码1 权限拒绝2 定位失败3 定位超时 reject(err) } }) }) }参数说明type: gcj02必须写死这是和后端约定的坐标系谁都不能改。isHighAccuracy: true在 Android 上会调起高精度模式耗电增加但定位快很多宿舍签到场景可以接受。accuracy字段返回精度范围单位米如果精度大于 100 米说明定位质量很差前端应该提示用户移动到窗边再试而不是盲目提交。权限配置是另一个大头。小程序端要在manifest.json里声明requiredPrivateInfos: [getLocation]以及permission字段里的scope.userLocation说明文案App 端要在App.vue的onLaunch里申请定位权限或者调用uni.authorize主动引导。审核规范里要求定位用途说明必须写清楚不能只写“签到”要写“用于宿舍考勤签到确认学生位置”否则小程序提审会被驳回。还有一个经常翻车的地方签到成功之后要把定位信息缓存下来而不是签到失败后再重新定位。因为宿舍楼内 GPS 信号弱学生第一次定位可能花了 10 秒如果因为网络超时提交失败重新走一遍定位流程会让学生烦躁。常见做法是把最近一次成功的定位坐标存在uni.setStorageSync(last_location)提交失败重试时优先使用缓存坐标然后后台静默刷新定位等下次进入页面时再更新。5. 签到开发避坑定位漂移、重复提交与时间窗口5.1 定位漂移学生在宿舍里却提示“超出范围”现象学生反馈“我明明坐在床上签到却提示超出范围 120 米”位置签到成功率不足 70%。原因GPS 在室内基本失效手机走的是基站和 Wi-Fi 定位误差在 50 到 200 米之间波动再加上宿舍楼本身有建筑偏移如果当初采集楼栋中心坐标时用的是地图 App 随手点的位置误差会进一步放大。解决分两步走。第一步把attendance_rule里的radius从默认 50 米放宽到 100 米这是最低成本止血方案但治标不治本第二步找一个晴好天气在楼栋门口、宿舍窗户边分别采集 5 个坐标点取平均值作为楼栋中心坐标用测试账号遍历楼层签到验证确保楼上楼下都能通过。后端把边缘签到的距离告警日志打开观察一周如果大量记录落在 80 到 100 米区间说明楼栋中心坐标偏了需要重新校准这就不是学生的手机问题了。5.2 弱网重试导致重复签到现象学生在楼道里点签到网络超时前端自动重试了两次后台出现两条签到记录虽然前端展示还是“已签到”但统计接口的数据翻倍了。原因前端请求超时后自动重试而后端接口没有幂等控制两次请求都通过了校验——因为第一次请求虽然写了库但响应还没回到前端第二次校验“今日是否已签到”时数据库里已经有了却被并发绕过。解决双保险。后端先加唯一索引兜底也就是 2.2 节里的UNIQUE KEY uk_student_date (student_id, sign_date)这样即使校验漏了数据库也会抛异常拒绝第二条同时前端把签到按钮改成submitting状态点击后立即禁用不依赖接口响应只有接口明确返回失败才恢复按钮。注意唯一索引的异常要捕获并转成友好提示“今日已签到”不能直接抛 500。5.3 手机时间被篡改绕过时间窗现象有学生把手机时间改到晚上 10 点整在下午 3 点就完成了晚签到后台记录的时间是下午 3 点和规则完全不符。原因前端把“当前时间”作为签到依据显示倒计时后端校验用的却是客户端传上来的时间字段。一些开发者在做接口时图省事让前端把signTime一起传上来这就留了个大后门。解决后端一律不信任客户端时间。签到接口的signTime字段在后端用LocalDateTime.now()生成前端传的任何时间字段都忽略。倒计时只用于 UI 展示不参与业务判断。另外在管理后台加一个“签到时间异常”的筛选条件查询sign_time和sign_date发生在不同自然日的记录定期导出检查防止有人利用跨时区漏洞。5.4 统计口径不一致迟到、未归、请假边界模糊现象宿管老师看到楼栋出勤率是 92%但辅导员导出名单后说“怎么少了几个人”两边数据对不上。原因出勤率的分子分母定义不统一。有人把“请假”算作出勤有人不算有人把“迟到”算作未签到有人算签到还有人把退宿学生算进分母导致统计值看起来很离谱。解决统计口径必须在 service 层定义一次所有接口调用同一个统计方法。我这里的口径是分母 在住状态且未请假的学生数分子 分母中当天有签到记录的人数迟到 签到时间晚于规则end_time但仍在当天 23:59 之前未归 当天无签到记录且无请假记录。这三个规则写进一个枚举类或者配置类里前端展示、Excel 导出、定时报表全走这个口径不许各写各的。开发时建议先在测试环境用模拟数据对一遍把“迟到算不算出勤”这类问题在开发阶段就吵完不要上线后让宿管老师背锅。6. 把考勤数据用起来导出、统计与异常预警的进阶做法签到做通以后这套系统真正产生价值的是数据层。我建议优先做三件事Excel 导出、楼栋出勤看板、未归预警。Excel 导出直接复用 Ruoyi 的ExcelUtil导出前把统计口径统一成上一章定义的规则避免二次加工PostMapping(/export) public void export(HttpServletResponse response, RequestBody ExportReq req) { ListAttendanceRecordVO list attendanceService.getExportList(req); ExcelUtilAttendanceRecordVO util new ExcelUtil(AttendanceRecordVO.class); util.exportExcel(response, list, 宿舍考勤记录); }楼栋出勤看板用一条 SQL 就能出核心数据按楼栋分组统计应到、实到、迟到、未归SELECT r.building_id, COUNT(DISTINCT s.student_id) AS total_students, COUNT(DISTINCT CASE WHEN rec.id IS NOT NULL THEN s.student_id END) AS signed_count, COUNT(DISTINCT CASE WHEN rec.sign_time r2.end_time THEN s.student_id END) AS late_count FROM dorm_student s LEFT JOIN dorm_room r ON r.room_id s.room_id LEFT JOIN attendance_record rec ON rec.student_id s.student_id AND rec.sign_date #{date} LEFT JOIN attendance_rule r2 ON r2.building_id r.building_id AND #{time} BETWEEN r2.start_time AND r2.end_time WHERE s.status 0 GROUP BY r.building_id;未归预警用 Ruoyi 的定时任务功能每天晚上查寝时间过后扫一遍当天无签到记录且无请假的学生触发微信订阅消息或短信通知。定时任务里要加一个保护开关如果系统刚部署、宿舍楼还没配规则不要推送避免第一天晚上就轰炸宿管老师。这一点是我做模拟项目 X 时最深刻的教训——上线当晚宿管老师收到 80 条未归通知结果是规则表里status字段忘记置为启用全员未签到。后来我在预警任务里加了一个前置判断attendance_rule表中生效规则数为 0 时直接跳过推送并把告警日志打出来。签到系统这类业务最怕的不是功能做不出来而是数据错了没人发现等月底对账才炸出来。希望这篇笔记能帮你把该踩的坑提前避开做出一套宿管老师愿意天天用的考勤系统。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑