Node.js倒计时剩余时间执行功能 基于EJS+Mongoose的web应用实现咨询
实现方案
1. 整体逻辑梳理
整个功能的核心逻辑可以拆成三层实现:
- 后端存储任务的倒计时配置、用户提交状态、两种展示结果
- 前端负责展示倒计时、处理用户提交请求、根据状态渲染表格
- 状态判断同时做前端实时展示和后端兜底校验,避免本地时间篡改导致逻辑异常
2. 后端实现(Node.js + Mongoose)
2.1 定义数据模型
首先用Mongoose创建倒计时任务的集合,存储所有相关状态:
const mongoose = require('mongoose'); const countdownTaskSchema = new mongoose.Schema({ expireAt: Date, // 倒计时的结束时间戳 threshold: Number, // 触发默认逻辑的剩余时间阈值,单位为秒 userHasSubmitted: { type: Boolean, default: false }, // 用户是否提交过POST请求 submitResult: Object, // 用户提交的请求结果 defaultResult: Object, // 阈值触发时展示的默认结果 status: { type: String, enum: ['pending', 'user_submitted', 'threshold_triggered', 'expired'], default: 'pending' } }); module.exports = mongoose.model('CountdownTask', countdownTaskSchema);
2.2 编写接口
- 提交结果接口:处理用户的POST请求,更新任务状态
app.post('/api/submit', async (req, res) => { const { taskId, formData } = req.body; const task = await CountdownTask.findById(taskId); if (!task || task.status !== 'pending') { return res.status(400).json({ success: false, msg: '任务已结束或不存在' }); } task.userHasSubmitted = true; task.submitResult = formData; task.status = 'user_submitted'; await task.save(); res.json({ success: true, data: task.submitResult }); });
- 状态查询接口:给前端返回当前任务的实时状态、剩余时间
app.get('/api/task-status/:taskId', async (req, res) => { const task = await CountdownTask.findById(req.params.taskId); if (!task) return res.status(404).json({ msg: '任务不存在' }); // 计算剩余时间,避免前端本地时间不准 const remainingTime = Math.max(0, Math.floor((task.expireAt - Date.now()) / 1000)); res.json({ remainingTime, status: task.status, submitResult: task.submitResult, defaultResult: task.defaultResult, threshold: task.threshold }); });
2.3 后端兜底逻辑(可选)
如果需要后端独立触发阈值逻辑,可以加定时扫描任务,定期更新符合条件的任务状态:
const schedule = require('node-schedule'); // 每分钟扫描一次待处理任务 schedule.scheduleJob('* * * * *', async () => { const pendingTasks = await CountdownTask.find({ status: 'pending' }); for (const task of pendingTasks) { const remaining = Math.floor((task.expireAt - Date.now()) / 1000); if (remaining <= task.threshold) { task.status = 'threshold_triggered'; await task.save(); } } });
3. 视图层实现(EJS + 前端JS)
EJS模板渲染初始数据,前端JS处理倒计时和状态更新:
<!-- EJS 渲染初始参数 --> <div>剩余时间:<span id="countdown"><%= initRemaining %></span> 秒</div> <table id="result-table"> <thead> <tr><th>结果类型</th><th>内容</th></tr> </thead> <tbody id="table-body"></tbody> </table> <form id="submit-form"> <!-- 你的表单字段 --> <button type="submit">提交结果</button> </form> <script> const taskId = '<%= taskId %>'; let remainingTime = <%= initRemaining %>; const threshold = <%= threshold %>; let timer = null; // 初始化倒计时 timer = setInterval(async () => { remainingTime--; document.getElementById('countdown').textContent = remainingTime; // 到阈值或倒计时结束时查询状态 if (remainingTime === threshold || remainingTime === 0) { const res = await fetch(`/api/task-status/${taskId}`); const statusData = await res.json(); renderTable(statusData); if (remainingTime === 0) clearInterval(timer); } }, 1000); // 表单提交逻辑 document.getElementById('submit-form').addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(e.target); const submitData = Object.fromEntries(formData.entries()); const res = await fetch('/api/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ taskId, formData: submitData }) }); const result = await res.json(); if (result.success) { renderTable({ status: 'user_submitted', submitResult: result.data }); clearInterval(timer); } }); // 渲染表格 function renderTable(data) { const tbody = document.getElementById('table-body'); if (data.status === 'user_submitted') { tbody.innerHTML = `<tr><td>用户提交结果</td><td>${JSON.stringify(data.submitResult)}</td></tr>`; } else if (data.status === 'threshold_triggered' || (data.status === 'pending' && remainingTime <= threshold)) { tbody.innerHTML = `<tr><td>默认结果</td><td>${JSON.stringify(data.defaultResult)}</td></tr>`; } } </script>
4. 注意事项
- 不要完全依赖前端本地倒计时判断状态,每次触发渲染前都要请求后端接口校验真实状态,避免用户篡改本地时间绕过逻辑
- 如果需要更高的实时性,可以把前端轮询替换为SSE或者WebSocket,减少无效请求
- 过期任务可以定期清理,避免数据库冗余
内容的提问来源于stack exchange,提问作者Mohammed Janati
相关产品推荐
相关产品推荐

