这个模块的特殊性要先说清楚:在绝大多数功能里,用户希望它正常工作;而在打卡功能里,有一小部分用户会主动尝试绕过它。模拟定位软件、改系统时间、找同事代打卡——这些是真实存在的。
但这里有个陷阱:如果只盯着防作弊,就会误伤正常员工,而误伤的代价更高。
我们第一版偏向了防作弊:检测到疑似模拟定位就直接拒绝打卡。上线后立刻收到大量投诉,而绝大多数投诉者是正常员工——有些机型的定位接口返回的信息容易被判成可疑、有些员工装了某些工具类应用被误判、还有单纯是定位精度差。他们打不了卡,直接影响工资和考勤记录,情绪极其强烈。
这件事教出这个模块最重要的判断:两类错误的代价严重不对称。误判正常员工的代价是「影响他的工资与考勤记录,而且他完全无辜」;放过一次可疑打卡的代价是「一次可能的作弊,而且事后仍可追溯」。所以处理方式必须是「标记并转人工复核」而不是「直接拒绝」。
第二个设计是多信源交叉校验,而不是依赖单一信号。单一信号的误判率太高,交叉之后才有区分力:定位来源类型(是卫星定位还是网络定位)、定位精度值(精度几百米的定位不能用来做精确围栏判定)、与上一次打卡的位移与时间是否物理可达(十分钟前在另一个城市打卡,现在出现在这里,物理上不可能)、设备与账号的常用特征(突然换了设备)。
这些信号汇总成一个可信度评分,低于阈值则标记为可疑。关键是「标记」这个动作的定位:它是给 HR 的信息,不是给员工的拒绝。
第三个设计针对一类高频误判:围栏判定必须带定位精度容差。第一版是「定位点是否在围栏多边形内」,但定位本身有误差——员工站在公司门口,定位精度是五十米,他的定位点可能落在围栏外五米处,被判为不在范围内。正确做法是把定位精度纳入判定:精度差时用「定位点加精度半径与围栏是否有交集」而不是「点是否在内」。
第四个是申诉与补卡路径。无论规则多好,误判一定会发生,所以必须有出路:员工可以提交申诉并附说明,系统保留完整证据(定位原始数据、可信度评分、判定原因)供 HR 复核。「有申诉路径」这件事本身就降低了误判的伤害。
最后一个是 To B 特有的:防作弊规则的强度必须由客户配置。不同企业的容忍度差别很大——有的客户要求严格(销售团队,考勤直接关联提成),有的希望宽松(研发团队,形式上的考勤)。而且「怎么处罚作弊」是客户的管理决定,不是我们的产品决定。所以我们提供宽松/标准/严格三档,策略与后果由客户承担。
「标记转复核」而不是「直接拒绝」,这是这个模块最重要的决策,依据是两类错误代价的严重不对称。直接拒绝看起来更严格、更能防住作弊;但它的错误方向是「挡住无辜的正常员工,而且直接影响他的工资」——这是不可接受的伤害,而且员工无法自证(他没法证明自己没用模拟定位)。标记转复核的代价是可疑打卡会先通过,但它仍然被记录、仍然可追溯、HR 仍然可以处理。判断依据是:当误判的受害者无法自证、且代价直接落在他身上时,系统就不该做终局判决。这条判断和实体归一里「双阈值三档、中间档转人工」是同一个结构,只是这里的「人」是客户的 HR 而不是我们的审核员。
围栏判定引入精度容差,代价是围栏边界变模糊。严格的点内判定最精确,但它假设定位是准的——而定位本身就有误差,尤其在高楼间、地下、室内。引入容差后,站在围栏外一点但精度较差的员工也能打卡成功,这确实放宽了边界。但取舍很清楚:把「站在公司门口打不了卡」这个高频误判消掉,值得接受边界的一点模糊。缓解手段是精度特别差时(比如超过某个米数)不做围栏判定而是标记为「位置精度不足」交给复核——宁可标记也不要用一个不可靠的定位去做二元判断。
规则强度交给客户配置,这是 To B 产品的必然。我们没有资格决定「多严算严」——销售团队的考勤直接关联提成,客户要求严格;研发团队的考勤是形式上的,客户希望宽松。更重要的是「怎么处罚作弊」是客户的管理决定,我们代为决定就是越界。取舍是多了配置复杂度、而且客户可能配出不合理的组合;缓解手段是给每档配置说明清楚它的影响(严格模式会拒绝哪些情况、可能造成多少误判),让客户在知情下选择。这和「涉及授权的决定要交给客户」是同一条判断。
踩过的坑:直接拒绝可疑打卡,大量正常员工被挡住。上线后投诉激增,而绝大多数投诉者是正常员工——有些机型的定位接口返回信息容易被判成可疑、有些员工装了某些工具类应用被误判、还有单纯是定位精度差。他们打不了卡直接影响工资和考勤记录,情绪极其强烈,客户 HR 也被员工围着问。修法是改为标记转复核。教训是:防作弊类的功能必须先想清「误判的受害者是谁、他能不能自证、代价落在谁身上」——如果受害者无辜且无法自证,就不能让系统做终局判决。
踩过的坑二:围栏按点内判定,站在公司门口的员工打不了卡。定位精度五十米时,员工明明站在门口,定位点落在围栏外几米处,被判为不在范围内。这是投诉量最大的单一原因。修法是引入精度容差,并在精度特别差时改为标记而不做二元判断。教训是:用有误差的测量值做二元判断时,必须把误差本身纳入判定——忽略误差的判定在边界附近必然大量出错,而用户恰恰经常正好在边界上(公司门口就是围栏边界)。
踩过的坑三:可信度评分不对客户可见,HR 无法判断是否采信。我们标记了「可疑」但只给一个结论,HR 问「为什么可疑」,我们答不上来(评分逻辑没有暴露),于是 HR 一律采信员工的说法,标记功能形同虚设。修法是把判定原因与关键信号对客户可见。教训是:不可解释的评分不会被信任——这和内容审核里「要给审核员看机器为什么判不准」是同一条:辅助人做决策的系统必须暴露自己的依据。
踩过的坑四:没有申诉路径,误判只能靠客服工单处理。员工被误判后只能找 HR、HR 找我们的客服、客服再找技术查日志,一次误判要走三层,处理周期好几天,而考勤是按天结算的。修法是在产品内提供申诉与补卡路径,并把证据一起呈现给 HR。教训是:会产生误判的功能必须自带申诉闭环,把纠错路径留在产品外(走客服)意味着纠错成本极高且不可控。
没做的部分:没做人脸活体检测防代打卡。客户提过,但涉及生物特征采集的合规成本很高(见模块三),而且端上活体检测的可靠性在低端机上不稳,当时的替代方案是「外勤打卡要求拍现场照片」——不做身份识别,但照片本身是有约束力的证据。也没做设备绑定(一个账号只能在固定设备打卡),因为员工换机、修机的场景太多,运维成本高。
误判率:这是这个模块最该报也最该诚实报的数字。报被标记为可疑的打卡中,经 HR 复核确认为正常的比例。这个数字直接度量误判,而且它有真实的复核结论作为依据,不是自评。
投诉量:报打卡相关投诉工单的数量,改造前后对比。改造前(直接拒绝)投诉激增是这个模块所有设计的起因,这个对比最能说明问题。
围栏误判:报因定位精度导致的围栏判定失败次数,改造前后对比。并且要报「精度不足转标记」的次数——说明那些不可靠的定位没有被用来做二元判断。
可疑标记的处理率:报标记后被 HR 实际处理的比例。这个数字很关键——如果标记了但 HR 从不处理,说明标记信息不足以支撑判断(可能是判定原因没暴露清楚),功能等于没做。
申诉的处理时长:报从员工申诉到 HR 处理完成的时长,改造前后对比。改造前要走「员工→HR→我们客服→技术查日志」三层,周期好几天,而考勤是按天结算的。
各档规则的客户选择分布:报客户选择宽松/标准/严格的分布。这个数据有产品价值——如果几乎没有客户选严格,说明严格模式的误判代价客户不愿承担,该重新设计那一档。
不要报「防作弊准确率 99%」。作弊手段在演进,而且我们无法知道「没被发现的作弊有多少」——分母压根不可知。也不要报「零误判」,我们的设计本来就假设误判会发生(所以有申诉路径)。正确表述是「可疑标记中经复核确认为正常的比例、投诉量的变化、围栏精度误判的下降、标记的实际处理率、申诉处理时长的变化」——都有外部依据。
打卡有两个和别的功能都不同的硬约束叠在一起。
第一个约束:打卡场所经常没网。地下车库、电梯里、厂区车间、仓库——这些恰恰是很多员工的实际打卡地点。而打卡时刻直接关系工资,不能允许失败。第一版是「点打卡 → 发请求 → 成功才算」,无网时直接失败,员工要跑到有信号的地方重新打,而这时时间已经过了,变成迟到。
第二个约束:设备本地时间不可信。用户可以改系统时间——而在打卡场景里,这不是理论风险,是有人真的会做的事(把时间改到八点五十打卡,实际是九点半到的)。
这两个约束叠在一起产生了这个模块的核心矛盾:无网时必须能打卡(所以时刻只能从本地取),但本地时间不可信(所以不能直接用本地时间)。
解法是「服务端时间校准 + 设备单调时钟推算」:
应用在有网络时定期拉取服务端时间,算出并保存本地时钟的偏移量。打卡时不读设备墙钟,而是用「最近一次校准的服务端时刻 + 单调时钟走过的时长」推算出当前的可信时刻。单调时钟不受用户改系统时间影响,所以这个推算值是可信的;而且它在完全无网时也能算。
同时把设备墙钟时间和偏移量一起上报——让服务端能看到「这个设备的本地时间和真实时间差了多少」,如果差异异常大,那本身就是一个可疑信号(模块一的交叉校验会用到)。
第三是离线暂存与自动补传:无网时打卡落为一条本地暂存记录(含推算的可信时刻、定位、照片),网络恢复后自动补传。
补传要处理三件事。幂等:用本地记录 ID 作幂等键,避免网络抖动导致的重复补传变成两条打卡记录(重复打卡在考勤系统里会引起混乱)。保序:多条暂存记录要按采集顺序补传,否则下班打卡先到、上班打卡后到,考勤计算会出错。失败重试:补传失败要退避重试,并且不能无限重试到耗尽电量。
第四是暂存状态必须在界面上明确可见。这一条很关键:如果只是静默暂存,员工看到的是「点了打卡但好像没成功」,他会反复点,或者跑到有信号的地方再打一次——那就产生了两条记录。所以要明确显示「已保存,待联网提交」,并提供手动重试入口。让用户知道「这次打卡已经算上了」,是这个设计能成立的前提。
「服务端校准 + 单调时钟推算」是这个模块的核心解法,它同时满足了两个看起来矛盾的要求。只用服务端时间:无网时压根打不了卡。只用设备墙钟:用户改时间就能作弊。校准加单调时钟推算既能离线工作,又不受改系统时间影响。代价是长时间不联网会有累积漂移(单调时钟本身也有误差,而且长期不校准偏差会变大),缓解手段是每次有网就校准、并且把「距上次校准多久」也上报——让服务端知道这个时刻的可信程度。这个思路我在拼团倒计时上用过同一套(服务端时间偏移加单调时钟),只是那里的目的是展示准确、这里的目的是防作弊。
上报本地墙钟与偏移量,是把「时间可信度」变成一个可判定的信号。我们本来只需要可信时刻就够了;但把偏移量也上报,服务端就能看出「这台设备的时间被改过很多」——这本身就是模块一交叉校验的一个输入。这个设计的价值是把一个本来会被丢弃的信息变成了风控信号,成本几乎为零。
暂存状态必须可见,这不是体验优化而是正确性要求。如果静默暂存,用户会因为「不确定是否成功」而重复操作,从而产生重复记录——而重复打卡记录会污染考勤计算。所以「让用户知道已经暂存成功」是防止重复记录的手段,而不只是让他安心。这个判断和「乐观更新失败必须明确告知」是同一条线:用户对系统状态的误解会转化成错误的操作。
踩过的坑:无网时打卡直接失败,员工跑到有信号的地方重打变成迟到。厂区车间和地下车库没信号,员工在打卡点点了没反应,只能跑到院子里或者上一层楼再打,而这时已经过了打卡时间;他要去找 HR 说明,HR 再来找我们确认。这类问题的处理成本远超技术成本。修法是离线暂存加自动补传。教训是:当功能的失败会直接影响用户的实际利益(工资)时,「网络失败」不能被当成一个可以让用户自己重试的普通错误——必须由系统承担。
踩过的坑二:静默暂存导致重复打卡记录。第一版做了暂存但界面上没有明确提示,员工点了打卡看到一个短暂的提示就消失了,他不确定是否成功,于是跑到有信号的地方又打了一次;后来暂存记录补传上来,同一个时段出现两条打卡,HR 要人工判断哪条有效。修法是明确显示「已保存,待联网提交」并展示待提交条数。教训是:状态不可见会转化成用户的重复操作,而重复操作在有副作用的场景里会产生脏数据——所以「让用户知道当前状态」有时是正确性要求而不是体验要求。
踩过的坑三:补传不保序,考勤计算出错。员工在无网环境里先打了上班卡、下班时又打了下班卡,两条暂存记录并发补传,下班卡先到达服务端;考勤系统按到达顺序处理,把下班卡当成了上班卡,算出一个荒谬的工时。修法是按采集顺序串行补传。教训是:有先后语义的离线记录必须保序提交——并发提交在网络条件不同时到达顺序不可控,而下游按到达顺序处理就会出错。
踩过的坑四:补传无限重试,耗电被投诉。某个客户的网络环境下补传一直失败(他们的网络策略拦了我们的域名),应用在后台不停重试,用户投诉「这个应用很耗电」。修法是退避重试加次数上限,超过后停止并提示转补卡申请。教训是:后台重试必须有上限与退避,而且「放弃」之后要给用户明确的下一步——只是停下来不告诉用户,暂存记录就永远悬着而他不知道。
没做的部分:没做基于蓝牙或 Wi-Fi 信标的近场打卡(在无网环境下用信标确认位置)。这个方案对地下车库这类场景很合适,但需要客户在场地部署硬件,属于交付成本,只有少数大客户接受。也没做打卡数据的端到端加密,暂存数据只做了本地加密存储。
离线打卡成功率:确定性验证加数据——断网后打卡,检查记录落为暂存、网络恢复后自动补传成功。同时报暂存记录最终补传成功的比例与转补卡申请的比例。
无网导致的打卡失败:报相关工单数量,改造前后对比。改造前员工要跑到有信号的地方重打变成迟到,HR 再来找我们确认,这类问题的处理成本远超技术成本——工单数最能说明改善。
重复打卡记录:报同一时段出现多条打卡记录的次数,改造前后对比。这个数字直接度量「暂存状态可见」这个改动的效果——静默暂存时员工会重复操作。
时刻可信度:报设备墙钟与校准时刻的偏移量分布。这个分布本身就是有价值的风控数据——偏移异常大的设备值得关注。同时报「距上次校准的时长」分布,说明推算值的可信程度。
补传保序:确定性验证——制造多条暂存记录后恢复网络,检查服务端接收顺序与采集顺序一致。这是踩坑后固化的用例。
补传幂等:确定性验证——模拟补传过程中网络抖动导致重复提交,检查服务端只产生一条记录。
后台耗电:报退避重试上线前后的后台耗电对比,并说明机型。这是踩过投诉之后的改动。
不要报「打卡成功率 100%」。设备没电、应用被卸载、本地存储被清理,这些仍会导致打卡记录丢失;而且补传长期失败时我们的设计是转补卡申请,本身就不承诺 100%。正确表述是「离线暂存与补传由用例保证、暂存补传成功率与转补卡比例、重复记录次数的下降、墙钟偏移量分布作为风控信号、保序与幂等由确定性用例保证」。
这个模块讲的是一件在 C 端产品里压根不存在的问题:我们采集的是员工的位置,而付钱的是他的雇主。
这个结构带来一个根本张力:客户(企业)的诉求和被采集者(员工)的利益不一致。客户提过的需求包括「记录员工全天轨迹」「上班时间每小时采一次位置」「离开办公区就告警」。这些技术上都不难,但做了就是问题。
第一版没有认真处理这件事,做了一个「外勤模式下持续上报位置」的功能(借用了骑手端的定位上报能力)。上线后出了两类事。
第一是员工的强烈反弹。有员工发现应用在持续获取位置,在公司内部群里讨论「公司在监控我们」,最后客户的 HR 来要求我们关掉。这件事的伤害是双向的:员工不信任这个应用(会想办法卸载或禁权限),客户也觉得我们给他带来了管理麻烦。
第二是合规审查提出了问题。另一个客户的法务在采购评估时问:「你们采集了什么、存多久、员工知不知道、能不能删」——我们答不上来。而这四个问题恰恰是这类合规审查的标准问题。
所以这个模块的核心是把「采集的边界」当成产品设计的一部分,而不是技术能力的自然延伸。具体五条。
第一是只做动作触发的单点采集,不做持续跟踪。打卡时采一次、外勤签到时采一次、提交拜访记录时采一次——每次都是用户主动发起的动作。「用户主动发起」这一点很关键:它让采集有了明确的、用户可感知的边界。而持续后台跟踪没有这个边界。
第二是采集时机与用途要显式告知。不是藏在隐私政策里的一句话,而是在采集的那一刻在界面上说清「本次将记录你的位置用于考勤核验」。告知的成本几乎为零,而它把「偷偷采集」变成了「明示采集」——这两者在员工感受和合规评价上是天壤之别。
第三是非工作时段与非外勤状态下不采集。这一条是主动的自我约束:即使客户配置了外勤模式,下班后也不采。因为下班后的位置和考勤无关,采了就是过界。
第四是字段最小化与留存期限。只存必要的坐标、精度、时刻——不存周边信息、不存移动轨迹、不存停留时长。留存期限按客户配置(考勤争议的追溯期通常是几个月),到期自动清理。
第五是员工可以查询自己被采集的全部记录。这一条既是合规要求(可访问性),也是建立信任的手段——让员工能看到「系统一共采了我多少次、每次是什么时候」,比任何隐私政策都有说服力。
最后,对于客户要求的更强能力:我们的做法不是直接拒绝,而是「需要在管理端显式确认并承担告知员工的责任」。因为这确实是客户的管理权范围,但责任必须落到他身上,不能由我们默默提供。
只做动作触发的单点采集、拒绝持续跟踪,这是一个产品边界决策而不是技术决策。技术上实现持续跟踪很简单(我们在骑手端已经有成熟的后台定位能力,直接搬过来就行);而恰恰因为它简单,才更需要在产品层面主动划边界。划边界的依据是「这个采集对完成用户的任务是否必要」:考勤核验只需要知道「打卡那一刻他在哪」,不需要知道他一天去过哪些地方。取舍是拒绝了一部分客户需求,缓解手段是给出替代方案(外勤拜访要求提交现场照片与签到,用「多个动作触发点」覆盖客户想要的过程管理诉求),而不是简单说「我们不做」。
「显式告知」的成本几乎为零而收益巨大,这类改动值得优先做。在采集那一刻显示一行说明,技术上不值一提;但它把「员工发现应用在偷偷取位置」变成了「员工知道这次打卡会记录位置」。前者会引发信任崩塌(员工会想办法卸载或禁权限),后者是可接受的。我认为这个案例说明:在涉及用户信任的问题上,「透明」往往比「技术上更完善」更有效。
把客户的更强需求处理成「显式确认加责任转移」,而不是直接拒绝或默默提供。直接拒绝会丢单,默默提供会让我们承担本不该承担的合规风险。中间路径是:能力可以有,但必须由客户在管理端显式开启、并确认由他负责告知员工。这和「首次登录策略由租户定」「防作弊强度由客户配」是同一条判断:涉及客户管理权与合规责任的决定,要交给客户并让责任可追溯。取舍是要做配置与确认流程,但这个流程本身就是合规证据。
踩过的坑:借用骑手端的持续定位能力做外勤,引发员工反弹。我们做了「外勤模式下持续上报位置」,员工发现应用在持续获取位置,在公司内部群里讨论「公司在监控我们」,最后客户 HR 来要求我们关掉。伤害是双向的:员工不信任这个应用(会想办法卸载或禁权限),客户也觉得我们给他带来了管理麻烦。修法是改为动作触发的单点采集并显式告知。教训是:技术能力可以复用,但产品边界不能复用——骑手端持续上报位置是履约必需且骑手对此有明确预期;员工端没有这个前提,同样的技术在不同的关系结构里性质完全不同。
踩过的坑二:合规审查的四个标准问题答不上来,影响了采购。客户法务问「你们采集了什么、存多久、员工知不知道、能不能删」,我们当时没有明确答案,采购评估被卡了一段时间。修法是把这四个问题的答案设计到产品里并文档化:采集字段清单、留存期限(可配置)、界面告知机制、员工可查询与到期自动清理。教训是:To B 产品里合规审查有标准问题清单,这些问题应该在设计阶段就被回答,而不是在采购阶段被动应付——而且它们的答案不是文档,是产品能力。
踩过的坑三:留存期限配了但清理任务没跑。我们做了留存期限配置,但清理的定时任务在一次发版后失效了,没人发现,历史位置数据一直在积累;是后来做数据盘点才发现的。修法是给清理任务加执行监控与结果上报。教训是:合规相关的自动化任务必须被监控——它的失败是静默的(不清理不会报错),而合规问题一旦被发现就是既成事实,无法追溯补救。
踩过的坑四:员工拒绝位置权限就不能打卡。第一版把位置作为打卡的硬前提,拒绝权限的员工压根打不了卡,只能被迫开启——这既引起反感也不合理。修法是降级为「无位置打卡并标记」,交客户策略决定是否接受。教训是:不要用「不给权限就不能用」去强迫用户,那是把系统的需要凌驾于用户的选择之上;正确做法是提供降级路径并把「是否接受」的决定权交给有权决定的一方(这里是客户企业)。
没做的部分:没做人脸识别防代打卡(模块一提过)。除了技术可靠性,更主要的原因是生物特征采集的合规成本远高于位置——它涉及更严格的告知同意与存储要求,而我们评估后认为收益不足以覆盖这个风险。也没做员工的数据导出(把自己的采集记录导出成文件),只做了应用内查询。
采集范围:这一项报的是清单而不是数字——采集了哪些字段、在哪些动作时采集、不采集什么。这个清单本身就是最有说服力的交付物,而且它是合规审查会直接要的东西。
持续跟踪类需求的处理:报收到多少次此类需求、给出了什么替代方案、多少客户接受了替代方案。「接受替代方案的比例」很关键——如果客户普遍不接受,说明替代方案没有真正覆盖他们的诉求,需要重新设计而不是硬顶。
告知的到达:报采集前告知的展示率(应该接近全部)。如果不是全部,要说清哪些路径漏了。
留存与清理:报清理任务的执行成功率与实际清理的数据量。这个数字必须有——我们踩过「配了期限但清理任务失效」的坑,而它的失败是静默的。
员工查询的使用:报员工查询自己采集记录的次数。这个数字的意义是双重的:证明入口可用;使用率高说明员工对这件事是关注的,这本身就支撑了「透明化」的必要性。
权限拒绝率与降级:报位置权限的拒绝率,以及降级为无位置打卡的次数。拒绝率的变化很有意义——改造前(持续跟踪)拒绝率高,改造后(单点采集加告知)应该下降,这说明透明化换回了信任。
合规审查结果:报客户方合规或法务评估中提出的问题数与整改情况。诚实的表述是「曾因四个标准问题答不上来被卡过采购,之后把答案设计进产品并文档化」——承认过去的不足比声称一直合规可信。
不要报「隐私合规 100% 达标」。合规要求随地区与法规变化,而且「达标」由客户与监管判定不由我们自评。正确表述是「采集字段与时机的清单、不采集什么、留存期限与清理任务的执行数据、员工可查询、持续跟踪类需求的替代方案与接受率、权限拒绝率的变化」——全部是可核查的事实。
没有匹配的内容,换个关键词试试。
项目拆解 · 考勤与外勤打卡(uni-app)· 共 3 个模块 · 数字均为示例,需替换成自己项目的真实数据