北海公司做网站,杭州建设网杨赟,网络服务部工作计划,网站后台更新后主页不显示背景痛点#xff1a;客服前端的三座大山 消息实时性#xff1a;HTTP 长轮询 1 s 一次#xff0c;高峰期 30 % 请求落在 504#xff0c;用户骂“机器人卡死”。会话状态同步#xff1a;PC 端把问题描述到第 5 轮#xff0c;切到手机小程序#xff0c;记录凭空消失#…背景痛点客服前端的三座大山消息实时性HTTP 长轮询 1 s 一次高峰期 30 % 请求落在 504用户骂“机器人卡死”。会话状态同步PC 端把问题描述到第 5 轮切到手机小程序记录凭空消失客服只能“从头再来”。多设备适配折叠屏、小窗、车载横屏气泡被键盘顶飞按钮被刘海挡住体验分直接腰斩。技术选型为什么放弃长轮询拥抱 WebSocket React长轮询在 3 G 弱网平均 RTT 1.2 sWebSocket 握手后仅 40 ms差距 30 倍。公司 Node 网关已支持 MQTT over WebSocket可直接复用 Qos1 重发机制零额外成本。Redux Toolkit 的 slice extraReducer 天然适合“会话-消息”两级状态比 Zustand 多设备时间旅行调试更香。React 18 的 concurrent render 能把高优先级“正在输入”与低优先级“历史消息”拆开保证首帧 60 fps。核心实现三层架构拆解1. 消息顺序保障MessageQueue 队列客户端本地维护一个sendQueue: MapsnowflakeId, Message。发送失败时unshift.shift()重试成功ackQueue.set(id, true)保证服务端回包顺序与本地渲染顺序一致。渲染层用useVirtualizer只挂载可视区域 10 条消息DOM 节点数从 1200 降到 60滚动掉帧率 0。2. 断线重连SWR 的妙用自定义useWsSWR(key, fetcher)key 为ws://broker/chat/${sessionId}。断网时 SWR 自动重试配合指数退避1 s → 2 s → 4 s最大 30 s。重连成功后拉取/api/miss?lastId${lastAckId}增量合并到本地队列用户无感刷新。3. 自适应气泡一个组件吃遍全端采用 CSSclamp()动态计算最大宽度min(80vw, 480px)。键盘弹起时通过visualViewport.addEventListener(resize)拿到真实可视高度把输入框translateY顶起避免被遮挡。图片气泡使用object-fit: coveraspect-ratio: 16/9防止超长图刷屏。代码示例可直接粘贴的 React Hook// hooks/useChat.ts import { useCallback, useEffect, useRef } from react; import { useDispatch, useSelector } from react-redux; import { sendMessage, ackMessage } from /store/chatSlice; import { snowflakeId } from /utils/snowflake; import { ws } from /utils/websocket; export default function useChat() { const dispatch useDispatch(); const { sessionId, sendQueue } useSelector((s: RootState) s.chat); // 发送消息 const send useCallback( (text: string) { const id snowflakeId(); // 雪花算法防冲突 const msg { id, text, from: user, ts: Date.now() }; dispatch(sendMessage(msg)); // 先写本地乐观更新 ws.emit(chat, { sessionId, ...msg }); // 再发网络 }, [dispatch, sessionId] ); // 监听服务端 ack useEffect(() { const onAck (data: { id: string }) dispatch(ackMessage(data.id)); ws.on(ack, onAck); return () ws.off(ack, onAck); }, [dispatch]); return { send }; } // 会话持久化 useEffect(() { const raw localStorage.getItem(chat_${sessionId}); if (raw) dispatch(loadSession(JSON.parse(raw))); window.addEventListener(beforeunload, () { localStorage.setItem(chat_${sessionId}, JSON.stringify(chatSlice.getInitialState())); }); }, [sessionId, dispatch]); // 性能埋点 useEffect(() { const observer new PerformanceObserver((list) { for (const entry of list.getEntries()) { if (entry.name.includes(websocket)) { analytics.track(ws_latency, { value: entry.duration }); } } }); observer.observe({ entryTypes: [resource] }); return () observer.disconnect(); }, []);生产考量上线前必须回答的三个问题WebSocket 连接数优化单机 4 C8 G 经过ulimit -n 65535调优单进程可扛 5000 并发CPU 65 %内存 2.3 G再涨就加 Pod横向扩容比调内核更划算。敏感信息过滤采用“本地正则预检 云端 NLP 二次复核”双保险正则 0.8 ms 拦截 90 % 关键词剩下 10 % 走 NLP平均延迟 120 ms可接受。负载测试数据用 k6 模拟 5000 用户同时发消息P99 延迟 380 ms比业务目标 500 ms 低一个身段断线重连成功率 99.7 %未达 100 % 是因为 2 % 用户主动杀进程。避坑指南踩过的坑比你头发还多消息 ID 冲突前端 snowflake 低位用Math.floor(Math.random()*16)曾撞车后改workerId hash(userId) % 32百万消息零碰撞。移动端输入法抖动安卓弹起时window.innerHeight变化 280 ms 一次用debounce 300 ms再重算布局终于不再“跳迪斯科”。对话上下文缓存IndexedDB 存 7 天历史超过 200 M 自动 LRU 清理防止“缓存爆炸”把小程序闪退。写在最后把 WebSocket、React、Redux Toolkit 拼在一起只是智能客服的“文本时代”。下一步语音、图片、视频多模态齐飞前端如何设计一套插件化架构让语音转文字、OCR 识图、甚至视频客服都能像搭积木一样插进来你有什么好思路欢迎留言一起头脑风暴。