feat(地理围栏): 添加地理围栏功能并优化打卡记录处理

- 添加@turf/turf依赖用于地理围栏计算
- 实现地理围栏检查,拒绝围栏外的打卡并记录失败事件
- 过滤掉失败事件在工人历史记录中显示
- 修复地图链接中的错误语法
- 移除开发提示和HTTPS相关代码
- 优化视图按钮样式和事件类型颜色标识
This commit is contained in:
sudomarcma
2025-06-30 16:03:12 +08:00
parent 57f2d5f6c5
commit d322404007
7 changed files with 2305 additions and 10 deletions
+45 -4
View File
@@ -1,5 +1,3 @@
import https from 'https'
import fs from 'fs'
import express from 'express'
import cors from 'cors'
import { Parser } from 'json2csv'
@@ -8,6 +6,11 @@ import mysql from 'mysql2/promise'
import dotenv from 'dotenv'
import bcrypt from 'bcrypt'
import jwt from 'jsonwebtoken'
// --- FIX START ---
// Import only the required functions from turf
import { point, polygon, booleanPointInPolygon, pointToLineDistance } from '@turf/turf'
// --- FIX END ---
// Main function to start the server
async function startServer() {
@@ -38,6 +41,22 @@ async function startServer() {
process.exit(1)
}
// --- FIX START ---
// Define the geofence polygon by calling the 'polygon' function directly
const geofence = polygon([
[
[101.80827335908509, 2.8350045747358337],
[101.80822799653066, 2.8340134829130363],
[101.80827902940462, 2.8335264317641418],
[101.80941309326164, 2.8332772427247335],
[101.81144873788423, 2.834596811345506],
[101.81166988033686, 2.8345911479647157],
[101.81199875885511, 2.83593336858695],
[101.80827335908509, 2.8350045747358337],
],
])
// --- FIX END ---
app.use(cors())
app.use(express.json())
@@ -96,6 +115,28 @@ async function startServer() {
try {
const { userId, eventType, qrCodeValue, latitude, longitude } = req.body
// Geofencing check using the directly imported functions
const userLocation = point([longitude, latitude]);
const isWithinGeofence = booleanPointInPolygon(userLocation, geofence);
if (!isWithinGeofence) {
// User is outside the geofence, log a 'failed' attempt
// Calculate the distance from the geofence
const distance = pointToLineDistance(userLocation, geofence.geometry.coordinates[0], { units: 'meters' });
// Create a descriptive note
const notes = `Clock-in outside of the zone: ${distance.toFixed(2)} meters.`;
// Insert the failed attempt into the database
await db.execute(
'INSERT INTO clock_records (worker_id, event_type, timestamp, qr_code_id, latitude, longitude, notes, distance_meters) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[userId, 'failed', new Date(), qrCodeValue, latitude, longitude, notes, distance]
);
// Return an error to the user
return res.status(403).json({ message: `You are not within the allowed work area.` });
// --- MODIFICATION END ---
}
const [qrRows] = await db.execute('SELECT name, is_active FROM qr_codes WHERE id = ?', [
qrCodeValue,
])
@@ -536,7 +577,7 @@ async function startServer() {
const placeholders = idsArray.map(() => '?').join(',')
// MODIFIED: Use LEFT JOIN and COALESCE to handle manual entries, and select `notes`
let query = `SELECT cr.id, w.full_name, cr.event_type, cr.timestamp, COALESCE(qc.name, 'Manual Entry') as qrCodeUsedName, cr.latitude, cr.longitude, cr.notes FROM clock_records cr LEFT JOIN qr_codes qc ON cr.qr_code_id = qc.id JOIN workers w ON cr.worker_id = w.id WHERE cr.worker_id IN (${placeholders})`
let query = `SELECT cr.id, w.full_name, cr.event_type, cr.timestamp, COALESCE(qc.name, 'Manual Entry') as qrCodeUsedName, cr.latitude, cr.longitude, cr.notes, cr.distance_meters FROM clock_records cr LEFT JOIN qr_codes qc ON cr.qr_code_id = qc.id JOIN workers w ON cr.worker_id = w.id WHERE cr.worker_id IN (${placeholders})`;
const params = [...idsArray]
if (startDate && endDate) {
@@ -657,7 +698,7 @@ async function startServer() {
// }
app.listen(port, () => {
console.log(`Server is running on https://localhost:${port}`)
console.log(`Server is running on http://localhost:${port}`)
})
}