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

如何在Next.js中通过Socket.io无刷新流式展示JSON阶段状态更新

实时流式展示多阶段任务状态的实现方案

后端(Node.js/Next.js + Socket.io)改造

核心思路是在每个任务阶段完成时主动推送状态,替代原有的仅连接时读取一次JSON文件的逻辑。

  1. 维护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);
    });
    
  2. 修改任务执行逻辑,阶段完成即推送状态
    在处理表单提交的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的状态推送事件,实时追加阶段状态到页面,无需刷新。

  1. 安装Socket.io客户端:

    npm install socket.io-client uuid
    
  2. 实现前端交互组件:

    // 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 06:05:22