修复客服后台聊天无实时刷新
后台 token 仅存 httpOnly cookie,localStorage 无 access token, useChatSSE 在空 token 时提前 return,导致后台 SSE 从未建立连接, 只有手动刷新才出消息。 - 前端:后台 SSE 不再因空 token 返回,去掉 query token, 改用 withCredentials 依赖 cookie 鉴权 - 后端:新增 Hub.NotifyAllAdmins,新消息与新会话广播给所有在线客服, 使「未分配 / 全部」视图实时刷新(参与者客服重复收到由前端按 id 去重) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
9271f47553
commit
cc1c0d33de
@@ -136,7 +136,7 @@ func (r *Repository) SendMessage(ctx context.Context, principal Principal, conve
|
|||||||
// 推送新消息事件给会话中的在线参与者
|
// 推送新消息事件给会话中的在线参与者
|
||||||
if r.hub != nil {
|
if r.hub != nil {
|
||||||
msg := items[0]
|
msg := items[0]
|
||||||
r.hub.NotifyConversation(conversationID, &chathub.ChatEvent{
|
event := &chathub.ChatEvent{
|
||||||
Type: "new_message",
|
Type: "new_message",
|
||||||
ConversationID: conversationID,
|
ConversationID: conversationID,
|
||||||
Message: &chathub.MessageData{
|
Message: &chathub.MessageData{
|
||||||
@@ -151,7 +151,11 @@ func (r *Repository) SendMessage(ctx context.Context, principal Principal, conve
|
|||||||
AttachmentURLS: msg.AttachmentURLS,
|
AttachmentURLS: msg.AttachmentURLS,
|
||||||
CreatedAt: msg.CreatedAt.Format(time.RFC3339),
|
CreatedAt: msg.CreatedAt.Format(time.RFC3339),
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
r.hub.NotifyConversation(conversationID, event)
|
||||||
|
// 同时广播给所有在线客服,使「未分配 / 全部」视图实时刷新;
|
||||||
|
// 参与者客服会重复收到,由前端按消息 id 去重。
|
||||||
|
r.hub.NotifyAllAdmins(event)
|
||||||
}
|
}
|
||||||
return &items[0], nil
|
return &items[0], nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,8 +102,11 @@ func (r *Repository) NotifyNewConversation(conversationID uint64) {
|
|||||||
if r.hub == nil {
|
if r.hub == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.hub.NotifyConversation(conversationID, &chathub.ChatEvent{
|
event := &chathub.ChatEvent{
|
||||||
Type: "conversation_updated",
|
Type: "conversation_updated",
|
||||||
ConversationID: conversationID,
|
ConversationID: conversationID,
|
||||||
})
|
}
|
||||||
|
r.hub.NotifyConversation(conversationID, event)
|
||||||
|
// 新会话(如未分配的平台咨询)需让所有在线客服的列表实时出现。
|
||||||
|
r.hub.NotifyAllAdmins(event)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,6 +135,25 @@ func (h *Hub) NotifyUser(pType string, pID uint64, event *ChatEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NotifyAllAdmins 向所有在线管理员(客服)推送事件。
|
||||||
|
// 客服工作台的「未分配 / 全部」视图会展示当前客服并非参与者的会话,
|
||||||
|
// 仅靠 NotifyConversation(只推参与者)无法实时刷新,故对所有在线客服广播。
|
||||||
|
func (h *Hub) NotifyAllAdmins(event *ChatEvent) {
|
||||||
|
h.mu.RLock()
|
||||||
|
defer h.mu.RUnlock()
|
||||||
|
for key, clients := range h.clients {
|
||||||
|
if key.Type != "admin" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for ch := range clients {
|
||||||
|
select {
|
||||||
|
case ch <- event:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MarshalEvent 将事件序列化为 SSE data 行。
|
// MarshalEvent 将事件序列化为 SSE data 行。
|
||||||
func MarshalEvent(event *ChatEvent) string {
|
func MarshalEvent(event *ChatEvent) string {
|
||||||
raw, _ := json.Marshal(event)
|
raw, _ := json.Marshal(event)
|
||||||
|
|||||||
@@ -40,10 +40,12 @@ export function useChatSSE(scope: AuthScope, endpoint: string) {
|
|||||||
function connect() {
|
function connect() {
|
||||||
if (stopped || source) return
|
if (stopped || source) return
|
||||||
const token = getAccessToken(scope)
|
const token = getAccessToken(scope)
|
||||||
if (!token) return
|
// 用户端通过 query token 鉴权;后台 token 存于 httpOnly cookie,
|
||||||
|
// localStorage 无 token,连接时依赖浏览器自动携带的 cookie,不能因空 token 提前返回。
|
||||||
|
if (scope !== 'admin' && !token) return
|
||||||
|
|
||||||
const url = `${endpoint}?token=${encodeURIComponent(token)}`
|
const url = scope === 'admin' ? endpoint : `${endpoint}?token=${encodeURIComponent(token)}`
|
||||||
source = new EventSource(url)
|
source = new EventSource(url, { withCredentials: true })
|
||||||
|
|
||||||
source.addEventListener('connected', () => {
|
source.addEventListener('connected', () => {
|
||||||
connected.value = true
|
connected.value = true
|
||||||
|
|||||||
Reference in New Issue
Block a user