共计 2512 个字符,预计需要花费 7 分钟才能阅读完成。
开篇:浏览器调用 ChatGPT 的三大挑战
在浏览器环境中直接调用 ChatGPT API 会遇到几个典型问题:

- 跨域限制:浏览器安全策略会拦截未正确配置 CORS 的 API 请求,需要后端代理或特殊处理
- 流式响应处理 :直接使用 Fetch API 处理 SSE(Server-Sent Events) 需要特殊解析逻辑
- token 管理:频繁的 API 调用会导致 token 刷新问题,需要实现可靠的本地存储方案
技术方案对比
通信协议选择
通过 JMeter 在 Chrome 116/100Mbps 网络环境下测试(模拟 100 并发):
- REST API 平均延迟:320ms(包含完整的请求 / 响应周期)
- WebSocket 连接平均延迟:180ms(建立连接后持续通信)
建议:短对话使用 REST+SSE,长对话切换 WebSocket
浏览器端存储方案
| 方案 | 容量上限 | 读取速度 | 安全性 |
|---|---|---|---|
| localStorage | 5MB | 0.2ms | 低 |
| IndexedDB | 50MB+ | 1.5ms | 中 |
核心代码实现
带重试机制的请求封装
/**
* 带指数退避的 API 请求
* @param {string} url - API 端点
* @param {object} options - fetch 选项
* @param {number} maxRetries - 最大重试次数
*/
async function fetchWithRetry(url, options, maxRetries = 3) {
let retryCount = 0
while (retryCount <= maxRetries) {
try {const response = await fetch(url, options)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return response
} catch (error) {if (retryCount === maxRetries) throw error
const delay = Math.pow(2, retryCount) * 1000
await new Promise(resolve => setTimeout(resolve, delay))
retryCount++
}
}
}
Web Worker 处理流式响应
// worker.js
self.onmessage = async (e) => {const { endpoint, payload} = e.data
const response = await fetch(endpoint, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload)
})
const reader = response.body.getReader()
while (true) {const { done, value} = await reader.read()
if (done) break
self.postMessage({chunk: new TextDecoder().decode(value) })
}
}
安全实践
对话内容加密方案
import {webcrypto} from 'crypto'
async function encryptData(text, secretKey) {const iv = webcrypto.getRandomValues(new Uint8Array(12))
const alg = {name: 'AES-GCM', iv}
const key = await webcrypto.subtle.importKey(
'raw',
new TextEncoder().encode(secretKey),
alg,
false,
['encrypt']
)
const encrypted = await webcrypto.subtle.encrypt(
alg,
key,
new TextEncoder().encode(text)
)
return {iv, data: new Uint8Array(encrypted) }
}
CSP 策略示例
<meta http-equiv="Content-Security-Policy"
content="default-src'self';
connect-src https://api.openai.com;
script-src 'self' 'unsafe-inline';">
避坑指南
请求队列设计
class RequestQueue {constructor(maxConcurrent = 3) {this.queue = []
this.activeCount = 0
this.maxConcurrent = maxConcurrent
}
add(requestFn) {return new Promise((resolve, reject) => {const run = async () => {
this.activeCount++
try {const result = await requestFn()
resolve(result)
} catch (error) {reject(error)
} finally {
this.activeCount--
this.next()}
}
if (this.activeCount < this.maxConcurrent) {run()
} else {this.queue.push(run)
}
})
}
next() {if (this.queue.length > 0 && this.activeCount < this.maxConcurrent) {this.queue.shift()()}
}
}
会话标识生成
function generateSessionKey() {const timestamp = Date.now().toString(36)
const random = Math.random().toString(36).slice(2, 10)
return `${timestamp}_${random}`
}
思考题
如果考虑实现浏览器插件的离线缓存,需要解决:
- 对话内容的存储结构设计(如何建立对话树?)
- Service Worker 如何拦截 API 请求?
- 加密存储的密钥管理方案
欢迎在评论区分享你的设计方案!
正文完
