无需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
相关产品推荐
相关产品推荐

