更新记录
1.0.0(2026-08-17)
下载此版本
- 首次发布:跨端流式请求 UTS 插件。
- 支持 Android / iOS / HarmonyOS / Web / 微信小程序 五端,兼容 uni-app 与 uni-app x。
- 支持 SSE / Line / JSONL / Raw 四种协议。
- 支持 GET / POST / PUT / PATCH / DELETE、自定义请求头与请求体。
- 支持增量 UTF-8 解码(中文 / Emoji 跨分片不乱码)、Last-Event-ID 续传。
- 支持自动重连(指数退避 / 抖动 / SSE retry / HTTP Retry-After / 状态码覆盖 / 累计预算)。
- 支持网络与前后台恢复、高频回调批处理、业务终止帧。
- 支持指标快照
getMetrics() 与脱敏诊断 exportDiagnostics()。
- 附带本地零依赖测试服务器与引擎逻辑 Node 验证脚本。
平台兼容性
uni-app(5.07)
| Vue2 |
Vue3 |
Chrome |
Safari |
app-vue |
app-nvue |
Android |
iOS |
鸿蒙 |
| √ |
√ |
√ |
√ |
√ |
- |
√ |
√ |
- |
| 微信小程序 |
支付宝小程序 |
抖音小程序 |
百度小程序 |
快手小程序 |
京东小程序 |
鸿蒙元服务 |
QQ小程序 |
飞书小程序 |
小红书小程序 |
快应用-华为 |
快应用-联盟 |
| √ |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
uni-app x(5.07)
| Chrome |
Safari |
Android |
iOS |
鸿蒙 |
微信小程序 |
| - |
- |
- |
- |
- |
- |
lucis-sse 流式请求(SSE/Line/JSONL/Raw) UTS 插件
面向 AI 对话、智能客服、消息推送、日志流和长任务进度的跨端流式请求 UTS 插件。一次接入即可在 Android、iOS、HarmonyOS、Web、微信小程序 中统一处理 SSE、逐行文本、JSONL 和 Raw 文本流。
插件不是对 EventSource 的简单包装:支持 POST/PUT/PATCH/DELETE、自定义请求头与请求体、严格 UTF-8 增量解码、首包保护、SSE 标准字段、Last-Event-ID、可控重连、超时、限流与幂等取消。
部分功能未验证,如需在生产环境中使用,请自行测试。仅供有需求的开发者参考使用!!!
功能特性
- 四种流协议:
sse / line / jsonl / raw,同一套回调处理不同服务端格式。
- 不局限 GET:支持
GET / POST / PUT / PATCH / DELETE,可接 OpenAI 兼容接口、私有 AI 网关和业务流接口。
- 两种请求头写法:兼容
headers 动态对象,同时推荐跨端更稳定的 headerList 强类型数组。
- 首包不丢:所有回调在网络请求发出前完成注册,适配"连接后立即返回第一段"的服务端。
- 完整 SSE 语义:支持 BOM、CR/LF/CRLF、注释心跳、多行
data、event、id、retry 和未知字段忽略。
- UTF-8 分片安全:五个平台统一使用自研增量解码器,保留不完整多字节尾部,中文与 Emoji 不会因网络分片乱码。
- 断线续传:可将最近
id 自动写入下一次请求的 Last-Event-ID。
- 生产级重试:指数退避、抖动、SSE
retry、HTTP Retry-After、状态码覆盖、累计时间预算和远端结束策略。
- 网络与前后台恢复:自动感知网络变化,App 回到前台后可按策略恢复连接。
- 连接指标:
getMetrics() 读取连接耗时、首字节、消息、字节、错误、重连次数。
- 脱敏诊断:
exportDiagnostics() 生成可直接交给技术支持的 JSON 报告,不包含 URL 查询参数、请求头值、请求体或消息正文。
- 高频批处理:按时间窗口或数量合并 chunk / message 回调,降低 AI 高频 token 对页面渲染的压力。
- 业务终止帧:可识别
[DONE] 或自定义 data/event 控制帧,以正常完成原因结束。
- 微信真实分块:使用
uni.request({ enableChunked: true }) + onChunkReceived,不是等待完整响应后伪装流式。
- 无额外网络 SDK:Android 使用 HttpURLConnection,iOS 使用 URLSessionDataDelegate,HarmonyOS 使用 requestInStream,Web 使用 Fetch 流,微信使用 RequestTask。
支持平台
| 平台 |
是否支持 |
说明 |
| uni-app Android |
✅ |
HttpURLConnection;需要自定义基座 |
| uni-app iOS |
✅ |
URLSessionDataDelegate 原生流;需要自定义基座 |
| uni-app HarmonyOS |
✅ |
@ohos.net.http requestInStream 源码实现;需要自定义基座/HAP |
| uni-app Web |
✅ |
Fetch 流;受 CORS 和代理缓冲影响,无需自定义基座 |
| uni-app 微信小程序 |
✅ |
enableChunked + onChunkReceived;需配置合法请求域名,无需自定义基座 |
| uni-app x 各端 |
✅ |
API 一致;小程序需 HBuilderX 5.07+ |
| 其他小程序 |
❌ |
各平台分块 API 与发布限制不同,不伪造支持 |
安装与运行前准备
- 从 DCloud 插件市场导入插件,或把
uni_modules/lucis-sse 目录放入项目。
- 页面始终从插件根目录导入:
import { connectStream } from '@/uni_modules/lucis-sse',不要指向 utssdk/index.uts。
- Android / iOS / HarmonyOS 首次接入或升级原生代码后,需要重新制作自定义基座(只改页面、示例 URL 或普通业务代码时不需要)。
- Web 无需自定义基座,但服务端必须允许当前站点跨域访问并关闭响应缓冲。
- 微信小程序 无需自定义基座,但必须在微信公众平台配置 HTTPS 合法请求域名;正式版不能依赖开发工具的"忽略域名校验"。
- 生产接口建议使用 HTTPS,不要把 Token 写死在源码或日志中。
最小可运行示例
uni-app(Vue2/Vue3)
import { connectStream } from '@/uni_modules/lucis-sse'
const connection = connectStream({
url: 'https://example.com/events',
// 回调与参数一次性传入,插件会先注册回调再发起请求。
onMessage(message) {
console.log('收到完整事件:', message.data)
},
onError(error) {
console.log('连接失败:', error.errCode, error.errMsg)
},
onComplete(event) {
console.log('连接最终结束:', event.reason)
}
})
// 页面离开或用户点击停止时调用;重复调用是安全的。
connection.abort()
uni-app x
import { connectStream, type StreamConnection, type StreamMessage, type StreamFail } from '@/uni_modules/lucis-sse'
let connection: StreamConnection | null = null
connection = connectStream({
url: 'https://example.com/events',
onMessage: (message: StreamMessage): void => {
console.log('收到完整事件:' + message.data)
},
onError: (error: StreamFail): void => {
console.log('连接失败:' + error.errCode.toString())
},
onComplete: (event): void => {
console.log('连接最终结束:' + event.reason)
connection = null
}
})
微信小程序
import { connectStream } from '@/uni_modules/lucis-sse'
const connection = connectStream({
// 必须提前配置为微信小程序合法请求域名。
url: 'https://stream.example.com/events',
protocol: 'sse',
onMessage(message) {
console.log('微信小程序收到事件:', message.data)
},
onComplete(event) {
console.log('连接结束:', event.reason)
}
})
// 页面卸载时主动取消,避免残留网络任务。
connection.abort()
API 列表
| API |
返回值 |
说明 |
connectStream(options) |
StreamConnection |
创建流式连接,立即返回控制对象 |
closeAllStreams() |
void |
幂等关闭本插件创建的全部连接 |
getStreamCapabilities() |
StreamCapabilities |
获取当前平台真实能力矩阵 |
notifyStreamAppState(state) |
void |
从 App.vue 通知前后台状态 |
notifyStreamNetworkChange(isConnected, networkType?) |
void |
手动通知网络状态;通常无需调用 |
connectStream(options)
创建一个流式 HTTP 请求。函数同步返回连接控制对象,网络事件通过 options 中的回调持续返回。所有回调会在请求发出前保存,避免立即首包丢失。
| 参数 |
类型 |
必填 |
说明 |
默认值 |
options.url |
string |
是 |
HTTP/HTTPS 流地址 |
无 |
options.method |
StreamMethod |
否 |
HTTP 方法 |
GET |
options.protocol |
StreamProtocol |
否 |
解析协议 |
sse |
options.headers |
UTSJSONObject |
否 |
兼容 JS 普通对象的动态请求头 |
无 |
options.headerList |
Array\<StreamHeader> |
否 |
推荐的强类型请求头;同名时覆盖 headers |
[] |
options.bodyText |
string |
否 |
文本请求体,与 jsonBody 互斥 |
无 |
options.jsonBody |
any |
否 |
JSON 请求体,与 bodyText 互斥 |
无 |
options.connectTimeoutMs |
number |
否 |
连接建立超时 |
30000 |
options.idleTimeoutMs |
number |
否 |
打开后无字节到达超时,0 不限制 |
0 |
options.totalTimeoutMs |
number |
否 |
单次连接总时长,0 不限制 |
0 |
options.lastEventId |
string |
否 |
首次请求的 Last-Event-ID |
'' |
options.maxBufferBytes |
number |
否 |
未完成帧最大缓冲(按字符近似) |
1048576 |
options.maxMessageBytes |
number |
否 |
单条完整消息最大长度(按字符近似) |
524288 |
options.reconnect |
StreamReconnectOptions |
否 |
自动重连配置 |
{ enabled: false } |
options.recovery |
StreamRecoveryOptions |
否 |
网络与前后台恢复策略 |
见下 |
options.batch |
StreamBatchOptions |
否 |
高频回调批处理 |
{ enabled: false } |
options.terminal |
StreamTerminalOptions |
否 |
业务终止帧策略 |
{ enabled: false } |
options.debug |
boolean |
否 |
输出精简中文诊断日志 |
false |
options.logPayload |
boolean |
否 |
debug 时打印业务文本,不建议生产开启 |
false |
options.onOpen |
function |
否 |
HTTP 连接打开时触发 |
无 |
options.onChunk |
function |
否 |
每个已解码文本分片触发 |
无 |
options.onChunkBatch |
function |
否 |
批量文本分片;提供后接管 onChunk |
无 |
options.onMessage |
function |
否 |
每条完整协议消息触发 |
无 |
options.onMessageBatch |
function |
否 |
批量完整消息;提供后接管 onMessage |
无 |
options.onTerminal |
function |
否 |
识别终止帧时触发,早于 onComplete |
无 |
options.onRetry |
function |
否 |
服务端 retry 更新或等待重连时触发 |
无 |
options.onStateChange |
function |
否 |
连接状态变化时触发 |
无 |
options.onEnvironmentChange |
function |
否 |
网络或 App 前后台变化时触发 |
无 |
options.onError |
function |
否 |
单次失败或最终失败时触发 |
无 |
options.onComplete |
function |
否 |
连接最终结束时只触发一次 |
无 |
options.success / fail / complete |
function |
否 |
兼容 uni 风格,与对应回调同时触发 |
无 |
重连参数 reconnect
| 参数 |
类型 |
说明 |
默认值 |
reconnect.enabled |
boolean |
是否自动重连 |
false |
reconnect.maxAttempts |
number |
最大重连次数 |
3 |
reconnect.initialDelayMs |
number |
初始等待毫秒数 |
1000 |
reconnect.maxDelayMs |
number |
最大等待毫秒数 |
30000 |
reconnect.multiplier |
number |
指数退避倍数 |
2 |
reconnect.jitterRatio |
number |
随机抖动比例(0 到 1) |
0.2 |
reconnect.useServerRetry |
boolean |
是否采用 SSE retry 字段 |
true |
reconnect.policy |
StreamRetryPolicyOptions |
Retry-After、状态码、预算等 |
无 |
生产级重试 reconnect.policy
| 参数 |
类型 |
说明 |
默认值 |
policy.respectRetryAfter |
boolean |
是否采用 HTTP Retry-After |
false |
policy.maxRetryAfterMs |
number |
Retry-After 最大贡献等待时间 |
60000 |
policy.maxElapsedMs |
number |
单个重试周期累计时间预算;0 不限制 |
0 |
policy.retryableStatusCodes |
Array\<number> |
额外允许重试的 HTTP 状态码 |
[] |
policy.nonRetryableStatusCodes |
Array\<number> |
明确禁止重试的状态码;冲突时优先 |
[] |
policy.remoteEnd |
string |
远端正常结束后的重连策略 |
always |
自动等待优先级:有效 HTTP Retry-After → SSE retry: → 客户端指数退避。POST 默认关闭自动重连,除非服务端提供幂等键并允许安全重放。
返回值 StreamConnection
| 字段 |
类型 |
说明 |
getId() |
() => string |
返回本次连接 ID |
getState() |
() => string |
返回当前状态(idle/connecting/open/reconnect-wait/closed) |
getMetrics() |
() => StreamMetrics |
返回当前连接指标快照 |
exportDiagnostics() |
() => string |
导出脱敏 JSON 诊断报告 |
reconnect(reason?) |
(reason?: string) => boolean |
请求立即恢复连接 |
abort(reason?) |
(reason?: string) => void |
幂等取消连接 |
常见用法
POST + 自定义请求头 + 鉴权
import { connectStream } from '@/uni_modules/lucis-sse'
const connection = connectStream({
url: 'https://api.example.com/v1/chat/stream',
method: 'POST',
protocol: 'sse',
headerList: [
{ name: 'Authorization', value: 'Bearer ' + runtimeToken },
{ name: 'X-Customer-ID', value: customerId }
],
jsonBody: {
model: 'your-model',
messages: [{ role: 'user', content: '你好' }],
stream: true
},
reconnect: { enabled: false },
terminal: { enabled: true, deliverMessage: false },
onMessage: (message) => console.log(message.data),
onTerminal: (event) => console.log('服务端业务完成:' + event.source),
onError: (error) => console.log(error.errCode + ':' + error.errMsg)
})
自动重连 + Last-Event-ID
const connection = connectStream({
url: 'https://example.com/events',
protocol: 'sse',
reconnect: {
enabled: true,
maxAttempts: 5,
initialDelayMs: 1000,
maxDelayMs: 15000,
multiplier: 2,
jitterRatio: 0.2,
useServerRetry: true
},
onRetry: (event) => {
console.log('第 ' + event.attempt + ' 次重连将在 ' + event.delayMs + 'ms 后执行')
},
onMessage: (message) => {
// 插件会保存 SSE id,并在重连时自动发送 Last-Event-ID。
console.log(message.id + ': ' + message.data)
}
})
App 前后台恢复
// App.vue
import { notifyStreamAppState } from '@/uni_modules/lucis-sse'
export default {
onShow() {
notifyStreamAppState('foreground')
},
onHide() {
notifyStreamAppState('background')
}
}
指标与脱敏诊断
const metrics = connection.getMetrics()
console.log('连接耗时:' + metrics.connectDurationMs + 'ms')
console.log('首字节:' + metrics.firstByteDurationMs + 'ms')
// 报告不包含 Authorization/Cookie 值、URL 查询参数、body、chunk 或 message.data。
const diagnosticJson = connection.exportDiagnostics()
回调数据
StreamMessage
| 字段 |
类型 |
说明 |
connectionId |
string |
连接 ID |
protocol |
string |
当前解析协议 |
data |
string |
完整文本消息 |
event |
string |
SSE event;缺省为 message,非 SSE 为空 |
id |
string |
最近一次有效 SSE id |
json |
any | null |
JSONL 解析结果;其他协议为 null |
sequence |
number |
消息序号,从 1 开始 |
StreamCompleteEvent
| 字段 |
类型 |
说明 |
reason |
string |
remote-end / terminal-message / abort / error / timeout / reconnect-exhausted / retry-budget-exhausted |
manuallyAborted |
boolean |
是否主动取消 |
statusCode |
number |
最终 HTTP 状态码 |
reconnectCount |
number |
实际重连次数 |
lastEventId |
string |
最后 SSE 事件 ID |
错误码
| 错误码 |
含义 |
| 9098001 |
参数不合法 |
| 9098002 |
平台不支持 |
| 9098003 |
网络连接失败 |
| 9098004 |
连接建立超时 |
| 9098005 |
连接空闲超时 |
| 9098006 |
连接总时长超时 |
| 9098007 |
HTTP 状态异常 |
| 9098008 |
响应流读取失败 |
| 9098009 |
UTF-8 解码失败 |
| 9098010 |
协议解析失败 |
| 9098011 |
JSONL 无效 |
| 9098012 |
缓冲区超限 |
| 9098013 |
消息超限 |
| 9098014 |
重连耗尽 |
| 9098015 |
重试预算耗尽 |
各平台注意事项
- Android:需要
android.permission.INTERNET(插件 Manifest 已声明);首次接入必须制作自定义基座。Thread SAM、ByteArray↔Uint8Array、String.toByteArray() 三个点在打基座时需验证。
- iOS:需要 macOS + Xcode;云端打包或自定义基座。UTS↔Swift 的闭包回调、
[UInt8]↔Array<number>、Map↔Dictionary 在打基座时需验证。
- HarmonyOS:需 DevEco 与自定义基座/HAP;不依赖加密 module.har。
- Web:服务端必须配置 CORS,代理层不能缓冲响应(建议返回
X-Accel-Buffering: no)。
- 微信小程序:基础库 ≥ 2.20.1;必须配置 HTTPS 合法请求域名;
abort() 在 enableChunked 下可能失效,插件会按连接自然结束兜底。
本地测试服务
仓库自带零依赖 Node 本地测试服务,覆盖 SSE 基础、中文跨分片、POST 回显、Line/JSONL/Raw、断线续传、Retry-After、终止帧、断流等场景:
# 在项目根目录执行;服务默认监听 0.0.0.0:8787,允许局域网真机访问
node uni_modules/lucis-sse/example/server/lucis-sse-test-server.js
| 运行环境 |
示例地址 |
说明 |
| Web / 同一台电脑 |
http://127.0.0.1:8787 |
浏览器与测试服务运行在同一台电脑 |
| Android 模拟器 |
http://10.0.2.2:8787 |
Android Studio 模拟器访问宿主机默认映射 |
| Android/iOS/HarmonyOS 真机 |
http://\<电脑局域网IPv4>:8787 |
手机和电脑需在同一网络,防火墙放行 8787 |
| 微信开发者工具 |
已备案的 HTTPS 测试域名 |
本地 HTTP 只适合临时开发调试 |