You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 21:45:02