如何在Next.js中通过Socket.io无刷新流式展示JSON阶段状态更新
实时流式展示多阶段任务状态的实现方案
后端(Node.js/Next.js + Socket.io)改造
核心思路是在每个任务阶段完成时主动推送状态,替代原有的仅连接时读取一次JSON文件的逻辑。
维护Socket连接与任务关联
在Server.js中,给每个任务分配唯一ID,让客户端通过ID加入对应Socket房间,确保状态推送的精准性:// Server.js const { Server } = require('socket.io'); const http = require('http'); const next = require('next'); const app = next({ dev: process.env.NODE_ENV !== 'production' }); const handle = app.getRequestHandler(); app.prepare().then(() => { const server = http.createServer((req, res) => handle(req, res)); const io = new Server(server); io.on('connection', (socket) => { // 接收客户端发送的任务ID,加入专属房间 socket.on('join-task-room', (taskId) => { socket.join(`task-${taskId}`); }); socket.on('disconnect', () => { socket.leaveAll(); }); }); // 暴露io实例给API路由调用 global.io = io; server.listen(3000); });修改任务执行逻辑,阶段完成即推送状态
在处理表单提交的API路由中,每完成一个阶段,先写入JSON文件,再通过Socket向对应房间推送当前阶段状态:// pages/api/run-task.js const fs = require('fs/promises'); const path = require('path'); const { v4: uuidv4 } = require('uuid'); // 可选:生成唯一任务ID export default async function handler(req, res) { if (req.method !== 'POST') return res.status(405).end(); const { text } = req.body; const taskId = uuidv4(); // 生成唯一任务ID const statusFile = path.join(process.cwd(), `task-${taskId}.json`); const taskStatus = { stages: [] }; try { // 阶段1:文本预处理 await new Promise(resolve => setTimeout(resolve, 1200)); // 模拟任务耗时 const stage1 = { stage: 1, status: '完成', message: '文本预处理完成' }; taskStatus.stages.push(stage1); await fs.writeFile(statusFile, JSON.stringify(taskStatus)); global.io.to(`task-${taskId}`).emit('task-status', stage1); // 阶段2:文本分析 await new Promise(resolve => setTimeout(resolve, 1500)); const stage2 = { stage: 2, status: '完成', message: '文本语义分析完成' }; taskStatus.stages.push(stage2); await fs.writeFile(statusFile, JSON.stringify(taskStatus)); global.io.to(`task-${taskId}`).emit('task-status', stage2); // 阶段3:结果生成 await new Promise(resolve => setTimeout(resolve, 1800)); const stage3 = { stage: 3, status: '完成', message: '初步结果生成' }; taskStatus.stages.push(stage3); await fs.writeFile(statusFile, JSON.stringify(taskStatus)); global.io.to(`task-${taskId}`).emit('task-status', stage3); // 阶段4:结果校验 await new Promise(resolve => setTimeout(resolve, 1000)); const stage4 = { stage: 4, status: '完成', message: '结果校验通过' }; taskStatus.stages.push(stage4); await fs.writeFile(statusFile, JSON.stringify(taskStatus)); global.io.to(`task-${taskId}`).emit('task-status', stage4); // 阶段5:任务收尾 await new Promise(resolve => setTimeout(resolve, 800)); const stage5 = { stage: 5, status: '完成', message: '任务全部结束' }; taskStatus.stages.push(stage5); await fs.writeFile(statusFile, JSON.stringify(taskStatus)); global.io.to(`task-${taskId}`).emit('task-status', stage5); res.status(200).json({ success: true, taskId }); } catch (err) { // 推送错误状态 global.io.to(`task-${taskId}`).emit('task-status', { stage: '异常', status: '失败', message: err.message }); res.status(500).json({ error: err.message }); } }
前端(Next.js)改造
核心是监听Socket的状态推送事件,实时追加阶段状态到页面,无需刷新。
安装Socket.io客户端:
npm install socket.io-client uuid实现前端交互组件:
// pages/index.js import { useState, useEffect } from 'react'; import { io } from 'socket.io-client'; import { v4 as uuidv4 } from 'uuid'; export default function Home() { const [inputText, setInputText] = useState(''); const [taskStatuses, setTaskStatuses] = useState([]); const [socket, setSocket] = useState(null); // 初始化Socket连接 useEffect(() => { const newSocket = io(); setSocket(newSocket); return () => newSocket.disconnect(); }, []); // 监听任务状态推送 useEffect(() => { if (!socket) return; socket.on('task-status', (stageData) => { setTaskStatuses(prev => [...prev, stageData]); }); return () => socket.off('task-status'); }, [socket]); const handleSubmit = async (e) => { e.preventDefault(); setTaskStatuses([]); // 清空历史状态 const taskId = uuidv4(); // 加入当前任务的Socket房间 socket.emit('join-task-room', taskId); // 提交表单到API const res = await fetch('/api/run-task', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: inputText, taskId }) }); const data = await res.json(); if (!data.success) alert('任务启动失败,请重试'); }; return ( <div style={{ maxWidth: 600, margin: '2rem auto', padding: '0 1rem' }}> <form onSubmit={handleSubmit} style={{ marginBottom: '2rem' }}> <input type="text" value={inputText} onChange={(e) => setInputText(e.target.value)} placeholder="输入待处理文本..." style={{ width: '70%', padding: '0.5rem' }} required /> <button type="submit" style={{ padding: '0.5rem 1rem', marginLeft: '0.5rem' }}> 启动处理任务 </button> </form> <div className="status-container"> <h3>任务进度:</h3> {taskStatuses.length === 0 ? ( <p>暂无任务进度</p> ) : ( taskStatuses.map((stage, idx) => ( <div key={idx} style={{ padding: '0.5rem', borderBottom: '1px solid #eee' }}> <span style={{ fontWeight: 600 }}>阶段{stage.stage}:</span> <span>{stage.message}</span> <span style={{ marginLeft: '1rem', color: stage.status === '完成' ? 'green' : 'red' }}> {stage.status} </span> </div> )) )} </div> </div> ); }
额外优化建议
- 重连状态恢复:客户端断开重连后,可通过API请求对应任务的已完成阶段状态,避免丢失历史进度。
- 状态持久化:高并发场景下,建议将任务状态存入Redis或MongoDB,替代JSON文件,提升读写效率。
- 超时处理:前端可设置任务超时逻辑,长时间未收到状态推送时提示用户。
内容的提问来源于stack exchange,提问作者calendo
相关产品推荐
相关产品推荐

