先说背景:我们服务的一家少儿培训连锁,3个校区、80多个班、约3000名学员。学员考勤和课时记录跑在一套托管式建站的SaaS底座上,日常签到数据都入这套系统的账本;但"老师手机断网时怎么把签到补上、补签怎么防重复、每天怎么对账"这类强业务逻辑,是我们自己在它的数据接口基础上实现的——它管基础考勤记录,我们管异常流水的补平。
问题很现实:校区在地下室、教室隔音墙厚,老师手机动不动就"网络不给力"。改造前每月考勤异常流水约200条,不是丢了就是重复,月底对账对到教务怀疑人生。
先拆场景,弱网下的签到失败其实有三种:
三种情况混在一起,光靠"多试几次"解决不了,得给签到设计一套能扛住断网的链路。我们最初的版本只有一条"签到→扣课"的直连请求,上线第二周就出现同一个学员同一天被扣两次课的事故,家长直接找上门。从那以后我们把签到拆成"记录、确认、扣课"三段,每一段都有状态可查。
老师端签到不再直接请求服务器,而是先写本地队列,再异步同步:
// 老师端:签到先落本地队列
function enqueueSignin(signin) {
const key = `${signin.lessonId}_${signin.studentId}`; // 本地幂等键
localQueue.put(key, signin); // SQLite/IndexedDB 持久化
syncWorker.start(); // 有网自动推,断网挂起重试
}本地队列有两个好处:断网时签到不丢,老师界面即时反馈"已记录,待同步";同步时每个请求带本地幂等键,服务端能认出来是不是同一次签到。这层解决的是"完全断网"。
同步还要处理两个细节:一是重试要有退避,网络恢复后不能一瞬间把几百条积压请求全打出去,我们按每批20条、间隔2秒的节奏推;二是乱序,老师先签了第二节再补第一节,队列按时间戳排序推送,避免服务端先处理后面的课导致课时包扣错顺序。对老师来说,操作上跟以前没有区别——点了就显示"已记录",同步是后台的事,不用再等转圈。
本地队列只能防客户端重复,服务端还得防网络重试和并发。签到流水表加唯一键和状态机:
CREATE TABLE signin_flow (
id BIGINT PRIMARY KEY,
biz_key VARCHAR(64) NOT NULL, -- lessonId_studentId_签到日期
status TINYINT NOT NULL DEFAULT 0, -- 0待确认 1已确认 2已补签 3已作废
UNIQUE KEY uk_biz (biz_key)
);
-- 扣课只发生在 status 从 0 -> 1 的那一次
UPDATE signin_flow SET status = 1
WHERE biz_key = ? AND status = 0;唯一索引让"同一次签到"只有一条流水,重复请求要么命中已存在返回原结果,要么更新影响行数为0——扣课只执行一次。这层解决的是"请求到了但重复了"。
要注意状态机为什么必须显式写:如果直接用"插入成功就扣课"的写法,唯一索引会把重复插入拦下来,但"拦下来"和"扣课成功"之间没有原子性,一旦扣课在拦截之后执行,重复请求还是会把课时扣成负数。把扣课放进状态流转(只有 0→1 那次才扣),重复请求走不到扣课分支,才真正闭环。
最后一道是"人"的环节。每天凌晨跑对账任务,把"课时消耗汇总"和"签到流水"比对,差异自动列出:
# 每日对账:找出有消耗无签到的流水
# 疑似重复:同一 biz_key 多条 active 流水对账发现的异常分两类处理:漏签的,走补签审批流(老师提交补签单→教务核对课表与学员课时→审批通过才补记,补记同样带幂等键);重复的,直接作废多余流水并回补课时。改造后补签审核平均10分钟完成,月度对账差异从200多条降到0。
对账脚本本身也要注意幂等:每天重跑不能重复生成差异单,我们给差异单也加了"对账日期+校区"的唯一键,当天重复执行只会更新同一张单。另外补签审批最好限定在课后48小时内,超过时限教务要写原因,防止补签流被滥用成"随便改考勤"的后门。
坑1:本地队列不持久化,App重启队列清空,断网数据全丢——必须落SQLite/IndexedDB
坑2:幂等键生成时机不对,老师重试时重新生成新key,服务端当成新签到——key必须在首次生成后固定
坑3:只防客户端不防服务端重试,网络层重发照样重复扣课——服务端唯一索引兜底
坑4:补签直接改流水,绕过状态机,重复补签重复扣——补签必须走审批流+幂等
坑5:不对账,月底发现课时对不上只能人工翻记录——每天定时对账,差异当天暴露
离线签到不是"换网络好点的教室"能解决的,它是客户端队列、服务端幂等、人工审批三层配合的工程:本地先记账、唯一键去重、每日对账补平。三层做完,老师断网也能正常签到,教务月底不再对账对到怀疑人生,家长群里也不会再出现"孩子课没上却被扣了课时"的投诉。这套链路不止适用于签到,凡是"移动端高频写入 + 弱网环境"的场景——比如门店收银离线记账、外勤打卡,都可以直接复用。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。