需正确处理SSE协议:一、用fetch+ReadableStream发起请求,响应头含text/event-stream等字段;二、逐行解析data:内容并拼接JSON;三、用textContent追加纯文本防XSS;四、按lastEventId重连并指数退避;五、兼容旧浏览器降级用EventSource或polyfill。
☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

如果您在网页端调用DeepSeek API并希望实时渲染模型返回的流式文本,但响应内容延迟显示或出现乱序、截断现象,则可能是由于未正确处理Server-Sent Events(SSE)协议的数据分块与解析逻辑。以下是实现流式数据在网页端稳定渲染的具体步骤:
一、建立符合SSE规范的HTTP请求
浏览器需通过EventSource或fetch+ReadableStream发起请求,且服务端响应头必须包含text/event-stream类型及必要的缓存禁用指令,否则浏览器将无法持续接收事件流。
1、使用fetch发送POST请求,设置headers中Content-Type为application/json,并在body中传入包含model、messages、stream: true等字段的JSON对象。
2、在fetch的Response对象上调用response.body.getReader()获取可读流,确保不使用response.json()或response.text()等终结型方法。
3、设置request headers中的Accept为text/event-stream,显式声明客户端支持SSE格式。
4、在服务端返回的HTTP响应头中,必须包含Content-Type: text/event-stream、Cache-Control: no-cache、Connection: keep-alive三项关键字段。
二、解析event: message与data: 字段的原始字节流
DeepSeek API的SSE响应并非标准单data行格式,实际每条消息含多行:event: message、id: xxx、data: {json},需逐行扫描换行符并提取data后的内容,否则会将JSON结构体误判为纯文本。
1、对每次read()返回的Uint8Array调用decoder.decode(chunk, {stream: true})转为字符串,避免UTF-8多字节字符被截断。
2、将解码后的字符串按\n分割,遍历每一行,识别以data: 开头的行并去除前缀,保留后续所有字符(包括嵌套JSON中的换行)。
3、当遇到空行时,将已累积的data片段拼接为完整JSON字符串,再通过JSON.parse()提取delta.content字段值。
4、忽略所有不含data: 的行,如event:、id:、retry:等控制字段,防止向UI注入非文本内容。
三、逐字符追加至DOM并防XSS注入
流式内容需以最小粒度更新页面,但直接innerHTML赋值存在脚本执行风险;应仅提取纯文本并使用textContent或createTextNode插入,同时保留换行与空格语义。
1、创建一个预置的
容器,设置CSS white-space: pre-wrap确保换行符生效。2、每次解析出delta.content后,调用document.getElementById('output').appendChild(document.createTextNode(content))。
3、若content含\n字符,在插入前将其替换为'\n',而非
标签,因pre元素原生支持换行渲染。
4、禁止将content作为HTML字符串拼接后赋给innerHTML,防止恶意payload如被执行。
四、处理流中断与重连逻辑
网络抖动或服务端超时会导致ReadableStream关闭,此时需检测done状态并触发自动重试,避免用户界面停滞;但重试必须携带上次成功接收的id,否则将重复返回历史片段。
1、在read() Promise拒绝或done为true时,检查lastEventId是否为空,若非空则在请求URL后添加?last_id=xxx参数。
2、设置指数退避重试间隔,首次延迟1秒,第二次2秒,第三次4秒,上限不超过30秒。
3、在重连请求头中添加X-Last-Event-ID: {lastEventId},供服务端定位续传位置。
4、限制最大重试次数为5次,超过后向用户显示“连接已断开,请刷新页面重试”提示。
五、适配不同浏览器的流式读取兼容性
Safari 15.4以下版本不支持ReadableStream默认构造,且部分安卓WebView对TextDecoder.stream()无响应;需降级使用EventSource并手动解析data字段,或引入web-streams-polyfill。
1、检测window.EventSource是否存在,若存在且navigator.userAgent包含Safari,则优先使用EventSource初始化。
2、若使用fetch,则在调用前判断window.ReadableStream是否可用,不可用时加载https://cdn.jsdelivr.net/npm/web-streams-polyfill@3.2.1/dist/ponyfill.min.js。
3、对EventSource实例监听message事件,从e.data提取JSON字符串,再执行与步骤二相同的data字段剥离逻辑。
4、禁用EventSource的自动重连机制(设置eventSource.readyState = 0),由自定义逻辑统一控制重试时机与参数。











