Laravel中是否可在会话销毁时执行代码?CRM工时追踪需求
Hey there! This is such a critical edge case for time-tracking features—glad you’re thinking ahead to cover it. Let’s break down the solutions you can use to capture that end_time even when users close their browser or shut down their machine:
1. Browser Unload Events (for Graceful Closures)
Browsers do provide events that fire when a page is about to unload, which you can leverage to send your end_time data. The key here is using the right event and a reliable way to send the data without blocking the user’s exit.
beforeunload + navigator.sendBeacon
The beforeunload event triggers just before the page starts unloading, and navigator.sendBeacon is specifically designed for this scenario—it sends data to the server in the background without blocking the page from closing. This is far more reliable than traditional async requests, which browsers often cancel during unload.
Here’s a quick implementation example:
// Track active task state (store in memory + localStorage for persistence) let activeTrackingTask = null; // Start tracking a task function startTask(taskId) { activeTrackingTask = { taskId: taskId, startTime: new Date().toISOString() }; localStorage.setItem('activeTrackingTask', JSON.stringify(activeTrackingTask)); } // Listen for page unload window.addEventListener('beforeunload', (e) => { if (activeTrackingTask) { const endTime = new Date().toISOString(); // Prepare data for the server const formData = new FormData(); formData.append('taskId', activeTrackingTask.taskId); formData.append('startTime', activeTrackingTask.startTime); formData.append('endTime', endTime); // Send data via beacon (non-blocking) navigator.sendBeacon('/api/crm/track-time', formData); // Clean up local state localStorage.removeItem('activeTrackingTask'); activeTrackingTask = null; } }); // Restore active task on page load (in case of accidental refresh) window.addEventListener('load', () => { const savedTask = localStorage.getItem('activeTrackingTask'); if (savedTask) { activeTrackingTask = JSON.parse(savedTask); // Optional: Prompt user to resume tracking or mark as completed alert(`You have an active task (ID: ${activeTrackingTask.taskId})—would you like to resume tracking?`); } });
Caveats for Unload Events
- These events won’t fire if the browser crashes, the user kills the browser process via task manager, or their device loses power abruptly. For these cases, you need a fallback.
2. Heartbeat Mechanism (for Abrupt Closures)
To handle scenarios where unload events don’t fire, implement a heartbeat system that periodically sends updates to your server about the active task. Your server can then use the last heartbeat time to infer end_time if the task goes silent.
How it works:
- When a user starts tracking a task, start an interval that sends a request to your server every 60-120 seconds, updating the task’s
last_active_time. - On the server side, run a scheduled job (e.g., cron job, database trigger) that checks for tasks marked as "in progress" where the
last_active_timeis older than a threshold (e.g., 5 minutes). For these tasks, automatically setend_timeto thelast_active_timeand mark the task as completed.
Example frontend code for heartbeats:
let heartbeatInterval; function startHeartbeat(taskId) { // Send heartbeat every 60 seconds heartbeatInterval = setInterval(() => { fetch('/api/crm/task-heartbeat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ taskId: taskId, lastActiveTime: new Date().toISOString() }) }); }, 60000); } // Stop heartbeat when user manually ends the task function endTask(taskId) { clearInterval(heartbeatInterval); // Send final end_time request fetch('/api/crm/track-time', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ taskId: taskId, startTime: activeTrackingTask.startTime, endTime: new Date().toISOString() }) }); // Clean up state localStorage.removeItem('activeTrackingTask'); activeTrackingTask = null; } // Start heartbeat when tracking begins function startTask(taskId) { activeTrackingTask = { taskId: taskId, startTime: new Date().toISOString() }; localStorage.setItem('activeTrackingTask', JSON.stringify(activeTrackingTask)); startHeartbeat(taskId); }
3. Combine Both Approaches for Full Coverage
The best solution is to use unload events for graceful closures and heartbeats for abrupt ones, plus localStorage to restore tasks if the user returns to the page later. This covers almost every scenario:
- User closes browser/tab →
beforeunload+ beacon capturesend_time - Browser crashes/device shuts down → heartbeat threshold triggers server-side
end_time - User refreshes the page → localStorage restores the active task
内容的提问来源于stack exchange,提问作者Dusko

