优化sse

This commit is contained in:
yml2213
2026-05-29 11:09:15 +08:00
parent 2346f2e064
commit a334f1837d
3 changed files with 40 additions and 12 deletions
+6 -5
View File
@@ -191,14 +191,15 @@ func (r *Repository) EnsureSupportConversation(userID uint64) (*ConversationDTO,
Joins("JOIN chat_participants AS cp ON cp.conversation_id = c.id"). Joins("JOIN chat_participants AS cp ON cp.conversation_id = c.id").
Where("c.type = ? AND cp.participant_type = ? AND cp.participant_id = ?", "general_support", "user", userID). Where("c.type = ? AND cp.participant_type = ? AND cp.participant_id = ?", "general_support", "user", userID).
Order("c.id ASC"). Order("c.id ASC").
First(&existing).Error Limit(1).
if err == nil { Find(&existing).Error
if err != nil {
return err
}
if existing.ID > 0 {
conversationID = existing.ID conversationID = existing.ID
return nil return nil
} }
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
now := time.Now() now := time.Now()
conversation := model.ChatConversation{ conversation := model.ChatConversation{
+2 -2
View File
@@ -73,7 +73,7 @@ function rejectPendingRequests(scope: AuthScope, error: unknown) {
pendingRequests.forEach(({ reject }) => reject(error)) pendingRequests.forEach(({ reject }) => reject(error))
} }
async function refreshToken(scope: AuthScope): Promise<string> { export async function refreshAccessToken(scope: AuthScope): Promise<string> {
const refreshToken = getRefreshToken(scope) const refreshToken = getRefreshToken(scope)
if (!refreshToken) throw new Error('no refresh token') if (!refreshToken) throw new Error('no refresh token')
@@ -153,7 +153,7 @@ apiClient.interceptors.response.use(
state.refreshing = true state.refreshing = true
try { try {
const newToken = await refreshToken(scope) const newToken = await refreshAccessToken(scope)
resolvePendingRequests(scope, newToken) resolvePendingRequests(scope, newToken)
originalRequest._retry = true originalRequest._retry = true
originalRequest.headers.Authorization = `Bearer ${newToken}` originalRequest.headers.Authorization = `Bearer ${newToken}`
+32 -5
View File
@@ -1,4 +1,5 @@
import { onBeforeUnmount, ref, type Ref } from 'vue' import { onBeforeUnmount, ref, type Ref } from 'vue'
import { refreshAccessToken } from '@/api/client'
import { getAccessToken, type AuthScope } from '@/utils/authStorage' import { getAccessToken, type AuthScope } from '@/utils/authStorage'
export interface SSEMessage { export interface SSEMessage {
@@ -29,6 +30,7 @@ export function useChatSSE(scope: AuthScope, endpoint: string) {
let source: EventSource | null = null let source: EventSource | null = null
let reconnectTimer: ReturnType<typeof setTimeout> | null = null let reconnectTimer: ReturnType<typeof setTimeout> | null = null
let stopped = false let stopped = false
let refreshing = false
const handlers: EventHandler[] = [] const handlers: EventHandler[] = []
function onEvent(handler: EventHandler) { function onEvent(handler: EventHandler) {
@@ -36,6 +38,7 @@ export function useChatSSE(scope: AuthScope, endpoint: string) {
} }
function connect() { function connect() {
if (stopped || source) return
const token = getAccessToken(scope) const token = getAccessToken(scope)
if (!token) return if (!token) return
@@ -60,18 +63,27 @@ export function useChatSSE(scope: AuthScope, endpoint: string) {
} catch { /* ignore */ } } catch { /* ignore */ }
}) })
source.onerror = () => { source.onerror = async () => {
connected.value = false connected.value = false
source?.close() source?.close()
source = null source = null
if (!stopped) { if (stopped || refreshing) return
reconnectTimer = setTimeout(connect, reconnectDelay)
refreshing = true
try {
await refreshAccessToken(scope)
if (!stopped) {
reconnectTimer = setTimeout(connect, reconnectDelay)
}
} catch {
closeSource()
} finally {
refreshing = false
} }
} }
} }
function disconnect() { function closeSource() {
stopped = true
if (reconnectTimer) { if (reconnectTimer) {
clearTimeout(reconnectTimer) clearTimeout(reconnectTimer)
reconnectTimer = null reconnectTimer = null
@@ -81,6 +93,21 @@ export function useChatSSE(scope: AuthScope, endpoint: string) {
connected.value = false connected.value = false
} }
function handleAuthStorageChanged(event: Event) {
const detail = (event as CustomEvent<{ scope?: AuthScope }>).detail
if (detail?.scope !== scope || stopped) return
closeSource()
connect()
}
function disconnect() {
stopped = true
closeSource()
window.removeEventListener('auth-storage-changed', handleAuthStorageChanged)
}
window.addEventListener('auth-storage-changed', handleAuthStorageChanged)
onBeforeUnmount(() => { onBeforeUnmount(() => {
disconnect() disconnect()
}) })