ASP.NET MVC考勤系统:先更新上条下班记录再新增打卡记录
考勤刷卡逻辑修复方案
现存问题定位
- 业务判断逻辑放前端完全无效:前端无法获取数据库中该卡号的历史打卡状态,原有
es.HoraEntrada == null的判断永远不成立,所有请求都会被错误标记为下班打卡 - 后端接口仅实现了新增记录逻辑,缺失「补全上条未闭合记录下班时间、计算工时」的核心流程
- 前端传参使用拼接的
dd/MM/yyyy格式时间字符串,无法被EF正确绑定为DateTime类型,容易出现日期格式解析错误 - AJAX回调函数名拼写错误:
sucess少写字母c,导致成功回调永远无法触发 - 刷卡后文本域未清空,同一张卡连续刷卡时不会触发change事件
后端修复实现(Controller层)
所有时间、打卡状态判断全部移到后端处理,统一取服务器时间,避免前端篡改本地时间作弊,同时用EF事务保证数据一致性,不会出现只更新没新增、或者只新增没更新的脏数据。
[HttpPost] public ActionResult AddReport(int IdCartao) { using (EMSistec bd = new EMSistec()) { // 开启事务,保证更新和新增操作原子性 using (var trans = bd.Database.BeginTransaction()) { try { DateTime now = DateTime.Now; // 1. 查询当前卡号最新的未闭合记录(下班时间为空) var lastUnclosedRecord = bd.entradas_saidas .Where(r => r.IdCartao == IdCartao && r.HoraSaida == null) .OrderByDescending(r => r.IdE_S) .FirstOrDefault(); // 2. 如果存在未闭合记录,补全下班时间、计算当日工时 if (lastUnclosedRecord != null) { lastUnclosedRecord.HoraSaida = now; // 计算工时,单位为小时,如需精确到分钟可以改存TotalMinutes double workHours = (now - lastUnclosedRecord.HoraEntrada.Value).TotalHours; // 兼容跨天夜班场景:如果算出来是负数说明跨天,加24小时 if (workHours < 0) workHours += 24; lastUnclosedRecord.HoraDiarias = (int)Math.Round(workHours, 0); bd.Entry(lastUnclosedRecord).State = System.Data.Entity.EntityState.Modified; } // 3. 新增本次的上班打卡记录 entradas_saidas newRecord = new entradas_saidas() { IdCartao = IdCartao, DataRegisto = now.Date, HoraEntrada = now, HoraSaida = null, HoraDiarias = null }; bd.entradas_saidas.Add(newRecord); // 4. 统一提交变更 bd.SaveChanges(); trans.Commit(); return Json(new { msg = "SUCCESS" }, JsonRequestBehavior.AllowGet); } catch (Exception ex) { trans.Rollback(); return Json(new { msg = "ERROR", error = ex.Message }, JsonRequestBehavior.AllowGet); } } } }
注意:接口参数只需要接收卡号即可,所有时间值由后端服务器生成,不要信任前端传入的时间参数,从根源上避免代打卡、篡改时间的问题。
前端修复实现(View脚本部分)
简化前端逻辑,只负责读取卡号、发送请求、处理结果,去掉所有前端业务判断:
$(function () { // 页面加载后自动聚焦刷卡输入框,避免手动点击 $("#readONchange").focus(); $("#readONchange").change(function () { // 读取卡号,去掉前后空格、读卡器带的特殊字符 let cardId = $.trim($(this).val()); if (!cardId) { alert("未读取到有效卡号"); $(this).val('').focus(); return; } $.ajax({ type: "POST", url: '@Url.Action("AddReport")', // 只传卡号参数 data: JSON.stringify({ IdCartao: parseInt(cardId) }), dataType: "json", contentType: "application/json; charset=utf-8", // 修正原拼写错误:sucess -> success success: function (res) { if (res.msg === "SUCCESS") { alert("打卡成功"); // 清空输入框、重新聚焦,准备下次刷卡 $("#readONchange").val('').focus(); // 刷新页面显示最新记录 location.reload(); } else { alert("打卡失败:" + res.error); $("#readONchange").val('').focus(); } }, error: function () { alert("网络错误,打卡失败,请重试"); $("#readONchange").val('').focus(); } }); }); });
优化建议
- 数据库层面对
IdCartao和HoraSaida字段建立联合索引,大幅提升未闭合记录的查询速度,适配大量打卡数据的场景 - 增加卡号合法性校验:后端查询卡号是否在员工表中存在,不存在直接返回错误,不写入无效记录
- 如果需要支持多次进出(比如一天打4次卡:上午上班、中午下班、下午上班、下午下班),现有逻辑完全兼容,每次刷卡都会自动闭合上一条、新增新的上班记录,工时会自动分段存储
- 可以给刷卡输入框加输入长度限制,避免读卡器异常时读入过长的无效字符串
内容的提问来源于stack exchange,提问作者NewbieProgrammer
相关产品推荐
相关产品推荐

