1
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* SSE工具类测试文件
|
||||
* 用于验证SSE功能是否正常工作
|
||||
*/
|
||||
|
||||
import { createSSERequest, createPetAssistantSSE, SSE_STATUS } from './sse.js'
|
||||
|
||||
/**
|
||||
* 测试基础SSE功能
|
||||
*/
|
||||
export function testBasicSSE() {
|
||||
console.log('开始测试基础SSE功能...')
|
||||
|
||||
const testData = {
|
||||
message: '测试消息',
|
||||
temperature: 0.7,
|
||||
maxTokens: 100
|
||||
}
|
||||
|
||||
let receivedChunks = 0
|
||||
let totalContent = ''
|
||||
|
||||
return createSSERequest({
|
||||
url: `${uni.$u.http.config.baseURL}/pet/user/assistant/stream-ask`,
|
||||
data: testData,
|
||||
onChunk: (chunk) => {
|
||||
receivedChunks++
|
||||
totalContent = chunk.totalContent
|
||||
console.log(`接收到第${receivedChunks}个数据块:`, chunk.content)
|
||||
},
|
||||
onComplete: (result) => {
|
||||
console.log('SSE响应完成:', {
|
||||
totalChunks: receivedChunks,
|
||||
totalContent: totalContent,
|
||||
metadata: result.data
|
||||
})
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('SSE测试失败:', error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试宠物助手专用SSE
|
||||
*/
|
||||
export function testPetAssistantSSE() {
|
||||
console.log('开始测试宠物助手SSE功能...')
|
||||
|
||||
const testData = {
|
||||
message: '你好,请介绍一下自己',
|
||||
temperature: 0.7,
|
||||
maxTokens: 200
|
||||
}
|
||||
|
||||
let chunkCount = 0
|
||||
|
||||
return createPetAssistantSSE(testData, {
|
||||
onChunk: (chunk) => {
|
||||
chunkCount++
|
||||
console.log(`宠物助手SSE - 第${chunkCount}块:`, chunk.content)
|
||||
},
|
||||
onComplete: (result) => {
|
||||
console.log('宠物助手SSE完成:', {
|
||||
chunks: chunkCount,
|
||||
message: result.data.message,
|
||||
isSensitive: result.data.isSensitive,
|
||||
tokenCount: result.data.tokenCount
|
||||
})
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('宠物助手SSE失败:', error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行所有测试
|
||||
*/
|
||||
export async function runAllTests() {
|
||||
console.log('=== 开始SSE工具类测试 ===')
|
||||
|
||||
try {
|
||||
// 测试基础SSE
|
||||
console.log('\n1. 测试基础SSE功能')
|
||||
await testBasicSSE()
|
||||
|
||||
// 等待一秒
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
// 测试宠物助手SSE
|
||||
console.log('\n2. 测试宠物助手SSE功能')
|
||||
await testPetAssistantSSE()
|
||||
|
||||
console.log('\n=== SSE工具类测试完成 ===')
|
||||
return true
|
||||
|
||||
} catch (error) {
|
||||
console.error('SSE测试过程中发生错误:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单的功能验证
|
||||
*/
|
||||
export function validateSSEFunctions() {
|
||||
console.log('验证SSE工具类函数是否存在...')
|
||||
|
||||
const checks = [
|
||||
{ name: 'createSSERequest', func: createSSERequest },
|
||||
{ name: 'createPetAssistantSSE', func: createPetAssistantSSE },
|
||||
{ name: 'SSE_STATUS', obj: SSE_STATUS }
|
||||
]
|
||||
|
||||
let allValid = true
|
||||
|
||||
checks.forEach(check => {
|
||||
if (typeof check.func === 'function' || typeof check.obj === 'object') {
|
||||
console.log(`✅ ${check.name} - 正常`)
|
||||
} else {
|
||||
console.error(`❌ ${check.name} - 缺失或类型错误`)
|
||||
allValid = false
|
||||
}
|
||||
})
|
||||
|
||||
if (allValid) {
|
||||
console.log('✅ 所有SSE工具类函数验证通过')
|
||||
} else {
|
||||
console.error('❌ SSE工具类函数验证失败')
|
||||
}
|
||||
|
||||
return allValid
|
||||
}
|
||||
|
||||
export default {
|
||||
testBasicSSE,
|
||||
testPetAssistantSSE,
|
||||
runAllTests,
|
||||
validateSSEFunctions
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
# SSE工具类使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
SSE (Server-Sent Events) 工具类提供了通用的流式响应处理功能,支持实时数据流处理、回调机制和错误处理。
|
||||
|
||||
## 主要功能
|
||||
|
||||
### 1. 基础SSE请求 - `createSSERequest()`
|
||||
|
||||
最灵活的底层API,支持完全自定义配置。
|
||||
|
||||
```javascript
|
||||
import { createSSERequest } from '@/utils/sse.js'
|
||||
|
||||
const response = await createSSERequest({
|
||||
url: 'https://api.example.com/stream',
|
||||
method: 'POST',
|
||||
data: { message: '你好' },
|
||||
headers: { 'Custom-Header': 'value' },
|
||||
onChunk: (chunk) => {
|
||||
console.log('接收到数据:', chunk.content)
|
||||
console.log('累积内容:', chunk.totalContent)
|
||||
},
|
||||
onComplete: (result) => {
|
||||
console.log('流式响应完成:', result.data)
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('发生错误:', error)
|
||||
},
|
||||
doneMarker: '[DONE]' // 自定义结束标记
|
||||
})
|
||||
```
|
||||
|
||||
### 2. 默认配置SSE请求 - `createDefaultSSERequest()`
|
||||
|
||||
带有默认配置的便捷API。
|
||||
|
||||
```javascript
|
||||
import { createDefaultSSERequest } from '@/utils/sse.js'
|
||||
|
||||
const response = await createDefaultSSERequest(
|
||||
'/api/chat/stream', // API端点
|
||||
{ message: '你好' }, // 请求数据
|
||||
{
|
||||
onChunk: (chunk) => {
|
||||
// 处理数据块
|
||||
updateUI(chunk.content)
|
||||
},
|
||||
onComplete: (result) => {
|
||||
// 处理完成
|
||||
console.log('完成:', result.data.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### 3. 宠物助手专用SSE - `createPetAssistantSSE()`
|
||||
|
||||
专门为宠物助手功能设计的API。
|
||||
|
||||
```javascript
|
||||
import { createPetAssistantSSE } from '@/utils/sse.js'
|
||||
|
||||
const response = await createPetAssistantSSE(
|
||||
{
|
||||
message: '我的宠物生病了怎么办?',
|
||||
temperature: 0.7,
|
||||
maxTokens: 1000
|
||||
},
|
||||
{
|
||||
onChunk: (chunk) => {
|
||||
// 实时更新AI回复
|
||||
aiMessage.content = chunk.totalContent
|
||||
scrollToBottom()
|
||||
},
|
||||
onComplete: (result) => {
|
||||
// 更新元数据
|
||||
aiMessage.isSensitive = result.data.isSensitive
|
||||
aiMessage.tokenCount = result.data.tokenCount
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### 4. 可取消的SSE请求 - `createCancellableSSE()`
|
||||
|
||||
支持取消操作的SSE请求。
|
||||
|
||||
```javascript
|
||||
import { createCancellableSSE, SSE_STATUS } from '@/utils/sse.js'
|
||||
|
||||
const { promise, cancel, getStatus } = createCancellableSSE({
|
||||
url: '/api/stream',
|
||||
data: { message: '长时间处理的请求' },
|
||||
onChunk: (chunk) => {
|
||||
console.log('状态:', getStatus()) // 'streaming'
|
||||
updateContent(chunk.content)
|
||||
}
|
||||
})
|
||||
|
||||
// 5秒后取消请求
|
||||
setTimeout(() => {
|
||||
if (getStatus() === SSE_STATUS.STREAMING) {
|
||||
cancel()
|
||||
console.log('请求已取消')
|
||||
}
|
||||
}, 5000)
|
||||
|
||||
try {
|
||||
const result = await promise
|
||||
console.log('请求完成:', result)
|
||||
} catch (error) {
|
||||
console.log('请求被取消或失败:', error)
|
||||
}
|
||||
```
|
||||
|
||||
## 回调函数参数
|
||||
|
||||
### onChunk(chunk)
|
||||
- `chunk.content` - 当前数据块内容
|
||||
- `chunk.totalContent` - 累积的完整内容
|
||||
- `chunk.chunk` - 原始解析的数据对象
|
||||
|
||||
### onComplete(result)
|
||||
- `result.data.message` - 完整的响应消息
|
||||
- `result.data.isSensitive` - 是否包含敏感内容
|
||||
- `result.data.tokenCount` - 使用的token数量
|
||||
- `result.data.responseTime` - 响应时间
|
||||
|
||||
### onError(error)
|
||||
- 网络错误、解析错误或其他异常
|
||||
|
||||
## 状态枚举
|
||||
|
||||
```javascript
|
||||
import { SSE_STATUS } from '@/utils/sse.js'
|
||||
|
||||
SSE_STATUS.PENDING // 等待中
|
||||
SSE_STATUS.STREAMING // 流式传输中
|
||||
SSE_STATUS.COMPLETED // 已完成
|
||||
SSE_STATUS.ERROR // 发生错误
|
||||
SSE_STATUS.CANCELLED // 已取消
|
||||
```
|
||||
|
||||
## 在Vue组件中使用
|
||||
|
||||
```javascript
|
||||
// 在组件中导入
|
||||
import { createPetAssistantSSE } from '@/utils/sse.js'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
async sendMessage() {
|
||||
// 创建消息对象
|
||||
const aiMessage = {
|
||||
type: 'ai',
|
||||
content: '',
|
||||
time: this.getCurrentTime()
|
||||
}
|
||||
this.messageList.push(aiMessage)
|
||||
|
||||
// 发起SSE请求
|
||||
try {
|
||||
const result = await createPetAssistantSSE(
|
||||
{ message: this.inputMessage },
|
||||
{
|
||||
onChunk: (chunk) => {
|
||||
// 实时更新内容
|
||||
aiMessage.content = chunk.totalContent
|
||||
this.$nextTick(() => {
|
||||
this.scrollToBottom()
|
||||
})
|
||||
},
|
||||
onComplete: (result) => {
|
||||
// 更新元数据
|
||||
Object.assign(aiMessage, result.data)
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('SSE失败:', error)
|
||||
// 可以在这里实现降级处理
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
// 处理异常
|
||||
this.handleError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **自动认证**: 工具类会自动添加Authorization头部
|
||||
2. **错误处理**: 建议同时实现onError回调和try-catch
|
||||
3. **内存管理**: 长时间的流式响应注意内存使用
|
||||
4. **网络状态**: 考虑网络中断的情况
|
||||
5. **降级处理**: SSE失败时可以降级到普通API请求
|
||||
|
||||
## 扩展使用
|
||||
|
||||
可以基于基础API创建更多专用函数:
|
||||
|
||||
```javascript
|
||||
// 创建客服聊天SSE
|
||||
export function createCustomerServiceSSE(data, callbacks) {
|
||||
return createDefaultSSERequest('/api/customer/stream', data, callbacks)
|
||||
}
|
||||
|
||||
// 创建代码生成SSE
|
||||
export function createCodeGenerationSSE(data, callbacks) {
|
||||
return createDefaultSSERequest('/api/code/generate', data, callbacks)
|
||||
}
|
||||
```
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* SSE (Server-Sent Events) 流式响应工具类
|
||||
* 提供通用的SSE数据流处理功能
|
||||
*/
|
||||
|
||||
/**
|
||||
* 创建SSE流式请求
|
||||
* @param {Object} options 配置选项
|
||||
* @param {string} options.url 请求URL
|
||||
* @param {string} options.method 请求方法,默认POST
|
||||
* @param {Object} options.data 请求数据
|
||||
* @param {Object} options.headers 请求头,会自动添加SSE相关头部
|
||||
* @param {Function} options.onChunk 接收到数据块时的回调 (chunk) => {}
|
||||
* @param {Function} options.onComplete 流式响应完成时的回调 (result) => {}
|
||||
* @param {Function} options.onError 发生错误时的回调 (error) => {}
|
||||
* @param {string} options.doneMarker 结束标记,默认为 '[DONE]'
|
||||
* @returns {Promise} 返回Promise,resolve时返回完整结果
|
||||
*/
|
||||
export function createSSERequest(options) {
|
||||
const {
|
||||
url,
|
||||
method = 'POST',
|
||||
data = {},
|
||||
headers = {},
|
||||
onChunk,
|
||||
onComplete,
|
||||
onError,
|
||||
doneMarker = '[DONE]'
|
||||
} = options
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// 构建完整的请求头
|
||||
const requestHeaders = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Authorization': `Bearer ${uni.getStorageSync('token') || ''}`,
|
||||
...headers
|
||||
}
|
||||
|
||||
// 累积的响应数据
|
||||
let accumulatedData = {
|
||||
content: '',
|
||||
metadata: {}
|
||||
}
|
||||
|
||||
// 发起uni.request请求
|
||||
const requestTask = uni.request({
|
||||
url,
|
||||
method,
|
||||
data,
|
||||
header: requestHeaders,
|
||||
responseType: 'text',
|
||||
success: (res) => {
|
||||
try {
|
||||
// 处理SSE数据流
|
||||
const lines = res.data.split('\n')
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.substring(6).trim()
|
||||
|
||||
// 检查是否为结束标记
|
||||
if (data === doneMarker) {
|
||||
// 流式响应结束
|
||||
const result = {
|
||||
data: {
|
||||
message: accumulatedData.content,
|
||||
...accumulatedData.metadata
|
||||
}
|
||||
}
|
||||
|
||||
// 调用完成回调
|
||||
if (onComplete) {
|
||||
onComplete(result)
|
||||
}
|
||||
|
||||
resolve(result)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data)
|
||||
|
||||
// 处理内容数据
|
||||
if (parsed.content) {
|
||||
accumulatedData.content += parsed.content
|
||||
|
||||
// 调用数据块回调
|
||||
if (onChunk) {
|
||||
onChunk({
|
||||
content: parsed.content,
|
||||
totalContent: accumulatedData.content,
|
||||
chunk: parsed
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 更新元数据
|
||||
if (parsed.isSensitive !== undefined) {
|
||||
accumulatedData.metadata.isSensitive = parsed.isSensitive
|
||||
}
|
||||
if (parsed.tokenCount !== undefined) {
|
||||
accumulatedData.metadata.tokenCount = parsed.tokenCount
|
||||
}
|
||||
if (parsed.responseTime !== undefined) {
|
||||
accumulatedData.metadata.responseTime = parsed.responseTime
|
||||
}
|
||||
|
||||
// 保存其他元数据
|
||||
Object.keys(parsed).forEach(key => {
|
||||
if (!['content', 'isSensitive', 'tokenCount', 'responseTime'].includes(key)) {
|
||||
accumulatedData.metadata[key] = parsed[key]
|
||||
}
|
||||
})
|
||||
|
||||
} catch (parseError) {
|
||||
console.warn('解析SSE数据失败:', parseError, data)
|
||||
// 继续处理其他数据,不中断流程
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有收到结束标记,也认为完成
|
||||
const result = {
|
||||
data: {
|
||||
message: accumulatedData.content,
|
||||
...accumulatedData.metadata
|
||||
}
|
||||
}
|
||||
|
||||
if (onComplete) {
|
||||
onComplete(result)
|
||||
}
|
||||
|
||||
resolve(result)
|
||||
|
||||
} catch (error) {
|
||||
console.error('处理SSE响应失败:', error)
|
||||
|
||||
if (onError) {
|
||||
onError(error)
|
||||
}
|
||||
|
||||
reject(error)
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error('SSE请求失败:', error)
|
||||
|
||||
if (onError) {
|
||||
onError(error)
|
||||
}
|
||||
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
|
||||
// 返回请求任务,允许外部取消请求
|
||||
return requestTask
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建带有默认配置的SSE请求
|
||||
* @param {string} endpoint API端点路径
|
||||
* @param {Object} data 请求数据
|
||||
* @param {Object} callbacks 回调函数 {onChunk, onComplete, onError}
|
||||
* @returns {Promise} SSE请求Promise
|
||||
*/
|
||||
export function createDefaultSSERequest(endpoint, data, callbacks = {}) {
|
||||
const baseURL = uni.$u?.http?.config?.baseURL || ''
|
||||
|
||||
return createSSERequest({
|
||||
url: `${baseURL}${endpoint}`,
|
||||
method: 'POST',
|
||||
data,
|
||||
...callbacks
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 专用于宠物助手的SSE请求
|
||||
* @param {Object} data 请求数据
|
||||
* @param {Object} callbacks 回调函数
|
||||
* @returns {Promise} SSE请求Promise
|
||||
*/
|
||||
export function createPetAssistantSSE(data, callbacks = {}) {
|
||||
return createDefaultSSERequest('/pet/user/assistant/stream-ask', data, callbacks)
|
||||
}
|
||||
|
||||
/**
|
||||
* SSE请求状态枚举
|
||||
*/
|
||||
export const SSE_STATUS = {
|
||||
PENDING: 'pending',
|
||||
STREAMING: 'streaming',
|
||||
COMPLETED: 'completed',
|
||||
ERROR: 'error',
|
||||
CANCELLED: 'cancelled'
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建可取消的SSE请求
|
||||
* @param {Object} options 配置选项
|
||||
* @returns {Object} 返回包含promise和cancel方法的对象
|
||||
*/
|
||||
export function createCancellableSSE(options) {
|
||||
let requestTask = null
|
||||
let status = SSE_STATUS.PENDING
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
const enhancedOptions = {
|
||||
...options,
|
||||
onChunk: (chunk) => {
|
||||
status = SSE_STATUS.STREAMING
|
||||
if (options.onChunk) {
|
||||
options.onChunk(chunk)
|
||||
}
|
||||
},
|
||||
onComplete: (result) => {
|
||||
status = SSE_STATUS.COMPLETED
|
||||
if (options.onComplete) {
|
||||
options.onComplete(result)
|
||||
}
|
||||
resolve(result)
|
||||
},
|
||||
onError: (error) => {
|
||||
status = SSE_STATUS.ERROR
|
||||
if (options.onError) {
|
||||
options.onError(error)
|
||||
}
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
requestTask = createSSERequest(enhancedOptions)
|
||||
})
|
||||
|
||||
return {
|
||||
promise,
|
||||
cancel: () => {
|
||||
if (requestTask && status === SSE_STATUS.STREAMING) {
|
||||
requestTask.abort?.()
|
||||
status = SSE_STATUS.CANCELLED
|
||||
}
|
||||
},
|
||||
getStatus: () => status
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
createSSERequest,
|
||||
createDefaultSSERequest,
|
||||
createPetAssistantSSE,
|
||||
createCancellableSSE,
|
||||
SSE_STATUS
|
||||
}
|
||||
Reference in New Issue
Block a user