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

无需WebSocket实现多阶段CSV验证进度推送的方案咨询

Node.js + React 实现多阶段验证结果实时推送方案

方案说明

采用HTTP长连接+分块传输编码实现,既避免WebSocket的额外复杂度,也不会像长轮询那样产生频繁连接开销。服务器在同一个HTTP连接中逐步推送验证阶段结果,客户端断开后自动3秒重连,直到验证完成。


Node.js 服务器端实现(基于Express)

先安装依赖:

npm install express cors

服务器代码:

const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 存储验证任务状态,用唯一ID关联
const verificationTasks = new Map();

// 提交数据并启动验证的接口
app.post('/submit-data', async (req, res) => {
  const { csvData } = req.body;
  const taskId = `task_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
  
  // 初始化任务
  verificationTasks.set(taskId, {
    isCompleted: false,
    results: []
  });

  // 异步启动多阶段验证
  startVerification(taskId, csvData);

  res.json({ taskId });
});

// 长连接获取验证结果(分块传输)
app.get('/verify-results/:taskId', async (req, res) => {
  const { taskId } = req.params;
  const task = verificationTasks.get(taskId);

  if (!task) {
    res.status(404).json({ error: '任务不存在' });
    return;
  }

  // 设置响应头,启用分块传输与长连接
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Transfer-Encoding', 'chunked');
  res.setHeader('Connection', 'keep-alive');
  res.setHeader('Cache-Control', 'no-cache');

  // 发送已有的历史结果
  task.results.forEach(result => {
    res.write(`data: ${JSON.stringify(result)}\n\n`);
  });

  // 定时检查新结果并推送
  const checkInterval = setInterval(() => {
    const latestResult = task.results.at(-1);
    // 任务完成时发送结束信号并清理
    if (task.isCompleted) {
      res.write(`data: ${JSON.stringify({ type: 'COMPLETE' })}\n\n`);
      clearInterval(checkInterval);
      res.end();
      verificationTasks.delete(taskId);
      return;
    }
    // 推送未发送的新结果
    if (latestResult && !latestResult.sent) {
      latestResult.sent = true;
      res.write(`data: ${JSON.stringify(latestResult)}\n\n`);
    }
  }, 500);

  // 客户端断开连接时清理定时器
  res.on('close', () => clearInterval(checkInterval));
});

// 模拟多阶段验证逻辑
async function startVerification(taskId, csvData) {
  const task = verificationTasks.get(taskId);
  
  // 阶段1:CSV格式验证
  await new Promise(resolve => setTimeout(resolve, 1500));
  task.results.push({
    stage: '格式验证',
    status: 'SUCCESS',
    message: 'CSV格式合法,列数匹配要求',
    sent: false
  });

  // 阶段2:数据完整性验证
  await new Promise(resolve => setTimeout(resolve, 2000));
  task.results.push({
    stage: '完整性验证',
    status: 'WARNING',
    message: '发现2条缺失字段的记录,已标记',
    sent: false
  });

  // 阶段3:业务规则验证
  await new Promise(resolve => setTimeout(resolve, 3000));
  task.results.push({
    stage: '业务规则验证',
    status: 'SUCCESS',
    message: '所有记录符合业务规则',
    sent: false
  });

  task.isCompleted = true;
}

const PORT = 3001;
app.listen(PORT, () => console.log(`服务器运行在 http://localhost:${PORT}`));

React 客户端实现

创建验证组件:

import { useState, useEffect } from 'react';

function CSVVerifier() {
  const [csvData, setCsvData] = useState('');
  const [taskId, setTaskId] = useState(null);
  const [verificationResults, setVerificationResults] = useState([]);
  const [isVerifying, setIsVerifying] = useState(false);

  // 提交CSV数据到服务器
  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsVerifying(true);
    setVerificationResults([]);

    try {
      const res = await fetch('http://localhost:3001/submit-data', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ csvData })
      });
      const data = await res.json();
      setTaskId(data.taskId);
    } catch (err) {
      console.error('提交失败:', err);
      setIsVerifying(false);
    }
  };

  // 建立长连接获取结果,断开后自动3秒重连
  useEffect(() => {
    if (!taskId) return;

    let reconnectTimer;
    let abortController;

    const fetchResults = async () => {
      abortController = new AbortController();
      try {
        const res = await fetch(`http://localhost:3001/verify-results/${taskId}`, {
          signal: abortController.signal,
          headers: { 'Accept': 'text/event-stream' }
        });

        if (!res.ok) throw new Error('连接异常');

        const reader = res.body.getReader();
        const decoder = new TextDecoder('utf-8');

        while (true) {
          const { done, value } = await reader.read();
          if (done) break;

          const chunk = decoder.decode(value);
          // 解析SSE格式的分块数据
          const messages = chunk.split('\n\n').filter(msg => msg.startsWith('data:'));
          messages.forEach(msg => {
            const data = JSON.parse(msg.replace('data: ', ''));
            if (data.type === 'COMPLETE') {
              setIsVerifying(false);
              return;
            }
            setVerificationResults(prev => [...prev, data]);
          });
        }
      } catch (err) {
        // 非主动终止的情况下,3秒后重连
        if (err.name !== 'AbortError' && isVerifying) {
          reconnectTimer = setTimeout(fetchResults, 3000);
        }
      }
    };

    fetchResults();

    // 组件卸载或任务结束时清理资源
    return () => {
      abortController?.abort();
      clearTimeout(reconnectTimer);
    };
  }, [taskId, isVerifying]);

  return (
    <div style={{ maxWidth: 600, margin: '2rem auto', padding: '0 1rem' }}>
      <h2>CSV数据验证</h2>
      <form onSubmit={handleSubmit}>
        <textarea
          value={csvData}
          onChange={(e) => setCsvData(e.target.value)}
          placeholder="粘贴CSV数据..."
          rows={8}
          style={{ width: '100%', marginBottom: '1rem', padding: '0.5rem' }}
          disabled={isVerifying}
        />
        <button
          type="submit"
          disabled={isVerifying || !csvData.trim()}
          style={{ padding: '0.5rem 1rem', fontSize: '1rem' }}
        >
          {isVerifying ? '验证中...' : '提交验证'}
        </button>
      </form>

      <div style={{ marginTop: '2rem' }}>
        <h3>验证进度</h3>
        {verificationResults.length === 0 ? (
          <p>{isVerifying ? '等待验证结果...' : '暂无验证记录'}</p>
        ) : (
          <ul style={{ listStyle: 'none', padding: 0 }}>
            {verificationResults.map((result, idx) => (
              <li
                key={idx}
                style={{
                  padding: '0.8rem',
                  marginBottom: '0.5rem',
                  borderRadius: '4px',
                  backgroundColor: result.status === 'SUCCESS' ? '#e8f5e9' : result.status === 'WARNING' ? '#fff3e0' : '#ffebee'
                }}
              >
                <strong>{result.stage}</strong>: {result.message}
                <span style={{
                  float: 'right',
                  padding: '0.2rem 0.5rem',
                  borderRadius: '4px',
                  color: '#fff',
                  backgroundColor: result.status === 'SUCCESS' ? '#4caf50' : result.status === 'WARNING' ? '#ff9800' : '#f44336'
                }}>
                  {result.status}
                </span>
              </li>
            ))}
          </ul>
        )}
      </div>
    </div>
  );
}

export default CSVVerifier;

内容的提问来源于stack exchange,提问作者bhushan laware

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 12:03:31