docs: 添加项目文档和优化后台服务

This commit is contained in:
sudomarcma
2025-07-11 14:07:20 +08:00
parent 4952f20528
commit 1a57c12749
9 changed files with 4086 additions and 74 deletions
+74 -53
View File
@@ -320,70 +320,88 @@ const geofence = polygon([
}
}
// Worker Clock In/Out Endpoint
// Worker Clock In/Out Endpoint - Optimized Version
app.post('/api/clock', authenticateJWT, async (req, res) => {
try {
const { userId, eventType, qrCodeValue, latitude, longitude, notes } = req.body
const { userId, eventType, qrCodeValue, latitude, longitude, notes } = req.body;
const connection = await db.getConnection(); // Get connection from pool first
// Bypass geofence and QR code validation for forced events
if (qrCodeValue !== 'FORCE_CLOCK_OUT' && qrCodeValue !== 'BLACKLIST_APP_DETECTED') {
// Geofencing check using the directly imported functions
const userLocation = point([longitude, latitude]);
const isWithinGeofence = booleanPointInPolygon(userLocation, geofence);
try {
// Start transaction
await connection.beginTransaction();
if (!isWithinGeofence) {
// User is outside the geofence, log a 'failed' attempt
const distance = pointToLineDistance(userLocation, geofence.geometry.coordinates[0], { units: 'meters' });
const notes = `Clock-in outside of the zone: ${distance.toFixed(2)} meters.`;
// Bypass checks for special cases
if (qrCodeValue !== 'FORCE_CLOCK_OUT') {
// Parallelize geofence and QR code checks
const [geofenceCheck, qrCheck] = await Promise.all([
booleanPointInPolygon(point([longitude, latitude]), geofence),
connection.execute('SELECT name, is_active FROM qr_codes WHERE id = ?', [qrCodeValue])
]);
await db.execute(
'INSERT INTO clock_records (worker_id, event_type, timestamp, qr_code_id, latitude, longitude, notes) VALUES (?, ?, ?, ?, ?, ?, ?)',
[userId, 'failed', new Date(), qrCodeValue, latitude, longitude, notes]
);
if (!geofenceCheck) {
const distance = pointToLineDistance(point([longitude, latitude]),
geofence.geometry.coordinates[0], { units: 'meters' });
const notes = `Clock-in outside of the zone: ${distance.toFixed(2)} meters.`;
return res.status(403).json({ message: `You are not within the allowed work area.` });
await connection.execute(
'INSERT INTO clock_records (worker_id, event_type, timestamp, qr_code_id, latitude, longitude, notes) VALUES (?, ?, ?, ?, ?, ?, ?)',
[userId, 'failed', new Date(), qrCodeValue, latitude, longitude, notes]
);
await connection.commit();
return res.status(403).json({ message: `You are not within the allowed work area.` });
}
if (qrCheck[0].length === 0) {
await connection.rollback();
return res.status(400).json({ message: 'Invalid QR Code scanned.' });
}
if (!qrCheck[0][0].is_active) {
await connection.rollback();
return res.status(400).json({ message: 'This QR Code has expired and is no longer active.' });
}
}
const [qrRows] = await db.execute('SELECT name, is_active FROM qr_codes WHERE id = ?', [
qrCodeValue,
]);
// Check last event
const [lastEvent] = await connection.execute(
'SELECT event_type FROM clock_records WHERE worker_id = ? ORDER BY timestamp DESC LIMIT 1',
[userId]
);
if (qrRows.length === 0) {
return res.status(400).json({ message: 'Invalid QR Code scanned.' });
}
if (!qrRows[0].is_active) {
return res
.status(400)
.json({ message: 'This QR Code has expired and is no longer active.' });
}
}
const [lastEventRows] = await db.execute(
'SELECT event_type FROM clock_records WHERE worker_id = ? ORDER BY timestamp DESC LIMIT 1',
[userId],
)
if (lastEventRows.length > 0 && lastEventRows[0].event_type === eventType) {
if (qrCodeValue === 'FORCE_CLOCK_OUT') {
// If it's a forced clock-out on an already clocked-out user, log it as a failed event
await db.execute(
'INSERT INTO clock_records (worker_id, event_type, timestamp, qr_code_id, latitude, longitude, notes) VALUES (?, ?, ?, ?, ?, ?, ?)',
[userId, 'failed', new Date(), qrCodeValue, latitude, longitude, `FAKE GPS APP Detected.`]
);
return res.status(200).json({ message: 'Forced clock-out attempt on already clocked-out user was logged.' });
}
return res
.status(400)
.json({ message: `You are already clocked ${eventType === 'clock_in' ? 'in' : 'out'}.` })
// If it's a forced clock-out, log it as a failed event regardless of previous state
await connection.execute(
'INSERT INTO clock_records (worker_id, event_type, timestamp, qr_code_id, latitude, longitude, notes) VALUES (?, ?, ?, ?, ?, ?, ?)',
[userId, 'failed', new Date(), qrCodeValue, latitude, longitude, `FAKE GPS APP Detected.`]
);
await connection.commit();
return res.status(200).json({ message: 'Forced clock-out attempt was logged.' });
}
if (lastEvent.length > 0 && lastEvent[0].event_type === eventType) {
await connection.rollback();
return res.status(400).json({ message: `You are already clocked ${eventType === 'clock_in' ? 'in' : 'out'}.` });
}
// Insert new record
const timestamp = new Date();
await connection.execute(
'INSERT INTO clock_records (worker_id, event_type, timestamp, qr_code_id, latitude, longitude, notes) VALUES (?, ?, ?, ?, ?, ?, ?)',
[userId, eventType, timestamp, qrCodeValue || null, latitude || null, longitude || null, notes || null]
);
await connection.commit();
res.status(201).json({ message: 'Clock event recorded successfully' });
} catch (err) {
await connection.rollback();
throw err;
} finally {
connection.release();
}
const timestamp = new Date()
await db.execute(
'INSERT INTO clock_records (worker_id, event_type, timestamp, qr_code_id, latitude, longitude, notes) VALUES (?, ?, ?, ?, ?, ?, ?)',
[userId, eventType, timestamp, qrCodeValue || null, latitude || null, longitude || null, notes || null],
)
res.status(201).json({ message: 'Clock event recorded successfully' })
} catch (error) {
console.error('Clock event error:', error)
res.status(500).json({ message: 'Database error during clock event.' })
console.error('Clock event error:', error);
res.status(500).json({ message: 'Database error during clock event.' });
}
})
@@ -1006,7 +1024,9 @@ const geofence = polygon([
})
// Security Check Endpoint
// Security Check Endpoint - COMMENTED OUT FOR SERVER-SIDE SECURITY PREFERENCE
// Client-side security computation removed per user preference for server-side security
/*
app.post('/api/security/check', authenticateJWT, async (req, res) => {
try {
const { userId, timestamp, deviceInfo, securityCheck } = req.body
@@ -1077,6 +1097,7 @@ const geofence = polygon([
res.status(500).json({ message: 'Database error during security check.' })
}
})
*/
// Get Security Status Endpoint
app.get('/api/security/status/:userId', authenticateJWT, async (req, res) => {