React Native考勤应用位置校验失效:非指定区域仍可打卡
问题修复:React Native考勤APP位置校验失效问题
核心问题分析
- 错误的距离判断逻辑:当前用经纬度直接加减1来判断是否在1米范围内,这完全错误——经纬度的1度约等于111公里,这种判断会导致几乎所有位置都被判定为“匹配”。
- 签退无位置校验:签退操作直接执行状态更新,没有检查用户是否在指定位置,导致任意位置都能签退。
watchPosition使用不当:签到时用watchPosition持续监听位置,即使第一次不匹配,后续位置变动可能会意外触发状态更新,且未正确管理监听ID。- 日期比较逻辑错误:直接用
Date对象比较是否为同一天,由于对象引用不同,永远会返回false,导致每日状态重置逻辑失效。
解决方案
1. 实现正确的距离计算函数(Haversine公式)
Haversine公式用于计算地球上两点间的球面距离,能准确得到米级的距离:
// 计算两点间距离(单位:米) const calculateDistance = (lat1, lon1, lat2, lon2) => { const R = 6371000; // 地球半径(米) const dLat = (lat2 - lat1) * Math.PI / 180; const dLon = (lon2 - lon1) * Math.PI / 180; const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon/2) * Math.sin(dLon/2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return R * c; // 返回距离(米) };
2. 修复签到函数
改用getCurrentPosition单次获取位置,用正确的距离判断,且仅在匹配时更新状态:
const distanceThreshold = 1; // 1米阈值 const checkIn = () => { console.log('Check In'); Geolocation.getCurrentPosition( position => { const currentLat = position.coords.latitude; const currentLon = position.coords.longitude; const distance = calculateDistance(currentLat, currentLon, User.latitude, User.longitude); if (distance <= distanceThreshold) { setCurrentLoc({ latitude: currentLat, longitude: currentLon }); setMessage('Matched'); console.log('Location matched, checking in'); setStatus('In'); AsyncStorage.setItem( '@status', JSON.stringify({ status: 'In', date: new Date().toISOString().split('T')[0] }) // 仅存日期部分 ); } else { setMessage('NotMatched'); console.log(`Location not matched, distance: ${distance.toFixed(2)}m`); } }, error => { console.error('Geolocation error:', error); setMessage('NotMatched'); }, { enableHighAccuracy: true, // 启用高精度定位,提升准确性 timeout: 10000, maximumAge: 0 } ); };
3. 修复签退函数
添加位置校验,仅在指定位置内允许签退:
const checkOut = () => { Geolocation.getCurrentPosition( position => { const currentLat = position.coords.latitude; const currentLon = position.coords.longitude; const distance = calculateDistance(currentLat, currentLon, User.latitude, User.longitude); if (distance <= distanceThreshold) { AsyncStorage.getItem('@status').then(status => { if (status) { const parsedStatus = JSON.parse(status); AsyncStorage.setItem( '@UserLog', JSON.stringify({ name: User.name, INstatus: parsedStatus.status, INdate: parsedStatus.date, OUTstatus: 'Out', OUTdate: new Date().toISOString().split('T')[0] }) ); } }); setStatus('AlreadyOut'); AsyncStorage.setItem( '@status', JSON.stringify({ status: 'AlreadyOut', date: new Date().toISOString().split('T')[0] }) ); Alert.alert('Check Out', 'You are checked out'); } else { Alert.alert('Error', 'You are not at the designated location to check out'); setMessage('NotMatched'); } }, error => { console.error('Geolocation error:', error); Alert.alert('Error', 'Failed to get location'); }, { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } ); };
4. 修复日期比较逻辑
在useEffect中比较日期字符串,而不是Date对象:
useEffect(() => { AsyncStorage.getItem('user').then(user => { if (user) { setUser(JSON.parse(user)); } }); AsyncStorage.getItem('@status').then(status => { if (status) { const parsedStatus = JSON.parse(status); const today = new Date().toISOString().split('T')[0]; if (parsedStatus.date !== today) { // 不是今天,重置状态为Out const newStatus = JSON.stringify({ status: 'Out', date: today }); AsyncStorage.setItem('@status', newStatus); setStatus('Out'); } else { setStatus(parsedStatus.status); } } else { // 无状态记录,初始化状态 const initStatus = JSON.stringify({ status: 'Out', date: new Date().toISOString().split('T')[0] }); AsyncStorage.setItem('@status', initStatus); setStatus('Out'); } }); // 初始化当前位置 Geolocation.getCurrentPosition( position => { setCurrentLoc({ latitude: position.coords.latitude, longitude: position.coords.longitude }); }, error => console.error('Geolocation error:', error), { enableHighAccuracy: true } ); }, []);
5. 其他优化点
- 确保
User状态加载完成后再执行位置校验,避免User.latitude/User.longitude为undefined导致计算错误。 - 给
TextInput添加数值校验,确保保存的经纬度是有效数字。
内容的提问来源于stack exchange,提问作者Khuzaima Amir
相关产品推荐
相关产品推荐

